Version 231

This commit is contained in:
Hydrus Network Developer 2016-11-09 17:13:22 -06:00
parent fc7605c13a
commit ab1bd6d208
22 changed files with 758 additions and 334 deletions

View File

@ -66,9 +66,7 @@ try:
#
log_path = os.path.join( db_dir, 'client.log' )
with HydrusLogger.HydrusLogger( log_path ) as logger:
with HydrusLogger.HydrusLogger( db_dir, 'client' ) as logger:
try:

View File

@ -66,9 +66,7 @@ try:
#
log_path = os.path.join( db_dir, 'client.log' )
with HydrusLogger.HydrusLogger( log_path ) as logger:
with HydrusLogger.HydrusLogger( db_dir, 'client' ) as logger:
try:

View File

@ -8,6 +8,26 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 231</h3></li>
<ul>
<li>added file lookup scripts suggested tags control and appropriate options panel</li>
<li>added final bit of gui->filelookup script->content update pipeline conversion and tie-in code</li>
<li>moved all the suggested tag options into a notebook</li>
<li>added notebook layout for suggested tag control columns. it is now the default</li>
<li>suggested tag columns now have a unified set width</li>
<li>the parsing ui's add script and node buttons now spawn menus rather than the awkward listofstrings dialog</li>
<li>added 'iqdb danbooru' file lookup script. it hangs the ui pretty bad atm, but that will change in future</li>
<li>fixed a critical pyinstaller problem with the os x builds</li>
<li>log files will now be appending with 'year-month', and will roll over to a new file as the month turns</li>
<li>help->about dialog now has some library version information</li>
<li>converted more menu stuff to the new system</li>
<li>wrote the guts of a new png-based object sharing system</li>
<li>dropdown choices are more resistant to missing init and invalid defaults</li>
<li>tweaked some disk cache maintenance timing</li>
<li>fixed a multi-version update bug regarding external thumbnail paths not being initalised</li>
<li>misc cleanup</li>
<li>misc cleanup, improvements</li>
</ul>
<li><h3>version 230</h3></li>
<ul>
<li>all multiline text ctrls now support ctrl+a for select all, wew</li>

View File

