Version 343

This commit is contained in:
Hydrus Network Developer 2019-03-13 16:04:21 -05:00
parent 1a7fe45043
commit 67e39d8f88
37 changed files with 2033 additions and 1126 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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 )

View File

@ -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()

View File

@ -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 )
#

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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:

View File

@ -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 ) )

View File

@ -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

View File

@ -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

View File

@ -106,8 +106,6 @@ class ImageRenderer( object ):
def _Initialise( self ):
time.sleep( 0.00001 )
self._numpy_image = ClientImageHandling.GenerateNumpyImage( self._path, self._mime )

View File

@ -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

View File

@ -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 )

View File

@ -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 ),

View File

@ -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:

View File

@ -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 ):

View File

@ -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

View File

@ -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

View File

@ -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 ):

View File

@ -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()

View File

@ -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 )

View File

@ -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

View File

@ -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()

725
include/TestController.py Normal file
View File

@ -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' )

View File

@ -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

View File

@ -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 )

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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
View File

@ -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: