Version 69

This commit is contained in:
Hydrus 2013-05-08 15:31:00 -05:00
parent bc47077773
commit 0ff02d3903
27 changed files with 2358 additions and 149 deletions

View File

@ -49,10 +49,10 @@
<p><img src="similar_gununus.png" /></p>
<h3>PIL errors</h3>
<p>At some point, you will probably encounter a PIL error when importing a file. PIL is the Python Image Library, the code I use to manipulate image files. Some files are kooky, and just won't load with it. I can't fix these errors, since PIL is not mine. It would take me thousands of hours to write my own image library, and even then it would have its own odd errors. Just gotta deal with it.</p>
<p>If the PIL error'ing file is one you particularly care about, I suggest you import it into photoshop or similar and save it again. Photoshop should be clever enough to parse the file's weirdness, and then it'll hopefully save again to a simpler format which PIL, and hence the client, will be able to understand.</p>
<p>If the PIL error'ing file is one you particularly care about, I suggest you import it into photoshop or similar and save it again. Photoshop should be clever enough to parse the file's weirdness, and then it'll hopefully save again to a simpler format that PIL, and hence the client, will be able to understand.</p>
<h3>busted up gifs</h3>
<p>Animated gifs are a real pain in the neck. There are several loopholes in the standard that permit odd palettes and colourspaces, and PIL has a hard time parsing it all.</p>
<p>So, some gifs will have a coloured first frame but grey frames thereafter; or they will have odd washy noise all over; or they will just be black. The file isn't broken, but lib is looking at it wrong. Every ten versions or so, I gather enough enthusiasm to fix a few more.</p>
<p>So, some gifs will have a coloured first frame but grey frames thereafter; or they will have odd washy noise all over; or they will just be black. The file isn't broken, the client is just looking at it wrong. Every ten versions or so, I gather enough enthusiasm to fix a few more.</p>
<h3>setting a password</h3>
<p>the client offers a very simple password system, enough to keep out noobs. You can set it at <i>database->set a password</i>. It will thereafter ask for the password every time you start the program, and will not open without it. However none of the database is encrypted, and someone with enough enthusiasm or a tool and access to your computer can still very easily see what files you have. The password is mainly to stop idle snoops checking your images if you are away from your machine.</p>
<h3>backing up</h3>

View File

@ -0,0 +1,32 @@
<html>
<head>
<title>advanced - parents</title>
<link href="hydrus.ico" rel="shortcut icon" />
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="content">
<p class="warning">This is a draft document. None of this code actually exists yet!</p>
<h3>what's the problem?</h3>
<p>Tags often fall into certain heirarchies. Files with one particular tag often always have certain other tags, and it is annoying and time-consuming to add them all individually every time.</p>
<p>For example, whenever you tag a file with <i>ak-47</i>, you probably also want to tag it <i>assault rifle</i>, and maybe even <i>firearm</i> as well.</p>
<p><img src="tag_parents_venn.png" /></p>
<p>Another time, you might tag a file <i>character:eddard stark</i>, and then also have to type in <i>house stark</i> and then <i>series:game of thrones</i>. (you might also think <i>series:game of thrones</i> should actually be <i>series:a song of ice and fire</i>, but that is an issue for <a href="advanced_siblings.html">siblings</a>)</p>
<p>Drawing more relationships would make a significantly more complicated venn diagram, so let's draw it like a family tree instead:</p>
<p><img src="tag_parents_got.png" /></p>
<p>If we were to search for the tag <i>house stark</i>, we would expect our results to include all the images of any of the Starks. Even so, it is tedious to have to remember to tag every image of a character with their house affiliation or other parent tags. You may not even remember.</p>
<p>Wouldn't it be great if we could add these sorts of tags at the same time?</p>
<h3>tag parents</h3>
<p>Let's define the relationship 'tag P is tag C's parent' as saying that tag P is the semantic superset of tag C. All files that have C should have P. Let's say that when the user tries to add a tag to a file, the tag's parents are added recursively.</p>
<p>Let's expand our weapon example:</p>
<p><img src="tag_parents_firearms.png" /></p>
<p>In that graph, adding <i>ar-15</i> to a file would also add <i>semi-automatic rifle</i>, <i>rifle</i>, and <i>firearm</i>. Searching for <i>handgun</i> would everything with <i>m1911</i> and <i>smith and wesson model 10</i>.</p>
<p>Although characters are an obvious and convenient step, we need to be careful to make sure we only link characters to series if they have unique names, and only if they are <i>inextricably</i> linked with their series. Series with crossovers and spinoffs (like CSI) make this difficult.</p>
<h3>how you do it</h3>
<p>services->manage tag parents</p>
<p>picture of dialog</p>
<p>when you add a tag, all of its parents will be added. This is transitive, so grandparents and great-grandparents and so on are also added.</p>
<p>parents are not cast-iron. from time to time, even the simplest and most seemingly-obvious of relationships can be broken. Rule 63 will often break a 'character x is a male' relationship, for example. so, you can de-pend and petition parents without the children being similarly removed. (screenshot?)</p>
</div>
</body>
</html>

View File

@ -0,0 +1,52 @@
<html>
<head>
<title>advanced - siblings</title>
<link href="hydrus.ico" rel="shortcut icon" />
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="content">
<p class="warning">This is another area under active development right now. I will expand this as I add remote siblings.</p>
<h3>what's the problem?</h3>
<p>It is a common problem, in communal tagging, for ambiguity to arise when different tags with the same meaning are applied non-uniformly.</p>
<p>A great example is in Japanese names, which are natively written surname first. <i>character:ayanami rei</i> and <i>character:rei ayanami</i> have the same meaning, but different users will use one, or the other, or even both.</p>
<p>Other examples are tiny syntactic changes, common misspellings, and unique acronyms:
<ul>
<li><i>smiling</i> and <i>smile</i></li>
<li><i>staring at camera</i> and <i>looking at viewer</i></li>
<li><i>pokemon</i> and <i>pokémon</i></li>
<li><i>jersualem</i> and <i>jerusalem</i></li>
<li><i>lotr</i> and <i>series:the lord of the rings</i></li>
<li><i>marimite</i> and <i>series:maria-sama ga miteru</i></li>
<li><i>ishygddt</i> and <i>i sure hope you guys don't do that</i></li>
</ul>
<p>A particular repository may have a preferred standard, but it is not easy to guarantee that all the users will know exactly which tag to use or search for.</p>
<p>After some time, you get this:</p>
<p><img src="tag_siblings_venn_1.png" /></p>
<p>Without continual intervention by janitors or other experienced users to make sure y&#x2287;x (i.e. making the yellow circle entirely overlap the blue by manually giving y to everything with x), searches can only return x (blue circle) or y (yellow circle) or x&#x2229;y (the lens-shaped overlap). What we really want is x&#x222a;y (both circles).</p>
<p>So, how do we fix this problem?</p>
<h3>tag siblings</h3>
<p>Let's define a relationship, <b>A->B</b>, that means that any time we would normally see or use tag A or tag B, we will instead get the union of tag A and tag B, and this union will be labelled as tag B. In short, we fold A into B, unioning the two tag spaces.</p>
<p>Note that this relationship implies that B is in some way 'better' than A. For instance, I might say that I prefer <i>character:ayanami rei</i>, as I think Japanese characters should probably get their names in the Japanese fashion. So, I would say <i>character:rei ayanami</i>-><i>character:ayanami rei</i>. This means that whenever I might see or type the Western arrangement of Rei's name, I'll automatically get the Japanese instead. Any search will return files with either tag as if they were all tagged <i>character:ayanami rei</i>.</p>
<p><img src="tag_siblings_venn_2.png" /></p>
<h3>ok, I understand; now confuse me</h3>
<p>This relationship is transitive, which means as well as saying A->B, you can also say B->C, which implies A->C and B->C.</p>
<p><img src="tag_siblings_usa.png" /></p>
<p>You can also have an A->C and B->C that does not include A->B.</p>
<p><img src="tag_siblings_what_is_a_man.png" /></p>
<p>The outcome of these two arrangements is the same (everything ends up as C), but the underlying semantics are a little different if you ever want to edit them.</p>
<p>Many complicated arrangements are possible:</p>
<p><img src="tag_siblings_yo_dawg.png" /></p>
<p>Note that if you say A->B, you cannot say A->C; the left-hand side can only go to one. The right-hand side can receive many. The client will stop you from constructing loops.</p>
<h3>how you do it</h3>
<p>It is easy. Just open <i>services->manage tag siblings</i>, and add a few.</p>
<p><img src="tag_siblings_dialog.png" /></p>
<p>The client will automatically collapse the tagspace to whatever you set. It'll even work with autocomplete, so when you start to type in 'rei ayanami' in search, it will show <i>character:ayanami rei</i> instead:</p>
<p><img src="tag_siblings_rei.png" /></p>
<p>It will not collapse in the write dialog. You will be able to add or remove A as normal, but it will be written in some form of "A (B)" to let you know that, ultimately, the tag will end up displaying in the main gui as B:</p>
<p><img src="tag_siblings_ac_write.png" /></p>
<p>Although the client may present A as B, it will secretly remember A! You can remove the association A->B, and everything will return to how it was. <b>No information is lost at any point.</b></p>
<p>At the moment, this only supports local siblings. Just like tags, don't go crazy and try to do everything yourself. Have a play with local, but please wait for remote support so your efforts are shared.</p>
</div>
</body>
</html>

View File

@ -8,6 +8,33 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 69</h3></li>
<ul>
<li>first dialog for tag siblings</li>
<li>tag siblings db table</li>
<li>tag siblings manager in controller</li>
<li>tag siblings display in tagsboxcpp</li>
<li>tag siblings display in tagsboxflat</li>
<li>tag siblings display in tagsboxmanage</li>
<li>tag siblings display in tagsboxactiveonly, via A/C dropdowns, for both read and write</li>
<li>top result sibling switcheroo in A/C write</li>
<li>A/C db fetch now does siblings too</li>
<li>db tag search does siblings</li>
<li>siblings help, with some nice mini-charts</li>
<li>I fixed collections, which were typo-broke; I'll make sure it doesn't happen again</li>
<li>more granular subs error handling, meaning individual file failures won't crash an entire sub</li>
<li>individual subs can now be paused</li>
<li>e621 fixed for real this time</li>
<li>A/C improvement that slows tag updates a little but should stop A/C lag after an update</li>
<li>as a result, trying out dropping the CPU-intensive fatten_ac_cache maintenance call</li>
<li>A/C read will now update system preds every time you click on it, so inbex/archive counts will stay accurate</li>
<li>A/C "all known files + tags" will no longer show the mega-laggy total file count</li>
<li>a list -> tuple convenience fix in sanelistctrl</li>
<li>if you don't have any pixiv credentials set up, you will now no longer get the option to start downloading pixiv stuff</li>
<li>fixed a tiny typo in the thumbnail resizer that made it wait far more politely than was intended</li>
<li>slight change to ratio system pred that fixes some lockups, sometimes, I think</li>
<li>fullscreen flash and video will get a pixel of whitespace on the right</li>
</ul>
<li><h3>version 68</h3></li>
<ul>
<li>fullscreen view now takes addmediaresult</li>

View File

@ -24,7 +24,8 @@
<li><a href="registration_keys.html">registering new accounts</a></li>
<li><a href="access_keys.html">access keys to my server's services</a></li>
<li><a href="tagging_schema.html">thoughts on a public tagging schema</a></li>
<li><a href="advanced.html">advanced usage</a></li>
<li><a href="advanced.html">advanced usage - general</a></li>
<li><a href="advanced_siblings.html">advanced usage - tag siblings</a></li>
<li><a href="privacy.html">privacy</a></li>
<li><a href="server.html">setting up your own server</a></li>
<li><a href="running_from_source.html">running a client or server from source</a></li>

722
help/tag_parents.svg Normal file
View File

@ -0,0 +1,722 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg2"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="tag_parents.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="231.77811"
inkscape:cy="465.57944"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1300"
inkscape:window-height="964"
inkscape:window-x="1327"
inkscape:window-y="858"
inkscape:window-maximized="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect2985"
width="374.87128"
height="264.81741"
x="15.099963"
y="17.913582"
inkscape:export-filename="C:\code\Hydrus\help\tag_parents_venn.png"
inkscape:export-xdpi="146.31"
inkscape:export-ydpi="146.31" />
<path
sodipodi:type="arc"
style="color:#000000;fill:#00ff00;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2.64672065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2987"
sodipodi:cx="194.95944"
sodipodi:cy="191.71222"
sodipodi:rx="147.48227"
sodipodi:ry="147.48227"
d="m 342.44171,191.71222 a 147.48227,147.48227 0 1 1 -294.964537,0 147.48227,147.48227 0 1 1 294.964537,0 z"
transform="matrix(0.75581183,0,0,0.75549245,107.86342,5.4851574)" />
<path
sodipodi:type="arc"
style="color:#000000;fill:#00ffff;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2.64672065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2989"
sodipodi:cx="167.68532"
sodipodi:cy="169.48886"
sodipodi:rx="69.700523"
sodipodi:ry="69.700523"
d="m 237.38584,169.48886 a 69.700523,69.700523 0 1 1 -139.401045,0 69.700523,69.700523 0 1 1 139.401045,0 z"
transform="matrix(0.75581183,0,0,0.75549245,93.738942,-7.8701796)" />
<path
sodipodi:type="arc"
style="color:#000000;fill:#ff00ff;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2.64672065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2991"
sodipodi:cx="148.9975"
sodipodi:cy="157.8721"
sodipodi:rx="24.748737"
sodipodi:ry="24.748737"
d="m 173.74623,157.8721 a 24.748737,24.748737 0 1 1 -49.49747,0 24.748737,24.748737 0 1 1 49.49747,0 z"
transform="matrix(0.75581183,0,0,0.75549245,95.265916,-9.3965047)" />
<text
xml:space="preserve"
style="font-size:13.60173798px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="43.33976"
y="107.22626"
id="text3761"
sodipodi:linespacing="125%"
transform="scale(1.0002113,0.9997887)"><tspan
sodipodi:role="line"
id="tspan3763"
x="43.33976"
y="107.22626">ak-47</tspan></text>
<text
xml:space="preserve"
style="font-size:13.60173798px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="26.546631"
y="144.62915"
id="text3765"
sodipodi:linespacing="125%"
transform="scale(1.0002113,0.9997887)"><tspan
sodipodi:role="line"
id="tspan3767"
x="26.546631"
y="144.62915">assault rifle</tspan></text>
<text
xml:space="preserve"
style="font-size:13.60173798px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="35.706516"
y="188.1386"
id="text3769"
sodipodi:linespacing="125%"
transform="scale(1.0002113,0.9997887)"><tspan
sodipodi:role="line"
id="tspan3771"
x="35.706516"
y="188.1386">firearm</tspan></text>
<text
xml:space="preserve"
style="font-size:13.60173798px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="20.440039"
y="256.0744"
id="text3773"
sodipodi:linespacing="125%"
transform="scale(1.0002113,0.9997887)"><tspan
sodipodi:role="line"
id="tspan3775"
x="20.440039"
y="256.0744">all ak-47s are assault rifles</tspan><tspan
sodipodi:role="line"
x="20.440039"
y="273.07657"
id="tspan3777">all assault rifles are firearms</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 202.15385,101.86147 -117.57673,0"
id="path3779"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 187.64763,140.78277 -87.03732,0"
id="path3781"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 175.43186,185.04619 -93.908681,0"
id="path3783"
inkscape:connector-curvature="0" />
<rect
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3785"
width="413.79507"
height="256.16187"
x="16.270475"
y="500.10254"
inkscape:export-filename="C:\code\Hydrus\help\tag_parents_firearms.png"
inkscape:export-xdpi="146.31"
inkscape:export-ydpi="146.31" />
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="176.80841"
y="516.01611"
id="text3787"
sodipodi:linespacing="125%"
transform="scale(0.99990979,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3789"
x="176.80841"
y="516.01611">firearm</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="105.77372"
y="578.70276"
id="text3791"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3793"
x="105.77372"
y="578.70276">rifle</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="25.647068"
y="616.71912"
id="text3795"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3797"
x="25.647068"
y="616.71912">assault rifle</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="212.80423"
y="575.19354"
id="text3799"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3801"
x="212.80423"
y="575.19354">shotgun</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="308.13751"
y="585.72113"
id="text3803"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3805"
x="308.13751"
y="585.72113">handgun</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="23.307581"
y="675.79053"
id="text3815"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3817"
x="23.307581"
y="675.79053">ak-47</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="36.174656"
y="703.86414"
id="text3819"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3821"
x="36.174656"
y="703.86414">ak-74</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="76.530373"
y="710.88257"
id="text3823"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3825"
x="76.530373"
y="710.88257">m16</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="107.6714"
y="698.70959"
id="text3827"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3829"
x="107.6714"
y="698.70959">m4</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="191.16414"
y="708.54297"
id="text3831"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3833"
x="191.16414"
y="708.54297">remington model 870</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="116.88616"
y="669.94189"
id="text3835"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3837"
x="116.88616"
y="669.94189">ar-15</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="295.85522"
y="675.79053"
id="text3839"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3841"
x="295.85522"
y="675.79053">smith &amp; wesson model 10</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="250.23575"
y="598.5882"
id="text3843"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3845"
x="250.23575"
y="598.5882">remington</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="338.55051"
y="614.37964"
id="text3847"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3849"
x="338.55051"
y="614.37964">smith and wesson</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="293.51581"
y="636.60449"
id="text3851"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3853"
x="293.51581"
y="636.60449">m1911</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 54.885403,605.66122 51.463557,-21.642"
id="path3855"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 123.89337,568.2264 66.66874,-47.37844"
id="path3857"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 230.32944,564.13196 -38.59765,-43.284"
id="path3859"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 328.57808,575.83035 194.65588,521.43289"
id="path3861"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 310.44894,625.54846 325.65399,589.8684"
id="path3863"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 351.38583,663.56817 c 0,-2.92456 -25.147,-72.52993 -25.147,-72.52993"
id="path3865"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 374.19358,617.3596 -22.22291,45.62368"
id="path3867"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 239.68643,695.73873 c -1.16957,-2.33967 -9.35699,-116.39886 -9.35699,-116.39886"
id="path3869"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 276.52967,602.1517 -35.08872,94.17197"
id="path3871"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 125.64778,620.86911 C 125.06306,618.52944 113.3667,582.84937 113.3667,582.84937"
id="path3873"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 113.3667,689.3046 60.733545,621.45403"
id="path3875"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 85.880569,699.83317 56.055026,624.37862"
id="path3877"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 49.622089,692.22922 2.33926,-69.60535"
id="path3879"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 36.756192,664.15312 49.622089,621.45403"
id="path3881"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="134.43222"
y="684.56348"
id="text3883"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3885"
x="134.43222"
y="684.56348">winchester model 70</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 184.71405,606.83105 117.46047,582.26446"
id="path3887"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="75.360664"
y="632.51044"
id="text3889"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3891"
x="75.360664"
y="632.51044">semi-automatic rifle</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="153.14783"
y="617.88885"
id="text3893"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
id="tspan3895"
x="153.14783"
y="617.88885">bolt-action rifle</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 125.06306,636.66193 2.33924,23.98168"
id="path3897"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 187.0533,621.45403 -3.50893,52.05778"
id="path3899"
inkscape:connector-curvature="0" />
<rect
style="color:#000000;fill:none;stroke:#001d00;stroke-width:1.99999964;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3901"
width="414.52017"
height="155.0789"
x="15.4862"
y="304.98154"
inkscape:export-filename="C:\code\Hydrus\help\tag_parents_got.png"
inkscape:export-xdpi="146.31"
inkscape:export-ydpi="146.31" />
<text
xml:space="preserve"
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="170.32401"
y="323.65546"
id="text3903"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3905"
x="170.32401"
y="323.65546">series:game of thrones</tspan></text>
<text
xml:space="preserve"
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="26.047758"
y="367.05563"
id="text3907"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3909"
x="26.047758"
y="367.05563">house stark</tspan></text>
<text
xml:space="preserve"
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="233.73761"
y="367.05563"
id="text3911"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3913"
x="233.73761"
y="367.05563">house lannister</tspan></text>
<text
xml:space="preserve"
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="118.00807"
y="367.05563"
id="text3915"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3917"
x="118.00807"
y="367.05563">house baratheon</tspan></text>
<text
xml:space="preserve"
style="font-size:10.45069885px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="342.75174"
y="366.01718"
id="text3919"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3921"
x="342.75174"
y="366.01718">house targaryen</tspan></text>
<text
xml:space="preserve"
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="332.19489"
y="414.56122"
id="text3923"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3925"
x="332.19489"
y="414.56122">character:daenerys targaryen</tspan></text>
<text
xml:space="preserve"
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="19.009895"
y="407.52341"
id="text3927"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3929"
x="19.009895"
y="407.52341">character:eddard stark</tspan></text>
<text
xml:space="preserve"
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="39.53701"
y="416.9072"
id="text3931"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3933"
x="39.53701"
y="416.9072">character:sansa stark</tspan></text>
<text
xml:space="preserve"
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="55.372204"
y="426.29102"
id="text3935"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3937"
x="55.372204"
y="426.29102">character:bran stark</tspan></text>
<text
xml:space="preserve"
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="220.17557"
y="408.10989"
id="text3939"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3941"
x="220.17557"
y="408.10989">character:cersei lannister</tspan></text>
<text
xml:space="preserve"
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="242.46216"
y="416.32071"
id="text3943"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3945"
x="242.46216"
y="416.32071">character:jaime lannister</tspan></text>
<text
xml:space="preserve"
style="font-size:6.96713257px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="117.53999"
y="407.52335"
id="text3947"
sodipodi:linespacing="125%"
transform="scale(0.99969264,1.0003075)"><tspan
sodipodi:role="line"
id="tspan3949"
x="117.53999"
y="407.52335">character:robert baratheon</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 54.768874,357.1951 C 59.459342,356.02176 220.6942,326.10165 220.6942,326.10165"
id="path3951"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 161.47703,355.43509 59.80348,-28.74678"
id="path3953"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 271.70305,357.78177 c -3.51785,-1.17333 -49.83623,-31.09346 -49.83623,-31.09346"
id="path3955"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 384.2743,357.78177 223.62575,326.10165"
id="path3957"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 41.870082,400.60863 55.355186,370.6885"
id="path3959"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 98.155705,409.99533 C 96.396789,407.06199 57.114114,370.10184 57.114114,370.10184"
id="path3961"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 112.22711,417.62203 c 0,-2.93335 -51.595154,-48.10688 -51.595154,-48.10688"
id="path3963"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 157.95918,399.43528 0,-28.16012"
id="path3965"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 255.87272,399.43528 c 0,-4.10667 9.96725,-27.57345 9.96725,-27.57345"
id="path3967"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 304.53633,407.06199 269.35782,371.86183"
id="path3969"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 381.34275,403.54197 3.51786,-31.68014"
id="path3971"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-size:9.28950977px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="224.76657"
y="445.36945"
id="text3973"
sodipodi:linespacing="125%"
transform="scale(0.99969262,1.0003075)"><tspan
sodipodi:role="line"
x="224.76657"
y="445.36945"
id="tspan3977">e.g. cersei lannister is a member of house lannister, which occurs in the series game of thrones</tspan></text>
<text
xml:space="preserve"
style="font-size:10.4217844px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="217.65623"
y="734.31873"
id="text3979"
sodipodi:linespacing="125%"
transform="scale(0.99990977,1.0000902)"><tspan
sodipodi:role="line"
x="217.65623"
y="734.31873"
id="tspan3999">searching for any term should generally return all the files tagged with anything below</tspan><tspan
sodipodi:role="line"
x="217.65623"
y="747.34595"
id="tspan3993">note that a tag can have more than one parent</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
help/tag_parents_got.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
help/tag_parents_venn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

