Version 343
This commit is contained in:
parent
1a7fe45043
commit
67e39d8f88
|
@ -8,6 +8,29 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 343</h3></li>
|
||||
<ul>
|
||||
<li>client api:</li>
|
||||
<li>fixed an int/str type mismatch issue with service_names_to_actions_to_tags in /add_tags/add_tags in the client api that meant that argument was not working</li>
|
||||
<li>fixed up some last /get_files/search_files stuff</li>
|
||||
<li>added /get_files/file_metadata</li>
|
||||
<li>added /get_files/file</li>
|
||||
<li>added /get_files/thumbnail</li>
|
||||
<li>added help and unit tests to reflect the above</li>
|
||||
<li>updated client api version to 4</li>
|
||||
<li>.</li>
|
||||
<li>the rest:</li>
|
||||
<li>the list of paths in the manual file import dialog is now sortable. this order will be preserved in regular and 'add tags' ok events for this dialog. it has a new '#' column so you can return to 'parse' order if desired</li>
|
||||
<li>animation and static image windows in the media viewer canvas are now recycled through media type transitions, making for slightly smoother browsing between mixed media</li>
|
||||
<li>increased aggression of media viewer image prefetch</li>
|
||||
<li>added support for 'MM' Tiffs</li>
|
||||
<li>fixed webm mime parsing for webms with no audio (these were falling back to mkv)</li>
|
||||
<li>improved the error reports when a serialised png fails to import</li>
|
||||
<li>the mass-open-urls popup is now pausable as well as cancellable</li>
|
||||
<li>fixed several recently broken ui unit tests</li>
|
||||
<li>misc old code cleanup</li>
|
||||
<li>some misc test controller/constant refactoring</li>
|
||||
</ul>
|
||||
<li><h3>version 342</h3></li>
|
||||
<ul>
|
||||
<li>added support for webp import. it does not yet support animated webps, which, if the local platform supports, will import like apngs used to: just the first frame</li>
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<p class="warning">This is all currently under construction!</p>
|
||||
<h3>client api</h3>
|
||||
<p>The hydrus client now supports a very simple API so you can access it with external programs.</p>
|
||||
<p>By default, the Client API is not turned on. Go to <i>services->manage services</i> and give it a port to get it started. I recommend you not allow non-local connections (i.e. only requests from the same computer will work) to start with.</p>
|
||||
|
@ -50,6 +49,9 @@
|
|||
<h4>Searching and Fetching Files</h4>
|
||||
<ul>
|
||||
<li><a href="#get_files_search_files">POST /get_files/search_files</a></li>
|
||||
<li><a href="#get_files_file_metadata">POST /get_files/file_metadata</a></li>
|
||||
<li><a href="#get_files_file">POST /get_files/file</a></li>
|
||||
<li><a href="#get_files_thumbnail">POST /get_files/thumbnail</a></li>
|
||||
</ul>
|
||||
</ul>
|
||||
<h3>Access Management</h3>
|
||||
|
@ -285,18 +287,19 @@
|
|||
"hash" : "df2a7b286d21329fc496e3aa8b8a08b67bb1747ca32749acb3f5d544cbfc0f56",
|
||||
"service_names_to_actions_to_tags" : {
|
||||
"local tags" : {
|
||||
0 : [ "character:supergirl", "rating:safe" ],
|
||||
1 : [ "character:superman" ]
|
||||
"0" : [ "character:supergirl", "rating:safe" ],
|
||||
"1" : [ "character:superman" ]
|
||||
},
|
||||
"public tag repository" : {
|
||||
2 : [ "character:supergirl", "rating:safe" ],
|
||||
3 : [ "filename:image.jpg" ],
|
||||
4 : [ [ "creator:danban faga", "typo" ], [ "character:super_girl", "underscore" ] ]
|
||||
5 : [ "skirt" ]
|
||||
"2" : [ "character:supergirl", "rating:safe" ],
|
||||
"3" : [ "filename:image.jpg" ],
|
||||
"4" : [ [ "creator:danban faga", "typo" ], [ "character:super_girl", "underscore" ] ]
|
||||
"5" : [ "skirt" ]
|
||||
}
|
||||
}
|
||||
}</pre>
|
||||
<p>This last example is far more complicated than you will usually see. Pend rescinds and petition rescinds are not common. Petitions are also quite rare, and gathering a good petition reason for each tag is often a pain.</p>
|
||||
<p>Note that the enumerated status keys in the service_names_to_actions_to_tags structure are strings, not ints (JSON does not support int keys for Objects).</p>
|
||||
<p>Response description: 200 and no content.</p>
|
||||
<p>Note also that hydrus tag actions are safely idempotent. You can pend a tag that is already pended and not worry about an error--it will be discarded. The same for other reasonable logical scenarios: deleting a tag that does not exist will silently make no change, pending a tag that is already 'current' will again be passed over. It is fine to just throw 'process this' tags at every file import you add and not have to worry about checking which files you already added it to.</p>
|
||||
</ul>
|
||||
|
@ -491,9 +494,9 @@
|
|||
</ul>
|
||||
</div>
|
||||
<h3>Searching Files</h3>
|
||||
|
||||
<p>File search in hydrus is not paginated like a booru--all searches return all results in one go. In order to keep this fast, search is split into two steps--fetching file identifiers with a search, and then fetching file metadata in batches. You may have noticed that the client itself performs searches like this--thinking a bit about a search and then bundling results in batches of 256 files before eventually throwing all the thumbnails on screen.</p>
|
||||
<div class="apiborder" id="get_files_search_files">
|
||||
<h3><b>POST /get_files/search_files</b></h3>
|
||||
<h3><b>GET /get_files/search_files</b></h3>
|
||||
<p><i>Search for the client's files.</i></p>
|
||||
<ul>
|
||||
<li>
|
||||
|
@ -506,8 +509,8 @@
|
|||
<p>Arguments (in percent-encoded JSON):</p>
|
||||
<ul>
|
||||
<li>tags : (a list of tags you wish to search for)</li>
|
||||
<li>system_inbox : true or false (optional, defaulting to false)
|
||||
<li>system_archive : true or false (optional, defaulting to false)
|
||||
<li>system_inbox : true or false (optional, defaulting to false)</li>
|
||||
<li>system_archive : true or false (optional, defaulting to false)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -528,21 +531,173 @@
|
|||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<p>File ids are internal and specific to an individual client. For a client, a file with hash H always has the same file id N, but two clients will have different ideas about which N goes with which H. They are a bit faster than hashes to retrieve and search with <i>en masse</i>, which is why they are exposed here.</p>
|
||||
<p>The search will be performed on the 'local files' file domain and 'all known tags' tag domain.</p>
|
||||
<p>Note that most clients will have an invisible system:limit of 10,000 files on all queries. I expect to add more system predicates to help searching for untagged files, but it is tricky to fetch all files under any circumstance. Large queries may take several seconds to respond.</p>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="apiborder">
|
||||
<h3><b>GET file metadata</b></h3>
|
||||
# id to info objects
|
||||
# hashes only arg
|
||||
<div class="apiborder" id="get_files_file_metadata">
|
||||
<h3><b>GET /get_files/file_metadata</b></h3>
|
||||
<p><i>Get metadata about files in the client.</i></p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Headers:</p>
|
||||
<ul>
|
||||
<li>Hydrus-Client-API-Access-Key : (Your hexadecimal access key)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Arguments (in percent-encoded JSON):</p>
|
||||
<ul>
|
||||
<li>file_ids : (a list of numerical file ids)</li>
|
||||
<li>hashes : (a list of hexadecimal SHA256 hashes)</li>
|
||||
<li>only_return_identifiers : true or false (optional, defaulting to false)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<p>You need one of file_ids or hashes. If your access key is restricted by tag, you cannot search by hashes, and <b>the file_ids you search for must have been in the most recent search result</b>.</p>
|
||||
<li>
|
||||
<p>Example request for two files with ids 123 and 4567:</p>
|
||||
<ul>
|
||||
<li><p>/get_files/file_metadata?file_ids=%5B123%2C%204567%5D</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>The same, but only wants hashes back:</p>
|
||||
<ul>
|
||||
<li><p>/get_files/file_metadata?file_ids=%5B123%2C%204567%5D&only_return_identifiers=true</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>And one that fetches two hashes, 4c77267f93415de0bc33b7725b8c331a809a924084bee03ab2f5fae1c6019eb2 and 3e7cb9044fe81bda0d7a84b5cb781cba4e255e4871cba6ae8ecd8207850d5b82:</p>
|
||||
<ul>
|
||||
<li><p>/get_files/file_metadata?hashes=%5B%224c77267f93415de0bc33b7725b8c331a809a924084bee03ab2f5fae1c6019eb2%22%2C%20%223e7cb9044fe81bda0d7a84b5cb781cba4e255e4871cba6ae8ecd8207850d5b82%22%5D</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<p>This request string can obviously get pretty ridiculously long. It also takes a bit of time to fetch metadata from the database. In its normal searches, the client usually fetches file metadata in batches of 256.</p>
|
||||
<p>Response description: A list of JSON Objects that store a variety of file metadata.</p>
|
||||
<li>
|
||||
<p>Example response:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<pre>{
|
||||
"metadata" : [
|
||||
{
|
||||
"file_id" : 123,
|
||||
"hash" : "4c77267f93415de0bc33b7725b8c331a809a924084bee03ab2f5fae1c6019eb2",
|
||||
"size" : 63405,
|
||||
"mime" : "image/jpg",
|
||||
"width" : 640,
|
||||
"height" : 480,
|
||||
"duration" : null,
|
||||
"num_frames" : null,
|
||||
"num_words" : null,
|
||||
"service_names_to_statuses_to_tags" : {}
|
||||
},
|
||||
{
|
||||
"file_id" : 4567,
|
||||
"hash" : "3e7cb9044fe81bda0d7a84b5cb781cba4e255e4871cba6ae8ecd8207850d5b82",
|
||||
"size" : 199713,
|
||||
"mime" : "video/webm",
|
||||
"width" : 1920,
|
||||
"height" : 1080,
|
||||
"duration" : 4040,
|
||||
"num_frames" : 102,
|
||||
"num_words" : null
|
||||
"service_names_to_statuses_to_tags" : {
|
||||
"local tags" : {
|
||||
"0" : [ "blonde hair", "blue eyes", "looking at viewer" ]
|
||||
"1" : [ "bodysuit" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}</pre>
|
||||
</li>
|
||||
</ul>
|
||||
<p>And one where only_return_identifiers is true:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<pre>{
|
||||
"metadata" : [
|
||||
{
|
||||
"file_id" : 123,
|
||||
"hash" : "4c77267f93415de0bc33b7725b8c331a809a924084bee03ab2f5fae1c6019eb2"
|
||||
},
|
||||
{
|
||||
"file_id" : 4567,
|
||||
"hash" : "3e7cb9044fe81bda0d7a84b5cb781cba4e255e4871cba6ae8ecd8207850d5b82"
|
||||
}
|
||||
]
|
||||
}</pre>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<p>Size is in bytes. Duration is in milliseconds, and may be an int or a float.</p>
|
||||
<p>The tags structure is similar to the /add_tags/add_tags scheme, excepting that the status numbers are:</p>
|
||||
<ul>
|
||||
<li>0 - current</li>
|
||||
<li>1 - pending</li>
|
||||
<li>2 - deleted</li>
|
||||
<li>3 - petitioned</li>
|
||||
</ul>
|
||||
<p>Note that since JSON Object keys must be strings, these status numbers are strings, not ints.</p>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="apiborder">
|
||||
<h3><b>GET file</b></h3>
|
||||
<div class="apiborder" id="get_files_file">
|
||||
<h3><b>GET /get_files/file</b></h3>
|
||||
<p><i>Get a file.</i></p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Headers:</p>
|
||||
<ul>
|
||||
<li>Hydrus-Client-API-Access-Key : (Your hexadecimal access key)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Arguments :</p>
|
||||
<ul>
|
||||
<li>file_id : (numerical file id for the file)</li>
|
||||
<li>hash : (a hexadecimal SHA256 hash for the file)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<p>Only use one. As with metadata fetching, you may only use the hash argument if you have access to all files. If you are tag-restricted, you will have to use a file_id in the last search you ran.</p>
|
||||
<li>
|
||||
<p>Example requests:</p>
|
||||
<ul>
|
||||
<li><p>/get_files/file?file_id=452158</p></li>
|
||||
<li><p>/get_files/file?hash=7f30c113810985b69014957c93bc25e8eb4cf3355dae36d8b9d011d8b0cf623a</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Response description: The file itself. You should get the correct mime type as the Content-Type header.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="apiborder">
|
||||
<h3><b>GET thumbnail</b></h3>
|
||||
<div class="apiborder" id="get_files_thumbnail">
|
||||
<h3><b>GET /get_files/thumbnail</b></h3>
|
||||
<p><i>Get a file's thumbnail.</i></p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Headers:</p>
|
||||
<ul>
|
||||
<li>Hydrus-Client-API-Access-Key : (Your hexadecimal access key)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Arguments :</p>
|
||||
<ul>
|
||||
<li>file_id : (numerical file id for the file)</li>
|
||||
<li>hash : (a hexadecimal SHA256 hash for the file)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<p>Only use one. As with metadata fetching, you may only use the hash argument if you have access to all files. If you are tag-restricted, you will have to use a file_id in the last search you ran.</p>
|
||||
<li>
|
||||
<p>Example requests:</p>
|
||||
<ul>
|
||||
<li><p>/get_files/thumbnail?file_id=452158</p></li>
|
||||
<li><p>/get_files/thumbnail?hash=7f30c113810985b69014957c93bc25e8eb4cf3355dae36d8b9d011d8b0cf623a</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Response description: The thumbnail for the file. It will give application/octet-stream as the mime type. Some hydrus thumbs are jpegs, some are pngs.</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<li><a href="advanced_siblings.html">advanced usage - tag siblings</a></li>
|
||||
<li><a href="advanced_parents.html">advanced usage - tag parents</a></li>
|
||||
<li><a href="database_migration.html">database migration</a></li>
|
||||
<li><a href="client_api.html">client api (under construction)</a></li>
|
||||
<li><a href="client_api.html">client api</a></li>
|
||||
<li><a href="ipfs.html">ipfs</a></li>
|
||||
<li><a href="local_booru.html">the local booru</a></li>
|
||||
<li><a href="server.html">setting up your own server</a></li>
|
||||
|
|
|
@ -231,7 +231,7 @@ class APIPermissions( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
if len( filtered_tags ) > 0:
|
||||
|
||||
return len( filtered_tags ) == len( tags )
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
@ -239,6 +239,17 @@ class APIPermissions( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
|
||||
|
||||
def CheckCanSeeAllFiles( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
if not ( self._HasPermission( CLIENT_API_PERMISSION_SEARCH_FILES ) and self._search_tag_filter.AllowsEverything() ):
|
||||
|
||||
raise HydrusExceptions.InsufficientCredentialsException( 'You do not have permission to see all files, so you cannot do this.' )
|
||||
|
||||
|
||||
|
||||
|
||||
def CheckPermission( self, permission ):
|
||||
|
||||
if not self.HasPermission( permission ):
|
||||
|
@ -270,7 +281,7 @@ class APIPermissions( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
error_text = error_text.format( HydrusData.ToHumanInt( num_files_asked_for ), HydrusData.ToHumanInt( num_files_allowed_to_see ) )
|
||||
|
||||
raise HydrusExceptions.BadRequestException( error_text )
|
||||
raise HydrusExceptions.InsufficientCredentialsException( error_text )
|
||||
|
||||
|
||||
self._search_results_timeout = HydrusData.GetNow() + SEARCH_RESULTS_CACHE_TIMEOUT
|
||||
|
|
|
@ -5680,6 +5680,22 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
def _GetHashIdsToHashes( self, hash_ids = None, hashes = None ):
|
||||
|
||||
if hash_ids is not None:
|
||||
|
||||
self._PopulateHashIdsToHashesCache( hash_ids, exception_on_error = True )
|
||||
|
||||
hash_ids_to_hashes = { hash_id : self._hash_ids_to_hashes_cache[ hash_id ] for hash_id in hash_ids }
|
||||
|
||||
elif hashes is not None:
|
||||
|
||||
hash_ids_to_hashes = { self._GetHashId( hash ) : hash for hash in hashes }
|
||||
|
||||
|
||||
return hash_ids_to_hashes
|
||||
|
||||
|
||||
def _GetHashIdStatus( self, hash_id, prefix = '' ):
|
||||
|
||||
hash = self._GetHash( hash_id )
|
||||
|
@ -7796,7 +7812,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
def _PopulateHashIdsToHashesCache( self, hash_ids ):
|
||||
def _PopulateHashIdsToHashesCache( self, hash_ids, exception_on_error = False ):
|
||||
|
||||
if len( self._hash_ids_to_hashes_cache ) > 25000:
|
||||
|
||||
|
@ -7819,6 +7835,11 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if hash_id not in uncached_hash_ids_to_hashes:
|
||||
|
||||
if exception_on_error:
|
||||
|
||||
raise HydrusExceptions.DataMissing( 'Did not find all entries for those hash ids!' )
|
||||
|
||||
|
||||
HydrusData.DebugPrint( 'Database hash error: hash_id ' + str( hash_id ) + ' was missing!' )
|
||||
|
||||
if not pubbed_error:
|
||||
|
@ -9223,6 +9244,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
elif action == 'filter_existing_tags': result = self._FilterExistingTags( *args, **kwargs )
|
||||
elif action == 'filter_hashes': result = self._FilterHashes( *args, **kwargs )
|
||||
elif action == 'force_refresh_tags_managers': result = self._GetForceRefreshTagsManagers( *args, **kwargs )
|
||||
elif action == 'hash_ids_to_hashes': result = self._GetHashIdsToHashes( *args, **kwargs )
|
||||
elif action == 'hash_status': result = self._GetHashStatus( *args, **kwargs )
|
||||
elif action == 'imageboards': result = self._GetYAMLDump( YAML_DUMP_ID_IMAGEBOARD, *args, **kwargs )
|
||||
elif action == 'in_inbox': result = self._InInbox( *args, **kwargs )
|
||||
|
|
|
@ -232,6 +232,10 @@ def CalculateMediaSize( media, zoom ):
|
|||
|
||||
return ( media_width, media_height )
|
||||
|
||||
def IsStaticImage( media ):
|
||||
|
||||
return media.GetMime() in HC.IMAGES and not ShouldHaveAnimationBar( media )
|
||||
|
||||
def ShouldHaveAnimationBar( media ):
|
||||
|
||||
is_animated_gif = media.GetMime() == HC.IMAGE_GIF and media.HasDuration()
|
||||
|
@ -602,7 +606,11 @@ class Animation( wx.Window ):
|
|||
|
||||
self._frame_bmp = None
|
||||
|
||||
if self._media is not None:
|
||||
if self._media is None:
|
||||
|
||||
HG.client_controller.gui.UnregisterAnimationUpdateWindow( self )
|
||||
|
||||
else:
|
||||
|
||||
HG.client_controller.gui.RegisterAnimationUpdateWindow( self )
|
||||
|
||||
|
@ -610,6 +618,11 @@ class Animation( wx.Window ):
|
|||
|
||||
|
||||
|
||||
def SetNoneMedia( self ):
|
||||
|
||||
self.SetMedia( None )
|
||||
|
||||
|
||||
def TIMERAnimationUpdate( self ):
|
||||
|
||||
try:
|
||||
|
@ -3885,22 +3898,10 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
|
|||
previous = self._current_media
|
||||
next = self._current_media
|
||||
|
||||
if self._just_started:
|
||||
|
||||
delay_base = 0.8
|
||||
|
||||
num_to_go_back = 1
|
||||
num_to_go_forward = 1
|
||||
|
||||
self._just_started = False
|
||||
|
||||
else:
|
||||
|
||||
delay_base = 0.4
|
||||
|
||||
num_to_go_back = 3
|
||||
num_to_go_forward = 5
|
||||
|
||||
delay_base = 0.1
|
||||
|
||||
num_to_go_back = 3
|
||||
num_to_go_forward = 5
|
||||
|
||||
# if media_looked_at nukes the list, we want shorter delays, so do next first
|
||||
|
||||
|
@ -3940,8 +3941,6 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
|
|||
to_render.append( ( previous, delay ) )
|
||||
|
||||
|
||||
( my_width, my_height ) = self.GetClientSize()
|
||||
|
||||
image_cache = HG.client_controller.GetCache( 'images' )
|
||||
|
||||
for ( media, delay ) in to_render:
|
||||
|
@ -3949,7 +3948,7 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
|
|||
hash = media.GetHash()
|
||||
mime = media.GetMime()
|
||||
|
||||
if mime in ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_WEBP, HC.IMAGE_TIFF ):
|
||||
if IsStaticImage( media ):
|
||||
|
||||
if not image_cache.HasImageRenderer( hash ):
|
||||
|
||||
|
@ -4929,7 +4928,13 @@ class MediaContainer( wx.Window ):
|
|||
self._embed_button = EmbedButton( self )
|
||||
self._embed_button.Bind( wx.EVT_LEFT_DOWN, self.EventEmbedButton )
|
||||
|
||||
self._animation_window = Animation( self )
|
||||
self._animation_bar = AnimationBar( self )
|
||||
self._static_image_window = StaticImage( self )
|
||||
|
||||
self._animation_window.Hide()
|
||||
self._animation_bar.Hide()
|
||||
self._static_image_window.Hide()
|
||||
|
||||
self.Hide()
|
||||
|
||||
|
@ -4938,16 +4943,19 @@ class MediaContainer( wx.Window ):
|
|||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
|
||||
def _DestroyThisMediaWindow( self, media_window ):
|
||||
def _DestroyOrHideThisMediaWindow( self, media_window ):
|
||||
|
||||
if media_window is not None:
|
||||
|
||||
if isinstance( media_window, Animation ):
|
||||
if isinstance( media_window, ( Animation, StaticImage ) ):
|
||||
|
||||
media_window.SetMedia( None )
|
||||
media_window.SetNoneMedia()
|
||||
media_window.Hide()
|
||||
|
||||
else:
|
||||
|
||||
media_window.DestroyLater()
|
||||
|
||||
|
||||
media_window.DestroyLater()
|
||||
|
||||
|
||||
|
||||
|
@ -5021,7 +5029,9 @@ class MediaContainer( wx.Window ):
|
|||
|
||||
else:
|
||||
|
||||
self._media_window = Animation( self )
|
||||
self._animation_window.Show()
|
||||
|
||||
self._media_window = self._animation_window
|
||||
|
||||
|
||||
self._media_window.SetMedia( self._media, start_paused = start_paused )
|
||||
|
@ -5046,7 +5056,9 @@ class MediaContainer( wx.Window ):
|
|||
|
||||
else:
|
||||
|
||||
self._media_window = StaticImage( self )
|
||||
self._static_image_window.Show()
|
||||
|
||||
self._media_window = self._static_image_window
|
||||
|
||||
|
||||
self._media_window.SetMedia( self._media )
|
||||
|
@ -5057,7 +5069,7 @@ class MediaContainer( wx.Window ):
|
|||
|
||||
if old_media_window is not None and destroy_old_media_window:
|
||||
|
||||
self._DestroyThisMediaWindow( old_media_window )
|
||||
self._DestroyOrHideThisMediaWindow( old_media_window )
|
||||
|
||||
|
||||
|
||||
|
@ -5247,7 +5259,7 @@ class MediaContainer( wx.Window ):
|
|||
|
||||
self._HideAnimationBar()
|
||||
|
||||
self._DestroyThisMediaWindow( self._media_window )
|
||||
self._DestroyOrHideThisMediaWindow( self._media_window )
|
||||
|
||||
self._media_window = None
|
||||
|
||||
|
@ -5285,10 +5297,10 @@ class MediaContainer( wx.Window ):
|
|||
|
||||
self._media = None
|
||||
|
||||
self._DestroyThisMediaWindow( self._media_window )
|
||||
|
||||
self._HideAnimationBar()
|
||||
|
||||
self._DestroyOrHideThisMediaWindow( self._media_window )
|
||||
|
||||
self._media_window = None
|
||||
|
||||
self.Hide()
|
||||
|
@ -5676,16 +5688,22 @@ class StaticImage( wx.Window ):
|
|||
HG.client_controller.gui.RegisterAnimationUpdateWindow( self )
|
||||
|
||||
|
||||
self._dirty = True
|
||||
self._SetDirty()
|
||||
|
||||
self.Refresh()
|
||||
|
||||
def SetNoneMedia( self ):
|
||||
|
||||
self._media = None
|
||||
self._image_renderer = None
|
||||
self._is_rendered = False
|
||||
self._first_background_drawn = False
|
||||
|
||||
|
||||
def TIMERAnimationUpdate( self ):
|
||||
|
||||
try:
|
||||
|
||||
if self._image_renderer.IsReady():
|
||||
if self._image_renderer is None or self._image_renderer.IsReady():
|
||||
|
||||
self._SetDirty()
|
||||
|
||||
|
|
|
@ -585,9 +585,11 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( self, filenames_callable = self._AddPathsToList ) )
|
||||
|
||||
listctrl_panel = ClientGUIListCtrl.SaneListCtrlPanel( self )
|
||||
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
|
||||
|
||||
self._paths_list = ClientGUIListCtrl.SaneListCtrl( listctrl_panel, 120, [ ( 'path', -1 ), ( 'guessed mime', 110 ), ( 'size', 60 ) ], delete_key_callback = self.RemovePaths )
|
||||
columns = [ ( '#', 4 ), ( 'path', -1 ), ( 'guessed mime', 16 ), ( 'size', 10 ) ]
|
||||
|
||||
self._paths_list = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, 'input_local_files', 28, 36, columns, self._ConvertListCtrlDataToTuple, delete_key_callback = self.RemovePaths )
|
||||
|
||||
listctrl_panel.SetListCtrl( self._paths_list )
|
||||
|
||||
|
@ -675,8 +677,7 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
self._current_paths = []
|
||||
self._current_paths_set = set()
|
||||
self._current_path_data = {}
|
||||
|
||||
self._job_key = ClientThreading.JobKey()
|
||||
|
||||
|
@ -715,6 +716,20 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
self._unparsed_paths_queue.put( paths )
|
||||
|
||||
|
||||
def _ConvertListCtrlDataToTuple( self, path ):
|
||||
|
||||
( index, mime, size ) = self._current_path_data[ path ]
|
||||
|
||||
pretty_index = HydrusData.ToHumanInt( index )
|
||||
pretty_mime = HC.mime_string_lookup[ mime ]
|
||||
pretty_size = HydrusData.ToHumanBytes( size )
|
||||
|
||||
display_tuple = ( pretty_index, path, pretty_mime, pretty_size )
|
||||
sort_tuple = ( index, path, pretty_mime, size )
|
||||
|
||||
return ( display_tuple, sort_tuple )
|
||||
|
||||
|
||||
def _TidyUp( self ):
|
||||
|
||||
self._pause_event.set()
|
||||
|
@ -769,7 +784,9 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
|
||||
self._TidyUp()
|
||||
|
||||
if len( self._current_paths ) > 0:
|
||||
paths = self._paths_list.GetData()
|
||||
|
||||
if len( paths ) > 0:
|
||||
|
||||
file_import_options = self._file_import_options.GetValue()
|
||||
|
||||
|
@ -777,7 +794,7 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
|
||||
delete_after_success = self._delete_after_success.GetValue()
|
||||
|
||||
HG.client_controller.pub( 'new_hdd_import', self._current_paths, file_import_options, paths_to_service_keys_to_tags, delete_after_success )
|
||||
HG.client_controller.pub( 'new_hdd_import', paths, file_import_options, paths_to_service_keys_to_tags, delete_after_success )
|
||||
|
||||
|
||||
self.Close()
|
||||
|
@ -785,13 +802,15 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
|
||||
def EventTags( self, event ):
|
||||
|
||||
if len( self._current_paths ) > 0:
|
||||
paths = self._paths_list.GetData()
|
||||
|
||||
if len( paths ) > 0:
|
||||
|
||||
file_import_options = self._file_import_options.GetValue()
|
||||
|
||||
with ClientGUITopLevelWindows.DialogEdit( self, 'filename tagging', frame_key = 'local_import_filename_tagging' ) as dlg:
|
||||
|
||||
panel = ClientGUIImport.EditLocalImportFilenameTaggingPanel( dlg, self._current_paths )
|
||||
panel = ClientGUIImport.EditLocalImportFilenameTaggingPanel( dlg, paths )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
|
@ -801,7 +820,7 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
|
||||
delete_after_success = self._delete_after_success.GetValue()
|
||||
|
||||
HG.client_controller.pub( 'new_hdd_import', self._current_paths, file_import_options, paths_to_service_keys_to_tags, delete_after_success )
|
||||
HG.client_controller.pub( 'new_hdd_import', paths, file_import_options, paths_to_service_keys_to_tags, delete_after_success )
|
||||
|
||||
self.Close()
|
||||
|
||||
|
@ -832,10 +851,29 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
|
||||
self._paths_list.RemoveAllSelected()
|
||||
paths_to_delete = self._paths_list.GetData( only_selected = True )
|
||||
|
||||
self._current_paths = [ row[0] for row in self._paths_list.GetClientData() ]
|
||||
self._current_paths_set = set( self._current_paths )
|
||||
self._paths_list.DeleteSelected()
|
||||
|
||||
for path in paths_to_delete:
|
||||
|
||||
del self._current_path_data[ path ]
|
||||
|
||||
|
||||
flat_path_data = [ ( index, path, mime, size ) for ( path, ( index, mime, size ) ) in self._current_path_data.items() ]
|
||||
|
||||
flat_path_data.sort()
|
||||
|
||||
new_index = 1
|
||||
|
||||
for ( old_index, path, mime, size ) in flat_path_data:
|
||||
|
||||
self._current_path_data[ path ] = ( new_index, mime, size )
|
||||
|
||||
new_index += 1
|
||||
|
||||
|
||||
self._paths_list.UpdateDatas()
|
||||
|
||||
|
||||
|
||||
|
@ -1051,20 +1089,25 @@ class FrameInputLocalFiles( wx.Frame ):
|
|||
|
||||
def TIMERUIUpdate( self ):
|
||||
|
||||
good_paths = list()
|
||||
|
||||
while not self._parsed_path_queue.empty():
|
||||
|
||||
( path, mime, size ) = self._parsed_path_queue.get()
|
||||
|
||||
pretty_mime = HC.mime_string_lookup[ mime ]
|
||||
pretty_size = HydrusData.ToHumanBytes( size )
|
||||
if not self._paths_list.HasData( path ):
|
||||
|
||||
good_paths.append( path )
|
||||
|
||||
index = len( self._current_path_data ) + 1
|
||||
|
||||
self._current_path_data[ path ] = ( index, mime, size )
|
||||
|
||||
|
||||
if path not in self._current_paths_set:
|
||||
|
||||
self._current_paths_set.add( path )
|
||||
self._current_paths.append( path )
|
||||
|
||||
self._paths_list.Append( ( path, pretty_mime, pretty_size ), ( path, mime, size ) )
|
||||
|
||||
|
||||
if len( good_paths ) > 0:
|
||||
|
||||
self._paths_list.AddDatas( good_paths )
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -1715,15 +1715,13 @@ class GalleryImportPanel( ClientGUICommon.StaticBox ):
|
|||
self._file_seed_cache_control = ClientGUIFileSeedCache.FileSeedCacheStatusControl( self._import_queue_panel, HG.client_controller, self._page_key )
|
||||
self._file_download_control = ClientGUIControls.NetworkJobControl( self._import_queue_panel )
|
||||
|
||||
self._files_pause_button = wx.BitmapButton( self._import_queue_panel, bitmap = CC.GlobalBMPs.pause )
|
||||
self._files_pause_button.Bind( wx.EVT_BUTTON, self.EventFilesPause )
|
||||
self._files_pause_button = ClientGUICommon.BetterBitmapButton( self._import_queue_panel, CC.GlobalBMPs.pause, self.PauseFiles )
|
||||
|
||||
self._gallery_panel = ClientGUICommon.StaticBox( self, 'gallery parser' )
|
||||
|
||||
self._gallery_status = ClientGUICommon.BetterStaticText( self._gallery_panel, style = wx.ST_ELLIPSIZE_END )
|
||||
|
||||
self._gallery_pause_button = wx.BitmapButton( self._gallery_panel, bitmap = CC.GlobalBMPs.pause )
|
||||
self._gallery_pause_button.Bind( wx.EVT_BUTTON, self.EventGalleryPause )
|
||||
self._gallery_pause_button = ClientGUICommon.BetterBitmapButton( self._gallery_panel, CC.GlobalBMPs.pause, self.PauseGallery )
|
||||
|
||||
self._gallery_seed_log_control = ClientGUIGallerySeedLog.GallerySeedLogStatusControl( self._gallery_panel, HG.client_controller, False, True, page_key = self._page_key )
|
||||
|
||||
|
@ -1922,7 +1920,7 @@ class GalleryImportPanel( ClientGUICommon.StaticBox ):
|
|||
event.Skip()
|
||||
|
||||
|
||||
def EventFilesPause( self, event ):
|
||||
def PauseFiles( self ):
|
||||
|
||||
if self._gallery_import is not None:
|
||||
|
||||
|
@ -1932,7 +1930,7 @@ class GalleryImportPanel( ClientGUICommon.StaticBox ):
|
|||
|
||||
|
||||
|
||||
def EventGalleryPause( self, event ):
|
||||
def PauseGallery( self ):
|
||||
|
||||
if self._gallery_import is not None:
|
||||
|
||||
|
@ -2241,8 +2239,7 @@ class WatcherReviewPanel( ClientGUICommon.StaticBox ):
|
|||
|
||||
imports_panel = ClientGUICommon.StaticBox( self._options_panel, 'file imports' )
|
||||
|
||||
self._files_pause_button = wx.BitmapButton( imports_panel, bitmap = CC.GlobalBMPs.pause )
|
||||
self._files_pause_button.Bind( wx.EVT_BUTTON, self.EventPauseFiles )
|
||||
self._files_pause_button = ClientGUICommon.BetterBitmapButton( imports_panel, CC.GlobalBMPs.pause, self.PauseFiles )
|
||||
|
||||
self._file_status = ClientGUICommon.BetterStaticText( imports_panel, style = wx.ST_ELLIPSIZE_END )
|
||||
self._file_seed_cache_control = ClientGUIFileSeedCache.FileSeedCacheStatusControl( imports_panel, HG.client_controller, self._page_key )
|
||||
|
@ -2254,8 +2251,7 @@ class WatcherReviewPanel( ClientGUICommon.StaticBox ):
|
|||
|
||||
self._file_velocity_status = ClientGUICommon.BetterStaticText( checker_panel, style = wx.ST_ELLIPSIZE_END )
|
||||
|
||||
self._checking_pause_button = wx.BitmapButton( checker_panel, bitmap = CC.GlobalBMPs.pause )
|
||||
self._checking_pause_button.Bind( wx.EVT_BUTTON, self.EventPauseChecking )
|
||||
self._checking_pause_button = ClientGUICommon.BetterBitmapButton( checker_panel, CC.GlobalBMPs.pause, self.PauseChecking )
|
||||
|
||||
self._watcher_status = ClientGUICommon.BetterStaticText( checker_panel, style = wx.ST_ELLIPSIZE_END )
|
||||
|
||||
|
@ -2520,21 +2516,21 @@ class WatcherReviewPanel( ClientGUICommon.StaticBox ):
|
|||
|
||||
|
||||
|
||||
def EventPauseFiles( self, event ):
|
||||
def PauseChecking( self ):
|
||||
|
||||
if self._watcher is not None:
|
||||
|
||||
self._watcher.PausePlayFiles()
|
||||
self._watcher.PausePlayChecking()
|
||||
|
||||
self._UpdateStatus()
|
||||
|
||||
|
||||
|
||||
def EventPauseChecking( self, event ):
|
||||
def PauseFiles( self ):
|
||||
|
||||
if self._watcher is not None:
|
||||
|
||||
self._watcher.PausePlayChecking()
|
||||
self._watcher.PausePlayFiles()
|
||||
|
||||
self._UpdateStatus()
|
||||
|
||||
|
|
|
@ -1444,8 +1444,7 @@ class ManagementPanelImporterHDD( ManagementPanelImporter ):
|
|||
self._current_action = ClientGUICommon.BetterStaticText( self._import_queue_panel )
|
||||
self._file_seed_cache_control = ClientGUIFileSeedCache.FileSeedCacheStatusControl( self._import_queue_panel, self._controller, self._page_key )
|
||||
|
||||
self._pause_button = wx.BitmapButton( self._import_queue_panel, bitmap = CC.GlobalBMPs.pause )
|
||||
self._pause_button.Bind( wx.EVT_BUTTON, self.EventPause )
|
||||
self._pause_button = ClientGUICommon.BetterBitmapButton( self._import_queue_panel, CC.GlobalBMPs.pause, self.Pause )
|
||||
|
||||
self._hdd_import = self._management_controller.GetVariable( 'hdd_import' )
|
||||
|
||||
|
@ -1518,7 +1517,7 @@ class ManagementPanelImporterHDD( ManagementPanelImporter ):
|
|||
|
||||
|
||||
|
||||
def EventPause( self, event ):
|
||||
def Pause( self ):
|
||||
|
||||
self._hdd_import.PausePlay()
|
||||
|
||||
|
@ -2937,8 +2936,7 @@ class ManagementPanelImporterSimpleDownloader( ManagementPanelImporter ):
|
|||
|
||||
self._import_queue_panel = ClientGUICommon.StaticBox( self._simple_downloader_panel, 'imports' )
|
||||
|
||||
self._pause_files_button = wx.BitmapButton( self._import_queue_panel, bitmap = CC.GlobalBMPs.pause )
|
||||
self._pause_files_button.Bind( wx.EVT_BUTTON, self.EventPauseFiles )
|
||||
self._pause_files_button = ClientGUICommon.BetterBitmapButton( self._import_queue_panel, CC.GlobalBMPs.pause, self.PauseFiles )
|
||||
|
||||
self._current_action = ClientGUICommon.BetterStaticText( self._import_queue_panel, style = wx.ST_ELLIPSIZE_END )
|
||||
self._file_seed_cache_control = ClientGUIFileSeedCache.FileSeedCacheStatusControl( self._import_queue_panel, self._controller, self._page_key )
|
||||
|
@ -2950,8 +2948,7 @@ class ManagementPanelImporterSimpleDownloader( ManagementPanelImporter ):
|
|||
|
||||
self._simple_parsing_jobs_panel = ClientGUICommon.StaticBox( self._simple_downloader_panel, 'simple parsing urls' )
|
||||
|
||||
self._pause_queue_button = wx.BitmapButton( self._simple_parsing_jobs_panel, bitmap = CC.GlobalBMPs.pause )
|
||||
self._pause_queue_button.Bind( wx.EVT_BUTTON, self.EventPauseQueue )
|
||||
self._pause_queue_button = ClientGUICommon.BetterBitmapButton( self._simple_parsing_jobs_panel, CC.GlobalBMPs.pause, self.PauseQueue )
|
||||
|
||||
self._parser_status = ClientGUICommon.BetterStaticText( self._simple_parsing_jobs_panel, style = wx.ST_ELLIPSIZE_END )
|
||||
|
||||
|
@ -3333,14 +3330,14 @@ class ManagementPanelImporterSimpleDownloader( ManagementPanelImporter ):
|
|||
event.Skip()
|
||||
|
||||
|
||||
def EventPauseQueue( self, event ):
|
||||
def PauseQueue( self ):
|
||||
|
||||
self._simple_downloader_import.PausePlayQueue()
|
||||
|
||||
self._UpdateImportStatus()
|
||||
|
||||
|
||||
def EventPauseFiles( self, event ):
|
||||
def PauseFiles( self ):
|
||||
|
||||
self._simple_downloader_import.PausePlayFiles()
|
||||
|
||||
|
@ -3369,8 +3366,7 @@ class ManagementPanelImporterURLs( ManagementPanelImporter ):
|
|||
|
||||
self._url_panel = ClientGUICommon.StaticBox( self, 'url downloader' )
|
||||
|
||||
self._pause_button = wx.BitmapButton( self._url_panel, bitmap = CC.GlobalBMPs.pause )
|
||||
self._pause_button.Bind( wx.EVT_BUTTON, self.EventPause )
|
||||
self._pause_button = ClientGUICommon.BetterBitmapButton( self._url_panel, CC.GlobalBMPs.pause, self.Pause )
|
||||
|
||||
self._file_download_control = ClientGUIControls.NetworkJobControl( self._url_panel )
|
||||
|
||||
|
@ -3471,7 +3467,7 @@ class ManagementPanelImporterURLs( ManagementPanelImporter ):
|
|||
|
||||
|
||||
|
||||
def EventPause( self, event ):
|
||||
def Pause( self ):
|
||||
|
||||
self._urls_import.PausePlay()
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ def OpenURLs( urls ):
|
|||
|
||||
if num_urls > 5:
|
||||
|
||||
job_key = ClientThreading.JobKey( cancellable = True )
|
||||
job_key = ClientThreading.JobKey( pausable = True, cancellable = True )
|
||||
|
||||
job_key.SetVariable( 'popup_title', 'Opening URLs' )
|
||||
|
||||
|
@ -131,7 +131,9 @@ def OpenURLs( urls ):
|
|||
|
||||
if job_key is not None:
|
||||
|
||||
if job_key.IsCancelled():
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return
|
||||
|
||||
|
|
|
@ -4099,12 +4099,10 @@ class ScriptManagementControl( wx.Panel ):
|
|||
self._status = ClientGUICommon.BetterStaticText( main_panel )
|
||||
self._gauge = ClientGUICommon.Gauge( main_panel )
|
||||
|
||||
self._link_button = wx.BitmapButton( main_panel, bitmap = CC.GlobalBMPs.link )
|
||||
self._link_button.Bind( wx.EVT_BUTTON, self.EventLinkButton )
|
||||
self._link_button = ClientGUICommon.BetterBitmapButton( main_panel, CC.GlobalBMPs.link, self.LinkButton )
|
||||
self._link_button.SetToolTip( 'urls found by the script' )
|
||||
|
||||
self._cancel_button = wx.BitmapButton( main_panel, bitmap = CC.GlobalBMPs.stop )
|
||||
self._cancel_button.Bind( wx.EVT_BUTTON, self.EventCancelButton )
|
||||
self._cancel_button = ClientGUICommon.BetterBitmapButton( main_panel, CC.GlobalBMPs.stop, self.CancelButton )
|
||||
|
||||
#
|
||||
|
||||
|
@ -4218,7 +4216,7 @@ class ScriptManagementControl( wx.Panel ):
|
|||
|
||||
|
||||
|
||||
def EventCancelButton( self, event ):
|
||||
def CancelButton( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
|
@ -4229,7 +4227,7 @@ class ScriptManagementControl( wx.Panel ):
|
|||
|
||||
|
||||
|
||||
def EventLinkButton( self, event ):
|
||||
def LinkButton( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
|
|
|
@ -1385,7 +1385,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._listbook.AddPage( 'maintenance and processing', 'maintenance and processing', self._MaintenanceAndProcessingPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'media', 'media', self._MediaPanel( self._listbook ) )
|
||||
#self._listbook.AddPage( 'sound', 'sound', self._SoundPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'default file system predicates', 'default file system predicates', self._DefaultFileSystemPredicatesPanel( self._listbook, self._new_options ) )
|
||||
self._listbook.AddPage( 'default system predicates', 'default system predicates', self._DefaultFileSystemPredicatesPanel( self._listbook, self._new_options ) )
|
||||
self._listbook.AddPage( 'colours', 'colours', self._ColoursPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'regex favourites', 'regex favourites', self._RegexPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'sort/collect', 'sort/collect', self._SortCollectPanel( self._listbook ) )
|
||||
|
|
|
@ -71,6 +71,9 @@ class HydrusServiceClientAPI( HydrusClientService ):
|
|||
root.putChild( b'get_files', get_files )
|
||||
|
||||
get_files.putChild( b'search_files', ClientLocalServerResources.HydrusResourceClientAPIRestrictedGetFilesSearchFiles( self._service, self._client_requests_domain ) )
|
||||
get_files.putChild( b'file_metadata', ClientLocalServerResources.HydrusResourceClientAPIRestrictedGetFilesFileMetadata( self._service, self._client_requests_domain ) )
|
||||
get_files.putChild( b'file', ClientLocalServerResources.HydrusResourceClientAPIRestrictedGetFilesGetFile( self._service, self._client_requests_domain ) )
|
||||
get_files.putChild( b'thumbnail', ClientLocalServerResources.HydrusResourceClientAPIRestrictedGetFilesGetThumbnail( self._service, self._client_requests_domain ) )
|
||||
|
||||
return root
|
||||
|
||||
|
|
|
@ -26,10 +26,10 @@ LOCAL_BOORU_BYTE_PARAMS = { 'share_key', 'hash' }
|
|||
LOCAL_BOORU_STRING_PARAMS = set()
|
||||
LOCAL_BOORU_JSON_PARAMS = set()
|
||||
|
||||
CLIENT_API_INT_PARAMS = set()
|
||||
CLIENT_API_BYTE_PARAMS = set()
|
||||
CLIENT_API_INT_PARAMS = { 'file_id' }
|
||||
CLIENT_API_BYTE_PARAMS = { 'hash' }
|
||||
CLIENT_API_STRING_PARAMS = { 'name', 'url' }
|
||||
CLIENT_API_JSON_PARAMS = { 'basic_permissions', 'system_inbox', 'system_archive', 'tags' }
|
||||
CLIENT_API_JSON_PARAMS = { 'basic_permissions', 'system_inbox', 'system_archive', 'tags', 'file_ids', 'hashes', 'only_return_identifiers' }
|
||||
|
||||
def ParseLocalBooruGETArgs( requests_args ):
|
||||
|
||||
|
@ -806,6 +806,8 @@ class HydrusResourceClientAPIRestrictedAddTagsAddTags( HydrusResourceClientAPIRe
|
|||
|
||||
for ( content_action, tags ) in actions_to_tags.items():
|
||||
|
||||
content_action = int( content_action )
|
||||
|
||||
tags = HydrusTags.CleanTags( tags )
|
||||
|
||||
if len( tags ) == 0:
|
||||
|
@ -1153,6 +1155,8 @@ class HydrusResourceClientAPIRestrictedGetFilesSearchFiles( HydrusResourceClient
|
|||
|
||||
hash_ids = HG.client_controller.Read( 'file_query_ids', file_search_context )
|
||||
|
||||
request.client_api_permissions.SetLastSearchResults( hash_ids )
|
||||
|
||||
body_dict = { 'file_ids' : list( hash_ids ) }
|
||||
|
||||
body = json.dumps( body_dict )
|
||||
|
@ -1166,37 +1170,170 @@ class HydrusResourceClientAPIRestrictedGetFilesGetFile( HydrusResourceClientAPIR
|
|||
|
||||
def _threadDoGETJob( self, request ):
|
||||
|
||||
file_id = request.parsed_request_args[ 'file_id' ]
|
||||
try:
|
||||
|
||||
if 'file_id' in request.parsed_request_args:
|
||||
|
||||
file_id = request.parsed_request_args[ 'file_id' ]
|
||||
|
||||
request.client_api_permissions.CheckPermissionToSeeFiles( ( file_id, ) )
|
||||
|
||||
( media_result, ) = HG.client_controller.Read( 'media_results_from_ids', ( file_id, ) )
|
||||
|
||||
elif 'hash' in request.parsed_request_args:
|
||||
|
||||
request.client_api_permissions.CheckCanSeeAllFiles()
|
||||
|
||||
hash = request.parsed_request_args[ 'hash' ]
|
||||
|
||||
( media_result, ) = HG.client_controller.Read( 'media_results', ( hash, ) )
|
||||
|
||||
else:
|
||||
|
||||
raise HydrusExceptions.BadRequestException( 'Please include a file_id or hash parameter!' )
|
||||
|
||||
|
||||
except HydrusExceptions.DataMissing as e:
|
||||
|
||||
raise HydrusExceptions.NotFoundException( 'One or more of those file identifiers was missing!' )
|
||||
|
||||
|
||||
request.client_api_permissions.CheckPermissionToSeeFiles( ( file_id, ) )
|
||||
|
||||
media_result = 'blah' # get it from controller
|
||||
|
||||
mime = media_result.GetMime()
|
||||
|
||||
client_files_manager = HG.client_controller.client_files_manager
|
||||
|
||||
path = client_files_manager.GetFilePath( hash, mime )
|
||||
try:
|
||||
|
||||
hash = media_result.GetHash()
|
||||
mime = media_result.GetMime()
|
||||
|
||||
path = HG.client_controller.client_files_manager.GetFilePath( hash, mime )
|
||||
|
||||
except HydrusExceptions.FileMissingException:
|
||||
|
||||
raise HydrusExceptions.NotFoundException( 'Could not find that file!' )
|
||||
|
||||
|
||||
response_context = HydrusServerResources.ResponseContext( 200, mime = mime, path = path )
|
||||
|
||||
return response_context
|
||||
|
||||
|
||||
class HydrusResourceClientAPIRestrictedGetFilesGetMetadata( HydrusResourceClientAPIRestrictedGetFiles ):
|
||||
class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClientAPIRestrictedGetFiles ):
|
||||
|
||||
def _threadDoGETJob( self, request ):
|
||||
|
||||
file_ids = request.parsed_request_args[ 'file_ids' ]
|
||||
only_return_identifiers = False
|
||||
|
||||
request.client_api_permissions.CheckPermissionToSeeFiles( file_ids )
|
||||
if 'only_return_identifiers' in request.parsed_request_args:
|
||||
|
||||
only_return_identifiers = request.parsed_request_args[ 'only_return_identifiers' ]
|
||||
|
||||
|
||||
media_results = 'blah' # get it from controller
|
||||
try:
|
||||
|
||||
if 'file_ids' in request.parsed_request_args:
|
||||
|
||||
file_ids = request.parsed_request_args[ 'file_ids' ]
|
||||
|
||||
request.client_api_permissions.CheckPermissionToSeeFiles( file_ids )
|
||||
|
||||
if only_return_identifiers:
|
||||
|
||||
file_ids_to_hashes = HG.client_controller.Read( 'hash_ids_to_hashes', file_ids = file_ids )
|
||||
|
||||
else:
|
||||
|
||||
media_results = HG.client_controller.Read( 'media_results_from_ids', file_ids )
|
||||
|
||||
|
||||
elif 'hashes' in request.parsed_request_args:
|
||||
|
||||
request.client_api_permissions.CheckCanSeeAllFiles()
|
||||
|
||||
hashes = request.parsed_request_args[ 'hashes' ]
|
||||
|
||||
if only_return_identifiers:
|
||||
|
||||
file_ids_to_hashes = HG.client_controller.Read( 'hash_ids_to_hashes', hashes = hashes )
|
||||
|
||||
else:
|
||||
|
||||
media_results = HG.client_controller.Read( 'media_results', hashes )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
raise HydrusExceptions.BadRequestException( 'Please include a file_ids or hashes parameter!' )
|
||||
|
||||
|
||||
except HydrusExceptions.DataMissing as e:
|
||||
|
||||
raise HydrusExceptions.NotFoundException( 'One or more of those file identifiers was missing!' )
|
||||
|
||||
|
||||
# turn media results into json/xml result. maybe start with json to keep it simple
|
||||
body_dict = {}
|
||||
|
||||
metadata = []
|
||||
|
||||
if only_return_identifiers:
|
||||
|
||||
for ( file_id, hash ) in file_ids_to_hashes.items():
|
||||
|
||||
metadata_row = {}
|
||||
|
||||
metadata_row[ 'file_id' ] = file_id
|
||||
metadata_row[ 'hash' ] = hash.hex()
|
||||
|
||||
metadata.append( metadata_row )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
services_manager = HG.client_controller.services_manager
|
||||
|
||||
service_keys_to_names = {}
|
||||
|
||||
for media_result in media_results:
|
||||
|
||||
metadata_row = {}
|
||||
|
||||
file_info_manager = media_result.GetFileInfoManager()
|
||||
|
||||
metadata_row[ 'file_id' ] = file_info_manager.hash_id
|
||||
metadata_row[ 'hash' ] = file_info_manager.hash.hex()
|
||||
metadata_row[ 'size' ] = file_info_manager.size
|
||||
metadata_row[ 'mime' ] = HC.mime_string_lookup[ file_info_manager.mime ]
|
||||
metadata_row[ 'width' ] = file_info_manager.width
|
||||
metadata_row[ 'height' ] = file_info_manager.height
|
||||
metadata_row[ 'duration' ] = file_info_manager.duration
|
||||
metadata_row[ 'num_frames' ] = file_info_manager.num_frames
|
||||
metadata_row[ 'num_words' ] = file_info_manager.num_words
|
||||
|
||||
tags_manager = media_result.GetTagsManager()
|
||||
|
||||
service_names_to_statuses_to_tags = {}
|
||||
|
||||
service_keys_to_statuses_to_tags = tags_manager.GetServiceKeysToStatusesToTags()
|
||||
|
||||
for ( service_key, statuses_to_tags ) in service_keys_to_statuses_to_tags.items():
|
||||
|
||||
if service_key not in service_keys_to_names:
|
||||
|
||||
service_keys_to_names[ service_key ] = services_manager.GetName( service_key )
|
||||
|
||||
|
||||
service_name = service_keys_to_names[ service_key ]
|
||||
|
||||
service_names_to_statuses_to_tags[ service_name ] = { str( status ) : list( tags ) for ( status, tags ) in statuses_to_tags.items() }
|
||||
|
||||
|
||||
metadata_row[ 'service_names_to_statuses_to_tags' ] = service_names_to_statuses_to_tags
|
||||
|
||||
metadata.append( metadata_row )
|
||||
|
||||
|
||||
|
||||
body_dict[ 'metadata' ] = metadata
|
||||
|
||||
mime = HC.APPLICATION_JSON
|
||||
body = 'blah'
|
||||
body = json.dumps( body_dict )
|
||||
|
||||
response_context = HydrusServerResources.ResponseContext( 200, mime = mime, body = body )
|
||||
|
||||
|
@ -1207,19 +1344,47 @@ class HydrusResourceClientAPIRestrictedGetFilesGetThumbnail( HydrusResourceClien
|
|||
|
||||
def _threadDoGETJob( self, request ):
|
||||
|
||||
file_id = request.parsed_request_args[ 'file_id' ]
|
||||
try:
|
||||
|
||||
if 'file_id' in request.parsed_request_args:
|
||||
|
||||
file_id = request.parsed_request_args[ 'file_id' ]
|
||||
|
||||
request.client_api_permissions.CheckPermissionToSeeFiles( ( file_id, ) )
|
||||
|
||||
( media_result, ) = HG.client_controller.Read( 'media_results_from_ids', ( file_id, ) )
|
||||
|
||||
elif 'hash' in request.parsed_request_args:
|
||||
|
||||
request.client_api_permissions.CheckCanSeeAllFiles()
|
||||
|
||||
hash = request.parsed_request_args[ 'hash' ]
|
||||
|
||||
( media_result, ) = HG.client_controller.Read( 'media_results', ( hash, ) )
|
||||
|
||||
else:
|
||||
|
||||
raise HydrusExceptions.BadRequestException( 'Please include a file_id or hash parameter!' )
|
||||
|
||||
|
||||
except HydrusExceptions.DataMissing as e:
|
||||
|
||||
raise HydrusExceptions.NotFoundException( 'One or more of those file identifiers was missing!' )
|
||||
|
||||
|
||||
request.client_api_permissions.CheckPermissionToSeeFiles( ( file_id, ) )
|
||||
try:
|
||||
|
||||
hash = media_result.GetHash()
|
||||
mime = media_result.GetMime()
|
||||
|
||||
path = HG.client_controller.client_files_manager.GetFullSizeThumbnailPath( hash, mime )
|
||||
|
||||
except HydrusExceptions.FileMissingException:
|
||||
|
||||
raise HydrusExceptions.NotFoundException( 'Could not find that file!' )
|
||||
|
||||
|
||||
media_result = 'blah' # get it from controller
|
||||
|
||||
mime = media_result.GetMime() # jpg or png
|
||||
|
||||
client_files_manager = HG.client_controller.client_files_manager
|
||||
|
||||
path = client_files_manager.GetFilePath( hash, mime )
|
||||
|
||||
response_context = HydrusServerResources.ResponseContext( 200, mime = mime, path = path )
|
||||
response_context = HydrusServerResources.ResponseContext( 200, mime = HC.APPLICATION_OCTET_STREAM, path = path )
|
||||
|
||||
return response_context
|
||||
|
||||
|
|
|
@ -106,8 +106,6 @@ class ImageRenderer( object ):
|
|||
|
||||
def _Initialise( self ):
|
||||
|
||||
time.sleep( 0.00001 )
|
||||
|
||||
self._numpy_image = ClientImageHandling.GenerateNumpyImage( self._path, self._mime )
|
||||
|
||||
|
||||
|
|
|
@ -267,27 +267,50 @@ def LoadFromPng( path ):
|
|||
|
||||
try:
|
||||
|
||||
( height, width ) = numpy_image.shape
|
||||
try:
|
||||
|
||||
( height, width ) = numpy_image.shape
|
||||
|
||||
except:
|
||||
|
||||
raise Exception( 'The file did not appear to be monochrome!' )
|
||||
|
||||
|
||||
complete_data = numpy_image.tostring()
|
||||
try:
|
||||
|
||||
complete_data = numpy_image.tostring()
|
||||
|
||||
top_height_header = complete_data[:2]
|
||||
|
||||
( top_height, ) = struct.unpack( '!H', top_height_header )
|
||||
|
||||
payload_and_header_bytes = complete_data[ width * top_height : ]
|
||||
|
||||
except:
|
||||
|
||||
raise Exception( 'Header bytes were invalid!' )
|
||||
|
||||
|
||||
top_height_header = complete_data[:2]
|
||||
|
||||
( top_height, ) = struct.unpack( '!H', top_height_header )
|
||||
|
||||
payload_and_header_bytes = complete_data[ width * top_height : ]
|
||||
|
||||
payload_length_header = payload_and_header_bytes[:4]
|
||||
|
||||
( payload_bytes_length, ) = struct.unpack( '!I', payload_length_header )
|
||||
|
||||
payload_bytes = payload_and_header_bytes[ 4 : 4 + payload_bytes_length ]
|
||||
try:
|
||||
|
||||
payload_length_header = payload_and_header_bytes[:4]
|
||||
|
||||
( payload_bytes_length, ) = struct.unpack( '!I', payload_length_header )
|
||||
|
||||
payload_bytes = payload_and_header_bytes[ 4 : 4 + payload_bytes_length ]
|
||||
|
||||
except:
|
||||
|
||||
raise Exception( 'Payload bytes were invalid!' )
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.ShowException( e )
|
||||
message = 'The image loaded, but it did not seem to be a hydrus serialised png! The error was: {}'.format( str( e ) )
|
||||
message += os.linesep * 2
|
||||
message += 'If you believe this is a legit non-resized, non-converted hydrus serialised png, please send it to hydrus_dev.'
|
||||
|
||||
raise Exception( 'The image loaded, but it did not seem to be a hydrus serialised png!' )
|
||||
raise Exception( message )
|
||||
|
||||
|
||||
return payload_bytes
|
||||
|
|
|
@ -67,8 +67,8 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 18
|
||||
SOFTWARE_VERSION = 342
|
||||
CLIENT_API_VERSION = 3
|
||||
SOFTWARE_VERSION = 343
|
||||
CLIENT_API_VERSION = 4
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ header_and_mime = [
|
|||
( 0, b'GIF89a', HC.IMAGE_GIF ),
|
||||
( 0, b'\x89PNG', HC.UNDETERMINED_PNG ),
|
||||
( 8, b'WEBP', HC.IMAGE_WEBP ),
|
||||
( 0, b'II*', HC.IMAGE_TIFF ),
|
||||
( 0, b'II*\x00', HC.IMAGE_TIFF ),
|
||||
( 0, b'MM\x00*', HC.IMAGE_TIFF ),
|
||||
( 0, b'BM', HC.IMAGE_BMP ),
|
||||
( 0, b'CWS', HC.APPLICATION_FLASH ),
|
||||
( 0, b'FWS', HC.APPLICATION_FLASH ),
|
||||
|
|
|
@ -262,6 +262,11 @@ def GetMime( path ):
|
|||
|
||||
has_webm_audio = True in ( webm_audio_format in audio_format for webm_audio_format in webm_audio_formats )
|
||||
|
||||
else:
|
||||
|
||||
# no audio at all is not a vote against webm
|
||||
has_webm_audio = True
|
||||
|
||||
|
||||
if has_webm_video and has_webm_audio:
|
||||
|
||||
|
|
|
@ -2,10 +2,13 @@ from . import ClientConstants as CC
|
|||
from . import ClientAPI
|
||||
from . import ClientLocalServer
|
||||
from . import ClientLocalServerResources
|
||||
from . import ClientMedia
|
||||
from . import ClientRatings
|
||||
from . import ClientSearch
|
||||
from . import ClientServices
|
||||
from . import ClientTags
|
||||
import collections
|
||||
import hashlib
|
||||
import http.client
|
||||
from . import HydrusConstants as HC
|
||||
from . import HydrusExceptions
|
||||
|
@ -13,6 +16,8 @@ from . import HydrusTags
|
|||
from . import HydrusText
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import time
|
||||
import unittest
|
||||
import urllib
|
||||
|
@ -832,6 +837,20 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
#
|
||||
|
||||
tags = []
|
||||
|
||||
path = '/get_files/search_files?tags={}'.format( urllib.parse.quote( json.dumps( tags ) ) )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 403 )
|
||||
|
||||
#
|
||||
|
||||
tags = [ 'kino' ]
|
||||
|
||||
path = '/get_files/search_files?tags={}'.format( urllib.parse.quote( json.dumps( tags ) ) )
|
||||
|
@ -868,7 +887,10 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
# some file search param parsing
|
||||
|
||||
class PretendRequest( object ): pass
|
||||
class PretendRequest( object ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
pretend_request = PretendRequest()
|
||||
|
||||
|
@ -951,6 +973,407 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
self.assertEqual( set( predicates ), set( expected_predicates ) )
|
||||
|
||||
# test file metadata
|
||||
|
||||
api_permissions = set_up_permissions[ 'search_green_files' ]
|
||||
|
||||
access_key_hex = api_permissions.GetAccessKey().hex()
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex }
|
||||
|
||||
file_ids_to_hashes = { 1 : bytes.fromhex( 'a' * 64 ), 2 : bytes.fromhex( 'b' * 64 ), 3 : bytes.fromhex( 'c' * 64 ) }
|
||||
|
||||
metadata = []
|
||||
|
||||
for ( file_id, hash ) in file_ids_to_hashes.items():
|
||||
|
||||
metadata_row = { 'file_id' : file_id, 'hash' : hash.hex() }
|
||||
|
||||
metadata.append( metadata_row )
|
||||
|
||||
|
||||
expected_identifier_result = { 'metadata' : metadata }
|
||||
|
||||
media_results = []
|
||||
|
||||
for ( file_id, hash ) in file_ids_to_hashes.items():
|
||||
|
||||
size = random.randint( 8192, 20 * 1048576 )
|
||||
mime = random.choice( [ HC.IMAGE_JPEG, HC.VIDEO_WEBM, HC.APPLICATION_PDF ] )
|
||||
width = random.randint( 200, 4096 )
|
||||
height = random.randint( 200, 4096 )
|
||||
duration = random.choice( [ 220, 16.66667, None ] )
|
||||
|
||||
file_info_manager = ClientMedia.FileInfoManager( file_id, hash, size = size, mime = mime, width = width, height = height, duration = duration )
|
||||
|
||||
service_keys_to_statuses_to_tags = { CC.LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue eyes', 'blonde hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit' ] } }
|
||||
|
||||
tags_manager = ClientMedia.TagsManager( service_keys_to_statuses_to_tags )
|
||||
|
||||
locations_manager = ClientMedia.LocationsManager( set(), set(), set(), set() )
|
||||
ratings_manager = ClientRatings.RatingsManager( {} )
|
||||
file_viewing_stats_manager = ClientMedia.FileViewingStatsManager( 0, 0, 0, 0 )
|
||||
|
||||
media_result = ClientMedia.MediaResult( file_info_manager, tags_manager, locations_manager, ratings_manager, file_viewing_stats_manager )
|
||||
|
||||
media_results.append( media_result )
|
||||
|
||||
|
||||
metadata = []
|
||||
|
||||
services_manager = HG.client_controller.services_manager
|
||||
|
||||
service_keys_to_names = {}
|
||||
|
||||
for media_result in media_results:
|
||||
|
||||
metadata_row = {}
|
||||
|
||||
file_info_manager = media_result.GetFileInfoManager()
|
||||
|
||||
metadata_row[ 'file_id' ] = file_info_manager.hash_id
|
||||
metadata_row[ 'hash' ] = file_info_manager.hash.hex()
|
||||
metadata_row[ 'size' ] = file_info_manager.size
|
||||
metadata_row[ 'mime' ] = HC.mime_string_lookup[ file_info_manager.mime ]
|
||||
metadata_row[ 'width' ] = file_info_manager.width
|
||||
metadata_row[ 'height' ] = file_info_manager.height
|
||||
metadata_row[ 'duration' ] = file_info_manager.duration
|
||||
metadata_row[ 'num_frames' ] = file_info_manager.num_frames
|
||||
metadata_row[ 'num_words' ] = file_info_manager.num_words
|
||||
|
||||
tags_manager = media_result.GetTagsManager()
|
||||
|
||||
service_names_to_statuses_to_tags = {}
|
||||
|
||||
service_keys_to_statuses_to_tags = tags_manager.GetServiceKeysToStatusesToTags()
|
||||
|
||||
for ( service_key, statuses_to_tags ) in service_keys_to_statuses_to_tags.items():
|
||||
|
||||
if service_key not in service_keys_to_names:
|
||||
|
||||
service_keys_to_names[ service_key ] = services_manager.GetName( service_key )
|
||||
|
||||
|
||||
service_name = service_keys_to_names[ service_key ]
|
||||
|
||||
service_names_to_statuses_to_tags[ service_name ] = { str( status ) : list( tags ) for ( status, tags ) in statuses_to_tags.items() }
|
||||
|
||||
|
||||
metadata_row[ 'service_names_to_statuses_to_tags' ] = service_names_to_statuses_to_tags
|
||||
|
||||
metadata.append( metadata_row )
|
||||
|
||||
|
||||
expected_metadata_result = { 'metadata' : metadata }
|
||||
|
||||
HG.test_controller.SetRead( 'hash_ids_to_hashes', file_ids_to_hashes )
|
||||
HG.test_controller.SetRead( 'media_results', media_results )
|
||||
HG.test_controller.SetRead( 'media_results_from_ids', media_results )
|
||||
|
||||
api_permissions.SetLastSearchResults( [ 1, 2, 3, 4, 5, 6 ] )
|
||||
|
||||
# fail on non-permitted files
|
||||
|
||||
path = '/get_files/file_metadata?file_ids={}&only_return_identifiers=true'.format( urllib.parse.quote( json.dumps( [ 1, 2, 3, 7 ] ) ) )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 403 )
|
||||
|
||||
# fails on hashes even if the hashes are 'good'
|
||||
|
||||
path = '/get_files/file_metadata?hashes={}&only_return_identifiers=true'.format( urllib.parse.quote( json.dumps( [ hash.hex() for hash in file_ids_to_hashes.values() ] ) ) )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 403 )
|
||||
|
||||
# identifiers from file_ids
|
||||
|
||||
path = '/get_files/file_metadata?file_ids={}&only_return_identifiers=true'.format( urllib.parse.quote( json.dumps( [ 1, 2, 3 ] ) ) )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 200 )
|
||||
|
||||
text = str( data, 'utf-8' )
|
||||
|
||||
d = json.loads( text )
|
||||
|
||||
self.assertEqual( d, expected_identifier_result )
|
||||
|
||||
# metadata from file_ids
|
||||
|
||||
path = '/get_files/file_metadata?file_ids={}'.format( urllib.parse.quote( json.dumps( [ 1, 2, 3 ] ) ) )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 200 )
|
||||
|
||||
text = str( data, 'utf-8' )
|
||||
|
||||
d = json.loads( text )
|
||||
|
||||
self.assertEqual( d, expected_metadata_result )
|
||||
|
||||
# now from hashes
|
||||
|
||||
api_permissions = set_up_permissions[ 'everything' ]
|
||||
|
||||
access_key_hex = api_permissions.GetAccessKey().hex()
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex }
|
||||
|
||||
# identifiers from hashes
|
||||
|
||||
path = '/get_files/file_metadata?hashes={}&only_return_identifiers=true'.format( urllib.parse.quote( json.dumps( [ hash.hex() for hash in file_ids_to_hashes.values() ] ) ) )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 200 )
|
||||
|
||||
text = str( data, 'utf-8' )
|
||||
|
||||
d = json.loads( text )
|
||||
|
||||
self.assertEqual( d, expected_identifier_result )
|
||||
|
||||
# metadata from hashes
|
||||
|
||||
path = '/get_files/file_metadata?hashes={}'.format( urllib.parse.quote( json.dumps( [ hash.hex() for hash in file_ids_to_hashes.values() ] ) ) )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 200 )
|
||||
|
||||
text = str( data, 'utf-8' )
|
||||
|
||||
d = json.loads( text )
|
||||
|
||||
self.assertEqual( d, expected_metadata_result )
|
||||
|
||||
# files and thumbs
|
||||
|
||||
hash = b'\xadm5\x99\xa6\xc4\x89\xa5u\xeb\x19\xc0&\xfa\xce\x97\xa9\xcdey\xe7G(\xb0\xce\x94\xa6\x01\xd22\xf3\xc3'
|
||||
hash_hex = hash.hex()
|
||||
|
||||
size = 100
|
||||
mime = HC.IMAGE_PNG
|
||||
width = 20
|
||||
height = 20
|
||||
duration = None
|
||||
|
||||
file_info_manager = ClientMedia.FileInfoManager( file_id, hash, size = size, mime = mime, width = width, height = height, duration = duration )
|
||||
|
||||
service_keys_to_statuses_to_tags = { CC.LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue eyes', 'blonde hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit' ] } }
|
||||
|
||||
tags_manager = ClientMedia.TagsManager( service_keys_to_statuses_to_tags )
|
||||
|
||||
locations_manager = ClientMedia.LocationsManager( set(), set(), set(), set() )
|
||||
ratings_manager = ClientRatings.RatingsManager( {} )
|
||||
file_viewing_stats_manager = ClientMedia.FileViewingStatsManager( 0, 0, 0, 0 )
|
||||
|
||||
media_result = ClientMedia.MediaResult( file_info_manager, tags_manager, locations_manager, ratings_manager, file_viewing_stats_manager )
|
||||
|
||||
HG.test_controller.SetRead( 'media_results', ( media_result, ) )
|
||||
HG.test_controller.SetRead( 'media_results_from_ids', ( media_result, ) )
|
||||
|
||||
path = os.path.join( HC.STATIC_DIR, 'hydrus.png' )
|
||||
|
||||
file_path = HG.test_controller.client_files_manager.GetFilePath( hash, HC.IMAGE_PNG, check_file_exists = False )
|
||||
|
||||
shutil.copy2( path, file_path )
|
||||
|
||||
thumb_hash = b'\x17\xde\xd6\xee\x1b\xfa\x002\xbdj\xc0w\x92\xce5\xf0\x12~\xfe\x915\xb3\xb3tA\xac\x90F\x95\xc2T\xc5'
|
||||
|
||||
path = os.path.join( HC.STATIC_DIR, 'hydrus_small.png' )
|
||||
|
||||
thumb_path = HG.test_controller.client_files_manager._GenerateExpectedFullSizeThumbnailPath( hash )
|
||||
|
||||
shutil.copy2( path, thumb_path )
|
||||
|
||||
api_permissions = set_up_permissions[ 'search_green_files' ]
|
||||
|
||||
access_key_hex = api_permissions.GetAccessKey().hex()
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex }
|
||||
|
||||
# let's fail first
|
||||
|
||||
path = '/get_files/file?file_id={}'.format( 10 )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 403 )
|
||||
|
||||
#
|
||||
|
||||
path = '/get_files/thumbnail?file_id={}'.format( 10 )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 403 )
|
||||
|
||||
#
|
||||
|
||||
path = '/get_files/file?hash={}'.format( hash_hex )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 403 )
|
||||
|
||||
#
|
||||
|
||||
path = '/get_files/thumbnail?hash={}'.format( hash_hex )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 403 )
|
||||
|
||||
# now succeed
|
||||
|
||||
path = '/get_files/file?file_id={}'.format( 1 )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 200 )
|
||||
|
||||
self.assertEqual( hashlib.sha256( data ).digest(), hash )
|
||||
|
||||
#
|
||||
|
||||
path = '/get_files/thumbnail?file_id={}'.format( 1 )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 200 )
|
||||
|
||||
self.assertEqual( hashlib.sha256( data ).digest(), thumb_hash )
|
||||
|
||||
#
|
||||
|
||||
api_permissions = set_up_permissions[ 'everything' ]
|
||||
|
||||
access_key_hex = api_permissions.GetAccessKey().hex()
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex }
|
||||
|
||||
#
|
||||
|
||||
path = '/get_files/file?hash={}'.format( hash_hex )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 200 )
|
||||
|
||||
self.assertEqual( hashlib.sha256( data ).digest(), hash )
|
||||
|
||||
#
|
||||
|
||||
path = '/get_files/thumbnail?hash={}'.format( hash_hex )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 200 )
|
||||
|
||||
self.assertEqual( hashlib.sha256( data ).digest(), thumb_hash )
|
||||
|
||||
# now 404
|
||||
|
||||
hash_404 = os.urandom( 32 )
|
||||
|
||||
file_info_manager = ClientMedia.FileInfoManager( 123456, hash_404, size = size, mime = mime, width = width, height = height, duration = duration )
|
||||
|
||||
media_result = ClientMedia.MediaResult( file_info_manager, tags_manager, locations_manager, ratings_manager, file_viewing_stats_manager )
|
||||
|
||||
HG.test_controller.SetRead( 'media_results', ( media_result, ) )
|
||||
HG.test_controller.SetRead( 'media_results_from_ids', ( media_result, ) )
|
||||
|
||||
#
|
||||
|
||||
path = '/get_files/file?hash={}'.format( hash_404.hex() )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 404 )
|
||||
|
||||
#
|
||||
|
||||
path = '/get_files/thumbnail?hash={}'.format( hash_404.hex() )
|
||||
|
||||
connection.request( 'GET', path, headers = headers )
|
||||
|
||||
response = connection.getresponse()
|
||||
|
||||
data = response.read()
|
||||
|
||||
self.assertEqual( response.status, 404 )
|
||||
|
||||
#
|
||||
|
||||
os.unlink( file_path )
|
||||
os.unlink( thumb_path )
|
||||
|
||||
|
||||
def _test_permission_failures( self, connection, set_up_permissions ):
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ from . import ClientServices
|
|||
import collections
|
||||
from . import HydrusConstants as HC
|
||||
import os
|
||||
from . import TestConstants
|
||||
import unittest
|
||||
from . import HydrusData
|
||||
from . import HydrusGlobals as HG
|
||||
|
|
|
@ -7,7 +7,6 @@ from . import HydrusConstants as HC
|
|||
import os
|
||||
import shutil
|
||||
import stat
|
||||
from . import TestConstants
|
||||
import unittest
|
||||
from . import HydrusData
|
||||
from . import ClientConstants as CC
|
||||
|
|
|
@ -2,7 +2,6 @@ from . import ClientImageHandling
|
|||
import collections
|
||||
from . import HydrusConstants as HC
|
||||
import os
|
||||
from . import TestConstants
|
||||
import unittest
|
||||
|
||||
class TestImageHandling( unittest.TestCase ):
|
||||
|
|
|
@ -12,7 +12,7 @@ from . import HydrusData
|
|||
from . import HydrusExceptions
|
||||
from . import HydrusNetworking
|
||||
import os
|
||||
from . import TestConstants
|
||||
from . import TestController
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
@ -66,7 +66,7 @@ class TestSubscription( unittest.TestCase ):
|
|||
|
||||
def _PrepEngine( self ):
|
||||
|
||||
mock_controller = TestConstants.MockController()
|
||||
mock_controller = TestController.MockController()
|
||||
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
|
||||
session_manager = ClientNetworkingSessions.NetworkSessionManager()
|
||||
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
|
||||
|
|
|
@ -5,7 +5,7 @@ import collections
|
|||
from . import HydrusConstants as HC
|
||||
import os
|
||||
import random
|
||||
from . import TestConstants
|
||||
from . import TestController
|
||||
import time
|
||||
import unittest
|
||||
import wx
|
||||
|
@ -69,154 +69,159 @@ class TestListBoxes( unittest.TestCase ):
|
|||
|
||||
def test_listbox_colour_options( self ):
|
||||
|
||||
frame = TestConstants.TestFrame()
|
||||
|
||||
try:
|
||||
def wx_code():
|
||||
|
||||
initial_namespace_colours = { 'series' : ( 153, 101, 21 ), '' : ( 0, 111, 250 ), None : ( 114, 160, 193 ), 'creator' : ( 170, 0, 0 ) }
|
||||
frame = TestController.TestFrame()
|
||||
|
||||
panel = ClientGUIListBoxes.ListBoxTagsColourOptions( frame, initial_namespace_colours )
|
||||
|
||||
frame.SetPanel( panel )
|
||||
|
||||
self.assertEqual( panel.GetNamespaceColours(), initial_namespace_colours )
|
||||
|
||||
#
|
||||
|
||||
new_namespace_colours = dict( initial_namespace_colours )
|
||||
new_namespace_colours[ 'character' ] = ( 0, 170, 0 )
|
||||
|
||||
colour = wx.Colour( 0, 170, 0 )
|
||||
|
||||
panel.SetNamespaceColour( 'character', colour )
|
||||
|
||||
self.assertEqual( panel.GetNamespaceColours(), new_namespace_colours )
|
||||
|
||||
#
|
||||
|
||||
terms = set( panel._terms )
|
||||
ordered_terms = list( panel._ordered_terms )
|
||||
|
||||
self.assertEqual( len( terms ), len( ordered_terms ) )
|
||||
|
||||
#
|
||||
|
||||
all_clickable_indices = GetAllClickableIndices( panel )
|
||||
|
||||
self.assertEqual( len( list(all_clickable_indices.keys()) ), len( terms ) )
|
||||
self.assertEqual( set( all_clickable_indices.keys() ), set( range( len( list(all_clickable_indices.keys()) ) ) ) )
|
||||
|
||||
#
|
||||
|
||||
for ( index, y ) in list(all_clickable_indices.items()):
|
||||
try:
|
||||
|
||||
initial_namespace_colours = { 'series' : ( 153, 101, 21 ), '' : ( 0, 111, 250 ), None : ( 114, 160, 193 ), 'creator' : ( 170, 0, 0 ) }
|
||||
|
||||
panel = ClientGUIListBoxes.ListBoxTagsColourOptions( frame, initial_namespace_colours )
|
||||
|
||||
frame.SetPanel( panel )
|
||||
|
||||
self.assertEqual( panel.GetNamespaceColours(), initial_namespace_colours )
|
||||
|
||||
#
|
||||
|
||||
new_namespace_colours = dict( initial_namespace_colours )
|
||||
new_namespace_colours[ 'character' ] = ( 0, 170, 0 )
|
||||
|
||||
colour = wx.Colour( 0, 170, 0 )
|
||||
|
||||
panel.SetNamespaceColour( 'character', colour )
|
||||
|
||||
self.assertEqual( panel.GetNamespaceColours(), new_namespace_colours )
|
||||
|
||||
#
|
||||
|
||||
terms = set( panel._terms )
|
||||
ordered_terms = list( panel._ordered_terms )
|
||||
|
||||
self.assertEqual( len( terms ), len( ordered_terms ) )
|
||||
|
||||
#
|
||||
|
||||
all_clickable_indices = GetAllClickableIndices( panel )
|
||||
|
||||
self.assertEqual( len( list(all_clickable_indices.keys()) ), len( terms ) )
|
||||
self.assertEqual( set( all_clickable_indices.keys() ), set( range( len( list(all_clickable_indices.keys()) ) ) ) )
|
||||
|
||||
#
|
||||
|
||||
for ( index, y ) in list(all_clickable_indices.items()):
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( y )
|
||||
|
||||
DoClick( click, panel )
|
||||
|
||||
self.assertEqual( panel.GetSelectedNamespaceColours(), dict( [ ordered_terms[ index ] ] ) )
|
||||
|
||||
|
||||
#
|
||||
|
||||
current_y = 5
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( y )
|
||||
click.SetY( current_y )
|
||||
|
||||
while panel._GetIndexUnderMouse( click ) is not None:
|
||||
|
||||
current_y += 5
|
||||
|
||||
click.SetY( current_y )
|
||||
|
||||
|
||||
DoClick( click, panel )
|
||||
|
||||
self.assertEqual( panel.GetSelectedNamespaceColours(), dict( [ ordered_terms[ index ] ] ) )
|
||||
self.assertEqual( panel.GetSelectedNamespaceColours(), {} )
|
||||
|
||||
|
||||
#
|
||||
|
||||
current_y = 5
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( current_y )
|
||||
|
||||
while panel._GetIndexUnderMouse( click ) is not None:
|
||||
#
|
||||
|
||||
current_y += 5
|
||||
|
||||
click.SetY( current_y )
|
||||
|
||||
|
||||
DoClick( click, panel )
|
||||
|
||||
self.assertEqual( panel.GetSelectedNamespaceColours(), {} )
|
||||
|
||||
#
|
||||
|
||||
|
||||
|
||||
if len( list(all_clickable_indices.keys()) ) > 2:
|
||||
|
||||
indices = random.sample( list(all_clickable_indices.keys()), len( list(all_clickable_indices.keys()) ) - 1 )
|
||||
|
||||
for index in indices:
|
||||
if len( list(all_clickable_indices.keys()) ) > 2:
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
indices = random.sample( list(all_clickable_indices.keys()), len( list(all_clickable_indices.keys()) ) - 1 )
|
||||
|
||||
click.SetControlDown( True )
|
||||
for index in indices:
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
|
||||
click.SetControlDown( True )
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( all_clickable_indices[ index ] )
|
||||
|
||||
DoClick( click, panel )
|
||||
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( all_clickable_indices[ index ] )
|
||||
expected_selected_terms = [ ordered_terms[ index ] for index in indices ]
|
||||
|
||||
DoClick( click, panel )
|
||||
self.assertEqual( panel.GetSelectedNamespaceColours(), dict( expected_selected_terms ) )
|
||||
|
||||
|
||||
expected_selected_terms = [ ordered_terms[ index ] for index in indices ]
|
||||
|
||||
self.assertEqual( panel.GetSelectedNamespaceColours(), dict( expected_selected_terms ) )
|
||||
|
||||
|
||||
#
|
||||
|
||||
random_index = random.choice( list(all_clickable_indices.keys()) )
|
||||
|
||||
while ordered_terms[ random_index ][0] in panel.PROTECTED_TERMS:
|
||||
#
|
||||
|
||||
random_index = random.choice( list(all_clickable_indices.keys()) )
|
||||
|
||||
|
||||
del new_namespace_colours[ ordered_terms[ random_index ][0] ]
|
||||
|
||||
# select nothing
|
||||
|
||||
current_y = 5
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( current_y )
|
||||
|
||||
while panel._GetIndexUnderMouse( click ) is not None:
|
||||
while ordered_terms[ random_index ][0] in panel.PROTECTED_TERMS:
|
||||
|
||||
random_index = random.choice( list(all_clickable_indices.keys()) )
|
||||
|
||||
|
||||
current_y += 5
|
||||
del new_namespace_colours[ ordered_terms[ random_index ][0] ]
|
||||
|
||||
# select nothing
|
||||
|
||||
current_y = 5
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( current_y )
|
||||
|
||||
|
||||
DoClick( click, panel )
|
||||
|
||||
# select the random index
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( all_clickable_indices[ random_index ] )
|
||||
|
||||
DoClick( click, panel )
|
||||
|
||||
# now double-click to activate and hence remove
|
||||
|
||||
doubleclick = wx.MouseEvent( wx.wxEVT_LEFT_DCLICK )
|
||||
|
||||
doubleclick.SetX( 5 )
|
||||
doubleclick.SetY( all_clickable_indices[ random_index ] )
|
||||
|
||||
DoClick( doubleclick, panel, do_delayed_ok_afterwards = True )
|
||||
|
||||
self.assertEqual( panel.GetNamespaceColours(), new_namespace_colours )
|
||||
|
||||
finally:
|
||||
|
||||
frame.DestroyLater()
|
||||
while panel._GetIndexUnderMouse( click ) is not None:
|
||||
|
||||
current_y += 5
|
||||
|
||||
click.SetY( current_y )
|
||||
|
||||
|
||||
DoClick( click, panel )
|
||||
|
||||
# select the random index
|
||||
|
||||
click = wx.MouseEvent( wx.wxEVT_LEFT_DOWN )
|
||||
|
||||
click.SetX( 10 )
|
||||
click.SetY( all_clickable_indices[ random_index ] )
|
||||
|
||||
DoClick( click, panel )
|
||||
|
||||
# now double-click to activate and hence remove
|
||||
|
||||
doubleclick = wx.MouseEvent( wx.wxEVT_LEFT_DCLICK )
|
||||
|
||||
doubleclick.SetX( 5 )
|
||||
doubleclick.SetY( all_clickable_indices[ random_index ] )
|
||||
|
||||
DoClick( doubleclick, panel, do_delayed_ok_afterwards = True )
|
||||
|
||||
self.assertEqual( panel.GetNamespaceColours(), new_namespace_colours )
|
||||
|
||||
finally:
|
||||
|
||||
frame.DestroyLater()
|
||||
|
||||
|
||||
|
||||
HG.test_controller.CallBlockingToWX( HG.test_controller.win, wx_code )
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ from . import HydrusData
|
|||
from . import HydrusExceptions
|
||||
from . import HydrusNetworking
|
||||
import os
|
||||
from . import TestConstants
|
||||
from . import TestController
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
@ -224,7 +224,7 @@ class TestNetworkingEngine( unittest.TestCase ):
|
|||
|
||||
def test_engine_shutdown_app( self ):
|
||||
|
||||
mock_controller = TestConstants.MockController()
|
||||
mock_controller = TestController.MockController()
|
||||
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
|
||||
session_manager = ClientNetworkingSessions.NetworkSessionManager()
|
||||
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
|
||||
|
@ -254,7 +254,7 @@ class TestNetworkingEngine( unittest.TestCase ):
|
|||
|
||||
def test_engine_shutdown_manual( self ):
|
||||
|
||||
mock_controller = TestConstants.MockController()
|
||||
mock_controller = TestController.MockController()
|
||||
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
|
||||
session_manager = ClientNetworkingSessions.NetworkSessionManager()
|
||||
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
|
||||
|
@ -282,7 +282,7 @@ class TestNetworkingEngine( unittest.TestCase ):
|
|||
|
||||
def test_engine_simple_job( self ):
|
||||
|
||||
mock_controller = TestConstants.MockController()
|
||||
mock_controller = TestController.MockController()
|
||||
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
|
||||
session_manager = ClientNetworkingSessions.NetworkSessionManager()
|
||||
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
|
||||
|
@ -335,7 +335,7 @@ class TestNetworkingJob( unittest.TestCase ):
|
|||
|
||||
job.SetForLogin( for_login )
|
||||
|
||||
mock_controller = TestConstants.MockController()
|
||||
mock_controller = TestController.MockController()
|
||||
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
|
||||
session_manager = ClientNetworkingSessions.NetworkSessionManager()
|
||||
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
|
||||
|
@ -544,11 +544,11 @@ class TestNetworkingJobHydrus( unittest.TestCase ):
|
|||
|
||||
job.SetForLogin( for_login )
|
||||
|
||||
mock_controller = TestConstants.MockController()
|
||||
mock_controller = TestController.MockController()
|
||||
|
||||
mock_service = ClientServices.GenerateService( MOCK_HYDRUS_SERVICE_KEY, HC.TAG_REPOSITORY, 'test tag repo' )
|
||||
|
||||
mock_services_manager = TestConstants.MockServicesManager( ( mock_service, ) )
|
||||
mock_services_manager = TestController.MockServicesManager( ( mock_service, ) )
|
||||
|
||||
mock_controller.services_manager = mock_services_manager
|
||||
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
import collections
|
||||
from . import ClientConstants as CC
|
||||
from . import ClientOptions
|
||||
from . import HydrusConstants as HC
|
||||
from . import HydrusGlobals as HG
|
||||
from . import HydrusTags
|
||||
import os
|
||||
import random
|
||||
import threading
|
||||
from . import HydrusData
|
||||
from . import HydrusThreading
|
||||
import wx
|
||||
|
||||
DB_DIR = None
|
||||
|
||||
tiniest_gif = b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x00\xFF\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00\x3B'
|
||||
|
||||
LOCAL_RATING_LIKE_SERVICE_KEY = HydrusData.GenerateKey()
|
||||
LOCAL_RATING_NUMERICAL_SERVICE_KEY = HydrusData.GenerateKey()
|
||||
|
||||
def ConvertServiceKeysToContentUpdatesToComparable( service_keys_to_content_updates ):
|
||||
|
||||
comparable_dict = {}
|
||||
|
||||
for ( service_key, content_updates ) in list(service_keys_to_content_updates.items()):
|
||||
|
||||
comparable_dict[ service_key ] = set( content_updates )
|
||||
|
||||
|
||||
return comparable_dict
|
||||
|
||||
class MockController( object ):
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
self.model_is_shutdown = False
|
||||
|
||||
self.new_options = ClientOptions.ClientOptions()
|
||||
|
||||
|
||||
def CallToThread( self, callable, *args, **kwargs ):
|
||||
|
||||
return HG.test_controller.CallToThread( callable, *args, **kwargs )
|
||||
|
||||
|
||||
def JustWokeFromSleep( self ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def ModelIsShutdown( self ):
|
||||
|
||||
return self.model_is_shutdown or HG.test_controller.ModelIsShutdown()
|
||||
|
||||
|
||||
def pub( self, *args, **kwargs ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def sub( self, *args, **kwargs ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MockServicesManager( object ):
|
||||
|
||||
def __init__( self, services ):
|
||||
|
||||
self._service_keys_to_services = { service.GetServiceKey() : service for service in services }
|
||||
|
||||
|
||||
def GetName( self, service_key ):
|
||||
|
||||
return self._service_keys_to_services[ service_key ].GetName()
|
||||
|
||||
|
||||
def GetService( self, service_key ):
|
||||
|
||||
return self._service_keys_to_services[ service_key ]
|
||||
|
||||
|
||||
def ServiceExists( self, service_key ):
|
||||
|
||||
return service_key in self._service_keys_to_services
|
||||
|
||||
|
||||
class FakeWebSessionManager():
|
||||
|
||||
def EnsureLoggedIn( self, name ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def GetCookies( self, *args, **kwargs ):
|
||||
|
||||
return { 'session_cookie' : 'blah' }
|
||||
|
||||
|
||||
class TestFrame( wx.Frame ):
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
wx.Frame.__init__( self, None )
|
||||
|
||||
|
||||
def SetPanel( self, panel ):
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
vbox.Add( panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
self.Fit()
|
||||
|
||||
self.Show()
|
||||
|
||||
|
|
@ -0,0 +1,725 @@
|
|||
import collections
|
||||
import os
|
||||
import random
|
||||
import threading
|
||||
import collections
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
import unittest
|
||||
import wx
|
||||
from . import HydrusConstants as HC
|
||||
from . import ClientConstants as CC
|
||||
from . import HydrusGlobals as HG
|
||||
from . import ClientAPI
|
||||
from . import ClientDefaults
|
||||
from . import ClientNetworking
|
||||
from . import ClientNetworkingBandwidth
|
||||
from . import ClientNetworkingDomain
|
||||
from . import ClientNetworkingLogin
|
||||
from . import ClientNetworkingSessions
|
||||
from . import ClientServices
|
||||
from . import ClientThreading
|
||||
from . import HydrusExceptions
|
||||
from . import HydrusPubSub
|
||||
from . import HydrusSessions
|
||||
from . import HydrusTags
|
||||
from . import HydrusThreading
|
||||
from . import TestClientAPI
|
||||
from . import TestClientConstants
|
||||
from . import TestClientDaemons
|
||||
from . import TestClientData
|
||||
from . import TestClientImageHandling
|
||||
from . import TestClientImportOptions
|
||||
from . import TestClientImportSubscriptions
|
||||
from . import TestClientListBoxes
|
||||
from . import TestClientNetworking
|
||||
from . import TestDialogs
|
||||
from . import TestDB
|
||||
from . import TestFunctions
|
||||
from . import TestHydrusNATPunch
|
||||
from . import TestHydrusNetworking
|
||||
from . import TestHydrusSerialisable
|
||||
from . import TestHydrusServer
|
||||
from . import TestHydrusSessions
|
||||
from . import TestHydrusTags
|
||||
from twisted.internet import reactor
|
||||
from . import ClientCaches
|
||||
from . import ClientData
|
||||
from . import ClientOptions
|
||||
from . import HydrusData
|
||||
from . import HydrusPaths
|
||||
|
||||
|
||||
DB_DIR = None
|
||||
|
||||
tiniest_gif = b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x00\xFF\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00\x3B'
|
||||
|
||||
LOCAL_RATING_LIKE_SERVICE_KEY = HydrusData.GenerateKey()
|
||||
LOCAL_RATING_NUMERICAL_SERVICE_KEY = HydrusData.GenerateKey()
|
||||
|
||||
def ConvertServiceKeysToContentUpdatesToComparable( service_keys_to_content_updates ):
|
||||
|
||||
comparable_dict = {}
|
||||
|
||||
for ( service_key, content_updates ) in list(service_keys_to_content_updates.items()):
|
||||
|
||||
comparable_dict[ service_key ] = set( content_updates )
|
||||
|
||||
|
||||
return comparable_dict
|
||||
|
||||
class MockController( object ):
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
self.model_is_shutdown = False
|
||||
|
||||
self.new_options = ClientOptions.ClientOptions()
|
||||
|
||||
|
||||
def CallToThread( self, callable, *args, **kwargs ):
|
||||
|
||||
return HG.test_controller.CallToThread( callable, *args, **kwargs )
|
||||
|
||||
|
||||
def JustWokeFromSleep( self ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def ModelIsShutdown( self ):
|
||||
|
||||
return self.model_is_shutdown or HG.test_controller.ModelIsShutdown()
|
||||
|
||||
|
||||
def pub( self, *args, **kwargs ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def sub( self, *args, **kwargs ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MockServicesManager( object ):
|
||||
|
||||
def __init__( self, services ):
|
||||
|
||||
self._service_keys_to_services = { service.GetServiceKey() : service for service in services }
|
||||
|
||||
|
||||
def GetName( self, service_key ):
|
||||
|
||||
return self._service_keys_to_services[ service_key ].GetName()
|
||||
|
||||
|
||||
def GetService( self, service_key ):
|
||||
|
||||
return self._service_keys_to_services[ service_key ]
|
||||
|
||||
|
||||
def ServiceExists( self, service_key ):
|
||||
|
||||
return service_key in self._service_keys_to_services
|
||||
|
||||
|
||||
class FakeWebSessionManager():
|
||||
|
||||
def EnsureLoggedIn( self, name ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def GetCookies( self, *args, **kwargs ):
|
||||
|
||||
return { 'session_cookie' : 'blah' }
|
||||
|
||||
|
||||
class TestFrame( wx.Frame ):
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
wx.Frame.__init__( self, None )
|
||||
|
||||
|
||||
def SetPanel( self, panel ):
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
vbox.Add( panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
self.Fit()
|
||||
|
||||
self.Show()
|
||||
|
||||
|
||||
only_run = None
|
||||
|
||||
class Controller( object ):
|
||||
|
||||
def __init__( self, win, only_run ):
|
||||
|
||||
self.win = win
|
||||
self.only_run = only_run
|
||||
|
||||
self.db_dir = tempfile.mkdtemp()
|
||||
|
||||
global DB_DIR
|
||||
|
||||
DB_DIR = self.db_dir
|
||||
|
||||
self._server_files_dir = os.path.join( self.db_dir, 'server_files' )
|
||||
self._updates_dir = os.path.join( self.db_dir, 'test_updates' )
|
||||
|
||||
client_files_default = os.path.join( self.db_dir, 'client_files' )
|
||||
|
||||
HydrusPaths.MakeSureDirectoryExists( self._server_files_dir )
|
||||
HydrusPaths.MakeSureDirectoryExists( self._updates_dir )
|
||||
HydrusPaths.MakeSureDirectoryExists( client_files_default )
|
||||
|
||||
HG.controller = self
|
||||
HG.client_controller = self
|
||||
HG.server_controller = self
|
||||
HG.test_controller = self
|
||||
|
||||
self.gui = self
|
||||
|
||||
self._call_to_threads = []
|
||||
|
||||
self._pubsub = HydrusPubSub.HydrusPubSub( self )
|
||||
|
||||
self.new_options = ClientOptions.ClientOptions( self.db_dir )
|
||||
|
||||
HC.options = ClientDefaults.GetClientDefaultOptions()
|
||||
|
||||
self.options = HC.options
|
||||
|
||||
def show_text( text ): pass
|
||||
|
||||
HydrusData.ShowText = show_text
|
||||
|
||||
self._reads = {}
|
||||
|
||||
self._reads[ 'local_booru_share_keys' ] = []
|
||||
self._reads[ 'messaging_sessions' ] = []
|
||||
self._reads[ 'tag_censorship' ] = []
|
||||
self._reads[ 'options' ] = ClientDefaults.GetClientDefaultOptions()
|
||||
self._reads[ 'file_system_predicates' ] = []
|
||||
self._reads[ 'media_results' ] = []
|
||||
|
||||
self.example_tag_repo_service_key = HydrusData.GenerateKey()
|
||||
|
||||
services = []
|
||||
|
||||
services.append( ClientServices.GenerateService( CC.LOCAL_BOORU_SERVICE_KEY, HC.LOCAL_BOORU, 'local booru' ) )
|
||||
services.append( ClientServices.GenerateService( CC.CLIENT_API_SERVICE_KEY, HC.CLIENT_API_SERVICE, 'client api' ) )
|
||||
services.append( ClientServices.GenerateService( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, HC.COMBINED_LOCAL_FILE, 'all local files' ) )
|
||||
services.append( ClientServices.GenerateService( CC.LOCAL_FILE_SERVICE_KEY, HC.LOCAL_FILE_DOMAIN, 'my files' ) )
|
||||
services.append( ClientServices.GenerateService( CC.TRASH_SERVICE_KEY, HC.LOCAL_FILE_TRASH_DOMAIN, 'trash' ) )
|
||||
services.append( ClientServices.GenerateService( CC.LOCAL_TAG_SERVICE_KEY, HC.LOCAL_TAG, 'local tags' ) )
|
||||
services.append( ClientServices.GenerateService( self.example_tag_repo_service_key, HC.TAG_REPOSITORY, 'example tag repo' ) )
|
||||
services.append( ClientServices.GenerateService( CC.COMBINED_TAG_SERVICE_KEY, HC.COMBINED_TAG, 'all known tags' ) )
|
||||
services.append( ClientServices.GenerateService( LOCAL_RATING_LIKE_SERVICE_KEY, HC.LOCAL_RATING_LIKE, 'example local rating like service' ) )
|
||||
services.append( ClientServices.GenerateService( LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.LOCAL_RATING_NUMERICAL, 'example local rating numerical service' ) )
|
||||
|
||||
self._reads[ 'services' ] = services
|
||||
|
||||
client_files_locations = {}
|
||||
|
||||
for prefix in HydrusData.IterateHexPrefixes():
|
||||
|
||||
for c in ( 'f', 't', 'r' ):
|
||||
|
||||
client_files_locations[ c + prefix ] = client_files_default
|
||||
|
||||
|
||||
|
||||
self._reads[ 'client_files_locations' ] = client_files_locations
|
||||
|
||||
self._reads[ 'sessions' ] = []
|
||||
self._reads[ 'tag_parents' ] = {}
|
||||
self._reads[ 'tag_siblings' ] = {}
|
||||
self._reads[ 'in_inbox' ] = False
|
||||
|
||||
self._writes = collections.defaultdict( list )
|
||||
|
||||
self._managers = {}
|
||||
|
||||
self.services_manager = ClientCaches.ServicesManager( self )
|
||||
self.client_files_manager = ClientCaches.ClientFilesManager( self )
|
||||
|
||||
self.parsing_cache = ClientCaches.ParsingCache()
|
||||
|
||||
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
|
||||
session_manager = ClientNetworkingSessions.NetworkSessionManager()
|
||||
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
|
||||
|
||||
ClientDefaults.SetDefaultDomainManagerData( domain_manager )
|
||||
|
||||
login_manager = ClientNetworkingLogin.NetworkLoginManager()
|
||||
|
||||
self.network_engine = ClientNetworking.NetworkEngine( self, bandwidth_manager, session_manager, domain_manager, login_manager )
|
||||
|
||||
self.CallToThreadLongRunning( self.network_engine.MainLoop )
|
||||
|
||||
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager( self )
|
||||
self._managers[ 'tag_siblings' ] = ClientCaches.TagSiblingsManager( self )
|
||||
self._managers[ 'tag_parents' ] = ClientCaches.TagParentsManager( self )
|
||||
self._managers[ 'undo' ] = ClientCaches.UndoManager( self )
|
||||
self.server_session_manager = HydrusSessions.HydrusSessionManagerServer()
|
||||
|
||||
self.local_booru_manager = ClientCaches.LocalBooruCache( self )
|
||||
self.client_api_manager = ClientAPI.APIManager()
|
||||
|
||||
self._cookies = {}
|
||||
|
||||
self._job_scheduler = HydrusThreading.JobScheduler( self )
|
||||
|
||||
self._job_scheduler.start()
|
||||
|
||||
|
||||
def _GetCallToThread( self ):
|
||||
|
||||
for call_to_thread in self._call_to_threads:
|
||||
|
||||
if not call_to_thread.CurrentlyWorking():
|
||||
|
||||
return call_to_thread
|
||||
|
||||
|
||||
|
||||
if len( self._call_to_threads ) > 100:
|
||||
|
||||
raise Exception( 'Too many call to threads!' )
|
||||
|
||||
|
||||
call_to_thread = HydrusThreading.THREADCallToThread( self, 'CallToThread' )
|
||||
|
||||
self._call_to_threads.append( call_to_thread )
|
||||
|
||||
call_to_thread.start()
|
||||
|
||||
return call_to_thread
|
||||
|
||||
|
||||
def _SetupWx( self ):
|
||||
|
||||
self.locale = wx.Locale( wx.LANGUAGE_DEFAULT ) # Very important to init this here and keep it non garbage collected
|
||||
|
||||
CC.GlobalBMPs.STATICInitialise()
|
||||
|
||||
self.frame_icon = wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ), wx.BITMAP_TYPE_PNG )
|
||||
|
||||
|
||||
def pub( self, topic, *args, **kwargs ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def pubimmediate( self, topic, *args, **kwargs ):
|
||||
|
||||
self._pubsub.pubimmediate( topic, *args, **kwargs )
|
||||
|
||||
|
||||
def sub( self, object, method_name, topic ):
|
||||
|
||||
self._pubsub.sub( object, method_name, topic )
|
||||
|
||||
|
||||
def AcquirePageKey( self ):
|
||||
|
||||
return HydrusData.GenerateKey()
|
||||
|
||||
|
||||
def CallBlockingToWX( self, win, func, *args, **kwargs ):
|
||||
|
||||
def wx_code( win, job_key ):
|
||||
|
||||
try:
|
||||
|
||||
if win is not None and not win:
|
||||
|
||||
raise HydrusExceptions.WXDeadWindowException( 'Parent Window was destroyed before wx command was called!' )
|
||||
|
||||
|
||||
result = func( *args, **kwargs )
|
||||
|
||||
job_key.SetVariable( 'result', result )
|
||||
|
||||
except ( HydrusExceptions.WXDeadWindowException, HydrusExceptions.InsufficientCredentialsException, HydrusExceptions.ShutdownException ) as e:
|
||||
|
||||
job_key.SetVariable( 'error', e )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
job_key.SetVariable( 'error', e )
|
||||
|
||||
HydrusData.Print( 'CallBlockingToWX just caught this error:' )
|
||||
HydrusData.DebugPrint( traceback.format_exc() )
|
||||
|
||||
finally:
|
||||
|
||||
job_key.Finish()
|
||||
|
||||
|
||||
|
||||
job_key = ClientThreading.JobKey()
|
||||
|
||||
job_key.Begin()
|
||||
|
||||
wx.CallAfter( wx_code, win, job_key )
|
||||
|
||||
while not job_key.IsDone():
|
||||
|
||||
if HG.model_shutdown:
|
||||
|
||||
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
|
||||
|
||||
|
||||
time.sleep( 0.05 )
|
||||
|
||||
|
||||
if job_key.HasVariable( 'result' ):
|
||||
|
||||
# result can be None, for wx_code that has no return variable
|
||||
|
||||
result = job_key.GetIfHasVariable( 'result' )
|
||||
|
||||
return result
|
||||
|
||||
|
||||
error = job_key.GetIfHasVariable( 'error' )
|
||||
|
||||
if error is not None:
|
||||
|
||||
raise error
|
||||
|
||||
|
||||
raise HydrusExceptions.ShutdownException()
|
||||
|
||||
|
||||
def CallToThread( self, callable, *args, **kwargs ):
|
||||
|
||||
call_to_thread = self._GetCallToThread()
|
||||
|
||||
call_to_thread.put( callable, *args, **kwargs )
|
||||
|
||||
|
||||
CallToThreadLongRunning = CallToThread
|
||||
|
||||
def CallLater( self, initial_delay, func, *args, **kwargs ):
|
||||
|
||||
call = HydrusData.Call( func, *args, **kwargs )
|
||||
|
||||
job = HydrusThreading.SchedulableJob( self, self._job_scheduler, initial_delay, call )
|
||||
|
||||
self._job_scheduler.AddJob( job )
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def CallLaterWXSafe( self, window, initial_delay, func, *args, **kwargs ):
|
||||
|
||||
call = HydrusData.Call( func, *args, **kwargs )
|
||||
|
||||
job = ClientThreading.WXAwareJob( self, self._job_scheduler, window, initial_delay, call )
|
||||
|
||||
self._job_scheduler.AddJob( job )
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def CallRepeating( self, initial_delay, period, func, *args, **kwargs ):
|
||||
|
||||
call = HydrusData.Call( func, *args, **kwargs )
|
||||
|
||||
job = HydrusThreading.RepeatingJob( self, self._job_scheduler, initial_delay, period, call )
|
||||
|
||||
self._job_scheduler.AddJob( job )
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def CallRepeatingWXSafe( self, window, initial_delay, period, func, *args, **kwargs ):
|
||||
|
||||
call = HydrusData.Call( func, *args, **kwargs )
|
||||
|
||||
job = ClientThreading.WXAwareRepeatingJob( self, self._job_scheduler, window, initial_delay, period, call )
|
||||
|
||||
self._job_scheduler.AddJob( job )
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def ClearWrites( self, name ):
|
||||
|
||||
if name in self._writes:
|
||||
|
||||
del self._writes[ name ]
|
||||
|
||||
|
||||
|
||||
def DBCurrentlyDoingJob( self ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def GetFilesDir( self ):
|
||||
|
||||
return self._server_files_dir
|
||||
|
||||
|
||||
def GetNewOptions( self ):
|
||||
|
||||
return self.new_options
|
||||
|
||||
|
||||
def GetManager( self, manager_type ):
|
||||
|
||||
return self._managers[ manager_type ]
|
||||
|
||||
|
||||
def GetWrite( self, name ):
|
||||
|
||||
write = self._writes[ name ]
|
||||
|
||||
del self._writes[ name ]
|
||||
|
||||
return write
|
||||
|
||||
|
||||
def ImportURLFromAPI( self, url, service_keys_to_tags, destination_page_name ):
|
||||
|
||||
normalised_url = self.network_engine.domain_manager.NormaliseURL( url )
|
||||
|
||||
human_result_text = '"{}" URL added successfully.'.format( normalised_url )
|
||||
|
||||
self.Write( 'import_url_test', url, service_keys_to_tags, destination_page_name )
|
||||
|
||||
return ( normalised_url, human_result_text )
|
||||
|
||||
|
||||
def IsBooted( self ):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def IsCurrentPage( self, page_key ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def IsFirstStart( self ):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def IShouldRegularlyUpdate( self, window ):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def JustWokeFromSleep( self ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def ModelIsShutdown( self ):
|
||||
|
||||
return HG.model_shutdown
|
||||
|
||||
|
||||
def PageAlive( self, page_key ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def PageClosedButNotDestroyed( self, page_key ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def Read( self, name, *args, **kwargs ):
|
||||
|
||||
return self._reads[ name ]
|
||||
|
||||
|
||||
def RegisterUIUpdateWindow( self, window ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ReleasePageKey( self, page_key ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ReportDataUsed( self, num_bytes ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ReportRequestUsed( self ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ResetIdleTimer( self ): pass
|
||||
|
||||
def Run( self, window ):
|
||||
|
||||
# we are in wx thread here, we can do this
|
||||
self._SetupWx()
|
||||
|
||||
suites = []
|
||||
|
||||
if self.only_run is None:
|
||||
|
||||
run_all = True
|
||||
|
||||
else:
|
||||
|
||||
run_all = False
|
||||
|
||||
|
||||
# the gui stuff runs fine on its own but crashes in the full test if it is not early, wew
|
||||
# something to do with the delayed button clicking stuff
|
||||
if run_all or self.only_run == 'gui':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestDialogs ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientListBoxes ) )
|
||||
|
||||
if run_all or self.only_run == 'client_api':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientAPI ) )
|
||||
|
||||
if run_all or self.only_run == 'daemons':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDaemons ) )
|
||||
|
||||
if run_all or self.only_run == 'data':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientConstants ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientData ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImportOptions ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestFunctions ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSerialisable ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSessions ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusTags ) )
|
||||
|
||||
if run_all or self.only_run == 'db':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestDB ) )
|
||||
|
||||
if run_all or self.only_run == 'networking':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientNetworking ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNetworking ) )
|
||||
|
||||
if run_all or self.only_run == 'import':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImportSubscriptions ) )
|
||||
|
||||
if run_all or self.only_run == 'image':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImageHandling ) )
|
||||
|
||||
if run_all or self.only_run == 'nat':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNATPunch ) )
|
||||
|
||||
if run_all or self.only_run == 'server':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusServer ) )
|
||||
|
||||
|
||||
suite = unittest.TestSuite( suites )
|
||||
|
||||
runner = unittest.TextTestRunner( verbosity = 2 )
|
||||
|
||||
def do_it():
|
||||
|
||||
try:
|
||||
|
||||
runner.run( suite )
|
||||
|
||||
finally:
|
||||
|
||||
wx.CallAfter( self.win.Destroy )
|
||||
|
||||
|
||||
|
||||
self.win.Show()
|
||||
|
||||
test_thread = threading.Thread( target = do_it )
|
||||
|
||||
test_thread.start()
|
||||
|
||||
|
||||
def SetRead( self, name, value ):
|
||||
|
||||
self._reads[ name ] = value
|
||||
|
||||
|
||||
def SetStatusBarDirty( self ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def SetWebCookies( self, name, value ):
|
||||
|
||||
self._cookies[ name ] = value
|
||||
|
||||
|
||||
def TidyUp( self ):
|
||||
|
||||
time.sleep( 2 )
|
||||
|
||||
HydrusPaths.DeletePath( self.db_dir )
|
||||
|
||||
|
||||
def ViewIsShutdown( self ):
|
||||
|
||||
return HG.view_shutdown
|
||||
|
||||
|
||||
def WaitUntilModelFree( self ):
|
||||
|
||||
return
|
||||
|
||||
|
||||
def WaitUntilViewFree( self ):
|
||||
|
||||
return
|
||||
|
||||
|
||||
def Write( self, name, *args, **kwargs ):
|
||||
|
||||
self._writes[ name ].append( ( args, kwargs ) )
|
||||
|
||||
|
||||
def WriteSynchronous( self, name, *args, **kwargs ):
|
||||
|
||||
self._writes[ name ].append( ( args, kwargs ) )
|
||||
|
||||
if name == 'import_file':
|
||||
|
||||
( file_import_job, ) = args
|
||||
|
||||
if file_import_job.GetHash().hex() == 'a593942cb7ea9ffcd8ccf2f0fa23c338e23bfecd9a3e508dfc0bcf07501ead08': # 'blarg' in sha256 hex
|
||||
|
||||
raise Exception( 'File failed to import for some reason!' )
|
||||
|
||||
else:
|
||||
|
||||
return ( CC.STATUS_SUCCESSFUL_AND_NEW, 'test note' )
|
||||
|
||||
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ from . import ServerDB
|
|||
import shutil
|
||||
import sqlite3
|
||||
import stat
|
||||
from . import TestConstants
|
||||
from . import TestController
|
||||
import time
|
||||
import threading
|
||||
import unittest
|
||||
|
@ -43,7 +43,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
cls._delete_db()
|
||||
|
||||
# class variable
|
||||
cls._db = ClientDB.DB( HG.test_controller, TestConstants.DB_DIR, 'client' )
|
||||
cls._db = ClientDB.DB( HG.test_controller, TestController.DB_DIR, 'client' )
|
||||
|
||||
|
||||
@classmethod
|
||||
|
@ -60,7 +60,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
for filename in db_filenames:
|
||||
|
||||
path = os.path.join( TestConstants.DB_DIR, filename )
|
||||
path = os.path.join( TestController.DB_DIR, filename )
|
||||
|
||||
os.remove( path )
|
||||
|
||||
|
@ -71,7 +71,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
@classmethod
|
||||
def setUpClass( cls ):
|
||||
|
||||
cls._db = ClientDB.DB( HG.test_controller, TestConstants.DB_DIR, 'client' )
|
||||
cls._db = ClientDB.DB( HG.test_controller, TestController.DB_DIR, 'client' )
|
||||
|
||||
HG.test_controller.SetRead( 'hash_status', ( CC.STATUS_UNKNOWN, None, '' ) )
|
||||
|
||||
|
@ -674,7 +674,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
#
|
||||
|
||||
fsc = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY, predicates = [ ClientSearch.Predicate( HC.PREDICATE_TYPE_SYSTEM_RATING, ( '>', 0.2, TestConstants.LOCAL_RATING_NUMERICAL_SERVICE_KEY ) ), ClientSearch.Predicate( HC.PREDICATE_TYPE_SYSTEM_FILE_SERVICE, ( True, HC.CONTENT_STATUS_CURRENT, CC.LOCAL_FILE_SERVICE_KEY ) ) ] )
|
||||
fsc = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY, predicates = [ ClientSearch.Predicate( HC.PREDICATE_TYPE_SYSTEM_RATING, ( '>', 0.2, TestController.LOCAL_RATING_NUMERICAL_SERVICE_KEY ) ), ClientSearch.Predicate( HC.PREDICATE_TYPE_SYSTEM_FILE_SERVICE, ( True, HC.CONTENT_STATUS_CURRENT, CC.LOCAL_FILE_SERVICE_KEY ) ) ] )
|
||||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerQuery( 'files', CC.LOCAL_FILE_SERVICE_KEY, fsc, True )
|
||||
|
||||
|
@ -793,8 +793,8 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
def test_import_folders( self ):
|
||||
|
||||
import_folder_1 = ClientImportLocal.ImportFolder( 'imp 1', path = TestConstants.DB_DIR, mimes = HC.VIDEO, publish_files_to_popup_button = False )
|
||||
import_folder_2 = ClientImportLocal.ImportFolder( 'imp 2', path = TestConstants.DB_DIR, mimes = HC.IMAGES, period = 1200, publish_files_to_popup_button = False )
|
||||
import_folder_1 = ClientImportLocal.ImportFolder( 'imp 1', path = TestController.DB_DIR, mimes = HC.VIDEO, publish_files_to_popup_button = False )
|
||||
import_folder_2 = ClientImportLocal.ImportFolder( 'imp 2', path = TestController.DB_DIR, mimes = HC.IMAGES, period = 1200, publish_files_to_popup_button = False )
|
||||
|
||||
#
|
||||
|
||||
|
@ -827,11 +827,11 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
def test_init( self ):
|
||||
|
||||
self.assertTrue( os.path.exists( TestConstants.DB_DIR ) )
|
||||
self.assertTrue( os.path.exists( TestController.DB_DIR ) )
|
||||
|
||||
self.assertTrue( os.path.exists( os.path.join( TestConstants.DB_DIR, 'client.db' ) ) )
|
||||
self.assertTrue( os.path.exists( os.path.join( TestController.DB_DIR, 'client.db' ) ) )
|
||||
|
||||
client_files_default = os.path.join( TestConstants.DB_DIR, 'client_files' )
|
||||
client_files_default = os.path.join( TestController.DB_DIR, 'client_files' )
|
||||
|
||||
self.assertTrue( os.path.exists( client_files_default ) )
|
||||
|
||||
|
@ -1188,7 +1188,7 @@ class TestServerDB( unittest.TestCase ):
|
|||
@classmethod
|
||||
def setUpClass( cls ):
|
||||
|
||||
cls._db = ServerDB.DB( HG.test_controller, TestConstants.DB_DIR, 'server' )
|
||||
cls._db = ServerDB.DB( HG.test_controller, TestController.DB_DIR, 'server' )
|
||||
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -8,7 +8,6 @@ from . import ClientThreading
|
|||
import collections
|
||||
from . import HydrusConstants as HC
|
||||
import os
|
||||
from . import TestConstants
|
||||
import unittest
|
||||
import wx
|
||||
from . import HydrusGlobals as HG
|
||||
|
@ -61,114 +60,134 @@ class TestDBDialogs( unittest.TestCase ):
|
|||
|
||||
def test_dialog_manage_subs( self ):
|
||||
|
||||
title = 'subs test'
|
||||
def wx_code():
|
||||
|
||||
title = 'subs test'
|
||||
|
||||
with ClientGUITopLevelWindows.DialogEdit( None, title ) as dlg:
|
||||
|
||||
panel = ClientGUIScrolledPanelsEdit.EditSubscriptionsPanel( dlg, [] )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
HG.test_controller.CallLaterWXSafe( dlg, 2, panel.Add )
|
||||
|
||||
HG.test_controller.CallLaterWXSafe( dlg, 4, OKChildDialog, panel )
|
||||
|
||||
HG.test_controller.CallLaterWXSafe( dlg, 6, HitCancelButton, dlg )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_CANCEL )
|
||||
|
||||
|
||||
|
||||
with ClientGUITopLevelWindows.DialogEdit( None, title ) as dlg:
|
||||
|
||||
panel = ClientGUIScrolledPanelsEdit.EditSubscriptionsPanel( dlg, [] )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
HG.test_controller.CallLaterWXSafe( dlg, 2, panel.Add )
|
||||
|
||||
HG.test_controller.CallLaterWXSafe( dlg, 4, OKChildDialog, panel )
|
||||
|
||||
HG.test_controller.CallLaterWXSafe( dlg, 6, HitCancelButton, dlg )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_CANCEL )
|
||||
|
||||
HG.test_controller.CallBlockingToWX( HG.test_controller.win, wx_code )
|
||||
|
||||
|
||||
class TestNonDBDialogs( unittest.TestCase ):
|
||||
|
||||
def test_dialog_choose_new_service_method( self ):
|
||||
|
||||
with ClientGUIDialogs.DialogChooseNewServiceMethod( None ) as dlg:
|
||||
def wx_code():
|
||||
|
||||
HitButton( dlg._register )
|
||||
with ClientGUIDialogs.DialogChooseNewServiceMethod( None ) as dlg:
|
||||
|
||||
HitButton( dlg._register )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_OK )
|
||||
|
||||
register = dlg.GetRegister()
|
||||
|
||||
self.assertEqual( register, True )
|
||||
|
||||
|
||||
result = dlg.ShowModal()
|
||||
with ClientGUIDialogs.DialogChooseNewServiceMethod( None ) as dlg:
|
||||
|
||||
HitButton( dlg._setup )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_OK )
|
||||
|
||||
register = dlg.GetRegister()
|
||||
|
||||
self.assertEqual( register, False )
|
||||
|
||||
|
||||
self.assertEqual( result, wx.ID_OK )
|
||||
|
||||
register = dlg.GetRegister()
|
||||
|
||||
self.assertEqual( register, True )
|
||||
with ClientGUIDialogs.DialogChooseNewServiceMethod( None ) as dlg:
|
||||
|
||||
HitCancelButton( dlg )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_CANCEL )
|
||||
|
||||
|
||||
|
||||
with ClientGUIDialogs.DialogChooseNewServiceMethod( None ) as dlg:
|
||||
|
||||
HitButton( dlg._setup )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_OK )
|
||||
|
||||
register = dlg.GetRegister()
|
||||
|
||||
self.assertEqual( register, False )
|
||||
|
||||
|
||||
with ClientGUIDialogs.DialogChooseNewServiceMethod( None ) as dlg:
|
||||
|
||||
HitCancelButton( dlg )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_CANCEL )
|
||||
|
||||
HG.test_controller.CallBlockingToWX( HG.test_controller.win, wx_code )
|
||||
|
||||
|
||||
def test_dialog_finish_filtering( self ):
|
||||
|
||||
with ClientGUIDialogs.DialogFinishFiltering( None, 'keep 3 files and delete 5 files?' ) as dlg:
|
||||
def wx_code():
|
||||
|
||||
HitButton( dlg._back )
|
||||
with ClientGUIDialogs.DialogFinishFiltering( None, 'keep 3 files and delete 5 files?' ) as dlg:
|
||||
|
||||
HitButton( dlg._back )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_CANCEL )
|
||||
|
||||
|
||||
result = dlg.ShowModal()
|
||||
with ClientGUIDialogs.DialogFinishFiltering( None, 'keep 3 files and delete 5 files?' ) as dlg:
|
||||
|
||||
HitButton( dlg._commit )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_YES )
|
||||
|
||||
|
||||
self.assertEqual( result, wx.ID_CANCEL )
|
||||
with ClientGUIDialogs.DialogFinishFiltering( None, 'keep 3 files and delete 5 files?' ) as dlg:
|
||||
|
||||
HitButton( dlg._forget )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_NO )
|
||||
|
||||
|
||||
|
||||
with ClientGUIDialogs.DialogFinishFiltering( None, 'keep 3 files and delete 5 files?' ) as dlg:
|
||||
|
||||
HitButton( dlg._commit )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_YES )
|
||||
|
||||
|
||||
with ClientGUIDialogs.DialogFinishFiltering( None, 'keep 3 files and delete 5 files?' ) as dlg:
|
||||
|
||||
HitButton( dlg._forget )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_NO )
|
||||
|
||||
HG.test_controller.CallBlockingToWX( HG.test_controller.win, wx_code )
|
||||
|
||||
|
||||
def test_dialog_yes_no( self ):
|
||||
|
||||
with ClientGUIDialogs.DialogYesNo( None, 'hello' ) as dlg:
|
||||
def wx_code():
|
||||
|
||||
HitButton( dlg._yes )
|
||||
with ClientGUIDialogs.DialogYesNo( None, 'hello' ) as dlg:
|
||||
|
||||
HitButton( dlg._yes )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_YES )
|
||||
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_YES )
|
||||
with ClientGUIDialogs.DialogYesNo( None, 'hello' ) as dlg:
|
||||
|
||||
HitButton( dlg._no )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_NO )
|
||||
|
||||
|
||||
|
||||
with ClientGUIDialogs.DialogYesNo( None, 'hello' ) as dlg:
|
||||
|
||||
HitButton( dlg._no )
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
self.assertEqual( result, wx.ID_NO )
|
||||
|
||||
HG.test_controller.CallBlockingToWX( HG.test_controller.win, wx_code )
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ from . import HydrusConstants as HC
|
|||
from . import ClientData
|
||||
from . import ClientTags
|
||||
import os
|
||||
from . import TestConstants
|
||||
import unittest
|
||||
from . import HydrusData
|
||||
from . import ClientConstants as CC
|
||||
|
|
|
@ -2,7 +2,6 @@ import collections
|
|||
from . import HydrusConstants as HC
|
||||
import os
|
||||
import random
|
||||
from . import TestConstants
|
||||
import time
|
||||
import unittest
|
||||
from . import HydrusData
|
||||
|
|
|
@ -17,7 +17,7 @@ from . import HydrusConstants as HC
|
|||
from . import HydrusData
|
||||
from . import HydrusNetwork
|
||||
from . import HydrusSerialisable
|
||||
from . import TestConstants as TC
|
||||
from . import TestController as TC
|
||||
import os
|
||||
import unittest
|
||||
import wx
|
||||
|
|
|
@ -20,7 +20,7 @@ import random
|
|||
from . import ServerFiles
|
||||
from . import ServerServer
|
||||
import ssl
|
||||
from . import TestConstants
|
||||
from . import TestController
|
||||
import time
|
||||
import unittest
|
||||
import urllib
|
||||
|
@ -79,8 +79,8 @@ class TestServer( unittest.TestCase ):
|
|||
|
||||
def TWISTEDSetup():
|
||||
|
||||
cls._ssl_cert_path = os.path.join( TestConstants.DB_DIR, 'server.crt' )
|
||||
cls._ssl_key_path = os.path.join( TestConstants.DB_DIR, 'server.key' )
|
||||
cls._ssl_cert_path = os.path.join( TestController.DB_DIR, 'server.crt' )
|
||||
cls._ssl_key_path = os.path.join( TestController.DB_DIR, 'server.key' )
|
||||
|
||||
# if db test ran, this is still hanging around and read-only, so don't bother to fail overwriting
|
||||
if not os.path.exists( cls._ssl_cert_path ):
|
||||
|
@ -243,7 +243,7 @@ class TestServer( unittest.TestCase ):
|
|||
share_key = HydrusData.GenerateKey()
|
||||
hashes = [ HydrusData.GenerateKey() for i in range( 5 ) ]
|
||||
|
||||
client_files_default = os.path.join( TestConstants.DB_DIR, 'client_files' )
|
||||
client_files_default = os.path.join( TestController.DB_DIR, 'client_files' )
|
||||
|
||||
hash_encoded = hashes[0].hex()
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ from . import HydrusExceptions
|
|||
from . import HydrusNetwork
|
||||
from . import HydrusSessions
|
||||
import os
|
||||
from . import TestConstants
|
||||
import unittest
|
||||
from . import HydrusData
|
||||
from . import HydrusGlobals as HG
|
||||
|
|
|
@ -2,7 +2,6 @@ import collections
|
|||
from . import HydrusConstants as HC
|
||||
from . import HydrusTags
|
||||
import os
|
||||
from . import TestConstants
|
||||
import unittest
|
||||
from . import HydrusData
|
||||
from . import ClientCaches
|
||||
|
|
614
test.py
614
test.py
|
@ -6,612 +6,15 @@ try: locale.setlocale( locale.LC_ALL, '' )
|
|||
except: pass
|
||||
|
||||
from include import HydrusConstants as HC
|
||||
from include import ClientConstants as CC
|
||||
from include import HydrusData
|
||||
from include import HydrusGlobals as HG
|
||||
from include import ClientAPI
|
||||
from include import ClientDefaults
|
||||
from include import ClientNetworking
|
||||
from include import ClientNetworkingBandwidth
|
||||
from include import ClientNetworkingDomain
|
||||
from include import ClientNetworkingLogin
|
||||
from include import ClientNetworkingSessions
|
||||
from include import ClientServices
|
||||
from include import ClientThreading
|
||||
from include import HydrusExceptions
|
||||
from include import HydrusPubSub
|
||||
from include import HydrusSessions
|
||||
from include import HydrusTags
|
||||
from include import HydrusThreading
|
||||
from include import TestClientAPI
|
||||
from include import TestClientConstants
|
||||
from include import TestClientDaemons
|
||||
from include import TestClientData
|
||||
from include import TestClientImageHandling
|
||||
from include import TestClientImportOptions
|
||||
from include import TestClientImportSubscriptions
|
||||
from include import TestClientListBoxes
|
||||
from include import TestClientNetworking
|
||||
from include import TestConstants
|
||||
from include import TestDialogs
|
||||
from include import TestDB
|
||||
from include import TestFunctions
|
||||
from include import TestHydrusNATPunch
|
||||
from include import TestHydrusNetworking
|
||||
from include import TestHydrusSerialisable
|
||||
from include import TestHydrusServer
|
||||
from include import TestHydrusSessions
|
||||
from include import TestHydrusTags
|
||||
import collections
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
from include import TestController
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import unittest
|
||||
import wx
|
||||
from twisted.internet import reactor
|
||||
from include import ClientCaches
|
||||
from include import ClientData
|
||||
from include import ClientOptions
|
||||
from include import HydrusData
|
||||
from include import HydrusPaths
|
||||
|
||||
only_run = None
|
||||
|
||||
class Controller( object ):
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
self.db_dir = tempfile.mkdtemp()
|
||||
|
||||
TestConstants.DB_DIR = self.db_dir
|
||||
|
||||
self._server_files_dir = os.path.join( self.db_dir, 'server_files' )
|
||||
self._updates_dir = os.path.join( self.db_dir, 'test_updates' )
|
||||
|
||||
client_files_default = os.path.join( self.db_dir, 'client_files' )
|
||||
|
||||
HydrusPaths.MakeSureDirectoryExists( self._server_files_dir )
|
||||
HydrusPaths.MakeSureDirectoryExists( self._updates_dir )
|
||||
HydrusPaths.MakeSureDirectoryExists( client_files_default )
|
||||
|
||||
HG.controller = self
|
||||
HG.client_controller = self
|
||||
HG.server_controller = self
|
||||
HG.test_controller = self
|
||||
|
||||
self.gui = self
|
||||
|
||||
self._call_to_threads = []
|
||||
|
||||
self._pubsub = HydrusPubSub.HydrusPubSub( self )
|
||||
|
||||
self.new_options = ClientOptions.ClientOptions( self.db_dir )
|
||||
|
||||
HC.options = ClientDefaults.GetClientDefaultOptions()
|
||||
|
||||
self.options = HC.options
|
||||
|
||||
def show_text( text ): pass
|
||||
|
||||
HydrusData.ShowText = show_text
|
||||
|
||||
self._reads = {}
|
||||
|
||||
self._reads[ 'local_booru_share_keys' ] = []
|
||||
self._reads[ 'messaging_sessions' ] = []
|
||||
self._reads[ 'tag_censorship' ] = []
|
||||
self._reads[ 'options' ] = ClientDefaults.GetClientDefaultOptions()
|
||||
self._reads[ 'file_system_predicates' ] = []
|
||||
self._reads[ 'media_results' ] = []
|
||||
|
||||
self.example_tag_repo_service_key = HydrusData.GenerateKey()
|
||||
|
||||
services = []
|
||||
|
||||
services.append( ClientServices.GenerateService( CC.LOCAL_BOORU_SERVICE_KEY, HC.LOCAL_BOORU, 'local booru' ) )
|
||||
services.append( ClientServices.GenerateService( CC.CLIENT_API_SERVICE_KEY, HC.CLIENT_API_SERVICE, 'client api' ) )
|
||||
services.append( ClientServices.GenerateService( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, HC.COMBINED_LOCAL_FILE, 'all local files' ) )
|
||||
services.append( ClientServices.GenerateService( CC.LOCAL_FILE_SERVICE_KEY, HC.LOCAL_FILE_DOMAIN, 'my files' ) )
|
||||
services.append( ClientServices.GenerateService( CC.TRASH_SERVICE_KEY, HC.LOCAL_FILE_TRASH_DOMAIN, 'trash' ) )
|
||||
services.append( ClientServices.GenerateService( CC.LOCAL_TAG_SERVICE_KEY, HC.LOCAL_TAG, 'local tags' ) )
|
||||
services.append( ClientServices.GenerateService( self.example_tag_repo_service_key, HC.TAG_REPOSITORY, 'example tag repo' ) )
|
||||
services.append( ClientServices.GenerateService( CC.COMBINED_TAG_SERVICE_KEY, HC.COMBINED_TAG, 'all known tags' ) )
|
||||
services.append( ClientServices.GenerateService( TestConstants.LOCAL_RATING_LIKE_SERVICE_KEY, HC.LOCAL_RATING_LIKE, 'example local rating like service' ) )
|
||||
services.append( ClientServices.GenerateService( TestConstants.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.LOCAL_RATING_NUMERICAL, 'example local rating numerical service' ) )
|
||||
|
||||
self._reads[ 'services' ] = services
|
||||
|
||||
client_files_locations = {}
|
||||
|
||||
for prefix in HydrusData.IterateHexPrefixes():
|
||||
|
||||
for c in ( 'f', 't', 'r' ):
|
||||
|
||||
client_files_locations[ c + prefix ] = client_files_default
|
||||
|
||||
|
||||
|
||||
self._reads[ 'client_files_locations' ] = client_files_locations
|
||||
|
||||
self._reads[ 'sessions' ] = []
|
||||
self._reads[ 'tag_parents' ] = {}
|
||||
self._reads[ 'tag_siblings' ] = {}
|
||||
self._reads[ 'in_inbox' ] = False
|
||||
|
||||
self._writes = collections.defaultdict( list )
|
||||
|
||||
self._managers = {}
|
||||
|
||||
self.services_manager = ClientCaches.ServicesManager( self )
|
||||
self.client_files_manager = ClientCaches.ClientFilesManager( self )
|
||||
|
||||
self.parsing_cache = ClientCaches.ParsingCache()
|
||||
|
||||
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
|
||||
session_manager = ClientNetworkingSessions.NetworkSessionManager()
|
||||
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
|
||||
|
||||
ClientDefaults.SetDefaultDomainManagerData( domain_manager )
|
||||
|
||||
login_manager = ClientNetworkingLogin.NetworkLoginManager()
|
||||
|
||||
self.network_engine = ClientNetworking.NetworkEngine( self, bandwidth_manager, session_manager, domain_manager, login_manager )
|
||||
|
||||
self.CallToThreadLongRunning( self.network_engine.MainLoop )
|
||||
|
||||
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager( self )
|
||||
self._managers[ 'tag_siblings' ] = ClientCaches.TagSiblingsManager( self )
|
||||
self._managers[ 'tag_parents' ] = ClientCaches.TagParentsManager( self )
|
||||
self._managers[ 'undo' ] = ClientCaches.UndoManager( self )
|
||||
self.server_session_manager = HydrusSessions.HydrusSessionManagerServer()
|
||||
|
||||
self.local_booru_manager = ClientCaches.LocalBooruCache( self )
|
||||
self.client_api_manager = ClientAPI.APIManager()
|
||||
|
||||
self._cookies = {}
|
||||
|
||||
self._job_scheduler = HydrusThreading.JobScheduler( self )
|
||||
|
||||
self._job_scheduler.start()
|
||||
|
||||
|
||||
def _GetCallToThread( self ):
|
||||
|
||||
for call_to_thread in self._call_to_threads:
|
||||
|
||||
if not call_to_thread.CurrentlyWorking():
|
||||
|
||||
return call_to_thread
|
||||
|
||||
|
||||
|
||||
if len( self._call_to_threads ) > 100:
|
||||
|
||||
raise Exception( 'Too many call to threads!' )
|
||||
|
||||
|
||||
call_to_thread = HydrusThreading.THREADCallToThread( self, 'CallToThread' )
|
||||
|
||||
self._call_to_threads.append( call_to_thread )
|
||||
|
||||
call_to_thread.start()
|
||||
|
||||
return call_to_thread
|
||||
|
||||
|
||||
def _SetupWx( self ):
|
||||
|
||||
self.locale = wx.Locale( wx.LANGUAGE_DEFAULT ) # Very important to init this here and keep it non garbage collected
|
||||
|
||||
CC.GlobalBMPs.STATICInitialise()
|
||||
|
||||
self.frame_icon = wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ), wx.BITMAP_TYPE_PNG )
|
||||
|
||||
|
||||
def pub( self, topic, *args, **kwargs ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def pubimmediate( self, topic, *args, **kwargs ):
|
||||
|
||||
self._pubsub.pubimmediate( topic, *args, **kwargs )
|
||||
|
||||
|
||||
def sub( self, object, method_name, topic ):
|
||||
|
||||
self._pubsub.sub( object, method_name, topic )
|
||||
|
||||
|
||||
def AcquirePageKey( self ):
|
||||
|
||||
return HydrusData.GenerateKey()
|
||||
|
||||
|
||||
def CallBlockingToWX( self, win, func, *args, **kwargs ):
|
||||
|
||||
def wx_code( win, job_key ):
|
||||
|
||||
try:
|
||||
|
||||
if win is not None and not win:
|
||||
|
||||
raise HydrusExceptions.WXDeadWindowException( 'Parent Window was destroyed before wx command was called!' )
|
||||
|
||||
|
||||
result = func( *args, **kwargs )
|
||||
|
||||
job_key.SetVariable( 'result', result )
|
||||
|
||||
except ( HydrusExceptions.WXDeadWindowException, HydrusExceptions.InsufficientCredentialsException, HydrusExceptions.ShutdownException ) as e:
|
||||
|
||||
job_key.SetVariable( 'error', e )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
job_key.SetVariable( 'error', e )
|
||||
|
||||
HydrusData.Print( 'CallBlockingToWX just caught this error:' )
|
||||
HydrusData.DebugPrint( traceback.format_exc() )
|
||||
|
||||
finally:
|
||||
|
||||
job_key.Finish()
|
||||
|
||||
|
||||
|
||||
job_key = ClientThreading.JobKey()
|
||||
|
||||
job_key.Begin()
|
||||
|
||||
wx.CallAfter( wx_code, win, job_key )
|
||||
|
||||
while not job_key.IsDone():
|
||||
|
||||
if HG.model_shutdown:
|
||||
|
||||
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
|
||||
|
||||
|
||||
time.sleep( 0.05 )
|
||||
|
||||
|
||||
if job_key.HasVariable( 'result' ):
|
||||
|
||||
# result can be None, for wx_code that has no return variable
|
||||
|
||||
result = job_key.GetIfHasVariable( 'result' )
|
||||
|
||||
return result
|
||||
|
||||
|
||||
error = job_key.GetIfHasVariable( 'error' )
|
||||
|
||||
if error is not None:
|
||||
|
||||
raise error
|
||||
|
||||
|
||||
raise HydrusExceptions.ShutdownException()
|
||||
|
||||
|
||||
def CallToThread( self, callable, *args, **kwargs ):
|
||||
|
||||
call_to_thread = self._GetCallToThread()
|
||||
|
||||
call_to_thread.put( callable, *args, **kwargs )
|
||||
|
||||
|
||||
CallToThreadLongRunning = CallToThread
|
||||
|
||||
def CallLater( self, initial_delay, func, *args, **kwargs ):
|
||||
|
||||
call = HydrusData.Call( func, *args, **kwargs )
|
||||
|
||||
job = HydrusThreading.SchedulableJob( self, self._job_scheduler, initial_delay, call )
|
||||
|
||||
self._job_scheduler.AddJob( job )
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def CallLaterWXSafe( self, window, initial_delay, func, *args, **kwargs ):
|
||||
|
||||
call = HydrusData.Call( func, *args, **kwargs )
|
||||
|
||||
job = ClientThreading.WXAwareJob( self, self._job_scheduler, window, initial_delay, call )
|
||||
|
||||
self._job_scheduler.AddJob( job )
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def CallRepeating( self, initial_delay, period, func, *args, **kwargs ):
|
||||
|
||||
call = HydrusData.Call( func, *args, **kwargs )
|
||||
|
||||
job = HydrusThreading.RepeatingJob( self, self._job_scheduler, initial_delay, period, call )
|
||||
|
||||
self._job_scheduler.AddJob( job )
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def CallRepeatingWXSafe( self, window, initial_delay, period, func, *args, **kwargs ):
|
||||
|
||||
call = HydrusData.Call( func, *args, **kwargs )
|
||||
|
||||
job = ClientThreading.WXAwareRepeatingJob( self, self._job_scheduler, window, initial_delay, period, call )
|
||||
|
||||
self._job_scheduler.AddJob( job )
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def ClearWrites( self, name ):
|
||||
|
||||
if name in self._writes:
|
||||
|
||||
del self._writes[ name ]
|
||||
|
||||
|
||||
|
||||
def DBCurrentlyDoingJob( self ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def GetFilesDir( self ):
|
||||
|
||||
return self._server_files_dir
|
||||
|
||||
|
||||
def GetNewOptions( self ):
|
||||
|
||||
return self.new_options
|
||||
|
||||
|
||||
def GetManager( self, manager_type ):
|
||||
|
||||
return self._managers[ manager_type ]
|
||||
|
||||
|
||||
def GetWrite( self, name ):
|
||||
|
||||
write = self._writes[ name ]
|
||||
|
||||
del self._writes[ name ]
|
||||
|
||||
return write
|
||||
|
||||
|
||||
def ImportURLFromAPI( self, url, service_keys_to_tags, destination_page_name ):
|
||||
|
||||
normalised_url = self.network_engine.domain_manager.NormaliseURL( url )
|
||||
|
||||
human_result_text = '"{}" URL added successfully.'.format( normalised_url )
|
||||
|
||||
self.Write( 'import_url_test', url, service_keys_to_tags, destination_page_name )
|
||||
|
||||
return ( normalised_url, human_result_text )
|
||||
|
||||
|
||||
def IsBooted( self ):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def IsCurrentPage( self, page_key ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def IsFirstStart( self ):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def IShouldRegularlyUpdate( self, window ):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def JustWokeFromSleep( self ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def ModelIsShutdown( self ):
|
||||
|
||||
return HG.model_shutdown
|
||||
|
||||
|
||||
def PageAlive( self, page_key ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def PageClosedButNotDestroyed( self, page_key ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def Read( self, name, *args, **kwargs ):
|
||||
|
||||
return self._reads[ name ]
|
||||
|
||||
|
||||
def RegisterUIUpdateWindow( self, window ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ReleasePageKey( self, page_key ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ReportDataUsed( self, num_bytes ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ReportRequestUsed( self ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def ResetIdleTimer( self ): pass
|
||||
|
||||
def Run( self, window ):
|
||||
|
||||
# we are in wx thread here, we can do this
|
||||
self._SetupWx()
|
||||
|
||||
suites = []
|
||||
|
||||
if only_run is None: run_all = True
|
||||
else: run_all = False
|
||||
|
||||
# the gui stuff runs fine on its own but crashes in the full test if it is not early, wew
|
||||
# something to do with the delayed button clicking stuff
|
||||
if run_all or only_run == 'gui':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestDialogs ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientListBoxes ) )
|
||||
|
||||
if run_all or only_run == 'client_api':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientAPI ) )
|
||||
|
||||
if run_all or only_run == 'daemons':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDaemons ) )
|
||||
|
||||
if run_all or only_run == 'data':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientConstants ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientData ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImportOptions ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestFunctions ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSerialisable ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSessions ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusTags ) )
|
||||
|
||||
if run_all or only_run == 'db':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestDB ) )
|
||||
|
||||
if run_all or only_run == 'networking':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientNetworking ) )
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNetworking ) )
|
||||
|
||||
if run_all or only_run == 'import':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImportSubscriptions ) )
|
||||
|
||||
if run_all or only_run == 'image':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImageHandling ) )
|
||||
|
||||
if run_all or only_run == 'nat':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNATPunch ) )
|
||||
|
||||
if run_all or only_run == 'server':
|
||||
|
||||
suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusServer ) )
|
||||
|
||||
|
||||
suite = unittest.TestSuite( suites )
|
||||
|
||||
runner = unittest.TextTestRunner( verbosity = 2 )
|
||||
|
||||
def do_it():
|
||||
|
||||
try:
|
||||
|
||||
runner.run( suite )
|
||||
|
||||
finally:
|
||||
|
||||
wx.CallAfter( win.Destroy )
|
||||
|
||||
|
||||
|
||||
win.Show()
|
||||
|
||||
test_thread = threading.Thread( target = do_it )
|
||||
|
||||
test_thread.start()
|
||||
|
||||
|
||||
def SetRead( self, name, value ):
|
||||
|
||||
self._reads[ name ] = value
|
||||
|
||||
|
||||
def SetStatusBarDirty( self ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def SetWebCookies( self, name, value ):
|
||||
|
||||
self._cookies[ name ] = value
|
||||
|
||||
|
||||
def TidyUp( self ):
|
||||
|
||||
time.sleep( 2 )
|
||||
|
||||
HydrusPaths.DeletePath( self.db_dir )
|
||||
|
||||
|
||||
def ViewIsShutdown( self ):
|
||||
|
||||
return HG.view_shutdown
|
||||
|
||||
|
||||
def WaitUntilModelFree( self ):
|
||||
|
||||
return
|
||||
|
||||
|
||||
def WaitUntilViewFree( self ):
|
||||
|
||||
return
|
||||
|
||||
|
||||
def Write( self, name, *args, **kwargs ):
|
||||
|
||||
self._writes[ name ].append( ( args, kwargs ) )
|
||||
|
||||
|
||||
def WriteSynchronous( self, name, *args, **kwargs ):
|
||||
|
||||
self._writes[ name ].append( ( args, kwargs ) )
|
||||
|
||||
if name == 'import_file':
|
||||
|
||||
( file_import_job, ) = args
|
||||
|
||||
if file_import_job.GetHash().hex() == 'a593942cb7ea9ffcd8ccf2f0fa23c338e23bfecd9a3e508dfc0bcf07501ead08': # 'blarg' in sha256 hex
|
||||
|
||||
raise Exception( 'File failed to import for some reason!' )
|
||||
|
||||
else:
|
||||
|
||||
return ( CC.STATUS_SUCCESSFUL_AND_NEW, 'test note' )
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
args = sys.argv[1:]
|
||||
|
@ -620,7 +23,10 @@ if __name__ == '__main__':
|
|||
|
||||
only_run = args[0]
|
||||
|
||||
else: only_run = None
|
||||
else:
|
||||
|
||||
only_run = None
|
||||
|
||||
|
||||
try:
|
||||
|
||||
|
@ -628,8 +34,6 @@ if __name__ == '__main__':
|
|||
|
||||
app = wx.App()
|
||||
|
||||
controller = Controller()
|
||||
|
||||
try:
|
||||
|
||||
# we run the tests on the wx thread atm
|
||||
|
@ -637,6 +41,8 @@ if __name__ == '__main__':
|
|||
|
||||
win = wx.Frame( None, title = 'Running tests...' )
|
||||
|
||||
controller = TestController.Controller( win, only_run )
|
||||
|
||||
def do_it():
|
||||
|
||||
controller.Run( win )
|
||||
|
@ -648,8 +54,6 @@ if __name__ == '__main__':
|
|||
|
||||
except:
|
||||
|
||||
import traceback
|
||||
|
||||
HydrusData.DebugPrint( traceback.format_exc() )
|
||||
|
||||
finally:
|
||||
|
@ -667,8 +71,6 @@ if __name__ == '__main__':
|
|||
|
||||
except:
|
||||
|
||||
import traceback
|
||||
|
||||
HydrusData.DebugPrint( traceback.format_exc() )
|
||||
|
||||
finally:
|
||||
|
|
Loading…
Reference in New Issue