Version 172

This commit is contained in:
Hydrus 2015-09-02 18:16:09 -05:00
parent a61df3303c
commit 91b77e6648
31 changed files with 1531 additions and 906 deletions

View File

@ -21,6 +21,9 @@ try:
import threading
from twisted.internet import reactor
from include import HydrusGlobals
import traceback
HydrusGlobals.instance = HC.HYDRUS_CLIENT
initial_sys_stdout = sys.stdout
initial_sys_stderr = sys.stderr
@ -36,15 +39,14 @@ try:
threading.Thread( target = reactor.run, kwargs = { 'installSignalHandlers' : 0 } ).start()
app = ClientController.Controller()
controller = ClientController.Controller()
app.MainLoop()
controller.Run()
except:
print( 'hydrus client failed at ' + time.ctime() )
import traceback
print( traceback.format_exc() )
finally:
@ -52,7 +54,8 @@ try:
HydrusGlobals.view_shutdown = True
HydrusGlobals.model_shutdown = True
app.pubimmediate( 'shutdown' )
try: controller.pubimmediate( 'wake_daemons' )
except: pass
reactor.callFromThread( reactor.stop )

View File

@ -8,6 +8,39 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 172</h3></li>
<ul>
<li>the server no longer needs wx!</li>
<li>the server now runs from command line</li>
<li>the server is now .py, not .pyw!</li>
<li>run 'server help' for more info</li>
<li>improved how the controllers start</li>
<li>improved how the controllers stop</li>
<li>improved controller boot error handling</li>
<li>improved how pages close</li>
<li>general controller code cleanup</li>
<li>general server code cleanup</li>
<li>fixed the always_on_shutdown (without asking) option, which was asking anyway</li>
<li>removed a debug statement in isalreadyrunning code, whoops!</li>
<li>improved isalreadyrunning detection. it should work breddy gud now</li>
<li>added gallery file limit to new 'downloading' options page, which folds in the old thread checker options as well</li>
<li>added option to always embed the autocomplete dropdown window (rather than having it a floating window), which is now default on for Linux and OS X</li>
<li>manage services now supports two kinds of service reset for repositories</li>
<li>the service reset buttons now only fire on dialog ok</li>
<li>service reset will try, as cpu allows, to update its progress in a message popup window</li>
<li>administrators now have a 'sync now' button on the review services window that lets you catch up immediately to the service without having to wait for the normal update time (this will burn cpu time serverside, so be careful!)</li>
<li>fixed a bug when searching boorus with unicode-16 characters</li>
<li>the client updates directory is neater</li>
<li>'system busy' status is now shown on the status bar</li>
<li>'force unbusy' added to debug menu</li>
<li>invalid characters in export filenames will now be replaced by underscores</li>
<li>fixed a bug where rating services' cached file counts were not decrementing on de-ratings</li>
<li>rating services' cached file counts are reset on update</li>
<li>hover windows will pop in over video again, but will not if the mouse is _near_ the animation bar</li>
<li>searching for ':' in the autocomplete dropdown will no longer search the db for every single tag jej</li>
<li>improved some server_busy logic</li>
<li>improved some server shutdown logic</li>
</ul>
<li><h3>version 171</h3></li>
<ul>
<li>improved isalreadyrunning code</li>

View File

@ -28,20 +28,18 @@ import threading
import time
import traceback
import wx
import wx.richtext
from twisted.internet import reactor
from twisted.internet import defer
ID_ANIMATED_EVENT_TIMER = wx.NewId()
ID_MAINTENANCE_EVENT_TIMER = wx.NewId()
MAINTENANCE_PERIOD = 5 * 60
class Controller( HydrusController.HydrusController ):
db_class = ClientDB.DB
pubsub_binding_errors_to_ignore = [ wx.PyDeadObjectError ]
def _InitDB( self ):
return ClientDB.DB( self )
def BackupDatabase( self ):
with wx.DirDialog( self._gui, 'Select backup location.' ) as dlg:
@ -79,11 +77,11 @@ class Controller( HydrusController.HydrusController ):
except Exception as e:
print( 'CallBlockingToWx just caught this error:' )
print( traceback.format_exc() )
job_key.SetVariable( 'error', e )
print( 'CallBlockingToWx just caught this error:' )
HydrusData.DebugPrint( traceback.format_exc() )
finally: job_key.Finish()
@ -104,6 +102,43 @@ class Controller( HydrusController.HydrusController ):
else: raise job_key.GetVariable( 'error' )
def CheckAlreadyRunning( self ):
while HydrusData.IsAlreadyRunning( 'client' ):
self.pub( 'splash_set_text', 'client already running' )
def wx_code():
message = 'It looks like another instance of this client is already running, so this instance cannot start.'
message += os.linesep * 2
message += 'If the old instance is closing and does not quit for a _very_ long time, it is usually safe to force-close it from task manager.'
with ClientGUIDialogs.DialogYesNo( self._splash, message, 'The client is already running.', yes_label = 'wait a bit, then try again', no_label = 'forget it' ) as dlg:
if dlg.ShowModal() != wx.ID_YES:
raise HydrusExceptions.PermissionException()
self.CallBlockingToWx( wx_code )
for i in range( 10, 0, -1 ):
if not HydrusData.IsAlreadyRunning( 'client' ):
break
self.pub( 'splash_set_text', 'waiting ' + str( i ) + ' seconds' )
time.sleep( 1 )
def Clipboard( self, data_type, data ):
# need this cause can't do it in a non-gui thread
@ -224,25 +259,29 @@ class Controller( HydrusController.HydrusController ):
def Exit( self ):
try: self._splash = ClientGUI.FrameSplash()
try:
self._gui.TestAbleToClose()
except HydrusExceptions.PermissionException:
return
try:
self._splash = ClientGUI.FrameSplash()
except Exception as e:
print( 'There was an error trying to start the splash screen!' )
print( traceback.format_exc() )
try:
wx.CallAfter( self._splash.Destroy )
except: pass
raise e
exit_thread = threading.Thread( target = self.THREADExitEverything, name = 'Application Exit Thread' )
wx.CallAfter( exit_thread.start )
exit_thread.start()
def ForceIdle( self ):
@ -253,9 +292,17 @@ class Controller( HydrusController.HydrusController ):
self.pub( 'refresh_status' )
def GetGUI( self ): return self._gui
def ForceUnbusy( self ):
self._system_busy = False
self.pub( 'wake_daemons' )
self.pub( 'refresh_status' )
def GetManager( self, manager_type ): return self._managers[ manager_type ]
def GetDB( self ): return self._db
def GetGUI( self ): return self._gui
def GetOptions( self ):
@ -267,24 +314,97 @@ class Controller( HydrusController.HydrusController ):
return self._services_manager
def InitCheckPassword( self ):
def InitModel( self ):
while True:
self.pub( 'splash_set_text', 'booting db' )
self._http = HydrusNetworking.HTTPConnectionManager()
HydrusController.HydrusController.InitModel( self )
self._options = self.Read( 'options' )
HC.options = self._options
self._services_manager = ClientData.ServicesManager()
self._managers[ 'hydrus_sessions' ] = HydrusSessions.HydrusSessionManagerClient()
self._managers[ 'local_booru' ] = ClientCaches.LocalBooruCache()
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager()
self._managers[ 'tag_siblings' ] = ClientCaches.TagSiblingsManager()
self._managers[ 'tag_parents' ] = ClientCaches.TagParentsManager()
self._managers[ 'undo' ] = ClientData.UndoManager()
self._managers[ 'web_sessions' ] = HydrusSessions.WebSessionManagerClient()
if HC.options[ 'proxy' ] is not None:
with wx.PasswordEntryDialog( self._splash, 'Enter your password', 'Enter password' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
if hashlib.sha256( dlg.GetValue() ).digest() == self._options[ 'password' ]: break
else: raise HydrusExceptions.PermissionException()
( proxytype, host, port, username, password ) = HC.options[ 'proxy' ]
HydrusNetworking.SetProxy( proxytype, host, port, username, password )
def wx_code():
self._caches[ 'fullscreen' ] = ClientCaches.RenderedImageCache( 'fullscreen' )
self._caches[ 'preview' ] = ClientCaches.RenderedImageCache( 'preview' )
self._caches[ 'thumbnail' ] = ClientCaches.ThumbnailCache()
CC.GlobalBMPs.STATICInitialise()
self.CallBlockingToWx( wx_code )
self.sub( self, 'Clipboard', 'clipboard' )
self.sub( self, 'RestartServer', 'restart_server' )
self.sub( self, 'RestartBooru', 'restart_booru' )
def InitDaemons( self ):
def InitView( self ):
HydrusController.HydrusController.InitDaemons( self )
if self._options[ 'password' ] is not None:
self.pub( 'splash_set_text', 'waiting for password' )
def wx_code_password():
while True:
with wx.PasswordEntryDialog( self._splash, 'Enter your password', 'Enter password' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
if hashlib.sha256( dlg.GetValue() ).digest() == self._options[ 'password' ]: break
else: raise HydrusExceptions.PermissionException( 'Bad password check' )
self.CallBlockingToWx( wx_code_password )
self.pub( 'splash_set_text', 'booting gui' )
def wx_code_gui():
self._gui = ClientGUI.FrameGUI()
# this is because of some bug in wx C++ that doesn't add these by default
wx.richtext.RichTextBuffer.AddHandler( wx.richtext.RichTextHTMLHandler() )
wx.richtext.RichTextBuffer.AddHandler( wx.richtext.RichTextXMLHandler() )
self.ResetIdleTimer()
self.CallBlockingToWx( wx_code_gui )
HydrusController.HydrusController.InitView( self )
self._local_service = None
self._booru_service = None
self.RestartServer()
self.RestartBooru()
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckImportFolders', ClientDaemons.DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckExportFolders', ClientDaemons.DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 ) )
@ -297,53 +417,9 @@ class Controller( HydrusController.HydrusController ):
self._daemons.append( HydrusThreading.DAEMONQueue( self, 'FlushRepositoryUpdates', ClientDaemons.DAEMONFlushServiceUpdates, 'service_updates_delayed', period = 5 ) )
def InitGUI( self ):
self._managers = {}
self._services_manager = ClientData.ServicesManager()
self._managers[ 'hydrus_sessions' ] = HydrusSessions.HydrusSessionManagerClient()
self._managers[ 'local_booru' ] = ClientCaches.LocalBooruCache()
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager()
self._managers[ 'tag_siblings' ] = ClientCaches.TagSiblingsManager()
self._managers[ 'tag_parents' ] = ClientCaches.TagParentsManager()
self._managers[ 'undo' ] = ClientData.UndoManager()
self._managers[ 'web_sessions' ] = HydrusSessions.WebSessionManagerClient()
self._caches[ 'fullscreen' ] = ClientCaches.RenderedImageCache( 'fullscreen' )
self._caches[ 'preview' ] = ClientCaches.RenderedImageCache( 'preview' )
self._caches[ 'thumbnail' ] = ClientCaches.ThumbnailCache()
if HC.options[ 'proxy' ] is not None:
( proxytype, host, port, username, password ) = HC.options[ 'proxy' ]
HydrusNetworking.SetProxy( proxytype, host, port, username, password )
CC.GlobalBMPs.STATICInitialise()
self._gui = ClientGUI.FrameGUI()
self.sub( self, 'Clipboard', 'clipboard' )
self.sub( self, 'RestartServer', 'restart_server' )
self.sub( self, 'RestartBooru', 'restart_booru' )
# this is because of some bug in wx C++ that doesn't add these by default
wx.richtext.RichTextBuffer.AddHandler( wx.richtext.RichTextHTMLHandler() )
wx.richtext.RichTextBuffer.AddHandler( wx.richtext.RichTextXMLHandler() )
if HydrusGlobals.is_first_start: wx.CallAfter( self._gui.DoFirstStart )
if HydrusGlobals.is_db_updated: wx.CallLater( 1, HydrusData.ShowText, 'The client has updated to version ' + HydrusData.ToString( HC.SOFTWARE_VERSION ) + '!' )
self.RestartServer()
self.RestartBooru()
self.InitDaemons()
self.ResetIdleTimer()
def MaintainDB( self ):
@ -399,38 +475,6 @@ class Controller( HydrusController.HydrusController ):
wx.CallAfter( self.ProcessPubSub )
def OnInit( self ):
self.InitData()
self._local_service = None
self._booru_service = None
self._http = HydrusNetworking.HTTPConnectionManager()
try:
self._splash = ClientGUI.FrameSplash()
except:
print( 'There was an error trying to start the splash screen!' )
print( traceback.format_exc() )
try: wx.CallAfter( self._splash.Destroy )
except: pass
return False
boot_thread = threading.Thread( target = self.THREADBootEverything, name = 'Application Boot Thread' )
wx.CallAfter( boot_thread.start )
return True
def PrepStringForDisplay( self, text ):
if self._options[ 'gui_capitalisation' ]: return text
@ -603,7 +647,7 @@ class Controller( HydrusController.HydrusController ):
restart_thread = threading.Thread( target = THREADRestart, name = 'Application Restart Thread' )
wx.CallAfter( restart_thread.start )
restart_thread.start()
@ -611,6 +655,99 @@ class Controller( HydrusController.HydrusController ):
def Run( self ):
self._app = wx.App()
self._app.SetAssertMode( wx.PYAPP_ASSERT_SUPPRESS )
try:
self._splash = ClientGUI.FrameSplash()
except:
print( 'There was an error trying to start the splash screen!' )
print( traceback.format_exc() )
raise
self.pub( 'splash_set_text', 'initialising' )
boot_thread = threading.Thread( target = self.THREADBootEverything, name = 'Application Boot Thread' )
boot_thread.start()
self._app.MainLoop()
def ShutdownModel( self ):
self.pub( 'splash_set_text', 'exiting db' )
HydrusController.HydrusController.ShutdownModel( self )
def ShutdownView( self ):
self.pub( 'splash_set_text', 'exiting gui' )
self.CallBlockingToWx( self._gui.Shutdown )
self.pub( 'splash_set_text', 'waiting for daemons to exit' )
HydrusController.HydrusController.ShutdownView( self )
idle_shutdown_action = self._options[ 'idle_shutdown' ]
if idle_shutdown_action in ( CC.IDLE_ON_SHUTDOWN, CC.IDLE_ON_SHUTDOWN_ASK_FIRST ):
# callblockingtowx needs view_shutdown to be true for the job_key to not quit early lol! fix this!
HydrusGlobals.view_shutdown = False
self.pub( 'splash_set_text', 'running maintenance' )
self.ResetIdleTimer()
do_it = True
if CC.IDLE_ON_SHUTDOWN_ASK_FIRST:
if self.ThereIsIdleShutdownWorkDue():
def wx_code():
text = 'Is now a good time for the client to do up to ' + HydrusData.ConvertIntToPrettyString( self._options[ 'idle_shutdown_max_minutes' ] ) + ' minutes\' maintenance work?'
with ClientGUIDialogs.DialogYesNo( self._splash, text, title = 'Maintenance is due' ) as dlg_yn:
if dlg_yn.ShowModal() == wx.ID_YES:
return True
else:
return False
do_it = self.CallBlockingToWx( wx_code )
if do_it:
self.DoIdleShutdownWork()
HydrusGlobals.view_shutdown = True
def StartFileQuery( self, query_key, search_context ):
self.CallToThread( self.THREADDoFileQuery, query_key, search_context )
@ -707,67 +844,25 @@ class Controller( HydrusController.HydrusController ):
try:
while HydrusData.IsAlreadyRunning():
self.pub( 'splash_set_text', 'client already running' )
def wx_code():
message = 'It looks like another instance of this client is already running, so this instance cannot start.'
message += os.linesep * 2
message += 'If the old instance is closing and does not quit for a _very_ long time, it is usually safe to force-close it from task manager.'
with ClientGUIDialogs.DialogYesNo( self._splash, message, 'The client is already running.', yes_label = 'wait a bit, then try again', no_label = 'forget it' ) as dlg:
if dlg.ShowModal() != wx.ID_YES:
raise HydrusExceptions.PermissionException()
self.CallBlockingToWx( wx_code )
for i in range( 10, 0, -1 ):
if not HydrusData.IsAlreadyRunning():
break
self.pub( 'splash_set_text', 'waiting ' + str( i ) + ' seconds' )
time.sleep( 1 )
self.CheckAlreadyRunning()
self.pub( 'splash_set_text', 'booting db' )
HydrusData.RecordRunningStart( 'client' )
self.InitDB() # can't run on wx thread because we need event queue free to update splash text
self.InitModel()
self._options = self.Read( 'options' )
self.InitView()
HC.options = self._options
except HydrusExceptions.PermissionException as e:
if self._options[ 'password' ] is not None:
self.pub( 'splash_set_text', 'waiting for password' )
self.CallBlockingToWx( self.InitCheckPassword )
print( e )
self.pub( 'splash_set_text', 'booting gui' )
self.CallBlockingToWx( self.InitGUI )
except HydrusExceptions.PermissionException as e: pass
except:
traceback.print_exc()
text = 'A serious error occured while trying to start the program. Its traceback will be shown next. It should have also been written to client.log.'
print( text )
traceback.print_exc()
HydrusData.DebugPrint( text )
wx.CallAfter( wx.MessageBox, text )
wx.CallAfter( wx.MessageBox, traceback.format_exc() )
@ -780,70 +875,11 @@ class Controller( HydrusController.HydrusController ):
def THREADExitEverything( self ):
self.pub( 'splash_set_text', 'exiting gui' )
try: self.CallBlockingToWx( self._gui.TestAbleToClose )
except:
self.pub( 'splash_destroy' )
return
try:
self.CallBlockingToWx( self._gui.Shutdown )
self.ShutdownView()
HydrusGlobals.view_shutdown = True
self.pub( 'wake_daemons' )
self.pub( 'splash_set_text', 'waiting for daemons to exit' )
while True in ( daemon.is_alive() for daemon in self._daemons ):
time.sleep( 0.1 )
idle_shutdown_action = self._options[ 'idle_shutdown' ]
if idle_shutdown_action in ( CC.IDLE_ON_SHUTDOWN, CC.IDLE_ON_SHUTDOWN_ASK_FIRST ):
self.ResetIdleTimer()
self.pub( 'splash_set_text', 'running maintenance' )
do_it = True
if True and CC.IDLE_ON_SHUTDOWN_ASK_FIRST:
if self.ThereIsIdleShutdownWorkDue():
text = 'Is now a good time for the client to do up to ' + HydrusData.ConvertIntToPrettyString( self._options[ 'idle_shutdown_max_minutes' ] ) + ' minutes\' maintenance work?'
with ClientGUIDialogs.DialogYesNo( self._splash, text, title = 'Maintenance is due' ) as dlg_yn:
if dlg_yn.ShowModal() != wx.ID_YES:
do_it = False
if do_it:
HydrusGlobals.view_shutdown = False
self.DoIdleShutdownWork()
HydrusGlobals.view_shutdown = True
self.pub( 'splash_set_text', 'exiting db' )
self.ShutdownDB() # can't run on wx thread because we need event queue free to update splash text
self.ShutdownModel()
except HydrusExceptions.PermissionException as e: pass
except:
@ -852,7 +888,7 @@ class Controller( HydrusController.HydrusController ):
text = 'A serious error occured while trying to exit the program. Its traceback will be shown next. It should have also been written to client.log. You may need to quit the program from task manager.'
print( text )
HydrusData.DebugPrint( text )
wx.CallAfter( wx.MessageBox, text )