@ -1,7 +1,10 @@
import collections
import cv2
import HydrusConstants as HC
import HydrusExceptions
import os
import PIL
import ssl
import sys
import threading
import traceback
@ -40,7 +43,22 @@ Shift-LeftClick-Drag - Drag (in Filter)
Ctrl + MouseWheel - Zoom
Z - Zoom Full/Fit'''
CLIENT_DESCRIPTION = '''This client is the media management application of the hydrus software suite.'''
library_versions = []
library_versions.append( ( 'openssl', ssl.OPENSSL_VERSION ) )
library_versions.append( ( 'PIL', PIL.VERSION ) )
if hasattr( PIL, 'PILLOW_VERSION' ):
library_versions.append( ( 'Pillow', PIL.PILLOW_VERSION ) )
library_versions.append( ( 'OpenCV', cv2.__version__ ) )
library_versions.append( ( 'wx', wx.version() ) )
CLIENT_DESCRIPTION = 'This client is the media management application of the hydrus software suite.'
CLIENT_DESCRIPTION += os.linesep * 2 + os.linesep.join( ( lib + ': ' + version for ( lib, version ) in library_versions ) )
COLLECT_BY_S = 0
COLLECT_BY_SV = 1

View File

@ -650,7 +650,7 @@ class Controller( HydrusController.HydrusController ):
if not self.CurrentlyVeryIdle():
stop_time = HydrusData.GetNow() + 30
stop_time = HydrusData.GetNow() + 10

View File

@ -5362,6 +5362,11 @@ class DB( HydrusDB.HydrusDB ):
def _LoadIntoDiskCache( self, stop_time = None, caller_limit = None ):
if stop_time is None:
stop_time = HydrusData.GetNow() + 5
self._CloseDBCursor()
try:
@ -7874,7 +7879,12 @@ class DB( HydrusDB.HydrusDB ):
if version == 229:
self._c.executemany( 'INSERT INTO json_dumps_named VALUES ( ?, ?, ?, ? );', [ ( 32, 'gelbooru md5', 1, '''["http://gelbooru.com/index.php", 0, 1, 1, "md5", {"s": "list", "page": "post"}, [[30, 1, ["we got sent back to main gallery page -- title test", 8, [27, 1, [[["head", {}, 0], ["title", {}, 0]], null]], [true, true, "Image List"]]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-general"}, null], ["a", {}, 1]], null]], ""]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-copyright"}, null], ["a", {}, 1]], null]], "series"]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-artist"}, null], ["a", {}, 1]], null]], "creator"]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-character"}, null], ["a", {}, 1]], null]], "character"]], [30, 1, ["we got sent back to main gallery page -- page links exist", 8, [27, 1, [[["div", {}, null]], "class"]], [true, true, "pagination"]]]]]''' ) ] )
self._c.executemany( 'INSERT OR IGNORE INTO json_dumps_named VALUES ( ?, ?, ?, ? );', [ ( 32, 'gelbooru md5', 1, '''["http://gelbooru.com/index.php", 0, 1, 1, "md5", {"s": "list", "page": "post"}, [[30, 1, ["we got sent back to main gallery page -- title test", 8, [27, 1, [[["head", {}, 0], ["title", {}, 0]], null]], [true, true, "Image List"]]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-general"}, null], ["a", {}, 1]], null]], ""]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-copyright"}, null], ["a", {}, 1]], null]], "series"]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-artist"}, null], ["a", {}, 1]], null]], "creator"]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-character"}, null], ["a", {}, 1]], null]], "character"]], [30, 1, ["we got sent back to main gallery page -- page links exist", 8, [27, 1, [[["div", {}, null]], "class"]], [true, true, "pagination"]]]]]''' ) ] )
if version == 230:
self._c.executemany( 'INSERT OR IGNORE INTO json_dumps_named VALUES ( ?, ?, ?, ? );', [ ( 32, 'iqdb danbooru', 1, '''["http://danbooru.iqdb.org/", 1, 0, 0, "file", {}, [[29, 1, ["link to danbooru", [27, 1, [[["td", {"class": "image"}, 1], ["a", {}, 0]], "href"]], [[30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-1"}, null], ["a", {"class": "search-tag"}, 0]], null]], "creator"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-3"}, null], ["a", {"class": "search-tag"}, 0]], null]], "series"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-4"}, null], ["a", {"class": "search-tag"}, 0]], null]], "character"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-0"}, null], ["a", {"class": "search-tag"}, 0]], null]], ""]]]]], [30, 1, ["no iqdb match found", 8, [27, 1, [[["th", {}, null]], null]], [false, true, "Best match"]]]]]''' ) ] )
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )

View File

@ -5,7 +5,6 @@ import ClientFiles
import ClientNetworking
import ClientThreading
import collections
import datetime
import HydrusConstants as HC
import HydrusExceptions
import HydrusFileHandling
@ -536,6 +535,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'replace_siblings_on_manage_tags' ] = True
self._dictionary[ 'booleans' ][ 'show_related_tags' ] = False
self._dictionary[ 'booleans' ][ 'show_file_lookup_script_tags' ] = False
self._dictionary[ 'booleans' ][ 'hide_message_manager_on_gui_iconise' ] = HC.PLATFORM_OSX
self._dictionary[ 'booleans' ][ 'hide_message_manager_on_gui_deactive' ] = False
@ -547,11 +547,12 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'integers' ][ 'video_buffer_size_mb' ] = 96
self._dictionary[ 'integers' ][ 'related_tags_width' ] = 150
self._dictionary[ 'integers' ][ 'related_tags_search_1_duration_ms' ] = 250
self._dictionary[ 'integers' ][ 'related_tags_search_2_duration_ms' ] = 2000
self._dictionary[ 'integers' ][ 'related_tags_search_3_duration_ms' ] = 6000
self._dictionary[ 'integers' ][ 'suggested_tags_width' ] = 300
#
self._dictionary[ 'keys' ] = {}
@ -567,14 +568,19 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'noneable_integers' ][ 'disk_cache_maintenance_mb' ] = 256
self._dictionary[ 'noneable_integers' ][ 'disk_cache_init_period' ] = 4
self._dictionary[ 'noneable_integers' ][ 'suggested_tags_width' ] = None
self._dictionary[ 'noneable_integers' ][ 'num_recent_tags' ] = None
self._dictionary[ 'noneable_integers' ][ 'maintenance_vacuum_period_days' ] = 30
#
self._dictionary[ 'noneable_strings' ] = {}
self._dictionary[ 'noneable_strings' ][ 'favourite_file_lookup_script' ] = 'gelbooru md5'
self._dictionary[ 'noneable_strings' ][ 'suggested_tags_layout' ] = 'notebook'
#
client_files_default = os.path.join( db_dir, 'client_files' )
self._dictionary[ 'client_files_locations_ideal_weights' ] = [ ( HydrusPaths.ConvertAbsPathToPortablePath( client_files_default ), 1.0 ) ]
@ -752,20 +758,29 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
updated_cfliw = []
for ( old_portable_path, weight ) in loaded_dictionary[ 'client_files_locations_ideal_weights' ]:
if 'client_files_locations_ideal_weights' in loaded_dictionary:
new_portable_path = update_portable_path( old_portable_path )
updated_cfliw = []
updated_cfliw.append( ( new_portable_path, weight ) )
for ( old_portable_path, weight ) in loaded_dictionary[ 'client_files_locations_ideal_weights' ]:
new_portable_path = update_portable_path( old_portable_path )
updated_cfliw.append( ( new_portable_path, weight ) )
loaded_dictionary[ 'client_files_locations_ideal_weights' ] = updated_cfliw
loaded_dictionary[ 'client_files_locations_ideal_weights' ] = updated_cfliw
if 'client_files_locations_resized_thumbnail_override' in loaded_dictionary:
loaded_dictionary[ 'client_files_locations_resized_thumbnail_override' ] = update_portable_path( loaded_dictionary[ 'client_files_locations_resized_thumbnail_override' ] )
loaded_dictionary[ 'client_files_locations_resized_thumbnail_override' ] = update_portable_path( loaded_dictionary[ 'client_files_locations_resized_thumbnail_override' ] )
loaded_dictionary[ 'client_files_locations_full_size_thumbnail_override' ] = update_portable_path( loaded_dictionary[ 'client_files_locations_full_size_thumbnail_override' ] )
if 'client_files_locations_full_size_thumbnail_override' in loaded_dictionary:
loaded_dictionary[ 'client_files_locations_full_size_thumbnail_override' ] = update_portable_path( loaded_dictionary[ 'client_files_locations_full_size_thumbnail_override' ] )
new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
@ -983,6 +998,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
def GetNoneableString( self, name ):
with self._lock:
return self._dictionary[ 'noneable_strings' ][ name ]
def GetPreviewShowAction( self, mime ):
with self._lock:
@ -1094,6 +1117,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
def SetNoneableString( self, name, value ):
with self._lock:
self._dictionary[ 'noneable_strings' ][ name ] = value
def SetSuggestedTagsFavourites( self, service_key, tags ):
with self._lock:

View File

@ -567,6 +567,7 @@ def GetDefaultScriptRows():
script_info = []
script_info.append( ( 32, 'gelbooru md5', 1, '''["http://gelbooru.com/index.php", 0, 1, 1, "md5", {"s": "list", "page": "post"}, [[30, 1, ["we got sent back to main gallery page -- title test", 8, [27, 1, [[["head", {}, 0], ["title", {}, 0]], null]], [true, true, "Image List"]]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-general"}, null], ["a", {}, 1]], null]], ""]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-copyright"}, null], ["a", {}, 1]], null]], "series"]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-artist"}, null], ["a", {}, 1]], null]], "creator"]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-character"}, null], ["a", {}, 1]], null]], "character"]], [30, 1, ["we got sent back to main gallery page -- page links exist", 8, [27, 1, [[["div", {}, null]], "class"]], [true, true, "pagination"]]]]]''' ) )
script_info.append( ( 32, 'iqdb danbooru', 1, '''["http://danbooru.iqdb.org/", 1, 0, 0, "file", {}, [[29, 1, ["link to danbooru", [27, 1, [[["td", {"class": "image"}, 1], ["a", {}, 0]], "href"]], [[30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-1"}, null], ["a", {"class": "search-tag"}, 0]], null]], "creator"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-3"}, null], ["a", {"class": "search-tag"}, 0]], null]], "series"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-4"}, null], ["a", {"class": "search-tag"}, 0]], null]], "character"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-0"}, null], ["a", {"class": "search-tag"}, 0]], null]], ""]]]]], [30, 1, ["no iqdb match found", 8, [27, 1, [[["th", {}, null]], null]], [false, true, "Best match"]]]]]''' ) )
return script_info

View File

@ -721,26 +721,29 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
def file():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'import_files' ), p( '&Import Files' ), p( 'Add new files to the database.' ) )
ClientGUIMenus.AppendMenuItem( menu, 'import files', 'Add new files to the database.', self, self._ImportFiles )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'manage_import_folders' ), p( 'Manage Import Folders' ), p( 'Manage folders from which the client can automatically import.' ) )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'manage_export_folders' ), p( 'Manage Export Folders' ), p( 'Manage folders to which the client can automatically export.' ) )
ClientGUIMenus.AppendMenuItem( menu, 'manage import folders', 'Manage folders from which the client can automatically import.', self, self._ManageImportFolders )
ClientGUIMenus.AppendMenuItem( menu, 'manage export folders', 'Manage folders to which the client can automatically export.', self, self._ManageExportFolders )
menu.AppendSeparator()
open = wx.Menu()
open.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'open_install_folder' ), p( 'Installation Directory' ), p( 'Open the installation directory for this client.' ) )
open.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'open_db_folder' ), p( 'Database Directory' ), p( 'Open the database directory for this instance of the client.' ) )
open.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'open_export_folder' ), p( 'Quick Export Directory' ), p( 'Open the export directory so you can easily access the files you have exported.' ) )
ClientGUIMenus.AppendMenuItem( open, 'installation directory', 'Open the installation directory for this client.', self, self._OpenInstallFolder )
ClientGUIMenus.AppendMenuItem( open, 'database directory', 'Open the database directory for this instance of the client.', self, self._OpenDBFolder )
ClientGUIMenus.AppendMenuItem( open, 'quick export directory', 'Open the export directory so you can easily access the files you have exported.', self, self._OpenExportFolder )
menu.AppendMenu( CC.ID_NULL, p( 'Open' ), open )
ClientGUIMenus.AppendMenu( menu, open, 'open' )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'options' ), p( '&Options' ) )
ClientGUIMenus.AppendMenuItem( menu, 'options', 'Change how the client operates.', self, self._ManageOptions )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'restart' ), p( '&Restart' ) )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'exit' ), p( '&Exit' ) )
ClientGUIMenus.AppendMenuItem( menu, 'restart', 'Shut the client down and then start it up again.', self, self.Exit, restart = True )
ClientGUIMenus.AppendMenuItem( menu, 'exit', 'Shut the client down.', self, self.Exit )
return ( menu, p( '&File' ), True )
@ -1188,8 +1191,6 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
pubsub_profile_mode_id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'pubsub_profile_mode' )
force_idle_mode_id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'force_idle_mode' )
no_focus_changed_id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'no_focus_changed' )
debug = wx.Menu()
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'debug_make_popups' ), p( 'Make Some Popups' ) )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'debug_make_a_delayed_popup' ), p( 'Make a Popup in Five Seconds' ) )
@ -1199,8 +1200,6 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
debug.Check( pubsub_profile_mode_id, HydrusGlobals.pubsub_profile_mode )
debug.AppendCheckItem( force_idle_mode_id, p( '&Force Idle Mode' ) )
debug.Check( force_idle_mode_id, HydrusGlobals.force_idle_mode )
debug.AppendCheckItem( no_focus_changed_id, p( '&No Focus Changed Mode' ) )
debug.Check( no_focus_changed_id, HydrusGlobals.no_focus_changed )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'debug_garbage' ), p( 'Garbage' ) )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'clear_caches' ), p( '&Clear Preview/Fullscreen Caches' ) )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'delete_service_info' ), p( '&Clear DB Service Info Cache' ), p( 'Delete all cached service info, in case it has become desynchronised.' ) )
@ -2439,10 +2438,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._controller.pub( 'notify_new_sessions' )
elif command == 'delete_service_info': self._DeleteServiceInfo()
elif command == 'exit':
self.Exit()
elif command == 'fetch_ip': self._FetchIP( data )
elif command == 'force_idle_mode':
@ -2452,7 +2447,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif command == 'help': webbrowser.open( 'file://' + HC.HELP_DIR + '/index.html' )
elif command == 'help_about': self._AboutWindow()
elif command == 'help_shortcuts': wx.MessageBox( CC.SHORTCUT_HELP )
elif command == 'import_files': self._ImportFiles()
elif command == 'load_gui_session': self._LoadGUISession( data )
elif command == 'load_into_disk_cache':
@ -2460,8 +2454,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif command == 'manage_account_types': self._ManageAccountTypes( data )
elif command == 'manage_boorus': self._ManageBoorus()
elif command == 'manage_export_folders': self._ManageExportFolders()
elif command == 'manage_import_folders': self._ManageImportFolders()
elif command == 'manage_parsing_scripts': self._ManageParsingScripts()
elif command == 'manage_pixiv_account': self._ManagePixivAccount()
elif command == 'manage_server_services': self._ManageServer( data )
@ -2491,14 +2483,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif command == 'new_page_query': self._NewPageQuery( data )
elif command == 'news': self._News( data )
elif command == 'no_focus_changed':
HydrusGlobals.no_focus_changed = not HydrusGlobals.no_focus_changed
elif command == 'open_db_folder': self._OpenDBFolder()
elif command == 'open_export_folder': self._OpenExportFolder()
elif command == 'open_install_folder': self._OpenInstallFolder()
elif command == 'options': self._ManageOptions()
elif command == 'patreon': webbrowser.open( 'https://www.patreon.com/hydrus_dev' )
elif command == 'pause_export_folders_sync': self._PauseSync( 'export_folders' )
elif command == 'pause_import_folders_sync': self._PauseSync( 'import_folders' )
@ -2517,10 +2501,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
if page is not None: page.RefreshQuery()
elif command == 'restart':
self.Exit( restart = True )
elif command == 'review_services': self._ReviewServices()
elif command == 'save_gui_session': self._SaveGUISession()
elif command == 'set_media_focus': self._SetMediaFocus()

View File

@ -1976,11 +1976,6 @@ class CanvasPanel( Canvas ):
def FocusChanged( self, page_key, media ):
if HydrusGlobals.no_focus_changed:
return
if HC.options[ 'hide_preview' ]:
return

View File

@ -207,7 +207,8 @@ class BetterChoice( wx.Choice ):
selection = self.GetSelection()
if selection != wx.NOT_FOUND: return self.GetClientData( selection )
else: return self.GetClientData( 0 )
elif self.GetCount() > 0: return self.GetClientData( 0 )
else: return None
def SelectClientData( self, client_data ):
@ -222,7 +223,10 @@ class BetterChoice( wx.Choice ):
self.Select( 0 )
if self.GetCount() > 0:
self.Select( 0 )
class BufferedWindow( wx.Window ):

View File

@ -1818,6 +1818,8 @@ class DialogInputLocalFiles( Dialog ):
if size == 0:
HydrusData.Print( 'Empty file: ' + path )
num_empty_files += 1
continue
@ -1833,6 +1835,8 @@ class DialogInputLocalFiles( Dialog ):
else:
HydrusData.Print( 'Unparsable file: ' + path )
num_uninteresting_mime_files += 1
continue
@ -1885,6 +1889,7 @@ class DialogInputLocalFiles( Dialog ):
message += '.'
HydrusData.Print( message )
wx.CallAfter( self.SetGaugeInfo, num_file_paths, num_file_paths, message )

View File

@ -1,6 +1,7 @@
import ClientConstants as CC
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIMenus
import ClientGUIScrolledPanels
import ClientGUITopLevelWindows
import ClientNetworking
@ -78,20 +79,15 @@ class EditHTMLFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
self._tag_rules = wx.ListBox( edit_panel, style = wx.LB_SINGLE )
self._tag_rules.Bind( wx.EVT_LEFT_DCLICK, self.EventEdit )
self._add_rule = wx.Button( edit_panel, label = 'add' )
self._add_rule.Bind( wx.EVT_BUTTON, self.EventAdd )
self._add_rule = ClientGUICommon.BetterButton( edit_panel, 'add', self.Add )
self._edit_rule = wx.Button( edit_panel, label = 'edit' )
self._edit_rule.Bind( wx.EVT_BUTTON, self.EventEdit )
self._edit_rule = ClientGUICommon.BetterButton( edit_panel, 'edit', self.Edit )
self._move_rule_up = wx.Button( edit_panel, label = u'\u2191' )
self._move_rule_up.Bind( wx.EVT_BUTTON, self.EventMoveUp )
self._move_rule_up = ClientGUICommon.BetterButton( edit_panel, u'\u2191', self.MoveUp )
self._delete_rule = wx.Button( edit_panel, label = 'X' )
self._delete_rule.Bind( wx.EVT_BUTTON, self.EventDelete )
self._delete_rule = ClientGUICommon.BetterButton( edit_panel, 'X', self.Delete )
self._move_rule_down = wx.Button( edit_panel, label = u'\u2193' )
self._move_rule_down.Bind( wx.EVT_BUTTON, self.EventMoveDown )
self._move_rule_down = ClientGUICommon.BetterButton( edit_panel, u'\u2193', self.MoveDown )
self._content_rule = wx.TextCtrl( edit_panel )
@ -107,8 +103,7 @@ class EditHTMLFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
self._example_data.SetValue( example_data )
self._run_test = wx.Button( test_panel, label = 'test parse' )
self._run_test.Bind( wx.EVT_BUTTON, self.EventTestParse )
self._run_test = ClientGUICommon.BetterButton( test_panel, 'test parse', self.TestParse )
self._results = ClientGUICommon.SaneMultilineTextCtrl( test_panel )
@ -217,7 +212,7 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
self.SetSizer( vbox )
def EventAdd( self, event ):
def Add( self ):
dlg_title = 'edit tag rule'
@ -240,7 +235,7 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
def EventDelete( self, event ):
def Delete( self ):
selection = self._tag_rules.GetSelection()
@ -257,7 +252,7 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
def EventEdit( self, event ):
def Edit( self ):
selection = self._tag_rules.GetSelection()
@ -286,7 +281,27 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
def EventMoveDown( self, event ):
def EventEdit( self, event ):
self.Edit()
def GetValue( self ):
tags_rules = [ self._tag_rules.GetClientData( i ) for i in range( self._tag_rules.GetCount() ) ]
content_rule = self._content_rule.GetValue()
if content_rule == '':
content_rule = None
formula = ClientParsing.ParseFormulaHTML( tags_rules, content_rule )
return formula
def MoveDown( self ):
selection = self._tag_rules.GetSelection()
@ -301,7 +316,7 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
def EventMoveUp( self, event ):
def MoveUp( self ):
selection = self._tag_rules.GetSelection()
@ -316,7 +331,7 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
def EventTestParse( self, event ):
def TestParse( self ):
formula = self.GetValue()
@ -342,21 +357,6 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
def GetValue( self ):
tags_rules = [ self._tag_rules.GetClientData( i ) for i in range( self._tag_rules.GetCount() ) ]
content_rule = self._content_rule.GetValue()
if content_rule == '':
content_rule = None
formula = ClientParsing.ParseFormulaHTML( tags_rules, content_rule )
return formula
class EditNodes( wx.Panel ):
def __init__( self, parent, nodes, referral_url_callable, example_data_callable ):
@ -368,23 +368,17 @@ class EditNodes( wx.Panel ):
self._nodes = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'name', 120 ), ( 'node type', 80 ), ( 'produces', -1 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit, use_display_tuple_for_sort = True )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
self._add_button = ClientGUICommon.BetterButton( self, 'add', self.Add )
self._copy_button = wx.Button( self, label = 'copy' )
self._copy_button.Bind( wx.EVT_BUTTON, self.EventCopy )
self._copy_button = ClientGUICommon.BetterButton( self, 'copy', self.Copy )
self._paste_button = wx.Button( self, label = 'paste' )
self._paste_button.Bind( wx.EVT_BUTTON, self.EventPaste )
self._paste_button = ClientGUICommon.BetterButton( self, 'paste', self.Paste )
self._duplicate_button = wx.Button( self, label = 'duplicate' )
self._duplicate_button.Bind( wx.EVT_BUTTON, self.EventDuplicate )
self._duplicate_button = ClientGUICommon.BetterButton( self, 'duplicate', self.Duplicate )
self._edit_button = wx.Button( self, label = 'edit' )
self._edit_button.Bind( wx.EVT_BUTTON, self.EventEdit )
self._edit_button = ClientGUICommon.BetterButton( self, 'edit', self.Edit )
self._delete_button = wx.Button( self, label = 'delete' )
self._delete_button.Bind( wx.EVT_BUTTON, self.EventDelete )
self._delete_button = ClientGUICommon.BetterButton( self, 'delete', self.Delete )
#
@ -423,47 +417,54 @@ class EditNodes( wx.Panel ):
def Add( self ):
with ClientGUIDialogs.DialogSelectFromListOfStrings( self, 'select the node type', [ 'content', 'link' ] ) as dlg_type:
menu = wx.Menu()
ClientGUIMenus.AppendMenuItem( menu, 'content node', 'A node that parses the given data for content.', self, self.AddContentNode )
ClientGUIMenus.AppendMenuItem( menu, 'link node', 'A node that parses the given data for a link, which it then pursues.', self, self.AddLinkNode )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def AddContentNode( self ):
dlg_title = 'edit content node'
empty_node = ClientParsing.ParseNodeContent()
panel_class = EditParseNodeContentPanel
self.AddNode( dlg_title, empty_node, panel_class )
def AddLinkNode( self ):
dlg_title = 'edit link node'
empty_node = ClientParsing.ParseNodeContentLink()
panel_class = EditParseNodeContentLinkPanel
self.AddNode( dlg_title, empty_node, panel_class )
def AddNode( self, dlg_title, empty_node, panel_class ):
with ClientGUITopLevelWindows.DialogEdit( self, dlg_title ) as dlg_edit:
if dlg_type.ShowModal() == wx.ID_OK:
referral_url = self._referral_url_callable()
example_data = self._example_data_callable()
panel = panel_class( dlg_edit, empty_node, referral_url, example_data )
dlg_edit.SetPanel( panel )
if dlg_edit.ShowModal() == wx.ID_OK:
node_type_string = dlg_type.GetString()
new_node = panel.GetValue()
if node_type_string == 'content':
dlg_title = 'edit content node'
empty_node = ClientParsing.ParseNodeContent()
panel_class = EditParseNodeContentPanel
elif node_type_string == 'link':
dlg_title = 'edit link node'
empty_node = ClientParsing.ParseNodeContentLink()
panel_class = EditParseNodeContentLinkPanel
( display_tuple, data_tuple ) = self._ConvertNodeToTuples( new_node )
with ClientGUITopLevelWindows.DialogEdit( self, dlg_title ) as dlg_edit:
referral_url = self._referral_url_callable()
example_data = self._example_data_callable()
panel = panel_class( dlg_edit, empty_node, referral_url, example_data )
dlg_edit.SetPanel( panel )
if dlg_edit.ShowModal() == wx.ID_OK:
new_node = panel.GetValue()
( display_tuple, data_tuple ) = self._ConvertNodeToTuples( new_node )
self._nodes.Append( display_tuple, data_tuple )
self._nodes.Append( display_tuple, data_tuple )
@ -586,36 +587,6 @@ class EditNodes( wx.Panel ):
def EventAdd( self, event ):
self.Add()
def EventCopy( self, event ):
self.Copy()
def EventDelete( self, event ):
self.Delete()
def EventDuplicate( self, event ):
self.Duplicate()
def EventEdit( self, event ):
self.Edit()
def EventPaste( self, event ):
self.Paste()
class EditParseNodeContentPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, node, referral_url = None, example_data = None ):
@ -675,8 +646,7 @@ class EditParseNodeContentPanel( ClientGUIScrolledPanels.EditPanel ):
self._formula_description.Disable()
self._edit_formula = wx.Button( formula_panel, label = 'edit formula' )
self._edit_formula.Bind( wx.EVT_BUTTON, self.EventEditFormula )
self._edit_formula = ClientGUICommon.BetterButton( formula_panel, 'edit formula', self.EditFormula )
#
@ -688,8 +658,7 @@ class EditParseNodeContentPanel( ClientGUIScrolledPanels.EditPanel ):
self._example_data.SetMinSize( ( -1, 200 ) )
self._test_parse = wx.Button( test_panel, label = 'test parse' )
self._test_parse.Bind( wx.EVT_BUTTON, self.EventTestParse )
self._test_parse = ClientGUICommon.BetterButton( test_panel, 'test parse', self.TestParse )
self._results = ClientGUICommon.SaneMultilineTextCtrl( test_panel )
@ -844,7 +813,7 @@ The 'veto' type will tell the parent panel that this page, while it returned 200
self._edit_panel.Layout()
def EventEditFormula( self, event ):
def EditFormula( self ):
dlg_title = 'edit html formula'
@ -865,38 +834,6 @@ The 'veto' type will tell the parent panel that this page, while it returned 200
def EventTestParse( self, event ):
node = self.GetValue()
try:
data = self._example_data.GetValue()
referral_url = self._referral_url
desired_content = 'all'
results = node.Parse( data, referral_url, desired_content )
result_lines = [ '*** RESULTS BEGIN ***' ]
result_lines.extend( ( ClientParsing.ConvertContentResultToPrettyString( result ) for result in results ) )
result_lines.append( '*** RESULTS END ***' )
results_text = os.linesep.join( result_lines )
self._results.SetValue( results_text )
except Exception as e:
HydrusData.ShowException( e )
message = 'Could not parse!'
wx.MessageBox( message )
def GetValue( self ):
name = self._name.GetValue()
@ -925,6 +862,38 @@ The 'veto' type will tell the parent panel that this page, while it returned 200
return node
def TestParse( self ):
node = self.GetValue()
try:
data = self._example_data.GetValue()
referral_url = self._referral_url
desired_content = 'all'
results = node.Parse( data, referral_url, desired_content )
result_lines = [ '*** RESULTS BEGIN ***' ]
result_lines.extend( ( ClientParsing.ConvertContentResultToPrettyString( result ) for result in results ) )
result_lines.append( '*** RESULTS END ***' )
results_text = os.linesep.join( result_lines )
self._results.SetValue( results_text )
except Exception as e:
HydrusData.ShowException( e )
message = 'Could not parse!'
wx.MessageBox( message )
class EditParseNodeContentLinkPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, node, referral_url = None, example_data = None ):
@ -1554,47 +1523,50 @@ class ManageParsingScriptsPanel( ClientGUIScrolledPanels.ManagePanel ):
def Add( self ):
with ClientGUIDialogs.DialogSelectFromListOfStrings( self, 'select the script type', [ 'file metadata lookup' ] ) as dlg_type:
menu = wx.Menu()
ClientGUIMenus.AppendMenuItem( menu, 'file lookup script', 'A script that fetches content for a known file.', self, self.AddFileLookupScript )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def AddFileLookupScript( self ):
name = 'new script'
url = ''
query_type = HC.GET
file_identifier_type = ClientParsing.FILE_IDENTIFIER_TYPE_MD5
file_identifier_encoding = HC.ENCODING_BASE64
file_identifier_arg_name = 'md5'
static_args = {}
children = []
dlg_title = 'edit file metadata lookup script'
empty_script = ClientParsing.ParseRootFileLookup( name, url = url, query_type = query_type, file_identifier_type = file_identifier_type, file_identifier_encoding = file_identifier_encoding, file_identifier_arg_name = file_identifier_arg_name, static_args = static_args, children = children)
panel_class = EditParsingScriptFileLookupPanel
self.AddScript( dlg_title, empty_script, panel_class )
def AddScript( self, dlg_title, empty_script, panel_class ):
with ClientGUITopLevelWindows.DialogEdit( self, dlg_title ) as dlg_edit:
if dlg_type.ShowModal() == wx.ID_OK:
panel = panel_class( dlg_edit, empty_script )
dlg_edit.SetPanel( panel )
if dlg_edit.ShowModal() == wx.ID_OK:
script_type_string = dlg_type.GetString()
new_script = panel.GetValue()
if script_type_string == 'file metadata lookup':
name = 'new script'
url = ''
query_type = HC.GET
file_identifier_type = ClientParsing.FILE_IDENTIFIER_TYPE_MD5
file_identifier_encoding = HC.ENCODING_BASE64
file_identifier_arg_name = 'md5'
static_args = {}
children = []
empty_script = ClientParsing.ParseRootFileLookup( name, url = url, query_type = query_type, file_identifier_type = file_identifier_type, file_identifier_encoding = file_identifier_encoding, file_identifier_arg_name = file_identifier_arg_name, static_args = static_args, children = children)
dlg_title = 'edit file metadata lookup script'
panel_class = EditParsingScriptFileLookupPanel
self._SetNonDupeName( new_script )
with ClientGUITopLevelWindows.DialogEdit( self, dlg_title ) as dlg_edit:
panel = panel_class( dlg_edit, empty_script )
dlg_edit.SetPanel( panel )
if dlg_edit.ShowModal() == wx.ID_OK:
new_script = panel.GetValue()
self._SetNonDupeName( new_script )
( display_tuple, data_tuple ) = self._ConvertScriptToTuples( new_script )
self._scripts.Append( display_tuple, data_tuple )
( display_tuple, data_tuple ) = self._ConvertScriptToTuples( new_script )
self._scripts.Append( display_tuple, data_tuple )

View File

@ -2383,11 +2383,22 @@ class ManageOptionsPanel( ManagePanel ):
self._apply_all_parents_to_all_services = wx.CheckBox( general_panel )
#
suggested_tags_panel = ClientGUICommon.StaticBox( self, 'suggested tags' )
self._suggested_tags_width = ClientGUICommon.NoneableSpinCtrl( suggested_tags_panel, 'width of suggested tags control', min = 20, none_phrase = 'width of longest tag', unit = 'pixels' )
self._suggested_tags_width = wx.SpinCtrl( suggested_tags_panel, min = 20, max = 65535 )
suggested_tags_favourites_panel = ClientGUICommon.StaticBox( suggested_tags_panel, 'favourites' )
self._suggested_tags_layout = ClientGUICommon.BetterChoice( suggested_tags_panel )
self._suggested_tags_layout.Append( 'notebook', 'notebook' )
self._suggested_tags_layout.Append( 'side-by-side', 'columns' )
suggest_tags_panel_notebook = wx.Notebook( suggested_tags_panel )
#
suggested_tags_favourites_panel = wx.Panel( suggest_tags_panel_notebook )
suggested_tags_favourites_panel.SetMinSize( ( 400, -1 ) )
@ -2412,17 +2423,34 @@ class ManageOptionsPanel( ManagePanel ):
self._suggested_favourites_input = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( suggested_tags_favourites_panel, self._suggested_favourites.AddTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, CC.LOCAL_TAG_SERVICE_KEY )
suggested_tags_related_panel = ClientGUICommon.StaticBox( suggested_tags_panel, 'related' )
#
suggested_tags_related_panel = wx.Panel( suggest_tags_panel_notebook )
self._show_related_tags = wx.CheckBox( suggested_tags_related_panel )
self._related_tags_width = wx.SpinCtrl( suggested_tags_related_panel, min = 60, max = 400 )
self._related_tags_search_1_duration_ms = wx.SpinCtrl( suggested_tags_related_panel, min = 50, max = 60000 )
self._related_tags_search_2_duration_ms = wx.SpinCtrl( suggested_tags_related_panel, min = 50, max = 60000 )
self._related_tags_search_3_duration_ms = wx.SpinCtrl( suggested_tags_related_panel, min = 50, max = 60000 )
suggested_tags_recent_panel = ClientGUICommon.StaticBox( suggested_tags_panel, 'recent' )
#
suggested_tags_file_lookup_script_panel = wx.Panel( suggest_tags_panel_notebook )
self._show_file_lookup_script_tags = wx.CheckBox( suggested_tags_file_lookup_script_panel )
self._favourite_file_lookup_script = ClientGUICommon.BetterChoice( suggested_tags_file_lookup_script_panel )
script_names = HydrusGlobals.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_PARSE_ROOT_FILE_LOOKUP )
for name in script_names:
self._favourite_file_lookup_script.Append( name, name )
#
suggested_tags_recent_panel = wx.Panel( suggest_tags_panel_notebook )
self._num_recent_tags = ClientGUICommon.NoneableSpinCtrl( suggested_tags_recent_panel, 'number of recent tags to show', min = 1, none_phrase = 'do not show' )
@ -2458,18 +2486,22 @@ class ManageOptionsPanel( ManagePanel ):
self._apply_all_parents_to_all_services.SetValue( self._new_options.GetBoolean( 'apply_all_parents_to_all_services' ) )
self._suggested_tags_width.SetValue( self._new_options.GetNoneableInteger( 'suggested_tags_width' ) )
self._suggested_tags_width.SetValue( self._new_options.GetInteger( 'suggested_tags_width' ) )
self._suggested_tags_layout.SelectClientData( self._new_options.GetNoneableString( 'suggested_tags_layout' ) )
self._suggested_favourites_services.SelectClientData( CC.LOCAL_TAG_SERVICE_KEY )
self._show_related_tags.SetValue( self._new_options.GetBoolean( 'show_related_tags' ) )
self._related_tags_width.SetValue( self._new_options.GetInteger( 'related_tags_width' ) )
self._related_tags_search_1_duration_ms.SetValue( self._new_options.GetInteger( 'related_tags_search_1_duration_ms' ) )
self._related_tags_search_2_duration_ms.SetValue( self._new_options.GetInteger( 'related_tags_search_2_duration_ms' ) )
self._related_tags_search_3_duration_ms.SetValue( self._new_options.GetInteger( 'related_tags_search_3_duration_ms' ) )
self._show_file_lookup_script_tags.SetValue( self._new_options.GetBoolean( 'show_file_lookup_script_tags' ) )
self._favourite_file_lookup_script.SelectClientData( self._new_options.GetNoneableString( 'favourite_file_lookup_script' ) )
self._num_recent_tags.SetValue( self._new_options.GetNoneableInteger( 'num_recent_tags' ) )
#
@ -2492,28 +2524,72 @@ class ManageOptionsPanel( ManagePanel ):
#
suggested_tags_favourites_panel.AddF( self._suggested_favourites_services, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_favourites_panel.AddF( self._suggested_favourites, CC.FLAGS_EXPAND_BOTH_WAYS )
suggested_tags_favourites_panel.AddF( self._suggested_favourites_input, CC.FLAGS_EXPAND_PERPENDICULAR )
panel_vbox = wx.BoxSizer( wx.VERTICAL )
panel_vbox.AddF( self._suggested_favourites_services, CC.FLAGS_EXPAND_PERPENDICULAR )
panel_vbox.AddF( self._suggested_favourites, CC.FLAGS_EXPAND_BOTH_WAYS )
panel_vbox.AddF( self._suggested_favourites_input, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_favourites_panel.SetSizer( panel_vbox )
#
panel_vbox = wx.BoxSizer( wx.VERTICAL )
rows = []
rows.append( ( 'Show related tags on single-file manage tags windows: ', self._show_related_tags ) )
rows.append( ( 'Width of related tags list: ', self._related_tags_width ) )
rows.append( ( 'Initial search duration (ms): ', self._related_tags_search_1_duration_ms ) )
rows.append( ( 'Medium search duration (ms): ', self._related_tags_search_2_duration_ms ) )
rows.append( ( 'Thorough search duration (ms): ', self._related_tags_search_3_duration_ms ) )
related_gridbox = ClientGUICommon.WrapInGrid( suggested_tags_related_panel, rows )
gridbox = ClientGUICommon.WrapInGrid( suggested_tags_related_panel, rows )
suggested_tags_related_panel.AddF( related_gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
panel_vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_recent_panel.AddF( self._num_recent_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_related_panel.SetSizer( panel_vbox )
suggested_tags_panel.AddF( self._suggested_tags_width, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.AddF( suggested_tags_favourites_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
suggested_tags_panel.AddF( suggested_tags_related_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.AddF( suggested_tags_recent_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
#
panel_vbox = wx.BoxSizer( wx.VERTICAL )
rows = []
rows.append( ( 'Show file lookup scripts on single-file manage tags windows: ', self._show_file_lookup_script_tags ) )
rows.append( ( 'Favourite file lookup script: ', self._favourite_file_lookup_script ) )
gridbox = ClientGUICommon.WrapInGrid( suggested_tags_file_lookup_script_panel, rows )
panel_vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_file_lookup_script_panel.SetSizer( panel_vbox )
#
panel_vbox = wx.BoxSizer( wx.VERTICAL )
panel_vbox.AddF( self._num_recent_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_recent_panel.SetSizer( panel_vbox )
#
suggest_tags_panel_notebook.AddPage( suggested_tags_favourites_panel, 'favourites' )
suggest_tags_panel_notebook.AddPage( suggested_tags_related_panel, 'related' )
suggest_tags_panel_notebook.AddPage( suggested_tags_file_lookup_script_panel, 'file lookup scripts' )
suggest_tags_panel_notebook.AddPage( suggested_tags_recent_panel, 'recent' )
#
rows = []
rows.append( ( 'Width of suggested tags columns: ', self._suggested_tags_width ) )
rows.append( ( 'Column layout: ', self._suggested_tags_layout ) )
gridbox = ClientGUICommon.WrapInGrid( suggested_tags_panel, rows )
suggested_tags_panel.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.AddF( suggest_tags_panel_notebook, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( suggested_tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
@ -2564,7 +2640,8 @@ class ManageOptionsPanel( ManagePanel ):
self._new_options.SetKey( 'default_tag_service_search_page', self._default_tag_service_search_page.GetChoice() )
self._new_options.SetNoneableInteger( 'suggested_tags_width', self._suggested_tags_width.GetValue() )
self._new_options.SetInteger( 'suggested_tags_width', self._suggested_tags_width.GetValue() )
self._new_options.SetNoneableString( 'suggested_tags_layout', self._suggested_tags_layout.GetChoice() )
self._new_options.SetBoolean( 'apply_all_parents_to_all_services', self._apply_all_parents_to_all_services.GetValue() )
@ -2577,12 +2654,13 @@ class ManageOptionsPanel( ManagePanel ):
self._new_options.SetBoolean( 'show_related_tags', self._show_related_tags.GetValue() )
self._new_options.SetInteger( 'related_tags_width', self._related_tags_width.GetValue() )
self._new_options.SetInteger( 'related_tags_search_1_duration_ms', self._related_tags_search_1_duration_ms.GetValue() )
self._new_options.SetInteger( 'related_tags_search_2_duration_ms', self._related_tags_search_2_duration_ms.GetValue() )
self._new_options.SetInteger( 'related_tags_search_3_duration_ms', self._related_tags_search_3_duration_ms.GetValue() )
self._new_options.SetBoolean( 'show_file_lookup_script_tags', self._show_file_lookup_script_tags.GetValue() )
self._new_options.SetNoneableString( 'favourite_file_lookup_script', self._favourite_file_lookup_script.GetChoice() )
self._new_options.SetNoneableInteger( 'num_recent_tags', self._num_recent_tags.GetValue() )

View File

@ -1,10 +1,13 @@
import ClientConstants as CC
import ClientData
import ClientGUICommon
import ClientGUIDialogs
import ClientParsing
import ClientSearch
import collections
import HydrusConstants as HC
import HydrusGlobals
import HydrusSerialisable
import wx
class ListBoxTagsSuggestionsFavourites( ClientGUICommon.ListBoxTagsStrings ):
@ -15,6 +18,13 @@ class ListBoxTagsSuggestionsFavourites( ClientGUICommon.ListBoxTagsStrings ):
self._activate_callable = activate_callable
width = HydrusGlobals.client_controller.GetNewOptions().GetInteger( 'suggested_tags_width' )
if width is not None:
self.SetMinSize( ( width, -1 ) )
def _Activate( self ):
@ -25,12 +35,13 @@ class ListBoxTagsSuggestionsFavourites( ClientGUICommon.ListBoxTagsStrings ):
self._activate_callable( tags )
'''
# Maybe reinclude this if per-column autoresizing is desired and not completely buggy
def SetTags( self, tags ):
ClientGUICommon.ListBoxTagsStrings.SetTags( self, tags )
width = HydrusGlobals.client_controller.GetNewOptions().GetNoneableInteger( 'suggested_tags_width' )
width = HydrusGlobals.client_controller.GetNewOptions().GetInteger( 'suggested_tags_width' )
if width is None:
@ -52,7 +63,7 @@ class ListBoxTagsSuggestionsFavourites( ClientGUICommon.ListBoxTagsStrings ):
wx.PostEvent( self.GetParent(), CC.SizeChangedEvent( -1 ) )
'''
class ListBoxTagsSuggestionsRelated( ClientGUICommon.ListBoxTags ):
def __init__( self, parent, activate_callable ):
@ -61,9 +72,9 @@ class ListBoxTagsSuggestionsRelated( ClientGUICommon.ListBoxTags ):
self._activate_callable = activate_callable
width = HydrusGlobals.client_controller.GetNewOptions().GetInteger( 'related_tags_width' )
width = HydrusGlobals.client_controller.GetNewOptions().GetInteger( 'suggested_tags_width' )
self.SetMinSize( ( 200, -1 ) )
self.SetMinSize( ( width, -1 ) )
def _Activate( self ):
@ -242,6 +253,75 @@ class RelatedTagsPanel( wx.Panel ):
self._FetchRelatedTags( max_time_to_take )
class FileLookupScriptTagsPanel( wx.Panel ):
def __init__( self, parent, service_key, media, activate_callable ):
wx.Panel.__init__( self, parent )
self._service_key = service_key
self._media = media
scripts = HydrusGlobals.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_PARSE_ROOT_FILE_LOOKUP )
self._script_choice = ClientGUICommon.BetterChoice( self )
for script in scripts:
self._script_choice.Append( script.GetName(), script )
new_options = HydrusGlobals.client_controller.GetNewOptions()
favourite_file_lookup_script = new_options.GetNoneableString( 'favourite_file_lookup_script' )
self._script_choice.SelectClientData( favourite_file_lookup_script )
fetch_button = ClientGUICommon.BetterButton( self, 'fetch tags', self.FetchTags )
self._tags = ListBoxTagsSuggestionsFavourites( self, activate_callable, sort_tags = True )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._script_choice, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( fetch_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def FetchTags( self ):
script = self._script_choice.GetChoice()
if script.UsesUserInput():
message = 'Enter the custom input for the file lookup script.'
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() != wx.ID_OK:
return
file_identifier = dlg.GetValue()
else:
( m, ) = self._media
file_identifier = script.ConvertMediaToFileIdentifier( m )
content_results = script.DoQuery( file_identifier, 'all' )
tags = ClientParsing.GetTagsFromContentResults( content_results )
self._tags.SetTags( tags )
class SuggestedTagsPanel( wx.Panel ):
def __init__( self, parent, service_key, media, activate_callable, canvas_key = None ):
@ -254,44 +334,79 @@ class SuggestedTagsPanel( wx.Panel ):
self._new_options = HydrusGlobals.client_controller.GetNewOptions()
something_to_show = False
layout_mode = self._new_options.GetNoneableString( 'suggested_tags_layout' )
hbox = wx.BoxSizer( wx.HORIZONTAL )
if layout_mode == 'notebook':
notebook = wx.Notebook( self )
panel_parent = notebook
else:
panel_parent = self
panels = []
favourites = self._new_options.GetSuggestedTagsFavourites( service_key )
if len( favourites ) > 0:
favourite_tags = ListBoxTagsSuggestionsFavourites( self, activate_callable )
favourite_tags = ListBoxTagsSuggestionsFavourites( panel_parent, activate_callable )
favourite_tags.SetTags( favourites )
hbox.AddF( favourite_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
something_to_show = True
panels.append( ( 'favourites', favourite_tags ) )
if self._new_options.GetBoolean( 'show_related_tags' ) and len( media ) == 1:
related_tags = RelatedTagsPanel( self, service_key, media, activate_callable, canvas_key = self._canvas_key )
related_tags = RelatedTagsPanel( panel_parent, service_key, media, activate_callable, canvas_key = self._canvas_key )
hbox.AddF( related_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
panels.append( ( 'related', related_tags ) )
something_to_show = True
if self._new_options.GetBoolean( 'show_file_lookup_script_tags' ) and len( media ) == 1:
file_lookup_script_tags = FileLookupScriptTagsPanel( panel_parent, service_key, media, activate_callable )
panels.append( ( 'file lookup scripts', file_lookup_script_tags ) )
if self._new_options.GetNoneableInteger( 'num_recent_tags' ) is not None:
recent_tags = RecentTagsPanel( self, service_key, activate_callable, canvas_key = self._canvas_key )
recent_tags = RecentTagsPanel( panel_parent, service_key, activate_callable, canvas_key = self._canvas_key )
hbox.AddF( recent_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
something_to_show = True
panels.append( ( 'recent', recent_tags ) )
self.SetSizer( hbox )
if layout_mode == 'notebook':
hbox = wx.BoxSizer( wx.HORIZONTAL )
for ( name, panel ) in panels:
notebook.AddPage( panel, name )
hbox.AddF( notebook, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( hbox )
elif layout_mode == 'columns':
hbox = wx.BoxSizer( wx.HORIZONTAL )
for ( name, panel ) in panels:
hbox.AddF( panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( hbox )
if not something_to_show:
if len( panels ) == 0:
self.Hide()

View File

@ -2,6 +2,8 @@ import bs4
import ClientNetworking
import HydrusConstants as HC
import HydrusData
import HydrusExceptions
import HydrusGlobals
import HydrusSerialisable
import HydrusTags
import os
@ -87,6 +89,22 @@ def GetChildrenContent( children, data, referral_url, desired_content ):
return content
def GetTagsFromContentResults( results ):
tag_results = []
for ( ( name, content_type, additional_info ), parsed_text ) in results:
if content_type == HC.CONTENT_TYPE_MAPPINGS:
tag_results.append( HydrusTags.CombineTag( additional_info, parsed_text ) )
tag_results = HydrusTags.CleanTags( tag_results )
return tag_results
def GetVetoes( parsed_texts, additional_info ):
( veto_if_matches_found, match_if_text_present, search_text ) = additional_info
@ -460,7 +478,9 @@ class ParseNodeContentLink( HydrusSerialisable.SerialisableBase ):
response = ClientNetworking.RequestsGet( search_url, headers = headers )
children_content = GetChildrenContent( self._children, data, search_url, desired_content )
linked_data = response.content
children_content = GetChildrenContent( self._children, linked_data, search_url, desired_content )
content.extend( children_content )
@ -542,6 +562,64 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
self._children = [ HydrusSerialisable.CreateFromSerialisableTuple( serialisable_child ) for serialisable_child in serialisable_children ]
def ConvertMediaToFileIdentifier( self, media ):
if self._file_identifier_type == FILE_IDENTIFIER_TYPE_USER_INPUT:
raise Exception( 'Cannot convert media to file identifier--this script takes user input!' )
elif self._file_identifier_type == FILE_IDENTIFIER_TYPE_SHA256:
return media.GetHash()
elif self._file_identifier_type in ( FILE_IDENTIFIER_TYPE_MD5, FILE_IDENTIFIER_TYPE_SHA1, FILE_IDENTIFIER_TYPE_SHA512 ):
sha256_hash = media.GetHash()
if self._file_identifier_type == FILE_IDENTIFIER_TYPE_MD5:
hash_type = 'md5'
elif self._file_identifier_type == FILE_IDENTIFIER_TYPE_SHA1:
hash_type = 'sha1'
elif self._file_identifier_type == FILE_IDENTIFIER_TYPE_SHA512:
hash_type = 'sha512'
try:
( other_hash, ) = HydrusGlobals.client_controller.Read( 'file_hashes', ( sha256_hash, ), 'sha256', hash_type )
return other_hash
except:
raise Exception( 'I do not know that file\'s ' + hash_type + ' hash, so I cannot look it up!' )
elif self._file_identifier_type == FILE_IDENTIFIER_TYPE_FILE:
hash = media.GetHash()
mime = media.GetMime()
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
try:
path = client_files_manager.GetFilePath( hash, mime )
return path
except HydrusExceptions.FileMissingException as e:
raise Exception( 'That file is not in the database\'s local files, so I cannot look it up!' )
def FetchData( self, file_identifier ):
request_args = dict( self._static_args )
@ -602,9 +680,9 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
return self.Parse( data, desired_content )
def GetFileIdentifier( self ):
def UsesUserInput( self ):
return ( self._file_identifier_type, self._file_identifier_encoding )
return self._file_identifier_type == FILE_IDENTIFIER_TYPE_USER_INPUT
def Parse( self, data, desired_content ):

View File

@ -0,0 +1,82 @@
import ClientImageHandling
import cv2
import HydrusSerialisable
import numpy
import struct
def DumpToPng( payload, title, payload_type, text, path ):
payload_length = len( payload )
payload_string_length = payload_length + 4
square_width = int( float( payload_string_length ) ** 0.5 )
width = max( 512, square_width )
payload_height = float( payload_string_length ) / width
if float( payload_string_length ) / width % 1.0 > 0:
payload_height += 1
# given this width, figure out how much height we need for title and text and object type
# does cv have gettextentent or similar?
# getTextSize looks like the one
# intelligently wrap the text to fit into our width sans padding
# we now know our height
top_height = 250
top_image = numpy.empty( ( top_height, width ), dtype = 'uint8' )
top_image.fill( 255 )
# draw hydrus icon in the corner
# draw title
# draw payload_type
# draw text
top_height_header = struct.pack( '!H', top_height )
( byte0, byte1 ) = top_height_header
top_image[0][0] = ord( byte0 )
top_image[0][1] = ord( byte1 )
payload_length_header = struct.pack( '!I', payload_length )
num_empty_bytes = payload_height * width - payload_string_length
full_payload_string = payload_length_header + payload + '\x00' * num_empty_bytes
payload_image = numpy.fromstring( full_payload_string, dtype = 'uint8' ).reshape( ( payload_height, width ) )
# if this works out wrong, you can change axis or do stack_vertically instead or something. one of those will do the trick
finished_image = numpy.concatenate( top_image, payload_image )
# make sure this is how cv 'params' work
cv2.imwrite( path, finished_image, params = { cv2.IMWRITE_PNG_COMPRESSION : 9 } )
def LoadFromPng( path ):
numpy_image = cv2.imread( path )
( height, width ) = numpy_image.shape
complete_data = numpy_image.tostring()
top_height_header = complete_data[:2]
( top_height, ) = struct.unpack( '!H', top_height_header )
full_payload_string = complete_data[ width * top_height : ]
payload_length_header = full_payload_string[:4]
( payload_length, ) = struct.unpack( '!I', payload_length_header )
payload = full_payload_string[ 4 : 4 + payload_length ]
return payload

View File

@ -44,7 +44,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 230
SOFTWARE_VERSION = 231
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -10,8 +10,6 @@ pubsub_profile_mode = False
force_idle_mode = False
server_busy = False
no_focus_changed = False
do_idle_shutdown_work = False
shutdown_complete = False
restart = False

View File

@ -2,15 +2,15 @@ import HydrusConstants as HC
import HydrusData
import os
import sys
import threading
import time
# I am having unreliable problems with stdout on Windows when I launch client.pyw with pythonw.exe, hence the except IOError business
# I guess I am sending bad characters or something to the 'windowised' environment of pythonw
class HydrusLogger( object ):
def __init__( self, log_path ):
def __init__( self, base_dir, prefix ):
self._log_path = log_path
self._log_path_base = os.path.join( base_dir, prefix )
self._lock = threading.Lock()
def __enter__( self ):
@ -23,14 +23,14 @@ class HydrusLogger( object ):
sys.stdout = self
sys.stderr = self
self._log_file = open( self._log_path, 'a' )
self._OpenLog()
return self
def __exit__( self, exc_type, exc_val, exc_tb ):
self._log_file.close()
self._CloseLog()
sys.stdout = self._previous_sys_stdout
sys.stderr = self._previous_sys_stderr
@ -38,21 +38,61 @@ class HydrusLogger( object ):
return False
def _CloseLog( self ):
self._log_file.close()
def _GetLogPath( self ):
current_time_struct = time.gmtime()
( current_year, current_month ) = ( current_time_struct.tm_year, current_time_struct.tm_mon )
log_path = self._log_path_base + ' - ' + str( current_year ) + '-' + str( current_month ) + '.log'
return log_path
def _OpenLog( self ):
self._log_path = self._GetLogPath()
self._log_file = open( self._log_path, 'a' )
def _SwitchToANewLogFileIfDue( self ):
correct_log_path = self._GetLogPath()
if correct_log_path != self._log_path:
self._CloseLog()
self._OpenLog()
def flush( self ):
if not self._problem_with_previous_stdout:
with self._lock:
try:
if not self._problem_with_previous_stdout:
self._previous_sys_stdout.flush()
except IOError:
self._problem_with_previous_stdout = True
try:
self._previous_sys_stdout.flush()
except IOError:
self._problem_with_previous_stdout = True
self._log_file.flush()
self._log_file.flush()
self._SwitchToANewLogFileIfDue()
def isatty( self ):
@ -62,29 +102,32 @@ class HydrusLogger( object ):
def write( self, value ):
if value in ( os.linesep, '\n' ):
with self._lock:
prefix = ''
else:
prefix = time.strftime( '%Y/%m/%d %H:%M:%S: ', time.localtime() )
message = HydrusData.ToByteString( prefix + value )
if not self._problem_with_previous_stdout:
try:
if value in ( os.linesep, '\n' ):
self._previous_sys_stdout.write( message )
prefix = ''
except IOError:
else:
self._problem_with_previous_stdout = True
prefix = time.strftime( '%Y/%m/%d %H:%M:%S: ', time.localtime() )
self._log_file.write( message )
message = HydrusData.ToByteString( prefix + value )
if not self._problem_with_previous_stdout:
try:
self._previous_sys_stdout.write( message )
except IOError:
self._problem_with_previous_stdout = True
self._log_file.write( message )

View File

@ -227,7 +227,7 @@ def FilterFreePaths( paths ):
except OSError as e: # 'already in use by another process'
HydrusData.Print( path + ' ' + str( e ) )
HydrusData.Print( 'Already in use: ' + path )

View File

@ -71,9 +71,7 @@ try:
action = ServerController.ProcessStartingAction( db_dir, action )
log_path = os.path.join( db_dir, 'server.log' )
with HydrusLogger.HydrusLogger( log_path ) as logger:
with HydrusLogger.HydrusLogger( db_dir, 'server' ) as logger:
try: