changelog
-
+
version 144
+ - files named 'Thumbs.db' will now be skipped in the import files dialog +
- fixed wildcard searches, which last week's predicate rewrite broke +
- fixed a typo that was showing namespace predicates as exclusive (-series:*) when they were actually inclusive (series:*) and vice versa +
- added wildcard namespace searching for database autocomplete queries +
- fixed database wildcard autocomplete searching when wildcard match is not the first word in a tag +
- fixed database file searching when wildcard match is not the first word in a tag +
- added a comprehensive suite of predicate-unicode conversion tests +
- cleaned and improved some of the downloader code +
- added five second per-gallery-page delay to subscription daemon +
- added three second per-file-delay for regular gallery downloads and subscriptions, just to be polite to those web services +
- added three second per-file-delay to the thread watcher for the same reason +
- added a 'check now' button to the thread watcher +
- fixed a problem that was sometimes causing subscriptions, when paused, to continually restart +
- removed unnamespaced tag parsing from deviant art +
- fixed creator parsing for deviant art, which was formerly cutting off the first character +
- patched an account sync problem in the manage tags dialog +
- in add tags by path dialog, tags are now sorted before being added to the file list +
- in add tags by path dialog, the regex sections now generate tags for every match in the string, not just the first +
- stopped collapsible panels resizing dialogs to minsize on collapse or expand +
- added shortcut for 'open externally', default Ctrl+E +
- moved 8chan to new 8ch.net domain (old domain still works) +
version 143
- when making a READ autocomplete tag query, instances of tags that only have a count in a single namespaced domain will no longer accumulate helper results in the non-namespaced domain i.e. no more 'blah (1)' 'title:blah (1)' dupes diff --git a/include/ClientConstants.py b/include/ClientConstants.py index 029a3155..f11df15e 100755 --- a/include/ClientConstants.py +++ b/include/ClientConstants.py @@ -262,6 +262,7 @@ shortcuts[ wx.ACCEL_CTRL ][ ord( 'M' ) ] = 'set_media_focus' shortcuts[ wx.ACCEL_CTRL ][ ord( 'I' ) ] = 'synchronised_wait_switch' shortcuts[ wx.ACCEL_CTRL ][ ord( 'Z' ) ] = 'undo' shortcuts[ wx.ACCEL_CTRL ][ ord( 'Y' ) ] = 'redo' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'E' ) ] = 'open_externally' shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_UP ] = 'previous' shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_LEFT ] = 'previous' diff --git a/include/ClientDB.py b/include/ClientDB.py index 9a41d8fa..5818d328 100755 --- a/include/ClientDB.py +++ b/include/ClientDB.py @@ -1849,7 +1849,14 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ): - def GetPossibleTagIds(): + def GetPossibleWildcardNamespaceIds( wildcard_namespace ): + + wildcard_namespace = wildcard_namespace.replace( '*', '%' ) + + return [ namespace_id for ( namespace_id, ) in self._c.execute( 'SELECT namespace_id FROM namespaces WHERE namespace LIKE ?;', ( wildcard_namespace, ) ) ] + + + def GetPossibleTagIds( h_c_t ): # the issue is that the tokenizer for fts4 doesn't like weird characters # a search for '[s' actually only does 's' @@ -1857,10 +1864,10 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ): # note that queries with '*' are passed to LIKE, because MATCH only supports appended wildcards 'gun*', and not complex stuff like '*gun*' - if half_complete_tag_can_be_matched: return [ tag_id for ( tag_id, ) in self._c.execute( 'SELECT docid FROM tags_fts4 WHERE tag MATCH ?;', ( '"' + half_complete_tag + '*"', ) ) ] + if half_complete_tag_can_be_matched: return [ tag_id for ( tag_id, ) in self._c.execute( 'SELECT docid FROM tags_fts4 WHERE tag MATCH ?;', ( '"' + h_c_t + '*"', ) ) ] else: - possible_tag_ids_half_complete_tag = half_complete_tag + possible_tag_ids_half_complete_tag = h_c_t if '*' in possible_tag_ids_half_complete_tag: @@ -1868,7 +1875,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ): else: possible_tag_ids_half_complete_tag += '%' - return [ tag_id for ( tag_id, ) in self._c.execute( 'SELECT tag_id FROM tags WHERE tag LIKE ? OR tag LIKE ?;', ( possible_tag_ids_half_complete_tag, ' ' + possible_tag_ids_half_complete_tag ) ) ] + return [ tag_id for ( tag_id, ) in self._c.execute( 'SELECT tag_id FROM tags WHERE tag LIKE ? OR tag LIKE ?;', ( possible_tag_ids_half_complete_tag, '% ' + possible_tag_ids_half_complete_tag ) ) ] @@ -1881,22 +1888,34 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ): if half_complete_tag == '': return CC.AutocompleteMatchesCounted( {} ) else: - result = self._c.execute( 'SELECT namespace_id FROM namespaces WHERE namespace = ?;', ( namespace, ) ).fetchone() - - if result is None: return CC.AutocompleteMatchesCounted( {} ) + if '*' in namespace: + + possible_namespace_ids = GetPossibleWildcardNamespaceIds( namespace ) + + predicates_phrase_1 = 'namespace_id IN ' + HC.SplayListForDB( possible_namespace_ids ) + else: - ( namespace_id, ) = result - possible_tag_ids = GetPossibleTagIds() + result = self._c.execute( 'SELECT namespace_id FROM namespaces WHERE namespace = ?;', ( namespace, ) ).fetchone() - predicates_phrase = 'namespace_id = ' + HC.u( namespace_id ) + ' AND tag_id IN ' + HC.SplayListForDB( possible_tag_ids ) + if result is None: return CC.AutocompleteMatchesCounted( {} ) + else: + + ( namespace_id, ) = result + + predicates_phrase_1 = 'namespace_id = ' + HC.u( namespace_id ) + + possible_tag_ids = GetPossibleTagIds( half_complete_tag ) + + predicates_phrase = predicates_phrase_1 + ' AND tag_id IN ' + HC.SplayListForDB( possible_tag_ids ) + else: - possible_tag_ids = GetPossibleTagIds() + possible_tag_ids = GetPossibleTagIds( half_complete_tag ) predicates_phrase = 'tag_id IN ' + HC.SplayListForDB( possible_tag_ids ) @@ -2632,7 +2651,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ): w = w.replace( '*', '%' ) - return { tag_id for ( tag_id, ) in self._c.execute( 'SELECT tag_id FROM tags WHERE tag LIKE ?;', ( w, ) ) } + return { tag_id for ( tag_id, ) in self._c.execute( 'SELECT tag_id FROM tags WHERE tag LIKE ? or tag LIKE ?;', ( w, '% ' + w ) ) } else: @@ -5326,25 +5345,6 @@ class DB( ServiceDB ): def _UpdateDB( self, version ): - if version == 94: - - # I changed a variable name in account, so old yaml dumps need to be refreshed - - unknown_account = HC.GetUnknownAccount() - - self._c.execute( 'UPDATE accounts SET account = ?;', ( unknown_account, ) ) - - for ( name, info ) in self._c.execute( 'SELECT name, info FROM gui_sessions;' ).fetchall(): - - for ( page_name, c_text, args, kwargs ) in info: - - if 'do_query' in kwargs: del kwargs[ 'do_query' ] - - - self._c.execute( 'UPDATE gui_sessions SET info = ? WHERE name = ?;', ( info, name ) ) - - - if version == 95: self._c.execute( 'COMMIT' ) @@ -5964,6 +5964,15 @@ class DB( ServiceDB ): self._c.execute( 'REPLACE INTO yaml_dumps VALUES ( ?, ?, ? );', ( YAML_DUMP_ID_REMOTE_BOORU, 'sankaku chan', CC.DEFAULT_BOORUS[ 'sankaku chan' ] ) ) + if version == 143: + + ( HC.options, ) = self._c.execute( 'SELECT options FROM options;' ).fetchone() + + HC.options[ 'shortcuts' ][ wx.ACCEL_CTRL ][ ord( 'E' ) ] = 'open_externally' + + self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) ) + + self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) ) HC.is_db_updated = True @@ -7223,7 +7232,7 @@ def DAEMONSynchroniseRepositories(): def DAEMONSynchroniseSubscriptions(): - HC.repos_changed = False + HC.subs_changed = False if not HC.options[ 'pause_subs_sync' ]: @@ -7373,6 +7382,8 @@ def DAEMONSynchroniseSubscriptions(): job_key.SetVariable( 'popup_message_text_1', 'found ' + HC.ConvertIntToPrettyString( len( all_url_args ) ) + ' new files' ) + time.sleep( 5 ) + for downloader in downloaders_to_remove: downloaders.remove( downloader ) @@ -7503,6 +7514,8 @@ def DAEMONSynchroniseSubscriptions(): HC.app.WaitUntilGoodTimeToUseGUIThread() + time.sleep( 3 ) + job_key.DeleteVariable( 'popup_message_gauge_1' ) diff --git a/include/ClientGUICanvas.py b/include/ClientGUICanvas.py index f6304b72..c1629dd2 100755 --- a/include/ClientGUICanvas.py +++ b/include/ClientGUICanvas.py @@ -705,12 +705,15 @@ class Canvas( object ): def _OpenExternally( self ): - hash = self._current_display_media.GetHash() - mime = self._current_display_media.GetMime() - - path = CC.GetFilePath( hash, mime ) - - HC.LaunchFile( path ) + if self._current_display_media is not None: + + hash = self._current_display_media.GetHash() + mime = self._current_display_media.GetMime() + + path = CC.GetFilePath( hash, mime ) + + HC.LaunchFile( path ) + def _PrefetchImages( self ): pass diff --git a/include/ClientGUICommon.py b/include/ClientGUICommon.py index d9be1fe1..33a93639 100755 --- a/include/ClientGUICommon.py +++ b/include/ClientGUICommon.py @@ -3364,18 +3364,20 @@ class CollapsiblePanel( wx.Panel ): parent_of_container = self.GetParent().GetParent() + old_height = self.GetMinHeight() + parent_of_container.Layout() + new_height = self.GetMinHeight() + + height_difference = new_height - old_height + if isinstance( parent_of_container, wx.ScrolledWindow ): # fitinside is like fit, but it does the virtual size! parent_of_container.FitInside() - tlp = self.GetTopLevelParent() - - if issubclass( type( tlp ), wx.Dialog ): tlp.Fit() - def ExpandCollapse( self ): self._Change() diff --git a/include/ClientGUIDialogs.py b/include/ClientGUIDialogs.py index 95d0f6ca..f6a1f4fd 100755 --- a/include/ClientGUIDialogs.py +++ b/include/ClientGUIDialogs.py @@ -886,7 +886,7 @@ class DialogInputCustomFilterAction( Dialog ): self._none_panel = ClientGUICommon.StaticBox( self, 'non-service actions' ) - self._none_actions = wx.Choice( self._none_panel, choices = [ 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'delete', 'fullscreen_switch', 'frame_back', 'frame_next', 'previous', 'next', 'first', 'last' ] ) + self._none_actions = wx.Choice( self._none_panel, choices = [ 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'delete', 'fullscreen_switch', 'frame_back', 'frame_next', 'previous', 'next', 'first', 'last', 'open_externally' ] ) self._ok_none = wx.Button( self._none_panel, label = 'ok' ) self._ok_none.Bind( wx.EVT_BUTTON, self.EventOKNone ) @@ -2683,6 +2683,8 @@ class DialogInputLocalFiles( Dialog ): for ( i, path ) in enumerate( file_paths ): + if path.endswith( os.path.sep + 'Thumbs.db' ) or path.endswith( os.path.sep + 'thumbs.db' ): continue + if i % 500 == 0: gc.collect() wx.CallAfter( self.SetGaugeInfo, num_file_paths, i, u'Done ' + HC.u( i ) + '/' + HC.u( num_file_paths ) ) @@ -3444,7 +3446,7 @@ class DialogInputShortcut( Dialog ): self._shortcut = ClientGUICommon.Shortcut( self, modifier, key ) - self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', 'ratings_filter', 'frame_back', 'frame_next', 'manage_ratings', 'manage_tags', 'new_page', 'refresh', 'set_search_focus', 'show_hide_splitters', 'synchronised_wait_switch', 'previous', 'next', 'first', 'last', 'undo', 'redo' ] ) + self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', 'ratings_filter', 'frame_back', 'frame_next', 'manage_ratings', 'manage_tags', 'new_page', 'refresh', 'set_search_focus', 'show_hide_splitters', 'synchronised_wait_switch', 'previous', 'next', 'first', 'last', 'undo', 'redo', 'open_externally' ] ) self._ok = wx.Button( self, id= wx.ID_OK, label = 'Ok' ) self._ok.SetForegroundColour( ( 0, 128, 0 ) ) @@ -4452,14 +4454,9 @@ class DialogPathsToTagsRegex( Dialog ): try: - m = re.search( regex, path ) + result = re.findall( regex, path ) - if m is not None: - - match = m.group() - - if len( match ) > 0: tags.append( match ) - + for match in result: tags.append( match ) except: pass @@ -4468,14 +4465,9 @@ class DialogPathsToTagsRegex( Dialog ): try: - m = re.search( regex, path ) + result = re.findall( regex, path ) - if m is not None: - - match = m.group() - - if len( match ) > 0: tags.append( namespace + ':' + match ) - + for match in result: tags.append( namespace + ':' + match ) except: pass @@ -4491,6 +4483,10 @@ class DialogPathsToTagsRegex( Dialog ): tags = HC.CleanTags( tags ) + tags = list( tags ) + + tags.sort() + return tags @@ -5272,7 +5268,7 @@ class DialogSetupCustomFilterActions( Dialog ): for ( key, action ) in key_dict.items(): - if action in ( 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'fullscreen_switch', 'frame_back', 'frame_next', 'previous', 'next', 'first', 'last', 'pan_up', 'pan_down', 'pan_left', 'pan_right' ): + if action in ( 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'fullscreen_switch', 'frame_back', 'frame_next', 'previous', 'next', 'first', 'last', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'open_externally' ): service_key = None diff --git a/include/ClientGUIDialogsManage.py b/include/ClientGUIDialogsManage.py index 16682970..821271b6 100644 --- a/include/ClientGUIDialogsManage.py +++ b/include/ClientGUIDialogsManage.py @@ -7703,7 +7703,6 @@ class DialogManageTags( ClientGUIDialogs.Dialog ): if self._i_am_local_tag_service: self._modify_mappers.Hide() else: - if not ( self._account.HasPermission( HC.POST_DATA ) or self._account.IsUnknownAccount() ): self._add_tag_box.Hide() if not self._account.HasPermission( HC.MANAGE_USERS ): self._modify_mappers.Hide() @@ -7737,7 +7736,8 @@ class DialogManageTags( ClientGUIDialogs.Dialog ): service = HC.app.GetManager( 'services' ).GetService( tag_service_key ) - self._account = service.GetInfo( 'account' ) + try: self._account = service.GetInfo( 'account' ) + except: self._account = HC.GetUnknownAccount() hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media ) ) ) @@ -7899,7 +7899,7 @@ class DialogManageTags( ClientGUIDialogs.Dialog ): def SetTagBoxFocus( self ): - if self._i_am_local_tag_service or self._account.HasPermission( HC.POST_DATA ) or self._account.IsUnknownAccount(): self._add_tag_box.SetFocus() + self._add_tag_box.SetFocus() diff --git a/include/ClientGUIManagement.py b/include/ClientGUIManagement.py index fdc852c7..9f30504e 100755 --- a/include/ClientGUIManagement.py +++ b/include/ClientGUIManagement.py @@ -1222,8 +1222,8 @@ class ManagementPanelImport( ManagementPanel ): import_controller_job_key = self._import_controller.GetJobKey( 'controller' ) import_job_key = self._import_controller.GetJobKey( 'import' ) - import_queue_position_job_key = self._import_controller.GetJobKey( 'import_queue_position' ) import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) # info @@ -1247,13 +1247,13 @@ class ManagementPanelImport( ManagementPanel ): if import_status != self._import_current_info.GetLabel(): self._import_current_info.SetLabel( import_status ) - import_queue_status = import_queue_position_job_key.GetVariable( 'status' ) + import_queue_status = import_queue_job_key.GetVariable( 'status' ) if import_queue_status != self._import_queue_info.GetLabel(): self._import_queue_info.SetLabel( import_queue_status ) # buttons - if import_queue_position_job_key.IsPaused(): + if import_queue_job_key.IsPaused(): if self._import_pause_button.GetLabel() != 'resume': @@ -1270,7 +1270,7 @@ class ManagementPanelImport( ManagementPanel ): - if import_queue_position_job_key.IsWorking() and not import_queue_position_job_key.IsCancelled(): + if import_queue_job_key.IsWorking() and not import_queue_job_key.IsCancelled(): self._import_pause_button.Enable() self._import_cancel_button.Enable() @@ -1294,11 +1294,11 @@ class ManagementPanelImport( ManagementPanel ): self._import_gauge.SetValue( value ) - queue = import_queue_job_key.GetVariable( 'queue' ) + queue = import_queue_builder_job_key.GetVariable( 'queue' ) if len( queue ) == 0: - if import_queue_job_key.IsWorking(): self._import_queue_gauge.Pulse() + if import_queue_builder_job_key.IsWorking(): self._import_queue_gauge.Pulse() else: self._import_queue_gauge.SetRange( 1 ) @@ -1307,7 +1307,7 @@ class ManagementPanelImport( ManagementPanel ): else: - queue_position = import_queue_position_job_key.GetVariable( 'queue_position' ) + queue_position = import_queue_job_key.GetVariable( 'queue_position' ) self._import_queue_gauge.SetRange( len( queue ) ) self._import_queue_gauge.SetValue( queue_position ) @@ -1316,20 +1316,20 @@ class ManagementPanelImport( ManagementPanel ): def EventCancelImportQueue( self, event ): - import_queue_position_job_key = self._import_controller.GetJobKey( 'import_queue_position' ) import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) - import_queue_position_job_key.Cancel() import_queue_job_key.Cancel() + import_queue_builder_job_key.Cancel() self._UpdateGUI() def EventPauseImportQueue( self, event ): - import_queue_position_job_key = self._import_controller.GetJobKey( 'import_queue_position' ) + import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) - import_queue_position_job_key.PauseResume() + import_queue_job_key.PauseResume() self._UpdateGUI() @@ -1340,9 +1340,9 @@ class ManagementPanelImport( ManagementPanel ): def TestAbleToClose( self ): - import_queue_position_job_key = self._import_controller.GetJobKey( 'import_queue_position' ) + import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) - if import_queue_position_job_key.IsWorking() and not import_queue_position_job_key.IsPaused(): + if import_queue_job_key.IsWorking() and not import_queue_job_key.IsPaused(): with ClientGUIDialogs.DialogYesNo( self, 'This page is still importing. Are you sure you want to close it?' ) as dlg: @@ -1364,11 +1364,11 @@ class ManagementPanelImports( ManagementPanelImport ): self._building_import_queue_info = wx.StaticText( self._building_import_queue_panel ) self._building_import_queue_pause_button = wx.Button( self._building_import_queue_panel, label = 'pause' ) - self._building_import_queue_pause_button.Bind( wx.EVT_BUTTON, self.EventPauseBuildImportQueue ) + self._building_import_queue_pause_button.Bind( wx.EVT_BUTTON, self.EventPauseImportQueueBuilder ) self._building_import_queue_pause_button.Disable() self._building_import_queue_cancel_button = wx.Button( self._building_import_queue_panel, label = 'that\'s enough' ) - self._building_import_queue_cancel_button.Bind( wx.EVT_BUTTON, self.EventCancelBuildImportQueue ) + self._building_import_queue_cancel_button.Bind( wx.EVT_BUTTON, self.EventCancelImportQueueBuilder ) self._building_import_queue_cancel_button.SetForegroundColour( ( 128, 0, 0 ) ) self._building_import_queue_cancel_button.Disable() @@ -1424,20 +1424,20 @@ class ManagementPanelImports( ManagementPanelImport ): ManagementPanelImport._UpdateGUI( self ) import_job_key = self._import_controller.GetJobKey( 'import' ) - import_queue_position_job_key = self._import_controller.GetJobKey( 'import_queue_position' ) import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) # info - extend_import_queue_status = import_queue_job_key.GetVariable( 'status' ) + import_queue_builder_status = import_queue_builder_job_key.GetVariable( 'status' ) - if extend_import_queue_status != self._building_import_queue_info.GetLabel(): self._building_import_queue_info.SetLabel( extend_import_queue_status ) + if import_queue_builder_status != self._building_import_queue_info.GetLabel(): self._building_import_queue_info.SetLabel( import_queue_builder_status ) # buttons # - if import_queue_job_key.IsPaused(): + if import_queue_builder_job_key.IsPaused(): if self._building_import_queue_pause_button.GetLabel() != 'resume': @@ -1454,7 +1454,7 @@ class ManagementPanelImports( ManagementPanelImport ): - if import_queue_job_key.IsWorking() and not import_queue_job_key.IsCancelled(): + if import_queue_builder_job_key.IsWorking() and not import_queue_job_key.IsCancelled(): self._building_import_queue_pause_button.Enable() self._building_import_queue_cancel_button.Enable() @@ -1480,19 +1480,19 @@ class ManagementPanelImports( ManagementPanelImport ): # pending import queues - import_queues = self._import_controller.GetPendingImportQueues() + pending_import_queue_jobs = self._import_controller.GetPendingImportQueueJobs() - if import_queues != self._pending_import_queues_listbox.GetItems(): + if pending_import_queue_jobs != self._pending_import_queues_listbox.GetItems(): - self._pending_import_queues_listbox.SetItems( import_queues ) + self._pending_import_queues_listbox.SetItems( pending_import_queue_jobs ) - def EventCancelBuildImportQueue( self, event ): + def EventCancelImportQueueBuilder( self, event ): - import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) - import_queue_job_key.Cancel() + import_queue_builder_job_key.Cancel() self._UpdateGUI() @@ -1505,7 +1505,7 @@ class ManagementPanelImports( ManagementPanelImport ): if s != '': - self._import_controller.PendImportQueue( s ) + self._import_controller.PendImportQueueJob( s ) self._UpdateGUI() @@ -1515,11 +1515,11 @@ class ManagementPanelImports( ManagementPanelImport ): else: event.Skip() - def EventPauseBuildImportQueue( self, event ): + def EventPauseImportQueueBuilder( self, event ): - import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) - import_queue_job_key.PauseResume() + import_queue_builder_job_key.PauseResume() self._UpdateGUI() @@ -1534,7 +1534,7 @@ class ManagementPanelImports( ManagementPanelImport ): s = self._pending_import_queues_listbox.GetString( selection ) - self._import_controller.MovePendingImportQueueUp( s ) + self._import_controller.MovePendingImportQueueJobUp( s ) self._UpdateGUI() @@ -1551,7 +1551,7 @@ class ManagementPanelImports( ManagementPanelImport ): s = self._pending_import_queues_listbox.GetString( selection ) - self._import_controller.RemovePendingImportQueue( s ) + self._import_controller.RemovePendingImportQueueJob( s ) self._UpdateGUI() @@ -1567,7 +1567,7 @@ class ManagementPanelImports( ManagementPanelImport ): s = self._pending_import_queues_listbox.GetString( selection ) - self._import_controller.MovePendingImportQueueDown( s ) + self._import_controller.MovePendingImportQueueJobDown( s ) self._UpdateGUI() @@ -1679,7 +1679,10 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): self._thread_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) self._thread_pause_button = wx.Button( self._thread_panel, label = 'pause' ) - self._thread_pause_button.Bind( wx.EVT_BUTTON, self.EventPauseBuildImportQueue ) + self._thread_pause_button.Bind( wx.EVT_BUTTON, self.EventPauseImportQueueBuilder ) + + self._thread_manual_refresh_button = wx.Button( self._thread_panel, label = 'check now' ) + self._thread_manual_refresh_button.Bind( wx.EVT_BUTTON, self.EventManualRefresh ) hbox = wx.BoxSizer( wx.HORIZONTAL ) @@ -1689,10 +1692,15 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): hbox.AddF( self._thread_check_period, FLAGS_MIXED ) hbox.AddF( wx.StaticText( self._thread_panel, label = ' seconds' ), FLAGS_MIXED ) + button_box = wx.BoxSizer( wx.HORIZONTAL ) + + button_box.AddF( self._thread_pause_button, FLAGS_EXPAND_BOTH_WAYS ) + button_box.AddF( self._thread_manual_refresh_button, FLAGS_EXPAND_BOTH_WAYS ) + self._thread_panel.AddF( self._thread_info, FLAGS_EXPAND_PERPENDICULAR ) self._thread_panel.AddF( self._thread_input, FLAGS_EXPAND_PERPENDICULAR ) self._thread_panel.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR ) - self._thread_panel.AddF( self._thread_pause_button, FLAGS_EXPAND_PERPENDICULAR ) + self._thread_panel.AddF( button_box, FLAGS_EXPAND_SIZER_PERPENDICULAR ) vbox.AddF( self._thread_panel, FLAGS_EXPAND_SIZER_PERPENDICULAR ) @@ -1705,13 +1713,14 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): def _SetThreadVariables( self ): - import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) thread_time = self._thread_check_period.GetValue() thread_times_to_check = self._thread_times_to_check.GetValue() - import_queue_job_key.SetVariable( 'thread_time', thread_time ) - import_queue_job_key.SetVariable( 'thread_times_to_check', thread_times_to_check ) + import_queue_builder_job_key.SetVariable( 'manual_refresh', False ) + import_queue_builder_job_key.SetVariable( 'thread_time', thread_time ) + import_queue_builder_job_key.SetVariable( 'thread_times_to_check', thread_times_to_check ) def _UpdateGUI( self ): @@ -1719,21 +1728,21 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): ManagementPanelImport._UpdateGUI( self ) import_job_key = self._import_controller.GetJobKey( 'import' ) - import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) # thread_info - status = import_queue_job_key.GetVariable( 'status' ) + status = import_queue_builder_job_key.GetVariable( 'status' ) if status != self._thread_info.GetLabel(): self._thread_info.SetLabel( status ) # button - if import_queue_job_key.IsWorking(): + if import_queue_builder_job_key.IsWorking(): self._thread_pause_button.Enable() - if import_queue_job_key.IsPaused(): + if import_queue_builder_job_key.IsPaused(): self._thread_pause_button.SetLabel( 'resume' ) self._thread_pause_button.SetForegroundColour( ( 0, 128, 0 ) ) @@ -1750,12 +1759,21 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): try: - thread_times_to_check = import_queue_job_key.GetVariable( 'thread_times_to_check' ) + thread_times_to_check = import_queue_builder_job_key.GetVariable( 'thread_times_to_check' ) self._thread_times_to_check.SetValue( thread_times_to_check ) except: self._SetThreadVariables() + try: + + manual_refresh = import_queue_builder_job_key.GetVariable( 'manual_refresh' ) + + if not import_queue_builder_job_key.IsWorking() or manual_refresh: self._thread_manual_refresh_button.Disable() + else: self._thread_manual_refresh_button.Enable() + + except: self._SetThreadVariables() + def EventKeyDown( self, event ): @@ -1782,7 +1800,7 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): except: raise Exception ( 'Could not understand that url!' ) is_4chan = '4chan.org' in host - is_8chan = '8chan.co' in host + is_8chan = '8chan.co' in host or '8ch.net' in host if not ( is_4chan or is_8chan ): raise Exception( 'This only works for 4chan and 8chan right now!' ) @@ -1813,16 +1831,16 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): ( board, rest_of_request ) = request[1:].split( '/res/', 1 ) json_url = url[:-4] + 'json' - image_base = 'http://8chan.co/' + board + '/src/' + image_base = 'http://8ch.net/' + board + '/src/' except: raise Exception( 'Could not understand the board or thread id!' ) except Exception as e: - import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) - import_queue_job_key.SetVariable( 'status', HC.u( e ) ) + import_queue_builder_job_key.SetVariable( 'status', HC.u( e ) ) HC.ShowException( e ) @@ -1833,16 +1851,25 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): self._SetThreadVariables() - self._import_controller.PendImportQueue( ( json_url, image_base ) ) + self._import_controller.PendImportQueueJob( ( json_url, image_base ) ) else: event.Skip() - def EventPauseBuildImportQueue( self, event ): + def EventManualRefresh( self, event ): - import_queue_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) - import_queue_job_key.PauseResume() + import_queue_builder_job_key.SetVariable( 'manual_refresh', True ) + + self._thread_manual_refresh_button.Disable() + + + def EventPauseImportQueueBuilder( self, event ): + + import_queue_builder_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) + + import_queue_builder_job_key.PauseResume() self._UpdateGUI() @@ -1858,9 +1885,9 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ): def TestAbleToClose( self ): - import_queue_position_job_key = self._import_controller.GetJobKey( 'import_queue' ) + import_queue_builder_position_job_key = self._import_controller.GetJobKey( 'import_queue_builder' ) - if self._thread_times_to_check.GetValue() > 0 and import_queue_position_job_key.IsWorking() and not import_queue_position_job_key.IsPaused(): + if self._thread_times_to_check.GetValue() > 0 and import_queue_builder_position_job_key.IsWorking() and not import_queue_builder_position_job_key.IsPaused(): with ClientGUIDialogs.DialogYesNo( self, 'This page is still importing. Are you sure you want to close it?' ) as dlg: diff --git a/include/ClientGUIMedia.py b/include/ClientGUIMedia.py index b11868eb..ad6a901b 100755 --- a/include/ClientGUIMedia.py +++ b/include/ClientGUIMedia.py @@ -469,12 +469,15 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ): def _OpenExternally( self ): - hash = self._focussed_media.GetHash() - mime = self._focussed_media.GetMime() - - path = CC.GetFilePath( hash, mime ) - - HC.LaunchFile( path ) + if self._focussed_media is not None: + + hash = self._focussed_media.GetHash() + mime = self._focussed_media.GetMime() + + path = CC.GetFilePath( hash, mime ) + + HC.LaunchFile( path ) + def _PetitionFiles( self, file_service_key ): diff --git a/include/ClientGUIPages.py b/include/ClientGUIPages.py index 15c9b69f..a2980b9b 100755 --- a/include/ClientGUIPages.py +++ b/include/ClientGUIPages.py @@ -260,11 +260,11 @@ class PageImport( PageWithMedia ): return factory - def _GenerateImportQueueGeneratorFactory( self ): + def _GenerateImportQueueBuilderFactory( self ): def factory( job_key, item ): - return HydrusDownloading.ImportQueueGenerator( job_key, item ) + return HydrusDownloading.ImportQueueBuilder( job_key, item ) return factory @@ -275,9 +275,9 @@ class PageImport( PageWithMedia ): def _InitControllers( self ): import_args_generator_factory = self._GenerateImportArgsGeneratorFactory() - import_queue_generator_factory = self._GenerateImportQueueGeneratorFactory() + import_queue_builder_factory = self._GenerateImportQueueBuilderFactory() - self._import_controller = HydrusDownloading.ImportController( import_args_generator_factory, import_queue_generator_factory, page_key = self._page_key ) + self._import_controller = HydrusDownloading.ImportController( import_args_generator_factory, import_queue_builder_factory, page_key = self._page_key ) self._import_controller.StartDaemon() @@ -338,13 +338,13 @@ class PageImportGallery( PageImport ): return factory - def _GenerateImportQueueGeneratorFactory( self ): + def _GenerateImportQueueBuilderFactory( self ): def factory( job_key, item ): downloaders_factory = self._GetDownloadersFactory() - return HydrusDownloading.ImportQueueGeneratorGallery( job_key, item, downloaders_factory ) + return HydrusDownloading.ImportQueueBuilderGallery( job_key, item, downloaders_factory ) return factory @@ -471,7 +471,7 @@ class PageImportGallery( PageImport ): if self._gallery_type == 'artist': name = 'deviant art' - namespaces = [ 'creator', 'title', '' ] + namespaces = [ 'creator', 'title' ] initial_search_value = 'artist username' @@ -538,7 +538,7 @@ class PageImportHDD( PageImport ): PageImport.__init__( self, parent, initial_hashes = initial_hashes, starting_from_session = starting_from_session ) - self._import_controller.PendImportQueue( self._paths_info ) + self._import_controller.PendImportQueueJob( self._paths_info ) def _GenerateImportArgsGeneratorFactory( self ): @@ -582,11 +582,11 @@ class PageImportThreadWatcher( PageImport ): return factory - def _GenerateImportQueueGeneratorFactory( self ): + def _GenerateImportQueueBuilderFactory( self ): def factory( job_key, item ): - return HydrusDownloading.ImportQueueGeneratorThread( job_key, item ) + return HydrusDownloading.ImportQueueBuilderThread( job_key, item ) return factory @@ -608,11 +608,11 @@ class PageImportURL( PageImport ): return factory - def _GenerateImportQueueGeneratorFactory( self ): + def _GenerateImportQueueBuilderFactory( self ): def factory( job_key, item ): - return HydrusDownloading.ImportQueueGeneratorURLs( job_key, item ) + return HydrusDownloading.ImportQueueBuilderURLs( job_key, item ) return factory diff --git a/include/HydrusConstants.py b/include/HydrusConstants.py index 44005279..a616e340 100755 --- a/include/HydrusConstants.py +++ b/include/HydrusConstants.py @@ -66,7 +66,7 @@ options = {} # Misc NETWORK_VERSION = 15 -SOFTWARE_VERSION = 143 +SOFTWARE_VERSION = 144 UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 ) @@ -2228,7 +2228,7 @@ class Predicate( HydrusYAMLBase ): ( operator, value ) = info - base += u' ' + operator + u' ' + u( value ) + base += u' ' + operator + u' ' + ConvertIntToPrettyString( value ) elif system_predicate_type == SYSTEM_PREDICATE_TYPE_RATIO: @@ -2261,7 +2261,7 @@ class Predicate( HydrusYAMLBase ): value = info - base += u' is ' + u( value ) + base += u' is ' + ConvertIntToPrettyString( value ) elif system_predicate_type == SYSTEM_PREDICATE_TYPE_AGE: @@ -2312,7 +2312,7 @@ class Predicate( HydrusYAMLBase ): elif system_predicate_type == SYSTEM_PREDICATE_TYPE_SIMILAR_TO: - base = u'system:similar to ' + base = u'system:similar to' if info is not None: @@ -2323,16 +2323,14 @@ class Predicate( HydrusYAMLBase ): elif system_predicate_type == SYSTEM_PREDICATE_TYPE_FILE_SERVICE: - base = u'system:file service' + base = u'system:' if info is not None: ( operator, current_or_pending, service_key ) = info - base += u':' - - if operator == True: base += u' is' - else: base += u' is not' + if operator == True: base += u'is' + else: base += u'is not' if current_or_pending == PENDING: base += u' pending to ' else: base += u' currently in ' @@ -2376,14 +2374,14 @@ class Predicate( HydrusYAMLBase ): namespace = self._value - if not self._inclusive == '-': base = u'-' + if not self._inclusive: base = u'-' else: base = u'' base += namespace + u':*' elif self._predicate_type == PREDICATE_TYPE_WILDCARD: - ( operator, wildcard ) = self._value + wildcard = self._value if not self._inclusive: base = u'-' else: base = u'' diff --git a/include/HydrusDownloading.py b/include/HydrusDownloading.py index 1fefe969..977a66bb 100644 --- a/include/HydrusDownloading.py +++ b/include/HydrusDownloading.py @@ -359,6 +359,7 @@ class DownloaderDeviantArt( Downloader ): def __init__( self, artist ): self._gallery_url = 'http://' + artist + '.deviantart.com/gallery/?catpath=/&offset=' + self._artist = artist Downloader.__init__( self ) @@ -383,38 +384,18 @@ class DownloaderDeviantArt( Downloader ): page_url = link[ 'href' ] # something in the form of blah.da.com/art/blah-123456 - raw_title = link[ 'title' ] # sweet dolls by ~AngeniaC, Feb 29, 2012 in Artisan Crafts > Miniatures > Jewelry + raw_title = link[ 'title' ] # sweet dolls by AngeniaC, date, blah blah blah - raw_title_reversed = raw_title[::-1] # yrleweJ ;tg& serutainiM ;tg& stfarC nasitrA ni 2102 ,92 beF ,CainegnA~ yb sllod teews + raw_title_reversed = raw_title[::-1] # trAtnaiveD no CainegnA yb sllod teews - ( creator_and_date_and_tags_reversed, title_reversed ) = raw_title_reversed.split( ' yb ', 1 ) - - creator_and_date_and_tags = creator_and_date_and_tags_reversed[::-1] # ~AngeniaC, Feb 29, 2012 in Artisan Crafts > Miniatures > Jewelry - - ( creator_with_username_char, date_and_tags ) = creator_and_date_and_tags.split( ',', 1 ) - - creator = creator_with_username_char[1:] # AngeniaC + ( creator_and_gumpf_reversed, title_reversed ) = raw_title_reversed.split( ' yb ', 1 ) title = title_reversed[::-1] # sweet dolls - try: - - ( date_gumpf, raw_category_tags ) = date_and_tags.split( ' in ', 1 ) - - category_tags = raw_category_tags.split( ' > ' ) - - except Exception as e: - - HC.ShowException( e ) - - category_tags = [] - - tags = [] tags.append( 'title:' + title ) - tags.append( 'creator:' + creator ) - tags.extend( category_tags ) + tags.append( 'creator:' + self._artist ) results.append( ( page_url, tags ) ) @@ -1153,6 +1134,8 @@ class ImportArgsGeneratorGallery( ImportArgsGenerator ): service_keys_to_tags = ConvertTagsToServiceKeysToTags( tags, self._advanced_tag_options ) + time.sleep( 3 ) + return ( url, temp_path, service_keys_to_tags, url ) @@ -1186,6 +1169,8 @@ class ImportArgsGeneratorGallery( ImportArgsGenerator ): HC.app.Write( 'content_updates', service_keys_to_content_updates ) + time.sleep( 3 ) + return ( status, media_result ) @@ -1280,6 +1265,8 @@ class ImportArgsGeneratorThread( ImportArgsGenerator ): service_keys_to_tags = ConvertTagsToServiceKeysToTags( tags, self._advanced_tag_options ) + time.sleep( 3 ) + return ( image_url, temp_path, service_keys_to_tags, image_url ) @@ -1297,6 +1284,21 @@ class ImportArgsGeneratorThread( ImportArgsGenerator ): ( media_result, ) = HC.app.ReadDaemon( 'media_results', HC.LOCAL_FILE_SERVICE_KEY, ( hash, ) ) + do_tags = len( self._advanced_tag_options ) > 0 + + if do_tags: + + tags = [ 'filename:' + filename ] + + service_keys_to_tags = ConvertTagsToServiceKeysToTags( tags, self._advanced_tag_options ) + + service_keys_to_content_updates = ConvertServiceKeysToTagsToServiceKeysToContentUpdates( hash, service_keys_to_tags ) + + HC.app.Write( 'content_updates', service_keys_to_content_updates ) + + time.sleep( 3 ) + + return ( status, media_result ) else: return ( status, None ) @@ -1344,17 +1346,17 @@ class ImportArgsGeneratorURLs( ImportArgsGenerator ): class ImportController( object ): - def __init__( self, import_args_generator_factory, import_queue_generator_factory, page_key = None ): + def __init__( self, import_args_generator_factory, import_queue_builder_factory, page_key = None ): self._controller_job_key = self._GetNewJobKey( 'controller' ) self._import_args_generator_factory = import_args_generator_factory - self._import_queue_generator_factory = import_queue_generator_factory + self._import_queue_builder_factory = import_queue_builder_factory self._page_key = page_key self._import_job_key = self._GetNewJobKey( 'import' ) - self._import_queue_position_job_key = self._GetNewJobKey( 'import_queue_position' ) self._import_queue_job_key = self._GetNewJobKey( 'import_queue' ) + self._import_queue_builder_job_key = self._GetNewJobKey( 'import_queue_builder' ) self._pending_import_queue_jobs = [] self._lock = threading.Lock() @@ -1381,11 +1383,11 @@ class ImportController( object ): job_key.SetVariable( 'range', 1 ) job_key.SetVariable( 'value', 0 ) - elif job_type == 'import_queue_position': + elif job_type == 'import_queue': job_key.SetVariable( 'queue_position', 0 ) - elif job_type == 'import_queue': + elif job_type == 'import_queue_builder': job_key.SetVariable( 'queue', [] ) @@ -1402,22 +1404,22 @@ class ImportController( object ): if job_type == 'controller': return self._controller_job_key elif job_type == 'import': return self._import_job_key - elif job_type == 'import_queue_position': return self._import_queue_position_job_key elif job_type == 'import_queue': return self._import_queue_job_key + elif job_type == 'import_queue_builder': return self._import_queue_builder_job_key - def GetPendingImportQueues( self ): + def GetPendingImportQueueJobs( self ): with self._lock: return self._pending_import_queue_jobs - def PendImportQueue( self, job ): + def PendImportQueueJob( self, job ): with self._lock: self._pending_import_queue_jobs.append( job ) - def RemovePendingImportQueue( self, job ): + def RemovePendingImportQueueJob( self, job ): with self._lock: @@ -1425,7 +1427,7 @@ class ImportController( object ): - def MovePendingImportQueueUp( self, job ): + def MovePendingImportQueueJobUp( self, job ): with self._lock: @@ -1443,7 +1445,7 @@ class ImportController( object ): - def MovePendingImportQueueDown( self, job ): + def MovePendingImportQueueJobDown( self, job ): with self._lock: @@ -1472,8 +1474,8 @@ class ImportController( object ): time.sleep( 0.1 ) self._import_job_key.Pause() - self._import_queue_position_job_key.Pause() self._import_queue_job_key.Pause() + self._import_queue_builder_job_key.Pause() if HC.shutdown or self._controller_job_key.IsDone(): break @@ -1482,8 +1484,8 @@ class ImportController( object ): with self._lock: - queue_position = self._import_queue_position_job_key.GetVariable( 'queue_position' ) - queue = self._import_queue_job_key.GetVariable( 'queue' ) + queue_position = self._import_queue_job_key.GetVariable( 'queue_position' ) + queue = self._import_queue_builder_job_key.GetVariable( 'queue' ) if self._import_job_key.IsDone(): @@ -1499,23 +1501,23 @@ class ImportController( object ): queue_position += 1 - self._import_queue_position_job_key.SetVariable( 'queue_position', queue_position ) + self._import_queue_job_key.SetVariable( 'queue_position', queue_position ) position_string = HC.u( queue_position + 1 ) + '/' + HC.u( len( queue ) ) - if self._import_queue_position_job_key.IsPaused(): self._import_queue_position_job_key.SetVariable( 'status', 'paused at ' + position_string ) - elif self._import_queue_position_job_key.IsWorking(): + if self._import_queue_job_key.IsPaused(): self._import_queue_job_key.SetVariable( 'status', 'paused at ' + position_string ) + elif self._import_queue_job_key.IsWorking(): if self._import_job_key.IsWorking(): - self._import_queue_position_job_key.SetVariable( 'status', 'processing ' + position_string ) + self._import_queue_job_key.SetVariable( 'status', 'processing ' + position_string ) else: if queue_position < len( queue ): - self._import_queue_position_job_key.SetVariable( 'status', 'preparing ' + position_string ) + self._import_queue_job_key.SetVariable( 'status', 'preparing ' + position_string ) self._import_job_key.Begin() @@ -1527,38 +1529,38 @@ class ImportController( object ): else: - if self._import_queue_job_key.IsWorking(): self._import_queue_position_job_key.SetVariable( 'status', 'waiting for more items' ) - else: self._import_queue_position_job_key.Finish() + if self._import_queue_builder_job_key.IsWorking(): self._import_queue_job_key.SetVariable( 'status', 'waiting for more items' ) + else: self._import_queue_job_key.Finish() else: - if self._import_queue_position_job_key.IsDone(): + if self._import_queue_job_key.IsDone(): - if self._import_queue_position_job_key.IsCancelled(): status = 'cancelled at ' + position_string + if self._import_queue_job_key.IsCancelled(): status = 'cancelled at ' + position_string else: status = 'done' - self._import_queue_position_job_key = self._GetNewJobKey( 'import_queue_position' ) - self._import_queue_job_key = self._GetNewJobKey( 'import_queue' ) + self._import_queue_builder_job_key = self._GetNewJobKey( 'import_queue_builder' ) + else: status = '' - self._import_queue_position_job_key.SetVariable( 'status', status ) + self._import_queue_job_key.SetVariable( 'status', status ) if len( self._pending_import_queue_jobs ) > 0: - self._import_queue_position_job_key.Begin() - self._import_queue_job_key.Begin() + self._import_queue_builder_job_key.Begin() + item = self._pending_import_queue_jobs.pop( 0 ) - queue_generator = self._import_queue_generator_factory( self._import_queue_job_key, item ) + queue_builder = self._import_queue_builder_factory( self._import_queue_builder_job_key, item ) # make it a daemon, not a thread job, as it has a loop! - threading.Thread( target = queue_generator ).start() + threading.Thread( target = queue_builder ).start() @@ -1570,14 +1572,14 @@ class ImportController( object ): finally: self._import_job_key.Cancel() - self._import_queue_position_job_key.Cancel() self._import_queue_job_key.Cancel() + self._import_queue_builder_job_key.Cancel() def StartDaemon( self ): threading.Thread( target = self.MainLoop ).start() -class ImportQueueGenerator( object ): +class ImportQueueBuilder( object ): def __init__( self, job_key, item ): @@ -1594,11 +1596,11 @@ class ImportQueueGenerator( object ): self._job_key.Finish() -class ImportQueueGeneratorGallery( ImportQueueGenerator ): +class ImportQueueBuilderGallery( ImportQueueBuilder ): def __init__( self, job_key, item, downloaders_factory ): - ImportQueueGenerator.__init__( self, job_key, item ) + ImportQueueBuilder.__init__( self, job_key, item ) self._downloaders_factory = downloaders_factory @@ -1686,7 +1688,7 @@ class ImportQueueGeneratorGallery( ImportQueueGenerator ): finally: self._job_key.Finish() -class ImportQueueGeneratorURLs( ImportQueueGenerator ): +class ImportQueueBuilderURLs( ImportQueueBuilder ): def __call__( self ): @@ -1719,7 +1721,7 @@ class ImportQueueGeneratorURLs( ImportQueueGenerator ): finally: self._job_key.Finish() -class ImportQueueGeneratorThread( ImportQueueGenerator ): +class ImportQueueBuilderThread( ImportQueueBuilder ): def __call__( self ): @@ -1731,6 +1733,7 @@ class ImportQueueGeneratorThread( ImportQueueGenerator ): image_infos_already_added = set() first_run = True + manual_refresh = False while True: @@ -1767,7 +1770,11 @@ class ImportQueueGeneratorThread( ImportQueueGenerator ): next_thread_check = last_thread_check + thread_time - if next_thread_check < HC.GetNow(): + manual_refresh = self._job_key.GetVariable( 'manual_refresh' ) + + not_too_soon_for_manual_refresh = HC.GetNow() - last_thread_check > 10 + + if ( manual_refresh and not_too_soon_for_manual_refresh ) or next_thread_check < HC.GetNow(): self._job_key.SetVariable( 'status', 'checking thread' ) @@ -1812,6 +1819,7 @@ class ImportQueueGeneratorThread( ImportQueueGenerator ): last_thread_check = HC.GetNow() if first_run: first_run = False + elif manual_refresh: self._job_key.SetVariable( 'manual_refresh', False ) else: if thread_times_to_check > 0: self._job_key.SetVariable( 'thread_times_to_check', thread_times_to_check - 1 ) diff --git a/include/TestHydrusDownloading.py b/include/TestHydrusDownloading.py index 1b3bc015..7610c7ec 100644 --- a/include/TestHydrusDownloading.py +++ b/include/TestHydrusDownloading.py @@ -29,9 +29,9 @@ class TestDownloaders( unittest.TestCase ): with open( HC.STATIC_DIR + os.path.sep + 'testing' + os.path.sep + 'da_page.html' ) as f: da_page = f.read() HC.http.SetResponse( HC.GET, 'http://sakimichan.deviantart.com/gallery/?catpath=/&offset=0', da_gallery ) - HC.http.SetResponse( HC.GET, 'http://sakimichan.deviantart.com/art/Thumbs-up-411079893', da_page ) + HC.http.SetResponse( HC.GET, 'http://sakimichan.deviantart.com/art/Sailor-moon-in-PJs-506918040', da_page ) - HC.http.SetResponse( HC.GET, 'http://fc04.deviantart.net/fs70/f/2013/306/1/5/thumbs_up_by_sakimichan-d6sqv9x.jpg', 'image file' ) + HC.http.SetResponse( HC.GET, 'http://fc00.deviantart.net/fs71/f/2015/013/3/c/3c026edbe356b22c802e7be0db6fbd0b-d8dt0go.jpg', 'image file' ) # @@ -41,15 +41,15 @@ class TestDownloaders( unittest.TestCase ): gallery_urls = downloader.GetAnotherPage() - expected_gallery_urls = [('http://sakimichan.deviantart.com/art/Thumbs-up-411079893', ['title:Thumbs up', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Sci-Fi']), ('http://sakimichan.deviantart.com/art/The-Major-405852926', ['title:The Major', 'creator:akimichan', 'Fan Art', 'Cartoons & Comics', 'Digital', 'Movies & TV']), ('http://sakimichan.deviantart.com/art/Little-mermaid-fearie-401197163', ['title:Little mermaid fearie', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/Get-Em-Boy-or-not-399250537', ['title:Get Em Boy..or not', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/Welcome-To-The-Gang-398381756', ['title:Welcome To The Gang', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/Sci-Fi-Elf-398195577', ['title:Sci-Fi Elf', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Sci-Fi']), ('http://sakimichan.deviantart.com/art/Ahri-391295844', ['title:Ahri', 'creator:akimichan', 'Fan Art', 'Cartoons & Comics', 'Digital', 'Games']), ('http://sakimichan.deviantart.com/art/Orphan-386257383', ['title:Orphan', 'creator:akimichan', 'Manga & Anime', 'Digital Media', 'Drawings']), ('http://sakimichan.deviantart.com/art/The-summon-385996679', ['title:The summon', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/Rainbow2-377174679', ['title:Rainbow2', 'creator:akimichan', 'Manga & Anime', 'Digital Media', 'Drawings']), ('http://sakimichan.deviantart.com/art/Sword-Art-online-378517412', ['title:Sword Art online', 'creator:akimichan', 'Fan Art', 'Cartoons & Comics', 'Digital', 'Movies & TV']), ('http://sakimichan.deviantart.com/art/Adventure-Time-Group-photo-371913392', ['title:Adventure Time Group photo', 'creator:akimichan', 'Fan Art', 'Cartoons & Comics', 'Digital', 'Movies & TV']), ('http://sakimichan.deviantart.com/art/resurrection-353827147', ['title:resurrection', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/playful-Snow-Harpy-343275265', ['title:playful Snow Harpy', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/Link-369553015', ['title:Link', 'creator:akimichan', 'Fan Art', 'Digital Art', 'Painting & Airbrushing', 'Games']), ('http://sakimichan.deviantart.com/art/Dc-girls-363385250', ['title:Dc girls', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/Running-With-Spirits-337981944', ['title:Running With Spirits', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/FMA-358647977', ['title:FMA', 'creator:akimichan', 'Fan Art', 'Cartoons & Comics', 'Digital', 'Movies & TV']), ('http://sakimichan.deviantart.com/art/Wonder-woman-347864644', ['title:Wonder woman', 'creator:akimichan', 'Fan Art', 'Cartoons & Comics', 'Digital', 'Movies & TV']), ('http://sakimichan.deviantart.com/art/Ariel-found-Headphones-341927000', ['title:Ariel found Headphones', 'creator:akimichan', 'Fan Art', 'Cartoons & Comics', 'Digital', 'Movies & TV']), ('http://sakimichan.deviantart.com/art/Jack-Frost-340925669', ['title:Jack Frost', 'creator:akimichan', 'Fan Art', 'Cartoons & Comics', 'Digital', 'Movies & TV']), ('http://sakimichan.deviantart.com/art/Musics-blooming-336308163', ['title:Musics blooming', 'creator:akimichan', 'Digital Art', 'Drawings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/Yin-Yang-Goddess-327641961', ['title:Yin Yang Goddess', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy']), ('http://sakimichan.deviantart.com/art/Angelof-Justice-322844340', ['title:Angelof Justice', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Fantasy'])] + expected_gallery_urls = [('http://sakimichan.deviantart.com/art/Sailor-moon-in-PJs-506918040', ['title:Sailor moon in PJs', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Johnny-Bravo-505601401', ['title:Johnny Bravo', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Daphne-505394693', ['title:Daphne !', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/kim-Possible-505195132', ['title:kim Possible', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Levi-s-evil-plan-504966437', ["title:Levi's evil plan", 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Velma-504483448', ['title:Velma', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Scoobydoo-504238131', ['title:Scoobydoo', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Kerrigan-chilling-503477012', ['title:Kerrigan chilling', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Kiki-498525851', ['title:Kiki', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Waiter-Howl-502377515', ['title:Waiter Howl', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Modern-Loki-497985045', ['title:Modern Loki', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Emma-501919103', ['title:Emma', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Lola-494941222', ['title:Lola', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Elsas-501262184', ['title:Elsas', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Tsunade-499517356', ['title:Tsunade', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/A-little-cold-out-commission-498326494', ['title:A little cold out(commission)', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Girl-496999831', ['title:Girl', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Green-elf-496797148', ['title:Green elf', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Itachi-496625357', ['title:Itachi', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Sesshomaru-495474394', ['title:Sesshomaru', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Mononoke-years-later-502160436', ['title:Mononoke years later', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Jinx-488513585', ['title:Jinx', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Alex-in-wonderland-485819661', ['title:Alex in wonderland', 'creator:sakimichan']), ('http://sakimichan.deviantart.com/art/Ariels-476991263', ['title:Ariels', 'creator:sakimichan'])] self.assertEqual( gallery_urls, expected_gallery_urls ) # - tags = ['title:Thumbs up', 'creator:akimichan', 'Digital Art', 'Drawings & Paintings', 'Sci-Fi'] + tags = ['title:Sailor moon in PJs', 'creator:sakimichan'] - info = downloader.GetFileAndTags( 'http://sakimichan.deviantart.com/art/Thumbs-up-411079893', tags ) + info = downloader.GetFileAndTags( 'http://sakimichan.deviantart.com/art/Sailor-moon-in-PJs-506918040', tags ) ( temp_path, tags ) = info diff --git a/include/TestHydrusTags.py b/include/TestHydrusTags.py index 1a49c9c3..7f89d911 100644 --- a/include/TestHydrusTags.py +++ b/include/TestHydrusTags.py @@ -388,6 +388,153 @@ class TestTagsManager( unittest.TestCase ): self.assertEqual( self._other_tags_manager.GetPetitioned( self._reset_service_key ), set() ) +class TestTagObjects( unittest.TestCase ): + + def test_predicates( self ): + + p = HC.Predicate( HC.PREDICATE_TYPE_TAG, 'tag' ) + + self.assertEqual( p.GetUnicode(), u'tag' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_TAG, 'tag', counts = { HC.CURRENT : 1, HC.PENDING : 2 } ) + + self.assertEqual( p.GetUnicode( with_count = False ), u'tag' ) + self.assertEqual( p.GetUnicode( with_count = True ), u'tag (1) (+2)' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_TAG, 'tag', inclusive = False ) + + self.assertEqual( p.GetUnicode(), u'-tag' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_TAG, 'tag', inclusive = False, counts = { HC.CURRENT : 1, HC.PENDING : 2 } ) + + self.assertEqual( p.GetUnicode( with_count = False ), u'-tag' ) + self.assertEqual( p.GetUnicode( with_count = True ), u'-tag (1) (+2)' ) + + # + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_AGE, ( '<', 1, 2, 3, 4 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:age < 1y2m3d4h' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_AGE, ( u'\u2248', 1, 2, 3, 4 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:age ' + u'\u2248' + ' 1y2m3d4h' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_AGE, ( '>', 1, 2, 3, 4 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:age > 1y2m3d4h' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_ARCHIVE, None ), counts = { HC.CURRENT : 1000 } ) + + self.assertEqual( p.GetUnicode(), u'system:archive (1,000)' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_DURATION, ( '<', 1000 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:duration < 1,000' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_EVERYTHING, None ), counts = { HC.CURRENT : 2000 } ) + + self.assertEqual( p.GetUnicode(), u'system:everything (2,000)' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_FILE_SERVICE, ( True, HC.CURRENT, HC.LOCAL_FILE_SERVICE_KEY ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:is currently in local files' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_FILE_SERVICE, ( False, HC.PENDING, HC.LOCAL_FILE_SERVICE_KEY ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:is not pending to local files' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_HASH, 'abcd'.decode( 'hex' ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:hash is abcd' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_HEIGHT, ( '<', 2000 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:height < 2,000' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_INBOX, None ), counts = { HC.CURRENT : 1000 } ) + + self.assertEqual( p.GetUnicode(), u'system:inbox (1,000)' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_LIMIT, 2000 ) ) + + self.assertEqual( p.GetUnicode(), u'system:limit is 2,000' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_LOCAL, None ), counts = { HC.CURRENT : 100 } ) + + self.assertEqual( p.GetUnicode(), u'system:local (100)' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_MIME, HC.IMAGES ) ) + + self.assertEqual( p.GetUnicode(), u'system:mime is image' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_MIME, HC.VIDEO_WEBM ) ) + + self.assertEqual( p.GetUnicode(), u'system:mime is video/webm' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_NOT_LOCAL, None ), counts = { HC.CURRENT : 100 } ) + + self.assertEqual( p.GetUnicode(), u'system:not local (100)' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_NUM_TAGS, ( '<', 2 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:number of tags < 2' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_NUM_WORDS, ( '<', 5000 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:number of words < 5,000' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_RATING, ( HC.LOCAL_FILE_SERVICE_KEY, '>', 0.2 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:rating for local files > 0.2' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_RATIO, ( '=', 16, 9 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:ratio = 16:9' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_SIMILAR_TO, ( 'abcd'.decode( 'hex' ), 5 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:similar to abcd using max hamming of 5' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_SIZE, ( '>', 5, 1048576 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:size > 5MB' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_UNTAGGED, HC.IMAGES ) ) + + self.assertEqual( p.GetUnicode(), u'system:untagged' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_SYSTEM, ( HC.SYSTEM_PREDICATE_TYPE_WIDTH, ( '=', 1920 ) ) ) + + self.assertEqual( p.GetUnicode(), u'system:width = 1,920' ) + + # + + p = HC.Predicate( HC.PREDICATE_TYPE_NAMESPACE, 'series' ) + + self.assertEqual( p.GetUnicode(), u'series:*' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_TAG, 'series', inclusive = False ) + + self.assertEqual( p.GetUnicode(), u'-series' ) + + # + + p = HC.Predicate( HC.PREDICATE_TYPE_WILDCARD, 'a*i:o*' ) + + self.assertEqual( p.GetUnicode(), u'a*i:o*' ) + + p = HC.Predicate( HC.PREDICATE_TYPE_TAG, 'a*i:o*', inclusive = False ) + + self.assertEqual( p.GetUnicode(), u'-a*i:o*' ) + + # + + p = HC.Predicate( HC.PREDICATE_TYPE_PARENT, 'series:game of thrones' ) + + self.assertEqual( p.GetUnicode(), u' series:game of thrones' ) + + class TestTagParents( unittest.TestCase ): @classmethod diff --git a/static/testing/da_gallery.html b/static/testing/da_gallery.html index 3c0bf55c..434c6224 100644 --- a/static/testing/da_gallery.html +++ b/static/testing/da_gallery.html @@ -8,28 +8,56 @@ -
-
+
-