View File

@ -1112,6 +1112,13 @@ class DB( HydrusDB.HydrusDB ):
if service_type in HC.REPOSITORIES:
update_dir = ClientFiles.GetExpectedUpdateDir( service_key )
if not os.path.exists( update_dir ):
os.mkdir( update_dir )
if 'first_timestamp' not in info: info[ 'first_timestamp' ] = None
if 'next_download_timestamp' not in info: info[ 'next_download_timestamp' ] = 0
if 'next_processing_timestamp' not in info: info[ 'next_processing_timestamp' ] = 0
@ -4629,7 +4636,7 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'DELETE FROM local_ratings WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( service_id, ) )
rowcount = self._GetRowCount()
ratings_added -= self._GetRowCount()
if rating is not None:
@ -4876,7 +4883,7 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'DELETE FROM autocomplete_tags_cache WHERE tag_service_id = ?;', ( self._combined_tag_service_id, ) )
def _ResetService( self, service_key ):
def _ResetService( self, service_key, delete_updates = False ):
service_id = self._GetServiceId( service_key )
@ -4884,26 +4891,59 @@ class DB( HydrusDB.HydrusDB ):
( service_key, service_type, name, info ) = service.ToTuple()
prefix = 'resetting ' + name
job_key = HydrusData.JobKey()
job_key.SetVariable( 'popup_text_1', prefix + ': deleting from main service table' )
self._controller.pub( 'message', job_key )
self._c.execute( 'DELETE FROM services WHERE service_id = ?;', ( service_id, ) )
if service_id in self._service_cache: del self._service_cache[ service_id ]
if service_type == HC.TAG_REPOSITORY: self._ClearCombinedAutocompleteTags()
if service_type == HC.TAG_REPOSITORY:
job_key.SetVariable( 'popup_text_1', prefix + ': recomputing combined tags' )
self._ClearCombinedAutocompleteTags()
if service_type in HC.REPOSITORIES:
job_key.SetVariable( 'popup_text_1', prefix + ': deleting downloaded updates' )
if delete_updates:
updates_dir = ClientFiles.GetExpectedUpdateDir( service_key )
if os.path.exists( updates_dir ):
HydrusData.DeletePath( updates_dir )
info[ 'first_timestamp' ] = None
info[ 'next_download_timestamp' ] = 0
info[ 'next_processing_timestamp' ] = 0
self.pub_after_commit( 'notify_restart_repo_sync_daemon' )
job_key.SetVariable( 'popup_text_1', prefix + ': recreating service' )
self._AddService( service_key, service_type, name, info )
self.pub_service_updates_after_commit( { service_key : [ HydrusData.ServiceUpdate( HC.SERVICE_UPDATE_RESET ) ] } )
self.pub_after_commit( 'notify_new_pending' )
self.pub_after_commit( 'notify_new_services_data' )
self.pub_after_commit( 'notify_new_services_gui' )
HydrusData.ShowText( 'Service ' + name + ' was reset successfully!' )
job_key.SetVariable( 'popup_text_1', prefix + ': done!' )
job_key.Finish()
def _SaveOptions( self, options ):
@ -5670,6 +5710,41 @@ class DB( HydrusDB.HydrusDB ):
if version == 171:
self._controller.pub( 'splash_set_text', 'moving updates about' )
for filename in os.listdir( HC.CLIENT_UPDATES_DIR ):
try:
( service_key_encoded, gumpf ) = filename.split( '_', 1 )
except ValueError:
continue
dest_dir = HC.CLIENT_UPDATES_DIR + os.path.sep + service_key_encoded
if not os.path.exists( dest_dir ):
os.mkdir( dest_dir )
source_path = HC.CLIENT_UPDATES_DIR + os.path.sep + filename
dest_path = dest_dir + os.path.sep + gumpf
shutil.move( source_path, dest_path )
#
service_ids = self._GetServiceIds( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
for service_id in service_ids: self._c.execute( 'DELETE FROM service_info WHERE service_id = ?;', ( service_id, ) )
self._controller.pub( 'splash_set_text', 'updating db to v' + HydrusData.ToString( version + 1 ) )
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
@ -6014,18 +6089,11 @@ class DB( HydrusDB.HydrusDB ):
self.pub_service_updates_after_commit( service_keys_to_service_updates )
service_key_hex = server_service_key.encode( 'hex' )
update_dir = ClientFiles.GetExpectedUpdateDir( server_service_key )
all_update_filenames = os.listdir( HC.CLIENT_UPDATES_DIR )
for filename in all_update_filenames:
if os.path.exists( update_dir ):
if filename.startswith( service_key_hex ):
path = HC.CLIENT_UPDATES_DIR + os.path.sep + filename
HydrusData.DeletePath( path )
HydrusData.DeletePath( update_dir )
if service_type == HC.TAG_REPOSITORY: clear_combined_autocomplete = True
@ -6078,7 +6146,7 @@ class DB( HydrusDB.HydrusDB ):
service = self._GetService( service_id )
if service.GetServiceType() == HC.TAG_REPOSITORY: clear_combined_autocomplete = True
service_type = service.GetServiceType()
self._c.execute( 'DELETE FROM services WHERE service_id = ?;', ( service_id, ) )
@ -6090,19 +6158,14 @@ class DB( HydrusDB.HydrusDB ):
self.pub_service_updates_after_commit( service_keys_to_service_updates )
service_key_hex = service_key.encode( 'hex' )
update_dir = ClientFiles.GetExpectedUpdateDir( service_key )
all_update_filenames = os.listdir( HC.CLIENT_UPDATES_DIR )
if os.path.exists( update_dir ):
HydrusData.DeletePath( update_dir )
for filename in all_update_filenames:
if filename.startswith( service_key_hex ):
path = HC.CLIENT_UPDATES_DIR + os.path.sep + filename
HydrusData.DeletePath( path )
if service_type == HC.TAG_REPOSITORY: clear_combined_autocomplete = True
elif action == HC.EDIT:

View File

@ -1734,6 +1734,8 @@ class Service( HydrusData.HydrusYAMLBase ):
num_updates_processed += 1
time.sleep( 0.1 )
job_key.DeleteVariable( 'popup_gauge_1' )
job_key.DeleteVariable( 'popup_text_2' )

View File

@ -38,6 +38,8 @@ def GetClientDefaultOptions():
options[ 'remove_trashed_files' ] = False
options[ 'remove_filtered_files' ] = False
options[ 'external_host' ] = None
options[ 'gallery_file_limit' ] = 200
options[ 'always_embed_autocompletes' ] = HC.PLATFORM_LINUX or HC.PLATFORM_OSX
system_predicates = {}

View File

@ -413,7 +413,7 @@ class GalleryParserBooru( GalleryParser ):
return self._search_url.replace( '%tags%', self._search_separator.join( [ urllib.quote( tag, '' ) for tag in tags_to_use ] ) ).replace( '%index%', HydrusData.ToString( url_index ) )
return self._search_url.replace( '%tags%', self._search_separator.join( [ urllib.quote( tag.encode( 'utf-8' ), '' ) for tag in tags_to_use ] ) ).replace( '%index%', HydrusData.ToString( url_index ) )
def _ParseGalleryPage( self, html, url_base ):
@ -736,7 +736,7 @@ class GalleryParserGiphy( GalleryParser ):
def __init__( self, tag ):
self._gallery_url = 'http://giphy.com/api/gifs?tag=' + urllib.quote( tag.replace( ' ', '+' ), '' ) + '&page='
self._gallery_url = 'http://giphy.com/api/gifs?tag=' + urllib.quote( tag.encode( 'utf-8' ).replace( ' ', '+' ), '' ) + '&page='
GalleryParser.__init__( self )

View File

@ -10,6 +10,7 @@ import HydrusSerialisable
import itertools
import os
import random
import re
import shutil
import stat
import wx
@ -60,6 +61,15 @@ def GenerateExportFilename( media, terms ):
if HC.PLATFORM_WINDOWS:
filename = re.sub( '\\\\|/|:|\\*|\\?|"|<|>|\\|', '_', filename, flags = re.UNICODE )
else:
filename = re.sub( '/', '_', filename, flags = re.UNICODE )
return filename
def GetAllPaths( raw_paths ):
@ -204,11 +214,15 @@ def GetThumbnailPath( hash, full_size = True ):
def GetExpectedContentUpdatePackagePath( service_key, begin, subindex ):
return HC.CLIENT_UPDATES_DIR + os.path.sep + service_key.encode( 'hex' ) + '_' + str( begin ) + '_' + str( subindex ) + '.json'
return GetExpectedUpdateDir( service_key ) + os.path.sep + str( begin ) + '_' + str( subindex ) + '.json'
def GetExpectedServiceUpdatePackagePath( service_key, begin ):
return HC.CLIENT_UPDATES_DIR + os.path.sep + service_key.encode( 'hex' ) + '_' + str( begin ) + '_metadata.json'
return GetExpectedUpdateDir( service_key ) + os.path.sep + str( begin ) + '_metadata.json'
def GetExpectedUpdateDir( service_key ):
return HC.CLIENT_UPDATES_DIR + os.path.sep + service_key.encode( 'hex' )
def IterateAllFileHashes():

View File

@ -50,8 +50,8 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.ImportFiles ) )
self._statusbar = self.CreateStatusBar()
self._statusbar.SetFieldsCount( 3 )
self._statusbar.SetStatusWidths( [ -1, 25, 50 ] )
self._statusbar.SetFieldsCount( 4 )
self._statusbar.SetStatusWidths( [ -1, 25, 25, 50 ] )
self._focus_holder = wx.Window( self, size = ( 0, 0 ) )
@ -64,7 +64,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
self._tab_right_click_index = -1
HydrusGlobals.controller.SetTopWindow( self )
wx.GetApp().SetTopWindow( self )
self.RefreshAcceleratorTable()
@ -242,19 +242,26 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
HydrusData.ShowText( u'Starting server\u2026' )
my_scriptname = sys.argv[0]
if HC.PLATFORM_WINDOWS:
my_exe = HC.BASE_DIR + os.path.sep + 'client.exe'
else:
my_exe = HC.BASE_DIR + os.path.sep + 'client'
if my_scriptname.endswith( 'pyw' ):
if sys.executable == my_exe:
if HC.PLATFORM_WINDOWS: subprocess.Popen( [ HC.BASE_DIR + os.path.sep + 'server.exe' ] )
else: subprocess.Popen( [ '.' + HC.BASE_DIR + os.path.sep + 'server' ] )
else:
if HC.PLATFORM_WINDOWS or HC.PLATFORM_OSX: python_bin = 'pythonw'
else: python_bin = 'python'
subprocess.Popen( [ python_bin, HC.BASE_DIR + os.path.sep + 'server.pyw' ] )
else:
if HC.PLATFORM_WINDOWS: subprocess.Popen( [ HC.BASE_DIR + os.path.sep + 'server.exe' ] )
else: subprocess.Popen( [ './' + HC.BASE_DIR + os.path.sep + 'server' ] )
subprocess.Popen( [ python_bin, HC.BASE_DIR + os.path.sep + 'server.py' ] )
time_waited = 0
@ -468,7 +475,10 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
if polite:
try: page.TestAbleToClose()
except: return
except HydrusExceptions.PermissionException:
return
page.Pause()
@ -967,6 +977,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
debug.AppendCheckItem( db_profile_mode_id, p( '&DB Profile Mode' ) )
debug.Check( db_profile_mode_id, HydrusGlobals.db_profile_mode )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'force_idle' ), p( 'Force Idle Mode' ) )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'force_unbusy' ), p( 'Force Unbusy Mode' ) )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'debug_garbage' ), p( 'Garbage' ) )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'clear_caches' ), p( '&Clear Caches' ) )
@ -1061,7 +1072,10 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
for page in [ self._notebook.GetPage( i ) for i in range( self._notebook.GetPageCount() ) ]:
try: page.TestAbleToClose()
except: return
except HydrusExceptions.PermissionException:
return
while self._notebook.GetPageCount() > 0:
@ -1371,6 +1385,15 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
idle_status = ''
if HydrusGlobals.controller.SystemBusy():
busy_status = 'busy'
else:
busy_status = ''
if HydrusGlobals.controller.GetDB().CurrentlyDoingJob():
db_status = 'db locked'
@ -1382,7 +1405,8 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
self._statusbar.SetStatusText( media_status, number = 0 )
self._statusbar.SetStatusText( idle_status, number = 1 )
self._statusbar.SetStatusText( db_status, number = 2 )
self._statusbar.SetStatusText( busy_status, number = 2 )
self._statusbar.SetStatusText( db_status, number = 3 )
def _RegenerateThumbnails( self ):
@ -1984,6 +2008,9 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif command == 'force_idle':
HydrusGlobals.controller.ForceIdle()
elif command == 'force_unbusy':
HydrusGlobals.controller.ForceUnbusy()
elif command == '8chan_board': webbrowser.open( 'http://8ch.net/hydrus/index.html' )
elif command == 'file_integrity': self._CheckFileIntegrity()
@ -2333,7 +2360,7 @@ class FrameReviewServices( ClientGUICommon.Frame ):
pos = ( pos_x + 25, pos_y + 50 )
tlp = HydrusGlobals.controller.GetTopWindow()
tlp = wx.GetApp().GetTopWindow()
ClientGUICommon.Frame.__init__( self, tlp, title = HydrusGlobals.controller.PrepStringForDisplay( 'Review Services' ), pos = pos )
@ -2444,234 +2471,6 @@ class FrameReviewServices( ClientGUICommon.Frame ):
def __init__( self, parent, service_key ):
def InitialiseControls():
if service_type in HC.REPOSITORIES + HC.LOCAL_SERVICES:
self._info_panel = ClientGUICommon.StaticBox( self, 'service information' )
if service_type in ( HC.LOCAL_FILE, HC.FILE_REPOSITORY ):
self._files_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._deleted_files_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
if service_type == HC.FILE_REPOSITORY:
self._num_thumbs = 0
self._num_local_thumbs = 0
self._thumbnails = ClientGUICommon.Gauge( self._info_panel )
self._thumbnails_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
elif service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
self._tags_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
if service_type == HC.TAG_REPOSITORY:
self._deleted_tags_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
elif service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
self._ratings_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
elif service_type == HC.LOCAL_BOORU:
self._num_shares = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._bytes = ClientGUICommon.Gauge( self._info_panel )
self._bytes_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
if service_type in HC.RESTRICTED_SERVICES:
self._permissions_panel = ClientGUICommon.StaticBox( self, 'service permissions' )
self._account_type = wx.StaticText( self._permissions_panel, style = wx.ALIGN_CENTER )
self._age = ClientGUICommon.Gauge( self._permissions_panel )
self._age_text = wx.StaticText( self._permissions_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._bytes = ClientGUICommon.Gauge( self._permissions_panel )
self._bytes_text = wx.StaticText( self._permissions_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._requests = ClientGUICommon.Gauge( self._permissions_panel )
self._requests_text = wx.StaticText( self._permissions_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
if service_type in HC.REPOSITORIES:
self._synchro_panel = ClientGUICommon.StaticBox( self, 'repository synchronisation' )
self._updates = ClientGUICommon.Gauge( self._synchro_panel )
self._updates_text = wx.StaticText( self._synchro_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
if service_type == HC.LOCAL_BOORU:
self._booru_shares_panel = ClientGUICommon.StaticBox( self, 'shares' )
self._booru_shares = ClientGUICommon.SaneListCtrl( self._booru_shares_panel, -1, [ ( 'title', 110 ), ( 'text', -1 ), ( 'expires', 170 ), ( 'num files', 70 ) ], delete_key_callback = self.DeleteBoorus )
self._booru_open_search = wx.Button( self._booru_shares_panel, label = 'open share in new page' )
self._booru_open_search.Bind( wx.EVT_BUTTON, self.EventBooruOpenSearch )
self._copy_internal_share_link = wx.Button( self._booru_shares_panel, label = 'copy internal share link' )
self._copy_internal_share_link.Bind( wx.EVT_BUTTON, self.EventCopyInternalShareURL )
self._copy_external_share_link = wx.Button( self._booru_shares_panel, label = 'copy external share link' )
self._copy_external_share_link.Bind( wx.EVT_BUTTON, self.EventCopyExternalShareURL )
self._booru_edit = wx.Button( self._booru_shares_panel, label = 'edit' )
self._booru_edit.Bind( wx.EVT_BUTTON, self.EventBooruEdit )
self._booru_delete = wx.Button( self._booru_shares_panel, label = 'delete' )
self._booru_delete.Bind( wx.EVT_BUTTON, self.EventBooruDelete )
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
self._service_wide_update = wx.Button( self, label = 'perform a service-wide operation' )
self._service_wide_update.Bind( wx.EVT_BUTTON, self.EventServiceWideUpdate )
if service_type == HC.SERVER_ADMIN:
self._init = wx.Button( self, label = 'initialise server' )
self._init.Bind( wx.EVT_BUTTON, self.EventServerInitialise )
if service_type in HC.RESTRICTED_SERVICES:
self._refresh = wx.Button( self, label = 'refresh account' )
self._refresh.Bind( wx.EVT_BUTTON, self.EventServiceRefreshAccount )
self._copy_account_key = wx.Button( self, label = 'copy account key' )
self._copy_account_key.Bind( wx.EVT_BUTTON, self.EventCopyAccountKey )
def PopulateControls():
self._DisplayService()
def ArrangeControls():
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
vbox = wx.BoxSizer( wx.VERTICAL )
if service_type in HC.REPOSITORIES + HC.LOCAL_SERVICES:
if service_type in ( HC.LOCAL_FILE, HC.FILE_REPOSITORY ):
self._info_panel.AddF( self._files_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._deleted_files_text, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type == HC.FILE_REPOSITORY:
self._info_panel.AddF( self._thumbnails, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._thumbnails_text, CC.FLAGS_EXPAND_PERPENDICULAR )
elif service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
self._info_panel.AddF( self._tags_text, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type == HC.TAG_REPOSITORY:
self._info_panel.AddF( self._deleted_tags_text, CC.FLAGS_EXPAND_PERPENDICULAR )
elif service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
self._info_panel.AddF( self._ratings_text, CC.FLAGS_EXPAND_PERPENDICULAR )
elif service_type == HC.LOCAL_BOORU:
self._info_panel.AddF( self._num_shares, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._bytes, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._bytes_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._info_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type in HC.RESTRICTED_SERVICES:
self._permissions_panel.AddF( self._account_type, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._age, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._age_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._bytes, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._bytes_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._requests, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._requests_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._permissions_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type in HC.REPOSITORIES:
self._synchro_panel.AddF( self._updates, CC.FLAGS_EXPAND_PERPENDICULAR )
self._synchro_panel.AddF( self._updates_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._synchro_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type == HC.LOCAL_BOORU:
self._booru_shares_panel.AddF( self._booru_shares, CC.FLAGS_EXPAND_BOTH_WAYS )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._booru_open_search, CC.FLAGS_MIXED )
b_box.AddF( self._copy_internal_share_link, CC.FLAGS_MIXED )
b_box.AddF( self._copy_external_share_link, CC.FLAGS_MIXED )
b_box.AddF( self._booru_edit, CC.FLAGS_MIXED )
b_box.AddF( self._booru_delete, CC.FLAGS_MIXED )
self._booru_shares_panel.AddF( b_box, CC.FLAGS_BUTTON_SIZER )
vbox.AddF( self._booru_shares_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
if service_type in HC.RESTRICTED_SERVICES + [ HC.LOCAL_TAG ]:
repo_buttons_hbox = wx.BoxSizer( wx.HORIZONTAL )
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
repo_buttons_hbox.AddF( self._service_wide_update, CC.FLAGS_MIXED )
if service_type == HC.SERVER_ADMIN:
repo_buttons_hbox.AddF( self._init, CC.FLAGS_MIXED )
if service_type in HC.RESTRICTED_SERVICES:
repo_buttons_hbox.AddF( self._refresh, CC.FLAGS_MIXED )
repo_buttons_hbox.AddF( self._copy_account_key, CC.FLAGS_MIXED )
vbox.AddF( repo_buttons_hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
wx.ScrolledWindow.__init__( self, parent )
self.SetScrollRate( 0, 20 )
@ -2682,11 +2481,232 @@ class FrameReviewServices( ClientGUICommon.Frame ):
service_type = self._service.GetServiceType()
InitialiseControls()
if service_type in HC.REPOSITORIES + HC.LOCAL_SERVICES:
self._info_panel = ClientGUICommon.StaticBox( self, 'service information' )
if service_type in ( HC.LOCAL_FILE, HC.FILE_REPOSITORY ):
self._files_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._deleted_files_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
if service_type == HC.FILE_REPOSITORY:
self._num_thumbs = 0
self._num_local_thumbs = 0
self._thumbnails = ClientGUICommon.Gauge( self._info_panel )
self._thumbnails_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
elif service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
self._tags_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
if service_type == HC.TAG_REPOSITORY:
self._deleted_tags_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
elif service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
self._ratings_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
elif service_type == HC.LOCAL_BOORU:
self._num_shares = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._bytes = ClientGUICommon.Gauge( self._info_panel )
self._bytes_text = wx.StaticText( self._info_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
PopulateControls()
if service_type in HC.RESTRICTED_SERVICES:
self._permissions_panel = ClientGUICommon.StaticBox( self, 'service permissions' )
self._account_type = wx.StaticText( self._permissions_panel, style = wx.ALIGN_CENTER )
self._age = ClientGUICommon.Gauge( self._permissions_panel )
self._age_text = wx.StaticText( self._permissions_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._bytes = ClientGUICommon.Gauge( self._permissions_panel )
self._bytes_text = wx.StaticText( self._permissions_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._requests = ClientGUICommon.Gauge( self._permissions_panel )
self._requests_text = wx.StaticText( self._permissions_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
ArrangeControls()
if service_type in HC.REPOSITORIES:
self._synchro_panel = ClientGUICommon.StaticBox( self, 'repository synchronisation' )
self._updates = ClientGUICommon.Gauge( self._synchro_panel )
self._updates_text = wx.StaticText( self._synchro_panel, style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE )
self._immediate_sync = wx.Button( self._synchro_panel, label = 'sync now' )
self._immediate_sync.Bind( wx.EVT_BUTTON, self.EventImmediateSync)
if service_type == HC.LOCAL_BOORU:
self._booru_shares_panel = ClientGUICommon.StaticBox( self, 'shares' )
self._booru_shares = ClientGUICommon.SaneListCtrl( self._booru_shares_panel, -1, [ ( 'title', 110 ), ( 'text', -1 ), ( 'expires', 170 ), ( 'num files', 70 ) ], delete_key_callback = self.DeleteBoorus )
self._booru_open_search = wx.Button( self._booru_shares_panel, label = 'open share in new page' )
self._booru_open_search.Bind( wx.EVT_BUTTON, self.EventBooruOpenSearch )
self._copy_internal_share_link = wx.Button( self._booru_shares_panel, label = 'copy internal share link' )
self._copy_internal_share_link.Bind( wx.EVT_BUTTON, self.EventCopyInternalShareURL )
self._copy_external_share_link = wx.Button( self._booru_shares_panel, label = 'copy external share link' )
self._copy_external_share_link.Bind( wx.EVT_BUTTON, self.EventCopyExternalShareURL )
self._booru_edit = wx.Button( self._booru_shares_panel, label = 'edit' )
self._booru_edit.Bind( wx.EVT_BUTTON, self.EventBooruEdit )
self._booru_delete = wx.Button( self._booru_shares_panel, label = 'delete' )
self._booru_delete.Bind( wx.EVT_BUTTON, self.EventBooruDelete )
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
self._service_wide_update = wx.Button( self, label = 'perform a service-wide operation' )
self._service_wide_update.Bind( wx.EVT_BUTTON, self.EventServiceWideUpdate )
if service_type == HC.SERVER_ADMIN:
self._init = wx.Button( self, label = 'initialise server' )
self._init.Bind( wx.EVT_BUTTON, self.EventServerInitialise )
if service_type in HC.RESTRICTED_SERVICES:
self._refresh = wx.Button( self, label = 'refresh account' )
self._refresh.Bind( wx.EVT_BUTTON, self.EventServiceRefreshAccount )
self._copy_account_key = wx.Button( self, label = 'copy account key' )
self._copy_account_key.Bind( wx.EVT_BUTTON, self.EventCopyAccountKey )
#
self._DisplayService()
#
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
vbox = wx.BoxSizer( wx.VERTICAL )
if service_type in HC.REPOSITORIES + HC.LOCAL_SERVICES:
if service_type in ( HC.LOCAL_FILE, HC.FILE_REPOSITORY ):
self._info_panel.AddF( self._files_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._deleted_files_text, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type == HC.FILE_REPOSITORY:
self._info_panel.AddF( self._thumbnails, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._thumbnails_text, CC.FLAGS_EXPAND_PERPENDICULAR )
elif service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
self._info_panel.AddF( self._tags_text, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type == HC.TAG_REPOSITORY:
self._info_panel.AddF( self._deleted_tags_text, CC.FLAGS_EXPAND_PERPENDICULAR )
elif service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
self._info_panel.AddF( self._ratings_text, CC.FLAGS_EXPAND_PERPENDICULAR )
elif service_type == HC.LOCAL_BOORU:
self._info_panel.AddF( self._num_shares, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._bytes, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._bytes_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._info_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type in HC.RESTRICTED_SERVICES:
self._permissions_panel.AddF( self._account_type, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._age, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._age_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._bytes, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._bytes_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._requests, CC.FLAGS_EXPAND_PERPENDICULAR )
self._permissions_panel.AddF( self._requests_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._permissions_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type in HC.REPOSITORIES:
self._synchro_panel.AddF( self._updates, CC.FLAGS_EXPAND_PERPENDICULAR )
self._synchro_panel.AddF( self._updates_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self._synchro_panel.AddF( self._immediate_sync, CC.FLAGS_LONE_BUTTON )
vbox.AddF( self._synchro_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type == HC.LOCAL_BOORU:
self._booru_shares_panel.AddF( self._booru_shares, CC.FLAGS_EXPAND_BOTH_WAYS )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._booru_open_search, CC.FLAGS_MIXED )
b_box.AddF( self._copy_internal_share_link, CC.FLAGS_MIXED )
b_box.AddF( self._copy_external_share_link, CC.FLAGS_MIXED )
b_box.AddF( self._booru_edit, CC.FLAGS_MIXED )
b_box.AddF( self._booru_delete, CC.FLAGS_MIXED )
self._booru_shares_panel.AddF( b_box, CC.FLAGS_BUTTON_SIZER )
vbox.AddF( self._booru_shares_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
if service_type in HC.RESTRICTED_SERVICES + [ HC.LOCAL_TAG ]:
repo_buttons_hbox = wx.BoxSizer( wx.HORIZONTAL )
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
repo_buttons_hbox.AddF( self._service_wide_update, CC.FLAGS_MIXED )
if service_type == HC.SERVER_ADMIN:
repo_buttons_hbox.AddF( self._init, CC.FLAGS_MIXED )
if service_type in HC.RESTRICTED_SERVICES:
repo_buttons_hbox.AddF( self._refresh, CC.FLAGS_MIXED )
repo_buttons_hbox.AddF( self._copy_account_key, CC.FLAGS_MIXED )
vbox.AddF( repo_buttons_hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
self._timer_updates = wx.Timer( self, id = ID_TIMER_UPDATES )
@ -2815,6 +2835,15 @@ class FrameReviewServices( ClientGUICommon.Frame ):
self._updates_text.SetLabel( self._service.GetUpdateStatus() )
if account.HasPermission( HC.RESOLVE_PETITIONS ):
self._immediate_sync.Show()
else:
self._immediate_sync.Hide()
self._refresh.Enable()
@ -3034,6 +3063,91 @@ class FrameReviewServices( ClientGUICommon.Frame ):
def EventImmediateSync( self, event ):
def do_it():
job_key = HydrusData.JobKey( pausable = True, cancellable = True )
job_key.SetVariable( 'popup_title', self._service.GetName() + ': immediate sync' )
job_key.SetVariable( 'popup_text_1', 'downloading' )
HydrusGlobals.controller.pub( 'message', job_key )
content_update_package = self._service.Request( HC.GET, 'immediate_content_update_package' )
c_u_p_num_rows = content_update_package.GetNumRows()
c_u_p_total_weight_processed = 0
pending_content_updates = []
pending_weight = 0
update_speed_string = ''
content_update_index_string = 'content row ' + HydrusData.ConvertValueRangeToPrettyString( c_u_p_total_weight_processed, c_u_p_num_rows ) + ': '
job_key.SetVariable( 'popup_text_1', content_update_index_string + 'committing' + update_speed_string )
job_key.SetVariable( 'popup_gauge_1', ( c_u_p_total_weight_processed, c_u_p_num_rows ) )
for content_update in content_update_package.IterateContentUpdates():
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
job_key.Delete()
return
pending_content_updates.append( content_update )
content_update_weight = len( content_update.GetHashes() )
pending_weight += content_update_weight
if pending_weight > 100:
content_update_index_string = 'content row ' + HydrusData.ConvertValueRangeToPrettyString( c_u_p_total_weight_processed, c_u_p_num_rows ) + ': '
job_key.SetVariable( 'popup_text_1', content_update_index_string + 'committing' + update_speed_string )
job_key.SetVariable( 'popup_gauge_1', ( c_u_p_total_weight_processed, c_u_p_num_rows ) )
precise_timestamp = HydrusData.GetNowPrecise()
HydrusGlobals.controller.WriteSynchronous( 'content_updates', { self._service_key : pending_content_updates } )
it_took = HydrusData.GetNowPrecise() - precise_timestamp
rows_s = pending_weight / it_took
update_speed_string = ' at ' + HydrusData.ConvertIntToPrettyString( rows_s ) + ' rows/s'
c_u_p_total_weight_processed += pending_weight
pending_content_updates = []
pending_weight = 0
if len( pending_content_updates ) > 0:
HydrusGlobals.controller.WriteSynchronous( { self._service_key : pending_content_updates } )
c_u_p_total_weight_processed += pending_weight
job_key.SetVariable( 'popup_text_1', 'done! ' + HydrusData.ConvertIntToPrettyString( c_u_p_num_rows ) + ' rows added.' )
job_key.DeleteVariable( 'popup_gauge_1' )
job_key.Finish()
HydrusGlobals.controller.CallToThread( do_it )
def EventServiceWideUpdate( self, event ):
with ClientGUIDialogs.DialogAdvancedContentUpdate( self, self._service_key ) as dlg:
@ -3116,7 +3230,7 @@ class FrameSeedCache( ClientGUICommon.Frame ):
pos = ( pos_x + 25, pos_y + 50 )
tlp = HydrusGlobals.controller.GetTopWindow()
tlp = wx.GetApp().GetTopWindow()
ClientGUICommon.Frame.__init__( self, tlp, title = HydrusGlobals.controller.PrepStringForDisplay( 'File Import Status' ), pos = pos )

View File

@ -936,12 +936,34 @@ class Canvas( object ):
def KeepCursorAlive( self ): pass
def MouseIsNearAnimationBar( self ):
if ShouldHaveAnimationBar( self._current_display_media ):
animation_bar = self._media_container.GetAnimationBar()
( x, y ) = animation_bar.GetScreenPosition()
( width, height ) = animation_bar.GetSize()
( mouse_x, mouse_y ) = wx.GetMousePosition()
buffer_distance = 75
if mouse_x >= x - buffer_distance and mouse_x <= x + width + buffer_distance and mouse_y >= y - buffer_distance and mouse_y <= y + height + buffer_distance:
return True
return False
def MouseIsOverMedia( self ):
( x, y ) = self._media_container.GetPosition()
( x, y ) = self._media_container.GetScreenPosition()
( width, height ) = self._media_container.GetSize()
( mouse_x, mouse_y ) = self.ScreenToClient( wx.GetMousePosition() )
( mouse_x, mouse_y ) = wx.GetMousePosition()
if mouse_x >= x and mouse_x <= x + width and mouse_y >= y and mouse_y <= y + height: return True
@ -1407,7 +1429,7 @@ class CanvasFullscreenMediaList( ClientMedia.ListeningMediaList, CanvasWithDetai
self.Show( True )
HydrusGlobals.controller.SetTopWindow( self )
wx.GetApp().SetTopWindow( self )
self._timer_cursor_hide = wx.Timer( self, id = ID_TIMER_CURSOR_HIDE )
@ -2990,6 +3012,11 @@ class MediaContainer( wx.Window ):
def GetAnimationBar( self ):
return self._animation_bar
def GotoPreviousOrNextFrame( self, direction ):
if self._media_window is not None:

View File

@ -85,7 +85,7 @@ class AutoCompleteDropdown( wx.Panel ):
# There's a big bug in wx where FRAME_FLOAT_ON_PARENT Frames don't get passed their mouse events if their parent is a Dialog jej
# I think it is something to do with the initialisation order; if the frame is init'ed before the ShowModal call, but whatever.
if issubclass( type( tlp ), wx.Dialog ): self._float_mode = False
if issubclass( type( tlp ), wx.Dialog ) or HC.options[ 'always_embed_autocompletes' ]: self._float_mode = False
else: self._float_mode = True
self._text_ctrl = wx.TextCtrl( self, style=wx.TE_PROCESS_ENTER )
@ -693,7 +693,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
( inclusive, search_text, entry_predicate ) = self._ParseSearchText()
if search_text == '':
if search_text in ( '', ':' ):
self._cache_text = ''
self._current_namespace = ''
@ -990,7 +990,7 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
( search_text, entry_predicate, sibling_predicate ) = self._ParseSearchText()
if search_text == '':
if search_text in ( '', ':' ):
self._cache_text = ''
self._current_namespace = ''

View File

@ -2840,7 +2840,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( 'thread checker', self._ThreadCheckerPanel( self._listbook ) )
self._listbook.AddPage( 'downloading', self._DownloadingPanel( self._listbook ) )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'Save' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
@ -3151,6 +3151,70 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
class _DownloadingPanel( wx.Panel ):
def __init__( self, parent ):
wx.Panel.__init__( self, parent )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
gallery_downloader = ClientGUICommon.StaticBox( self, 'gallery downloader' )
self._gallery_file_limit = ClientGUICommon.NoneableSpinCtrl( gallery_downloader, 'default file limit', none_phrase = 'no limit', min = 1, max = 1000000 )
thread_checker = ClientGUICommon.StaticBox( self, 'thread checker' )
self._thread_times_to_check = wx.SpinCtrl( thread_checker, min = 0, max = 100 )
self._thread_times_to_check.SetToolTipString( 'how many times the thread checker will check' )
self._thread_check_period = wx.SpinCtrl( thread_checker, min = 30, max = 86400 )
self._thread_check_period.SetToolTipString( 'how long the checker will wait between checks' )
#
self._gallery_file_limit.SetValue( HC.options[ 'gallery_file_limit' ] )
( times_to_check, check_period ) = HC.options[ 'thread_checker_timings' ]
self._thread_times_to_check.SetValue( times_to_check )
self._thread_check_period.SetValue( check_period )
#
gallery_downloader.AddF( self._gallery_file_limit, CC.FLAGS_EXPAND_PERPENDICULAR )
#
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( thread_checker, label = 'default number of times to check: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._thread_times_to_check, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( thread_checker, label = 'default wait in seconds between checks: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._thread_check_period, CC.FLAGS_MIXED )
thread_checker.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( gallery_downloader, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( thread_checker, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
def UpdateOptions( self ):
HC.options[ 'gallery_file_limit' ] = self._gallery_file_limit.GetValue()
HC.options[ 'thread_checker_timings' ] = ( self._thread_times_to_check.GetValue(), self._thread_check_period.GetValue() )
class _MaintenanceAndProcessingPanel( wx.Panel ):
def __init__( self, parent ):
@ -3561,6 +3625,8 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._confirm_client_exit = wx.CheckBox( self )
self._always_embed_autocompletes = wx.CheckBox( self )
self._gui_capitalisation = wx.CheckBox( self )
self._gui_show_all_tags_in_autocomplete = wx.CheckBox( self )
@ -3593,6 +3659,8 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._confirm_client_exit.SetValue( HC.options[ 'confirm_client_exit' ] )
self._always_embed_autocompletes.SetValue( HC.options[ 'always_embed_autocompletes' ] )
self._gui_capitalisation.SetValue( HC.options[ 'gui_capitalisation' ] )
self._gui_show_all_tags_in_autocomplete.SetValue( HC.options[ 'show_all_tags_in_autocomplete' ] )
@ -3634,10 +3702,13 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddF( wx.StaticText( self, label = 'Confirm client exit:' ), CC.FLAGS_MIXED )
gridbox.AddF( self._confirm_client_exit, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'Always embed autocomplete dropdown results window:' ), CC.FLAGS_MIXED )
gridbox.AddF( self._always_embed_autocompletes, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'Default tag service in manage tag dialogs:' ), CC.FLAGS_MIXED )
gridbox.AddF( self._default_tag_repository, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'Default tag sort on management panel:' ), CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'Default tag sort:' ), CC.FLAGS_MIXED )
gridbox.AddF( self._default_tag_sort, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'Capitalise gui: ' ), CC.FLAGS_MIXED )
@ -3662,6 +3733,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
HC.options[ 'default_gui_session' ] = self._default_gui_session.GetStringSelection()
HC.options[ 'confirm_client_exit' ] = self._confirm_client_exit.GetValue()
HC.options[ 'always_embed_autocompletes' ] = self._always_embed_autocompletes.GetValue()
HC.options[ 'gui_capitalisation' ] = self._gui_capitalisation.GetValue()
HC.options[ 'show_all_tags_in_autocomplete' ] = self._gui_show_all_tags_in_autocomplete.GetValue()
HC.options[ 'default_tag_repository' ] = self._default_tag_repository.GetChoice()
@ -4252,48 +4324,6 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
class _ThreadCheckerPanel( wx.Panel ):
def __init__( self, parent ):
wx.Panel.__init__( self, parent )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._thread_times_to_check = wx.SpinCtrl( self, min = 0, max = 100 )
self._thread_times_to_check.SetToolTipString( 'how many times the thread checker will check' )
self._thread_check_period = wx.SpinCtrl( self, min = 30, max = 86400 )
self._thread_check_period.SetToolTipString( 'how long the checker will wait between checks' )
#
( times_to_check, check_period ) = HC.options[ 'thread_checker_timings' ]
self._thread_times_to_check.SetValue( times_to_check )
self._thread_check_period.SetValue( check_period )
#
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self, label = 'default number of times to check: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._thread_times_to_check, CC.FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self, label = 'default wait in seconds between checks: ' ), CC.FLAGS_MIXED )
gridbox.AddF( self._thread_check_period, CC.FLAGS_MIXED )
self.SetSizer( gridbox )
def UpdateOptions( self ):
HC.options[ 'thread_checker_timings' ] = ( self._thread_times_to_check.GetValue(), self._thread_check_period.GetValue() )
def EventOK( self, event ):
for ( name, page ) in self._listbook.GetNamesToActivePages().items():
@ -5371,6 +5401,16 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
all_listbooks = self._service_types_to_listbooks.values()
for listbook in all_listbooks:
all_pages = listbook.GetNamesToActivePages().values()
for page in all_pages:
page.DoOnOKStuff()
for listbook in all_listbooks:
all_pages = listbook.GetNamesToActivePages().values()
@ -5514,6 +5554,9 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
self._original_info = ( service_key, service_type, name, info )
self._reset_downloading = False
self._reset_processing = False
def InitialiseControls():
if service_type not in HC.NONEDITABLE_SERVICES:
@ -5548,8 +5591,11 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
self._pause_synchronisation = wx.CheckBox( self._repositories_panel, label = 'pause synchronisation' )
self._reset = wx.Button( self._repositories_panel, label = 'reset cache' )
self._reset.Bind( wx.EVT_BUTTON, self.EventServiceReset )
self._reset_processing_button = wx.Button( self._repositories_panel, label = 'reset processing cache on dialog ok' )
self._reset_processing_button.Bind( wx.EVT_BUTTON, self.EventServiceResetProcessing )
self._reset_downloading_button = wx.Button( self._repositories_panel, label = 'reset processing and download cache on dialog ok' )
self._reset_downloading_button.Bind( wx.EVT_BUTTON, self.EventServiceResetDownload )
if service_type in HC.RATINGS_SERVICES:
@ -5700,7 +5746,8 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
if service_type in HC.REPOSITORIES:
self._repositories_panel.AddF( self._pause_synchronisation, CC.FLAGS_MIXED )
self._repositories_panel.AddF( self._reset, CC.FLAGS_LONE_BUTTON )
self._repositories_panel.AddF( self._reset_processing_button, CC.FLAGS_LONE_BUTTON )
self._repositories_panel.AddF( self._reset_downloading_button, CC.FLAGS_LONE_BUTTON )
vbox.AddF( self._repositories_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -5816,6 +5863,20 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
else: self._archive_sync_add.Enable()
def DoOnOKStuff( self ):
( service_key, service_type, name, info ) = self._original_info
if self._reset_downloading:
HydrusGlobals.controller.Write( 'reset_service', service_key, delete_updates = True )
elif self._reset_processing:
HydrusGlobals.controller.Write( 'reset_service', service_key )
def EventArchiveAdd( self, event ):
if self._archive_sync.GetCount() == 0:
@ -6047,17 +6108,36 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
return ( service_key, service_type, name, info )
def EventServiceReset( self, event ):
def EventServiceResetDownload( self, event ):
( service_key, service_type, name, info ) = self._original_info
message = 'This will remove all the information for ' + name + ' from the database so it can be reprocessed. 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 information that was deleted, which will take another long time.' + os.linesep * 2 + 'If you do not understand what this button does, you probably 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 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!'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
with wx.BusyCursor(): HydrusGlobals.controller.Write( 'reset_service', service_key )
self._reset_downloading_button.SetLabel( 'everything will be reset on dialog ok!' )
self._reset_downloading = True
def EventServiceResetProcessing( self, event ):
( 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!'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._reset_processing_button.SetLabel( 'processing will be reset on dialog ok!' )
self._reset_processing = True
@ -6410,7 +6490,17 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
info[ 'frequency_type' ] = 86400
info[ 'frequency' ] = 7
info[ 'get_tags_if_redundant' ] = False
info[ 'initial_limit' ] = 500
if HC.options[ 'gallery_file_limit' ] is None:
file_limit = 200
else:
file_limit = min( 200, HC.options[ 'gallery_file_limit' ] )
info[ 'initial_limit' ] = file_limit
info[ 'advanced_tag_options' ] = {}
info[ 'advanced_import_options' ] = ClientDefaults.GetDefaultImportFileOptions()
info[ 'last_checked' ] = None
@ -6582,7 +6672,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
def EventResetCache( self, event ):
message = '''Resetting this subscription's cache will delete ''' + HydrusData.ConvertIntToPrettyString( len( self._original_info[ 'url_cache' ] ) ) + ''' remembered links, meaning when the subscription next runs, it will try to download those all over again. This may be expensive in time and data. Only do it if you are willing to wait. Do you want to do it?'''
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:

View File

@ -85,7 +85,6 @@ class FullscreenHoverFrame( wx.Frame ):
( mouse_x, mouse_y ) = wx.GetMousePosition()
( my_width, my_height ) = self.GetSize()
( my_x, my_y ) = self.GetPosition()
( should_resize, ( my_ideal_width, my_ideal_height ), ( my_ideal_x, my_ideal_y ) ) = self._GetIdealSizeAndPosition()
@ -108,7 +107,11 @@ class FullscreenHoverFrame( wx.Frame ):
in_position = in_x and in_y
mouse_over_important_media = ( ClientGUICanvas.ShouldHaveAnimationBar( self._current_media ) or mime in HC.VIDEO or mime == HC.APPLICATION_FLASH ) and self.GetParent().MouseIsOverMedia()
mouse_is_over_interactable_media = mime == HC.APPLICATION_FLASH and self.GetParent().MouseIsOverMedia()
mouse_is_near_animation_bar = self.GetParent().MouseIsNearAnimationBar()
mouse_is_over_something_important = mouse_is_over_interactable_media or mouse_is_near_animation_bar
current_focus = wx.Window.FindFocus()
@ -123,7 +126,7 @@ class FullscreenHoverFrame( wx.Frame ):
tlp = tlp.GetParent()
ready_to_show = in_position and not mouse_over_important_media and no_dialogs_open and my_parent_in_focus_tree
ready_to_show = in_position and not mouse_is_over_something_important and no_dialogs_open and my_parent_in_focus_tree
ready_to_hide = not in_position or not no_dialogs_open or not my_parent_in_focus_tree
if ready_to_show:

View File

@ -1311,7 +1311,10 @@ class ManagementPanelDumper( ManagementPanel ):
with ClientGUIDialogs.DialogYesNo( self, 'This page is still dumping. Are you sure you want to close it?' ) as dlg:
if dlg.ShowModal() == wx.ID_NO: raise Exception()
if dlg.ShowModal() == wx.ID_NO:
raise HydrusExceptions.PermissionException()
@ -1686,7 +1689,10 @@ class ManagementPanelImport( ManagementPanel ):
with ClientGUIDialogs.DialogYesNo( self, 'This page is still importing. Are you sure you want to close it?' ) as dlg:
if dlg.ShowModal() == wx.ID_NO: raise Exception()
if dlg.ShowModal() == wx.ID_NO:
raise HydrusExceptions.PermissionException()
@ -1745,7 +1751,7 @@ class ManagementPanelImports( ManagementPanelImport ):
self._get_tags_if_redundant.Hide()
self._file_limit = ClientGUICommon.NoneableSpinCtrl( self._pending_import_queues_panel, 'file limit', none_phrase = 'no limit', min = 1, max = 1000000 )
self._file_limit.SetValue( 500 )
self._file_limit.SetValue( HC.options[ 'gallery_file_limit' ] )
queue_buttons_vbox = wx.BoxSizer( wx.VERTICAL )
@ -2637,7 +2643,10 @@ class ManagementPanelImportHDD( ManagementPanel ):
with ClientGUIDialogs.DialogYesNo( self, 'This page is still importing. Are you sure you want to close it?' ) as dlg:
if dlg.ShowModal() == wx.ID_NO: raise Exception()
if dlg.ShowModal() == wx.ID_NO:
raise HydrusExceptions.PermissionException()
@ -3004,7 +3013,10 @@ class ManagementPanelImportThreadWatcher( ManagementPanel ):
with ClientGUIDialogs.DialogYesNo( self, 'This page is still importing. Are you sure you want to close it?' ) as dlg:
if dlg.ShowModal() == wx.ID_NO: raise Exception()
if dlg.ShowModal() == wx.ID_NO:
raise HydrusExceptions.PermissionException()

View File

@ -157,7 +157,10 @@ class Page( wx.SplitterWindow ):
def TestAbleToClose( self ): self._management_panel.TestAbleToClose()
def TestAbleToClose( self ):
self._management_panel.TestAbleToClose()
class GUISession( HydrusSerialisable.SerialisableBaseNamed ):

View File

@ -240,7 +240,7 @@ class GalleryQuery( HydrusSerialisable.SerialisableBase ):
self._query_type = None
self._query = None
self._get_tags_if_redundant = False
self._file_limit = 500
self._file_limit = HC.options[ 'gallery_file_limit' ]
self._paused = False
self._page_index = 0
self._url_cache = None
@ -718,7 +718,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
self._query_type = None
self._query = None
self._get_tags_if_redundant = False
self._file_limit = 500
self._file_limit = HC.options[ 'gallery_file_limit' ]
self._periodic = None
self._page_index = 0
self._url_cache = None

View File

@ -37,7 +37,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 171
SOFTWARE_VERSION = 172
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -92,6 +92,10 @@ IMPORT_FOLDER_TYPE_SYNCHRONISE = 1
EXPORT_FOLDER_TYPE_REGULAR = 0
EXPORT_FOLDER_TYPE_SYNCHRONISE = 1
HYDRUS_CLIENT = 0
HYDRUS_SERVER = 1
HYDRUS_TEST = 2
GET_DATA = 0
POST_DATA = 1
POST_PETITIONS = 2

View File

@ -13,13 +13,38 @@ import sys
import threading
import time
import traceback
import wx
class HydrusController( wx.App ):
class HydrusController( object ):
db_class = HydrusDB.HydrusDB
pubsub_binding_errors_to_ignore = []
def __init__( self ):
HydrusGlobals.controller = self
self._pubsub = HydrusPubSub.HydrusPubSub( self, self.pubsub_binding_errors_to_ignore )
self._currently_doing_pubsub = False
self._daemons = []
self._caches = {}
self._managers = {}
self._call_to_threads = [ HydrusThreading.DAEMONCallToThread( self ) for i in range( 10 ) ]
self._timestamps = collections.defaultdict( lambda: 0 )
self._timestamps[ 'boot' ] = HydrusData.GetNow()
self._just_woke_from_sleep = False
self._system_busy = False
def _InitDB( self ):
raise NotImplementedError()
def _Read( self, action, *args, **kwargs ):
result = self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
@ -71,8 +96,6 @@ class HydrusController( wx.App ):
def GetCache( self, name ): return self._caches[ name ]
def GetDB( self ): return self._db
def GetManager( self, name ): return self._managers[ name ]
def GoodTimeToDoBackgroundWork( self ):
@ -87,45 +110,23 @@ class HydrusController( wx.App ):
return self._just_woke_from_sleep
def InitDaemons( self ):
def InitModel( self ):
self._db = self._InitDB()
threading.Thread( target = self._db.MainLoop, name = 'Database Main Loop' ).start()
def InitView( self ):
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SleepCheck', HydrusDaemons.DAEMONSleepCheck, period = 120 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'MaintainDB', HydrusDaemons.DAEMONMaintainDB, period = 300 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'MaintainMemory', HydrusDaemons.DAEMONMaintainMemory, period = 300 ) )
def InitData( self ):
self.SetAssertMode( wx.PYAPP_ASSERT_SUPPRESS )
HydrusGlobals.controller = self
self._pubsub = HydrusPubSub.HydrusPubSub( self, self.pubsub_binding_errors_to_ignore )
self._currently_doing_pubsub = False
self._daemons = []
self._caches = {}
self._managers = {}
self._call_to_threads = [ HydrusThreading.DAEMONCallToThread( self ) for i in range( 10 ) ]
self._timestamps = collections.defaultdict( lambda: 0 )
self._timestamps[ 'boot' ] = HydrusData.GetNow()
self._just_woke_from_sleep = False
self._system_busy = False
def InitDB( self ):
self._db = self.db_class( self )
threading.Thread( target = self._db.MainLoop, name = 'Database Main Loop' ).start()
def MaintainDB( self ):
raise NotImplementedError()
pass
def MaintainMemory( self ):
@ -151,13 +152,25 @@ class HydrusController( wx.App ):
def Read( self, action, *args, **kwargs ): return self._Read( action, *args, **kwargs )
def ShutdownDB( self ):
def ShutdownModel( self ):
self._db.Shutdown()
HydrusGlobals.model_shutdown = True
while not self._db.LoopIsFinished(): time.sleep( 0.1 )
def ShutdownView( self ):
HydrusGlobals.view_shutdown = True
self.pub( 'wake_daemons' )
while True in ( daemon.is_alive() for daemon in self._daemons ):
time.sleep( 0.1 )
def SleepCheck( self ):
if HydrusData.TimeHasPassed( self._timestamps[ 'now_awake' ] ):
@ -195,7 +208,7 @@ class HydrusController( wx.App ):
while True:
if HydrusGlobals.view_shutdown: raise Exception( 'Application shutting down!' )
if HydrusGlobals.view_shutdown: raise HydrusExceptions.ShutdownException( 'Application shutting down!' )
elif self._pubsub.NoJobsQueued() and not self._currently_doing_pubsub: return
else: time.sleep( 0.00001 )

View File

@ -235,7 +235,7 @@ class HydrusDB( object ):
try:
( priority, job ) = self._jobs.get( timeout = 1 )
( priority, job ) = self._jobs.get( timeout = 0.1 )
self._currently_doing_job = True

View File

@ -578,63 +578,43 @@ def IntelligentMassIntersect( sets_to_reduce ):
if answer is None: return set()
else: return answer
def IsAlreadyRunning():
def IsAlreadyRunning( instance ):
try:
me = psutil.Process()
my_pid = me.pid
my_exe = me.exe()
my_cmd = me.cmdline()
except psutil.Error:
return False
path = HC.BASE_DIR + os.path.sep + instance + '_running'
for p in psutil.process_iter():
if os.path.exists( path ):
try:
with open( path, 'rb' ) as f:
if not p.is_running():
result = f.read()
try:
continue
( pid, create_time ) = result.split( os.linesep )
pid = int( pid )
create_time = float( create_time )
except ValueError:
return False
# this is to skip the linux PyInstaller loader
is_my_parent = False
for c in p.children():
try:
if c.pid == my_pid:
if psutil.pid_exists( pid ):
is_my_parent = True
p = psutil.Process( pid )
break
if p.create_time() == create_time and p.is_running():
return True
if is_my_parent:
except psutil.Error:
continue
p_pid = p.pid
p_exe = p.exe()
p_cmd = p.cmdline()
except psutil.Error:
continue
if p_pid != my_pid:
if p_exe == my_exe and p_cmd == my_cmd:
print( p.children( recursive = True ) )
return True
return False
@ -662,6 +642,30 @@ def ReadFileLikeAsBlocks( f, block_size ):
next_block = f.read( block_size )
def RecordRunningStart( instance ):
path = HC.BASE_DIR + os.path.sep + instance + '_running'
record_string = ''
try:
me = psutil.Process()
record_string += str( me.pid )
record_string += os.linesep
record_string += str( me.create_time() )
except psutil.Error:
return
with open( path, 'wb' ) as f:
f.write( record_string )
def ShowExceptionDefault( e ):
etype = type( e )
@ -1249,7 +1253,7 @@ class JobDatabase( object ):
while True:
if self._result_ready.wait( 5 ) == True: break
elif HydrusGlobals.model_shutdown: raise Exception( 'Application quit before db could serve result!' )
elif HydrusGlobals.model_shutdown: raise HydrusExceptions.ShutdownException( 'Application quit before db could serve result!' )
if isinstance( self._result, HydrusExceptions.DBException ):

View File

@ -2,6 +2,8 @@ controller = None
view_shutdown = False
model_shutdown = False
instance = None
is_first_start = False
is_db_updated = False

View File

@ -537,8 +537,9 @@ class HTTPConnection( object ):
# some booru is giving daft redirect responses
print( url )
url = urllib.quote( url, safe = '/?=&' )
url = urllib.quote( url.encode( 'utf-8' ), safe = '/?=&' )
print( url )
if not url.startswith( self._scheme ):

View File

@ -290,6 +290,7 @@ class HydrusServiceRepository( HydrusServiceRestricted ):
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

View File

@ -3,6 +3,7 @@ import HydrusConstants as HC
import HydrusExceptions
import HydrusFileHandling
import HydrusImageHandling
import HydrusSerialisable
import HydrusThreading
import os
import ServerFiles
@ -1018,6 +1019,21 @@ class HydrusResourceCommandRestrictedContentUpdate( HydrusResourceCommandRestric
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 = HydrusSerialisable.DumpToNetworkString( content_update )
response_context = ResponseContext( 200, mime = HC.APPLICATION_JSON, body = network_string )
return response_context
class HydrusResourceCommandRestrictedServiceUpdate( HydrusResourceCommandRestricted ):
GET_PERMISSION = HC.GET_DATA

View File

@ -17,15 +17,9 @@ class DAEMON( threading.Thread ):
self._event = threading.Event()
self._controller.sub( self, 'shutdown', 'shutdown' )
self._controller.sub( self, 'wake', 'wake_daemons' )
def shutdown( self ):
self._event.set()
def wake( self ):
self._event.set()

View File

@ -2,37 +2,116 @@ import httplib
import HydrusConstants as HC
import HydrusController
import HydrusData
import HydrusExceptions
import HydrusGlobals
import HydrusNetworking
import HydrusServer
import HydrusSessions
import HydrusThreading
import os
import ServerDaemons
import ServerDB
import os
import sys
import time
import traceback
import wx
from twisted.internet import reactor
from twisted.internet import defer
ID_MAINTENANCE_EVENT_TIMER = wx.NewId()
MAINTENANCE_PERIOD = 5 * 60
def GetStartingAction():
action = None
args = sys.argv[1:]
if len( args ) > 0:
command = args[0]
while command.startswith( '-' ):
command = command[ 1: ]
if command == 'help':
action = 'help'
else:
already_running = HydrusData.IsAlreadyRunning( 'server' )
if command == 'start':
if already_running:
raise HydrusExceptions.PermissionException( 'The server is already running!' )
else:
action = None
elif command == 'stop':
if already_running:
action = 'stop'
else:
raise HydrusExceptions.PermissionException( 'The server is not running, so it cannot be stopped!' )
elif command == 'restart':
if already_running:
action = 'restart'
else:
action = 'start'
else:
already_running = HydrusData.IsAlreadyRunning( 'server' )
if not already_running:
action = 'start'
else:
print( 'The server is already running. Would you like to [s]top it, or [r]estart it?' )
answer = raw_input()
if len( answer ) > 0:
answer = answer[0]
if answer == 's':
action = 'stop'
elif answer == 'r':
action = 'restart'
return action
class Controller( HydrusController.HydrusController ):
db_class = ServerDB.DB
def _AlreadyRunning( self, port ):
def _InitDB( self ):
try:
connection = HydrusNetworking.GetLocalConnection( port )
connection.close()
return True
except: return False
return ServerDB.DB( self )
def ActionService( self, service_key, action ):
@ -93,23 +172,55 @@ class Controller( HydrusController.HydrusController ):
elif action == 'stop': del self._services[ service_key ]
reactor.callFromThread( TWISTEDDoIt )
def EventExit( self, event ):
def CheckIfAdminPortInUse( self ):
wx.CallAfter( self._tbicon.Destroy )
( service_type, options ) = self.Read( 'service_info', HC.SERVER_ADMIN_KEY )
self.ShutdownDB()
port = options[ 'port' ]
already_bound = False
try:
connection = HydrusNetworking.GetLocalConnection( port )
connection.close()
already_bound = True
except: pass
if already_bound:
raise HydrusExceptions.PermissionException( 'Something was already bound to port ' + HydrusData.ToString( port ) )
def GetManager( self, manager_type ): return self._managers[ manager_type ]
def InitDaemons( self ):
def Exit( self ):
HydrusController.HydrusController.InitDaemons( self )
self.ShutdownView()
self.ShutdownModel()
def InitModel( self ):
HydrusController.HydrusController.InitModel( self )
self._managers[ 'restricted_services_sessions' ] = HydrusSessions.HydrusSessionManagerServer()
self._managers[ 'messaging_sessions' ] = HydrusSessions.HydrusMessagingSessionManagerServer()
self._services = {}
self.sub( self, 'ActionService', 'action_service' )
def InitView( self ):
HydrusController.HydrusController.InitView( self )
self._daemons.append( HydrusThreading.DAEMONQueue( self, 'FlushRequestsMade', ServerDaemons.DAEMONFlushRequestsMade, 'request_made', period = 60 ) )
@ -120,110 +231,48 @@ class Controller( HydrusController.HydrusController ):
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckDataUsage', ServerDaemons.DAEMONCheckDataUsage, period = 86400 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'UPnP', ServerDaemons.DAEMONUPnP, ( 'notify_new_options', ), period = 43200 ) )
self.CheckIfAdminPortInUse()
service_keys = self.Read( 'service_keys' )
for service_key in service_keys: self.ActionService( service_key, 'start' )
def JustWokeFromSleep( self ): return False
def MaintainDB( self ):
pass
def NotifyPubSubs( self ):
self.CallToThread( self.ProcessPubSub )
def OnInit( self ):
def Run( self, action ):
try:
if action in ( 'stop', 'restart' ):
self.InitData()
raise HydrusExceptions.PermissionException( 'This isn\'t working yet! You will have to Ctrl+C to quit an existing server for now!' )
if HydrusData.IsAlreadyRunning():
raise Exception( 'It looks like this server is already running!' )
self.InitDB()
self.Bind( wx.EVT_MENU, self.EventExit, id=wx.ID_EXIT )
self._managers[ 'restricted_services_sessions' ] = HydrusSessions.HydrusSessionManagerServer()
self._managers[ 'messaging_sessions' ] = HydrusSessions.HydrusMessagingSessionManagerServer()
self.sub( self, 'ActionService', 'action_service' )
self._services = {}
#
( service_type, options ) = self.Read( 'service_info', HC.SERVER_ADMIN_KEY )
port = options[ 'port' ]
try:
connection = HydrusNetworking.GetLocalConnection( port )
connection.close()
message = 'Something was already bound to port ' + HydrusData.ToString( port )
wx.MessageBox( message )
return False
except: pass
#
service_keys = self.Read( 'service_keys' )
for service_key in service_keys: self.ActionService( service_key, 'start' )
self.InitDaemons()
if HC.PLATFORM_WINDOWS: self._tbicon = TaskBarIcon()
else:
stay_open_frame = wx.Frame( None, title = 'Hydrus Server' )
stay_open_frame.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
stay_open_frame.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) )
wx.StaticText( stay_open_frame, label = 'The hydrus server is now running.' + os.linesep * 2 + 'Close this window to stop it.' )
( x, y ) = stay_open_frame.GetEffectiveMinSize()
stay_open_frame.SetInitialSize( ( x, y ) )
stay_open_frame.Show()
return True
except Exception as e:
print( traceback.format_exc() )
return False
self.ShutdownSiblingInstance()
class TaskBarIcon( wx.TaskBarIcon ):
def __init__( self ):
wx.TaskBarIcon.__init__( self )
icon = wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO )
self.SetIcon( icon, 'hydrus server' )
self._tbmenu = wx.Menu()
self._tbmenu.Append( wx.ID_EXIT, 'exit' )
self.Bind( wx.EVT_TASKBAR_RIGHT_DOWN, lambda event: self.PopupMenu( self._tbmenu ) )
if action in ( 'start', 'restart' ):
HydrusData.RecordRunningStart( 'server' )
self.InitModel()
self.InitView()
while not HydrusGlobals.model_shutdown:
try:
time.sleep( 1 )
except KeyboardInterrupt:
self.Exit()

View File

@ -799,6 +799,53 @@ class DB( HydrusDB.HydrusDB ):
return [ registration_key for ( registration_key, account_key, access_key ) in keys ]
def _GenerateImmediateContentUpdate( self, service_key ):
update_ends = self._GetUpdateEnds()
latest_end = update_ends[ service_key ]
begin = latest_end + 1
end = HydrusData.GetNow()
service_id = self._GetServiceId( service_key )
service_type = self._GetServiceType( service_id )
if service_type == HC.FILE_REPOSITORY:
iterator = self._IterateFileUpdateContentData
elif service_type == HC.TAG_REPOSITORY:
iterator = self._IterateTagUpdateContentData
subindex = 0
weight = 0
content_update_package = HydrusData.ServerToClientContentUpdatePackage()
smaller_time_step = max( 10, ( end - begin ) / 100 )
sub_begin = begin
while sub_begin <= end:
sub_end = min( ( sub_begin + smaller_time_step ) - 1, end )
for ( data_type, action, rows, hash_ids_to_hashes, rows_weight ) in iterator( service_id, sub_begin, sub_end ):
content_update_package.AddContentData( data_type, action, rows, hash_ids_to_hashes )
sub_begin += smaller_time_step
return content_update_package
def _GenerateUpdate( self, service_key, begin, end ):
service_id = self._GetServiceId( service_key )
@ -2060,6 +2107,7 @@ class DB( HydrusDB.HydrusDB ):
elif action == 'account_key_from_identifier': result = self._GetAccountKeyFromIdentifier( *args, **kwargs )
elif action == 'account_types': result = self._GetAccountTypes( *args, **kwargs )
elif action == 'dirty_updates': result = self._GetDirtyUpdates( *args, **kwargs )
elif action == 'immediate_content_update': result = self._GenerateImmediateContentUpdate( *args, **kwargs )
elif action == 'init': result = self._InitAdmin( *args, **kwargs )
elif action == 'ip': result = self._GetIPTimestamp( *args, **kwargs )
elif action == 'messaging_sessions': result = self._GetMessagingSessions( *args, **kwargs )

View File

@ -33,36 +33,61 @@ def DAEMONFlushRequestsMade( all_requests ): HydrusGlobals.controller.WriteSynch
def DAEMONGenerateUpdates():
dirty_updates = HydrusGlobals.controller.Read( 'dirty_updates' )
for ( service_key, tuples ) in dirty_updates.items():
if not HydrusGlobals.server_busy:
for ( begin, end ) in tuples:
dirty_updates = HydrusGlobals.controller.Read( 'dirty_updates' )
for ( service_key, tuples ) in dirty_updates.items():
HydrusGlobals.controller.WriteSynchronous( 'clean_update', service_key, begin, end )
for ( begin, end ) in tuples:
if HydrusGlobals.view_shutdown:
return
HydrusGlobals.server_busy = True
HydrusGlobals.controller.WriteSynchronous( 'clean_update', service_key, begin, end )
HydrusGlobals.server_busy = False
time.sleep( 1 )
update_ends = HydrusGlobals.controller.Read( 'update_ends' )
for ( service_key, biggest_end ) in update_ends.items():
update_ends = HydrusGlobals.controller.Read( 'update_ends' )
now = HydrusData.GetNow()
next_begin = biggest_end + 1
next_end = biggest_end + HC.UPDATE_DURATION
while next_end < now:
for ( service_key, biggest_end ) in update_ends.items():
HydrusGlobals.controller.WriteSynchronous( 'create_update', service_key, next_begin, next_end )
biggest_end = next_end
if HydrusGlobals.view_shutdown:
return
now = HydrusData.GetNow()
next_begin = biggest_end + 1
next_end = biggest_end + HC.UPDATE_DURATION
HydrusGlobals.server_busy = True
while next_end < now:
HydrusGlobals.controller.WriteSynchronous( 'create_update', service_key, next_begin, next_end )
biggest_end = next_end
now = HydrusData.GetNow()
next_begin = biggest_end + 1
next_end = biggest_end + HC.UPDATE_DURATION
HydrusGlobals.server_busy = False
time.sleep( 1 )
def DAEMONUPnP():

126
server.py Normal file
View File

@ -0,0 +1,126 @@
# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
# http://sam.zoy.org/wtfpl/COPYING for more details.
try:
import locale
try: locale.setlocale( locale.LC_ALL, '' )
except: pass
from include import HydrusConstants as HC
import os
import sys
import time
from include import ServerController
import threading
from twisted.internet import reactor
from include import HydrusExceptions
from include import HydrusGlobals
import traceback
HydrusGlobals.instance = HC.HYDRUS_SERVER
action = ServerController.GetStartingAction()
if action is None or action == 'help':
print( 'This is the hydrus server. It accepts these commands:' )
print( '' )
print( 'server start - runs the server' )
print( 'server stop - stops an already running server' )
print( 'server restart - stops an already running server, then runs itself' )
print( '' )
print( 'You can also run \'server\' without arguments. Depending on what is going on, it will try to start or it will ask you if you want to stop or restart.' )
print( 'You can also stop the server just by hitting your keyboard interrupt (usually Ctrl+C).')
print( '' )
print( 'PROTIP: stop and restart don\'t work yet lol')
else:
if action in ( 'start', 'restart' ):
print( 'Running...' )
error_occured = False
initial_sys_stdout = sys.stdout
initial_sys_stderr = sys.stderr
with open( HC.LOGS_DIR + os.path.sep + 'server.log', 'a' ) as f:
sys.stdout = f
sys.stderr = f
try:
print( 'hydrus server started at ' + time.ctime() )
threading.Thread( target = reactor.run, kwargs = { 'installSignalHandlers' : 0 } ).start()
controller = ServerController.Controller()
controller.Run( action )
except HydrusExceptions.PermissionException as e:
error_occured = True
error = str( e )
print( error )
except:
error_occured = True
error = traceback.format_exc()
print( 'hydrus server failed at ' + time.ctime() )
print( traceback.format_exc() )
finally:
HydrusGlobals.view_shutdown = True
HydrusGlobals.model_shutdown = True
try: controller.pubimmediate( 'wake_daemons' )
except: pass
reactor.callFromThread( reactor.stop )
print( 'hydrus server shut down at ' + time.ctime() )
sys.stdout = initial_sys_stdout
sys.stderr = initial_sys_stderr
if error_occured:
print( error )
if action in ( 'start', 'restart' ):
print( 'Finished.' )
except HydrusExceptions.PermissionException as e:
print( e )
except:
import traceback
print( 'Critical error occured! Details written to crash.log!' )
with open( 'crash.log', 'wb' ) as f: f.write( traceback.format_exc() )

View File

@ -1,73 +0,0 @@
# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
# http://sam.zoy.org/wtfpl/COPYING for more details.
try:
import locale
try: locale.setlocale( locale.LC_ALL, '' )
except: pass
from include import HydrusConstants as HC
import os
import sys
import time
from include import ServerController
import threading
from twisted.internet import reactor
from include import HydrusGlobals
initial_sys_stdout = sys.stdout
initial_sys_stderr = sys.stderr
with open( HC.LOGS_DIR + os.path.sep + 'server.log', 'a' ) as f:
sys.stdout = f
sys.stderr = f
try:
print( 'hydrus server started at ' + time.ctime() )
threading.Thread( target = reactor.run, kwargs = { 'installSignalHandlers' : 0 } ).start()
app = ServerController.Controller()
app.MainLoop()
except:
print( 'hydrus server failed at ' + time.ctime() )
import traceback
print( traceback.format_exc() )
finally:
HydrusGlobals.view_shutdown = True
HydrusGlobals.model_shutdown = True
app.pubimmediate( 'shutdown' )
reactor.callFromThread( reactor.stop )
print( 'hydrus server shut down at ' + time.ctime() )
sys.stdout = initial_sys_stdout
sys.stderr = initial_sys_stderr
except:
import traceback
print( 'Critical error occured! Details written to crash.log!' )
with open( 'crash.log', 'wb' ) as f: f.write( traceback.format_exc() )

76
test.py
View File

@ -38,11 +38,13 @@ from include import ClientCaches
from include import ClientData
from include import HydrusData
HydrusGlobals.instance = HC.HYDRUS_TEST
only_run = None
class App( wx.App ):
class Controller( object ):
def OnInit( self ):
def __init__( self ):
HydrusGlobals.controller = self
self._pubsub = HydrusPubSub.HydrusPubSub( self )
@ -94,32 +96,6 @@ class App( wx.App ):
self._cookies = {}
suites = []
if only_run is None: run_all = True
else: run_all = False
if run_all or only_run == 'cc': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientConstants ) )
if run_all or only_run == 'daemons': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDaemons ) )
if run_all or only_run == 'dialogs': suites.append( unittest.TestLoader().loadTestsFromModule( TestDialogs ) )
if run_all or only_run == 'db': suites.append( unittest.TestLoader().loadTestsFromModule( TestDB ) )
if run_all or only_run == 'downloading': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDownloading ) )
if run_all or only_run == 'encryption': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusEncryption ) )
if run_all or only_run == 'functions': suites.append( unittest.TestLoader().loadTestsFromModule( TestFunctions ) )
if run_all or only_run == 'image': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusImageHandling ) )
if run_all or only_run == 'nat': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNATPunch ) )
if run_all or only_run == 'server': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusServer ) )
if run_all or only_run == 'sessions': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSessions ) )
if run_all or only_run == 'tags': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusTags ) )
suite = unittest.TestSuite( suites )
runner = unittest.TextTestRunner( verbosity = 1 )
runner.run( suite )
return True
def pub( self, topic, *args, **kwargs ):
@ -174,6 +150,33 @@ class App( wx.App ):
def ResetIdleTimer( self ): pass
def Run( self ):
suites = []
if only_run is None: run_all = True
else: run_all = False
if run_all or only_run == 'cc': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientConstants ) )
if run_all or only_run == 'daemons': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDaemons ) )
if run_all or only_run == 'dialogs': suites.append( unittest.TestLoader().loadTestsFromModule( TestDialogs ) )
if run_all or only_run == 'db': suites.append( unittest.TestLoader().loadTestsFromModule( TestDB ) )
if run_all or only_run == 'downloading': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDownloading ) )
if run_all or only_run == 'encryption': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusEncryption ) )
if run_all or only_run == 'functions': suites.append( unittest.TestLoader().loadTestsFromModule( TestFunctions ) )
if run_all or only_run == 'image': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusImageHandling ) )
if run_all or only_run == 'nat': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNATPunch ) )
if run_all or only_run == 'server': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusServer ) )
if run_all or only_run == 'sessions': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSessions ) )
if run_all or only_run == 'tags': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusTags ) )
suite = unittest.TestSuite( suites )
runner = unittest.TextTestRunner( verbosity = 1 )
runner.run( suite )
def SetHTTP( self, http ): self._http = http
def SetRead( self, name, value ): self._reads[ name ] = value
@ -213,17 +216,28 @@ if __name__ == '__main__':
threading.Thread( target = reactor.run, kwargs = { 'installSignalHandlers' : 0 } ).start()
app = App()
controller = Controller()
app = wx.App()
win = wx.Frame( None )
wx.CallAfter( controller.Run )
#threading.Thread( target = controller.Run ).start()
wx.CallAfter( win.Destroy )
app.MainLoop()
finally:
HydrusGlobals.view_shutdown = True
app.pubimmediate( 'wake_daemons' )
controller.pubimmediate( 'wake_daemons' )
HydrusGlobals.model_shutdown = True
app.pubimmediate( 'shutdown' )
controller.pubimmediate( 'wake_daemons' )
reactor.callFromThread( reactor.stop )