Version 184

This commit is contained in:
Hydrus 2015-12-02 16:32:18 -06:00
parent 8a84453f89
commit 1ef52cff14
40 changed files with 1870 additions and 1235 deletions

View File

@ -27,8 +27,6 @@ try:
from include import HydrusLogger
import traceback
HydrusGlobals.instance = HC.HYDRUS_CLIENT
with HydrusLogger.HydrusLogger( 'client.log' ) as logger:
try:

View File

@ -20,6 +20,8 @@
<p>In this case, selecting the 'title:cool pic*' predicate will return all three images in the same search, where you can conveniently give them some more-easily searched tags like 'series:cool pic' and 'page:1', 'page:2', 'page:3'.</p>
<h3>exclude deleted files</h3>
<p>In the client's options is a checkbox to exclude deleted files. It recurs pretty much anywhere you can import, under 'import file options'. If you select this, any file you ever deleted will be excluded from all future remote searches and import operations. This can stop you from importing/downloading and filtering out the same bad files several times over. The default is off. You may wish to have it set one way most of the time, but switch it the other just for one specific import or search.</p>
<h3>inputting non-english lanuages</h3>
<p>If you typically use an IME to input Japanese or another non-english language, you may have encountered problems entering into the autocomplete tag entry control in that you need Up/Down/Enter to navigate the IME, but the autocomplete steals those key presses away to navigate the list of results. To fix this, press Shift+Tab to temporarily disable the autocomplete's key event capture. The autocomplete text box will change colour to let you know it has released its normal key capture. Use your IME to get the text you want, then hit Shift+Tab again to restore the autocomplete to normal behaviour.</p>
<h3>tag censorship</h3>
<p>If you do not like a particular tag or namespace, you can easily hide it with <i>services->manage tag censorship</i>:</p>
<p><img src="tag_censorship.png" /></p>

View File

@ -8,6 +8,39 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 184</h3></li>
<ul>
<li>added external client_files storage!</li>
<li>you can add external client_files folders in options->file storage locations, further giving them weight</li>
<li>a new daemon will incrementally rebalance your files (and recover orphaned subfolders!) over your different storage locations</li>
<li>you can also force a full rebalance from the new database->maintenance->rebalance file storage</li>
<li>simplified the maintenance and processing panel</li>
<li>the maintenance and processing panel controls now appropriately support being set to 'none'</li>
<li>the 'run jobs on idle?' question is now explicit on the maintenance and processing panel</li>
<li>deselecting 'run jobs on idle/shutdown' will now disable subordinate controls</li>
<li>hitting tab on the autocomplete control now triggers an immediate 'fetch results' call</li>
<li>added a checkbox to options->speed and memory to completely disable automatic autocomplete results fetching (i.e. if you want to manually control a/c result-fetching only with this new tab shortcut)</li>
<li>the new less-laggy autocomplete results fetch won't trigger if the latest query is shorter than the cached query (i.e. you won't get lag when hitting backspace a bunch of times on autocomplete)</li>
<li>hitting shift+tab on the autocomplete control now disables all other key event capture, letting you enter IME without your up/down/enter presses controlling the dropdown list of results</li>
<li>'waiting politely' time is now indicated with a small 'traffic light'-type circle control on all downloader pages</li>
<li>fixed some occasional 'I'll go sit in the top-left corner of the screen and not fix my position' hover window behaviour in Linux</li>
<li>fixed sibling predicate collapse for the 'read' autocomplete dropdown for database results</li>
<li>refactored how predicates are collapsed and sorted to be a bit more sensible</li>
<li>fixed maintenance of local booru data use even if local booru does not receive any requests</li>
<li>improved some service/content update error handling in repository sync</li>
<li>the system:rating value-entry dialog no longer lists its rating services in random order</li>
<li>'open externally' will no longer show for non-local thumbnails' right-click menus</li>
<li>fixed audio/pdf thumbnail display for local booru, although they will be full size for now</li>
<li>harmonised some hex-prefix folder generation code</li>
<li>improved some file and directory copying code</li>
<li>improved upload pending popup message cleanup</li>
<li>added help_dir to hydrusconstants</li>
<li>miscellaneous refactoring</li>
<li>misc cleanup</li>
<li>extracted server-specific services and server resources from the common import path to the server import path</li>
<li>a bit more misc server refactoring</li>
<li>updated a couple of bits of help</li>
</ul>
<li><h3>version 183</h3></li>
<ul>
<li>added swf thumbnail support--it works ok for most swfs!</li>

View File

@ -53,7 +53,7 @@
<p>All of a server's files and options are stored in its accompanying .db file and respective subdirectories, which are created on first startup (just like with the client). To backup or restore, you have two options:</p>
<ul>
<li>Shut down the server, copy the database files and directories, then restart it. This is the only way, currently, to restore a db.</li>
<li>Hit admin->your server->make a backup on your client. This will lock the db server-side while it makes a copy right in the /db directory. The .db file will be tidied up and copied to .db.backup and the subdirectories will be copied to _backup as well. When the operation is complete, you can ftp/batch-copy/whatever the backup wherever you like.</li>
<li>In the client, hit admin->your server->make a backup. This will lock the db server-side while it makes a copy of everything server-related to server_install_dir/db/server_backup. When the operation is complete, you can ftp/batch-copy/whatever the server_backup folder wherever you like.</li>
</ul>
<h3>OMG EVERYTHING WENT WRONG</h3>
<p>If you get to a point where you can no longer boot the repository, try running SQLite Studio and opening server.db. If the issue is simple—like manually changing the port number—you may be in luck. Send me an email if it is tricky.</p>

View File

@ -12,6 +12,7 @@ import itertools
import os
import random
import Queue
import shutil
import threading
import time
import urllib
@ -190,14 +191,40 @@ class ClientFilesManager( object ):
self._lock = threading.Lock()
# fetch the current exact mapping from the db
self._prefixes_to_locations = {}
self._Reinit()
def _Reinit( self ):
def _GetLocation( self, hash ):
self._prefixes_to_locations = self._controller.Read( 'client_files_locations' )
hash_encoded = hash.encode( 'hex' )
prefix = hash_encoded[:2]
location = self._prefixes_to_locations[ prefix ]
return location
def _GetRecoverTuple( self ):
paths = { path for path in self._prefixes_to_locations.values() }
for path in paths:
for prefix in HydrusData.IterateHexPrefixes():
correct_path = self._prefixes_to_locations[ prefix ]
if path != correct_path and os.path.exists( os.path.join( path, prefix ) ):
return ( prefix, path, correct_path )
return None
def _GetRebalanceTuple( self ):
@ -208,13 +235,21 @@ class ClientFilesManager( object ):
paths_to_normalised_ideal_weights = { path : weight / total_weight for ( path, weight ) in paths_to_ideal_weights.items() }
current_paths_to_normalised_weights = {}
current_paths_to_normalised_weights = collections.defaultdict( lambda: 0 )
for ( prefix, path ) in self._prefixes_to_locations.items():
current_paths_to_normalised_weights[ path ] += 1.0 / 256
for path in current_paths_to_normalised_weights.keys():
if path not in paths_to_normalised_ideal_weights:
paths_to_normalised_ideal_weights[ path ] = 0.0
#
overweight_paths = []
@ -252,7 +287,11 @@ class ClientFilesManager( object ):
overweight_path = overweight_paths.pop( 0 )
underweight_path = underweight_paths.pop( 0 )
for ( prefix, path ) in self._prefixes_to_locations.items():
prefixes_and_paths = self._prefixes_to_locations.items()
random.shuffle( prefixes_and_paths )
for ( prefix, path ) in prefixes_and_paths:
if path == overweight_path:
@ -262,6 +301,79 @@ class ClientFilesManager( object ):
def _IterateAllFilePaths( self ):
for ( prefix, location ) in self._prefixes_to_locations.items():
dir = os.path.join( location, prefix )
next_filenames = os.listdir( dir )
for filename in next_filenames:
yield os.path.join( dir, filename )
def _Reinit( self ):
self._prefixes_to_locations = self._controller.Read( 'client_files_locations' )
def GetExpectedFilePath( self, hash, mime ):
with self._lock:
location = self._GetLocation( hash )
return ClientFiles.GetExpectedFilePath( location, hash, mime )
def GetFilePath( self, hash, mime = None ):
with self._lock:
location = self._GetLocation( hash )
return ClientFiles.GetFilePath( location, hash, mime )
def IterateAllFileHashes( self ):
with self._lock:
for path in self._IterateAllFilePaths():
( base, filename ) = os.path.split( path )
result = filename.split( '.', 1 )
if len( result ) != 2: continue
( hash_encoded, ext ) = result
try: hash = hash_encoded.decode( 'hex' )
except TypeError: continue
yield hash
def IterateAllFilePaths( self ):
with self._lock:
for path in self._IterateAllFilePaths():
yield path
def Rebalance( self, partial = True ):
with self._lock:
@ -272,7 +384,18 @@ class ClientFilesManager( object ):
( prefix, overweight_path, underweight_path ) = rebalance_tuple
self._controller.Write( 'move_client_files', prefix, overweight_path, underweight_path )
text = 'Moving \'' + prefix + '\' files from ' + overweight_path + ' to ' + underweight_path
if partial:
HydrusData.Print( text )
else:
HydrusData.ShowText( text )
self._controller.Write( 'relocate_client_files', prefix, overweight_path, underweight_path )
self._Reinit()
@ -284,6 +407,49 @@ class ClientFilesManager( object ):
rebalance_tuple = self._GetRebalanceTuple()
recover_tuple = self._GetRecoverTuple()
while recover_tuple is not None:
( prefix, incorrect_path, correct_path ) = recover_tuple
text = 'Recovering \'' + prefix + '\' files from ' + incorrect_path + ' to ' + correct_path
if partial:
HydrusData.Print( text )
else:
HydrusData.ShowText( text )
full_incorrect_path = os.path.join( incorrect_path, prefix )
full_correct_path = os.path.join( correct_path, prefix )
HydrusPaths.CopyAndMergeTree( full_incorrect_path, full_correct_path )
try: HydrusPaths.RecyclePath( full_incorrect_path )
except:
HydrusData.ShowText( 'After recovering some files, attempting to remove ' + full_incorrect_path + ' failed.' )
return
if partial:
break
recover_tuple = self._GetRecoverTuple()
if not partial:
HydrusData.ShowText( 'All folders balanced!' )
class DataCache( object ):
@ -1392,7 +1558,10 @@ class TagSiblingsManager( object ):
new_predicate.AddToCount( HC.CURRENT, current_count )
new_predicate.AddToCount( HC.PENDING, pending_count )
else: tags_to_include_in_results.add( tag )
else:
tags_to_include_in_results.add( tag )
results.extend( [ tags_to_predicates[ tag ] for tag in tags_to_include_in_results ] )

View File

@ -265,31 +265,29 @@ class Controller( HydrusController.HydrusController ):
def CurrentlyIdle( self ):
# the existence of an idle test permits a True result
# any single fail vetoes a True
idle_normal = self._options[ 'idle_normal' ]
idle_period = self._options[ 'idle_period' ]
idle_mouse_period = self._options[ 'idle_mouse_period' ]
possibly_idle = False
definitely_not_idle = False
if self._options[ 'idle_period' ] > 0:
if idle_normal:
if HydrusData.TimeHasPassed( self._timestamps[ 'last_user_action' ] + self._options[ 'idle_period' ] ):
possibly_idle = True
else:
possibly_idle = True
if idle_period is not None:
if not HydrusData.TimeHasPassed( self._timestamps[ 'last_user_action' ] + idle_period ):
definitely_not_idle = True
if self._options[ 'idle_mouse_period' ] > 0:
if idle_mouse_period is not None:
if HydrusData.TimeHasPassed( self._timestamps[ 'last_mouse_action' ] + self._options[ 'idle_mouse_period' ] ):
possibly_idle = True
else:
if not HydrusData.TimeHasPassed( self._timestamps[ 'last_mouse_action' ] + idle_mouse_period ):
definitely_not_idle = True
@ -386,6 +384,11 @@ class Controller( HydrusController.HydrusController ):
self.pub( 'refresh_status' )
def GetClientFilesManager( self ):
return self._client_files_manager
def GetDB( self ): return self._db
def GetGUI( self ): return self._gui
@ -420,6 +423,8 @@ class Controller( HydrusController.HydrusController ):
self._services_manager = ClientCaches.ServicesManager( self )
self._client_files_manager = ClientCaches.ClientFilesManager( self )
self._managers[ 'hydrus_sessions' ] = ClientCaches.HydrusSessionManager( self )
self._managers[ 'local_booru' ] = ClientCaches.LocalBooruCache( self )
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager( self )
@ -503,6 +508,7 @@ class Controller( HydrusController.HydrusController ):
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 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'RebalanceClientFiles', ClientDaemons.DAEMONRebalanceClientFiles, period = 3600 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseAccounts', ClientDaemons.DAEMONSynchroniseAccounts, ( 'permissions_are_stale', ) ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseRepositories', ClientDaemons.DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ), period = 360 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseSubscriptions', ClientDaemons.DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ), period = 360, init_wait = 120 ) )
@ -520,9 +526,14 @@ class Controller( HydrusController.HydrusController ):
shutdown_timestamps = self.Read( 'shutdown_timestamps' )
if self._options[ 'maintenance_vacuum_period' ] != 0:
maintenance_vacuum_period = self._options[ 'maintenance_vacuum_period' ]
if maintenance_vacuum_period is not None and maintenance_vacuum_period > 0:
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > self._options[ 'maintenance_vacuum_period' ]: self.WriteSynchronous( 'vacuum' )
if HydrusData.TimeHasPassed( shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] + maintenance_vacuum_period ):
self.WriteSynchronous( 'vacuum' )
if self._timestamps[ 'last_service_info_cache_fatten' ] == 0:
@ -530,7 +541,7 @@ class Controller( HydrusController.HydrusController ):
self._timestamps[ 'last_service_info_cache_fatten' ] = HydrusData.GetNow()
if now - self._timestamps[ 'last_service_info_cache_fatten' ] > 60 * 20:
if HydrusData.TimeHasPassed( self._timestamps[ 'last_service_info_cache_fatten' ] + ( 60 * 20 ) ):
self.pub( 'splash_set_status_text', 'fattening service info' )
@ -868,24 +879,31 @@ class Controller( HydrusController.HydrusController ):
def SystemBusy( self ):
max_cpu = self._options[ 'idle_cpu_max' ]
if HydrusData.TimeHasPassed( self._timestamps[ 'last_cpu_check' ] + 60 ):
if max_cpu is None:
max_cpu = self._options[ 'idle_cpu_max' ]
self._system_busy = False
cpu_times = psutil.cpu_percent( percpu = True )
else:
if True in ( cpu_time > max_cpu for cpu_time in cpu_times ):
if HydrusData.TimeHasPassed( self._timestamps[ 'last_cpu_check' ] + 60 ):
self._system_busy = True
cpu_times = psutil.cpu_percent( percpu = True )
else:
if True in ( cpu_time > max_cpu for cpu_time in cpu_times ):
self._system_busy = True
else:
self._system_busy = False
self._system_busy = False
self._timestamps[ 'last_cpu_check' ] = HydrusData.GetNow()
self._timestamps[ 'last_cpu_check' ] = HydrusData.GetNow()
return self._system_busy
@ -896,9 +914,11 @@ class Controller( HydrusController.HydrusController ):
shutdown_timestamps = self.Read( 'shutdown_timestamps' )
if self._options[ 'maintenance_vacuum_period' ] != 0:
maintenance_vacuum_period = self._options[ 'maintenance_vacuum_period' ]
if maintenance_vacuum_period is not None and maintenance_vacuum_period > 0:
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > self._options[ 'maintenance_vacuum_period' ]:
if HydrusData.TimeHasPassed( shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] + maintenance_vacuum_period ):
return True

View File

@ -832,9 +832,11 @@ class MessageDB( object ):
files = []
client_files_manager = self._controller.GetClientFilesManager()
for hash in attachment_hashes:
path = ClientFiles.GetFilePath( hash )
path = client_files_manager.GetFilePath( hash )
with open( path, 'rb' ) as f: file = f.read()
@ -876,9 +878,11 @@ class MessageDB( object ):
files = []
client_files_manager = self._controller.GetClientFilesManager()
for hash in attachment_hashes:
path = ClientFiles.GetFilePath( hash )
path = client_files_manager.GetFilePath( hash )
with open( path, 'rb' ) as f: file = f.read()
@ -1203,8 +1207,8 @@ class DB( HydrusDB.HydrusDB ):
ClientData.DeletePath( deletee_path )
shutil.copy( self._db_path, os.path.join( path, 'client.db' ) )
if os.path.exists( self._db_path + '-wal' ): shutil.copy( self._db_path + '-wal', os.path.join( path, 'client.db-wal' ) )
shutil.copy2( self._db_path, os.path.join( path, 'client.db' ) )
if os.path.exists( self._db_path + '-wal' ): shutil.copy2( self._db_path + '-wal', os.path.join( path, 'client.db-wal' ) )
shutil.copytree( HC.CLIENT_ARCHIVES_DIR, os.path.join( path, 'client_archives' ) )
shutil.copytree( HC.CLIENT_FILES_DIR, os.path.join( path, 'client_files' ) )
@ -1279,6 +1283,8 @@ class DB( HydrusDB.HydrusDB ):
missing_count = 0
deletee_hash_ids = []
client_files_manager = self._controller.GetClientFilesManager()
for ( i, ( hash_id, mime ) ) in enumerate( info ):
( i_paused, should_quit ) = job_key.WaitIfNeeded()
@ -1293,7 +1299,7 @@ class DB( HydrusDB.HydrusDB ):
hash = self._GetHash( hash_id )
try: path = ClientFiles.GetFilePath( hash, mime )
try: path = client_files_manager.GetFilePath( hash, mime )
except HydrusExceptions.NotFoundException:
deletee_hash_ids.append( hash_id )
@ -1374,6 +1380,8 @@ class DB( HydrusDB.HydrusDB ):
def _CopyFiles( self, hashes ):
client_files_manager = self._controller.GetClientFilesManager()
if len( hashes ) > 0:
error_messages = set()
@ -1384,7 +1392,7 @@ class DB( HydrusDB.HydrusDB ):
try:
path = ClientFiles.GetFilePath( hash )
path = client_files_manager.GetFilePath( hash )
paths.append( path )
@ -1409,15 +1417,13 @@ class DB( HydrusDB.HydrusDB ):
if not os.path.exists( HC.CLIENT_THUMBNAILS_DIR ): os.mkdir( HC.CLIENT_THUMBNAILS_DIR )
if not os.path.exists( HC.CLIENT_UPDATES_DIR ): os.mkdir( HC.CLIENT_UPDATES_DIR )
hex_chars = '0123456789abcdef'
for ( one, two ) in itertools.product( hex_chars, hex_chars ):
for prefix in HydrusData.IterateHexPrefixes():
dir = os.path.join( HC.CLIENT_FILES_DIR, one + two )
dir = os.path.join( HC.CLIENT_FILES_DIR, prefix )
if not os.path.exists( dir ): os.mkdir( dir )
dir = os.path.join( HC.CLIENT_THUMBNAILS_DIR, one + two )
dir = os.path.join( HC.CLIENT_THUMBNAILS_DIR, prefix )
if not os.path.exists( dir ): os.mkdir( dir )
@ -1439,6 +1445,8 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'CREATE TABLE autocomplete_tags_cache ( file_service_id INTEGER REFERENCES services ( service_id ) ON DELETE CASCADE, tag_service_id INTEGER REFERENCES services ( service_id ) ON DELETE CASCADE, namespace_id INTEGER, tag_id INTEGER, current_count INTEGER, pending_count INTEGER, PRIMARY KEY ( file_service_id, tag_service_id, namespace_id, tag_id ) );' )
self._c.execute( 'CREATE INDEX autocomplete_tags_cache_tag_service_id_namespace_id_tag_id_index ON autocomplete_tags_cache ( tag_service_id, namespace_id, tag_id );' )
self._c.execute( 'CREATE TABLE client_files_locations ( prefix TEXT, location TEXT );' )
self._c.execute( 'CREATE TABLE contacts ( contact_id INTEGER PRIMARY KEY, contact_key BLOB_BYTES, public_key TEXT, name TEXT, host TEXT, port INTEGER );' )
self._c.execute( 'CREATE UNIQUE INDEX contacts_contact_key_index ON contacts ( contact_key );' )
self._c.execute( 'CREATE UNIQUE INDEX contacts_name_index ON contacts ( name );' )
@ -1572,6 +1580,13 @@ class DB( HydrusDB.HydrusDB ):
# inserts
location = HydrusPaths.ConvertAbsPathToPortablePath( HC.CLIENT_FILES_DIR )
for prefix in HydrusData.IterateHexPrefixes():
self._c.execute( 'INSERT INTO client_files_locations ( prefix, location ) VALUES ( ?, ? );', ( prefix, location ) )
init_service_info = []
init_service_info.append( ( CC.LOCAL_FILE_SERVICE_KEY, HC.LOCAL_FILE, CC.LOCAL_FILE_SERVICE_KEY ) )
@ -1716,7 +1731,9 @@ class DB( HydrusDB.HydrusDB ):
time.sleep( 1 )
for hash in ClientFiles.IterateAllFileHashes():
client_files_manager = self._controller.GetClientFilesManager()
for hash in client_files_manager.IterateAllFileHashes():
( i_paused, should_quit ) = job_key.WaitIfNeeded()
@ -1727,7 +1744,7 @@ class DB( HydrusDB.HydrusDB ):
if hash in deletee_hashes:
try: path = ClientFiles.GetFilePath( hash )
try: path = client_files_manager.GetFilePath( hash )
except HydrusExceptions.NotFoundException: continue
try:
@ -1866,9 +1883,11 @@ class DB( HydrusDB.HydrusDB ):
file_hashes = self._GetHashes( deletable_file_hash_ids )
client_files_manager = self._controller.GetClientFilesManager()
for hash in file_hashes:
try: path = ClientFiles.GetFilePath( hash )
try: path = client_files_manager.GetFilePath( hash )
except HydrusExceptions.NotFoundException: continue
deletee_paths.add( path )
@ -2268,6 +2287,13 @@ class DB( HydrusDB.HydrusDB ):
return predicates
def _GetClientFilesLocations( self ):
result = { prefix : HydrusPaths.ConvertPortablePathToAbsPath( location ) for ( prefix, location ) in self._c.execute( 'SELECT prefix, location FROM client_files_locations;' ) }
return result
def _GetDownloads( self ): return { hash for ( hash, ) in self._c.execute( 'SELECT hash FROM file_transfers, hashes USING ( hash_id ) WHERE service_id = ?;', ( self._local_file_service_id, ) ) }
def _GetFileHash( self, hash, hash_type ):
@ -3578,6 +3604,27 @@ class DB( HydrusDB.HydrusDB ):
self._service_cache[ service_id ] = service
if service.GetServiceType() == HC.LOCAL_BOORU:
info = service.GetInfo()
current_time_struct = time.gmtime()
( current_year, current_month ) = ( current_time_struct.tm_year, current_time_struct.tm_mon )
( booru_year, booru_month ) = info[ 'current_data_month' ]
if current_year != booru_year or current_month != booru_month:
info[ 'used_monthly_data' ] = 0
info[ 'used_monthly_requests' ] = 0
info[ 'current_data_month' ] = ( current_year, current_month )
self._c.execute( 'UPDATE services SET info = ? WHERE service_id = ?;', ( info, service_id ) )
return service
@ -4048,11 +4095,13 @@ class DB( HydrusDB.HydrusDB ):
mime = HydrusFileHandling.GetMime( path )
dest_path = ClientFiles.GetExpectedFilePath( hash, mime )
client_files_manager = self._controller.GetClientFilesManager()
dest_path = client_files_manager.GetExpectedFilePath( hash, mime )
if not os.path.exists( dest_path ):
shutil.copy( path, dest_path )
shutil.copy2( path, dest_path )
try: os.chmod( dest_path, stat.S_IWRITE | stat.S_IREAD )
except: pass
@ -4265,7 +4314,7 @@ class DB( HydrusDB.HydrusDB ):
content_update_index_string = 'content row ' + HydrusData.ConvertValueRangeToPrettyString( c_u_p_total_weight_processed, c_u_p_num_rows ) + ': '
HydrusGlobals.client_controller.pub( 'splash_set_status_text', content_update_index_string + 'committing' + update_speed_string )
self._controller.pub( 'splash_set_status_text', content_update_index_string + 'committing' + update_speed_string )
job_key.SetVariable( 'popup_text_2', content_update_index_string + 'committing' + update_speed_string )
job_key.SetVariable( 'popup_gauge_2', ( c_u_p_total_weight_processed, c_u_p_num_rows ) )
@ -4900,20 +4949,6 @@ class DB( HydrusDB.HydrusDB ):
info = service.GetInfo()
current_time_struct = time.gmtime()
( current_year, current_month ) = ( current_time_struct.tm_year, current_time_struct.tm_mon )
( booru_year, booru_month ) = info[ 'current_data_month' ]
if current_year != booru_year or current_month != booru_month:
info[ 'used_monthly_data' ] = 0
info[ 'used_monthly_requests' ] = 0
info[ 'current_data_month' ] = ( current_year, current_month )
info[ 'used_monthly_data' ] += sum( local_booru_requests_made )
info[ 'used_monthly_requests' ] += len( local_booru_requests_made )
@ -4929,6 +4964,7 @@ class DB( HydrusDB.HydrusDB ):
elif action == 'tag_archive_info': result = self._GetTagArchiveInfo( *args, **kwargs )
elif action == 'tag_archive_tags': result = self._GetTagArchiveTags( *args, **kwargs )
elif action == 'autocomplete_predicates': result = self._GetAutocompletePredicates( *args, **kwargs )
elif action == 'client_files_locations': result = self._GetClientFilesLocations( *args, **kwargs )
elif action == 'downloads': result = self._GetDownloads( *args, **kwargs )
elif action == 'file_hash': result = self._GetFileHash( *args, **kwargs )
elif action == 'file_query_ids': result = self._GetHashIdsFromQuery( *args, **kwargs )
@ -4967,6 +5003,31 @@ class DB( HydrusDB.HydrusDB ):
return result
def _RelocateClientFiles( self, prefix, source, dest ):
full_source = os.path.join( source, prefix )
full_dest = os.path.join( dest, prefix )
if os.path.exists( full_source ):
HydrusPaths.CopyAndMergeTree( full_source, full_dest )
elif not os.path.exists( full_dest ):
os.mkdir( full_dest )
portable_dest = HydrusPaths.ConvertAbsPathToPortablePath( dest )
self._c.execute( 'UPDATE client_files_locations SET location = ? WHERE prefix = ?;', ( portable_dest, prefix ) )
if os.path.exists( full_source ):
try: HydrusPaths.RecyclePath( full_source )
except: pass
def _ResetService( self, service_key, delete_updates = False ):
service_id = self._GetServiceId( service_key )
@ -5273,88 +5334,6 @@ class DB( HydrusDB.HydrusDB ):
self._controller.pub( 'splash_set_title_text', 'updating db to v' + str( version + 1 ) )
if version == 131:
service_info = self._c.execute( 'SELECT service_id, info FROM services;' ).fetchall()
for ( service_id, info ) in service_info:
if 'account' in info:
info[ 'account' ] = HydrusData.GetUnknownAccount()
self._c.execute( 'UPDATE services SET info = ? WHERE service_id = ?;', ( info, service_id ) )
if version == 132:
self._c.execute( 'DELETE FROM service_info WHERE info_type = ?;', ( HC.SERVICE_INFO_NUM_FILES, ) )
#
options = self._GetOptions()
client_size = options[ 'client_size' ]
client_size[ 'fs_fullscreen' ] = True
client_size[ 'gui_fullscreen' ] = False
del options[ 'fullscreen_borderless' ]
self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
if version == 135:
if not os.path.exists( HC.CLIENT_ARCHIVES_DIR ): os.mkdir( HC.CLIENT_ARCHIVES_DIR )
#
extra_hashes_data = self._c.execute( 'SELECT * FROM local_hashes;' ).fetchall()
self._c.execute( 'DROP TABLE local_hashes;' )
self._c.execute( 'CREATE TABLE local_hashes ( hash_id INTEGER PRIMARY KEY, md5 BLOB_BYTES, sha1 BLOB_BYTES, sha512 BLOB_BYTES );' )
self._c.execute( 'CREATE INDEX local_hashes_md5_index ON local_hashes ( md5 );' )
self._c.execute( 'CREATE INDEX local_hashes_sha1_index ON local_hashes ( sha1 );' )
self._c.execute( 'CREATE INDEX local_hashes_sha512_index ON local_hashes ( sha512 );' )
for ( i, ( hash_id, md5, sha1 ) ) in enumerate( extra_hashes_data ):
hash = self._GetHash( hash_id )
try: path = ClientFiles.GetFilePath( hash )
except HydrusExceptions.NotFoundException: continue
h_sha512 = hashlib.sha512()
with open( path, 'rb' ) as f:
for block in HydrusPaths.ReadFileLikeAsBlocks( f ): h_sha512.update( block )
sha512 = h_sha512.digest()
self._c.execute( 'INSERT INTO local_hashes ( hash_id, md5, sha1, sha512 ) VALUES ( ?, ?, ?, ? );', ( hash_id, sqlite3.Binary( md5 ), sqlite3.Binary( sha1 ), sqlite3.Binary( sha512 ) ) )
if i % 100 == 0: self._controller.pub( 'splash_set_status_text', 'generating sha512 hashes: ' + HydrusData.ConvertIntToPrettyString( i ) )
#
tag_service_info = self._c.execute( 'SELECT service_id, info FROM services WHERE service_type IN ' + HydrusData.SplayListForDB( HC.TAG_SERVICES ) + ';' ).fetchall()
for ( service_id, info ) in tag_service_info:
info[ 'tag_archive_sync' ] = {}
self._c.execute( 'UPDATE services SET info = ? WHERE service_id = ?;', ( info, service_id ) )
if version == 136:
result = self._c.execute( 'SELECT tag_id FROM tags WHERE tag = ?;', ( '', ) ).fetchone()
@ -5733,7 +5712,22 @@ class DB( HydrusDB.HydrusDB ):
#
for ( i, path ) in enumerate( ClientFiles.IterateAllFilePaths() ):
def iterate_all_file_paths():
for prefix in HydrusData.IterateHexPrefixes():
dir = os.path.join( HC.CLIENT_FILES_DIR, prefix )
next_paths = os.listdir( dir )
for path in next_paths:
yield os.path.join( dir, path )
for ( i, path ) in enumerate( iterate_all_file_paths() ):
try: os.chmod( path, stat.S_IWRITE | stat.S_IREAD )
except: pass
@ -5862,7 +5856,7 @@ class DB( HydrusDB.HydrusDB ):
if version == 175:
HC.options = self._GetOptions()
HydrusGlobals.client_controller._options = HC.options
self._controller._options = HC.options
self._c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ?;', ( YAML_DUMP_ID_IMPORT_FOLDER, ) )
@ -6160,18 +6154,14 @@ class DB( HydrusDB.HydrusDB ):
hash = self._GetHash( hash_id )
try:
file_path = ClientFiles.GetFilePath( hash, HC.APPLICATION_FLASH )
except HydrusExceptions.NotFoundException:
continue
file_path = ClientFiles.GetExpectedFilePath( HC.CLIENT_FILES_DIR, hash, HC.APPLICATION_FLASH )
thumbnail = HydrusFileHandling.GenerateThumbnail( file_path )
self._AddThumbnails( [ ( hash, thumbnail ) ] )
if os.path.exists( file_path ):
thumbnail = HydrusFileHandling.GenerateThumbnail( file_path )
self._AddThumbnails( [ ( hash, thumbnail ) ] )
#
@ -6179,6 +6169,18 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'DELETE FROM service_info WHERE info_type IN ( ?, ? );', ( HC.SERVICE_INFO_NUM_THUMBNAILS, HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL ) )
if version == 183:
self._c.execute( 'CREATE TABLE client_files_locations ( prefix TEXT, location TEXT );' )
location = HydrusPaths.ConvertAbsPathToPortablePath( HC.CLIENT_FILES_DIR )
for prefix in HydrusData.IterateHexPrefixes():
self._c.execute( 'INSERT INTO client_files_locations ( prefix, location ) VALUES ( ?, ? );', ( prefix, location ) )
self._controller.pub( 'splash_set_title_text', 'updating db to v' + str( version + 1 ) )
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
@ -6693,7 +6695,7 @@ class DB( HydrusDB.HydrusDB ):
except sqlite3.OperationalError:
HC.options[ 'maintenance_vacuum_period' ] = 0
HC.options[ 'maintenance_vacuum_period' ] = None
self._SaveOptions( HC.options )
@ -6749,6 +6751,7 @@ class DB( HydrusDB.HydrusDB ):
elif action == 'import_file': result = self._ImportFile( *args, **kwargs )
elif action == 'local_booru_share': result = self._SetYAMLDump( YAML_DUMP_ID_LOCAL_BOORU, *args, **kwargs )
elif action == 'pixiv_account': result = self._SetYAMLDump( YAML_DUMP_ID_SINGLE, 'pixiv_account', *args, **kwargs )
elif action == 'relocate_client_files': result = self._RelocateClientFiles( *args, **kwargs )
elif action == 'remote_booru': result = self._SetYAMLDump( YAML_DUMP_ID_REMOTE_BOORU, *args, **kwargs )
elif action == 'reset_service': result = self._ResetService( *args, **kwargs )
elif action == 'save_options': result = self._SaveOptions( *args, **kwargs )
@ -6792,10 +6795,10 @@ class DB( HydrusDB.HydrusDB ):
shutil.copy( os.path.join( path, 'client.db' ), self._db_path )
shutil.copy2( os.path.join( path, 'client.db' ), self._db_path )
wal_path = os.path.join( path, 'client.db-wal' )
if os.path.exists( wal_path ): shutil.copy( wal_path, self._db_path + '-wal' )
if os.path.exists( wal_path ): shutil.copy2( wal_path, self._db_path + '-wal' )
shutil.copytree( os.path.join( path, 'client_archives' ), HC.CLIENT_ARCHIVES_DIR )
shutil.copytree( os.path.join( path, 'client_files' ), HC.CLIENT_FILES_DIR )

View File

@ -234,6 +234,13 @@ def DAEMONMaintainTrash( controller ):
def DAEMONRebalanceClientFiles( controller ):
if controller.CurrentlyIdle() and not controller.SystemBusy():
controller.GetClientFilesManager().Rebalance()
def DAEMONSynchroniseAccounts( controller ):
services = controller.GetServicesManager().GetServices( HC.RESTRICTED_SERVICES )

View File

@ -292,6 +292,20 @@ def ShowTextClient( text ):
HydrusGlobals.client_controller.pub( 'message', job_key )
def WaitPolitely( page_key = None ):
if page_key is not None:
HydrusGlobals.client_controller.pub( 'waiting_politely', page_key, True )
time.sleep( HC.options[ 'website_download_polite_wait' ] )
if page_key is not None:
HydrusGlobals.client_controller.pub( 'waiting_politely', page_key, False )
class Booru( HydrusData.HydrusYAMLBase ):
yaml_tag = u'!Booru'
@ -480,6 +494,13 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
def SetClientFilesLocationsToIdealWeights( self, locations_to_weights ):
portable_locations_and_weights = [ ( HydrusPaths.ConvertAbsPathToPortablePath( location ), float( weight ) ) for ( location, weight ) in locations_to_weights.items() ]
self._dictionary[ 'client_files_locations_ideal_weights' ] = portable_locations_and_weights
def SetDefaultImportTagOptions( self, gallery_identifier, import_tag_options ):
with self._lock:
@ -2169,6 +2190,11 @@ class Service( HydrusData.HydrusYAMLBase ):
service_update_package = HydrusSerialisable.CreateFromString( obj_string )
if not isinstance( service_update_package, HydrusData.ServerToClientServiceUpdatePackage ):
raise Exception()
except:
self._ReportSyncProcessingError( path, 'did not parse' )
@ -2230,6 +2256,11 @@ class Service( HydrusData.HydrusYAMLBase ):
content_update_package = HydrusSerialisable.CreateFromString( obj_string )
if not isinstance( content_update_package, HydrusData.ServerToClientContentUpdatePackage ):
raise Exception()
except:
self._ReportSyncProcessingError( path, 'did not parse' )

View File

@ -24,11 +24,13 @@ def GetClientDefaultOptions():
options[ 'num_autocomplete_chars' ] = 2
options[ 'gui_capitalisation' ] = False
options[ 'default_gui_session' ] = 'just a blank page'
options[ 'fetch_ac_results_automatically' ] = True
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_normal' ] = True
options[ 'idle_shutdown' ] = CC.IDLE_ON_SHUTDOWN_ASK_FIRST
options[ 'idle_shutdown_max_minutes' ] = 30
options[ 'maintenance_delete_orphans_period' ] = 86400 * 3

View File

@ -105,7 +105,7 @@ def GetAllPaths( raw_paths ):
gc.collect()
return file_paths
def GetAllThumbnailHashes():
thumbnail_hashes = set()
@ -116,14 +116,6 @@ def GetAllThumbnailHashes():
return thumbnail_hashes
def GetExpectedFilePath( hash, mime ):
hash_encoded = hash.encode( 'hex' )
first_two_chars = hash_encoded[:2]
return os.path.join( HC.CLIENT_FILES_DIR, first_two_chars, hash_encoded + HC.mime_ext_lookup[ mime ] )
def GetExportPath():
@ -147,35 +139,13 @@ def GetExportPath():
return path
def GetFilePath( hash, mime = None ):
def GetExpectedFilePath( location, hash, mime ):
if mime is None:
path = None
for potential_mime in HC.ALLOWED_MIMES:
potential_path = GetExpectedFilePath( hash, potential_mime )
if os.path.exists( potential_path ):
path = potential_path
break
else:
path = GetExpectedFilePath( hash, mime )
hash_encoded = hash.encode( 'hex' )
if path is None or not os.path.exists( path ):
raise HydrusExceptions.NotFoundException( 'File not found!' )
prefix = hash_encoded[:2]
return path
return os.path.join( location, prefix, hash_encoded + HC.mime_ext_lookup[ mime ] )
def GetExpectedThumbnailPath( hash, full_size = True ):
@ -191,7 +161,37 @@ def GetExpectedThumbnailPath( hash, full_size = True ):
return path
def GetFilePath( location, hash, mime = None ):
if mime is None:
path = None
for potential_mime in HC.ALLOWED_MIMES:
potential_path = GetExpectedFilePath( location, hash, potential_mime )
if os.path.exists( potential_path ):
path = potential_path
break
else:
path = GetExpectedFilePath( location, hash, mime )
if path is None or not os.path.exists( path ):
raise HydrusExceptions.NotFoundException( 'File not found!' )
return path
def GetThumbnailPath( hash, full_size = True ):
path = GetExpectedThumbnailPath( hash, full_size )
@ -240,40 +240,6 @@ def GetExpectedUpdateDir( service_key ):
return os.path.join( HC.CLIENT_UPDATES_DIR, service_key.encode( 'hex' ) )
def IterateAllFileHashes():
for path in IterateAllFilePaths():
( base, filename ) = os.path.split( path )
result = filename.split( '.', 1 )
if len( result ) != 2: continue
( hash_encoded, ext ) = result
try: hash = hash_encoded.decode( 'hex' )
except TypeError: continue
yield hash
def IterateAllFilePaths():
hex_chars = '0123456789abcdef'
for ( one, two ) in itertools.product( hex_chars, hex_chars ):
dir = os.path.join( HC.CLIENT_FILES_DIR, one + two )
next_paths = os.listdir( dir )
for path in next_paths:
yield os.path.join( dir, path )
def IterateAllThumbnailHashes():
for path in IterateAllThumbnailPaths():
@ -291,11 +257,9 @@ def IterateAllThumbnailHashes():
def IterateAllThumbnailPaths():
hex_chars = '0123456789abcdef'
for ( one, two ) in itertools.product( hex_chars, hex_chars ):
for prefix in HydrusData.IterateHexPrefixes():
dir = os.path.join( HC.CLIENT_THUMBNAILS_DIR, one + two )
dir = os.path.join( HC.CLIENT_THUMBNAILS_DIR, prefix )
next_paths = os.listdir( dir )
@ -413,22 +377,14 @@ 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 )
@ -445,8 +401,6 @@ 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 )
@ -459,83 +413,74 @@ 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( HydrusData.ToUnicode( 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:
HydrusData.Print( previous_filename )
sync_filenames = set()
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
for media_result in media_results:
hash = media_result.GetHash()
mime = media_result.GetMime()
size = media_result.GetSize()
source_path = GetFilePath( hash, mime )
source_path = client_files_manager.GetFilePath( hash, mime )
filename = GenerateExportFilename( media_result, terms )
dest_path = os.path.join( folder_path, 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 )
shutil.copy2( 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 = os.path.join( folder_path, deletee_filename )
if HydrusGlobals.special_debug_mode: HydrusData.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

@ -830,6 +830,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
submenu = wx.Menu()
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'vacuum_db' ), p( '&Vacuum' ), p( 'Rebuild the Database.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'rebalance_client_files' ), p( '&Rebalance File Storage' ), p( 'Move your files around your chosen storage directories until they satisfy the weights you have set in the options.' ) )
#submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'delete_orphans' ), p( '&Delete Orphan Files' ), p( 'Go through the client\'s file store, deleting any files that are no longer needed.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'regenerate_thumbnails' ), p( '&Regenerate All Thumbnails' ), p( 'Delete all thumbnails and regenerate from original files.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'file_integrity' ), p( '&Check File Integrity' ), p( 'Review and fix all local file records.' ) )
@ -1027,15 +1028,12 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
db_profile_mode_id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'db_profile_mode' )
pubsub_profile_mode_id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'pubsub_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( pubsub_profile_mode_id, p( '&PubSub Profile Mode' ) )
debug.Check( pubsub_profile_mode_id, HydrusGlobals.pubsub_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' ) )
@ -1431,6 +1429,21 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
def _RebalanceClientFiles( self ):
text = 'This will move your files around your storage directories until they satisfy the weights you have set in the options. It will also recover and folders that are in the wrong place. Use this if you have recently changed your file storage locations and want to hurry any transfers you have set up, or if you are recovering a complicated backup.'
text += os.linesep * 2
text += 'The operation will lock file access and the database. Popup messages will report its progress.'
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._controller.CallToThread( self._controller.GetClientFilesManager().Rebalance, partial = False )
def _RefreshStatusBar( self ):
page = self._notebook.GetCurrentPage()
@ -1491,11 +1504,9 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
if not os.path.exists( HC.CLIENT_THUMBNAILS_DIR ): os.mkdir( HC.CLIENT_THUMBNAILS_DIR )
hex_chars = '0123456789abcdef'
for ( one, two ) in itertools.product( hex_chars, hex_chars ):
for prefix in HydrusData.IterateHexPrefixes():
dir = os.path.join( HC.CLIENT_THUMBNAILS_DIR, one + two )
dir = os.path.join( HC.CLIENT_THUMBNAILS_DIR, prefix )
if not os.path.exists( dir ):
@ -1505,7 +1516,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
num_broken = 0
for ( i, path ) in enumerate( ClientFiles.IterateAllFilePaths() ):
for ( i, path ) in enumerate( self._controller.GetClientFilesManager().IterateAllFilePaths() ):
try:
@ -1812,6 +1823,8 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
error_messages = set()
client_files_manager = self._controller.GetClientFilesManager()
for ( i, media_result ) in enumerate( media_results ):
while job_key.IsPaused() or job_key.IsCancelled():
@ -1843,7 +1856,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
try:
path = ClientFiles.GetFilePath( hash, mime )
path = client_files_manager.GetFilePath( hash, mime )
with open( path, 'rb' ) as f: file = f.read()
@ -1954,6 +1967,8 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
job_key.SetVariable( 'popup_text_1', prefix + 'upload done!' )
job_key.DeleteVariable( 'popup_gauge_1' )
HydrusData.Print( job_key.ToString() )
job_key.Finish()
@ -2105,7 +2120,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif command == '8chan_board': webbrowser.open( 'https://8ch.net/hydrus/index.html' )
elif command == 'file_integrity': self._CheckFileIntegrity()
elif command == 'help': webbrowser.open( 'file://' + HC.BASE_DIR + '/help/index.html' )
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()
@ -2157,6 +2172,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
HydrusGlobals.pubsub_profile_mode = not HydrusGlobals.pubsub_profile_mode
elif command == 'rebalance_client_files': self._RebalanceClientFiles()
elif command == 'redo': self._controller.pub( 'redo' )
elif command == 'refresh':
@ -2178,10 +2194,6 @@ 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 )

View File

@ -710,7 +710,9 @@ class Canvas( object ):
def _CopyPathToClipboard( self ):
path = ClientFiles.GetFilePath( self._current_display_media.GetHash(), self._current_display_media.GetMime() )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( self._current_display_media.GetHash(), self._current_display_media.GetMime() )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', path )
@ -861,7 +863,9 @@ class Canvas( object ):
hash = self._current_display_media.GetHash()
mime = self._current_display_media.GetMime()
path = ClientFiles.GetFilePath( hash, mime )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash, mime )
HydrusPaths.LaunchFile( path )
@ -2716,7 +2720,9 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaListNavigable
def _CopyPathToClipboard( self ):
path = ClientFiles.GetFilePath( self._current_display_media.GetHash(), self._current_display_media.GetMime() )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( self._current_display_media.GetHash(), self._current_display_media.GetMime() )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', path )
@ -3175,7 +3181,9 @@ class MediaContainer( wx.Window ):
self._media_window = wx.lib.flashwin.FlashWindow( self, size = media_initial_size, pos = media_initial_position )
self._media_window.movie = ClientFiles.GetFilePath( self._media.GetHash(), HC.APPLICATION_FLASH )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
self._media_window.movie = client_files_manager.GetFilePath( self._media.GetHash(), HC.APPLICATION_FLASH )
else:
@ -3417,7 +3425,9 @@ class OpenExternallyButton( wx.Button ):
hash = self._media.GetHash()
mime = self._media.GetMime()
path = ClientFiles.GetFilePath( hash, mime )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash, mime )
HydrusPaths.LaunchFile( path )

View File

@ -145,6 +145,8 @@ class AutoCompleteDropdown( wx.Panel ):
wx.Panel.__init__( self, parent )
self._intercept_key_events = True
self._last_search_text = ''
self._next_updatelist_is_probably_fast = False
@ -394,39 +396,87 @@ class AutoCompleteDropdown( wx.Panel ):
HydrusGlobals.client_controller.ResetIdleTimer()
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ) and self._ShouldTakeResponsibilityForEnter():
if event.KeyCode in ( wx.WXK_TAB, wx.WXK_NUMPAD_TAB ):
self._TakeResponsibilityForEnter()
elif event.KeyCode == wx.WXK_ESCAPE:
self.GetTopLevelParent().SetFocus()
elif event.KeyCode in ( wx.WXK_UP, wx.WXK_NUMPAD_UP, wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ) and self._text_ctrl.GetValue() == '' and len( self._dropdown_list ) == 0:
if event.KeyCode in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ): id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'select_up' )
elif event.KeyCode in ( wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ): id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'select_down' )
new_event = wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = id )
self._text_ctrl.ProcessEvent( new_event )
elif event.KeyCode in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN, wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ) and self._text_ctrl.GetValue() == '' and len( self._dropdown_list ) == 0:
if event.KeyCode in ( wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ):
if event.ShiftDown():
id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'canvas_show_previous' )
if self._intercept_key_events:
self._intercept_key_events = False
( r, g, b ) = HC.options[ 'gui_colours' ][ 'autocomplete_background' ]
if r != g or r != b or g != b:
colour = wx.Colour( g, b, r )
elif r > 127:
colour = wx.Colour( g, b, r / 2 )
else:
colour = wx.Colour( g, b, r * 2 )
else:
self._intercept_key_events = True
colour = wx.Colour( *HC.options[ 'gui_colours' ][ 'autocomplete_background' ] )
elif event.KeyCode in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN ):
self._text_ctrl.SetBackgroundColour( colour )
id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'canvas_show_next' )
self._text_ctrl.Refresh()
else:
self._UpdateList()
self._lag_timer.Stop()
new_event = wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = id )
elif self._intercept_key_events:
self._text_ctrl.ProcessEvent( new_event )
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ) and self._ShouldTakeResponsibilityForEnter():
self._TakeResponsibilityForEnter()
elif event.KeyCode == wx.WXK_ESCAPE:
self.GetTopLevelParent().SetFocus()
elif event.KeyCode in ( wx.WXK_UP, wx.WXK_NUMPAD_UP, wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ) and self._text_ctrl.GetValue() == '' and len( self._dropdown_list ) == 0:
if event.KeyCode in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ): id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'select_up' )
elif event.KeyCode in ( wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ): id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'select_down' )
new_event = wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = id )
self._text_ctrl.ProcessEvent( new_event )
elif event.KeyCode in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN, wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ) and self._text_ctrl.GetValue() == '' and len( self._dropdown_list ) == 0:
if event.KeyCode in ( wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ):
id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'canvas_show_previous' )
elif event.KeyCode in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN ):
id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'canvas_show_next' )
new_event = wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = id )
self._text_ctrl.ProcessEvent( new_event )
else: self._dropdown_list.ProcessEvent( event )
else:
event.Skip()
else: self._dropdown_list.ProcessEvent( event )
def EventKillFocus( self, event ):
@ -500,11 +550,20 @@ class AutoCompleteDropdown( wx.Panel ):
num_chars = len( self._text_ctrl.GetValue() )
( char_limit, long_wait, short_wait ) = HC.options[ 'ac_timings' ]
if num_chars == 0 or self._next_updatelist_is_probably_fast: self._UpdateList()
elif num_chars < char_limit: self._lag_timer.Start( long_wait, wx.TIMER_ONE_SHOT )
else: self._lag_timer.Start( short_wait, wx.TIMER_ONE_SHOT )
if num_chars == 0:
self._UpdateList()
elif HC.options[ 'fetch_ac_results_automatically' ]:
( char_limit, long_wait, short_wait ) = HC.options[ 'ac_timings' ]
self._next_updatelist_is_probably_fast = self._next_updatelist_is_probably_fast and num_chars > len( self._last_search_text )
if self._next_updatelist_is_probably_fast: self._UpdateList()
elif num_chars < char_limit: self._lag_timer.Start( long_wait, wx.TIMER_ONE_SHOT )
else: self._lag_timer.Start( short_wait, wx.TIMER_ONE_SHOT )
def TIMEREventDropdownHide( self, event ):
@ -680,7 +739,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
def __init__( self, parent, page_key, file_service_key, tag_service_key, media_callable = None, include_current = True, include_pending = True, synchronised = True ):
def __init__( self, parent, page_key, file_service_key, tag_service_key, media_callable = None, include_current = True, include_pending = True, synchronised = True, include_unusual_predicate_types = True ):
AutoCompleteDropdownTags.__init__( self, parent, file_service_key, tag_service_key )
@ -698,6 +757,8 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._synchronised = OnOffButton( self._dropdown_window, self._page_key, 'notify_search_immediately', on_label = 'searching immediately', off_label = 'waiting -- tag counts may be inaccurate', start_on = synchronised )
self._synchronised.SetToolTipString( 'select whether to renew the search as soon as a new predicate is entered' )
self._include_unusual_predicate_types = include_unusual_predicate_types
button_hbox_1 = wx.BoxSizer( wx.HORIZONTAL )
button_hbox_1.AddF( self._include_current_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
@ -833,6 +894,8 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
half_complete_tag = search_text
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
if half_complete_tag == '':
self._cache_text = self._current_namespace + ':'
@ -858,7 +921,9 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, tag = search_text, include_current = self._include_current, include_pending = self._include_pending, add_namespaceless = True )
predicates = ClientSearch.SortPredicates( predicates, collapse_siblings = True )
predicates = siblings_manager.CollapsePredicates( predicates )
predicates = ClientSearch.SortPredicates( predicates )
else:
@ -868,7 +933,9 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._cached_results = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, half_complete_tag = search_text, include_current = self._include_current, include_pending = self._include_pending, add_namespaceless = True )
self._cached_results = ClientSearch.SortPredicates( self._cached_results, collapse_siblings = False )
self._cached_results = siblings_manager.CollapsePredicates( self._cached_results )
self._cached_results = ClientSearch.SortPredicates( self._cached_results )
predicates = self._cached_results
@ -908,7 +975,9 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
predicates = [ ClientData.Predicate( HC.PREDICATE_TYPE_TAG, tag, inclusive = inclusive, counts = { HC.CURRENT : current_tags_to_count[ tag ], HC.PENDING : pending_tags_to_count[ tag ] } ) for tag in tags_to_do ]
predicates = ClientSearch.SortPredicates( predicates, collapse_siblings = True )
predicates = siblings_manager.CollapsePredicates( predicates )
predicates = ClientSearch.SortPredicates( predicates )
self._next_updatelist_is_probably_fast = True
@ -916,24 +985,27 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
matches = ClientSearch.FilterPredicates( search_text, predicates )
if self._current_namespace != '':
if self._include_unusual_predicate_types:
if '*' not in self._current_namespace:
if self._current_namespace != '':
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_NAMESPACE, self._current_namespace, inclusive = inclusive ) )
if half_complete_tag != '':
if '*' in self._current_namespace or ( '*' in half_complete_tag and half_complete_tag != '*' ):
if '*' not in self._current_namespace:
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive = inclusive ) )
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_NAMESPACE, self._current_namespace, inclusive = inclusive ) )
elif '*' in search_text:
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive = inclusive ) )
if half_complete_tag != '':
if '*' in self._current_namespace or ( '*' in half_complete_tag and half_complete_tag != '*' ):
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive = inclusive ) )
elif '*' in search_text:
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive = inclusive ) )
try:
@ -1144,7 +1216,7 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, tag = search_text, add_namespaceless = False )
predicates = ClientSearch.SortPredicates( predicates, collapse_siblings = False )
predicates = ClientSearch.SortPredicates( predicates )
else:
@ -1154,7 +1226,7 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
self._cached_results = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, half_complete_tag = search_text, add_namespaceless = False )
self._cached_results = ClientSearch.SortPredicates( self._cached_results, collapse_siblings = False )
self._cached_results = ClientSearch.SortPredicates( self._cached_results )
predicates = self._cached_results
@ -1304,8 +1376,8 @@ class BufferedWindow( wx.Window ):
self._dirty = True
self.Refresh()
self.Refresh()
class BufferedWindowIcon( BufferedWindow ):
@ -3746,12 +3818,13 @@ class ListCtrlAutoWidth( wx.ListCtrl, ListCtrlAutoWidthMixin ):
class NoneableSpinCtrl( wx.Panel ):
def __init__( self, parent, message, none_phrase = 'no limit', min = 0, max = 1000000, multiplier = 1, num_dimensions = 1 ):
def __init__( self, parent, message, none_phrase = 'no limit', min = 0, max = 1000000, unit = None, multiplier = 1, num_dimensions = 1 ):
wx.Panel.__init__( self, parent )
self._num_dimensions = num_dimensions
self._unit = unit
self._multiplier = multiplier
self._num_dimensions = num_dimensions
self._checkbox = wx.CheckBox( self, label = none_phrase )
self._checkbox.Bind( wx.EVT_CHECKBOX, self.EventCheckBox )
@ -3775,6 +3848,11 @@ class NoneableSpinCtrl( wx.Panel ):
hbox.AddF( self._two, CC.FLAGS_MIXED )
if self._unit is not None:
hbox.AddF( wx.StaticText( self, label = unit ), CC.FLAGS_MIXED )
hbox.AddF( self._checkbox, CC.FLAGS_MIXED )
self.SetSizer( hbox )
@ -5767,4 +5845,58 @@ class ShowKeys( Frame ):
class WaitingPolitely( BufferedWindow ):
def __init__( self, parent, page_key ):
BufferedWindow.__init__( self, parent, size = ( 19, 19 ) )
self._page_key = page_key
self._waiting = False
HydrusGlobals.client_controller.sub( self, 'SetWaitingPolitely', 'waiting_politely' )
self.SetWaitingPolitely( self._page_key, False )
def _Draw( self, dc ):
dc.SetBackground( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
dc.Clear()
if self._waiting:
dc.SetBrush( wx.Brush( wx.Colour( 250, 190, 77 ) ) )
else:
dc.SetBrush( wx.Brush( wx.Colour( 77, 250, 144 ) ) )
dc.SetPen( wx.BLACK_PEN )
dc.DrawCircle( 9, 9, 7 )
def SetWaitingPolitely( self, page_key, value ):
if page_key == self._page_key:
self._waiting = value
if self._waiting:
self.SetToolTipString( 'waiting before attempting another download' )
else:
self.SetToolTipString( 'ready to download' )
self._dirty = True
self.Refresh()

View File

@ -648,7 +648,7 @@ class DialogFirstStart( Dialog ):
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
message1 = 'Hi, this looks like the first time you have started the hydrus client. Don\'t forget to check out the'
link = wx.HyperlinkCtrl( self, id = -1, label = 'help', url = 'file://' + HC.BASE_DIR + '/help/index.html' )
link = wx.HyperlinkCtrl( self, id = -1, label = 'help', url = 'file://' + HC.HELP_DIR + '/index.html' )
message2 = 'if you haven\'t already.'
message3 = 'When you close this dialog, the client will start its local http server. You will probably get a firewall warning.'
message4 = 'You can block it if you like, or you can allow it. It doesn\'t phone home, or expose your files to your network; it just provides another way to locally export your files.'
@ -4468,6 +4468,8 @@ class DialogSetupExport( Dialog ):
terms = ClientFiles.ParseExportPhrase( pattern )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
for ( ( ordering_index, media ), mime, path ) in self._paths.GetClientData():
try:
@ -4490,10 +4492,10 @@ class DialogSetupExport( Dialog ):
source_path = ClientFiles.GetFilePath( hash, mime )
source_path = client_files_manager.GetFilePath( hash, mime )
shutil.copy2( source_path, path )
shutil.copy( source_path, path )
shutil.copystat( source_path, path )
try: os.chmod( path, stat.S_IWRITE | stat.S_IREAD )
except: pass

View File

@ -3069,6 +3069,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._listbook.AddPage( 'local server', self._ServerPanel( self._listbook ) )
self._listbook.AddPage( 'sort/collect', self._SortCollectPanel( self._listbook ) )
self._listbook.AddPage( 'shortcuts', self._ShortcutsPanel( self._listbook ) )
self._listbook.AddPage( 'file storage locations', self._ClientFilesPanel( self._listbook ) )
self._listbook.AddPage( 'downloading', self._DownloadingPanel( self._listbook ) )
self._listbook.AddPage( 'tags', self._TagsPanel( self._listbook, self._new_options ) )
@ -3106,7 +3107,136 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
wx.CallAfter( self._ok.SetFocus )
class _ClientFilesPanel( wx.Panel ):
def __init__( self, parent ):
wx.Panel.__init__( self, parent )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._client_files = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'path', -1 ), ( 'weight', 80 ) ] )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._edit = wx.Button( self, label = 'edit weight' )
self._edit.Bind( wx.EVT_BUTTON, self.EventEditWeight )
self._delete = wx.Button( self, label = 'delete' )
self._delete.Bind( wx.EVT_BUTTON, self.EventDelete )
#
self._new_options = HydrusGlobals.client_controller.GetNewOptions()
for ( location, weight ) in self._new_options.GetClientFilesLocationsToIdealWeights().items():
self._client_files.Append( ( location, HydrusData.ConvertIntToPrettyString( int( weight ) ) ), ( location, weight ) )
#
vbox = wx.BoxSizer( wx.VERTICAL )
text = 'Here you can change the folders where the client stores your files. Setting a higher weight increases the proportion of your collection that that folder stores.'
text += os.linesep * 2
text += 'If you add or remove folders here, it will take time for the client to incrementally rebalance your files across the new selection, but if you are in a hurry, you can force a full rebalance from the database->maintenance menu on the main gui.'
st = wx.StaticText( self, label = text )
st.Wrap( 400 )
vbox.AddF( st, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._client_files, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._add, CC.FLAGS_MIXED )
hbox.AddF( self._edit, CC.FLAGS_MIXED )
hbox.AddF( self._delete, CC.FLAGS_MIXED )
vbox.AddF( hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
def EventAdd( self, event ):
with wx.DirDialog( self, 'Select the file location' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
for ( location, weight ) in self._client_files.GetClientData():
if path == location:
wx.MessageBox( 'You already have that location entered!' )
return
with wx.NumberEntryDialog( self, 'Enter the weight of ' + path + '.', '', 'Enter Weight', value = 1, min = 1, max = 256 ) as dlg_num:
if dlg_num.ShowModal() == wx.ID_OK:
weight = dlg_num.GetValue()
weight = float( weight )
self._client_files.Append( ( path, HydrusData.ConvertIntToPrettyString( int( weight ) ) ), ( path, weight ) )
def EventDelete( self, event ):
if len( self._client_files.GetAllSelected() ) < self._client_files.GetItemCount():
self._client_files.RemoveAllSelected()
def EventEditWeight( self, event ):
for i in self._client_files.GetAllSelected():
( location, weight ) = self._client_files.GetClientData( i )
with wx.NumberEntryDialog( self, 'Enter the weight of ' + location + '.', '', 'Enter Weight', value = int( weight ), min = 1, max = 256 ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
weight = dlg.GetValue()
weight = float( weight )
self._client_files.UpdateRow( i, ( location, HydrusData.ConvertIntToPrettyString( int( weight ) ) ), ( location, weight ) )
def UpdateOptions( self ):
locations_to_weights = {}
for ( location, weight ) in self._client_files.GetClientData():
locations_to_weights[ location ] = weight
self._new_options.SetClientFilesLocationsToIdealWeights( locations_to_weights )
class _ColoursPanel( wx.Panel ):
def __init__( self, parent ):
@ -3474,13 +3604,16 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._idle_panel = ClientGUICommon.StaticBox( self, 'when to run big jobs' )
self._idle_panel = ClientGUICommon.StaticBox( self, 'when to run high cpu jobs' )
self._maintenance_panel = ClientGUICommon.StaticBox( self, 'maintenance period' )
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_normal = wx.CheckBox( self._idle_panel )
self._idle_normal.Bind( wx.EVT_CHECKBOX, self.EventIdleNormal )
self._idle_period = ClientGUICommon.NoneableSpinCtrl( self._idle_panel, '', min = 1, max = 1000, multiplier = 60, unit = 'minutes', none_phrase = 'ignore normal browsing' )
self._idle_mouse_period = ClientGUICommon.NoneableSpinCtrl( self._idle_panel, '', min = 1, max = 1000, multiplier = 60, unit = 'minutes', none_phrase = 'ignore mouse movements' )
self._idle_cpu_max = ClientGUICommon.NoneableSpinCtrl( self._idle_panel, '', min = 0, max = 99, unit = '%', none_phrase = 'ignore cpu usage' )
self._idle_shutdown = ClientGUICommon.BetterChoice( self._idle_panel )
@ -3489,22 +3622,25 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._idle_shutdown.Append( CC.idle_string_lookup[ idle_id ], idle_id )
self._idle_shutdown.Bind( wx.EVT_CHOICE, self.EventIdleShutdown )
self._idle_shutdown_max_minutes = wx.SpinCtrl( self._idle_panel, min = 1, max = 1440 )
self._maintenance_vacuum_period = wx.SpinCtrl( self._maintenance_panel, min = 0, max = 365 )
self._maintenance_vacuum_period = ClientGUICommon.NoneableSpinCtrl( self._maintenance_panel, '', min = 1, max = 365, multiplier = 86400, none_phrase = 'do not automatically vacuum' )
self._processing_phase = wx.SpinCtrl( self._processing_panel, min = 0, max = 100000 )
self._processing_phase.SetToolTipString( 'how long this client will delay processing updates after they are due. useful if you have multiple clients and do not want them to process at the same time' )
#
self._idle_period.SetValue( HC.options[ 'idle_period' ] / 60 )
self._idle_mouse_period.SetValue( HC.options[ 'idle_mouse_period' ] / 60 )
self._idle_normal.SetValue( HC.options[ 'idle_normal' ] )
self._idle_period.SetValue( HC.options[ 'idle_period' ] )
self._idle_mouse_period.SetValue( HC.options[ 'idle_mouse_period' ] )
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' ] )
self._maintenance_vacuum_period.SetValue( HC.options[ 'maintenance_vacuum_period' ] / 86400 )
self._maintenance_vacuum_period.SetValue( HC.options[ 'maintenance_vacuum_period' ] )
self._processing_phase.SetValue( HC.options[ 'processing_phase' ] )
@ -3514,13 +3650,16 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Minutes of general client inactivity until client can be considered idle: ' ), CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Run jobs when the client is not busy?: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._idle_normal, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Assume the client is busy if general browsing activity has occured in the past: ' ), 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 can be considered idle: ' ), CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Assume the client is busy if the mouse has been moved in the past: ' ), 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( wx.StaticText( self._idle_panel, label = 'Assume the client is busy if any CPU core has recent average usage above: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._idle_cpu_max, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Run jobs on shutdown?: ' ), CC.FLAGS_MIXED )
@ -3529,13 +3668,13 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddF( wx.StaticText( self._idle_panel, label = 'Max number of minutes to run shutdown jobs: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._idle_shutdown_max_minutes, CC.FLAGS_MIXED )
text = 'CPU-heavy jobs like maintenance routines and repository synchronisation processing will only start by themselves when you are not using the client.'
text = 'CPU-heavy jobs like maintenance routines and repository synchronisation processing will stutter or lock up your gui, so they do not normally run when you are searching for and looking at files.'
text += os.linesep * 2
text += 'If you set both client inactivy and unmoving mouse to 0, the client will never consider itself idle and you should set up shutdown processing.'
text += 'You can set these jobs to run only when the client is not busy, or only during shutdown, or neither, or both.'
st = wx.StaticText( self._idle_panel, label = text )
st.Wrap( 400 )
st.Wrap( 500 )
self._idle_panel.AddF( st, CC.FLAGS_EXPAND_PERPENDICULAR )
self._idle_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -3546,7 +3685,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._maintenance_panel, label = 'Number of days to wait between vacuums (0 for never): ' ), CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._maintenance_panel, label = 'Number of days to wait between vacuums: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._maintenance_vacuum_period, CC.FLAGS_MIXED )
self._maintenance_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -3557,7 +3696,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._processing_panel, label = 'Delay update processing by (s): ' ), CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._processing_panel, label = 'Delay repository update processing by (s): ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._processing_phase, CC.FLAGS_MIXED )
self._processing_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -3572,16 +3711,60 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self.SetSizer( vbox )
self._EnableDisableIdleNormal()
self._EnableDisableIdleShutdown()
def _EnableDisableIdleNormal( self ):
if self._idle_normal.GetValue() == True:
self._idle_period.Enable()
self._idle_mouse_period.Enable()
self._idle_cpu_max.Enable()
else:
self._idle_period.Disable()
self._idle_mouse_period.Disable()
self._idle_cpu_max.Disable()
def _EnableDisableIdleShutdown( self ):
if self._idle_shutdown.GetChoice() == CC.IDLE_NOT_ON_SHUTDOWN:
self._idle_shutdown_max_minutes.Disable()
else:
self._idle_shutdown_max_minutes.Enable()
def EventIdleNormal( self, event ):
self._EnableDisableIdleNormal()
def EventIdleShutdown( self, event ):
self._EnableDisableIdleShutdown()
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_normal' ] = self._idle_normal.GetValue()
HC.options[ 'idle_period' ] = self._idle_period.GetValue()
HC.options[ 'idle_mouse_period' ] = 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()
HC.options[ 'maintenance_vacuum_period' ] = 86400 * self._maintenance_vacuum_period.GetValue()
HC.options[ 'maintenance_vacuum_period' ] = self._maintenance_vacuum_period.GetValue()
HC.options[ 'processing_phase' ] = self._processing_phase.GetValue()
@ -4427,16 +4610,19 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._estimated_number_fullscreens = wx.StaticText( self, label = '' )
self._num_autocomplete_chars = wx.SpinCtrl( self, min = 1, max = 100 )
self._num_autocomplete_chars.SetToolTipString( 'how many characters you enter before the gui fetches autocomplete results from the db' + os.linesep + 'increase this if you find autocomplete results are slow' )
self._num_autocomplete_chars.SetToolTipString( 'how many characters you enter before the gui fetches autocomplete results from the db. (otherwise, it will only fetch exact matches)' + os.linesep + 'increase this if you find autocomplete results are slow' )
self._fetch_ac_results_automatically = wx.CheckBox( self )
self._fetch_ac_results_automatically.Bind( wx.EVT_CHECKBOX, self.EventFetchAuto )
self._autocomplete_long_wait = wx.SpinCtrl( self, min = 0, max = 10000 )
self._autocomplete_long_wait.SetToolTipString( 'how long the gui will wait, after you enter a character, before it queries the db with what you have entered so far' )
self._autocomplete_long_wait.SetToolTipString( 'how long the gui will typically wait, after you enter a character, before it queries the db with what you have entered so far' )
self._autocomplete_short_wait_chars = wx.SpinCtrl( self, min = 1, max = 100 )
self._autocomplete_short_wait_chars.SetToolTipString( 'how many characters you enter before the gui starts waiting the short time before querying the db' )
self._autocomplete_short_wait = wx.SpinCtrl( self, min = 0, max = 10000 )
self._autocomplete_short_wait.SetToolTipString( 'how long the gui will wait, after you enter a lot of characters, before it queries the db with what you have entered so far' )
self._autocomplete_short_wait.SetToolTipString( 'how long the gui will typically wait, after you enter a lot of characters, before it queries the db with what you have entered so far' )
#
@ -4454,6 +4640,8 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._num_autocomplete_chars.SetValue( HC.options[ 'num_autocomplete_chars' ] )
self._fetch_ac_results_automatically.SetValue( HC.options[ 'fetch_ac_results_automatically' ] )
( char_limit, long_wait, short_wait ) = HC.options[ 'ac_timings' ]
self._autocomplete_long_wait.SetValue( long_wait )
@ -4479,6 +4667,8 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
fullscreens_sizer.AddF( self._fullscreen_cache_size, CC.FLAGS_MIXED )
fullscreens_sizer.AddF( self._estimated_number_fullscreens, CC.FLAGS_MIXED )
vbox = wx.BoxSizer( wx.VERTICAL )
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
@ -4489,6 +4679,12 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddF( wx.StaticText( self, label = 'Thumbnail height: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._thumbnail_height, CC.FLAGS_MIXED )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self, label = 'MB memory reserved for thumbnail cache: ' ), CC.FLAGS_MIXED )
gridbox.AddF( thumbnails_sizer, CC.FLAGS_NONE )
@ -4498,9 +4694,22 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddF( wx.StaticText( self, label = 'MB memory reserved for media viewer cache: ' ), CC.FLAGS_MIXED )
gridbox.AddF( fullscreens_sizer, CC.FLAGS_NONE )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
text = 'If you want to disable automatic autocomplete results fetching, use the Tab key to fetch results manually.'
vbox.AddF( wx.StaticText( self, label = text ), CC.FLAGS_EXPAND_PERPENDICULAR )
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self, label = 'Autocomplete character threshold: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._num_autocomplete_chars, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'Automatically fetch autocomplete results after a short delay: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._fetch_ac_results_automatically, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'Autocomplete long wait (ms): ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._autocomplete_long_wait, CC.FLAGS_MIXED )
@ -4510,10 +4719,13 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddF( wx.StaticText( self, label = 'Autocomplete short wait (ms): ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._autocomplete_short_wait, CC.FLAGS_MIXED )
self.SetSizer( gridbox )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
#
self.EventFetchAuto( None )
self.EventFullscreensUpdate( None )
self.EventPreviewsUpdate( None )
self.EventThumbnailsUpdate( None )
@ -4521,6 +4733,22 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
wx.CallAfter( self.Layout ) # draws the static texts correctly
def EventFetchAuto( self, event ):
if self._fetch_ac_results_automatically.GetValue() == True:
self._autocomplete_long_wait.Enable()
self._autocomplete_short_wait_chars.Enable()
self._autocomplete_short_wait.Enable()
else:
self._autocomplete_long_wait.Disable()
self._autocomplete_short_wait_chars.Disable()
self._autocomplete_short_wait.Disable()
def EventFullscreensUpdate( self, event ):
( width, height ) = wx.GetDisplaySize()
@ -4566,6 +4794,8 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
HC.options[ 'num_autocomplete_chars' ] = self._num_autocomplete_chars.GetValue()
HC.options[ 'fetch_ac_results_automatically' ] = self._fetch_ac_results_automatically.GetValue()
long_wait = self._autocomplete_long_wait.GetValue()
char_limit = self._autocomplete_short_wait_chars.GetValue()
@ -6562,7 +6792,7 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
( service_key, service_type, name, info ) = self._original_info
message = 'This will completely reset ' + name + ', deleting all downloaded and processed information from the database. It may take several minutes to finish the operation, during which time the gui will likely freeze.' + os.linesep * 2 + 'Once the service is reset, the client will have to redownload and reprocess everything, which could take a very long time.' + os.linesep * 2 + 'If you do not understand what this button does, you definitely want to click no!'
message = 'This will completely reset ' + name + ', deleting all downloaded and processed information from the database. It may take several minutes to finish the operation, during which time the gui will likely freeze.' + os.linesep * 2 + 'Once the service is reset, the client will slowly redownload and reprocess everything in the background.' + os.linesep * 2 + 'If you do not understand what this button does, you definitely want to click no!'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
@ -6579,7 +6809,7 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
( service_key, service_type, name, info ) = self._original_info
message = 'This will remove all the processed information for ' + name + ' from the database. It may take several minutes to finish the operation, during which time the gui will likely freeze.' + os.linesep * 2 + 'Once the service is reset, the client will have to reprocess all the downloaded updates, which could take a very long time.' + os.linesep * 2 + 'If you do not understand what this button does, you probably want to click no!'
message = 'This will remove all the processed information for ' + name + ' from the database. It may take several minutes to finish the operation, during which time the gui will likely freeze.' + os.linesep * 2 + 'Once the service is reset, the client will slowly reprocess everything in the background.' + os.linesep * 2 + 'If you do not understand what this button does, you probably want to click no!'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:

View File

@ -26,7 +26,7 @@ class FullscreenHoverFrame( wx.Frame ):
self._canvas_key = canvas_key
self._current_media = None
self._last_ideal_size_and_position = None
self._last_ideal_position = None
self.SetBackgroundColour( wx.WHITE )
self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) )
@ -51,20 +51,16 @@ class FullscreenHoverFrame( wx.Frame ):
( should_resize, my_ideal_size, my_ideal_position ) = self._GetIdealSizeAndPosition()
if should_resize or ( my_ideal_size, my_ideal_position ) != self._last_ideal_size_and_position:
if should_resize:
self._last_ideal_size_and_position = ( my_ideal_size, my_ideal_position )
self.Fit()
if should_resize:
self.Fit()
self.SetSize( my_ideal_size )
self.SetPosition( my_ideal_position )
self.SetSize( my_ideal_size )
self.SetPosition( my_ideal_position )
def SetDisplayMedia( self, canvas_key, media ):

View File

@ -135,13 +135,13 @@ def CreateManagementControllerQuery( file_service_key, file_search_context, sear
return management_controller
def CreateManagementPanel( parent, page, management_controller ):
def CreateManagementPanel( parent, page, controller, management_controller ):
management_type = management_controller.GetType()
management_class = management_panel_types_to_classes[ management_type ]
management_panel = management_class( parent, page, management_controller )
management_panel = management_class( parent, page, controller, management_controller )
return management_panel
@ -372,13 +372,13 @@ def GenerateDumpMultipartFormDataCTAndBody( fields ):
def EventRefreshCaptcha( self, event ):
javascript_string = HydrusGlobals.client_controller.DoHTTP( HC.GET, 'http://www.google.com/recaptcha/api/challenge?k=' + self._captcha_key )
javascript_string = self._controller.DoHTTP( HC.GET, 'http://www.google.com/recaptcha/api/challenge?k=' + self._captcha_key )
( trash, rest ) = javascript_string.split( 'challenge : \'', 1 )
( self._captcha_challenge, trash ) = rest.split( '\'', 1 )
jpeg = HydrusGlobals.client_controller.DoHTTP( HC.GET, 'http://www.google.com/recaptcha/api/image?c=' + self._captcha_challenge )
jpeg = self._controller.DoHTTP( HC.GET, 'http://www.google.com/recaptcha/api/image?c=' + self._captcha_challenge )
( os_file_handle, temp_path ) = HydrusPaths.GetTempPath()
@ -621,7 +621,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class ManagementPanel( wx.lib.scrolledpanel.ScrolledPanel ):
def __init__( self, parent, page, management_controller ):
def __init__( self, parent, page, controller, management_controller ):
wx.lib.scrolledpanel.ScrolledPanel.__init__( self, parent, style = wx.BORDER_NONE | wx.VSCROLL )
@ -629,12 +629,13 @@ class ManagementPanel( wx.lib.scrolledpanel.ScrolledPanel ):
self.SetBackgroundColour( wx.WHITE )
self._controller = management_controller
self._controller = controller
self._management_controller = management_controller
self._page = page
self._page_key = self._controller.GetKey( 'page' )
self._page_key = self._management_controller.GetKey( 'page' )
HydrusGlobals.client_controller.sub( self, 'SetSearchFocus', 'set_search_focus' )
self._controller.sub( self, 'SetSearchFocus', 'set_search_focus' )
def _MakeCollect( self, sizer ):
@ -670,11 +671,11 @@ class ManagementPanel( wx.lib.scrolledpanel.ScrolledPanel ):
'''
class ManagementPanelDumper( ManagementPanel ):
def __init__( self, parent, page, management_controller ):
def __init__( self, parent, page, controller, management_controller ):
ManagementPanel.__init__( self, parent, page, management_controller )
ManagementPanel.__init__( self, parent, page, controller, management_controller )
( self._4chan_token, pin, timeout ) = HydrusGlobals.client_controller.Read( '4chan_pass' )
( self._4chan_token, pin, timeout ) = self._controller.Read( '4chan_pass' )
self._have_4chan_pass = timeout > HydrusData.GetNow()
@ -802,8 +803,8 @@ class ManagementPanelDumper( ManagementPanel ):
self.SetSizer( vbox )
HydrusGlobals.client_controller.sub( self, 'FocusChanged', 'focus_changed' )
HydrusGlobals.client_controller.sub( self, 'SortedMediaPulse', 'sorted_media_pulse' )
self._controller.sub( self, 'FocusChanged', 'focus_changed' )
self._controller.sub( self, 'SortedMediaPulse', 'sorted_media_pulse' )
self._sorted_media_hashes = [ media_result.GetHash() for media_result in media_results ]
@ -841,7 +842,7 @@ class ManagementPanelDumper( ManagementPanel ):
try:
response = HydrusGlobals.client_controller.DoHTTP( HC.POST, self._post_url, request_headers = headers, body = body )
response = self._controller.DoHTTP( HC.POST, self._post_url, request_headers = headers, body = body )
( status, phrase ) = ClientDownloading.Parse4chanPostScreen( response )
@ -894,7 +895,7 @@ class ManagementPanelDumper( ManagementPanel ):
tags_manager = media.GetTagsManager()
try: service = HydrusGlobals.client_controller.GetServicesManager().GetService( service_key )
try: service = self._controller.GetServicesManager().GetService( service_key )
except HydrusExceptions.NotFoundException: continue
service_key = service.GetServiceKey()
@ -1028,7 +1029,7 @@ class ManagementPanelDumper( ManagementPanel ):
dump_status_enum = CC.DUMPER_DUMPED_OK
dump_status_string = 'dumped ok'
if hash == self._current_hash: HydrusGlobals.client_controller.pub( 'set_focus', self._page_key, None )
if hash == self._current_hash: self._controller.pub( 'set_focus', self._page_key, None )
self._next_dump_time = HydrusData.GetNow() + self._flood_time
@ -1100,7 +1101,7 @@ class ManagementPanelDumper( ManagementPanel ):
dump_status_enum = CC.DUMPER_UNRECOVERABLE_ERROR
dump_status_string = phrase
if hash == self._current_hash: HydrusGlobals.client_controller.pub( 'set_focus', self._page_key, None )
if hash == self._current_hash: self._controller.pub( 'set_focus', self._page_key, None )
self._next_dump_time = HydrusData.GetNow() + self._flood_time
@ -1109,7 +1110,7 @@ class ManagementPanelDumper( ManagementPanel ):
self._hashes_to_dump_info[ hash ] = ( dump_status_enum, dump_status_string, post_field_info )
HydrusGlobals.client_controller.pub( 'file_dumped', self._page_key, hash, dump_status_enum )
self._controller.pub( 'file_dumped', self._page_key, hash, dump_status_enum )
if self._next_dump_index == len( self._sorted_media_hashes ):
@ -1262,7 +1263,7 @@ class ManagementPanelDumper( ManagementPanel ):
media_to_select = self._hashes_to_media[ hash_to_select ]
HydrusGlobals.client_controller.pub( 'set_focus', self._page_key, media_to_select )
self._controller.pub( 'set_focus', self._page_key, media_to_select )
@ -1363,7 +1364,9 @@ class ManagementPanelDumper( ManagementPanel ):
mime = media.GetMime()
path = ClientFiles.GetFilePath( hash, mime )
client_files_manager = self._controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash, mime )
with open( path, 'rb' ) as f: file = f.read()
@ -1377,7 +1380,7 @@ class ManagementPanelDumper( ManagementPanel ):
self._actually_dumping = True
HydrusGlobals.client_controller.CallToThread( self._THREADDoDump, hash, post_field_info, headers, body )
self._controller.CallToThread( self._THREADDoDump, hash, post_field_info, headers, body )
except Exception as e:
@ -1400,9 +1403,9 @@ management_panel_types_to_classes[ MANAGEMENT_TYPE_DUMPER ] = ManagementPanelDum
'''
class ManagementPanelGalleryImport( ManagementPanel ):
def __init__( self, parent, page, management_controller ):
def __init__( self, parent, page, controller, management_controller ):
ManagementPanel.__init__( self, parent, page, management_controller )
ManagementPanel.__init__( self, parent, page, controller, management_controller )
self._gallery_downloader_panel = ClientGUICommon.StaticBox( self, 'gallery downloader' )
@ -1413,6 +1416,8 @@ class ManagementPanelGalleryImport( ManagementPanel ):
self._file_gauge = ClientGUICommon.Gauge( self._import_queue_panel )
self._overall_gauge = ClientGUICommon.Gauge( self._import_queue_panel )
self._waiting_politely_indicator = ClientGUICommon.WaitingPolitely( self._import_queue_panel, self._page_key )
self._seed_cache_button = wx.BitmapButton( self._import_queue_panel, bitmap = CC.GlobalBMPs.seed_cache )
self._seed_cache_button.Bind( wx.EVT_BUTTON, self.EventSeedCache )
self._seed_cache_button.SetToolTipString( 'open detailed file import status' )
@ -1487,6 +1492,7 @@ class ManagementPanelGalleryImport( ManagementPanel ):
button_sizer = wx.BoxSizer( wx.HORIZONTAL )
button_sizer.AddF( self._waiting_politely_indicator, CC.FLAGS_MIXED )
button_sizer.AddF( self._seed_cache_button, CC.FLAGS_MIXED )
button_sizer.AddF( self._files_pause_button, CC.FLAGS_MIXED )
@ -1520,9 +1526,9 @@ class ManagementPanelGalleryImport( ManagementPanel ):
self.Bind( wx.EVT_MENU, self.EventMenu )
HydrusGlobals.client_controller.sub( self, 'UpdateStatus', 'update_status' )
self._controller.sub( self, 'UpdateStatus', 'update_status' )
self._gallery_import = self._controller.GetVariable( 'gallery_import' )
self._gallery_import = self._management_controller.GetVariable( 'gallery_import' )
gallery_identifier = self._gallery_import.GetGalleryIdentifier()
@ -1763,7 +1769,7 @@ class ManagementPanelGalleryImport( ManagementPanel ):
seed_cache = self._gallery_import.GetSeedCache()
HydrusGlobals.client_controller.pub( 'show_seed_cache', seed_cache )
self._controller.pub( 'show_seed_cache', seed_cache )
def SetSearchFocus( self, page_key ):
@ -1783,9 +1789,9 @@ management_panel_types_to_classes[ MANAGEMENT_TYPE_IMPORT_GALLERY ] = Management
class ManagementPanelHDDImport( ManagementPanel ):
def __init__( self, parent, page, management_controller ):
def __init__( self, parent, page, controller, management_controller ):
ManagementPanel.__init__( self, parent, page, management_controller )
ManagementPanel.__init__( self, parent, page, controller, management_controller )
self._import_queue_panel = ClientGUICommon.StaticBox( self, 'import summary' )
@ -1824,9 +1830,9 @@ class ManagementPanelHDDImport( ManagementPanel ):
#
HydrusGlobals.client_controller.sub( self, 'UpdateStatus', 'update_status' )
self._controller.sub( self, 'UpdateStatus', 'update_status' )
self._hdd_import = self._controller.GetVariable( 'hdd_import' )
self._hdd_import = self._management_controller.GetVariable( 'hdd_import' )
self._Update()
@ -1904,7 +1910,7 @@ class ManagementPanelHDDImport( ManagementPanel ):
seed_cache = self._hdd_import.GetSeedCache()
HydrusGlobals.client_controller.pub( 'show_seed_cache', seed_cache )
self._controller.pub( 'show_seed_cache', seed_cache )
def TestAbleToClose( self ):
@ -1935,9 +1941,9 @@ management_panel_types_to_classes[ MANAGEMENT_TYPE_IMPORT_HDD ] = ManagementPane
class ManagementPanelPageOfImagesImport( ManagementPanel ):
def __init__( self, parent, page, management_controller ):
def __init__( self, parent, page, controller, management_controller ):
ManagementPanel.__init__( self, parent, page, management_controller )
ManagementPanel.__init__( self, parent, page, controller, management_controller )
self._page_of_images_panel = ClientGUICommon.StaticBox( self, 'page of images downloader' )
@ -1952,12 +1958,15 @@ class ManagementPanelPageOfImagesImport( ManagementPanel ):
self._pause_button = wx.BitmapButton( self._import_queue_panel, bitmap = CC.GlobalBMPs.pause )
self._pause_button.Bind( wx.EVT_BUTTON, self.EventPause )
self._waiting_politely_indicator = ClientGUICommon.WaitingPolitely( self._import_queue_panel, self._page_key )
self._seed_cache_button = wx.BitmapButton( self._import_queue_panel, bitmap = CC.GlobalBMPs.seed_cache )
self._seed_cache_button.Bind( wx.EVT_BUTTON, self.EventSeedCache )
self._seed_cache_button.SetToolTipString( 'open detailed file import status' )
button_sizer = wx.BoxSizer( wx.HORIZONTAL )
button_sizer.AddF( self._waiting_politely_indicator, CC.FLAGS_MIXED )
button_sizer.AddF( self._seed_cache_button, CC.FLAGS_MIXED )
button_sizer.AddF( self._pause_button, CC.FLAGS_MIXED )
@ -2034,9 +2043,9 @@ class ManagementPanelPageOfImagesImport( ManagementPanel ):
self.Bind( wx.EVT_MENU, self.EventMenu )
HydrusGlobals.client_controller.sub( self, 'UpdateStatus', 'update_status' )
self._controller.sub( self, 'UpdateStatus', 'update_status' )
self._page_of_images_import = self._controller.GetVariable( 'page_of_images_import' )
self._page_of_images_import = self._management_controller.GetVariable( 'page_of_images_import' )
def file_download_hook( gauge_range, gauge_value ):
@ -2225,7 +2234,7 @@ class ManagementPanelPageOfImagesImport( ManagementPanel ):
seed_cache = self._page_of_images_import.GetSeedCache()
HydrusGlobals.client_controller.pub( 'show_seed_cache', seed_cache )
self._controller.pub( 'show_seed_cache', seed_cache )
def SetSearchFocus( self, page_key ):
@ -2245,13 +2254,13 @@ management_panel_types_to_classes[ MANAGEMENT_TYPE_IMPORT_PAGE_OF_IMAGES ] = Man
class ManagementPanelPetitions( ManagementPanel ):
def __init__( self, parent, page, management_controller ):
def __init__( self, parent, page, controller, management_controller ):
self._petition_service_key = management_controller.GetKey( 'petition_service' )
ManagementPanel.__init__( self, parent, page, management_controller )
ManagementPanel.__init__( self, parent, page, controller, management_controller )
self._service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._petition_service_key )
self._service = self._controller.GetServicesManager().GetService( self._petition_service_key )
self._can_ban = self._service.GetInfo( 'account' ).HasPermission( HC.MANAGE_USERS )
self._num_petitions = None
@ -2321,7 +2330,7 @@ class ManagementPanelPetitions( ManagementPanel ):
wx.CallAfter( self.EventRefreshNumPetitions, None )
HydrusGlobals.client_controller.sub( self, 'RefreshQuery', 'refresh_query' )
self._controller.sub( self, 'RefreshQuery', 'refresh_query' )
def _DrawCurrentPetition( self ):
@ -2375,9 +2384,9 @@ class ManagementPanelPetitions( ManagementPanel ):
def _ShowHashes( self, hashes ):
file_service_key = self._controller.GetKey( 'file_service' )
file_service_key = self._management_controller.GetKey( 'file_service' )
with wx.BusyCursor(): media_results = HydrusGlobals.client_controller.Read( 'media_results', file_service_key, hashes )
with wx.BusyCursor(): media_results = self._controller.Read( 'media_results', file_service_key, hashes )
panel = ClientGUIMedia.MediaPanelThumbnails( self._page, self._page_key, file_service_key, media_results )
@ -2385,7 +2394,7 @@ class ManagementPanelPetitions( ManagementPanel ):
panel.Sort( self._page_key, self._sort_by.GetChoice() )
HydrusGlobals.client_controller.pub( 'swap_media_panel', self._page_key, panel )
self._controller.pub( 'swap_media_panel', self._page_key, panel )
def _DrawNumPetitions( self ):
@ -2436,7 +2445,7 @@ class ManagementPanelPetitions( ManagementPanel ):
self._service.Request( HC.POST, 'content_update_package', { 'update' : update } )
HydrusGlobals.client_controller.Write( 'content_updates', { self._petition_service_key : update.GetContentUpdates( for_client = True ) } )
self._controller.Write( 'content_updates', { self._petition_service_key : update.GetContentUpdates( for_client = True ) } )
if len( denied_contents ) > 0:
@ -2445,7 +2454,7 @@ class ManagementPanelPetitions( ManagementPanel ):
self._service.Request( HC.POST, 'content_update_package', { 'update' : update } )
HydrusGlobals.client_controller.Write( 'content_updates', { self._petition_service_key : update.GetContentUpdates( for_client = True ) } )
self._controller.Write( 'content_updates', { self._petition_service_key : update.GetContentUpdates( for_client = True ) } )
self._current_petition = None
@ -2468,7 +2477,7 @@ class ManagementPanelPetitions( ManagementPanel ):
self._DrawCurrentPetition()
HydrusGlobals.client_controller.CallToThread( do_it )
self._controller.CallToThread( do_it )
def EventModifyPetitioner( self, event ):
@ -2503,7 +2512,7 @@ class ManagementPanelPetitions( ManagementPanel ):
self._num_petitions_text.SetLabel( u'Fetching\u2026' )
HydrusGlobals.client_controller.CallToThread( do_it )
self._controller.CallToThread( do_it )
def RefreshQuery( self, page_key ):
@ -2515,13 +2524,13 @@ management_panel_types_to_classes[ MANAGEMENT_TYPE_PETITIONS ] = ManagementPanel
class ManagementPanelQuery( ManagementPanel ):
def __init__( self, parent, page, management_controller ):
def __init__( self, parent, page, controller, management_controller ):
ManagementPanel.__init__( self, parent, page, management_controller )
ManagementPanel.__init__( self, parent, page, controller, management_controller )
file_search_context = self._controller.GetVariable( 'file_search_context' )
file_search_context = self._management_controller.GetVariable( 'file_search_context' )
search_enabled = self._controller.GetVariable( 'search_enabled' )
search_enabled = self._management_controller.GetVariable( 'search_enabled' )
self._query_key = HydrusThreading.JobKey( cancellable = True )
@ -2533,12 +2542,12 @@ class ManagementPanelQuery( ManagementPanel ):
self._current_predicates_box = ClientGUICommon.ListBoxTagsPredicates( self._search_panel, self._page_key, initial_predicates )
file_service_key = self._controller.GetKey( 'file_service' )
tag_service_key = self._controller.GetKey( 'tag_service' )
file_service_key = self._management_controller.GetKey( 'file_service' )
tag_service_key = self._management_controller.GetKey( 'tag_service' )
include_current = self._controller.GetVariable( 'include_current' )
include_pending = self._controller.GetVariable( 'include_pending' )
synchronised = self._controller.GetVariable( 'synchronised' )
include_current = self._management_controller.GetVariable( 'include_current' )
include_pending = self._management_controller.GetVariable( 'include_pending' )
synchronised = self._management_controller.GetVariable( 'synchronised' )
self._searchbox = ClientGUICommon.AutoCompleteDropdownTagsRead( self._search_panel, self._page_key, file_service_key, tag_service_key, self._page.GetMedia, include_current = include_current, include_pending = include_pending, synchronised = synchronised )
self._search_panel.AddF( self._current_predicates_box, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -2558,43 +2567,43 @@ 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, 'ChangeFileRepositoryPubsub', 'change_file_repository' )
HydrusGlobals.client_controller.sub( self, 'ChangeTagRepositoryPubsub', 'change_tag_repository' )
HydrusGlobals.client_controller.sub( self, 'IncludeCurrent', 'notify_include_current' )
HydrusGlobals.client_controller.sub( self, 'IncludePending', 'notify_include_pending' )
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' )
self._controller.sub( self, 'AddMediaResultsFromQuery', 'add_media_results_from_query' )
self._controller.sub( self, 'ChangeFileRepositoryPubsub', 'change_file_repository' )
self._controller.sub( self, 'ChangeTagRepositoryPubsub', 'change_tag_repository' )
self._controller.sub( self, 'IncludeCurrent', 'notify_include_current' )
self._controller.sub( self, 'IncludePending', 'notify_include_pending' )
self._controller.sub( self, 'SearchImmediately', 'notify_search_immediately' )
self._controller.sub( self, 'ShowQuery', 'file_query_done' )
self._controller.sub( self, 'RefreshQuery', 'refresh_query' )
def _DoQuery( self ):
HydrusGlobals.client_controller.ResetIdleTimer()
self._controller.ResetIdleTimer()
self._query_key.Cancel()
self._query_key = HydrusThreading.JobKey()
if self._controller.GetVariable( 'search_enabled' ) and self._controller.GetVariable( 'synchronised' ):
if self._management_controller.GetVariable( 'search_enabled' ) and self._management_controller.GetVariable( 'synchronised' ):
try:
file_service_key = self._controller.GetKey( 'file_service' )
tag_service_key = self._controller.GetKey( 'tag_service' )
file_service_key = self._management_controller.GetKey( 'file_service' )
tag_service_key = self._management_controller.GetKey( 'tag_service' )
include_current = self._controller.GetVariable( 'include_current' )
include_pending = self._controller.GetVariable( 'include_pending' )
include_current = self._management_controller.GetVariable( 'include_current' )
include_pending = self._management_controller.GetVariable( 'include_pending' )
current_predicates = self._current_predicates_box.GetPredicates()
search_context = ClientData.FileSearchContext( file_service_key, tag_service_key, include_current, include_pending, current_predicates )
self._controller.SetVariable( 'file_search_context', search_context )
self._management_controller.SetVariable( 'file_search_context', search_context )
if len( current_predicates ) > 0:
HydrusGlobals.client_controller.StartFileQuery( self._query_key, search_context )
self._controller.StartFileQuery( self._query_key, search_context )
panel = ClientGUIMedia.MediaPanelLoading( self._page, self._page_key, file_service_key )
@ -2603,7 +2612,7 @@ class ManagementPanelQuery( ManagementPanel ):
panel = ClientGUIMedia.MediaPanelThumbnails( self._page, self._page_key, file_service_key, [] )
HydrusGlobals.client_controller.pub( 'swap_media_panel', self._page_key, panel )
self._controller.pub( 'swap_media_panel', self._page_key, panel )
except: wx.MessageBox( traceback.format_exc() )
@ -2611,14 +2620,14 @@ class ManagementPanelQuery( ManagementPanel ):
def AddMediaResultsFromQuery( self, query_key, media_results ):
if query_key == self._query_key: HydrusGlobals.client_controller.pub( 'add_media_results', self._page_key, media_results, append = False )
if query_key == self._query_key: self._controller.pub( 'add_media_results', self._page_key, media_results, append = False )
def ChangeFileRepositoryPubsub( self, page_key, service_key ):
if page_key == self._page_key:
self._controller.SetKey( 'file_service', service_key )
self._management_controller.SetKey( 'file_service', service_key )
self._DoQuery()
@ -2628,7 +2637,7 @@ class ManagementPanelQuery( ManagementPanel ):
if page_key == self._page_key:
self._controller.SetKey( 'tag_service', service_key )
self._management_controller.SetKey( 'tag_service', service_key )
self._DoQuery()
@ -2651,7 +2660,7 @@ class ManagementPanelQuery( ManagementPanel ):
if page_key == self._page_key:
self._controller.SetVariable( 'include_current', value )
self._management_controller.SetVariable( 'include_current', value )
self._DoQuery()
@ -2661,7 +2670,7 @@ class ManagementPanelQuery( ManagementPanel ):
if page_key == self._page_key:
self._controller.SetVariable( 'include_pending', value )
self._management_controller.SetVariable( 'include_pending', value )
self._DoQuery()
@ -2676,7 +2685,7 @@ class ManagementPanelQuery( ManagementPanel ):
if page_key == self._page_key:
self._controller.SetVariable( 'synchronised', value )
self._management_controller.SetVariable( 'synchronised', value )
self._DoQuery()
@ -2687,7 +2696,7 @@ class ManagementPanelQuery( ManagementPanel ):
if page_key == self._page_key:
try: self._searchbox.SetFocus() # there's a chance this doesn't exist!
except: HydrusGlobals.client_controller.pub( 'set_media_focus' )
except: self._controller.pub( 'set_media_focus' )
@ -2699,7 +2708,7 @@ class ManagementPanelQuery( ManagementPanel ):
current_predicates = self._current_predicates_box.GetPredicates()
file_service_key = self._controller.GetKey( 'file_service' )
file_service_key = self._management_controller.GetKey( 'file_service' )
panel = ClientGUIMedia.MediaPanelThumbnails( self._page, self._page_key, file_service_key, media_results )
@ -2707,7 +2716,7 @@ class ManagementPanelQuery( ManagementPanel ):
panel.Sort( self._page_key, self._sort_by.GetChoice() )
HydrusGlobals.client_controller.pub( 'swap_media_panel', self._page_key, panel )
self._controller.pub( 'swap_media_panel', self._page_key, panel )
except: wx.MessageBox( traceback.format_exc() )
@ -2717,9 +2726,9 @@ management_panel_types_to_classes[ MANAGEMENT_TYPE_QUERY ] = ManagementPanelQuer
class ManagementPanelThreadWatcherImport( ManagementPanel ):
def __init__( self, parent, page, management_controller ):
def __init__( self, parent, page, controller, management_controller ):
ManagementPanel.__init__( self, parent, page, management_controller )
ManagementPanel.__init__( self, parent, page, controller, management_controller )
self._thread_watcher_panel = ClientGUICommon.StaticBox( self, 'thread watcher' )
@ -2747,6 +2756,8 @@ class ManagementPanelThreadWatcherImport( ManagementPanel ):
self._thread_check_now_button = wx.Button( self._options_panel, label = 'check now' )
self._thread_check_now_button.Bind( wx.EVT_BUTTON, self.EventCheckNow )
self._waiting_politely_indicator = ClientGUICommon.WaitingPolitely( self._options_panel, self._page_key )
self._seed_cache_button = wx.BitmapButton( self._options_panel, bitmap = CC.GlobalBMPs.seed_cache )
self._seed_cache_button.Bind( wx.EVT_BUTTON, self.EventSeedCache )
self._seed_cache_button.SetToolTipString( 'open detailed file import status' )
@ -2774,6 +2785,7 @@ class ManagementPanelThreadWatcherImport( ManagementPanel ):
button_sizer = wx.BoxSizer( wx.HORIZONTAL )
button_sizer.AddF( self._thread_check_now_button, CC.FLAGS_MIXED )
button_sizer.AddF( self._waiting_politely_indicator, CC.FLAGS_MIXED )
button_sizer.AddF( self._seed_cache_button, CC.FLAGS_MIXED )
button_sizer.AddF( self._pause_button, CC.FLAGS_MIXED )
@ -2811,10 +2823,10 @@ class ManagementPanelThreadWatcherImport( ManagementPanel ):
self.Bind( wx.EVT_MENU, self.EventMenu )
HydrusGlobals.client_controller.sub( self, 'UpdateStatus', 'update_status' )
HydrusGlobals.client_controller.sub( self, 'DecrementTimesToCheck', 'decrement_times_to_check' )
self._controller.sub( self, 'UpdateStatus', 'update_status' )
self._controller.sub( self, 'DecrementTimesToCheck', 'decrement_times_to_check' )
self._thread_watcher_import = self._controller.GetVariable( 'thread_watcher_import' )
self._thread_watcher_import = self._management_controller.GetVariable( 'thread_watcher_import' )
def file_download_hook( gauge_range, gauge_value ):
@ -3019,7 +3031,7 @@ class ManagementPanelThreadWatcherImport( ManagementPanel ):
seed_cache = self._thread_watcher_import.GetSeedCache()
HydrusGlobals.client_controller.pub( 'show_seed_cache', seed_cache )
self._controller.pub( 'show_seed_cache', seed_cache )
def EventTimesToCheck( self, event ):

View File

@ -191,7 +191,9 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
display_media = self._focussed_media.GetDisplayMedia()
path = ClientFiles.GetFilePath( display_media.GetHash(), display_media.GetMime() )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( display_media.GetHash(), display_media.GetMime() )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', path )
@ -330,7 +332,9 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
hash = display_media.GetHash()
mime = display_media.GetMime()
path = ClientFiles.GetFilePath( hash, mime )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash, mime )
HydrusPaths.LaunchFile( path )
@ -586,14 +590,19 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
if self._focussed_media is not None:
hash = self._focussed_media.GetHash()
mime = self._focussed_media.GetMime()
path = ClientFiles.GetFilePath( hash, mime )
self._SetFocussedMedia( None )
HydrusPaths.LaunchFile( path )
if CC.LOCAL_FILE_SERVICE_KEY in self._focussed_media.GetLocationsManager().GetCurrent():
hash = self._focussed_media.GetHash()
mime = self._focussed_media.GetMime()
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash, mime )
self._SetFocussedMedia( None )
HydrusPaths.LaunchFile( path )
@ -1620,9 +1629,11 @@ class MediaPanelThumbnails( MediaPanel ):
file_data_object = wx.FileDataObject()
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
for hash in hashes:
path = ClientFiles.GetFilePath( hash )
path = client_files_manager.GetFilePath( hash )
file_data_object.AddFile( path )
@ -2196,7 +2207,10 @@ class MediaPanelThumbnails( MediaPanel ):
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'open_externally' ), '&open externally' )
if selection_has_local_file_service:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'open_externally' ), '&open externally' )
share_menu = wx.Menu()