487
help/tag_siblings.svg Normal file
View File

@ -0,0 +1,487 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg2"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="tag_siblings.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="199.89168"
inkscape:cy="212.99738"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1038"
inkscape:window-height="1015"
inkscape:window-x="1989"
inkscape:window-y="820"
inkscape:window-maximized="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="arc"
style="color:#000000;fill:#ffff00;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2987"
sodipodi:cx="319.71329"
sodipodi:cy="175.04469"
sodipodi:rx="107.58125"
sodipodi:ry="107.58125"
d="m 427.29454,175.04469 a 107.58125,107.58125 0 1 1 -215.16251,0 107.58125,107.58125 0 1 1 215.16251,0 z"
transform="translate(-5.0507632,-36.870568)" />
<path
sodipodi:type="arc"
style="color:#000000;fill:#0000ff;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="path2989"
sodipodi:cx="124.24876"
sodipodi:cy="148.27565"
sodipodi:rx="85.862968"
sodipodi:ry="85.862968"
d="m 210.11172,148.27565 a 85.862968,85.862968 0 1 1 -171.725932,0 85.862968,85.862968 0 1 1 171.725932,0 z"
transform="translate(59.599,-10.101525)" />
<text
xml:space="preserve"
style="font-size:10px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="18.182747"
y="39.17918"
id="text3759"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3761"
x="18.182747"
y="39.17918"
style="font-size:14px">character:rei ayanami, x</tspan></text>
<text
xml:space="preserve"
style="font-size:10px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="385.58438"
y="40.696476"
id="text3759-1"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3761-7"
x="385.58438"
y="40.696476"
style="font-size:14px">character:ayanami rei, y</tspan></text>
<rect
style="color:#000000;fill:none;stroke:#001d00;stroke-width:1.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3784"
width="553.5636"
height="321.22849"
x="1.0101523"
y="0.79338306"
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_venn_1.png"
inkscape:export-xdpi="146.31"
inkscape:export-ydpi="146.31" />
<text
xml:space="preserve"
style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="277.3613"
y="286.66656"
id="text3786"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3788"
x="277.3613"
y="286.66656">some files only have x, some only have y, some have both</tspan><tspan
sodipodi:role="line"
x="277.3613"
y="305.54153"
id="tspan3835">we can typically only get xy if we search for y after making y⊇x, rendering x pointless</tspan></text>
<path
style="color:#000000;fill:#ffff00;fill-opacity:0.24705882;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 314.65625 396.28125 C 282.09122 396.28125 252.9155 410.73365 233.1875 433.59375 C 219.22457 423.76775 202.21489 418 183.84375 418 C 136.42294 418 98 456.42294 98 503.84375 C 98 551.26456 136.42294 589.71875 183.84375 589.71875 C 202.21879 589.71875 219.22291 583.92355 233.1875 574.09375 C 252.9155 596.95385 282.09122 611.4375 314.65625 611.4375 C 374.07174 611.4375 422.25 563.25924 422.25 503.84375 C 422.25 444.42826 374.07174 396.28125 314.65625 396.28125 z "
id="path2987-4" />
<text
xml:space="preserve"
style="font-size:10px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="385.58435"
y="406.3717"
id="text3759-1-8"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3761-7-8"
x="385.58435"
y="406.3717"
style="font-size:14px">character:ayanami rei, y</tspan></text>
<rect
style="color:#000000;fill:none;stroke:#001d00;stroke-width:1.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3784-2"
width="553.5636"
height="321.22849"
x="1.0101514"
y="366.46863"
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_venn_2.png"
inkscape:export-xdpi="146.31"
inkscape:export-ydpi="146.31" />
<text
xml:space="preserve"
style="font-size:14px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="256.71335"
y="652.34174"
id="text3786-4"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3788-5"
x="256.71335"
y="652.34174">all the files that used to have x or y now only have y</tspan><tspan
sodipodi:role="line"
x="256.71335"
y="669.84174"
id="tspan3833">via database magic, any queries for x return y</tspan></text>
<rect
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3837"
width="262.13458"
height="26.263969"
x="13.417028"
y="748.92218"
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_usa.png"
inkscape:export-xdpi="146.31"
inkscape:export-ydpi="146.31" />
<g
id="g3957"
transform="matrix(0.5,0,0,0.5,29.43228,358.76646)">
<path
inkscape:connector-curvature="0"
id="path3866"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="19.47794"
y="764.57959"
id="text3962"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3964"
x="19.47794"
y="764.57959">murrikkka</tspan></text>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="88.865494"
y="765.41455"
id="text3966"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3968"
x="88.865494"
y="765.41455">united states</tspan></text>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="172.51604"
y="765.46948"
id="text3970"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3972"
x="172.51604"
y="765.46948">united states of america</tspan></text>
<g
transform="matrix(0.5,0,0,0.5,43.245636,403.47959)"
id="g3957-2">
<path
inkscape:connector-curvature="0"
id="path3866-1"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-6"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1-8"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
transform="matrix(0.5,0,0,0.5,126.89619,403.47959)"
id="g3957-5">
<path
inkscape:connector-curvature="0"
id="path3866-7"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-61"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1-89"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<rect
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3837-2"
width="82.832489"
height="62.124378"
x="12.911961"
y="790.84351"
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_what_is_a_man.png"
inkscape:export-xdpi="146.31"
inkscape:export-ydpi="146.31" />
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="18.972874"
y="806.50092"
id="text3962-7"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3964-9"
x="18.972874"
y="806.50092">boy</tspan></text>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="18.968479"
y="843.32935"
id="text3966-5"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3968-4"
x="18.968479"
y="843.32935">man</tspan></text>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="66.862312"
y="826.34912"
id="text3970-3"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3972-1"
x="66.862312"
y="826.34912">male</tspan></text>
<g
transform="matrix(0.48296291,0.12940952,-0.12940952,0.48296291,114.52425,457.13986)"
id="g3957-2-2">
<path
inkscape:connector-curvature="0"
id="path3866-1-3"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-6-3"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1-8-4"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
transform="matrix(0.48296291,-0.12940952,0.12940952,0.48296291,-71.198483,495.61989)"
id="g3957-5-1">
<path
inkscape:connector-curvature="0"
id="path3866-7-1"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-61-3"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1-89-8"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<rect
style="color:#000000;fill:none;stroke:#001d00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
id="rect3837-2-7"
width="347.49246"
height="69.837944"
x="118.97797"
y="791.60114"
inkscape:export-filename="C:\code\Hydrus\help\tag_siblings_yo_dawg.png"
inkscape:export-xdpi="146.31"
inkscape:export-ydpi="146.31" />
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="124.02876"
y="812.30927"
id="text3962-7-4"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3964-9-2"
x="124.02876"
y="812.30927">Calvin Broadus</tspan></text>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="333.09293"
y="811.36224"
id="text3966-5-7"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3968-4-7"
x="333.09293"
y="811.36224">snoop dogg</tspan></text>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="222.1778"
y="811.36224"
id="text3970-3-9"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3972-1-3"
x="222.1778"
y="811.36224">snoop doggy dogg</tspan></text>
<g
transform="matrix(0.35355339,-0.35355339,0.35355339,0.35355339,128.03402,599.30536)"
id="g3957-5-1-5">
<path
inkscape:connector-curvature="0"
id="path3866-7-1-0"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-61-3-2"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1-89-8-8"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="416.97293"
y="811.41498"
id="text4134"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4136"
x="416.97293"
y="811.41498">snoop lion</tspan></text>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="335.1506"
y="848.42224"
id="text4138"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4140"
x="335.1506"
y="848.42224">dj snoopadelic</tspan></text>
<g
transform="matrix(0.5,0,0,0.5,368.973,450.32161)"
id="g3957-5-6">
<path
inkscape:connector-curvature="0"
id="path3866-7-0"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-61-2"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1-89-4"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
transform="matrix(0.5,0,0,0.5,285.09299,450.32161)"
id="g3957-5-8">
<path
inkscape:connector-curvature="0"
id="path3866-7-6"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-61-5"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1-89-0"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
transform="matrix(0.5,0,0,0.5,174.17783,450.32161)"
id="g3957-5-9">
<path
inkscape:connector-curvature="0"
id="path3866-7-00"
d="m 40.47483,717.37932 35.197325,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-61-6"
d="m 65.156091,706.09888 12.0982,12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3868-1-89-1"
d="m 65.025985,728.96696 12.0982,-12.09821"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
help/tag_siblings_rei.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
help/tag_siblings_usa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -604,10 +604,14 @@ class AutocompleteMatchesCounted():
class AutocompleteMatchesPredicates():
def __init__( self, predicates ):
def __init__( self, predicates, do_siblings_collapse = True ):
self._predicates = predicates
siblings_manager = wx.GetApp().GetTagSiblingsManager()
if do_siblings_collapse: self._predicates = siblings_manager.CollapsePredicates( self._predicates )
def cmp_func( x, y ): return cmp( x.GetCount(), y.GetCount() )
self._predicates.sort( cmp = cmp_func, reverse = True )
@ -1902,7 +1906,7 @@ class FileSystemPredicates():
def OkFirstRound( self, width, height ):
if len( self._predicates[ self.RATIO ] ) > 0 and ( width is None or height is None ): return False
if len( self._predicates[ self.RATIO ] ) > 0 and ( width is None or height is None or width == 0 or height == 0): return False
if False in ( function( float( width ) / float( height ), arg ) for ( function, arg ) in self._predicates[ self.RATIO ] ): return False
@ -2621,6 +2625,214 @@ class ServiceRemoteRestrictedDepotMessage( ServiceRemoteRestrictedDepot ):
def Decrypt( self, encrypted_message ): return HydrusMessageHandling.UnpackageDeliveredMessage( encrypted_message, self._private_key )
class TagSiblingsManager():
def __init__( self ):
self._tag_service_precedence = wx.GetApp().Read( 'tag_service_precedence' )
# I should offload this to a thread (rather than the gui thread), and have an event to say when it is ready
self._RefreshSiblings()
self._lock = threading.Lock()
HC.pubsub.sub( self, 'RefreshSiblings', 'notify_new_siblings' )
def _RefreshSiblings( self ):
unprocessed_siblings = wx.GetApp().Read( 'tag_siblings' )
t_s_p = list( self._tag_service_precedence )
t_s_p.reverse()
processed_siblings = {}
for service_identifier in t_s_p:
if service_identifier in unprocessed_siblings: processed_siblings.update( unprocessed_siblings[ service_identifier ] )
# now to collapse chains
# A -> B and B -> C goes to A -> C and B -> C
self._siblings = {}
for ( old_tag, new_tag ) in processed_siblings.items():
# adding A -> B
if new_tag in self._siblings: # B -> C already added, so add A -> C
new_tag = self._siblings[ new_tag ]
self._siblings[ old_tag ] = new_tag
else:
while new_tag in processed_siblings: # while B -> C, C -> D, D -> E in preprocessed list, pursue endpoint F
new_tag = processed_siblings[ new_tag ]
if new_tag == old_tag: break # we have a loop!
if old_tag != new_tag: self._siblings[ old_tag ] = new_tag
self._reverse_lookup = collections.defaultdict( list )
for ( old_tag, new_tag ) in self._siblings.items(): self._reverse_lookup[ new_tag ].append( old_tag )
HC.pubsub.pub( 'new_siblings_gui' )
def GetAutocompleteSiblings( self, half_complete_tag ):
key_based_matching_values = { self._siblings[ key ] for key in self._siblings.keys() if HC.SearchEntryMatchesTag( half_complete_tag, key ) }
value_based_matching_values = { value for value in self._siblings.values() if HC.SearchEntryMatchesTag( half_complete_tag, value ) }
matching_values = key_based_matching_values.union( value_based_matching_values )
# all the matching values have a matching sibling somewhere in their network
# so now fetch the networks
lists_of_matching_keys = [ self._reverse_lookup[ value ] for value in matching_values ]
matching_keys = itertools.chain.from_iterable( lists_of_matching_keys )
matches = matching_values.union( matching_keys )
return matches
def GetSibling( self, tag ):
with self._lock:
if tag in self._siblings: return self._siblings[ tag ]
else: return None
def GetAllSiblings( self, tag ):
with self._lock:
if tag in self._siblings:
new_tag = self._siblings[ tag ]
elif tag in self._reverse_lookup: new_tag = tag
else: return [ tag ]
all_siblings = list( self._reverse_lookup[ new_tag ] )
all_siblings.append( new_tag )
return all_siblings
def RefreshSiblings( self ):
with self._lock: self._RefreshSiblings()
def CollapseCountedTagList( self, tag_list ):
with self._lock:
results = collections.Counter()
for ( tag, count ) in tag_list:
if tag in self._siblings: tag = self._siblings[ tag ]
results[ tag ] += count
results = results.items()
return results
def CollapsePredicates( self, predicates ):
with self._lock:
results = [ predicate for predicate in predicates if predicate.GetPredicateType() != HC.PREDICATE_TYPE_TAG ]
tag_predicates = [ predicate for predicate in predicates if predicate.GetPredicateType() == HC.PREDICATE_TYPE_TAG ]
tags_to_predicates = { predicate.GetTag() : predicate for predicate in predicates if predicate.GetPredicateType() == HC.PREDICATE_TYPE_TAG }
tags = tags_to_predicates.keys()
tags_to_include_in_results = set()
for tag in tags:
if tag in self._siblings:
old_tag = tag
old_predicate = tags_to_predicates[ old_tag ]
new_tag = self._siblings[ old_tag ]
if new_tag not in tags_to_predicates:
( old_operator, old_tag ) = old_predicate.GetValue()
new_predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( old_operator, new_tag ), 0 )
tags_to_predicates[ new_tag ] = new_predicate
tags_to_include_in_results.add( new_tag )
new_predicate = tags_to_predicates[ new_tag ]
count = old_predicate.GetCount()
new_predicate.AddToCount( count )
else: tags_to_include_in_results.add( tag )
results.extend( [ tags_to_predicates[ tag ] for tag in tags_to_include_in_results ] )
return results
def CollapseTagList( self, tags ):
with self._lock: return { self._siblings[ tag ] if tag in self._siblings else tag for tag in tags }
def CollapseTagsToCount( self, tags_to_count ):
with self._lock:
results = collections.Counter()
for ( tag, count ) in tags_to_count.items():
if tag in self._siblings: tag = self._siblings[ tag ]
results[ tag ] += count
return results
class ThumbnailCache():
def __init__( self, db, options ):

