Version 179

This commit is contained in:
Hydrus 2015-10-28 16:29:05 -05:00
parent 440a4c0cc2
commit f929f878b3
27 changed files with 1546 additions and 992 deletions

View File

@ -8,6 +8,40 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 179</h3></li>
<ul>
<li>all tag listboxes support multiple selection</li>
<li>all tag listboxes manipulate their data as sets of tags rather than individual tags</li>
<li>all tag listboxes report to their callbacks as sets of tags</li>
<li>many dialogs and other windows that use tag listboxes now deal with tags as sets</li>
<li>double clicking the media viewer's tag hover window now launches the manage tags dialog</li>
<li>the media viewer's tag hover window now includes (+1) counts like the manage tags dialog--harmonising the underlying canvas tag list will follow</li>
<li>lots of tag/sibling/parent code has been iterated over</li>
<li>ditched some misc redundant code and needlessly tightly coupled object relationships</li>
<li>some bizarro tag sibling dialog code has been rewritten</li>
<li>the autocomplete dropdown for 'writing' will now not expand search results to include parents when the receiving control isn't interested in them or it is otherwise not appropriate</li>
<li>when the autocomplete dropdown does have parents, they will be selected in a group with their child</li>
<li>the autocomplete dropdown for writing now broadcasts 'I'm done with tagging, close the dialog pls' in a better way</li>
<li>'activating' the sibling or parent dialogs' listcontrols (usually by hitting enter) now processes all rows that are selected, not just the first</li>
<li>manage siblings and parents dialogs can now take multiple initialising tags from the taglist right-click menu</li>
<li>removed some redundant listbox code</li>
<li>cleaned up and improved some tag list event processing</li>
<li>cleaned up some taglist right-click menu code</li>
<li>added system-wide mouse idle test to idle calculation. you can set this in files->options->maintenance and processing, and it defaults to ten minutes</li>
<li>import folders now support tag import options' explicit tags for multiple tags to any tag service</li>
<li>existing import folders will be updated to the new version, and if a local tag exists, it will be intserted into the new import tag options</li>
<li>fixed a hentai foundry page parsing bug</li>
<li>the deviant art downloader can now download >1024 pixel width versions of images via the download button (and a bit of cookie magic)</li>
<li>gallery page queries that 404 (like for a non-existent username) will now report 'Gallery 404' rather than spamming the gallery's whole custom 404 html page to the status box</li>
<li>fixed a bad layout flag that meant some namespace checkboxes in tag import options could remain hidden during first panel expand until first mouseover</li>
<li>the import status frame now initialises its status text properly</li>
<li>fixed last week's num_tags optimisations, which accidentally broke num_tags < x for x > 1</li>
<li>the client should work on both OpenCV 2.4.x and 3.0.0 (thankfully, the only difference for our purposes turned out to be some static variable renaming)</li>
<li>the Windows and OS X releases now come with OpenCV 3.0.0</li>
<li>increased the max period of import and export folders to 30 days</li>
<li>the launchfile/directory thread is now a daemon, so the (some flavours of Linux) client can shutdown even if an externally launched file/dir remains open</li>
<li>misc cleanup</li>
</ul>
<li><h3>version 178</h3></li>
<ul>
<li>import tag options now supports 'explicit tags', which will be added to all files imported</li>

View File

@ -1126,6 +1126,12 @@ class WebSessionManagerClient( object ):
# name not found, or expired
if name == 'deviant art':
( response_gumpf, cookies ) = HydrusGlobals.client_controller.DoHTTP( HC.GET, 'http://www.deviantart.com/', return_cookies = True )
expires = now + 30 * 86400
if name == 'hentai foundry':
( response_gumpf, cookies ) = HydrusGlobals.client_controller.DoHTTP( HC.GET, 'http://www.hentai-foundry.com/?enterAgree=1', return_cookies = True )

View File

@ -28,7 +28,7 @@ Ctrl + A - Select all
Escape - Deselect all
Ctrl + C - Copy selected files to clipboard
- In Fullscreen -
- In Media Viewer -
Shift-LeftClick-Drag - Drag (in Filter)
Ctrl + MouseWheel - Zoom
Z - Zoom Full/Fit'''

View File

@ -43,6 +43,8 @@ class Controller( HydrusController.HydrusController ):
HydrusGlobals.client_controller = self
self._last_mouse_position = None
def _InitDB( self ):
@ -148,6 +150,31 @@ class Controller( HydrusController.HydrusController ):
def CheckMouseIdle( self ):
mouse_position = wx.GetMousePosition()
if self._last_mouse_position is None:
self._last_mouse_position = mouse_position
elif mouse_position != self._last_mouse_position:
idle_before = self.CurrentlyIdle()
self._timestamps[ 'last_mouse_action' ] = HydrusData.GetNow()
self._last_mouse_position = mouse_position
idle_after = self.CurrentlyIdle()
if idle_before != idle_after:
self.pub( 'refresh_status' )
def Clipboard( self, data_type, data ):
# need this cause can't do it in a non-gui thread
@ -234,12 +261,48 @@ class Controller( HydrusController.HydrusController ):
def CurrentlyIdle( self ):
if self._options[ 'idle_period' ] == 0:
# the existence of an idle test permits a True result
# any single fail vetoes a True
possibly_idle = False
definitely_not_idle = False
if self._options[ 'idle_period' ] > 0:
if HydrusData.TimeHasPassed( self._timestamps[ 'last_user_action' ] + self._options[ 'idle_period' ] ):
possibly_idle = True
else:
definitely_not_idle = True
if self._options[ 'idle_mouse_period' ] > 0:
if HydrusData.TimeHasPassed( self._timestamps[ 'last_mouse_action' ] + self._options[ 'idle_mouse_period' ] ):
possibly_idle = True
else:
definitely_not_idle = True
if definitely_not_idle:
return False
elif possibly_idle:
return True
else:
return False
return HydrusData.TimeHasPassed( self._timestamps[ 'last_user_action' ] + self._options[ 'idle_period' ] )
def DoHTTP( self, *args, **kwargs ): return self._http.Request( *args, **kwargs )
@ -295,7 +358,10 @@ class Controller( HydrusController.HydrusController ):
def ForceIdle( self ):
self._timestamps[ 'last_user_action' ] = 0
del self._timestamps[ 'last_user_action' ]
del self._timestamps[ 'last_mouse_action' ]
self._last_mouse_position = None
self.pub( 'wake_daemons' )
self.pub( 'refresh_status' )
@ -422,6 +488,7 @@ class Controller( HydrusController.HydrusController ):
self.RestartBooru()
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckImportFolders', ClientDaemons.DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckMouseIdle', ClientDaemons.DAEMONCheckMouseIdle, period = 10 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckExportFolders', ClientDaemons.DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'DownloadFiles', ClientDaemons.DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ), pre_callable_wait = 0 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'MaintainTrash', ClientDaemons.DAEMONMaintainTrash, init_wait = 60 ) )
@ -506,7 +573,10 @@ class Controller( HydrusController.HydrusController ):
else: return text.lower()
def ResetIdleTimer( self ): self._timestamps[ 'last_user_action' ] = HydrusData.GetNow()
def ResetIdleTimer( self ):
self._timestamps[ 'last_user_action' ] = HydrusData.GetNow()
def ResetPageChangeTimer( self ):

View File

@ -2733,6 +2733,7 @@ class DB( HydrusDB.HydrusDB ):
num_tags_zero = False
num_tags_nonzero = False
max_num_tags_exists = False
tag_predicates = []
@ -2778,10 +2779,12 @@ class DB( HydrusDB.HydrusDB ):
if num_tags_zero or num_tags_nonzero:
if tag_service_key == CC.COMBINED_TAG_SERVICE_KEY: service_phrase = ''
else: service_phrase = 'service_id = ' + HydrusData.ToString( tag_service_id ) + ' AND '
tag_predicates_care_about_zero_counts = len( tag_predicates ) > 0 and False not in ( pred( 0 ) for pred in tag_predicates )
if tag_service_key == CC.COMBINED_TAG_SERVICE_KEY: service_phrase = ''
else: service_phrase = 'service_id = ' + HydrusData.ToString( tag_service_id ) + ' AND '
if num_tags_zero or num_tags_nonzero or tag_predicates_care_about_zero_counts:
nonzero_tag_query_hash_ids = { id for ( id, ) in self._c.execute( 'SELECT DISTINCT hash_id FROM mappings WHERE ' + service_phrase + 'hash_id IN ' + HydrusData.SplayListForDB( query_hash_ids ) + ' AND status IN ' + HydrusData.SplayListForDB( statuses ) + ';' ) }
@ -2791,20 +2794,15 @@ class DB( HydrusDB.HydrusDB ):
if len( tag_predicates ) > 0:
if tag_service_key == CC.COMBINED_TAG_SERVICE_KEY: service_phrase = ''
else: service_phrase = 'service_id = ' + HydrusData.ToString( tag_service_id ) + ' AND '
old_query_hash_ids = query_hash_ids
nonzero_counts_query = 'SELECT hash_id, COUNT( DISTINCT tag_id ) FROM mappings WHERE ' + service_phrase + 'hash_id IN ' + HydrusData.SplayListForDB( query_hash_ids ) + ' AND status IN ' + HydrusData.SplayListForDB( statuses ) + ' GROUP BY hash_id;'
query_hash_ids = { id for ( id, count ) in self._c.execute( nonzero_counts_query ) if False not in ( pred( count ) for pred in tag_predicates ) }
include_zero_count_in_calculation = False not in ( pred( 0 ) for pred in tag_predicates )
if include_zero_count_in_calculation:
if tag_predicates_care_about_zero_counts:
zero_hash_ids = old_query_hash_ids.difference( query_hash_ids )
zero_hash_ids = old_query_hash_ids.difference( nonzero_tag_query_hash_ids )
query_hash_ids.update( zero_hash_ids )
@ -5836,9 +5834,19 @@ class DB( HydrusDB.HydrusDB ):
period = details[ 'check_period' ]
tag = details[ 'local_tag' ]
import_folder = ClientImporting.ImportFolder( name, path, import_file_options = import_file_options, actions = actions, action_locations = {}, period = period, open_popup = True, tag = tag )
service_keys_to_explicit_tags = dict()
if tag is not None:
service_keys_to_explicit_tags[ CC.LOCAL_TAG_SERVICE_KEY ] = { tag }
import_tag_options = ClientData.ImportTagOptions( service_keys_to_explicit_tags = service_keys_to_explicit_tags )
import_folder = ClientImporting.ImportFolder( name, path, import_file_options = import_file_options, import_tag_options = import_tag_options, actions = actions, action_locations = {}, period = period, open_popup = True )
import_folder._last_checked = details[ 'last_checked' ]

View File

@ -69,6 +69,10 @@ def DAEMONCheckImportFolders():
def DAEMONCheckMouseIdle():
HydrusGlobals.client_controller.CheckMouseIdle()
def DAEMONDownloadFiles():
hashes = HydrusGlobals.client_controller.Read( 'downloads' )

View File

@ -27,6 +27,7 @@ def GetClientDefaultOptions():
options[ 'ac_timings' ] = ( 3, 500, 250 )
options[ 'thread_checker_timings' ] = ( 3, 1200 )
options[ 'idle_period' ] = 60 * 30
options[ 'idle_mouse_period' ] = 60 * 10
options[ 'idle_cpu_max' ] = 50
options[ 'idle_shutdown' ] = CC.IDLE_ON_SHUTDOWN_ASK_FIRST
options[ 'idle_shutdown_max_minutes' ] = 30

View File

@ -723,6 +723,15 @@ class GalleryBooru( Gallery ):
class GalleryDeviantArt( Gallery ):
def _AddSessionCookies( self, request_headers ):
manager = HydrusGlobals.client_controller.GetManager( 'web_sessions' )
cookies = manager.GetCookies( 'deviant art' )
ClientNetworking.AddCookiesToHeaders( cookies, request_headers )
def _GetGalleryPageURL( self, query, page_index ):
artist = query
@ -774,30 +783,45 @@ class GalleryDeviantArt( Gallery ):
return ( urls, definitely_no_more_pages )
def _ParseImagePage( self, html ):
def _ParseImagePage( self, html, referer_url ):
soup = bs4.BeautifulSoup( html )
img = soup.find( class_ = 'dev-content-full' )
download_button = soup.find( 'a', class_ = 'dev-page-download' )
if img is None:
if download_button is None:
# this probably means it is mature
# DA hide the url pretty much everywhere except the tumblr share thing
# this method maxes out at 1024 width
a_tumblr = soup.find( id = 'gmi-ResourceViewShareTumblr' )
img = soup.find( class_ = 'dev-content-full' )
tumblr_url = a_tumblr[ 'href' ] # http://www.tumblr.com/share/photo?source=http%3A%2F%2Fimg09.deviantart.net%2Ff19a%2Fi%2F2015%2F054%2Fe%2Fd%2Fass_by_gmgkaiser-d8j7ija.png&amp;caption=%3Ca+href%3D%22http%3A%2F%2Fgmgkaiser.deviantart.com%2Fart%2Fass-515992726%22%3Eass%3C%2Fa%3E+by+%3Ca+href%3D%22http%3A%2F%2Fgmgkaiser.deviantart.com%2F%22%3EGMGkaiser%3C%2Fa%3E&amp;clickthru=http%3A%2F%2Fgmgkaiser.deviantart.com%2Fart%2Fass-515992726
parse_result = urlparse.urlparse( tumblr_url )
query_parse_result = urlparse.parse_qs( parse_result.query )
img_url = query_parse_result[ 'source' ][0] # http://img09.deviantart.net/f19a/i/2015/054/e/d/ass_by_gmgkaiser-d8j7ija.png
if img is None:
# this probably means it is mature
# DA hide the url pretty much everywhere except the tumblr share thing
a_tumblr = soup.find( id = 'gmi-ResourceViewShareTumblr' )
tumblr_url = a_tumblr[ 'href' ] # http://www.tumblr.com/share/photo?source=http%3A%2F%2Fimg09.deviantart.net%2Ff19a%2Fi%2F2015%2F054%2Fe%2Fd%2Fass_by_gmgkaiser-d8j7ija.png&amp;caption=%3Ca+href%3D%22http%3A%2F%2Fgmgkaiser.deviantart.com%2Fart%2Fass-515992726%22%3Eass%3C%2Fa%3E+by+%3Ca+href%3D%22http%3A%2F%2Fgmgkaiser.deviantart.com%2F%22%3EGMGkaiser%3C%2Fa%3E&amp;clickthru=http%3A%2F%2Fgmgkaiser.deviantart.com%2Fart%2Fass-515992726
parse_result = urlparse.urlparse( tumblr_url )
query_parse_result = urlparse.parse_qs( parse_result.query )
img_url = query_parse_result[ 'source' ][0] # http://img09.deviantart.net/f19a/i/2015/054/e/d/ass_by_gmgkaiser-d8j7ija.png
else:
img_url = img[ 'src' ]
else:
img_url = img[ 'src' ]
# something like http://www.deviantart.com/download/518046750/varda_and_the_sacred_trees_of_valinor_by_implosinoatic-d8kfjfi.jpg?token=476cb73aa2ab22bb8554542bc9f14982e09bd534&ts=1445717843
# given the right cookies, it redirects to the truly fullsize image_url
# otherwise, it seems to redirect to a small interstitial redirect page that heads back to the original image page
img_url = download_button[ 'href' ]
return img_url
@ -807,7 +831,7 @@ class GalleryDeviantArt( Gallery ):
html = self._FetchData( url, report_hooks = report_hooks )
return self._ParseImagePage( html )
return self._ParseImagePage( html, url )
def GetFile( self, temp_path, url, report_hooks = None ):
@ -925,6 +949,11 @@ class GalleryHentaiFoundry( Gallery ):
def correct_url( href ):
if href is None:
return False
# a good url is in the form "/pictures/user/artist_name/file_id/title"
if href.count( '/' ) == 5 and href.startswith( '/pictures/user/' ):

View File

@ -376,14 +376,22 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
def DoWork( self ):
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' checking' )
if HydrusData.TimeHasPassed( self._last_checked + self._period ):
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' time to begin' )
folder_path = self._name
if os.path.exists( folder_path ) and os.path.isdir( folder_path ):
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' folder checks out ok' )
query_hash_ids = HydrusGlobals.client_controller.Read( 'file_query_ids', self._file_search_context )
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' results found: ' + str( len( query_hash_ids ) ) )
query_hash_ids = list( query_hash_ids )
random.shuffle( query_hash_ids )
@ -400,6 +408,8 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
while i < len( query_hash_ids ):
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' building results: ' + str( i ) + '/' + str( len( query_hash_ids ) ) )
if HC.options[ 'pause_export_folders_sync' ]: return
if i == 0: ( last_i, i ) = ( 0, base )
@ -412,12 +422,21 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
media_results.extend( more_media_results )
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' media_results: ' + str( len( media_results ) ) )
#
terms = ParseExportPhrase( self._phrase )
previous_filenames = set( os.listdir( folder_path ) )
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' existing filenames: ' + str( len( previous_filenames ) ) )
if HydrusGlobals.special_debug_mode:
for previous_filename in previous_filenames:
print( previous_filename )
sync_filenames = set()
for media_result in media_results:
@ -431,54 +450,54 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
filename = GenerateExportFilename( media_result, terms ) + HC.mime_ext_lookup[ mime ]
dest_path = folder_path + os.path.sep + filename
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' dest path: ' + dest_path )
do_copy = True
if filename in sync_filenames:
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' it was already attempted this run' )
do_copy = False
elif os.path.exists( dest_path ):
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' it exists' )
dest_info = os.lstat( dest_path )
dest_size = dest_info[6]
if dest_size == size:
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' and the file size is the same' )
do_copy = False
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' copy decision: ' + str( do_copy ) )
if do_copy:
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' copy started' )
shutil.copy( source_path, dest_path )
shutil.copystat( source_path, dest_path )
try: os.chmod( dest_path, stat.S_IWRITE | stat.S_IREAD )
except: pass
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' copy ok' )
sync_filenames.add( filename )
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' media results done' )
if self._export_type == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' inside sync delete code' )
deletee_filenames = previous_filenames.difference( sync_filenames )
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' delete filenames: ' + str( len( deletee_filenames ) ) )
for deletee_filename in deletee_filenames:
deletee_path = folder_path + os.path.sep + deletee_filename
if HydrusGlobals.special_debug_mode: print( deletee_path )
ClientData.DeletePath( deletee_path )
self._last_checked = HydrusData.GetNow()
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' writing self back to db' )
HydrusGlobals.client_controller.WriteSynchronous( 'serialisable', self )
if HydrusGlobals.special_debug_mode: HydrusData.ShowText( self._name + ' saved ok' )
def ToTuple( self ):

View File

@ -1012,10 +1012,13 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
menu.AppendMenu( wx.ID_NONE, p( 'Links' ), links )
db_profile_mode_id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'db_profile_mode' )
special_debug_mode_id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'special_debug_mode' )
debug = wx.Menu()
debug.AppendCheckItem( db_profile_mode_id, p( '&DB Profile Mode' ) )
debug.Check( db_profile_mode_id, HydrusGlobals.db_profile_mode )
debug.AppendCheckItem( special_debug_mode_id, p( '&Special Debug Mode' ) )
debug.Check( special_debug_mode_id, HydrusGlobals.special_debug_mode )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'force_idle' ), p( 'Force Idle Mode' ) )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'force_unbusy' ), p( 'Force Unbusy Mode' ) )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'debug_garbage' ), p( 'Garbage' ) )
@ -2137,6 +2140,10 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
if page is not None: page.ShowHideSplit()
elif command == 'site': webbrowser.open( 'https://hydrusnetwork.github.io/hydrus/' )
elif command == 'special_debug_mode':
HydrusGlobals.special_debug_mode = not HydrusGlobals.special_debug_mode
elif command == 'start_url_download': self._StartURLDownload()
elif command == 'start_youtube_download': self._StartYoutubeDownload()
elif command == 'stats': self._Stats( data )
@ -3342,10 +3349,12 @@ class FrameSeedCache( ClientGUICommon.Frame ):
HydrusGlobals.client_controller.sub( self, 'NotifySeedUpdated', 'seed_cache_seed_updated' )
wx.CallAfter( self._UpdateText )
wx.CallAfter( self.Raise )
def NotifySeedUpdated( self, seed ):
def _UpdateText( self ):
( status, ( total_processed, total ) ) = self._seed_cache.GetStatus()
@ -3354,6 +3363,11 @@ class FrameSeedCache( ClientGUICommon.Frame ):
self.Layout()
def NotifySeedUpdated( self, seed ):
self._UpdateText()
class FrameSplash( ClientGUICommon.Frame ):
WIDTH = 300

View File

@ -588,6 +588,7 @@ class Canvas( object ):
HydrusGlobals.client_controller.sub( self, 'ZoomOut', 'canvas_zoom_out' )
HydrusGlobals.client_controller.sub( self, 'ZoomSwitch', 'canvas_zoom_switch' )
HydrusGlobals.client_controller.sub( self, 'OpenExternally', 'canvas_open_externally' )
HydrusGlobals.client_controller.sub( self, 'ManageTags', 'canvas_manage_tags' )
def _Archive( self ): HydrusGlobals.client_controller.Write( 'content_updates', { CC.LOCAL_FILE_SERVICE_KEY : [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, ( self._current_display_media.GetHash(), ) ) ] } )
@ -772,6 +773,7 @@ class Canvas( object ):
with ClientGUIDialogsManage.DialogManageTags( self, self._file_service_key, ( self._current_display_media, ), canvas_key = self._canvas_key ) as dlg:
dlg.ShowModal()
@ -1023,6 +1025,14 @@ class Canvas( object ):
def KeepCursorAlive( self ): pass
def ManageTags( self, canvas_key ):
if canvas_key == self._canvas_key:
self._ManageTags()
def MouseIsNearAnimationBar( self ):
return self._media_container.MouseIsNearAnimationBar()
@ -1487,7 +1497,7 @@ class CanvasFullscreenMediaList( ClientMedia.ListeningMediaList, CanvasWithDetai
def __init__( self, my_parent, page_key, media_results ):
ClientGUICommon.FrameThatResizes.__init__( self, my_parent, resize_option_prefix = 'fs_', title = 'hydrus client fullscreen media viewer' )
ClientGUICommon.FrameThatResizes.__init__( self, my_parent, resize_option_prefix = 'fs_', title = 'hydrus client media viewer' )
CanvasWithDetails.__init__( self, HydrusGlobals.client_controller.GetCache( 'fullscreen' ) )
ClientMedia.ListeningMediaList.__init__( self, CC.LOCAL_FILE_SERVICE_KEY, media_results )

File diff suppressed because it is too large Load Diff

View File

@ -286,7 +286,9 @@ class DialogAdvancedContentUpdate( Dialog ):
self._go = wx.Button( self._internal_actions, label = 'Go!' )
self._go.Bind( wx.EVT_BUTTON, self.EventGo )
self._tag_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._internal_actions, self.SetSomeTag, CC.COMBINED_FILE_SERVICE_KEY, self._service_key )
expand_parents = False
self._tag_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._internal_actions, self.SetSomeTags, expand_parents, CC.COMBINED_FILE_SERVICE_KEY, self._service_key )
self._specific_tag = wx.StaticText( self._internal_actions, label = '', size = ( 100, -1 ) )
self._import_from_hta = wx.Button( self._internal_actions, label = 'one-time mass import or delete using a hydrus tag archive' )
@ -418,6 +420,9 @@ class DialogAdvancedContentUpdate( Dialog ):
def EventGo( self, event ):
# at some point, rewrite this to cope with multiple tags. setsometag is ready to go on that front
# this should prob be with a listbox so people can enter their new multiple tags in several separate goes, rather than overwriting every time
with DialogYesNo( self, 'Are you sure?' ) as dlg:
if dlg.ShowModal() != wx.ID_YES: return
@ -475,13 +480,14 @@ class DialogAdvancedContentUpdate( Dialog ):
def SetSomeTag( self, tag, parents = None ):
def SetSomeTags( self, tags ):
if parents is None: parents = []
self._tag = tag
self._specific_tag.SetLabel( tag )
if len( tags ) > 0:
self._tag = list( tags )[0]
self._specific_tag.SetLabel( self._tag )
class DialogButtonChoice( Dialog ):
@ -844,7 +850,10 @@ class DialogInputCustomFilterAction( Dialog ):
self._tag_service_keys = wx.Choice( self._tag_panel )
self._tag_value = wx.TextCtrl( self._tag_panel, style = wx.TE_READONLY )
self._tag_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._tag_panel, self.SetTag, CC.LOCAL_FILE_SERVICE_KEY, CC.COMBINED_TAG_SERVICE_KEY )
expand_parents = False
self._tag_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._tag_panel, self.SetTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, CC.COMBINED_TAG_SERVICE_KEY )
self._ok_tag = wx.Button( self._tag_panel, label = 'ok' )
self._ok_tag.Bind( wx.EVT_BUTTON, self.EventOKTag )
@ -1142,6 +1151,8 @@ class DialogInputCustomFilterAction( Dialog ):
def EventOKTag( self, event ):
# this could support multiple tags now
selection = self._tag_service_keys.GetSelection()
if selection != wx.NOT_FOUND:
@ -1175,11 +1186,14 @@ class DialogInputCustomFilterAction( Dialog ):
return ( ( pretty_modifier, pretty_key, pretty_service_key, self._pretty_action ), ( modifier, key, self._service_key, self._action ) )
def SetTag( self, tag, parents = None ):
def SetTags( self, tags ):
if parents is None: parents = []
self._tag_value.SetValue( tag )
if len( tags ) > 0:
tag = list( tags )[0]
self._tag_value.SetValue( tag )
class DialogInputFileSystemPredicates( Dialog ):
@ -2460,7 +2474,9 @@ class DialogInputTags( Dialog ):
self._tags = ClientGUICommon.ListBoxTagsStrings( self )
self._tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddTag, CC.LOCAL_FILE_SERVICE_KEY, service_key )
expand_parents = True
self._tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.EnterTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key, null_entry_callable = self.Ok )
self._ok = wx.Button( self, id= wx.ID_OK, label = 'Ok' )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
@ -2496,17 +2512,17 @@ class DialogInputTags( Dialog ):
wx.CallAfter( self._tag_box.SetFocus )
def AddTag( self, tag, parents = None ):
def EnterTags( self, tags, parents = None ):
if parents is None: parents = []
if parents is None:
parents = []
if tag is None:
if len( tags ) > 0:
self.EndModal( wx.ID_OK )
else:
self._tags.AddTag( tag, parents )
self._tags.EnterTags( tags )
self._tags.AddTags( parents )
@ -2515,6 +2531,11 @@ class DialogInputTags( Dialog ):
return self._tags.GetTags()
def Ok( self ):
self.EndModal( wx.ID_OK )
class DialogInputUPnPMapping( Dialog ):
def __init__( self, parent, external_port, protocol_type, internal_port, description, duration ):
@ -3404,19 +3425,23 @@ class DialogPathsToTags( Dialog ):
self._tags_panel = ClientGUICommon.StaticBox( self, 'tags for all' )
self._tags = ClientGUICommon.ListBoxTagsStrings( self._tags_panel, self.TagRemoved )
self._tags = ClientGUICommon.ListBoxTagsStrings( self._tags_panel, self.TagsRemoved )
self._tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._tags_panel, self.AddTag, CC.LOCAL_FILE_SERVICE_KEY, service_key )
expand_parents = True
self._tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._tags_panel, self.EnterTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
#
self._single_tags_panel = ClientGUICommon.StaticBox( self, 'tags just for selected files' )
self._paths_to_single_tags = collections.defaultdict( list )
self._paths_to_single_tags = collections.defaultdict( set )
self._single_tags = ClientGUICommon.ListBoxTagsStrings( self._single_tags_panel, self.SingleTagRemoved )
self._single_tags = ClientGUICommon.ListBoxTagsStrings( self._single_tags_panel, self.SingleTagsRemoved )
self._single_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._single_tags_panel, self.AddTagSingle, CC.LOCAL_FILE_SERVICE_KEY, service_key )
expand_parents = True
self._single_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._single_tags_panel, self.EnterTagsSingle, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
def PopulateControls():
@ -3544,7 +3569,10 @@ class DialogPathsToTags( Dialog ):
except: pass
if path in self._paths_to_single_tags: tags.extend( self._paths_to_single_tags[ path ] )
if path in self._paths_to_single_tags:
tags.extend( list( self._paths_to_single_tags[ path ] ) )
num_namespace = self._num_namespace.GetValue()
@ -3581,48 +3609,6 @@ class DialogPathsToTags( Dialog ):
def AddTag( self, tag, parents = None ):
if parents is None: parents = []
if tag is not None:
self._tags.AddTag( tag, parents )
self._RefreshFileList()
def AddTagSingle( self, tag, parents = None ):
if parents is None: parents = []
if tag is not None:
self._single_tags.AddTag( tag, parents )
indices = self._paths_list.GetAllSelected()
for index in indices:
( ( original_num, processed_num ), path, old_tags ) = self._paths_list.GetClientData( index )
if tag in self._paths_to_single_tags[ path ]: self._paths_to_single_tags[ path ].remove( tag )
else:
self._paths_to_single_tags[ path ].append( tag )
for parent in parents:
if parent not in self._paths_to_single_tags[ path ]: self._paths_to_single_tags[ path ].append( parent )
self._RefreshFileList()
def DeleteQuickNamespaces( self ):
self._quick_namespaces_list.RemoveAllSelected()
@ -3630,6 +3616,61 @@ class DialogPathsToTags( Dialog ):
self._RefreshFileList()
def EnterTags( self, tags, parents = None ):
if parents is None:
parents = []
if len( tags ) > 0:
self._tags.EnterTags( tags )
self._tags.AddTags( parents )
self._RefreshFileList()
def EnterTagsSingle( self, tags, parents = None ):
if parents is None:
parents = []
if len( tags ) > 0:
self._single_tags.EnterTags( tags )
self._single_tags.AddTags( parents )
indices = self._paths_list.GetAllSelected()
for index in indices:
( ( original_num, processed_num ), path, old_pretty_tags ) = self._paths_list.GetClientData( index )
current_tags = self._paths_to_single_tags[ path ]
for tag in tags:
if tag in current_tags:
current_tags.discard( tag )
else:
current_tags.add( tag )
current_tags.update( parents )
self._RefreshFileList()
def EventAddRegex( self, event ):
regex = self._regex_box.GetValue()
@ -3708,7 +3749,10 @@ class DialogPathsToTags( Dialog ):
path = self._paths_list.GetClientData( index )[1]
if path in self._paths_to_single_tags: single_tags.update( self._paths_to_single_tags[ path ] )
if path in self._paths_to_single_tags:
single_tags.update( self._paths_to_single_tags[ path ] )
self._single_tag_box.Enable()
@ -3760,7 +3804,7 @@ class DialogPathsToTags( Dialog ):
def SetTagBoxFocus( self ): self._tag_box.SetFocus()
def SingleTagRemoved( self, tag ):
def SingleTagsRemoved( self, tags ):
indices = self._paths_list.GetAllSelected()
@ -3768,13 +3812,18 @@ class DialogPathsToTags( Dialog ):
( ( original_num, processed_num ), path, old_tags ) = self._paths_list.GetClientData( index )
if tag in self._paths_to_single_tags[ path ]: self._paths_to_single_tags[ path ].remove( tag )
current_tags = self._paths_to_single_tags[ path ]
current_tags.difference_update( tags )
self._RefreshFileList()
def TagRemoved( self, tag ): self._RefreshFileList()
def TagsRemoved( self, tag ):
self._RefreshFileList()
class DialogRegisterService( Dialog ):

View File

@ -1656,7 +1656,7 @@ class DialogManageExportFoldersEdit( ClientGUIDialogs.Dialog ):
self._period = wx.SpinCtrl( self._period_box )
self._period.SetRange( 3, 180 )
self._period.SetRange( 3, 60 * 24 * 30 )
self._period.SetValue( period / 60 )
@ -1729,17 +1729,6 @@ If you select synchronise, be careful!'''
wx.CallAfter( self._ok.SetFocus )
HydrusGlobals.client_controller.sub( self, 'AddPredicate', 'add_predicate' )
HydrusGlobals.client_controller.sub( self, 'RemovePredicate', 'remove_predicate' )
def AddPredicate( self, page_key, predicate ):
if page_key == self._page_key:
if self._predicates_box.HasPredicate( predicate ): self._predicates_box.RemovePredicate( predicate )
else: self._predicates_box.AddPredicate( predicate )
def EventOK( self, event ):
@ -1778,17 +1767,6 @@ If you select synchronise, be careful!'''
return self._export_folder
def RemovePredicate( self, page_key, predicate ):
if page_key == self._page_key:
if self._predicates_box.HasPredicate( predicate ):
self._predicates_box.RemovePredicate( predicate )
class DialogManageImageboards( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
@ -2513,7 +2491,7 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage import folders' )
self._import_folders = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'name', 120 ), ( 'path', -1 ), ( 'check period', 120 ), ( 'local tag', 120 ) ], delete_key_callback = self.Delete )
self._import_folders = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'name', 120 ), ( 'path', -1 ), ( 'check period', 120 ) ], delete_key_callback = self.Delete )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
@ -2539,11 +2517,11 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
for import_folder in import_folders:
( name, path, check_period, tag ) = import_folder.ToListBoxTuple()
( name, path, check_period ) = import_folder.ToListBoxTuple()
( pretty_check_period, pretty_tag ) = self._GetPrettyVariables( check_period, tag )
pretty_check_period = self._GetPrettyVariables( check_period )
self._import_folders.Append( ( name, path, pretty_check_period, pretty_tag ), ( name, path, check_period, tag ) )
self._import_folders.Append( ( name, path, pretty_check_period ), ( name, path, check_period ) )
self._names_to_import_folders[ name ] = import_folder
@ -2596,11 +2574,11 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
import_folder = dlg.GetInfo()
( name, path, check_period, tag ) = import_folder.ToListBoxTuple()
( name, path, check_period ) = import_folder.ToListBoxTuple()
( pretty_check_period, pretty_tag ) = self._GetPrettyVariables( check_period, tag )
pretty_check_period = self._GetPrettyVariables( check_period )
self._import_folders.Append( ( name, path, pretty_check_period, pretty_tag ), ( name, path, check_period, tag ) )
self._import_folders.Append( ( name, path, pretty_check_period ), ( name, path, check_period ) )
self._names_to_import_folders[ name ] = import_folder
@ -2608,14 +2586,11 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
def _GetPrettyVariables( self, check_period, tag ):
def _GetPrettyVariables( self, check_period ):
pretty_check_period = HydrusData.ToString( check_period / 60 ) + ' minutes'
if tag == None: pretty_tag = ''
else: pretty_tag = tag
return ( pretty_check_period, pretty_tag )
return pretty_check_period
def Delete( self ):
@ -2629,7 +2604,7 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
existing_names = set()
for ( name, path, check_period, tag ) in client_data:
for ( name, path, check_period ) in client_data:
existing_names.add( name )
@ -2669,7 +2644,7 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
for index in indices:
( name, path, check_period, tag ) = self._import_folders.GetClientData( index )
( name, path, check_period ) = self._import_folders.GetClientData( index )
import_folder = self._names_to_import_folders[ name ]
@ -2679,11 +2654,11 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
import_folder = dlg.GetInfo()
( name, path, check_period, tag ) = import_folder.ToListBoxTuple()
( name, path, check_period ) = import_folder.ToListBoxTuple()
( pretty_check_period, pretty_tag ) = self._GetPrettyVariables( check_period, tag )
pretty_check_period = self._GetPrettyVariables( check_period )
self._import_folders.UpdateRow( index, ( name, path, pretty_check_period, pretty_tag ), ( name, path, check_period, tag ) )
self._import_folders.UpdateRow( index, ( name, path, pretty_check_period ), ( name, path, check_period ) )
self._names_to_import_folders[ name ] = import_folder
@ -2697,7 +2672,7 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
names_to_save = set()
for ( name, path, check_period, tag ) in client_data:
for ( name, path, check_period ) in client_data:
names_to_save.add( name )
@ -2729,7 +2704,7 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
self._import_folder = import_folder
( name, path, mimes, import_file_options, actions, action_locations, period, open_popup, tag, paused ) = self._import_folder.ToTuple()
( name, path, mimes, import_file_options, import_tag_options, actions, action_locations, period, open_popup, paused ) = self._import_folder.ToTuple()
self._folder_box = ClientGUICommon.StaticBox( self, 'folder options' )
@ -2739,10 +2714,7 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
self._open_popup = wx.CheckBox( self._folder_box )
self._tag = wx.TextCtrl( self._folder_box )
self._tag.SetToolTipString( 'add this tag on the local tag service to anything imported from the folder' )
self._period = wx.SpinCtrl( self._folder_box, min = 3, max = 1440 )
self._period = wx.SpinCtrl( self._folder_box, min = 3, max = 60 * 24 * 30 )
self._paused = wx.CheckBox( self._folder_box )
@ -2783,6 +2755,8 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
self._location_failed = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
self._import_file_options = ClientGUIOptionsPanels.OptionsPanelImportFiles( self._file_box )
self._import_tag_options = ClientGUIOptionsPanels.OptionsPanelTags( self._file_box )
self._import_tag_options.SetNamespaces( [] )
#
@ -2799,11 +2773,6 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
self._path.SetPath( path )
self._open_popup.SetValue( open_popup )
if tag is not None:
self._tag.SetValue( tag )
self._period.SetValue( period / 60 )
self._paused.SetValue( paused )
@ -2834,6 +2803,7 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
self._import_file_options.SetOptions( import_file_options )
self._import_tag_options.SetOptions( import_tag_options )
#
@ -2853,9 +2823,6 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
gridbox.AddF( wx.StaticText( self._folder_box, label = 'currently paused: '), CC.FLAGS_MIXED )
gridbox.AddF( self._paused, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._folder_box, label = 'local tag to add to all imported files: '), CC.FLAGS_MIXED )
gridbox.AddF( self._tag, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._folder_box, label = 'open a popup if new files imported: '), CC.FLAGS_MIXED )
gridbox.AddF( self._open_popup, CC.FLAGS_EXPAND_BOTH_WAYS )
@ -2892,6 +2859,7 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
self._file_box.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._file_box.AddF( self._import_file_options, CC.FLAGS_EXPAND_PERPENDICULAR )
self._file_box.AddF( self._import_tag_options, CC.FLAGS_EXPAND_PERPENDICULAR )
#
@ -3016,6 +2984,7 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
path = self._path.GetPath()
mimes = self._mimes.GetInfo()
import_file_options = self._import_file_options.GetOptions()
import_tag_options = self._import_tag_options.GetOptions()
actions = {}
action_locations = {}
@ -3047,16 +3016,9 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
period = self._period.GetValue() * 60
open_popup = self._open_popup.GetValue()
tag = self._tag.GetValue()
if tag == '':
tag = None
paused = self._paused.GetValue()
self._import_folder.SetTuple( name, path, mimes, import_file_options, actions, action_locations, period, open_popup, tag, paused )
self._import_folder.SetTuple( name, path, mimes, import_file_options, import_tag_options, actions, action_locations, period, open_popup, paused )
return self._import_folder
@ -3205,11 +3167,9 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
def EventEditNamespaceColour( self, event ):
result = self._namespace_colours.GetSelectedNamespaceColour()
results = self._namespace_colours.GetSelectedNamespaceColours()
if result is not None:
( namespace, colour ) = result
for ( namespace, colour ) in results:
colour_data = wx.ColourData()
@ -3495,6 +3455,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._processing_panel = ClientGUICommon.StaticBox( self, 'processing' )
self._idle_period = wx.SpinCtrl( self._idle_panel, min = 0, max = 1000 )
self._idle_mouse_period = wx.SpinCtrl( self._idle_panel, min = 0, max = 1000 )
self._idle_cpu_max = wx.SpinCtrl( self._idle_panel, min = 0, max = 100 )
self._idle_shutdown = ClientGUICommon.BetterChoice( self._idle_panel )
@ -3514,6 +3475,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
#
self._idle_period.SetValue( HC.options[ 'idle_period' ] / 60 )
self._idle_mouse_period.SetValue( HC.options[ 'idle_mouse_period' ] / 60 )
self._idle_cpu_max.SetValue( HC.options[ 'idle_cpu_max' ] )
self._idle_shutdown.SelectClientData( HC.options[ 'idle_shutdown' ] )
self._idle_shutdown_max_minutes.SetValue( HC.options[ 'idle_shutdown_max_minutes' ] )
@ -3528,9 +3490,12 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Minutes of inactivity until client is considered idle (0 for never): ' ), CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Minutes of general client inactivity until client is considered idle (0 for never): ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._idle_period, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Minutes of unmoving mouse until client is considered idle (0 for never): ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._idle_mouse_period, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Do not start a job if any CPU core has more than this percent usage: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._idle_cpu_max, CC.FLAGS_MIXED )
@ -3585,6 +3550,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
def UpdateOptions( self ):
HC.options[ 'idle_period' ] = 60 * self._idle_period.GetValue()
HC.options[ 'idle_mouse_period' ] = 60 * self._idle_mouse_period.GetValue()
HC.options[ 'idle_cpu_max' ] = self._idle_cpu_max.GetValue()
HC.options[ 'idle_shutdown' ] = self._idle_shutdown.GetChoice()
HC.options[ 'idle_shutdown_max_minutes' ] = self._idle_shutdown_max_minutes.GetValue()
@ -4533,7 +4499,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddF( wx.StaticText( self, label = 'MB memory reserved for preview cache: ' ), CC.FLAGS_MIXED )
gridbox.AddF( previews_sizer, CC.FLAGS_NONE )
gridbox.AddF( wx.StaticText( self, label = 'MB memory reserved for fullscreen cache: ' ), CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'MB memory reserved for media viewer cache: ' ), CC.FLAGS_MIXED )
gridbox.AddF( fullscreens_sizer, CC.FLAGS_NONE )
gridbox.AddF( wx.StaticText( self, label = 'Autocomplete character threshold: ' ), CC.FLAGS_MIXED )
@ -7207,48 +7173,37 @@ class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
def __init__( self, parent, service_key ):
def InitialiseControls():
choice_pairs = [ ( 'blacklist', True ), ( 'whitelist', False ) ]
self._blacklist = ClientGUICommon.RadioBox( self, 'type', choice_pairs )
self._tags = ClientGUICommon.ListBoxTagsCensorship( self )
self._tag_input = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
self._tag_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDownTag )
def PopulateControls():
( blacklist, tags ) = HydrusGlobals.client_controller.Read( 'tag_censorship', service_key )
if blacklist: self._blacklist.SetSelection( 0 )
else: self._blacklist.SetSelection( 1 )
for tag in tags: self._tags.AddTag( tag )
def ArrangeControls():
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._blacklist, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._tag_input, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
wx.Panel.__init__( self, parent )
self._service_key = service_key
InitialiseControls()
choice_pairs = [ ( 'blacklist', True ), ( 'whitelist', False ) ]
PopulateControls()
self._blacklist = ClientGUICommon.RadioBox( self, 'type', choice_pairs )
ArrangeControls()
self._tags = ClientGUICommon.ListBoxTagsCensorship( self )
self._tag_input = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
self._tag_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDownTag )
#
( blacklist, tags ) = HydrusGlobals.client_controller.Read( 'tag_censorship', service_key )
if blacklist: self._blacklist.SetSelection( 0 )
else: self._blacklist.SetSelection( 1 )
self._tags.AddTags( tags )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._blacklist, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._tag_input, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
def EventKeyDownTag( self, event ):
@ -7257,7 +7212,7 @@ class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
tag = self._tag_input.GetValue()
self._tags.AddTag( tag )
self._tags.EnterTags( { tag } )
self._tag_input.SetValue( '' )
@ -7413,7 +7368,7 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
class _Panel( wx.Panel ):
def __init__( self, parent, service_key, tag = None ):
def __init__( self, parent, service_key, tags = None ):
wx.Panel.__init__( self, parent )
@ -7439,13 +7394,13 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
self._tag_parents.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._tag_parents.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
removed_callable = lambda tag: 1
self._children = ClientGUICommon.ListBoxTagsStrings( self )
self._parents = ClientGUICommon.ListBoxTagsStrings( self )
self._children = ClientGUICommon.ListBoxTagsStrings( self, removed_callable )
self._parents = ClientGUICommon.ListBoxTagsStrings( self, removed_callable )
expand_parents = True
self._child_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddChild, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._parent_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddParent, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._child_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.EnterChildren, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._parent_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.EnterParents, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAddButton )
@ -7475,7 +7430,10 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
self._tag_parents.SortListItems( 2 )
if tag is not None: self.AddChild( tag )
if tags is not None:
self.EnterChildren( tags )
#
@ -7737,29 +7695,25 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
else: self._add.Enable()
def AddChild( self, tag, parents = None ):
def EnterChildren( self, tags, parents = None ):
if parents is None: parents = []
if tag is not None:
if len( tags ) > 0:
if tag in self._parents.GetTags(): self._parents.AddTag( tag )
self._parents.RemoveTags( tags )
self._children.AddTag( tag )
self._children.AddTags( tags )
self._SetButtonStatus()
def AddParent( self, tag, parents = None ):
def EnterParents( self, tags, parents = None ):
if parents is None: parents = []
if tag is not None:
if len( tags ) > 0:
if tag in self._children.GetTags(): self._children.AddTag( tag )
self._children.RemoveTags( tags )
self._parents.AddTag( tag )
self._parents.AddTags( tags )
self._SetButtonStatus()
@ -7767,15 +7721,23 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
def EventActivated( self, event ):
parents_to_children = collections.defaultdict( set )
all_selected = self._tag_parents.GetAllSelected()
if len( all_selected ) > 0:
selection = all_selected[0]
for selection in all_selected:
( status, child, parent ) = self._tag_parents.GetClientData( selection )
self._AddPairs( ( child, ), parent )
parents_to_children[ parent ].add( child )
if len( parents_to_children ) > 0:
for ( parent, children ) in parents_to_children.items():
self._AddPairs( children, parent )
@ -7843,73 +7805,64 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
def __init__( self, parent, tag = None ):
def InitialiseControls():
self._tag_repositories = ClientGUICommon.ListBook( self )
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
page = self._Panel( self._tag_repositories, CC.LOCAL_TAG_SERVICE_KEY, tag )
name = CC.LOCAL_TAG_SERVICE_KEY
self._tag_repositories.AddPage( name, page )
services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.TAG_REPOSITORY, ) )
for service in services:
account = service.GetInfo( 'account' )
if account.HasPermission( HC.POST_DATA ) or account.IsUnknownAccount():
name = service.GetName()
service_key = service.GetServiceKey()
self._tag_repositories.AddPageArgs( name, self._Panel, ( self._tag_repositories, service_key, tag ), {} )
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
service = HydrusGlobals.client_controller.GetServicesManager().GetService( default_tag_repository_key )
self._tag_repositories.Select( service.GetName() )
def ArrangeControls():
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_repositories, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
self.SetInitialSize( ( 550, 780 ) )
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag siblings' )
InitialiseControls()
self._tag_repositories = ClientGUICommon.ListBook( self )
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
PopulateControls()
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
ArrangeControls()
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
page = self._Panel( self._tag_repositories, CC.LOCAL_TAG_SERVICE_KEY, tag )
name = CC.LOCAL_TAG_SERVICE_KEY
self._tag_repositories.AddPage( name, page )
services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.TAG_REPOSITORY, ) )
for service in services:
account = service.GetInfo( 'account' )
if account.HasPermission( HC.POST_DATA ) or account.IsUnknownAccount():
name = service.GetName()
service_key = service.GetServiceKey()
self._tag_repositories.AddPageArgs( name, self._Panel, ( self._tag_repositories, service_key, tag ), {} )
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
service = HydrusGlobals.client_controller.GetServicesManager().GetService( default_tag_repository_key )
self._tag_repositories.Select( service.GetName() )
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_repositories, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
self.SetInitialSize( ( 550, 780 ) )
#
interested_actions = [ 'set_search_focus' ]
@ -7969,7 +7922,7 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
class _Panel( wx.Panel ):
def __init__( self, parent, service_key, tag = None ):
def __init__( self, parent, service_key, tags = None ):
wx.Panel.__init__( self, parent )
@ -7999,11 +7952,13 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
removed_callable = lambda tags: 1
self._old_siblings = ClientGUICommon.ListBoxTagsStrings( self, removed_callable )
self._old_siblings = ClientGUICommon.ListBoxTagsStrings( self )
self._new_sibling = wx.StaticText( self )
self._old_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddOld, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._new_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetNew, CC.LOCAL_FILE_SERVICE_KEY, service_key )
expand_parents = False
self._old_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.EnterOlds, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._new_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetNew, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAddButton )
@ -8033,7 +7988,10 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._tag_siblings.SortListItems( 2 )
if tag is not None: self.AddOld( tag )
if tags is not None:
self.EnterOlds( tags )
#
@ -8314,35 +8272,41 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
else: self._add.Enable()
def AddOld( self, old, parents = None ):
def EnterOlds( self, olds ):
if parents is None: parents = []
potential_olds = olds
if old is not None:
olds = set()
for potential_old in potential_olds:
do_it = True
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
# test for ambiguity
while old in current_olds:
while potential_old in current_olds:
olds_to_news = dict( current_pairs )
new = olds_to_news[ old ]
conflicting_new = olds_to_news[ potential_old ]
message = 'There already is a relationship set for ' + old + '! It goes to ' + new + '.'
message = 'There already is a relationship set for ' + potential_old + '! It goes to ' + conflicting_new + '.'
message += os.linesep * 2
message += 'You cannot have two siblings for the same original term.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'I want to overwrite the existing record', no_label = 'do nothing' ) as dlg:
if self._service_key != CC.LOCAL_TAG_SERVICE_KEY:
if dlg.ShowModal() == wx.ID_YES:
if dlg.ShowModal() != wx.ID_YES: return
self._AddPairs( [ potential_old ], conflicting_new )
self._AddPairs( [ old ] , new )
else:
do_it = False
break
@ -8351,30 +8315,41 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
if do_it:
olds.add( potential_old )
#
if self._current_new in olds:
self.SetNew( set() )
if old is not None:
if old == self._current_new: self.SetNew( None )
self._old_siblings.AddTag( old )
self._old_siblings.EnterTags( olds )
self._SetButtonStatus()
def EventActivated( self, event ):
news_to_olds = collections.defaultdict( set )
all_selected = self._tag_siblings.GetAllSelected()
if len( all_selected ) > 0:
selection = all_selected[0]
for selection in all_selected:
( status, old, new ) = self._tag_siblings.GetClientData( selection )
self._AddPairs( [ old ], new )
news_to_olds[ new ].add( old )
if len( news_to_olds ) > 0:
for ( new, olds ) in news_to_olds.items():
self._AddPairs( olds, new )
@ -8386,8 +8361,8 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._AddPairs( olds, self._current_new )
self._old_siblings.SetTags( [] )
self.SetNew( None )
self._old_siblings.SetTags( set() )
self.SetNew( set() )
self._SetButtonStatus()
@ -8435,19 +8410,24 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
return ( self._service_key, content_updates )
def SetNew( self, new, parents = None ):
def SetNew( self, new_tags ):
if parents is None: parents = []
if new is None: self._new_sibling.SetLabel( '' )
if len( new_tags ) == 0:
self._new_sibling.SetLabel( '' )
self._current_new = None
else:
if new in self._old_siblings.GetTags(): self._old_siblings.AddTag( new )
new = list( new_tags )[0]
self._old_siblings.RemoveTags( { new } )
self._new_sibling.SetLabel( new )
self._current_new = new
self._current_new = new
self._SetButtonStatus()
@ -8788,7 +8768,7 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
self._tags_box_sorter = ClientGUICommon.StaticBoxSorterForListBoxTags( self, 'tags' )
self._tags_box = ClientGUICommon.ListBoxTagsSelectionTagsDialog( self._tags_box_sorter, self.AddTag )
self._tags_box = ClientGUICommon.ListBoxTagsSelectionTagsDialog( self._tags_box_sorter, self.AddTags )
self._tags_box_sorter.SetTagsBox( self._tags_box )
@ -8797,7 +8777,9 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
self._tags_box_sorter.AddF( self._show_deleted_checkbox, CC.FLAGS_LONE_BUTTON )
self._add_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddTag, self._file_service_key, self._tag_service_key )
expand_parents = True
self._add_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddTags, expand_parents, self._file_service_key, self._tag_service_key, null_entry_callable = self.Ok )
self._modify_mappers = wx.Button( self, label = 'Modify mappers' )
self._modify_mappers.Bind( wx.EVT_BUTTON, self.EventModify )
@ -8972,16 +8954,24 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
self._tags_box.SetTagsByMedia( self._media, force_reload = True )
def AddTag( self, tag, parents = None ):
def AddTags( self, tags, parents = None ):
if parents is None: parents = []
if parents is None:
parents = []
if tag is None: wx.PostEvent( self, wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'ok' ) ) )
else:
if len( tags ) > 0:
self._AddTag( tag )
for tag in tags:
self._AddTag( tag )
for parent in parents: self._AddTag( parent, only_add = True )
for parent in parents:
self._AddTag( parent, only_add = True )
@ -8998,11 +8988,16 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
def EventModify( self, event ):
tag = self._tags_box.GetSelectedTag()
contents = []
if tag is not None:
tags = self._tags_box.GetSelectedTags()
for tag in tags:
contents = [ HydrusData.Content( HC.CONTENT_TYPE_MAPPING, ( tag, hash ) ) for hash in self._hashes ]
contents.extend( [ HydrusData.Content( HC.CONTENT_TYPE_MAPPING, ( tag, hash ) ) for hash in self._hashes ] )
if len( contents ) > 0:
subject_identifiers = [ HydrusData.AccountIdentifier( content = content ) for content in contents ]
@ -9028,7 +9023,10 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
tags = HydrusTags.CleanTags( tags )
for tag in tags: self._AddTag( tag, only_add = True )
for tag in tags:
self._AddTag( tag, only_add = True )
except: wx.MessageBox( 'I could not understand what was in the clipboard' )
@ -9091,6 +9089,11 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
def HasChanges( self ): return len( self._content_updates ) > 0
def Ok( self ):
wx.PostEvent( self, wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'ok' ) ) )
def SetMedia( self, media ):
self._content_updates = []

View File

@ -644,7 +644,7 @@ class FullscreenHoverFrameTags( FullscreenHoverFrame ):
vbox = wx.BoxSizer( wx.VERTICAL )
self._tags = ClientGUICommon.ListBoxTags( self )
self._tags = ClientGUICommon.ListBoxTagsSelectionHoverFrame( self, self._canvas_key )
vbox.AddF( self._tags, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
@ -675,22 +675,10 @@ class FullscreenHoverFrameTags( FullscreenHoverFrame ):
def _ResetTags( self ):
if self._current_media is None: tags_i_want_to_display = []
else:
if self._current_media is not None:
tags_manager = self._current_media.GetTagsManager()
self._tags.SetTagsByMedia( [ self._current_media ], force_reload = True )
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
current = siblings_manager.CollapseTags( tags_manager.GetCurrent() )
pending = siblings_manager.CollapseTags( tags_manager.GetPending() )
tags_i_want_to_display = list( current.union( pending ) )
tags_i_want_to_display.sort()
self._tags.SetTexts( tags_i_want_to_display )
def ProcessContentUpdates( self, service_keys_to_content_updates ):
@ -727,13 +715,4 @@ class FullscreenHoverFrameTags( FullscreenHoverFrame ):
self._ResetTags()

View File

@ -2542,7 +2542,6 @@ class ManagementPanelQuery( ManagementPanel ):
if len( initial_predicates ) > 0 and not file_search_context.IsComplete(): wx.CallAfter( self._DoQuery )
HydrusGlobals.client_controller.sub( self, 'AddMediaResultsFromQuery', 'add_media_results_from_query' )
HydrusGlobals.client_controller.sub( self, 'AddPredicate', 'add_predicate' )
HydrusGlobals.client_controller.sub( self, 'ChangeFileRepositoryPubsub', 'change_file_repository' )
HydrusGlobals.client_controller.sub( self, 'ChangeTagRepositoryPubsub', 'change_tag_repository' )
HydrusGlobals.client_controller.sub( self, 'IncludeCurrent', 'notify_include_current' )
@ -2550,7 +2549,6 @@ class ManagementPanelQuery( ManagementPanel ):
HydrusGlobals.client_controller.sub( self, 'SearchImmediately', 'notify_search_immediately' )
HydrusGlobals.client_controller.sub( self, 'ShowQuery', 'file_query_done' )
HydrusGlobals.client_controller.sub( self, 'RefreshQuery', 'refresh_query' )
HydrusGlobals.client_controller.sub( self, 'RemovePredicate', 'remove_predicate' )
def _DoQuery( self ):
@ -2599,39 +2597,6 @@ class ManagementPanelQuery( ManagementPanel ):
if query_key == self._query_key: HydrusGlobals.client_controller.pub( 'add_media_results', self._page_key, media_results, append = False )
def AddPredicate( self, page_key, predicate ):
if self._controller.GetVariable( 'search_enabled' ) and page_key == self._page_key:
if predicate is not None:
( predicate_type, value, inclusive ) = predicate.GetInfo()
if predicate_type in [ HC.PREDICATE_TYPE_SYSTEM_NUM_TAGS, HC.PREDICATE_TYPE_SYSTEM_LIMIT, HC.PREDICATE_TYPE_SYSTEM_SIZE, HC.PREDICATE_TYPE_SYSTEM_DIMENSIONS, HC.PREDICATE_TYPE_SYSTEM_AGE, HC.PREDICATE_TYPE_SYSTEM_HASH, HC.PREDICATE_TYPE_SYSTEM_DURATION, HC.PREDICATE_TYPE_SYSTEM_NUM_WORDS, HC.PREDICATE_TYPE_SYSTEM_MIME, HC.PREDICATE_TYPE_SYSTEM_RATING, HC.PREDICATE_TYPE_SYSTEM_SIMILAR_TO, HC.PREDICATE_TYPE_SYSTEM_FILE_SERVICE ]:
with ClientGUIDialogs.DialogInputFileSystemPredicates( self, predicate_type ) as dlg:
if dlg.ShowModal() == wx.ID_OK: predicates = dlg.GetPredicates()
else: return
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_UNTAGGED: predicates = ( ClientData.Predicate( HC.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( '=', 0 ) ), )
else:
predicates = ( predicate, )
for predicate in predicates:
if self._current_predicates_box.HasPredicate( predicate ): self._current_predicates_box.RemovePredicate( predicate )
else: self._current_predicates_box.AddPredicate( predicate )
self._DoQuery()
def ChangeFileRepositoryPubsub( self, page_key, service_key ):
if page_key == self._page_key:
@ -2690,19 +2655,6 @@ class ManagementPanelQuery( ManagementPanel ):
if page_key == self._page_key: self._DoQuery()
def RemovePredicate( self, page_key, predicate ):
if page_key == self._page_key:
if self._current_predicates_box.HasPredicate( predicate ):
self._current_predicates_box.RemovePredicate( predicate )
self._DoQuery()
def SearchImmediately( self, page_key, value ):
if page_key == self._page_key:

View File

@ -453,12 +453,12 @@ class OptionsPanelTags( OptionsPanel ):
def SetNamespaces( self, namespaces ):
self._vbox.Clear( True )
self._service_keys_to_checkbox_info = {}
self._service_keys_to_explicit_button_info = {}
self._button_ids_to_service_keys = {}
self._vbox.Clear( True )
services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
button_id = 1
@ -505,12 +505,12 @@ class OptionsPanelTags( OptionsPanel ):
button_id += 1
vbox.AddF( explicit_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( explicit_button, CC.FLAGS_MIXED )
outer_gridbox.AddF( vbox, CC.FLAGS_MIXED )
outer_gridbox.AddF( vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self._vbox.AddF( outer_gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._vbox.AddF( outer_gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )

View File

@ -231,7 +231,7 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
HydrusGlobals.client_controller.pub( 'add_media_results', page_key, ( media_result, ) )
except Exception as e:
except Exception:
error_text = traceback.format_exc()
@ -353,13 +353,22 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
except Exception as e:
traceback.print_exc()
if isinstance( e, HydrusExceptions.NotFoundException ):
text = 'Gallery 404'
else:
text = str( e )
traceback.print_exc()
with self._lock:
self._current_gallery_stream_identifier = None
self._SetGalleryStatus( page_key, str( e ) )
self._SetGalleryStatus( page_key, text )
time.sleep( 5 )
@ -774,9 +783,9 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER
SERIALISABLE_VERSION = 1
SERIALISABLE_VERSION = 2
def __init__( self, name, path = '', import_file_options = None, mimes = None, actions = None, action_locations = None, period = 3600, open_popup = True, tag = None ):
def __init__( self, name, path = '', import_file_options = None, import_tag_options = None, mimes = None, actions = None, action_locations = None, period = 3600, open_popup = True ):
if mimes is None:
@ -788,6 +797,13 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
import_file_options = ClientDefaults.GetDefaultImportFileOptions()
if import_tag_options is None:
new_options = HydrusGlobals.client_controller.GetNewOptions()
import_tag_options = new_options.GetDefaultImportTagOptions( ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_DEFAULT ) )
if actions is None:
actions = {}
@ -808,11 +824,11 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
self._path = path
self._mimes = mimes
self._import_file_options = import_file_options
self._import_tag_options = import_tag_options
self._actions = actions
self._action_locations = action_locations
self._period = period
self._open_popup = open_popup
self._tag = tag
self._path_cache = SeedCache()
self._last_checked = 0
@ -914,26 +930,51 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
def _GetSerialisableInfo( self ):
serialisable_import_file_options = self._import_file_options.GetSerialisableTuple()
serialisable_import_tag_options = self._import_tag_options.GetSerialisableTuple()
serialisable_path_cache = self._path_cache.GetSerialisableTuple()
# json turns int dict keys to strings
action_pairs = self._actions.items()
action_location_pairs = self._action_locations.items()
return ( self._path, self._mimes, serialisable_import_file_options, action_pairs, action_location_pairs, self._period, self._open_popup, self._tag, serialisable_path_cache, self._last_checked, self._paused )
return ( self._path, self._mimes, serialisable_import_file_options, serialisable_import_tag_options, action_pairs, action_location_pairs, self._period, self._open_popup, serialisable_path_cache, self._last_checked, self._paused )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self._path, self._mimes, serialisable_import_file_options, action_pairs, action_location_pairs, self._period, self._open_popup, self._tag, serialisable_path_cache, self._last_checked, self._paused ) = serialisable_info
( self._path, self._mimes, serialisable_import_file_options, serialisable_import_tag_options, action_pairs, action_location_pairs, self._period, self._open_popup, serialisable_path_cache, self._last_checked, self._paused ) = serialisable_info
self._actions = dict( action_pairs )
self._action_locations = dict( action_location_pairs )
self._import_file_options = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_import_file_options )
self._import_tag_options = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_import_tag_options )
self._path_cache = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_path_cache )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
( path, mimes, serialisable_import_file_options, action_pairs, action_location_pairs, period, open_popup, tag, serialisable_path_cache, last_checked, paused ) = old_serialisable_info
service_keys_to_explicit_tags = {}
if tag is not None:
service_keys_to_explicit_tags[ CC.LOCAL_TAG_SERVICE_KEY ] = { tag }
import_tag_options = ClientData.ImportTagOptions( service_keys_to_explicit_tags = service_keys_to_explicit_tags )
serialisable_import_tag_options = import_tag_options.GetSerialisableTuple()
new_serialisable_info = ( path, mimes, serialisable_import_file_options, serialisable_import_tag_options, action_pairs, action_location_pairs, period, open_popup, serialisable_path_cache, last_checked, paused )
return ( 2, new_serialisable_info )
def DoWork( self ):
if HydrusGlobals.view_shutdown:
@ -976,22 +1017,13 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
if mime in self._mimes:
if self._tag is None:
service_keys_to_tags = {}
else:
service_keys_to_tags = { CC.LOCAL_TAG_SERVICE_KEY : [ self._tag ] }
( status, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', path, import_file_options = self._import_file_options )
self._path_cache.UpdateSeedStatus( path, status )
if status in ( CC.STATUS_SUCCESSFUL, CC.STATUS_REDUNDANT ):
service_keys_to_content_updates = ClientData.ConvertServiceKeysToTagsToServiceKeysToContentUpdates( { hash }, service_keys_to_tags )
service_keys_to_content_updates = self._import_tag_options.GetServiceKeysToContentUpdates( hash, set() )
if len( service_keys_to_content_updates ) > 0:
@ -1009,7 +1041,7 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
self._path_cache.UpdateSeedStatus( path, CC.STATUS_UNINTERESTING_MIME )
except Exception as e:
except Exception:
error_text = traceback.format_exc()
@ -1046,15 +1078,15 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
def ToListBoxTuple( self ):
return ( self._name, self._path, self._period, self._tag )
return ( self._name, self._path, self._period )
def ToTuple( self ):
return ( self._name, self._path, self._mimes, self._import_file_options, self._actions, self._action_locations, self._period, self._open_popup, self._tag, self._paused )
return ( self._name, self._path, self._mimes, self._import_file_options, self._import_tag_options, self._actions, self._action_locations, self._period, self._open_popup, self._paused )
def SetTuple( self, name, path, mimes, import_file_options, actions, action_locations, period, open_popup, tag, paused ):
def SetTuple( self, name, path, mimes, import_file_options, import_tag_options, actions, action_locations, period, open_popup, paused ):
if path != self._path:
@ -1070,11 +1102,11 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
self._path = path
self._mimes = mimes
self._import_file_options = import_file_options
self._import_tag_options
self._actions = actions
self._action_locations = action_locations
self._period = period
self._open_popup = open_popup
self._tag = tag
self._paused = paused
@ -1203,7 +1235,7 @@ class PageOfImagesImport( HydrusSerialisable.SerialisableBase ):
HydrusGlobals.client_controller.pub( 'add_media_results', page_key, ( media_result, ) )
except Exception as e:
except Exception:
error_text = traceback.format_exc()
print( error_text )
@ -1824,8 +1856,6 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
self._gallery_stream_identifiers = ClientDownloading.GetGalleryStreamIdentifiers( self._gallery_identifier )
( namespaces, search_value ) = ClientDefaults.GetDefaultNamespacesAndSearchValue( self._gallery_identifier )
self._query = ''
self._period = 86400 * 7
self._get_tags_if_redundant = False
@ -2151,7 +2181,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
if error_next_check_time > periodic_next_check_time and not HydrusData.TimeHasPassed( error_next_check_time ):
interim_text = ' | due to error + ' + HydrusData.ConvertTimestampToPrettySync( self._last_error ) + ', next check '
interim_text = ' | due to error ' + HydrusData.ConvertTimestampToPrettySync( self._last_error ) + ', next check '
next_check_time = error_next_check_time
else:

View File

@ -1,5 +1,4 @@
import ClientFiles
import cv2
import HydrusConstants as HC
import HydrusData
import HydrusExceptions

View File

@ -37,7 +37,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 178
SOFTWARE_VERSION = 179
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -82,7 +82,7 @@ class HydrusDB( object ):
def _AnalyzeAfterUpdate( self ):
print( 'Analysing db after update.' )
print( 'Analyzing db after update...' )
self._c.execute( 'ANALYZE' )

View File

@ -300,7 +300,11 @@ def LaunchDirectory( path ):
threading.Thread( target = do_it ).start()
thread = threading.Thread( target = do_it )
thread.daemon = True
thread.start()
def LaunchFile( path ):
@ -325,5 +329,9 @@ def LaunchFile( path ):
threading.Thread( target = do_it ).start()
thread = threading.Thread( target = do_it )
thread.daemon = True
thread.start()

View File

@ -11,4 +11,5 @@ is_first_start = False
is_db_updated = False
db_profile_mode = False
special_debug_mode = False
server_busy = False

View File

@ -18,8 +18,14 @@ import HydrusData
import HydrusFileHandling
import HydrusGlobals
#LINEAR_SCALE_PALETTE = [ 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 207, 207, 207, 208, 208, 208, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, 235, 235, 235, 236, 236, 236, 237, 237, 237, 238, 238, 238, 239, 239, 239, 240, 240, 240, 241, 241, 241, 242, 242, 242, 243, 243, 243, 244, 244, 244, 245, 245, 245, 246, 246, 246, 247, 247, 247, 248, 248, 248, 249, 249, 249, 250, 250, 250, 251, 251, 251, 252, 252, 252, 253, 253, 253, 254, 254, 254, 255, 255, 255 ]
if cv2.__version__.startswith( '2' ):
IMREAD_UNCHANGED = cv2.CV_LOAD_IMAGE_UNCHANGED
else:
IMREAD_UNCHANGED = cv2.IMREAD_UNCHANGED
def ConvertToPngIfBmp( path ):
with open( path, 'rb' ) as f: header = f.read( 2 )
@ -127,7 +133,7 @@ def GenerateNumPyImageFromPILImage( pil_image ):
def GeneratePerceptualHash( path ):
numpy_image = cv2.imread( path, cv2.CV_LOAD_IMAGE_UNCHANGED )
numpy_image = cv2.imread( path, IMREAD_UNCHANGED )
( y, x, depth ) = numpy_image.shape

View File

@ -21,13 +21,31 @@ if HC.PLATFORM_LINUX: FFMPEG_PATH = '' + HC.BIN_DIR + os.path.sep + 'ffmpeg'
elif HC.PLATFORM_OSX: FFMPEG_PATH = '' + HC.BIN_DIR + os.path.sep + 'ffmpeg'
elif HC.PLATFORM_WINDOWS: FFMPEG_PATH = '' + HC.BIN_DIR + os.path.sep + 'ffmpeg.exe'
if cv2.__version__.startswith( '2' ):
CAP_PROP_FRAME_COUNT = cv2.cv.CV_CAP_PROP_FRAME_COUNT
CAP_PROP_FPS = cv2.cv.CV_CAP_PROP_FPS
CAP_PROP_FRAME_WIDTH = cv2.cv.CV_CAP_PROP_FRAME_WIDTH
CAP_PROP_FRAME_HEIGHT = cv2.cv.CV_CAP_PROP_FRAME_HEIGHT
CAP_PROP_CONVERT_RGB = cv2.cv.CV_CAP_PROP_CONVERT_RGB
CAP_PROP_POS_FRAMES = cv2.cv.CV_CAP_PROP_POS_FRAMES
else:
CAP_PROP_FRAME_COUNT = cv2.CAP_PROP_FRAME_COUNT
CAP_PROP_FPS = cv2.CAP_PROP_FPS
CAP_PROP_FRAME_WIDTH = cv2.CAP_PROP_FRAME_WIDTH
CAP_PROP_FRAME_HEIGHT = cv2.CAP_PROP_FRAME_HEIGHT
CAP_PROP_CONVERT_RGB = cv2.CAP_PROP_CONVERT_RGB
CAP_PROP_POS_FRAMES = cv2.CAP_PROP_POS_FRAMES
def GetCVVideoProperties( path ):
capture = cv2.VideoCapture( path )
num_frames = int( capture.get( cv2.cv.CV_CAP_PROP_FRAME_COUNT ) )
num_frames = int( capture.get( CAP_PROP_FRAME_COUNT ) )
fps = capture.get( cv2.cv.CV_CAP_PROP_FPS )
fps = capture.get( CAP_PROP_FPS )
length_in_seconds = num_frames / fps
@ -35,9 +53,9 @@ def GetCVVideoProperties( path ):
duration = length_in_ms
width = int( capture.get( cv2.cv.CV_CAP_PROP_FRAME_WIDTH ) )
width = int( capture.get( CAP_PROP_FRAME_WIDTH ) )
height = int( capture.get( cv2.cv.CV_CAP_PROP_FRAME_HEIGHT ) )
height = int( capture.get( CAP_PROP_FRAME_HEIGHT ) )
return ( ( width, height ), duration, num_frames )
@ -98,7 +116,7 @@ def GetVideoFrameDuration( path ):
cv_video = cv2.VideoCapture( path )
fps = cv_video.get( cv2.cv.CV_CAP_PROP_FPS )
fps = cv_video.get( CAP_PROP_FPS )
if fps == 0: raise HydrusExceptions.CantRenderWithCVException()
@ -487,7 +505,7 @@ class GIFRenderer( object ):
self._cv_video = cv2.VideoCapture( self._path )
self._cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
self._cv_video.set( CAP_PROP_CONVERT_RGB, True )
self._next_render_index = 0
self._last_frame = None
@ -560,7 +578,7 @@ class GIFRenderer( object ):
self._cv_video.release()
self._cv_video.open( self._path )
#self._cv_video.set( cv2.cv.CV_CAP_PROP_POS_FRAMES, 0.0 )
#self._cv_video.set( CAP_PROP_POS_FRAMES, 0.0 )
else:
@ -579,6 +597,6 @@ class GIFRenderer( object ):
while self._next_render_index < index: self._GetCurrentFrame()
#self._cv_video.set( cv2.cv.CV_CAP_PROP_POS_FRAMES, index )
#self._cv_video.set( CV_CAP_PROP_POS_FRAMES, index )

View File

@ -30,7 +30,7 @@ class TestDaemons( unittest.TestCase ):
#
import_folder = ClientImporting.ImportFolder( 'imp', path = test_dir, tag = 'local tag' )
import_folder = ClientImporting.ImportFolder( 'imp', path = test_dir )
HydrusGlobals.test_controller.SetRead( 'serialisable_named', [ import_folder ] )

View File

@ -698,7 +698,7 @@ class TestClientDB( unittest.TestCase ):
def test_import_folders( self ):
import_folder_1 = ClientImporting.ImportFolder( 'imp 1', path = HC.DB_DIR, mimes = HC.VIDEO, open_popup = False )
import_folder_2 = ClientImporting.ImportFolder( 'imp 2', path = HC.DB_DIR, mimes = HC.IMAGES, period = 1200, open_popup = False, tag = 'test' )
import_folder_2 = ClientImporting.ImportFolder( 'imp 2', path = HC.DB_DIR, mimes = HC.IMAGES, period = 1200, open_popup = False )
#