View File

@ -45,7 +45,7 @@ class Page( wx.SplitterWindow ):
self._search_preview_split.Bind( wx.EVT_SPLITTER_DCLICK, self.EventPreviewUnsplit )
self._management_panel = ClientGUIManagement.CreateManagementPanel( self._search_preview_split, self, self._management_controller )
self._management_panel = ClientGUIManagement.CreateManagementPanel( self._search_preview_split, self, self._controller, self._management_controller )
file_service_key = self._management_controller.GetKey( 'file_service' )

View File

@ -440,7 +440,7 @@ class PanelPredicateSystemRating( PanelPredicateSystem ):
#
local_like_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, ) )
local_like_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, ), randomised = False )
self._like_checkboxes_to_info = {}
@ -471,7 +471,7 @@ class PanelPredicateSystemRating( PanelPredicateSystem ):
#
local_numerical_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_NUMERICAL, ) )
local_numerical_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_NUMERICAL, ), randomised = False )
self._numerical_checkboxes_to_info = {}

View File

@ -245,7 +245,7 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
if do_wait:
time.sleep( HC.options[ 'website_download_polite_wait' ] )
ClientData.WaitPolitely( page_key )
with self._lock:
@ -375,12 +375,7 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
time.sleep( 5 )
with self._lock:
self._SetGalleryStatus( page_key, 'waiting politely' )
time.sleep( HC.options[ 'website_download_polite_wait' ] )
ClientData.WaitPolitely( page_key )
with self._lock:
@ -1260,7 +1255,7 @@ class PageOfImagesImport( HydrusSerialisable.SerialisableBase ):
if do_wait:
time.sleep( HC.options[ 'website_download_polite_wait' ] )
ClientData.WaitPolitely( page_key )
@ -1358,12 +1353,7 @@ class PageOfImagesImport( HydrusSerialisable.SerialisableBase ):
if not error_occurred and do_wait:
with self._lock:
self._SetParserStatus( page_key, 'waiting politely' )
time.sleep( HC.options[ 'website_download_polite_wait' ] )
ClientData.WaitPolitely( page_key )
with self._lock:
@ -2039,7 +2029,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
if do_wait:
time.sleep( HC.options[ 'website_download_polite_wait' ] )
ClientData.WaitPolitely()
@ -2102,7 +2092,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
( page_of_urls, definitely_no_more_pages ) = gallery.GetPage( self._query, page_index )
time.sleep( HC.options[ 'website_download_polite_wait' ] )
ClientData.WaitPolitely()
page_index += 1
@ -2463,7 +2453,7 @@ class ThreadWatcherImport( HydrusSerialisable.SerialisableBase ):
if do_wait:
time.sleep( HC.options[ 'website_download_polite_wait' ] )
ClientData.WaitPolitely( page_key )
@ -2614,12 +2604,7 @@ class ThreadWatcherImport( HydrusSerialisable.SerialisableBase ):
if not error_occurred and do_wait:
with self._lock:
self._SetWatcherStatus( page_key, 'waiting politely' )
time.sleep( HC.options[ 'website_download_polite_wait' ] )
ClientData.WaitPolitely( page_key )
with self._lock:

View File

@ -1,5 +1,4 @@
import ClientLocalServerResources
import HydrusServerResources
import HydrusServer
class HydrusServiceBooru( HydrusServer.HydrusService ):
@ -12,7 +11,7 @@ class HydrusServiceBooru( HydrusServer.HydrusService ):
root.putChild( 'page', ClientLocalServerResources.HydrusResourceCommandBooruPage( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'file', ClientLocalServerResources.HydrusResourceCommandBooruFile( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'thumbnail', ClientLocalServerResources.HydrusResourceCommandBooruThumbnail( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'style.css', HydrusServerResources.local_booru_css )
root.putChild( 'style.css', ClientLocalServerResources.local_booru_css )
return root

View File

@ -5,7 +5,9 @@ import HydrusData
import HydrusGlobals
import HydrusServerResources
import os
import wx
from twisted.web.static import File as FileResource
local_booru_css = FileResource( os.path.join( HC.STATIC_DIR, 'local_booru_style.css' ), defaultType = 'text/css' )
class HydrusResourceCommandBooru( HydrusServerResources.HydrusResourceCommand ):
@ -46,7 +48,9 @@ class HydrusResourceCommandBooruFile( HydrusResourceCommandBooru ):
local_booru_manager.CheckFileAuthorised( share_key, hash )
path = ClientFiles.GetFilePath( hash )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash )
response_context = HydrusServerResources.ResponseContext( 200, path = path )
@ -242,11 +246,9 @@ class HydrusResourceCommandBooruThumbnail( HydrusResourceCommandBooru ):
mime = media_result.GetMime()
if mime in HC.MIMES_WITH_THUMBNAILS: path = ClientFiles.GetThumbnailPath( hash, full_size = False )
elif mime in HC.AUDIO: path = os.path.join( HC.STATIC_DIR, 'audio_resized.png' )
elif mime in HC.VIDEO: path = os.path.join( HC.STATIC_DIR, 'video_resized.png' )
elif mime == HC.APPLICATION_FLASH: path = os.path.join( HC.STATIC_DIR, 'flash_resized.png' )
elif mime == HC.APPLICATION_PDF: path = os.path.join( HC.STATIC_DIR, 'pdf_resized.png' )
else: path = os.path.join( HC.STATIC_DIR, 'hydrus_resized.png' )
elif mime in HC.AUDIO: path = os.path.join( HC.STATIC_DIR, 'audio.png' )
elif mime == HC.APPLICATION_PDF: path = os.path.join( HC.STATIC_DIR, 'pdf.png' )
else: path = os.path.join( HC.STATIC_DIR, 'hydrus.png' )
response_context = HydrusServerResources.ResponseContext( 200, path = path )
@ -259,7 +261,9 @@ class HydrusResourceCommandLocalFile( HydrusServerResources.HydrusResourceComman
hash = request.hydrus_args[ 'hash' ]
path = ClientFiles.GetFilePath( hash )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash )
response_context = HydrusServerResources.ResponseContext( 200, path = path )

View File

@ -71,7 +71,9 @@ class RasterContainer( object ):
hash = self._media.GetHash()
mime = self._media.GetMime()
self._path = ClientFiles.GetFilePath( hash, mime )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
self._path = client_files_manager.GetFilePath( hash, mime )
( original_width, original_height ) = self._media.GetResolution()
@ -174,7 +176,9 @@ class RasterContainerVideo( RasterContainer ):
hash = self._media.GetHash()
mime = self._media.GetMime()
path = ClientFiles.GetFilePath( hash, mime )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash, mime )
duration = self._media.GetDuration()
num_frames = self._media.GetNumFrames()

View File

@ -82,14 +82,7 @@ def FilterPredicates( search_entry, predicates, service_key = None, expand_paren
return matches
def SortPredicates( predicates, collapse_siblings = False ):
if collapse_siblings:
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
predicates = siblings_manager.CollapsePredicates( predicates )
def SortPredicates( predicates ):
def cmp_func( x, y ): return cmp( x.GetCount(), y.GetCount() )

View File

@ -29,6 +29,8 @@ CLIENT_THUMBNAILS_DIR = os.path.join( DB_DIR, 'client_thumbnails' )
SERVER_THUMBNAILS_DIR = os.path.join( DB_DIR, 'server_thumbnails' )
CLIENT_UPDATES_DIR = os.path.join( DB_DIR, 'client_updates' )
SERVER_UPDATES_DIR = os.path.join( DB_DIR, 'server_updates' )
HELP_DIR = os.path.join( BASE_DIR, 'help' )
INCLUDE_DIR = os.path.join( BASE_DIR, 'include' )
LOGS_DIR = os.path.join( BASE_DIR, 'logs' )
STATIC_DIR = os.path.join( BASE_DIR, 'static' )
@ -51,7 +53,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 183
SOFTWARE_VERSION = 184
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -741,6 +741,17 @@ def IsAlreadyRunning( instance ):
return False
def IterateHexPrefixes():
hex_chars = '0123456789abcdef'
for ( one, two ) in itertools.product( hex_chars, hex_chars ):
prefix = one + two
yield prefix
def MergeKeyToListDicts( key_to_list_dicts ):
result = collections.defaultdict( list )

View File

@ -5,12 +5,9 @@ test_controller = None
view_shutdown = False
model_shutdown = False
instance = None
is_first_start = False
is_db_updated = False
db_profile_mode = False
pubsub_profile_mode = False
special_debug_mode = False
server_busy = False

View File

@ -52,20 +52,48 @@ def CleanUpTempPath( os_file_handle, temp_path ):
def ConvertAbsPathToPortablePath( abs_path ):
if abs_path == '': return None
try: return os.path.relpath( abs_path, HC.BASE_DIR )
except: return abs_path
def ConvertPortablePathToAbsPath( portable_path ):
if portable_path is None: return None
if os.path.isabs( portable_path ): abs_path = portable_path
else: abs_path = os.path.normpath( os.path.join( HC.BASE_DIR, portable_path ) )
if os.path.exists( abs_path ): return abs_path
else: return None
return abs_path
def CopyAndMergeTree( source, dest ):
if not os.path.exists( dest ):
os.mkdir( dest )
for ( root, dirnames, filenames ) in os.walk( source ):
dest_root = root.replace( source, dest )
for dirname in dirnames:
source_path = os.path.join( root, dirname )
dest_path = os.path.join( dest_root, dirname )
if not os.path.exists( dest_path ):
os.mkdir( dest_path )
shutil.copystat( source_path, dest_path )
for filename in filenames:
source_path = os.path.join( root, filename )
dest_path = os.path.join( dest_root, filename )
shutil.copy2( source_path, dest_path )
def CopyFileLikeToFileLike( f_source, f_dest ):

View File

@ -169,28 +169,6 @@ eris = '''<html><head><title>hydrus</title></head><body><pre>
<font color="gray">MMMM</font>
</pre></body></html>'''
CLIENT_ROOT_MESSAGE = '''<html>
<head>
<title>hydrus client</title>
</head>
<body>
<p>This hydrus client uses software version ''' + str( HC.SOFTWARE_VERSION ) + ''' and network version ''' + str( HC.NETWORK_VERSION ) + '''.</p>
<p>It only serves requests from 127.0.0.1.</p>
</body>
</html>'''
ROOT_MESSAGE_BEGIN = '''<html>
<head>
<title>hydrus service</title>
</head>
<body>
<p>This hydrus service uses software version ''' + str( HC.SOFTWARE_VERSION ) + ''' and network version ''' + str( HC.NETWORK_VERSION ) + '''.</p>
<p>'''
ROOT_MESSAGE_END = '''</p>
</body>
</html>'''
LOCAL_DOMAIN = HydrusServerResources.HydrusDomain( True )
REMOTE_DOMAIN = HydrusServerResources.HydrusDomain( False )
@ -225,15 +203,6 @@ class HydrusRequest( Request ):
HydrusData.Print( message )
class HydrusRequestRestricted( HydrusRequest ):
def __init__( self, *args, **kwargs ):
HydrusRequest.__init__( self, *args, **kwargs )
self.hydrus_account = None
class HydrusService( Site ):
def __init__( self, service_key, service_type, message ):
@ -258,79 +227,7 @@ class HydrusService( Site ):
return root
class HydrusServiceRestricted( HydrusService ):
def __init__( self, service_key, service_type, message ):
HydrusService.__init__( self, service_key, service_type, message )
self.requestFactory = HydrusRequestRestricted
def _InitRoot( self ):
root = HydrusService._InitRoot( self )
root.putChild( 'access_key', HydrusServerResources.HydrusResourceCommandAccessKey( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'access_key_verification', HydrusServerResources.HydrusResourceCommandAccessKeyVerification( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'session_key', HydrusServerResources.HydrusResourceCommandSessionKey( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'account', HydrusServerResources.HydrusResourceCommandRestrictedAccount( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'account_info', HydrusServerResources.HydrusResourceCommandRestrictedAccountInfo( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'account_types', HydrusServerResources.HydrusResourceCommandRestrictedAccountTypes( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'registration_keys', HydrusServerResources.HydrusResourceCommandRestrictedRegistrationKeys( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'stats', HydrusServerResources.HydrusResourceCommandRestrictedStats( self._service_key, self._service_type, REMOTE_DOMAIN ) )
return root
class HydrusServiceAdmin( HydrusServiceRestricted ):
def _InitRoot( self ):
root = HydrusServiceRestricted._InitRoot( self )
root.putChild( 'busy', HydrusServerResources.HydrusResourceBusyCheck() )
root.putChild( 'backup', HydrusServerResources.HydrusResourceCommandRestrictedBackup( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'init', HydrusServerResources.HydrusResourceCommandInit( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'services', HydrusServerResources.HydrusResourceCommandRestrictedServices( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'services_info', HydrusServerResources.HydrusResourceCommandRestrictedServicesInfo( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'shutdown', HydrusServerResources.HydrusResourceCommandShutdown( self._service_key, self._service_type, LOCAL_DOMAIN ) )
return root
class HydrusServiceRepository( HydrusServiceRestricted ):
def _InitRoot( self ):
root = HydrusServiceRestricted._InitRoot( self )
root.putChild( 'news', HydrusServerResources.HydrusResourceCommandRestrictedNews( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'num_petitions', HydrusServerResources.HydrusResourceCommandRestrictedNumPetitions( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'petition', HydrusServerResources.HydrusResourceCommandRestrictedPetition( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'content_update_package', HydrusServerResources.HydrusResourceCommandRestrictedContentUpdate( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'immediate_content_update_package', HydrusServerResources.HydrusResourceCommandRestrictedImmediateContentUpdate( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'service_update_package', HydrusServerResources.HydrusResourceCommandRestrictedServiceUpdate( self._service_key, self._service_type, REMOTE_DOMAIN ) )
return root
class HydrusServiceRepositoryFile( HydrusServiceRepository ):
def _InitRoot( self ):
root = HydrusServiceRepository._InitRoot( self )
root.putChild( 'file', HydrusServerResources.HydrusResourceCommandRestrictedRepositoryFile( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'ip', HydrusServerResources.HydrusResourceCommandRestrictedIP( self._service_key, self._service_type, REMOTE_DOMAIN ) )
root.putChild( 'thumbnail', HydrusServerResources.HydrusResourceCommandRestrictedRepositoryThumbnail( self._service_key, self._service_type, REMOTE_DOMAIN ) )
return root
class HydrusServiceRepositoryTag( HydrusServiceRepository ): pass
'''
class MessagingServiceFactory( ServerFactory ):

View File

@ -1,13 +1,10 @@
import Cookie
import HydrusConstants as HC
import HydrusExceptions
import HydrusFileHandling
import HydrusImageHandling
import HydrusPaths
import HydrusSerialisable
import HydrusThreading
import os
import ServerFiles
import time
import traceback
import yaml
@ -88,7 +85,6 @@ def ParseFileArguments( path ):
return args
hydrus_favicon = FileResource( os.path.join( HC.STATIC_DIR, 'hydrus.ico' ), defaultType = 'image/x-icon' )
local_booru_css = FileResource( os.path.join( HC.STATIC_DIR, 'local_booru_style.css' ), defaultType = 'text/css' )
class HydrusDomain( object ):
@ -102,23 +98,6 @@ class HydrusDomain( object ):
if self._local_only and client_ip != '127.0.0.1': raise HydrusExceptions.ForbiddenException( 'Only local access allowed!' )
class HydrusResourceBusyCheck( Resource ):
def __init__( self ):
Resource.__init__( self )
self._server_version_string = HC.service_string_lookup[ HC.SERVER_ADMIN ] + '/' + str( HC.NETWORK_VERSION )
def render_GET( self, request ):
request.setHeader( 'Server', self._server_version_string )
if HydrusGlobals.server_busy: return '1'
else: return '0'
class HydrusResourceWelcome( Resource ):
def __init__( self, service_type, message ):
@ -135,6 +114,8 @@ class HydrusResourceWelcome( Resource ):
def render_GET( self, request ):
request.setResponseCode( 200 )
request.setHeader( 'Server', self._server_version_string )
return self._body
@ -583,524 +564,6 @@ class HydrusResourceCommand( Resource ):
return NOT_DONE_YET
class HydrusResourceCommandAccessKey( HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
registration_key = self._parseAccessKey( request )
access_key = HydrusGlobals.controller.Read( 'access_key', registration_key )
body = yaml.safe_dump( { 'access_key' : access_key } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandShutdown( HydrusResourceCommand ):
def _threadDoPOSTJob( self, request ):
HydrusGlobals.controller.ShutdownFromServer()
response_context = ResponseContext( 200 )
return response_context
class HydrusResourceCommandAccessKeyVerification( HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
access_key = self._parseAccessKey( request )
verified = HydrusGlobals.controller.Read( 'verify_access_key', self._service_key, access_key )
body = yaml.safe_dump( { 'verified' : verified } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandInit( HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
access_key = HydrusGlobals.controller.Read( 'init' )
body = yaml.safe_dump( { 'access_key' : access_key } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandSessionKey( HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
access_key = self._parseAccessKey( request )
session_manager = HydrusGlobals.controller.GetManager( 'restricted_services_sessions' )
( session_key, expires ) = session_manager.AddSession( self._service_key, access_key )
now = HydrusData.GetNow()
max_age = now - expires
cookies = [ ( 'session_key', session_key.encode( 'hex' ), { 'max_age' : max_age, 'path' : '/' } ) ]
response_context = ResponseContext( 200, cookies = cookies )
return response_context
class HydrusResourceCommandRestricted( HydrusResourceCommand ):
GET_PERMISSION = HC.GENERAL_ADMIN
POST_PERMISSION = HC.GENERAL_ADMIN
def _callbackCheckRestrictions( self, request ):
self._checkServerBusy()
self._checkUserAgent( request )
self._domain.CheckValid( request.getClientIP() )
self._checkSession( request )
self._checkPermission( request )
return request
def _checkPermission( self, request ):
account = request.hydrus_account
method = request.method
permission = None
if method == 'GET': permission = self.GET_PERMISSION
elif method == 'POST': permission = self.POST_PERMISSION
if permission is not None: account.CheckPermission( permission )
return request
def _checkSession( self, request ):
if not request.requestHeaders.hasHeader( 'Cookie' ): raise HydrusExceptions.PermissionException( 'No cookies found!' )
cookie_texts = request.requestHeaders.getRawHeaders( 'Cookie' )
cookie_text = cookie_texts[0]
try:
cookies = Cookie.SimpleCookie( cookie_text )
if 'session_key' not in cookies: session_key = None
else: session_key = cookies[ 'session_key' ].value.decode( 'hex' )
except: raise Exception( 'Problem parsing cookies!' )
session_manager = HydrusGlobals.controller.GetManager( 'restricted_services_sessions' )
account = session_manager.GetAccount( self._service_key, session_key )
request.hydrus_account = account
return request
def _recordDataUsage( self, request ):
path = request.path[1:] # /account -> account
if request.method == 'GET': method = HC.GET
else: method = HC.POST
if ( self._service_type, method, path ) in HC.BANDWIDTH_CONSUMING_REQUESTS:
account = request.hydrus_account
if account is not None:
num_bytes = request.hydrus_request_data_usage
account.RequestMade( num_bytes )
HydrusGlobals.controller.pub( 'request_made', ( account.GetAccountKey(), num_bytes ) )
class HydrusResourceCommandRestrictedAccount( HydrusResourceCommandRestricted ):
GET_PERMISSION = None
POST_PERMISSION = HC.MANAGE_USERS
def _threadDoGETJob( self, request ):
account = request.hydrus_account
body = yaml.safe_dump( { 'account' : account } )
response_context = ResponseContext( 200, body = body )
return response_context
def _threadDoPOSTJob( self, request ):
admin_account = request.hydrus_account
admin_account_key = admin_account.GetAccountKey()
action = request.hydrus_args[ 'action' ]
subject_identifiers = request.hydrus_args[ 'subject_identifiers' ]
subject_account_keys = { HydrusGlobals.controller.Read( 'account_key_from_identifier', self._service_key, subject_identifier ) for subject_identifier in subject_identifiers }
kwargs = request.hydrus_args # for things like expires, title, and so on
HydrusGlobals.controller.WriteSynchronous( 'account', self._service_key, admin_account_key, action, subject_account_keys, kwargs )
session_manager = HydrusGlobals.controller.GetManager( 'restricted_services_sessions' )
session_manager.RefreshAccounts( self._service_key, subject_account_keys )
response_context = ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedAccountInfo( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
subject_identifier = request.hydrus_args[ 'subject_identifier' ]
subject_account_key = HydrusGlobals.controller.Read( 'account_key_from_identifier', self._service_key, subject_identifier )
account_info = HydrusGlobals.controller.Read( 'account_info', self._service_key, subject_account_key )
body = yaml.safe_dump( { 'account_info' : account_info } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedAccountTypes( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
account_types = HydrusGlobals.controller.Read( 'account_types', self._service_key )
body = yaml.safe_dump( { 'account_types' : account_types } )
response_context = ResponseContext( 200, body = body )
return response_context
def _threadDoPOSTJob( self, request ):
edit_log = request.hydrus_args[ 'edit_log' ]
HydrusGlobals.controller.WriteSynchronous( 'account_types', self._service_key, edit_log )
response_context = ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedBackup( HydrusResourceCommandRestricted ):
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoPOSTJob( self, request ):
def do_it():
HydrusGlobals.server_busy = True
HydrusGlobals.controller.WriteSynchronous( 'backup' )
HydrusGlobals.server_busy = False
HydrusGlobals.controller.CallToThread( do_it )
response_context = ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedIP( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
( ip, timestamp ) = HydrusGlobals.controller.Read( 'ip', self._service_key, hash )
body = yaml.safe_dump( { 'ip' : ip, 'timestamp' : timestamp } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedNews( HydrusResourceCommandRestricted ):
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoPOSTJob( self, request ):
news = request.hydrus_args[ 'news' ]
HydrusGlobals.controller.WriteSynchronous( 'news', self._service_key, news )
response_context = ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedNumPetitions( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.RESOLVE_PETITIONS
def _threadDoGETJob( self, request ):
num_petitions = HydrusGlobals.controller.Read( 'num_petitions', self._service_key )
body = yaml.safe_dump( { 'num_petitions' : num_petitions } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedPetition( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.RESOLVE_PETITIONS
def _threadDoGETJob( self, request ):
petition = HydrusGlobals.controller.Read( 'petition', self._service_key )
body = petition.DumpToNetworkString()
response_context = ResponseContext( 200, mime = HC.APPLICATION_JSON, body = body )
return response_context
class HydrusResourceCommandRestrictedRegistrationKeys( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
num = request.hydrus_args[ 'num' ]
title = request.hydrus_args[ 'title' ]
if 'lifetime' in request.hydrus_args: lifetime = request.hydrus_args[ 'lifetime' ]
else: lifetime = None
registration_keys = HydrusGlobals.controller.Read( 'registration_keys', self._service_key, num, title, lifetime )
body = yaml.safe_dump( { 'registration_keys' : registration_keys } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedRepositoryFile( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
POST_PERMISSION = HC.POST_DATA
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
# don't I need to check that we aren't stealing the file from another service?
path = ServerFiles.GetPath( 'file', hash )
response_context = ResponseContext( 200, path = path )
return response_context
def _threadDoPOSTJob( self, request ):
account = request.hydrus_account
account_key = account.GetAccountKey()
file_dict = request.hydrus_args
file_dict[ 'ip' ] = request.getClientIP()
HydrusGlobals.controller.WriteSynchronous( 'file', self._service_key, account_key, file_dict )
response_context = ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedRepositoryThumbnail( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
# don't I need to check that we aren't stealing the file from another service?
path = ServerFiles.GetPath( 'thumbnail', hash )
response_context = ResponseContext( 200, path = path )
return response_context
class HydrusResourceCommandRestrictedServices( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoPOSTJob( self, request ):
account = request.hydrus_account
account_key = account.GetAccountKey()
edit_log = request.hydrus_args[ 'edit_log' ]
service_keys_to_access_keys = HydrusGlobals.controller.WriteSynchronous( 'services', account_key, edit_log )
body = yaml.safe_dump( { 'service_keys_to_access_keys' : service_keys_to_access_keys } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedServicesInfo( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
services_info = HydrusGlobals.controller.Read( 'services_info' )
body = yaml.safe_dump( { 'services_info' : services_info } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedStats( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
stats = HydrusGlobals.controller.Read( 'stats', self._service_key )
body = yaml.safe_dump( { 'stats' : stats } )
response_context = ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedContentUpdate( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
POST_PERMISSION = HC.POST_DATA
def _threadDoGETJob( self, request ):
begin = request.hydrus_args[ 'begin' ]
subindex = request.hydrus_args[ 'subindex' ]
path = ServerFiles.GetContentUpdatePackagePath( self._service_key, begin, subindex )
response_context = ResponseContext( 200, path = path, is_json = True )
return response_context
def _threadDoPOSTJob( self, request ):
account = request.hydrus_account
account_key = account.GetAccountKey()
update = request.hydrus_args[ 'update' ]
HydrusGlobals.controller.WriteSynchronous( 'update', self._service_key, account_key, update )
response_context = ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedImmediateContentUpdate( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.RESOLVE_PETITIONS
def _threadDoGETJob( self, request ):
content_update = HydrusGlobals.controller.Read( 'immediate_content_update', self._service_key )
network_string = content_update.DumpToNetworkString()
response_context = ResponseContext( 200, mime = HC.APPLICATION_JSON, body = network_string )
return response_context
class HydrusResourceCommandRestrictedServiceUpdate( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
def _threadDoGETJob( self, request ):
begin = request.hydrus_args[ 'begin' ]
path = ServerFiles.GetServiceUpdatePackagePath( self._service_key, begin )
response_context = ResponseContext( 200, path = path, is_json = True )
return response_context
class ResponseContext( object ):
def __init__( self, status_code, mime = HC.APPLICATION_YAML, body = None, path = None, is_json = False, cookies = None ):

View File

@ -5,12 +5,12 @@ import HydrusData
import HydrusExceptions
import HydrusGlobals
import HydrusNetworking
import HydrusServer
import HydrusSessions
import HydrusThreading
import os
import ServerDaemons
import ServerDB
import ServerServer
import sys
import time
import traceback
@ -227,9 +227,9 @@ class Controller( HydrusController.HydrusController ):
message = options[ 'message' ]
if service_type == HC.SERVER_ADMIN: service_object = HydrusServer.HydrusServiceAdmin( service_key, service_type, message )
elif service_type == HC.FILE_REPOSITORY: service_object = HydrusServer.HydrusServiceRepositoryFile( service_key, service_type, message )
elif service_type == HC.TAG_REPOSITORY: service_object = HydrusServer.HydrusServiceRepositoryTag( service_key, service_type, message )
if service_type == HC.SERVER_ADMIN: service_object = ServerServer.HydrusServiceAdmin( service_key, service_type, message )
elif service_type == HC.FILE_REPOSITORY: service_object = ServerServer.HydrusServiceRepositoryFile( service_key, service_type, message )
elif service_type == HC.TAG_REPOSITORY: service_object = ServerServer.HydrusServiceRepositoryTag( service_key, service_type, message )
elif service_type == HC.MESSAGE_DEPOT: return
self._services[ service_key ] = reactor.listenTCP( port, service_object )

View File

@ -541,13 +541,11 @@ class DB( HydrusDB.HydrusDB ):
dirs = ( HC.SERVER_FILES_DIR, HC.SERVER_THUMBNAILS_DIR )
hex_chars = '0123456789abcdef'
for dir in dirs:
for ( one, two ) in itertools.product( hex_chars, hex_chars ):
for prefix in HydrusData.IterateHexPrefixes():
new_dir = os.path.join( dir, one + two )
new_dir = os.path.join( dir, prefix )
if not os.path.exists( new_dir ):
@ -1767,7 +1765,7 @@ class DB( HydrusDB.HydrusDB ):
os.mkdir( backup_path )
HydrusData.Print( 'backing up: copying db file' )
shutil.copy( self._db_path, os.path.join( backup_path, self.DB_NAME + '.db' ) )
shutil.copy2( self._db_path, os.path.join( backup_path, self.DB_NAME + '.db' ) )
HydrusData.Print( 'backing up: copying files' )
shutil.copytree( HC.SERVER_FILES_DIR, os.path.join( backup_path, 'server_files' ) )
@ -2350,13 +2348,11 @@ class DB( HydrusDB.HydrusDB ):
dirs = ( HC.SERVER_FILES_DIR, HC.SERVER_THUMBNAILS_DIR )
hex_chars = '0123456789abcdef'
for dir in dirs:
for ( one, two ) in itertools.product( hex_chars, hex_chars ):
for prefix in HydrusData.IterateHexPrefixes():
new_dir = os.path.join( dir, one + two )
new_dir = os.path.join( dir, prefix )
if not os.path.exists( new_dir ):

84
include/ServerServer.py Normal file
View File

@ -0,0 +1,84 @@
import HydrusServer
import ServerServerResources
class HydrusRequestRestricted( HydrusServer.HydrusRequest ):
def __init__( self, *args, **kwargs ):
HydrusServer.HydrusRequest.__init__( self, *args, **kwargs )
self.hydrus_account = None
class HydrusServiceRestricted( HydrusServer.HydrusService ):
def __init__( self, service_key, service_type, message ):
HydrusServer.HydrusService.__init__( self, service_key, service_type, message )
self.requestFactory = HydrusRequestRestricted
def _InitRoot( self ):
root = HydrusServer.HydrusService._InitRoot( self )
root.putChild( 'access_key', ServerServerResources.HydrusResourceCommandAccessKey( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'access_key_verification', ServerServerResources.HydrusResourceCommandAccessKeyVerification( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'session_key', ServerServerResources.HydrusResourceCommandSessionKey( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'account', ServerServerResources.HydrusResourceCommandRestrictedAccount( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'account_info', ServerServerResources.HydrusResourceCommandRestrictedAccountInfo( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'account_types', ServerServerResources.HydrusResourceCommandRestrictedAccountTypes( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'registration_keys', ServerServerResources.HydrusResourceCommandRestrictedRegistrationKeys( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'stats', ServerServerResources.HydrusResourceCommandRestrictedStats( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
return root
class HydrusServiceAdmin( HydrusServiceRestricted ):
def _InitRoot( self ):
root = HydrusServiceRestricted._InitRoot( self )
root.putChild( 'busy', ServerServerResources.HydrusResourceBusyCheck() )
root.putChild( 'backup', ServerServerResources.HydrusResourceCommandRestrictedBackup( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'init', ServerServerResources.HydrusResourceCommandInit( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'services', ServerServerResources.HydrusResourceCommandRestrictedServices( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'services_info', ServerServerResources.HydrusResourceCommandRestrictedServicesInfo( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'shutdown', ServerServerResources.HydrusResourceCommandShutdown( self._service_key, self._service_type, HydrusServer.LOCAL_DOMAIN ) )
return root
class HydrusServiceRepository( HydrusServiceRestricted ):
def _InitRoot( self ):
root = HydrusServiceRestricted._InitRoot( self )
root.putChild( 'news', ServerServerResources.HydrusResourceCommandRestrictedNews( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'num_petitions', ServerServerResources.HydrusResourceCommandRestrictedNumPetitions( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'petition', ServerServerResources.HydrusResourceCommandRestrictedPetition( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'content_update_package', ServerServerResources.HydrusResourceCommandRestrictedContentUpdate( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'immediate_content_update_package', ServerServerResources.HydrusResourceCommandRestrictedImmediateContentUpdate( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'service_update_package', ServerServerResources.HydrusResourceCommandRestrictedServiceUpdate( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
return root
class HydrusServiceRepositoryFile( HydrusServiceRepository ):
def _InitRoot( self ):
root = HydrusServiceRepository._InitRoot( self )
root.putChild( 'file', ServerServerResources.HydrusResourceCommandRestrictedRepositoryFile( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'ip', ServerServerResources.HydrusResourceCommandRestrictedIP( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
root.putChild( 'thumbnail', ServerServerResources.HydrusResourceCommandRestrictedRepositoryThumbnail( self._service_key, self._service_type, HydrusServer.REMOTE_DOMAIN ) )
return root
class HydrusServiceRepositoryTag( HydrusServiceRepository ): pass

View File

@ -0,0 +1,546 @@
import Cookie
import HydrusConstants as HC
import HydrusData
import HydrusExceptions
import HydrusGlobals
import HydrusServerResources
import ServerFiles
import yaml
class HydrusResourceBusyCheck( HydrusServerResources.Resource ):
def __init__( self ):
HydrusServerResources.Resource.__init__( self )
self._server_version_string = HC.service_string_lookup[ HC.SERVER_ADMIN ] + '/' + str( HC.NETWORK_VERSION )
def render_GET( self, request ):
request.setResponseCode( 200 )
request.setHeader( 'Server', self._server_version_string )
if HydrusGlobals.server_busy: return '1'
else: return '0'
class HydrusResourceCommandAccessKey( HydrusServerResources.HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
registration_key = self._parseAccessKey( request )
access_key = HydrusGlobals.controller.Read( 'access_key', registration_key )
body = yaml.safe_dump( { 'access_key' : access_key } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandShutdown( HydrusServerResources.HydrusResourceCommand ):
def _threadDoPOSTJob( self, request ):
HydrusGlobals.controller.ShutdownFromServer()
response_context = HydrusServerResources.ResponseContext( 200 )
return response_context
class HydrusResourceCommandAccessKeyVerification( HydrusServerResources.HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
access_key = self._parseAccessKey( request )
verified = HydrusGlobals.controller.Read( 'verify_access_key', self._service_key, access_key )
body = yaml.safe_dump( { 'verified' : verified } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandInit( HydrusServerResources.HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
access_key = HydrusGlobals.controller.Read( 'init' )
body = yaml.safe_dump( { 'access_key' : access_key } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandSessionKey( HydrusServerResources.HydrusResourceCommand ):
def _threadDoGETJob( self, request ):
access_key = self._parseAccessKey( request )
session_manager = HydrusGlobals.controller.GetManager( 'restricted_services_sessions' )
( session_key, expires ) = session_manager.AddSession( self._service_key, access_key )
now = HydrusData.GetNow()
max_age = now - expires
cookies = [ ( 'session_key', session_key.encode( 'hex' ), { 'max_age' : max_age, 'path' : '/' } ) ]
response_context = HydrusServerResources.ResponseContext( 200, cookies = cookies )
return response_context
class HydrusResourceCommandRestricted( HydrusServerResources.HydrusResourceCommand ):
GET_PERMISSION = HC.GENERAL_ADMIN
POST_PERMISSION = HC.GENERAL_ADMIN
def _callbackCheckRestrictions( self, request ):
self._checkServerBusy()
self._checkUserAgent( request )
self._domain.CheckValid( request.getClientIP() )
self._checkSession( request )
self._checkPermission( request )
return request
def _checkPermission( self, request ):
account = request.hydrus_account
method = request.method
permission = None
if method == 'GET': permission = self.GET_PERMISSION
elif method == 'POST': permission = self.POST_PERMISSION
if permission is not None: account.CheckPermission( permission )
return request
def _checkSession( self, request ):
if not request.requestHeaders.hasHeader( 'Cookie' ): raise HydrusExceptions.PermissionException( 'No cookies found!' )
cookie_texts = request.requestHeaders.getRawHeaders( 'Cookie' )
cookie_text = cookie_texts[0]
try:
cookies = Cookie.SimpleCookie( cookie_text )
if 'session_key' not in cookies: session_key = None
else: session_key = cookies[ 'session_key' ].value.decode( 'hex' )
except: raise Exception( 'Problem parsing cookies!' )
session_manager = HydrusGlobals.controller.GetManager( 'restricted_services_sessions' )
account = session_manager.GetAccount( self._service_key, session_key )
request.hydrus_account = account
return request
def _recordDataUsage( self, request ):
path = request.path[1:] # /account -> account
if request.method == 'GET': method = HC.GET
else: method = HC.POST
if ( self._service_type, method, path ) in HC.BANDWIDTH_CONSUMING_REQUESTS:
account = request.hydrus_account
if account is not None:
num_bytes = request.hydrus_request_data_usage
account.RequestMade( num_bytes )
HydrusGlobals.controller.pub( 'request_made', ( account.GetAccountKey(), num_bytes ) )
class HydrusResourceCommandRestrictedAccount( HydrusResourceCommandRestricted ):
GET_PERMISSION = None
POST_PERMISSION = HC.MANAGE_USERS
def _threadDoGETJob( self, request ):
account = request.hydrus_account
body = yaml.safe_dump( { 'account' : account } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
def _threadDoPOSTJob( self, request ):
admin_account = request.hydrus_account
admin_account_key = admin_account.GetAccountKey()
action = request.hydrus_args[ 'action' ]
subject_identifiers = request.hydrus_args[ 'subject_identifiers' ]
subject_account_keys = { HydrusGlobals.controller.Read( 'account_key_from_identifier', self._service_key, subject_identifier ) for subject_identifier in subject_identifiers }
kwargs = request.hydrus_args # for things like expires, title, and so on
HydrusGlobals.controller.WriteSynchronous( 'account', self._service_key, admin_account_key, action, subject_account_keys, kwargs )
session_manager = HydrusGlobals.controller.GetManager( 'restricted_services_sessions' )
session_manager.RefreshAccounts( self._service_key, subject_account_keys )
response_context = HydrusServerResources.ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedAccountInfo( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
subject_identifier = request.hydrus_args[ 'subject_identifier' ]
subject_account_key = HydrusGlobals.controller.Read( 'account_key_from_identifier', self._service_key, subject_identifier )
account_info = HydrusGlobals.controller.Read( 'account_info', self._service_key, subject_account_key )
body = yaml.safe_dump( { 'account_info' : account_info } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedAccountTypes( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
account_types = HydrusGlobals.controller.Read( 'account_types', self._service_key )
body = yaml.safe_dump( { 'account_types' : account_types } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
def _threadDoPOSTJob( self, request ):
edit_log = request.hydrus_args[ 'edit_log' ]
HydrusGlobals.controller.WriteSynchronous( 'account_types', self._service_key, edit_log )
response_context = HydrusServerResources.ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedBackup( HydrusResourceCommandRestricted ):
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoPOSTJob( self, request ):
def do_it():
HydrusGlobals.server_busy = True
HydrusGlobals.controller.WriteSynchronous( 'backup' )
HydrusGlobals.server_busy = False
HydrusGlobals.controller.CallToThread( do_it )
response_context = HydrusServerResources.ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedIP( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
( ip, timestamp ) = HydrusGlobals.controller.Read( 'ip', self._service_key, hash )
body = yaml.safe_dump( { 'ip' : ip, 'timestamp' : timestamp } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedNews( HydrusResourceCommandRestricted ):
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoPOSTJob( self, request ):
news = request.hydrus_args[ 'news' ]
HydrusGlobals.controller.WriteSynchronous( 'news', self._service_key, news )
response_context = HydrusServerResources.ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedNumPetitions( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.RESOLVE_PETITIONS
def _threadDoGETJob( self, request ):
num_petitions = HydrusGlobals.controller.Read( 'num_petitions', self._service_key )
body = yaml.safe_dump( { 'num_petitions' : num_petitions } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedPetition( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.RESOLVE_PETITIONS
def _threadDoGETJob( self, request ):
petition = HydrusGlobals.controller.Read( 'petition', self._service_key )
body = petition.DumpToNetworkString()
response_context = HydrusServerResources.ResponseContext( 200, mime = HC.APPLICATION_JSON, body = body )
return response_context
class HydrusResourceCommandRestrictedRegistrationKeys( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
num = request.hydrus_args[ 'num' ]
title = request.hydrus_args[ 'title' ]
if 'lifetime' in request.hydrus_args: lifetime = request.hydrus_args[ 'lifetime' ]
else: lifetime = None
registration_keys = HydrusGlobals.controller.Read( 'registration_keys', self._service_key, num, title, lifetime )
body = yaml.safe_dump( { 'registration_keys' : registration_keys } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedRepositoryFile( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
POST_PERMISSION = HC.POST_DATA
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
# don't I need to check that we aren't stealing the file from another service?
path = ServerFiles.GetPath( 'file', hash )
response_context = HydrusServerResources.ResponseContext( 200, path = path )
return response_context
def _threadDoPOSTJob( self, request ):
account = request.hydrus_account
account_key = account.GetAccountKey()
file_dict = request.hydrus_args
file_dict[ 'ip' ] = request.getClientIP()
HydrusGlobals.controller.WriteSynchronous( 'file', self._service_key, account_key, file_dict )
response_context = HydrusServerResources.ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedRepositoryThumbnail( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
def _threadDoGETJob( self, request ):
hash = request.hydrus_args[ 'hash' ]
# don't I need to check that we aren't stealing the file from another service?
path = ServerFiles.GetPath( 'thumbnail', hash )
response_context = HydrusServerResources.ResponseContext( 200, path = path )
return response_context
class HydrusResourceCommandRestrictedServices( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoPOSTJob( self, request ):
account = request.hydrus_account
account_key = account.GetAccountKey()
edit_log = request.hydrus_args[ 'edit_log' ]
service_keys_to_access_keys = HydrusGlobals.controller.WriteSynchronous( 'services', account_key, edit_log )
body = yaml.safe_dump( { 'service_keys_to_access_keys' : service_keys_to_access_keys } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedServicesInfo( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
POST_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
services_info = HydrusGlobals.controller.Read( 'services_info' )
body = yaml.safe_dump( { 'services_info' : services_info } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedStats( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GENERAL_ADMIN
def _threadDoGETJob( self, request ):
stats = HydrusGlobals.controller.Read( 'stats', self._service_key )
body = yaml.safe_dump( { 'stats' : stats } )
response_context = HydrusServerResources.ResponseContext( 200, body = body )
return response_context
class HydrusResourceCommandRestrictedContentUpdate( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
POST_PERMISSION = HC.POST_DATA
def _threadDoGETJob( self, request ):
begin = request.hydrus_args[ 'begin' ]
subindex = request.hydrus_args[ 'subindex' ]
path = ServerFiles.GetContentUpdatePackagePath( self._service_key, begin, subindex )
response_context = HydrusServerResources.ResponseContext( 200, path = path, is_json = True )
return response_context
def _threadDoPOSTJob( self, request ):
account = request.hydrus_account
account_key = account.GetAccountKey()
update = request.hydrus_args[ 'update' ]
HydrusGlobals.controller.WriteSynchronous( 'update', self._service_key, account_key, update )
response_context = HydrusServerResources.ResponseContext( 200 )
return response_context
class HydrusResourceCommandRestrictedImmediateContentUpdate( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.RESOLVE_PETITIONS
def _threadDoGETJob( self, request ):
content_update = HydrusGlobals.controller.Read( 'immediate_content_update', self._service_key )
network_string = content_update.DumpToNetworkString()
response_context = HydrusServerResources.ResponseContext( 200, mime = HC.APPLICATION_JSON, body = network_string )
return response_context
class HydrusResourceCommandRestrictedServiceUpdate( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA
def _threadDoGETJob( self, request ):
begin = request.hydrus_args[ 'begin' ]
path = ServerFiles.GetServiceUpdatePackagePath( self._service_key, begin )
response_context = HydrusServerResources.ResponseContext( 200, path = path, is_json = True )
return response_context

View File

@ -47,15 +47,6 @@ class TestClientDB( unittest.TestCase ):
@classmethod
def setUpClass( self ):
self._old_db_dir = HC.DB_DIR
self._old_client_files_dir = HC.CLIENT_FILES_DIR
self._old_client_thumbnails_dir = HC.CLIENT_THUMBNAILS_DIR
HC.DB_DIR = tempfile.mkdtemp()
HC.CLIENT_FILES_DIR = os.path.join( HC.DB_DIR, 'client_files' )
HC.CLIENT_THUMBNAILS_DIR = os.path.join( HC.DB_DIR, 'client_thumbnails' )
self._db = ClientDB.DB( HydrusGlobals.test_controller )
threading.Thread( target = self._db.MainLoop, name = 'Database Main Loop' ).start()
@ -68,12 +59,6 @@ class TestClientDB( unittest.TestCase ):
while not self._db.LoopIsFinished(): time.sleep( 0.1 )
shutil.rmtree( HC.DB_DIR )
HC.DB_DIR = self._old_db_dir
HC.CLIENT_FILES_DIR = self._old_client_files_dir
HC.CLIENT_THUMBNAILS_DIR = self._old_client_thumbnails_dir
def test_4chan_pass( self ):
@ -738,16 +723,14 @@ class TestClientDB( unittest.TestCase ):
self.assertTrue( os.path.exists( HC.CLIENT_FILES_DIR ) )
self.assertTrue( os.path.exists( HC.CLIENT_THUMBNAILS_DIR ) )
hex_chars = '0123456789abcdef'
for ( one, two ) in itertools.product( hex_chars, hex_chars ):
for prefix in HydrusData.IterateHexPrefixes():
dir = os.path.join( HC.CLIENT_FILES_DIR, one + two )
dir = os.path.join( HC.CLIENT_FILES_DIR, prefix )
self.assertTrue( os.path.exists( dir ) )
dir = os.path.join( HC.CLIENT_THUMBNAILS_DIR, one + two )
dir = os.path.join( HC.CLIENT_THUMBNAILS_DIR, prefix )
self.assertTrue( os.path.exists( dir ) )

View File

@ -12,6 +12,7 @@ import HydrusSerialisable
import itertools
import os
import ServerFiles
import ServerServer
import shutil
import stat
import TestConstants
@ -60,11 +61,11 @@ class TestServer( unittest.TestCase ):
def TWISTEDSetup():
reactor.listenTCP( HC.DEFAULT_SERVER_ADMIN_PORT, HydrusServer.HydrusServiceAdmin( self._admin_service.GetServiceKey(), HC.SERVER_ADMIN, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_SERVER_ADMIN_PORT, ServerServer.HydrusServiceAdmin( self._admin_service.GetServiceKey(), HC.SERVER_ADMIN, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_LOCAL_FILE_PORT, ClientLocalServer.HydrusServiceLocal( CC.LOCAL_FILE_SERVICE_KEY, HC.LOCAL_FILE, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_LOCAL_BOORU_PORT, ClientLocalServer.HydrusServiceBooru( CC.LOCAL_BOORU_SERVICE_KEY, HC.LOCAL_BOORU, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_SERVICE_PORT, HydrusServer.HydrusServiceRepositoryFile( self._file_service.GetServiceKey(), HC.FILE_REPOSITORY, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_SERVICE_PORT + 1, HydrusServer.HydrusServiceRepositoryTag( self._tag_service.GetServiceKey(), HC.TAG_REPOSITORY, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_SERVICE_PORT, ServerServer.HydrusServiceRepositoryFile( self._file_service.GetServiceKey(), HC.FILE_REPOSITORY, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_SERVICE_PORT + 1, ServerServer.HydrusServiceRepositoryTag( self._tag_service.GetServiceKey(), HC.TAG_REPOSITORY, 'hello' ) )
reactor.callFromThread( TWISTEDSetup )
@ -115,7 +116,7 @@ class TestServer( unittest.TestCase ):
#
path = ClientFiles.GetExpectedFilePath( self._file_hash, HC.IMAGE_JPEG )
path = ClientFiles.GetExpectedFilePath( HC.CLIENT_FILES_DIR, self._file_hash, HC.IMAGE_JPEG )
with open( path, 'wb' ) as f: f.write( 'file' )
@ -237,7 +238,7 @@ class TestServer( unittest.TestCase ):
share_key = HydrusData.GenerateKey()
hashes = [ HydrusData.GenerateKey() for i in range( 5 ) ]
with open( ClientFiles.GetExpectedFilePath( hashes[0], HC.IMAGE_JPEG ), 'wb' ) as f: f.write( 'file' )
with open( ClientFiles.GetExpectedFilePath( HC.CLIENT_FILES_DIR, hashes[0], HC.IMAGE_JPEG ), 'wb' ) as f: f.write( 'file' )
with open( ClientFiles.GetExpectedThumbnailPath( hashes[0], False ), 'wb' ) as f: f.write( 'thumbnail' )
local_booru_manager = HydrusGlobals.test_controller.GetManager( 'local_booru' )

View File

@ -28,8 +28,6 @@ try:
from include import HydrusLogger
import traceback
HydrusGlobals.instance = HC.HYDRUS_SERVER
action = ServerController.GetStartingAction()
if action == 'help':
@ -47,8 +45,6 @@ try:
with HydrusLogger.HydrusLogger( 'server.log' ) as logger:
error_occured = False
try:
if action in ( 'stop', 'restart' ):
@ -69,14 +65,12 @@ try:
except HydrusExceptions.PermissionException as e:
error_occured = True
error = str( e )
HydrusData.Print( error )
except:
error_occured = True
error = traceback.format_exc()
HydrusData.Print( 'Hydrus server failed' )

34
test.py
View File

@ -30,7 +30,9 @@ from include import TestHydrusTags
import collections
import os
import random
import shutil
import sys
import tempfile
import threading
import time
import unittest
@ -40,8 +42,6 @@ from include import ClientCaches
from include import ClientData
from include import HydrusData
HydrusGlobals.instance = HC.HYDRUS_TEST
only_run = None
class Controller( object ):
@ -54,6 +54,15 @@ class Controller( object ):
HydrusGlobals.test_controller = self
self._pubsub = HydrusPubSub.HydrusPubSub( self )
self._old_db_dir = HC.DB_DIR
self._old_client_files_dir = HC.CLIENT_FILES_DIR
self._old_client_thumbnails_dir = HC.CLIENT_THUMBNAILS_DIR
HC.DB_DIR = tempfile.mkdtemp()
HC.CLIENT_FILES_DIR = os.path.join( HC.DB_DIR, 'client_files' )
HC.CLIENT_THUMBNAILS_DIR = os.path.join( HC.DB_DIR, 'client_thumbnails' )
self._new_options = ClientData.ClientOptions()
def show_text( text ): pass
@ -78,6 +87,10 @@ class Controller( object ):
services.append( ClientData.Service( CC.LOCAL_TAG_SERVICE_KEY, HC.LOCAL_TAG, CC.LOCAL_TAG_SERVICE_KEY, {} ) )
self._reads[ 'services' ] = services
client_files_locations = { prefix : HC.CLIENT_FILES_DIR for prefix in HydrusData.IterateHexPrefixes() }
self._reads[ 'client_files_locations' ] = client_files_locations
self._reads[ 'sessions' ] = []
self._reads[ 'tag_parents' ] = {}
self._reads[ 'tag_siblings' ] = {}
@ -90,6 +103,7 @@ class Controller( object ):
self._managers = {}
self._services_manager = ClientCaches.ServicesManager( self )
self._client_files_manager = ClientCaches.ClientFilesManager( self )
self._managers[ 'hydrus_sessions' ] = ClientCaches.HydrusSessionManager( self )
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager( self )
@ -130,6 +144,11 @@ class Controller( object ):
def DoHTTP( self, *args, **kwargs ): return self._http.Request( *args, **kwargs )
def GetClientFilesManager( self ):
return self._client_files_manager
def GetHTTP( self ): return self._http
def GetNewOptions( self ):
@ -200,6 +219,15 @@ class Controller( object ):
def SetWebCookies( self, name, value ): self._cookies[ name ] = value
def TidyUp( self ):
shutil.rmtree( HC.DB_DIR )
HC.DB_DIR = self._old_db_dir
HC.CLIENT_FILES_DIR = self._old_client_files_dir
HC.CLIENT_THUMBNAILS_DIR = self._old_client_thumbnails_dir
def ViewIsShutdown( self ):
return HydrusGlobals.view_shutdown
@ -268,6 +296,8 @@ if __name__ == '__main__':
controller.pubimmediate( 'wake_daemons' )
controller.TidyUp()
reactor.callFromThread( reactor.stop )
raw_input()