View File

@ -18,6 +18,17 @@ ID_MAINTENANCE_EVENT_TIMER = wx.NewId()
class Controller( wx.App ):
def _Read( self, action, *args, **kwargs ):
if action == 'options': return self._options
elif action == 'tag_service_precedence': return self._tag_service_precedence
elif action == 'file': return self._db.ReadFile( *args, **kwargs )
elif action == 'thumbnail': return self._db.ReadThumbnail( *args, **kwargs )
else: return self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
def _Write( self, action, priority, *args, **kwargs ): self._db.Write( action, priority, *args, **kwargs )
def ClearCaches( self ):
self._thumbnail_cache.Clear()
@ -120,10 +131,12 @@ class Controller( wx.App ):
def GetSessionKey( self, service_identifier ): return self._session_manager.GetSessionKey( service_identifier )
def GetWebCookies( self, name ): return self._web_session_manager.GetCookies( name )
def GetTagSiblingsManager( self ): return self._tag_siblings_manager
def GetThumbnailCache( self ): return self._thumbnail_cache
def GetWebCookies( self, name ): return self._web_session_manager.GetCookies( name )
def MaintainDB( self ):
now = int( time.time() )
@ -131,7 +144,8 @@ class Controller( wx.App ):
shutdown_timestamps = self.Read( 'shutdown_timestamps' )
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > 86400 * 5: self.Write( 'vacuum' )
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_FATTEN_AC_CACHE ] > 50000: self.Write( 'fatten_autocomplete_cache' )
# try no fatten, since we made the recent A/C changes
#if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_FATTEN_AC_CACHE ] > 50000: self.Write( 'fatten_autocomplete_cache' )
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_DELETE_ORPHANS ] > 86400 * 3: self.Write( 'delete_orphans' )
@ -157,6 +171,7 @@ class Controller( wx.App ):
self._session_manager = HydrusSessions.HydrusSessionManagerClient()
self._web_session_manager = CC.WebSessionManagerClient()
self._tag_siblings_manager = CC.TagSiblingsManager()
self.SetSplashText( 'caches' )
@ -233,15 +248,6 @@ class Controller( wx.App ):
def ProcessServerRequest( self, *args, **kwargs ): return self._db.ProcessRequest( *args, **kwargs )
def _Read( self, action, *args, **kwargs ):
if action == 'options': return self._options
elif action == 'tag_service_precedence': return self._tag_service_precedence
elif action == 'file': return self._db.ReadFile( *args, **kwargs )
elif action == 'thumbnail': return self._db.ReadThumbnail( *args, **kwargs )
else: return self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
def Read( self, action, *args, **kwargs ):
self._last_idle_time = int( time.time() )
@ -269,8 +275,6 @@ class Controller( wx.App ):
def _Write( self, action, priority, *args, **kwargs ): self._db.Write( action, priority, *args, **kwargs )
def Write( self, action, *args, **kwargs ):
self._last_idle_time = int( time.time() )

View File

@ -1289,7 +1289,21 @@ class TagDB():
result = c.execute( 'SELECT 1 FROM existing_tags WHERE namespace_id = ? AND tag_id = ?;', ( namespace_id, tag_id ) ).fetchone()
if result is None: c.execute( 'INSERT INTO existing_tags ( namespace_id, tag_id ) VALUES ( ?, ? );', ( namespace_id, tag_id ) )
if result is None:
c.execute( 'INSERT INTO existing_tags ( namespace_id, tag_id ) VALUES ( ?, ? );', ( namespace_id, tag_id ) )
tag_service_identifiers = self._GetServiceIdentifiers( c, ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
file_service_identifiers = self._GetServiceIdentifiers( c, ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ) )
tag_service_identifiers.add( HC.NULL_SERVICE_IDENTIFIER )
file_service_identifiers.add( HC.NULL_SERVICE_IDENTIFIER )
tag_service_ids = [ self._GetServiceId( c, service_identifier ) for service_identifier in tag_service_identifiers ]
file_service_ids = [ self._GetServiceId( c, service_identifier ) for service_identifier in file_service_identifiers ]
c.executemany( 'INSERT OR IGNORE INTO autocomplete_tags_cache ( file_service_id, tag_service_id, namespace_id, tag_id, current_count, pending_count ) VALUES ( ?, ?, ?, ?, ?, ? );', [ ( file_service_id, tag_service_id, namespace_id, tag_id, 0, 0 ) for ( tag_service_id, file_service_id ) in itertools.product( tag_service_ids, file_service_ids ) ] )
return ( namespace_id, tag_id )
@ -2050,7 +2064,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
return all_downloads
def _GetAutocompleteTags( self, c, tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER, file_service_identifier = HC.NULL_SERVICE_IDENTIFIER, half_complete_tag = '', include_current = True, include_pending = True ):
def _GetAutocompleteTags( self, c, tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER, file_service_identifier = HC.NULL_SERVICE_IDENTIFIER, half_complete_tag = '', include_current = True, include_pending = True, do_siblings_collapse = True ):
if tag_service_identifier == HC.NULL_SERVICE_IDENTIFIER:
@ -2139,6 +2153,16 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
results = { result for result in c.execute( 'SELECT namespace_id, tag_id FROM existing_tags WHERE ' + predicates_phrase + ';' ) }
# now fetch siblings, add to results set
siblings_manager = wx.GetApp().GetTagSiblingsManager()
all_associated_sibling_tags = siblings_manager.GetAutocompleteSiblings( half_complete_tag )
sibling_results = [ self._GetNamespaceIdTagId( c, sibling_tag ) for sibling_tag in all_associated_sibling_tags ]
results.update( sibling_results )
# fetch what we can from cache
cache_results = []
@ -2187,7 +2211,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
[ tags_to_count.update( { ( 1, tag_id ) : num_tags } ) for ( namespace_id, tag_id, num_tags ) in results if namespace_id != 1 and tag_id in unnamespaced_tag_ids ]
matches = CC.AutocompleteMatchesPredicates( [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', self._GetNamespaceTag( c, namespace_id, tag_id ) ), num_tags ) for ( ( namespace_id, tag_id ), num_tags ) in tags_to_count.items() if num_tags > 0 ] )
matches = CC.AutocompleteMatchesPredicates( [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', self._GetNamespaceTag( c, namespace_id, tag_id ) ), num_tags ) for ( ( namespace_id, tag_id ), num_tags ) in tags_to_count.items() if num_tags > 0 ], do_siblings_collapse = do_siblings_collapse )
return matches
@ -2254,7 +2278,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def _GetHashIdsFromTag( self, c, file_service_identifier, tag_service_identifier, tag, include_current_tags, include_pending_tags ):
hash_ids = set()
# this does siblings too!
if file_service_identifier == HC.NULL_SERVICE_IDENTIFIER:
@ -2304,17 +2328,26 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if ':' in tag:
hash_ids = set()
siblings_manager = wx.GetApp().GetTagSiblingsManager()
tags = siblings_manager.GetAllSiblings( tag )
for tag in tags:
( namespace, tag ) = tag.split( ':', 1 )
if include_current_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM namespaces, ( tags, ' + current_tables_phrase + ' USING ( tag_id ) ) USING ( namespace_id ) WHERE ' + current_predicates_phrase + 'namespace = ? AND tag = ?;', ( namespace, tag ) ) ] )
if include_pending_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM namespaces, ( tags, ' + pending_tables_phrase + ' USING ( tag_id ) ) USING ( namespace_id ) WHERE ' + pending_predicates_phrase + 'namespace = ? AND tag = ?;', ( namespace, tag ) ) ] )
else:
if include_current_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM tags, ' + current_tables_phrase + ' USING ( tag_id ) WHERE ' + current_predicates_phrase + 'tag = ?;', ( tag, ) ) ] )
if include_pending_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM tags, ' + pending_tables_phrase + ' USING ( tag_id ) WHERE ' + pending_predicates_phrase + 'tag = ?;', ( tag, ) ) ] )
if ':' in tag:
( namespace, tag ) = tag.split( ':', 1 )
if include_current_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM namespaces, ( tags, ' + current_tables_phrase + ' USING ( tag_id ) ) USING ( namespace_id ) WHERE ' + current_predicates_phrase + 'namespace = ? AND tag = ?;', ( namespace, tag ) ) ] )
if include_pending_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM namespaces, ( tags, ' + pending_tables_phrase + ' USING ( tag_id ) ) USING ( namespace_id ) WHERE ' + pending_predicates_phrase + 'namespace = ? AND tag = ?;', ( namespace, tag ) ) ] )
else:
if include_current_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM tags, ' + current_tables_phrase + ' USING ( tag_id ) WHERE ' + current_predicates_phrase + 'tag = ?;', ( tag, ) ) ] )
if include_pending_tags: hash_ids.update( [ id for ( id, ) in c.execute( 'SELECT hash_id FROM tags, ' + pending_tables_phrase + ' USING ( tag_id ) WHERE ' + pending_predicates_phrase + 'tag = ?;', ( tag, ) ) ] )
return hash_ids
@ -3025,7 +3058,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
predicates = []
if service_type in ( HC.NULL_SERVICE, HC.TAG_REPOSITORY, HC.LOCAL_TAG ):
if service_type == HC.NULL_SERVICE: predicates.extend( [ HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( system_predicate_type, None ), None ) for system_predicate_type in [ HC.SYSTEM_PREDICATE_TYPE_EVERYTHING, HC.SYSTEM_PREDICATE_TYPE_UNTAGGED, HC.SYSTEM_PREDICATE_TYPE_NUM_TAGS, HC.SYSTEM_PREDICATE_TYPE_HASH ] ] )
elif service_type in ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ):
service_info = self._GetServiceInfoSpecific( c, service_id, service_type, { HC.SERVICE_INFO_NUM_FILES } )
@ -3139,7 +3173,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def _GetSubscriptions( self, c ):
subscriptions = [ ( site_download_type, name, query_type, query, frequency_type, frequency_number, dict( advanced_tag_options ), advanced_import_options, last_checked, url_cache ) for ( site_download_type, name, ( query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) ) in c.execute( 'SELECT site_download_type, name, info FROM subscriptions;', ) ]
subscriptions = [ ( site_download_type, name, query_type, query, frequency_type, frequency_number, dict( advanced_tag_options ), advanced_import_options, last_checked, url_cache, paused ) for ( site_download_type, name, ( query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) ) in c.execute( 'SELECT site_download_type, name, info FROM subscriptions;' ) ]
return subscriptions
@ -3153,6 +3187,37 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
return [ self._GetServiceIdentifier( c, service_id ) for service_id in service_ids ]
def _GetTagSiblings( self, c, service_identifier = None ):
if service_identifier is None:
results = HC.BuildKeyToListDict( ( ( service_id, ( old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) ) for ( service_id, old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) in c.execute( 'SELECT service_id, old_namespace_id, old_tag_id, new_namespace_id, new_tag_id FROM tag_siblings;' ) ) )
processed_results = {}
for ( service_id, n_t_ids ) in results.items():
service_identifier = self._GetServiceIdentifier( c, service_id )
n_ts = [ ( self._GetNamespaceTag( c, old_namespace_id, old_tag_id ), self._GetNamespaceTag( c, new_namespace_id, new_tag_id ) ) for ( old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) in n_t_ids ]
processed_results[ service_identifier ] = n_ts
return processed_results
else:
service_id = self._GetServiceId( c, service_identifier )
n_t_ids = c.execute( 'SELECT old_namespace_id, old_tag_id, new_namespace_id, new_tag_id FROM tag_siblings WHERE service_id = ?;', ( service_id, ) ).fetchall()
n_ts = [ ( self._GetNamespaceTag( c, old_namespace_id, old_tag_id ), self._GetNamespaceTag( c, new_namespace_id, new_tag_id ) ) for ( old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) in n_t_ids ]
return n_ts
def _GetThumbnailHashesIShouldHave( self, c, service_identifier ):
service_id = self._GetServiceId( c, service_identifier )
@ -3794,9 +3859,9 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def _SetSubscription( self, c, subscription ):
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) = subscription
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) = subscription
info = [ query_type, query, frequency_type, frequency_number, advanced_tag_options.items(), advanced_import_options, last_checked, url_cache ]
info = [ query_type, query, frequency_type, frequency_number, advanced_tag_options.items(), advanced_import_options, last_checked, url_cache, paused ]
c.execute( 'DELETE FROM subscriptions WHERE site_download_type = ? AND name = ?;', ( site_download_type, name ) )
@ -3807,7 +3872,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
HC.repos_or_subs_changed = True
inserts = [ ( site_download_type, name, [ query_type, query, frequency_type, frequency_number, advanced_tag_options.items(), advanced_import_options, last_checked, url_cache ] ) for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) in subscriptions ]
inserts = [ ( site_download_type, name, [ query_type, query, frequency_type, frequency_number, advanced_tag_options.items(), advanced_import_options, last_checked, url_cache, paused ] ) for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) in subscriptions ]
c.execute( 'DELETE FROM subscriptions;' )
@ -4430,6 +4495,38 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def _UpdateTagSiblings( self, c, edit_log ):
for ( service_identifier, sub_edit_log ) in edit_log:
service_id = self._GetServiceId( c, service_identifier )
for ( action, data ) in sub_edit_log:
if action == HC.ADD:
( old_tag, new_tag ) = data
( old_namespace_id, old_tag_id ) = self._GetNamespaceIdTagId( c, old_tag )
( new_namespace_id, new_tag_id ) = self._GetNamespaceIdTagId( c, new_tag )
c.execute( 'INSERT OR IGNORE INTO tag_siblings ( service_id, old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) VALUES ( ?, ?, ?, ?, ? );', ( service_id, old_namespace_id, old_tag_id, new_namespace_id, new_tag_id ) )
elif action == HC.DELETE:
( old_tag, new_tag ) = data
( old_namespace_id, old_tag_id ) = self._GetNamespaceIdTagId( c, old_tag )
c.execute( 'DELETE FROM tag_siblings WHERE service_id = ? AND old_namespace_id = ? AND old_tag_id = ?;', ( service_id, old_namespace_id, old_tag_id ) )
self.pub( 'notify_new_siblings' )
class DB( ServiceDB ):
def __init__( self ):
@ -4518,7 +4615,7 @@ class DB( ServiceDB ):
HC.DAEMONWorker( 'DownloadFiles', self.DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) )
HC.DAEMONWorker( 'DownloadThumbnails', self.DAEMONDownloadThumbnails, ( 'notify_new_permissions', 'notify_new_thumbnails' ) )
HC.DAEMONWorker( 'ResizeThumbnails', self.DAEMONResizeThumbnails, init_wait = 1200 )
HC.DAEMONWorker( 'ResizeThumbnails', self.DAEMONResizeThumbnails, init_wait = 600 )
HC.DAEMONWorker( 'SynchroniseAccounts', self.DAEMONSynchroniseAccounts, ( 'notify_new_services', 'permissions_are_stale' ) )
HC.DAEMONWorker( 'SynchroniseMessages', self.DAEMONSynchroniseMessages, ( 'notify_new_permissions', 'notify_check_messages' ), period = 60 )
HC.DAEMONWorker( 'SynchroniseRepositoriesAndSubscriptions', self.DAEMONSynchroniseRepositoriesAndSubscriptions, ( 'notify_new_permissions', 'notify_new_subscriptions' ) )
@ -4780,6 +4877,8 @@ class DB( ServiceDB ):
c.execute( 'CREATE TABLE tag_service_precedence ( service_id INTEGER PRIMARY KEY REFERENCES services ON DELETE CASCADE, precedence INTEGER );' )
c.execute( 'CREATE TABLE tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_namespace_id INTEGER, old_tag_id INTEGER, new_namespace_id INTEGER, new_tag_id INTEGER, PRIMARY KEY ( service_id, old_namespace_id, old_tag_id ) );' )
c.execute( 'CREATE TABLE tags ( tag_id INTEGER PRIMARY KEY, tag TEXT );' )
c.execute( 'CREATE UNIQUE INDEX tags_tag_index ON tags ( tag );' )
@ -5235,7 +5334,7 @@ class DB( ServiceDB ):
search_url = 'http://e621.net/post/index?page=%index%&tags=%tags%'
search_separator = '%20'
advance_by_page_num = True
thumb_classname = 'thumb blacklist'
thumb_classname = 'thumb'
image_id = None
image_data = 'Download'
tag_classnames_to_namespaces = { 'tag-type-general' : '', 'tag-type-character' : 'character', 'tag-type-copyright' : 'series', 'tag-type-artist' : 'creator' }
@ -5263,6 +5362,64 @@ class DB( ServiceDB ):
if version < 69:
boorus = []
name = 'e621'
search_url = 'http://e621.net/post/index?page=%index%&tags=%tags%'
search_separator = '%20'
advance_by_page_num = True
thumb_classname = 'thumb'
image_id = None
image_data = 'Download'
tag_classnames_to_namespaces = { 'tag-type-general' : '', 'tag-type-character' : 'character', 'tag-type-copyright' : 'series', 'tag-type-artist' : 'creator' }
boorus.append( CC.Booru( name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) )
for booru in boorus:
name = booru.GetName()
c.execute( 'DELETE FROM boorus WHERE name = ?;', ( name, ) )
c.execute( 'INSERT INTO boorus VALUES ( ?, ? );', ( name, booru ) )
#
c.execute( 'CREATE TABLE tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_namespace_id INTEGER, old_tag_id INTEGER, new_namespace_id INTEGER, new_tag_id INTEGER, PRIMARY KEY ( service_id, old_namespace_id, old_tag_id ) );' )
#
subscriptions = c.execute( 'SELECT site_download_type, name, info FROM subscriptions;' ).fetchall()
paused = False
for ( site_download_type, name, ( query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) ) in subscriptions:
updated_info = [ query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ]
c.execute( 'UPDATE subscriptions SET info = ? WHERE site_download_type = ? AND name = ?;', ( updated_info, site_download_type, name ) )
#
wx.GetApp().SetSplashText( 'generating A/C cache' )
try:
tag_service_identifiers = self._GetServiceIdentifiers( c, ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
file_service_identifiers = self._GetServiceIdentifiers( c, ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ) )
tag_service_identifiers.add( HC.NULL_SERVICE_IDENTIFIER )
file_service_identifiers.add( HC.NULL_SERVICE_IDENTIFIER )
for ( tag_service_identifier, file_service_identifier ) in itertools.product( tag_service_identifiers, file_service_identifiers ): self._GetAutocompleteTags( c, tag_service_identifier = tag_service_identifier, file_service_identifier = file_service_identifier )
except: pass
unknown_account = CC.GetUnknownAccount()
unknown_account.MakeStale()
@ -6524,7 +6681,7 @@ class DB( ServiceDB ):
except: print( traceback.format_exc() )
if i % 10: time.sleep( 2 )
if i % 10 == 0: time.sleep( 2 )
else:
if limit > 10000: time.sleep( 0.05 )
@ -6887,7 +7044,9 @@ class DB( ServiceDB ):
subscriptions = wx.GetApp().ReadDaemon( 'subscriptions' )
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) in subscriptions:
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) in subscriptions:
if paused: continue
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: ( booru_name, query_type ) = query_type
@ -7032,56 +7191,66 @@ class DB( ServiceDB ):
if HC.shutdown: return
url = url_args[0]
url_cache.add( url )
x_out_of_y = HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( all_url_args ) )
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : checking url status' )
( status, hash ) = wx.GetApp().ReadDaemon( 'url_status', url )
if status == 'deleted' and 'exclude_deleted_files' not in advanced_import_options: status = 'new'
if status == 'redundant':
try:
if do_tags:
url = url_args[0]
url_cache.add( url )
x_out_of_y = HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( all_url_args ) )
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : checking url status' )
( status, hash ) = wx.GetApp().ReadDaemon( 'url_status', url )
if status == 'deleted' and 'exclude_deleted_files' not in advanced_import_options: status = 'new'
if status == 'redundant':
try:
if do_tags:
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : found file in db, fetching tags' )
try:
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : found file in db, fetching tags' )
tags = downloader.GetTags( *url_args )
service_identifiers_to_tags = HydrusDownloading.ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options )
content_updates = HydrusDownloading.ConvertServiceIdentifiersToTagsToContentUpdates( hash, service_identifiers_to_tags )
wx.GetApp().Write( 'content_updates', content_updates )
except: pass
tags = downloader.GetTags( *url_args )
elif status == 'new':
num_new += 1
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : downloading file' )
if do_tags: ( file, tags ) = downloader.GetFileAndTags( *url_args )
else:
service_identifiers_to_tags = HydrusDownloading.ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options )
file = downloader.GetFile( *url_args )
content_updates = HydrusDownloading.ConvertServiceIdentifiersToTagsToContentUpdates( hash, service_identifiers_to_tags )
tags = []
wx.GetApp().Write( 'content_updates', content_updates )
except: pass
service_identifiers_to_tags = HydrusDownloading.ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options )
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : importing file' )
wx.GetApp().Write( 'import_file', file, advanced_import_options = advanced_import_options, service_identifiers_to_tags = service_identifiers_to_tags, url = url )
elif status == 'new':
except Exception as e:
num_new += 1
print( 'while trying to execute a subscription, the url ' + url + ' caused this problem:' )
print( unicode( e ) )
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : downloading file' )
if do_tags: ( file, tags ) = downloader.GetFileAndTags( *url_args )
else:
file = downloader.GetFile( *url_args )
tags = []
service_identifiers_to_tags = HydrusDownloading.ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options )
HC.pubsub.pub( 'service_status', name + ': ' + x_out_of_y + ' : importing file' )
wx.GetApp().Write( 'import_file', file, advanced_import_options = advanced_import_options, service_identifiers_to_tags = service_identifiers_to_tags, url = url )
HC.pubsub.pub( 'log_error', 'synchronise subscriptions daemon', 'problem with ' + name + ' ' + unicode( e ) )
i += 1
@ -7090,7 +7259,7 @@ class DB( ServiceDB ):
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: query_type = ( booru_name, query_type )
subscription = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
subscription = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
wx.GetApp().Write( 'subscription', subscription )
@ -7122,7 +7291,7 @@ class DB( ServiceDB ):
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: query_type = ( booru_name, query_type )
subscription = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
subscription = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
wx.GetApp().Write( 'subscription', subscription )
@ -7271,6 +7440,7 @@ class DB( ServiceDB ):
elif action == 'status_num_inbox': result = self._DoStatusNumInbox( c, *args, **kwargs )
elif action == 'subscriptions': result = self._GetSubscriptions( c, *args, **kwargs )
elif action == 'tag_service_precedence': result = self._tag_service_precedence
elif action == 'tag_siblings': result = self._GetTagSiblings( c, *args, **kwargs )
elif action == 'thumbnail': result = self._GetThumbnail( *args, **kwargs )
elif action == 'thumbnail_hashes_i_should_have': result = self._GetThumbnailHashesIShouldHave( c, *args, **kwargs )
elif action == 'transport_message': result = self._GetTransportMessage( c, *args, **kwargs )
@ -7319,6 +7489,7 @@ class DB( ServiceDB ):
elif action == 'set_tag_service_precedence': self._SetTagServicePrecedence( c, *args, **kwargs )
elif action == 'subscription': self._SetSubscription( c, *args, **kwargs )
elif action == 'subscriptions': self._SetSubscriptions( c, *args, **kwargs )
elif action == 'tag_siblings': self._UpdateTagSiblings( c, *args, **kwargs )
elif action == 'thumbnails': self._AddThumbnails( c, *args, **kwargs )
elif action == 'update': self._AddUpdate( c, *args, **kwargs )
elif action == 'update_boorus': self._UpdateBoorus( c, *args, **kwargs )

View File

@ -674,6 +674,15 @@ class FrameGUI( ClientGUICommon.Frame ):
except Exception as e: wx.MessageBox( unicode( e ) + traceback.format_exc() )
def _ManageTagSiblings( self ):
try:
with ClientGUIDialogs.DialogManageTagSiblings( self ) as dlg: dlg.ShowModal()
except Exception as e: wx.MessageBox( unicode( e ) + traceback.format_exc() )
def _ModifyAccount( self, service_identifier ):
service = wx.GetApp().Read( 'service', service_identifier )
@ -1058,6 +1067,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif command == 'manage_services': self._ManageServices()
elif command == 'manage_subscriptions': self._ManageSubscriptions()
elif command == 'manage_tag_service_precedence': self._ManageTagServicePrecedence()
elif command == 'manage_tag_siblings': self._ManageTagSiblings()
elif command == 'modify_account': self._ModifyAccount( data )
elif command == 'new_accounts': self._NewAccounts( data )
elif command == 'new_import_booru': self._NewPageImportBooru()
@ -1328,7 +1338,10 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
services.AppendSeparator()
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'review_services' ), p( '&Review Services' ), p( 'Review your services.' ) )
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_services' ), p( '&Add, Remove or Edit Services' ), p( 'Edit your services.' ) )
if len( download_tag_service_identifiers ) > 1: services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_service_precedence' ), p( '&Manage Tag Service Precedence' ), p( 'Change the order in which tag repositories\' taxonomies will be added to the database.' ) )
services.AppendSeparator()
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_siblings' ), p( '&Manage Tag Siblings' ), p( 'Set certain tags to be automatically replaced with other tags.' ) )
#services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_service_precedence' ), p( '&Manage Tag Service Precedence' ), p( 'Change the order in which tag repositories\' taxonomies will be added to the database.' ) )
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tag_service_precedence' ), p( '&Manage Tag Service Precedence' ), p( 'Change the order in which tag repositories\' taxonomies will be added to the database.' ) )
services.AppendSeparator()
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_boorus' ), p( 'Manage &Boorus' ), p( 'Change the html parsing information for boorus to download from.' ) )
services.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_imageboards' ), p( 'Manage &Imageboards' ), p( 'Change the html POST form information for imageboards to dump to.' ) )
@ -1646,7 +1659,16 @@ class FramePageChooser( ClientGUICommon.Frame ):
entries = [ ( 'page_query', HC.LOCAL_FILE_SERVICE_IDENTIFIER ) ] + file_repos
elif menu_keyword == 'download': entries = [ ( 'page_import_url', None ), ( 'page_import_thread_watcher', None ), ( 'menu', 'gallery' ) ]
elif menu_keyword == 'gallery': entries = [ ( 'page_import_booru', None ), ( 'page_import_gallery', 'giphy' ), ( 'page_import_gallery', 'deviant art by artist' ), ( 'menu', 'hentai foundry' ), ( 'menu', 'pixiv' ), ( 'page_import_gallery', 'tumblr' ) ]
elif menu_keyword == 'gallery':
entries = [ ( 'page_import_booru', None ), ( 'page_import_gallery', 'giphy' ), ( 'page_import_gallery', 'deviant art by artist' ), ( 'menu', 'hentai foundry' ) ]
( id, password ) = wx.GetApp().Read( 'pixiv_account' )
if id != '' and password != '': entries.append( ( 'menu', 'pixiv' ) )
entries.extend( [ ( 'page_import_gallery', 'tumblr' ) ] )
elif menu_keyword == 'hentai foundry': entries = [ ( 'page_import_gallery', 'hentai foundry by artist' ), ( 'page_import_gallery', 'hentai foundry by tags' ) ]
elif menu_keyword == 'pixiv': entries = [ ( 'page_import_gallery', 'pixiv by artist' ), ( 'page_import_gallery', 'pixiv by tag' ) ]
elif menu_keyword == 'messages': entries = [ ( 'page_messages', identity ) for identity in self._identities ]

View File

@ -316,6 +316,11 @@ class Canvas():
( media_width, media_height ) = self._current_display_media.GetResolution()
if self._current_display_media.GetMime() in ( HC.APPLICATION_FLASH, HC.VIDEO_FLV ):
my_width -= 1
if media_width > my_width or media_height > my_height:
width_zoom = my_width / float( media_width )

View File

@ -119,8 +119,6 @@ class AutoCompleteDropdown( wx.TextCtrl ):
tlp.Bind( wx.EVT_MOVE, self.EventMove )
self._initialised = False
def _BroadcastChoice( self, predicate ): pass
@ -242,12 +240,7 @@ class AutoCompleteDropdown( wx.TextCtrl ):
def EventSetFocus( self, event ):
if not self._initialised:
self._UpdateList()
self._initialised = True
self._UpdateList()
self._ShowDropdownIfFocussed()
@ -423,7 +416,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
menu = wx.Menu()
if len( service_identifiers ) > 0: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known files' )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known files' )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', HC.LOCAL_FILE_SERVICE_IDENTIFIER ), 'local files' )
for service_identifier in service_identifiers: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', service_identifier ), service_identifier.GetName() )
@ -495,7 +488,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
menu = wx.Menu()
if len( service_identifiers ) > 0: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known tags' )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known tags' )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', HC.LOCAL_TAG_SERVICE_IDENTIFIER ), 'local tags' )
for service_identifier in service_identifiers: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', service_identifier ), service_identifier.GetName() )
@ -769,24 +762,39 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
self._first_letters = half_complete_tag
self._cached_results = wx.GetApp().Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, half_complete_tag = search_text )
self._cached_results = wx.GetApp().Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, half_complete_tag = search_text, do_siblings_collapse = False )
matches = self._cached_results.GetMatches( half_complete_tag )
else: matches = []
predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', search_text ), 0 )
# now do the 'put whatever they typed in at the top, whether it has count or not'
# now with sibling support!
try:
predicate = matches[ matches.index( predicate ) ]
matches.remove( predicate )
except: pass
top_predicates = []
matches.insert( 0, predicate )
top_predicates.append( HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', search_text ), 0 ) )
siblings_manager = wx.GetApp().GetTagSiblingsManager()
sibling = siblings_manager.GetSibling( search_text )
if sibling is not None: top_predicates.append( HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', sibling ), 0 ) )
for predicate in top_predicates:
try:
predicate = matches[ matches.index( predicate ) ]
matches.remove( predicate )
except: pass
matches.insert( 0, predicate )
return matches
@ -1436,7 +1444,7 @@ class ListBox( wx.ScrolledWindow ):
def __len__( self ): return len( self._ordered_strings )
def _Activate( self, tag ): pass
def _Activate( self, s, term ): pass
def _DrawTexts( self ):
@ -1564,7 +1572,14 @@ class ListBox( wx.ScrolledWindow ):
index = self._GetIndexUnderMouse( event )
if index is not None and index == self._current_selected_index: self._Activate( self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ] )
if index is not None and index == self._current_selected_index:
s = self._ordered_strings[ self._current_selected_index ]
term = self._strings_to_terms[ s ]
self._Activate( s, term )
def EventKeyDown( self, event ):
@ -1573,7 +1588,14 @@ class ListBox( wx.ScrolledWindow ):
if self._current_selected_index is not None:
if key_code in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ): self._Activate( self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ] )
if key_code in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
s = self._ordered_strings[ self._current_selected_index ]
term = self._strings_to_terms[ s ]
self._Activate( s, term )
elif key_code in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ): self._Select( self._current_selected_index - 1 )
elif key_code in ( wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ): self._Select( self._current_selected_index + 1 )
elif key_code == wx.WXK_PAGEUP: self._Select( self._current_selected_index - self._num_rows_per_page )
@ -1705,7 +1727,7 @@ class ListBoxMessagesActiveOnly( ListBoxMessages ):
self._matches = {}
def _Activate( self, tag ): self._callable( tag )
def _Activate( self, s, term ): self._callable( term )
def SetTerms( self, matches ):
@ -1751,7 +1773,7 @@ class ListBoxMessagesPredicates( ListBoxMessages ):
def _Activate( self, term ): HC.pubsub.pub( 'remove_predicate', self._page_key, term )
def _Activate( self, s, term ): HC.pubsub.pub( 'remove_predicate', self._page_key, term )
def ActivatePredicate( self, term ):
@ -2146,7 +2168,7 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
data_indicies = [ self.GetItemData( index ) for index in range( self.GetItemCount() ) ]
datas = [ self.itemDataMap[ data_index ] for data_index in data_indicies ]
datas = [ tuple( self.itemDataMap[ data_index ] ) for data_index in data_indicies ]
return datas
@ -2154,7 +2176,7 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
data_index = self.GetItemData( index )
return self.itemDataMap[ data_index ]
return tuple( self.itemDataMap[ data_index ] )
@ -2790,11 +2812,38 @@ class TagsBoxActiveOnly( TagsBox ):
self._predicates = {}
def _Activate( self, predicate ): self._callable( predicate )
def _Activate( self, s, term ): self._callable( term )
def SetPredicates( self, predicates ):
if predicates != self._predicates:
# need to do a clever compare, since normal predicate compare doesn't take count into account
they_are_the_same = True
if len( predicates ) == len( self._predicates ):
p_list_1 = list( predicates )
p_list_2 = list( self._predicates )
p_list_1.sort()
p_list_2.sort()
for index in range( len( p_list_1 ) ):
p_1 = p_list_1[ index ]
p_2 = p_list_2[ index ]
if p_1 != p_2 or p_1.GetCount() != p_2.GetCount():
they_are_the_same = False
break
else: they_are_the_same = False
if not they_are_the_same:
self._predicates = predicates
@ -2836,9 +2885,9 @@ class TagsBoxCPP( TagsBox ):
HC.pubsub.sub( self, 'ChangeTagRepository', 'change_tag_repository' )
def _Activate( self, tag ):
def _Activate( self, s, term ):
predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', tag ), None )
predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', term ), None )
HC.pubsub.pub( 'add_predicate', self._page_key, predicate )
@ -2882,6 +2931,10 @@ class TagsBoxCPP( TagsBox ):
def SetTags( self, current_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ):
siblings_manager = wx.GetApp().GetTagSiblingsManager()
current_tags_to_count = siblings_manager.CollapseTagsToCount( current_tags_to_count )
if current_tags_to_count != self._current_tags_to_count or pending_tags_to_count != self._pending_tags_to_count or petitioned_tags_to_count != self._petitioned_tags_to_count:
self._current_tags_to_count = current_tags_to_count
@ -2901,6 +2954,10 @@ class TagsBoxCPP( TagsBox ):
if tag in self._pending_tags_to_count: tag_string += ' (+' + HC.ConvertIntToPrettyString( self._pending_tags_to_count[ tag ] ) + ')'
if tag in self._petitioned_tags_to_count: tag_string += ' (-' + HC.ConvertIntToPrettyString( self._petitioned_tags_to_count[ tag ] ) + ')'
sibling = siblings_manager.GetSibling( tag )
if sibling is not None: tag_string += ' (' + sibling + ')'
self._ordered_strings.append( tag_string )
self._strings_to_terms[ tag_string ] = tag
@ -2968,38 +3025,57 @@ class TagsBoxFlat( TagsBox ):
TagsBox.__init__( self, parent )
self._removed_callable = removed_callable
self._tags = []
def _RecalcTags( self ):
self._ordered_strings = self._strings_to_terms.values()
self._strings_to_terms = {}
siblings_manager = wx.GetApp().GetTagSiblingsManager()
for tag in self._tags:
tag_string = tag
sibling = siblings_manager.GetSibling( tag )
if sibling is not None: tag_string += ' (' + sibling + ')'
self._strings_to_terms[ tag_string ] = tag
self._ordered_strings = self._strings_to_terms.keys()
self._ordered_strings.sort()
self._TextsHaveChanged()
def _Activate( self, tag ):
def _Activate( self, s, term ):
del self._strings_to_terms[ tag ]
self._RecalcTags()
self._removed_callable( tag )
if term in self._tags:
self._tags.remove( term )
self._RecalcTags()
self._removed_callable( tag )
def AddTag( self, tag ):
self._strings_to_terms[ tag ] = tag
self._tags.append( tag )
self._RecalcTags()
def GetTags( self ): return self._strings_to_terms.values()
def GetTags( self ): return self._tags
def SetTags( self, tags ):
self._strings_to_terms = { t : t for t in tags }
self._tags = tags
self._RecalcTags()
@ -3022,10 +3098,12 @@ class TagsBoxManage( TagsBox ):
self._RebuildTagStrings()
def _Activate( self, tag ): self._callable( tag )
def _Activate( self, s, term ): self._callable( term )
def _RebuildTagStrings( self ):
siblings_manager = wx.GetApp().GetTagSiblingsManager()
if self._show_deleted_tags: all_tags = self._current_tags | self._deleted_tags | self._pending_tags | self._petitioned_tags
else: all_tags = self._current_tags | self._pending_tags | self._petitioned_tags
@ -3039,6 +3117,10 @@ class TagsBoxManage( TagsBox ):
elif tag in self._pending_tags: tag_string = '(+) ' + tag
else: tag_string = '(X) ' + tag
sibling = siblings_manager.GetSibling( tag )
if sibling is not None: tag_string += ' (' + sibling + ')'
self._ordered_strings.append( tag_string )
self._strings_to_terms[ tag_string ] = tag
@ -3104,7 +3186,7 @@ class TagsBoxOptions( TagsBox ):
self._TextsHaveChanged()
def _Activate( self, tag ): self.RemoveNamespace( tag )
def _Activate( self, s, term ): self.RemoveNamespace( term )
def _GetNamespaceColours( self ): return self._namespace_colours
@ -3183,7 +3265,7 @@ class TagsBoxPredicates( TagsBox ):
def _Activate( self, predicate ): HC.pubsub.pub( 'remove_predicate', self._page_key, predicate )
def _Activate( self, s, term ): HC.pubsub.pub( 'remove_predicate', self._page_key, term )
def AddPredicate( self, predicate ):

View File

@ -5425,7 +5425,7 @@ class DialogManageRatings( Dialog ):
if len( content_updates ) > 0: wx.GetApp().Write( 'content_updates', content_updates )
except Exception as e: wx.MessageBox( 'Saving pending mapping changes to DB raised this error: ' + unicode( e ) )
except Exception as e: wx.MessageBox( 'Saving ratings changes to DB raised this error: ' + unicode( e ) )
self.EndModal( wx.ID_OK )
@ -6627,11 +6627,11 @@ class DialogManageSubscriptions( Dialog ):
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_BOORU ] = self._booru
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_TUMBLR ] = self._tumblr
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) in self._original_subscriptions:
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) in self._original_subscriptions:
listbook = types_to_listbooks[ site_download_type ]
page = self._Panel( listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
page = self._Panel( listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
listbook.AddPage( page, name )
@ -6746,6 +6746,18 @@ class DialogManageSubscriptions( Dialog ):
elif subscription_listbook == self._booru: site_download_type = HC.SITE_DOWNLOAD_TYPE_BOORU
elif subscription_listbook == self._tumblr: site_download_type = HC.SITE_DOWNLOAD_TYPE_TUMBLR
if site_download_type == HC.SITE_DOWNLOAD_TYPE_PIXIV:
( id, password ) = wx.GetApp().Read( 'pixiv_account' )
if id == '' and password == '':
wx.MessageBox( 'You need to set up your pixiv credentials before you can add a pixiv subscription!' )
return
if site_download_type in ( HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART, HC.SITE_DOWNLOAD_TYPE_TUMBLR ): query_type = 'artist'
else: query_type = 'tags'
@ -6762,7 +6774,9 @@ class DialogManageSubscriptions( Dialog ):
last_checked = None
url_cache = set()
page = self._Panel( subscription_listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
paused = False
page = self._Panel( subscription_listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
subscription_listbook.AddPage( page, name, select = True )
@ -6947,7 +6961,7 @@ class DialogManageSubscriptions( Dialog ):
class _Panel( wx.ScrolledWindow ):
def __init__( self, parent, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ):
def __init__( self, parent, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
wx.ScrolledWindow.__init__( self, parent )
@ -6957,7 +6971,7 @@ class DialogManageSubscriptions( Dialog ):
self.SetMinSize( ( 540, 620 ) )
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
# init controls
@ -6991,6 +7005,8 @@ class DialogManageSubscriptions( Dialog ):
self._info_panel = ClientGUICommon.StaticBox( self, 'info' )
self._paused = wx.CheckBox( self._info_panel, label = 'paused' )
self._reset_cache_button = wx.Button( self._info_panel, label = ' reset cache on dialog ok ' )
self._reset_cache_button.Bind( wx.EVT_BUTTON, self.EventResetCache )
@ -7035,6 +7051,7 @@ class DialogManageSubscriptions( Dialog ):
self._info_panel.AddF( wx.StaticText( self._info_panel, label = last_checked_message ), FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( wx.StaticText( self._info_panel, label = str( len( url_cache ) ) + ' urls in cache' ), FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._paused, FLAGS_LONE_BUTTON )
self._info_panel.AddF( self._reset_cache_button, FLAGS_LONE_BUTTON )
vbox = wx.BoxSizer( wx.VERTICAL )
@ -7048,7 +7065,7 @@ class DialogManageSubscriptions( Dialog ):
self.SetSizer( vbox )
def _SetControls( self, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ):
def _SetControls( self, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
self._name.SetValue( name )
@ -7090,6 +7107,8 @@ class DialogManageSubscriptions( Dialog ):
if index_to_select is not None: self._frequency_type.Select( index_to_select )
self._paused.SetValue( paused )
self._reset_cache_button.SetLabel( ' reset cache on dialog ok ' )
self._advanced_tag_options.SetInfo( advanced_tag_options )
@ -7107,7 +7126,7 @@ class DialogManageSubscriptions( Dialog ):
def GetInfo( self ):
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) = self._original_info
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) = self._original_info
name = self._name.GetValue()
@ -7135,22 +7154,360 @@ class DialogManageSubscriptions( Dialog ):
url_cache = set()
return ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
paused = self._paused.GetValue()
return ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
def GetName( self ): return self._name.GetValue()
def Update( self, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ):
def Update( self, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
site_download_type = self._original_info[0]
name = self._original_info[1]
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
self._SetControls( *self._original_info )
class DialogManageTagSiblings( Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._tag_repositories = ClientGUICommon.ListBook( self )
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
#services = wx.GetApp().Read( 'services', ( HC.TAG_REPOSITORY, ) )
#for service in services:
#
# account = service.GetAccount()
#
# if account.HasPermission( HC.POST_DATA ):
#
# service_identifier = service.GetServiceIdentifier()
#
# page_info = ( self._Panel, ( self._tag_repositories, service_identifier, paths ), {} )
#
# name = service_identifier.GetName()
#
# self._tag_repositories.AddPage( page_info, name )
#
#
page = self._Panel( self._tag_repositories, HC.LOCAL_TAG_SERVICE_IDENTIFIER )
name = HC.LOCAL_TAG_SERVICE_IDENTIFIER.GetName()
self._tag_repositories.AddPage( page, name )
#default_tag_repository = self._options[ 'default_tag_repository' ]
#self._tag_repositories.Select( default_tag_repository.GetName() )
self._ok_button = wx.Button( self, label='ok' )
self._ok_button.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok_button.SetForegroundColour( ( 0, 128, 0 ) )
self._close_button = wx.Button( self, id = wx.ID_CANCEL, label='cancel' )
self._close_button.Bind( wx.EVT_BUTTON, self.EventCancel )
self._close_button.SetForegroundColour( ( 128, 0, 0 ) )
def InitialisePanel():
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok_button, FLAGS_SMALL_INDENT )
buttons.AddF( self._close_button, FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_repositories, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
self.SetInitialSize( ( 450, 680 ) )
Dialog.__init__( self, parent, 'tag siblings' )
InitialiseControls()
InitialisePanel()
interested_actions = [ 'set_search_focus' ]
entries = []
for ( modifier, key_dict ) in self._options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
self.Bind( wx.EVT_MENU, self.EventMenu )
def _SetSearchFocus( self ):
page = self._tag_repositories.GetCurrentPage()
page.SetTagBoxFocus()
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventMenu( self, event ):
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
try:
( command, data ) = action
if command == 'set_search_focus': self._SetSearchFocus()
else: event.Skip()
except Exception as e:
wx.MessageBox( unicode( e ) )
wx.MessageBox( traceback.format_exc() )
def EventOK( self, event ):
edit_log = []
try:
for page in self._tag_repositories.GetNameToPageDict().values(): edit_log.append( page.GetChanges() )
if len( edit_log ) > 0: wx.GetApp().Write( 'tag_siblings', edit_log )
except Exception as e: wx.MessageBox( 'Saving tag sibling changes to DB raised this error: ' + unicode( e ) )
self.EndModal( wx.ID_OK )
def EventServiceChanged( self, event ):
page = self._tag_repositories.GetCurrentPage()
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):
def __init__( self, parent, service_identifier ):
def InitialiseControls():
self._tag_siblings = ClientGUICommon.SaneListCtrl( self, 250, [ ( 'old', 160 ), ( 'new', -1 ) ] )
for ( old, new ) in self._original_pairs: self._tag_siblings.Append( ( old, new ), ( old, new ) )
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
self._old_text = wx.StaticText( self )
self._new_text = wx.StaticText( self )
self._old_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetOld, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._new_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetNew, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._add.Disable()
self._remove = wx.Button( self, label = 'remove' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
self._remove.Disable()
def InitialisePanel():
button_box = wx.BoxSizer( wx.HORIZONTAL )
button_box.AddF( self._add, FLAGS_MIXED )
button_box.AddF( self._remove, FLAGS_MIXED )
text_box = wx.BoxSizer( wx.HORIZONTAL )
text_box.AddF( self._old_text, FLAGS_EXPAND_BOTH_WAYS )
text_box.AddF( self._new_text, FLAGS_EXPAND_BOTH_WAYS )
input_box = wx.BoxSizer( wx.HORIZONTAL )
input_box.AddF( self._old_input, FLAGS_EXPAND_BOTH_WAYS )
input_box.AddF( self._new_input, FLAGS_EXPAND_BOTH_WAYS )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_siblings, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( button_box, FLAGS_BUTTON_SIZERS )
vbox.AddF( text_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( input_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )
wx.Panel.__init__( self, parent )
self._service_identifier = service_identifier
self._added = set()
self._removed = set()
self._original_pairs = [ tuple( pair ) for pair in wx.GetApp().Read( 'tag_siblings', service_identifier ) ]
self._current_pairs = set( self._original_pairs )
self._current_new = None
self._current_old = None
InitialiseControls()
InitialisePanel()
def _SetButtonStatus( self ):
all_selected = self._tag_siblings.GetAllSelected()
if len( all_selected ) == 0: self._remove.Disable()
else: self._remove.Enable()
if self._current_new is None or self._current_old is None: self._add.Disable()
else: self._add.Enable()
def EventAdd( self, event ):
pair = ( self._current_old, self._current_new )
if pair in self._current_pairs:
wx.MessageBox( 'That relationship already exists!' )
return
self._current_olds = { old for ( old, new ) in self._current_pairs }
# test for ambiguity
if self._current_old in self._current_olds:
wx.MessageBox( 'There already is a relationship set for the tag ' + self._current_new + '.' )
return
# test for loops
if self._current_new in self._current_olds:
d = dict( self._current_pairs )
next_new = self._current_new
while next_new in d:
next_new = d[ next_new ]
if next_new == self._current_old:
wx.MessageBox( 'Adding that pair would create a loop!' )
return
# if we got here, we are great to do it
self._added.add( pair )
self._removed.discard( pair )
self._current_pairs.add( pair )
self._tag_siblings.Append( pair, pair )
self.SetOld( None )
self.SetNew( None )
def EventItemSelected( self, event ):
self._SetButtonStatus()
def EventRemove( self, event ):
all_selected = self._tag_siblings.GetAllSelected()
for selected in all_selected:
pair = self._tag_siblings.GetClientData( selected )
self._removed.add( pair )
self._added.discard( pair )
self._current_pairs.discard( pair )
self._tag_siblings.RemoveAllSelected()
def GetChanges( self ):
edit_log = []
if self._service_identifier == HC.LOCAL_TAG_SERVICE_IDENTIFIER:
edit_log += [ ( HC.ADD, pair ) for pair in self._added ]
edit_log += [ ( HC.DELETE, pair ) for pair in self._removed ]
return ( self._service_identifier, edit_log )
def SetNew( self, tag ):
if tag is not None and tag == self._current_old: self.SetOld( None )
self._current_new = tag
if tag is None: self._new_text.SetLabel( '' )
else: self._new_text.SetLabel( tag )
self._SetButtonStatus()
def SetOld( self, tag ):
if tag is not None and tag == self._current_new: self.SetNew( None )
self._current_old = tag
if tag is None: self._old_text.SetLabel( '' )
else: self._old_text.SetLabel( tag )
self._SetButtonStatus()
def SetTagBoxFocus( self ): self._old_input.SetFocus()
class DialogManageTagServicePrecedence( Dialog ):
def __init__( self, parent ):
@ -7395,7 +7752,7 @@ class DialogManageTags( Dialog ):
if len( content_updates ) > 0: wx.GetApp().Write( 'content_updates', content_updates )
except Exception as e: wx.MessageBox( 'Saving pending mapping changes to DB raised this error: ' + unicode( e ) )
except Exception as e: wx.MessageBox( 'Saving mapping changes to DB raised this error: ' + unicode( e ) )
self.EndModal( wx.ID_OK )
@ -8126,7 +8483,7 @@ class DialogPathsToTagsRegex( Dialog ):
for ( path, tags ) in page_of_paths_to_tags.items(): paths_to_tags[ path ][ service_identifier ] = tags
except Exception as e: wx.MessageBox( 'Saving pending mapping changes to DB raised this error: ' + unicode( e ) )
except Exception as e: wx.MessageBox( 'Saving regex tags to DB raised this error: ' + unicode( e ) )
return paths_to_tags

View File

@ -493,6 +493,8 @@ class MediaCollection( MediaList, Media ):
def GetDuration( self ): return self._duration
def GetHash( self ): return self.GetDisplayMedia().GetHash()
def GetHashes( self, discriminant = None, not_uploaded_to = None ):
if discriminant is None and not_uploaded_to is None: return self._hashes

View File

@ -30,7 +30,7 @@ TEMP_DIR = BASE_DIR + os.path.sep + 'temp'
# Misc
NETWORK_VERSION = 9
SOFTWARE_VERSION = 68
SOFTWARE_VERSION = 69
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -983,15 +983,24 @@ def SearchEntryMatchesPredicate( search_entry, predicate ):
def SearchEntryMatchesTag( search_entry, tag ):
# note that at no point is the namespace checked against the search_entry!
sibling_manager = wx.GetApp().GetTagSiblingsManager()
if ':' in tag:
tags = sibling_manager.GetAllSiblings( tag )
for tag in tags:
( n, t ) = tag.split( ':', 1 )
if ':' in tag:
( n, t ) = tag.split( ':', 1 )
comparee = t
else: comparee = tag
return t.startswith( search_entry )
if comparee.startswith( search_entry ): return True
else: return tag.startswith( search_entry )
return False
def SplayListForDB( xs ): return '(' + ','.join( [ '"' + str( x ) + '"' for x in xs ] ) + ')'
@ -1896,6 +1905,10 @@ class Predicate():
def __ne__( self, other ): return self.__hash__() != other.__hash__()
def AddToCount( self, count ): self._count += count
def GetCopy( self ): return Predicate( self._predicate_type, self._value, self._count )
def GetCountlessCopy( self ): return Predicate( self._predicate_type, self._value, None )
def GetCount( self ): return self._count
@ -2029,6 +2042,8 @@ class Predicate():
if self._count is not None: base += u' (' + ConvertIntToPrettyString( self._count ) + u')'
elif self._predicate_type == PREDICATE_TYPE_TAG:
( operator, tag ) = self._value
@ -2038,6 +2053,14 @@ class Predicate():
base += tag
if self._count is not None: base += u' (' + ConvertIntToPrettyString( self._count ) + u')'
siblings_manager = wx.GetApp().GetTagSiblingsManager()
sibling = siblings_manager.GetSibling( tag )
if sibling is not None: base += u' (' + sibling + u')'
elif self._predicate_type == PREDICATE_TYPE_NAMESPACE:
( operator, namespace ) = self._value
@ -2048,8 +2071,18 @@ class Predicate():
base += namespace + u':*'
if self._count is None: return base
else: return base + u' (' + ConvertIntToPrettyString( self._count ) + u')'
return base
def GetTag( self ):
if self._predicate_type == PREDICATE_TYPE_TAG:
( operator, tag ) = self._value
return tag
else: return 'no tag'
def GetValue( self ): return self._value