Version 182
This commit is contained in:
parent
3fce11362f
commit
facd3ded44
Binary file not shown.
Binary file not shown.
Binary file not shown.
23
client.pyw
23
client.pyw
|
@ -12,6 +12,7 @@ try:
|
|||
except: pass
|
||||
|
||||
from include import HydrusConstants as HC
|
||||
from include import HydrusData
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -21,21 +22,16 @@ try:
|
|||
import threading
|
||||
from twisted.internet import reactor
|
||||
from include import HydrusGlobals
|
||||
from include import HydrusLogger
|
||||
import traceback
|
||||
|
||||
HydrusGlobals.instance = HC.HYDRUS_CLIENT
|
||||
|
||||
initial_sys_stdout = sys.stdout
|
||||
initial_sys_stderr = sys.stderr
|
||||
|
||||
with open( os.path.join( HC.LOGS_DIR, 'client.log' ), 'a' ) as f:
|
||||
|
||||
sys.stdout = f
|
||||
sys.stderr = f
|
||||
with HydrusLogger.HydrusLogger( 'client.log' ) as logger:
|
||||
|
||||
try:
|
||||
|
||||
print( 'hydrus client started at ' + time.ctime() )
|
||||
HydrusData.Print( 'hydrus client started' )
|
||||
|
||||
threading.Thread( target = reactor.run, kwargs = { 'installSignalHandlers' : 0 } ).start()
|
||||
|
||||
|
@ -45,9 +41,9 @@ try:
|
|||
|
||||
except:
|
||||
|
||||
print( 'hydrus client failed at ' + time.ctime() )
|
||||
HydrusData.Print( 'hydrus client failed' )
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
finally:
|
||||
|
||||
|
@ -59,18 +55,15 @@ try:
|
|||
|
||||
reactor.callFromThread( reactor.stop )
|
||||
|
||||
print( 'hydrus client shut down at ' + time.ctime() )
|
||||
HydrusData.Print( 'hydrus client shut down' )
|
||||
|
||||
|
||||
|
||||
sys.stdout = initial_sys_stdout
|
||||
sys.stderr = initial_sys_stderr
|
||||
|
||||
except:
|
||||
|
||||
import traceback
|
||||
|
||||
print( 'Critical error occured! Details written to crash.log!' )
|
||||
HydrusData.Print( 'Critical error occured! Details written to crash.log!' )
|
||||
|
||||
with open( 'crash.log', 'wb' ) as f: f.write( traceback.format_exc() )
|
||||
|
|
@ -8,6 +8,43 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 182</h3></li>
|
||||
<ul>
|
||||
<li>all printing to the log should now be unicode safe</li>
|
||||
<li>some other, miscellaneous file-write locations should now be unicode safe</li>
|
||||
<li>cleaned up some now-redundant unicode-bytestring conversion error handling</li>
|
||||
<li>the animation scanbar will now draw a little darker while the animation is paused</li>
|
||||
<li>the client now uses the same new log object as the server, so all logged data will be copied to the terminal, and all logged statements will be timestamped</li>
|
||||
<li>the client and server's boot and exit statements are more harmonised</li>
|
||||
<li>server backup is more log-verbose</li>
|
||||
<li>server backup now makes a simple copy to 'server_backup' folder--no more _backup nonsense</li>
|
||||
<li>all serverside requests will now print a line in the form 'PORT METHOD PATH HTTP_CODE in TIME TOOK' to the log</li>
|
||||
<li>page up and down now work (again?) for the thumbnail view. adding shift also works for selecting a page at a time</li>
|
||||
<li>improved some animation painting logic</li>
|
||||
<li>improved some rating control painting logic</li>
|
||||
<li>improved splash screen painting logic</li>
|
||||
<li>improved all other, misc painting logic--many things should flicker less or render just a little quicker</li>
|
||||
<li>ratings hover window will no longer re-layout (causing flicker) on ratings set</li>
|
||||
<li>improved some media canvas painting logic to de-jaggify some zooming</li>
|
||||
<li>in the manage options dialog, tag-related gui options are moved from the gui panel to the new tags panel</li>
|
||||
<li>added 'apply all parents to all services' option to the tags panel</li>
|
||||
<li>the delete key now removes active search predicates and tags in the manage tags dialog</li>
|
||||
<li>fixed webm link parsing for rule34hentai.net</li>
|
||||
<li>if you attempt to petition multiple tags, you will now be presented with a dialog asking you if you want to use the same reason for all the petitions</li>
|
||||
<li>edit import folder dialog now has scrollbars and will resize itself based on your monitor size</li>
|
||||
<li>if the static ffmpeg executable is absent from the bin folder, the client will now attempt to just call 'ffmpeg' in the normal system path</li>
|
||||
<li>fixed some 'None media' data calculation bugs when media viewer closed during very fast slideshow</li>
|
||||
<li>fixed a 'None media' mouse position bug when media viewer closed during very fast slideshow</li>
|
||||
<li>all wx timers will explicitly stop on exceptions (which should reduce some types of error spam)</li>
|
||||
<li>refactored client hydrus network session manager to a better position</li>
|
||||
<li>wrote a better wma/wmv determining test using ffmpeg</li>
|
||||
<li>refactored cv2 (OpenCV) out of the server's import tree</li>
|
||||
<li>fixed some server boot crash error handling</li>
|
||||
<li>cleaned some autocomplete matches-compiling code</li>
|
||||
<li>deletepath and recyclepath will no longer throw an error if the path does not exist</li>
|
||||
<li>server_messages folder is no longer referenced in the code--if you have it, feel free to delete it</li>
|
||||
<li>memory cleans up a little faster after gui page deletion</li>
|
||||
</ul>
|
||||
<li><h3>version 181</h3></li>
|
||||
<ul>
|
||||
<li>fixed a potential bug in the server's db, very important you update this week if running on Windows</li>
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<li>export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH</li>
|
||||
</ul></p>
|
||||
<p>For Windows, you can do the same thing with easy_install and pip and module installers. Since it doesn't natively come with Python, you'll probably need to go through a bigger list. You'll find <a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/">this</a> page very helpful. I have a fair bit of experience with Windows python, so send me a mail if you need help.</a>
|
||||
<p>A user has also created <a href="https://github.com/eider-i128/hydrus-win">an environment</a> to help Windows users run from source and build a distribution.</p>
|
||||
<p>Some people have encountered problems with wxPython 3.0, so you might want to try 2.9.x. Again, YMMV.</p>
|
||||
<p>You'll probably want to install Pillow (pip should do it for Linux/OS X, easy_install for Windows) instead of PIL, but go back to PIL if Pillow doesn't work.</p>
|
||||
<p>If you want to import videos, you will need to put a static <a href="https://ffmpeg.org/">FFMPEG</a> executable in the install_dir/bin directory. Have a look at how I do it in the extractable compiled releases if you can't figure it out. You can either copy the exe from one of those releases, or download the latest build right from the FFMPEG site. I don't include these exes in the source release just because they are so big.</a>
|
||||
|
|
|
@ -7,6 +7,7 @@ import HydrusExceptions
|
|||
import HydrusFileHandling
|
||||
import HydrusImageHandling
|
||||
import HydrusPaths
|
||||
import HydrusSessions
|
||||
import os
|
||||
import random
|
||||
import Queue
|
||||
|
@ -525,7 +526,7 @@ class ThumbnailCache( object ):
|
|||
|
||||
except HydrusExceptions.NotFoundException:
|
||||
|
||||
print( 'Could not find the thumbnail for ' + hash.encode( 'hex' ) + '!' )
|
||||
HydrusData.Print( 'Could not find the thumbnail for ' + hash.encode( 'hex' ) + '!' )
|
||||
|
||||
return self._special_thumbs[ 'hydrus' ]
|
||||
|
||||
|
@ -610,7 +611,7 @@ class ThumbnailCache( object ):
|
|||
|
||||
try:
|
||||
|
||||
thumbnail = self.GetThumbnail( media ) # to load it
|
||||
self.GetThumbnail( media ) # to load it
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'waterfall_thumbnail', page_key, media )
|
||||
|
||||
|
@ -779,6 +780,60 @@ def BuildServiceKeysToSimpleChildrenToParents( service_keys_to_pairs_flat ):
|
|||
|
||||
return service_keys_to_simple_children_to_parents
|
||||
|
||||
class HydrusSessionManagerClient( object ):
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
existing_sessions = HydrusGlobals.client_controller.Read( 'hydrus_sessions' )
|
||||
|
||||
self._service_keys_to_sessions = { service_key : ( session_key, expires ) for ( service_key, session_key, expires ) in existing_sessions }
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
|
||||
def DeleteSessionKey( self, service_key ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
HydrusGlobals.client_controller.Write( 'delete_hydrus_session_key', service_key )
|
||||
|
||||
if service_key in self._service_keys_to_sessions: del self._service_keys_to_sessions[ service_key ]
|
||||
|
||||
|
||||
|
||||
def GetSessionKey( self, service_key ):
|
||||
|
||||
now = HydrusData.GetNow()
|
||||
|
||||
with self._lock:
|
||||
|
||||
if service_key in self._service_keys_to_sessions:
|
||||
|
||||
( session_key, expires ) = self._service_keys_to_sessions[ service_key ]
|
||||
|
||||
if now + 600 > expires: del self._service_keys_to_sessions[ service_key ]
|
||||
else: return session_key
|
||||
|
||||
|
||||
# session key expired or not found
|
||||
|
||||
service = HydrusGlobals.client_controller.GetServicesManager().GetService( service_key )
|
||||
|
||||
( response_gumpf, cookies ) = service.Request( HC.GET, 'session_key', return_cookies = True )
|
||||
|
||||
try: session_key = cookies[ 'session_key' ].decode( 'hex' )
|
||||
except: raise Exception( 'Service did not return a session key!' )
|
||||
|
||||
expires = now + HydrusSessions.HYDRUS_SESSION_LIFETIME
|
||||
|
||||
self._service_keys_to_sessions[ service_key ] = ( session_key, expires )
|
||||
|
||||
HydrusGlobals.client_controller.Write( 'hydrus_session', service_key, session_key, expires )
|
||||
|
||||
return session_key
|
||||
|
||||
|
||||
|
||||
class TagCensorshipManager( object ):
|
||||
|
||||
def __init__( self ):
|
||||
|
@ -807,8 +862,14 @@ class TagCensorshipManager( object ):
|
|||
|
||||
tag_matches = lambda tag: True in ( HydrusTags.CensorshipMatch( tag, censorship ) for censorship in censorships )
|
||||
|
||||
if blacklist: predicate = lambda tag: not tag_matches( tag )
|
||||
else: predicate = tag_matches
|
||||
if blacklist:
|
||||
|
||||
predicate = lambda tag: not tag_matches( tag )
|
||||
|
||||
else:
|
||||
|
||||
predicate = tag_matches
|
||||
|
||||
|
||||
self._service_keys_to_predicates[ service_key ] = predicate
|
||||
|
||||
|
@ -924,6 +985,13 @@ class TagParentsManager( object ):
|
|||
|
||||
def ExpandPredicates( self, service_key, predicates ):
|
||||
|
||||
new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
if new_options.GetBoolean( 'apply_all_parents_to_all_services' ):
|
||||
|
||||
service_key = CC.COMBINED_TAG_SERVICE_KEY
|
||||
|
||||
|
||||
results = []
|
||||
|
||||
with self._lock:
|
||||
|
@ -953,6 +1021,13 @@ class TagParentsManager( object ):
|
|||
|
||||
def ExpandTags( self, service_key, tags ):
|
||||
|
||||
new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
if new_options.GetBoolean( 'apply_all_parents_to_all_services' ):
|
||||
|
||||
service_key = CC.COMBINED_TAG_SERVICE_KEY
|
||||
|
||||
|
||||
with self._lock:
|
||||
|
||||
tags_results = set( tags )
|
||||
|
@ -968,6 +1043,13 @@ class TagParentsManager( object ):
|
|||
|
||||
def GetParents( self, service_key, tag ):
|
||||
|
||||
new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
if new_options.GetBoolean( 'apply_all_parents_to_all_services' ):
|
||||
|
||||
service_key = CC.COMBINED_TAG_SERVICE_KEY
|
||||
|
||||
|
||||
with self._lock:
|
||||
|
||||
return self._service_keys_to_children_to_parents[ service_key ][ tag ]
|
||||
|
|
|
@ -90,7 +90,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
job_key.SetVariable( 'error', e )
|
||||
|
||||
print( 'CallBlockingToWx just caught this error:' )
|
||||
HydrusData.Print( 'CallBlockingToWx just caught this error:' )
|
||||
HydrusData.DebugPrint( traceback.format_exc() )
|
||||
|
||||
finally: job_key.Finish()
|
||||
|
@ -349,9 +349,9 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
except Exception as e:
|
||||
|
||||
print( 'There was an error trying to start the splash screen!' )
|
||||
HydrusData.Print( 'There was an error trying to start the splash screen!' )
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
|
||||
exit_thread = threading.Thread( target = self.THREADExitEverything, name = 'Application Exit Thread' )
|
||||
|
@ -399,7 +399,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
def InitModel( self ):
|
||||
|
||||
self.pub( 'splash_set_status_text', 'booting db' )
|
||||
self.pub( 'splash_set_status_text', 'booting db...' )
|
||||
|
||||
self._http = ClientNetworking.HTTPConnectionManager()
|
||||
|
||||
|
@ -412,7 +412,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
self._services_manager = ClientData.ServicesManager()
|
||||
|
||||
self._managers[ 'hydrus_sessions' ] = HydrusSessions.HydrusSessionManagerClient()
|
||||
self._managers[ 'hydrus_sessions' ] = ClientCaches.HydrusSessionManagerClient()
|
||||
self._managers[ 'local_booru' ] = ClientCaches.LocalBooruCache()
|
||||
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager()
|
||||
self._managers[ 'tag_siblings' ] = ClientCaches.TagSiblingsManager()
|
||||
|
@ -467,7 +467,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
self.CallBlockingToWx( wx_code_password )
|
||||
|
||||
|
||||
self.pub( 'splash_set_status_text', 'booting gui' )
|
||||
self.pub( 'splash_set_status_text', 'booting gui...' )
|
||||
|
||||
def wx_code_gui():
|
||||
|
||||
|
@ -759,39 +759,32 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
self._app.SetAssertMode( wx.PYAPP_ASSERT_SUPPRESS )
|
||||
|
||||
HydrusData.Print( 'booting controller...' )
|
||||
|
||||
try:
|
||||
|
||||
self._splash = ClientGUI.FrameSplash( self )
|
||||
|
||||
except:
|
||||
|
||||
print( 'There was an error trying to start the splash screen!' )
|
||||
HydrusData.Print( 'There was an error trying to start the splash screen!' )
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
raise
|
||||
|
||||
|
||||
self.pub( 'splash_set_title_text', 'booting client' )
|
||||
|
||||
boot_thread = threading.Thread( target = self.THREADBootEverything, name = 'Application Boot Thread' )
|
||||
|
||||
boot_thread.start()
|
||||
|
||||
self._app.MainLoop()
|
||||
|
||||
|
||||
def ShutdownModel( self ):
|
||||
|
||||
self.pub( 'splash_set_status_text', 'exiting db' )
|
||||
|
||||
HydrusController.HydrusController.ShutdownModel( self )
|
||||
HydrusData.Print( 'shutting down controller...' )
|
||||
|
||||
|
||||
def ShutdownView( self ):
|
||||
|
||||
self.pub( 'splash_set_status_text', 'exiting gui' )
|
||||
|
||||
self.CallBlockingToWx( self._gui.Shutdown )
|
||||
|
||||
self.pub( 'splash_set_status_text', 'waiting for daemons to exit' )
|
||||
|
@ -951,7 +944,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
except HydrusExceptions.PermissionException as e:
|
||||
|
||||
print( e )
|
||||
HydrusData.Print( e )
|
||||
|
||||
except:
|
||||
|
||||
|
@ -974,11 +967,11 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
try:
|
||||
|
||||
self.pub( 'splash_set_title_text', 'exiting client' )
|
||||
self.pub( 'splash_set_title_text', 'shutting down gui...' )
|
||||
|
||||
self.ShutdownView()
|
||||
|
||||
self.pub( 'splash_set_title_text', 'exiting client' )
|
||||
self.pub( 'splash_set_title_text', 'shutting down db...' )
|
||||
|
||||
self.ShutdownModel()
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import json
|
|||
import HydrusConstants as HC
|
||||
import HydrusDB
|
||||
import ClientDownloading
|
||||
import ClientImageHandling
|
||||
import HydrusEncryption
|
||||
import HydrusExceptions
|
||||
import HydrusFileHandling
|
||||
|
@ -1147,7 +1148,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
with open( thumbnail_path, 'wb' ) as f: f.write( thumbnail )
|
||||
|
||||
phash = HydrusImageHandling.GeneratePerceptualHash( thumbnail_path )
|
||||
phash = ClientImageHandling.GeneratePerceptualHash( thumbnail_path )
|
||||
|
||||
hash_id = self._GetHashId( hash )
|
||||
|
||||
|
@ -1242,10 +1243,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if num_errors == 0:
|
||||
|
||||
print( 'During a db integrity check, these errors were discovered:' )
|
||||
HydrusData.Print( 'During a db integrity check, these errors were discovered:' )
|
||||
|
||||
|
||||
print( text )
|
||||
HydrusData.Print( text )
|
||||
|
||||
num_errors += 1
|
||||
|
||||
|
@ -1256,7 +1257,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
job_key.SetVariable( 'popup_title', prefix_string + 'completed' )
|
||||
job_key.SetVariable( 'popup_text_1', 'errors found: ' + HydrusData.ConvertIntToPrettyString( num_errors ) )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
job_key.Finish()
|
||||
|
||||
|
@ -1353,7 +1354,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
job_key.SetVariable( 'popup_text_1', prefix_string + final_text )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
job_key.Finish()
|
||||
|
||||
|
@ -1733,8 +1734,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
except OSError:
|
||||
|
||||
print( 'In trying to delete the orphan ' + path + ', this error was encountered:' )
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( 'In trying to delete the orphan ' + path + ', this error was encountered:' )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
|
||||
|
||||
|
@ -1785,8 +1786,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
except OSError:
|
||||
|
||||
print( 'In trying to delete the orphan ' + path + ' or ' + resized_path + ', this error was encountered:' )
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( 'In trying to delete the orphan ' + path + ' or ' + resized_path + ', this error was encountered:' )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
|
||||
|
||||
|
@ -1796,7 +1797,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
job_key.Finish()
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
wx.CallLater( 1000 * 3600, job_key.Delete )
|
||||
|
||||
|
@ -1847,8 +1848,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
except OSError:
|
||||
|
||||
print( 'In trying to delete the orphan ' + path + ', this error was encountered:' )
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( 'In trying to delete the orphan ' + path + ', this error was encountered:' )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
|
||||
|
||||
|
@ -1999,7 +2000,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
job_key.SetVariable( 'popup_text_1', prefix_string + 'done!' )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
job_key.Finish()
|
||||
|
||||
|
@ -5101,7 +5102,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
try: self._c.execute( 'INSERT INTO yaml_dumps ( dump_type, dump_name, dump ) VALUES ( ?, ?, ? );', ( dump_type, dump_name, data ) )
|
||||
except:
|
||||
|
||||
print( ( dump_type, dump_name, data ) )
|
||||
HydrusData.Print( ( dump_type, dump_name, data ) )
|
||||
|
||||
raise
|
||||
|
||||
|
@ -6655,7 +6656,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
job_key.SetVariable( 'popup_text_1', prefix + 'done!' )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
wx.CallLater( 1000 * 30, job_key.Delete )
|
||||
|
||||
|
|
|
@ -275,9 +275,9 @@ def DAEMONSynchroniseAccounts( controller ):
|
|||
|
||||
except Exception as e:
|
||||
|
||||
print( 'Failed to refresh account for ' + service.GetName() + ':' )
|
||||
HydrusData.Print( 'Failed to refresh account for ' + service.GetName() + ':' )
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -57,9 +57,9 @@ def CatchExceptionClient( etype, value, tb ):
|
|||
|
||||
if etype == wx.PyDeadObjectError:
|
||||
|
||||
print( 'Got a PyDeadObjectError, which can probably be ignored, but here it is anyway:' )
|
||||
print( HydrusData.ToUnicode( value ) )
|
||||
print( trace )
|
||||
HydrusData.Print( 'Got a PyDeadObjectError, which can probably be ignored, but here it is anyway:' )
|
||||
HydrusData.Print( HydrusData.ToUnicode( value ) )
|
||||
HydrusData.Print( trace )
|
||||
|
||||
return
|
||||
|
||||
|
@ -72,14 +72,9 @@ def CatchExceptionClient( etype, value, tb ):
|
|||
|
||||
text = job_key.ToString()
|
||||
|
||||
print( '' )
|
||||
print( 'The following uncaught exception occured at ' + HydrusData.ConvertTimestampToPrettyTime( HydrusData.GetNow() ) + ':' )
|
||||
HydrusData.Print( 'Uncaught exception:' )
|
||||
|
||||
try: print( text )
|
||||
except: print( repr( text ) )
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
HydrusData.DebugPrint( text )
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'message', job_key )
|
||||
|
||||
|
@ -259,9 +254,9 @@ def ShowExceptionClient( e ):
|
|||
|
||||
if etype == wx.PyDeadObjectError:
|
||||
|
||||
print( 'Got a PyDeadObjectError, which can probably be ignored, but here it is anyway:' )
|
||||
print( HydrusData.ToUnicode( value ) )
|
||||
print( trace )
|
||||
HydrusData.Print( 'Got a PyDeadObjectError, which can probably be ignored, but here it is anyway:' )
|
||||
HydrusData.Print( HydrusData.ToUnicode( value ) )
|
||||
HydrusData.Print( trace )
|
||||
|
||||
return
|
||||
|
||||
|
@ -276,14 +271,9 @@ def ShowExceptionClient( e ):
|
|||
|
||||
text = job_key.ToString()
|
||||
|
||||
print( '' )
|
||||
print( 'The following exception occured at ' + HydrusData.ConvertTimestampToPrettyTime( HydrusData.GetNow() ) + ':' )
|
||||
HydrusData.Print( 'Exception:' )
|
||||
|
||||
try: print( text )
|
||||
except: print( repr( text ) )
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
HydrusData.DebugPrint( text )
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'message', job_key )
|
||||
|
||||
|
@ -297,8 +287,7 @@ def ShowTextClient( text ):
|
|||
|
||||
text = job_key.ToString()
|
||||
|
||||
try: print( text )
|
||||
except: print( repr( text ) )
|
||||
HydrusData.Print( text )
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'message', job_key )
|
||||
|
||||
|
@ -358,10 +347,20 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
self._dictionary[ 'default_import_tag_options' ] = HydrusSerialisable.SerialisableDictionary()
|
||||
|
||||
self._dictionary[ 'booleans' ] = {}
|
||||
|
||||
self._dictionary[ 'booleans' ][ 'apply_all_parents_to_all_services' ] = False
|
||||
self._dictionary[ 'booleans' ][ 'apply_all_siblings_to_all_services' ] = False
|
||||
|
||||
|
||||
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
||||
|
||||
self._dictionary = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_info )
|
||||
loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_info )
|
||||
|
||||
for ( key, value ) in loaded_dictionary.items():
|
||||
|
||||
self._dictionary[ key ] = value
|
||||
|
||||
|
||||
|
||||
def ClearDefaultImportTagOptions( self ):
|
||||
|
@ -372,6 +371,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def GetBoolean( self, name ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
return self._dictionary[ 'booleans' ][ name ]
|
||||
|
||||
|
||||
|
||||
def GetDefaultImportTagOptions( self, gallery_identifier = None ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -448,6 +455,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def SetBoolean( self, name, value ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._dictionary[ 'booleans' ][ name ] = value
|
||||
|
||||
|
||||
|
||||
def SetDefaultImportTagOptions( self, gallery_identifier, import_tag_options ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -2341,7 +2356,7 @@ class Service( HydrusData.HydrusYAMLBase ):
|
|||
|
||||
job_key.SetVariable( 'popup_text_1', updates_text + ', and ' + content_text )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
time.sleep( 3 )
|
||||
|
||||
|
@ -2351,7 +2366,7 @@ class Service( HydrusData.HydrusYAMLBase ):
|
|||
|
||||
job_key.Cancel()
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
HydrusData.ShowText( 'Failed to update ' + self._name + ':' )
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ def Parse4chanPostScreen( html ):
|
|||
if title_tag.string == 'Post successful!': return ( 'success', None )
|
||||
elif title_tag.string == '4chan - Banned':
|
||||
|
||||
print( repr( soup ) )
|
||||
HydrusData.Print( soup )
|
||||
|
||||
text = 'You are banned from this board! html written to log.'
|
||||
|
||||
|
@ -270,8 +270,7 @@ def Parse4chanPostScreen( html ):
|
|||
|
||||
if problem_tag is None:
|
||||
|
||||
try: print( repr( soup ) )
|
||||
except: pass
|
||||
HydrusData.Print( soup )
|
||||
|
||||
text = 'Unknown problem; html written to log.'
|
||||
|
||||
|
@ -598,9 +597,36 @@ class GalleryBooru( Gallery ):
|
|||
|
||||
if image_string is None: image_string = soup.find( text = re.compile( 'Save this video' ) )
|
||||
|
||||
image = image_string.parent
|
||||
|
||||
image_url = image[ 'href' ]
|
||||
if image_string is None:
|
||||
|
||||
# catchall for rule34hentai.net's webms
|
||||
|
||||
if image_url is None:
|
||||
|
||||
a_tags = soup.find_all( 'a' )
|
||||
|
||||
for a_tag in a_tags:
|
||||
|
||||
href = a_tag[ 'href' ]
|
||||
|
||||
if href is not None:
|
||||
|
||||
if href.endswith( '.webm' ):
|
||||
|
||||
image_url = href
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
else:
|
||||
|
||||
image = image_string.parent
|
||||
|
||||
image_url = image[ 'href' ]
|
||||
|
||||
|
||||
else:
|
||||
|
||||
|
|
|
@ -451,7 +451,7 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
if HydrusGlobals.special_debug_mode:
|
||||
for previous_filename in previous_filenames:
|
||||
|
||||
print( previous_filename )
|
||||
HydrusData.Print( previous_filename )
|
||||
|
||||
|
||||
|
||||
|
@ -506,7 +506,7 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
for deletee_filename in deletee_filenames:
|
||||
|
||||
deletee_path = os.path.join( folder_path, deletee_filename )
|
||||
if HydrusGlobals.special_debug_mode: print( deletee_path )
|
||||
if HydrusGlobals.special_debug_mode: HydrusData.Print( deletee_path )
|
||||
ClientData.DeletePath( deletee_path )
|
||||
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import ClientGUIManagement
|
|||
import ClientGUIPages
|
||||
import ClientDownloading
|
||||
import ClientSearch
|
||||
import gc
|
||||
import HydrusData
|
||||
import HydrusExceptions
|
||||
import HydrusFileHandling
|
||||
|
@ -253,24 +254,30 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
if HC.PLATFORM_WINDOWS:
|
||||
|
||||
my_exe = os.path.join( HC.BASE_DIR, 'client.exe' )
|
||||
my_frozen_path = os.path.join( HC.BASE_DIR, 'client.exe' )
|
||||
|
||||
else:
|
||||
|
||||
my_exe = os.path.join( HC.BASE_DIR, 'client' )
|
||||
my_frozen_path = os.path.join( HC.BASE_DIR, 'client' )
|
||||
|
||||
|
||||
if sys.executable == my_exe:
|
||||
my_executable = sys.executable
|
||||
|
||||
if my_executable == my_frozen_path:
|
||||
|
||||
if HC.PLATFORM_WINDOWS: subprocess.Popen( [ os.path.join( HC.BASE_DIR, 'server.exe' ) ] )
|
||||
else: subprocess.Popen( [ os.path.join( '.', HC.BASE_DIR, 'server' ) ] )
|
||||
|
||||
else:
|
||||
|
||||
if HC.PLATFORM_WINDOWS or HC.PLATFORM_OSX: python_bin = 'pythonw'
|
||||
else: python_bin = 'python'
|
||||
server_executable = my_executable
|
||||
|
||||
subprocess.Popen( [ python_bin, os.path.join( HC.BASE_DIR, 'server.py' ) ] )
|
||||
if 'pythonw' in server_executable:
|
||||
|
||||
server_executable = server_executable.replace( 'pythonw', 'python' )
|
||||
|
||||
|
||||
subprocess.Popen( [ server_executable, os.path.join( HC.BASE_DIR, 'server.py' ) ] )
|
||||
|
||||
|
||||
time_waited = 0
|
||||
|
@ -582,6 +589,8 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
wx.CallAfter( page.Destroy )
|
||||
|
||||
|
||||
wx.CallAfter( gc.collect )
|
||||
|
||||
|
||||
def _FetchIP( self, service_key ):
|
||||
|
||||
|
@ -600,7 +609,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
text = 'File Hash: ' + hash.encode( 'hex' ) + os.linesep + 'Uploader\'s IP: ' + ip + os.linesep + 'Upload Time (GMT): ' + time.asctime( time.gmtime( int( timestamp ) ) )
|
||||
|
||||
print( text )
|
||||
HydrusData.Print( text )
|
||||
|
||||
wx.MessageBox( text + os.linesep + 'This has been written to the log.' )
|
||||
|
||||
|
@ -1510,7 +1519,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
job_key.SetVariable( 'popup_text_1', prefix + 'cancelled' )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
return
|
||||
|
||||
|
@ -1544,8 +1553,8 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
except:
|
||||
|
||||
print( path )
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( path )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
num_broken += 1
|
||||
|
||||
|
@ -1554,7 +1563,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
if num_broken > 0: job_key.SetVariable( 'popup_text_1', prefix + 'done! ' + HydrusData.ConvertIntToPrettyString( num_broken ) + ' files caused errors, which have been written to the log.' )
|
||||
else: job_key.SetVariable( 'popup_text_1', prefix + 'done!' )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
job_key.Finish()
|
||||
|
||||
|
@ -1815,7 +1824,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
job_key.SetVariable( 'popup_text_1', prefix + 'cancelled' )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
return
|
||||
|
||||
|
@ -1891,7 +1900,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
job_key.SetVariable( 'popup_text_1', prefix + 'cancelled' )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
return
|
||||
|
||||
|
@ -1931,7 +1940,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
job_key.DeleteVariable( 'popup_gauge_1' )
|
||||
job_key.SetVariable( 'popup_text_1', prefix + 'upload done!' )
|
||||
|
||||
print( job_key.ToString() )
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
job_key.Finish()
|
||||
|
||||
|
@ -2026,7 +2035,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
elif command == 'debug_garbage':
|
||||
|
||||
import gc
|
||||
import collections
|
||||
import types
|
||||
|
||||
|
@ -2047,7 +2055,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
elif type( o ) == types.BuiltinMethodType: class_count[ o.__name__ ] += 1
|
||||
|
||||
|
||||
print( 'gc:' )
|
||||
HydrusData.Print( 'gc:' )
|
||||
|
||||
for ( k, v ) in count.items():
|
||||
|
||||
|
@ -2059,7 +2067,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
if v > 100: print ( k, v )
|
||||
|
||||
|
||||
print( 'garbage: ' + HydrusData.ToUnicode( gc.garbage ) )
|
||||
HydrusData.Print( 'garbage: ' + HydrusData.ToUnicode( gc.garbage ) )
|
||||
|
||||
elif command == 'delete_all_closed_pages': self._DeleteAllClosedPages()
|
||||
elif command == 'delete_gui_session':
|
||||
|
@ -3330,7 +3338,23 @@ class FrameReviewServices( ClientGUICommon.Frame ):
|
|||
self._DisplayService()
|
||||
|
||||
|
||||
def TIMEREventUpdates( self, event ): self._updates_text.SetLabel( self._service.GetUpdateStatus() )
|
||||
def TIMEREventUpdates( self, event ):
|
||||
|
||||
try:
|
||||
|
||||
self._updates_text.SetLabel( self._service.GetUpdateStatus() )
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer_updates.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer_updates.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -3430,9 +3454,7 @@ class FrameSplash( ClientGUICommon.Frame ):
|
|||
self.Raise()
|
||||
|
||||
|
||||
def _Redraw( self ):
|
||||
|
||||
dc = wx.MemoryDC( self._bmp )
|
||||
def _Redraw( self, dc ):
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
|
||||
|
@ -3506,17 +3528,17 @@ class FrameSplash( ClientGUICommon.Frame ):
|
|||
|
||||
def EventPaint( self, event ):
|
||||
|
||||
dc = wx.BufferedPaintDC( self, self._bmp )
|
||||
|
||||
if self._dirty:
|
||||
|
||||
self._Redraw()
|
||||
self._Redraw( dc )
|
||||
|
||||
|
||||
wx.BufferedPaintDC( self, self._bmp )
|
||||
|
||||
|
||||
def SetStatusText( self, text ):
|
||||
|
||||
print( text )
|
||||
HydrusData.Print( text )
|
||||
|
||||
self._status_text = text
|
||||
|
||||
|
@ -3527,7 +3549,7 @@ class FrameSplash( ClientGUICommon.Frame ):
|
|||
|
||||
def SetTitleText( self, text ):
|
||||
|
||||
print( text )
|
||||
HydrusData.Print( text )
|
||||
|
||||
self._title_text = text
|
||||
|
||||
|
|
|
@ -152,9 +152,7 @@ class Animation( wx.Window ):
|
|||
wx.CallLater( 500, gc.collect )
|
||||
|
||||
|
||||
def _DrawFrame( self ):
|
||||
|
||||
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
def _DrawFrame( self, dc ):
|
||||
|
||||
current_frame = self._video_container.GetFrame( self._current_frame_index )
|
||||
|
||||
|
@ -175,7 +173,10 @@ class Animation( wx.Window ):
|
|||
|
||||
dc.SetUserScale( 1.0, 1.0 )
|
||||
|
||||
if self._animation_bar is not None: self._animation_bar.GotoFrame( self._current_frame_index )
|
||||
if self._animation_bar is not None:
|
||||
|
||||
self._animation_bar.GotoFrame( self._current_frame_index )
|
||||
|
||||
|
||||
self._current_frame_drawn = True
|
||||
|
||||
|
@ -200,22 +201,37 @@ class Animation( wx.Window ):
|
|||
self._a_frame_has_been_drawn = True
|
||||
|
||||
|
||||
def _DrawWhite( self ):
|
||||
|
||||
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
def _DrawWhite( self, dc ):
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
|
||||
def _TellAnimationBarAboutPausedStatus( self ):
|
||||
|
||||
if self._animation_bar is not None:
|
||||
|
||||
self._animation_bar.SetPaused( self._paused )
|
||||
|
||||
|
||||
|
||||
def CurrentFrame( self ): return self._current_frame_index
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPaint( self, event ):
|
||||
|
||||
wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
if not self._current_frame_drawn and self._video_container.HasFrame( self._current_frame_index ):
|
||||
|
||||
self._DrawFrame( dc )
|
||||
|
||||
elif not self._a_frame_has_been_drawn:
|
||||
|
||||
self._DrawWhite( dc )
|
||||
|
||||
|
||||
|
||||
def EventPropagateKey( self, event ):
|
||||
|
@ -282,23 +298,13 @@ class Animation( wx.Window ):
|
|||
self._video_container.SetFramePosition( self._current_frame_index )
|
||||
|
||||
self._current_frame_drawn = False
|
||||
self._a_frame_has_been_drawn = False
|
||||
|
||||
wx.CallAfter( self._canvas_bmp.Destroy )
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
|
||||
|
||||
dc = wx.MemoryDC( self._canvas_bmp )
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
del dc
|
||||
|
||||
self._a_frame_has_been_drawn = False
|
||||
|
||||
if self._video_container.HasFrame( self._current_frame_index ): self._DrawFrame()
|
||||
else: self._DrawWhite()
|
||||
self.Refresh()
|
||||
|
||||
|
||||
|
||||
|
@ -314,12 +320,13 @@ class Animation( wx.Window ):
|
|||
self._current_frame_drawn_at = 0.0
|
||||
self._current_frame_drawn = False
|
||||
|
||||
if self._video_container.HasFrame( self._current_frame_index ): self._DrawFrame()
|
||||
elif not self._a_frame_has_been_drawn: self._DrawWhite()
|
||||
self.Refresh()
|
||||
|
||||
|
||||
self._paused = True
|
||||
|
||||
self._TellAnimationBarAboutPausedStatus()
|
||||
|
||||
|
||||
def IsPlaying( self ):
|
||||
|
||||
|
@ -330,43 +337,70 @@ class Animation( wx.Window ):
|
|||
|
||||
self._paused = False
|
||||
|
||||
self._TellAnimationBarAboutPausedStatus()
|
||||
|
||||
|
||||
def Pause( self ):
|
||||
|
||||
self._paused = True
|
||||
|
||||
self._TellAnimationBarAboutPausedStatus()
|
||||
|
||||
|
||||
def PausePlay( self ):
|
||||
|
||||
self._paused = not self._paused
|
||||
|
||||
self._TellAnimationBarAboutPausedStatus()
|
||||
|
||||
|
||||
def SetAnimationBar( self, animation_bar ):
|
||||
|
||||
self._animation_bar = animation_bar
|
||||
|
||||
if self._animation_bar is not None: self._animation_bar.GotoFrame( self._current_frame_index )
|
||||
if self._animation_bar is not None:
|
||||
|
||||
self._animation_bar.GotoFrame( self._current_frame_index )
|
||||
|
||||
self._TellAnimationBarAboutPausedStatus()
|
||||
|
||||
|
||||
|
||||
def TIMEREventVideo( self, event ):
|
||||
|
||||
if self.IsShownOnScreen():
|
||||
try:
|
||||
|
||||
if self._current_frame_drawn:
|
||||
if self.IsShownOnScreen():
|
||||
|
||||
if not self._paused and HydrusData.TimeHasPassedPrecise( self._next_frame_due_at ):
|
||||
if self._current_frame_drawn:
|
||||
|
||||
num_frames = self._media.GetNumFrames()
|
||||
if not self._paused and HydrusData.TimeHasPassedPrecise( self._next_frame_due_at ):
|
||||
|
||||
num_frames = self._media.GetNumFrames()
|
||||
|
||||
self._current_frame_index = ( self._current_frame_index + 1 ) % num_frames
|
||||
|
||||
self._current_frame_drawn = False
|
||||
|
||||
self._video_container.SetFramePosition( self._current_frame_index )
|
||||
|
||||
|
||||
self._current_frame_index = ( self._current_frame_index + 1 ) % num_frames
|
||||
|
||||
if not self._current_frame_drawn and self._video_container.HasFrame( self._current_frame_index ):
|
||||
|
||||
self._current_frame_drawn = False
|
||||
|
||||
self._video_container.SetFramePosition( self._current_frame_index )
|
||||
self.Refresh()
|
||||
|
||||
|
||||
|
||||
if not self._current_frame_drawn and self._video_container.HasFrame( self._current_frame_index ): self._DrawFrame()
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer_video.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer_video.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
|
@ -384,6 +418,7 @@ class AnimationBar( wx.Window ):
|
|||
|
||||
self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) )
|
||||
|
||||
self._paused = True
|
||||
self._media = media
|
||||
self._media_window = media_window
|
||||
self._num_frames = self._media.GetNumFrames()
|
||||
|
@ -402,17 +437,28 @@ class AnimationBar( wx.Window ):
|
|||
self._timer_update.Start( 100, wx.TIMER_CONTINUOUS )
|
||||
|
||||
|
||||
def _Redraw( self ):
|
||||
def _Redraw( self, dc ):
|
||||
|
||||
( my_width, my_height ) = self._canvas_bmp.GetSize()
|
||||
|
||||
dc = wx.MemoryDC( self._canvas_bmp )
|
||||
|
||||
dc.SetPen( wx.TRANSPARENT_PEN )
|
||||
|
||||
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
|
||||
background_colour = wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE )
|
||||
|
||||
dc.DrawRectangle( 0, 0, my_width, ANIMATED_SCANBAR_HEIGHT )
|
||||
if self._paused:
|
||||
|
||||
( r, g, b ) = background_colour.Get()
|
||||
|
||||
r = int( r * 0.85 )
|
||||
g = int( g * 0.85 )
|
||||
b = int( b * 0.85 )
|
||||
|
||||
background_colour = wx.Colour( r, g, b )
|
||||
|
||||
|
||||
dc.SetBackground( wx.Brush( background_colour ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
#
|
||||
|
||||
|
@ -482,13 +528,13 @@ class AnimationBar( wx.Window ):
|
|||
|
||||
def EventPaint( self, event ):
|
||||
|
||||
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
if self._dirty:
|
||||
|
||||
self._Redraw()
|
||||
self._Redraw( dc )
|
||||
|
||||
|
||||
wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
|
||||
def EventResize( self, event ):
|
||||
|
||||
|
@ -520,39 +566,61 @@ class AnimationBar( wx.Window ):
|
|||
self.Refresh()
|
||||
|
||||
|
||||
def SetPaused( self, paused ):
|
||||
|
||||
self._paused = paused
|
||||
|
||||
self._dirty = True
|
||||
|
||||
self.Refresh()
|
||||
|
||||
|
||||
def TIMEREventUpdate( self, event ):
|
||||
|
||||
if self.IsShownOnScreen():
|
||||
try:
|
||||
|
||||
if self._media.GetMime() == HC.APPLICATION_FLASH:
|
||||
if self.IsShownOnScreen():
|
||||
|
||||
try:
|
||||
if self._media.GetMime() == HC.APPLICATION_FLASH:
|
||||
|
||||
frame_index = self._media_window.CurrentFrame()
|
||||
try:
|
||||
|
||||
frame_index = self._media_window.CurrentFrame()
|
||||
|
||||
except AttributeError:
|
||||
|
||||
text = 'The flash window produced an unusual error that probably means it never initialised properly. This is usually because Flash has not been installed for Internet Explorer. '
|
||||
text += os.linesep * 2
|
||||
text += 'Please close the client, open Internet Explorer, and install flash from Adobe\'s site and then try again. If that does not work, please tell the hydrus developer.'
|
||||
|
||||
HydrusData.ShowText( text )
|
||||
|
||||
self._timer_update.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
except AttributeError:
|
||||
|
||||
text = 'The flash window produced an unusual error that probably means it never initialised properly. This is usually because Flash has not been installed for Internet Explorer. '
|
||||
text += os.linesep * 2
|
||||
text += 'Please close the client, open Internet Explorer, and install flash from Adobe\'s site and then try again. If that does not work, please tell the hydrus developer.'
|
||||
|
||||
HydrusData.ShowText( text )
|
||||
|
||||
self._timer_update.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
if frame_index != self._current_frame_index:
|
||||
|
||||
self._current_frame_index = frame_index
|
||||
|
||||
self._dirty = True
|
||||
|
||||
self.Refresh()
|
||||
if frame_index != self._current_frame_index:
|
||||
|
||||
self._current_frame_index = frame_index
|
||||
|
||||
self._dirty = True
|
||||
|
||||
self.Refresh()
|
||||
|
||||
|
||||
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer_update.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer_update.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
class Canvas( object ):
|
||||
|
@ -705,9 +773,7 @@ class Canvas( object ):
|
|||
|
||||
|
||||
|
||||
def _DrawBackgroundBitmap( self ):
|
||||
|
||||
dc = wx.MemoryDC( self._canvas_bmp )
|
||||
def _DrawBackgroundBitmap( self, dc ):
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
|
@ -992,9 +1058,11 @@ class Canvas( object ):
|
|||
|
||||
def EventPaint( self, event ):
|
||||
|
||||
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
if self._dirty:
|
||||
|
||||
self._DrawBackgroundBitmap()
|
||||
self._DrawBackgroundBitmap( dc )
|
||||
|
||||
if self._media_container is not None:
|
||||
|
||||
|
@ -1002,8 +1070,6 @@ class Canvas( object ):
|
|||
|
||||
|
||||
|
||||
wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
|
||||
def EventResize( self, event ):
|
||||
|
||||
|
@ -1043,7 +1109,14 @@ class Canvas( object ):
|
|||
|
||||
def MouseIsNearAnimationBar( self ):
|
||||
|
||||
return self._media_container.MouseIsNearAnimationBar()
|
||||
if self._media_container is None:
|
||||
|
||||
return False
|
||||
|
||||
else:
|
||||
|
||||
return self._media_container.MouseIsNearAnimationBar()
|
||||
|
||||
|
||||
|
||||
def MouseIsOverMedia( self ):
|
||||
|
@ -1129,6 +1202,7 @@ class Canvas( object ):
|
|||
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'canvas_new_display_media', self._canvas_key, self._current_display_media )
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'canvas_new_index_string', self._canvas_key, self._GetIndexString() )
|
||||
|
||||
self._SetDirty()
|
||||
|
@ -1608,7 +1682,14 @@ class CanvasFullscreenMediaList( ClientMedia.ListeningMediaList, CanvasWithDetai
|
|||
|
||||
def _GetIndexString( self ):
|
||||
|
||||
index_string = HydrusData.ConvertValueRangeToPrettyString( self._sorted_media.index( self._current_media ) + 1, len( self._sorted_media ) )
|
||||
if self._current_media is None:
|
||||
|
||||
index_string = '-/' + HydrusData.ConvertIntToPrettyString( len( self._sorted_media ) )
|
||||
|
||||
else:
|
||||
|
||||
index_string = HydrusData.ConvertValueRangeToPrettyString( self._sorted_media.index( self._current_media ) + 1, len( self._sorted_media ) )
|
||||
|
||||
|
||||
return index_string
|
||||
|
||||
|
@ -1858,10 +1939,32 @@ class CanvasFullscreenMediaList( ClientMedia.ListeningMediaList, CanvasWithDetai
|
|||
|
||||
def TIMEREventCursorHide( self, event ):
|
||||
|
||||
if not CC.CAN_HIDE_MOUSE: return
|
||||
|
||||
if self._menu_open: self._timer_cursor_hide.Start( 800, wx.TIMER_ONE_SHOT )
|
||||
else: self.SetCursor( wx.StockCursor( wx.CURSOR_BLANK ) )
|
||||
try:
|
||||
|
||||
if not CC.CAN_HIDE_MOUSE:
|
||||
|
||||
return
|
||||
|
||||
|
||||
if self._menu_open:
|
||||
|
||||
self._timer_cursor_hide.Start( 800, wx.TIMER_ONE_SHOT )
|
||||
|
||||
else:
|
||||
|
||||
self.SetCursor( wx.StockCursor( wx.CURSOR_BLANK ) )
|
||||
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer_cursor_hide.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer_cursor_hide.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
class CanvasFullscreenMediaListFilter( CanvasFullscreenMediaList ):
|
||||
|
@ -2571,7 +2674,23 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaListNavigable ):
|
|||
event.Skip()
|
||||
|
||||
|
||||
def TIMEREventSlideshow( self, event ): self._ShowNext()
|
||||
def TIMEREventSlideshow( self, event ):
|
||||
|
||||
try:
|
||||
|
||||
self._ShowNext()
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer_slideshow.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer_slideshow.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaListNavigable ):
|
||||
|
||||
|
@ -3026,6 +3145,7 @@ class MediaContainer( wx.Window ):
|
|||
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
self.EventResize( None )
|
||||
|
||||
|
@ -3096,6 +3216,8 @@ class MediaContainer( wx.Window ):
|
|||
self._MakeMediaWindow( do_embed_button = False )
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPropagateMouse( self, event ):
|
||||
|
||||
mime = self._media.GetMime()
|
||||
|
@ -3184,6 +3306,8 @@ class MediaContainer( wx.Window ):
|
|||
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def Pause( self ):
|
||||
|
||||
|
@ -3201,22 +3325,22 @@ class EmbedButton( wx.Window ):
|
|||
|
||||
self._dirty = True
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( 20, 20, 24 )
|
||||
( x, y ) = size
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( x, y, 24 )
|
||||
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
|
||||
def _Redraw( self ):
|
||||
def _Redraw( self, dc ):
|
||||
|
||||
( x, y ) = self.GetClientSize()
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( x, y, 24 )
|
||||
background_brush = wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) )
|
||||
|
||||
dc = wx.MemoryDC( self._canvas_bmp )
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
dc.SetBackground( background_brush )
|
||||
|
||||
dc.Clear() # gcdc doesn't support clear
|
||||
|
||||
|
@ -3232,7 +3356,7 @@ class EmbedButton( wx.Window ):
|
|||
|
||||
dc.DrawCircle( center_x, center_y, radius )
|
||||
|
||||
dc.SetBrush( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
dc.SetBrush( background_brush )
|
||||
|
||||
m = ( 2 ** 0.5 ) / 2 # 45 degree angle
|
||||
|
||||
|
@ -3263,13 +3387,13 @@ class EmbedButton( wx.Window ):
|
|||
|
||||
def EventPaint( self, event ):
|
||||
|
||||
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
if self._dirty:
|
||||
|
||||
self._Redraw()
|
||||
self._Redraw( dc )
|
||||
|
||||
|
||||
wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
|
||||
def EventResize( self, event ):
|
||||
|
||||
|
@ -3281,6 +3405,8 @@ class EmbedButton( wx.Window ):
|
|||
|
||||
if my_width > 0 and my_height > 0:
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
|
||||
|
||||
self._dirty = True
|
||||
|
||||
self.Refresh()
|
||||
|
@ -3335,12 +3461,8 @@ class StaticImage( wx.Window ):
|
|||
|
||||
self.EventResize( None )
|
||||
|
||||
self._timer_render_wait.Start( 16, wx.TIMER_CONTINUOUS )
|
||||
|
||||
|
||||
def _Redraw( self ):
|
||||
|
||||
dc = wx.MemoryDC( self._canvas_bmp )
|
||||
def _Redraw( self, dc ):
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
|
@ -3384,17 +3506,20 @@ class StaticImage( wx.Window ):
|
|||
self.Refresh()
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
def EventEraseBackground( self, event ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def EventPaint( self, event ):
|
||||
|
||||
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
if self._dirty:
|
||||
|
||||
self._Redraw()
|
||||
self._Redraw( dc )
|
||||
|
||||
|
||||
wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
|
||||
def EventPropagateMouse( self, event ):
|
||||
|
||||
|
@ -3450,11 +3575,24 @@ class StaticImage( wx.Window ):
|
|||
|
||||
def TIMEREventRenderWait( self, event ):
|
||||
|
||||
if self._image_container is not None and self._image_container.IsRendered():
|
||||
try:
|
||||
|
||||
self._SetDirty()
|
||||
if self._image_container is not None and self._image_container.IsRendered():
|
||||
|
||||
self._SetDirty()
|
||||
|
||||
self._timer_render_wait.Stop()
|
||||
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer_render_wait.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer_render_wait.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
|
@ -63,27 +63,40 @@ class AnimatedStaticTextTimestamp( wx.StaticText ):
|
|||
|
||||
self.Bind( wx.EVT_TIMER, self.TIMEREventAnimated, id = ID_TIMER_ANIMATED )
|
||||
|
||||
animated_event_timer = wx.Timer( self, ID_TIMER_ANIMATED )
|
||||
animated_event_timer.Start( 1000, wx.TIMER_CONTINUOUS )
|
||||
self._timer_animated = wx.Timer( self, ID_TIMER_ANIMATED )
|
||||
self._timer_animated.Start( 1000, wx.TIMER_CONTINUOUS )
|
||||
|
||||
|
||||
def TIMEREventAnimated( self ):
|
||||
|
||||
update = False
|
||||
|
||||
now = HydrusData.GetNow()
|
||||
|
||||
difference = abs( now - self._timestamp )
|
||||
|
||||
if difference < 3600: update = True
|
||||
elif difference < 3600 * 24 and now - self._last_tick > 60: update = True
|
||||
elif now - self._last_tick > 3600: update = True
|
||||
|
||||
if update:
|
||||
try:
|
||||
|
||||
self.SetLabel( self._prefix + self._rendering_function( self._timestamp ) + self._suffix )
|
||||
update = False
|
||||
|
||||
wx.PostEvent( self.GetEventHandler(), wx.SizeEvent() )
|
||||
now = HydrusData.GetNow()
|
||||
|
||||
difference = abs( now - self._timestamp )
|
||||
|
||||
if difference < 3600: update = True
|
||||
elif difference < 3600 * 24 and now - self._last_tick > 60: update = True
|
||||
elif now - self._last_tick > 3600: update = True
|
||||
|
||||
if update:
|
||||
|
||||
self.SetLabel( self._prefix + self._rendering_function( self._timestamp ) + self._suffix )
|
||||
|
||||
wx.PostEvent( self.GetEventHandler(), wx.SizeEvent() )
|
||||
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer_animated.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer_animated.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
|
@ -472,10 +485,35 @@ class AutoCompleteDropdown( wx.Panel ):
|
|||
|
||||
self._move_hide_timer.Start( 250, wx.TIMER_ONE_SHOT )
|
||||
|
||||
except wx.PyDeadObjectError: pass
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._move_hide_timer.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._move_hide_timer.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
def TIMEREventLag( self, event ): self._UpdateList()
|
||||
def TIMEREventLag( self, event ):
|
||||
|
||||
try:
|
||||
|
||||
self._UpdateList()
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._lag_timer.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._lag_timer.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
class AutoCompleteDropdownTags( AutoCompleteDropdown ):
|
||||
|
||||
|
@ -1107,69 +1145,65 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
|
|||
|
||||
matches = ClientSearch.FilterPredicates( half_complete_tag, predicates, service_key = self._tag_service_key, expand_parents = self._expand_parents )
|
||||
|
||||
# do the 'put whatever they typed in at the top, whether it has count or not'
|
||||
# now with sibling support!
|
||||
# and parent support!
|
||||
# this is getting pretty ugly, and I should really move it into the matches processing, I think!
|
||||
|
||||
top_predicates = []
|
||||
|
||||
top_predicates.append( entry_predicate )
|
||||
self._PutAtTopOfMatches( matches, entry_predicate )
|
||||
|
||||
if sibling_predicate is not None:
|
||||
|
||||
top_predicates.append( sibling_predicate )
|
||||
|
||||
|
||||
for predicate in top_predicates:
|
||||
|
||||
if self._expand_parents:
|
||||
|
||||
parents = []
|
||||
|
||||
try:
|
||||
|
||||
index = matches.index( predicate )
|
||||
|
||||
predicate = matches[ index ]
|
||||
|
||||
matches.remove( predicate )
|
||||
|
||||
while matches[ index ].GetType() == HC.PREDICATE_TYPE_PARENT:
|
||||
|
||||
parent = matches[ index ]
|
||||
|
||||
matches.remove( parent )
|
||||
|
||||
parents.append( parent )
|
||||
|
||||
|
||||
except:
|
||||
|
||||
if predicate.GetType() == HC.PREDICATE_TYPE_TAG:
|
||||
|
||||
tag = predicate.GetValue()
|
||||
|
||||
parents_manager = HydrusGlobals.client_controller.GetManager( 'tag_parents' )
|
||||
|
||||
raw_parents = parents_manager.GetParents( self._tag_service_key, tag )
|
||||
|
||||
parents = [ ClientData.Predicate( HC.PREDICATE_TYPE_PARENT, raw_parent ) for raw_parent in raw_parents ]
|
||||
|
||||
|
||||
|
||||
parents.reverse()
|
||||
|
||||
for parent in parents: matches.insert( 0, parent )
|
||||
|
||||
|
||||
matches.insert( 0, predicate )
|
||||
self._PutAtTopOfMatches( matches, sibling_predicate )
|
||||
|
||||
|
||||
|
||||
return matches
|
||||
|
||||
|
||||
def _PutAtTopOfMatches( self, matches, predicate ):
|
||||
|
||||
if self._expand_parents:
|
||||
|
||||
parents = []
|
||||
|
||||
try:
|
||||
|
||||
index = matches.index( predicate )
|
||||
|
||||
predicate = matches[ index ]
|
||||
|
||||
matches.remove( predicate )
|
||||
|
||||
while matches[ index ].GetType() == HC.PREDICATE_TYPE_PARENT:
|
||||
|
||||
parent = matches[ index ]
|
||||
|
||||
matches.remove( parent )
|
||||
|
||||
parents.append( parent )
|
||||
|
||||
|
||||
except:
|
||||
|
||||
if predicate.GetType() == HC.PREDICATE_TYPE_TAG:
|
||||
|
||||
tag = predicate.GetValue()
|
||||
|
||||
parents_manager = HydrusGlobals.client_controller.GetManager( 'tag_parents' )
|
||||
|
||||
raw_parents = parents_manager.GetParents( self._tag_service_key, tag )
|
||||
|
||||
parents = [ ClientData.Predicate( HC.PREDICATE_TYPE_PARENT, raw_parent ) for raw_parent in raw_parents ]
|
||||
|
||||
|
||||
|
||||
parents.reverse()
|
||||
|
||||
for parent in parents:
|
||||
|
||||
matches.insert( 0, parent )
|
||||
|
||||
|
||||
|
||||
matches.insert( 0, predicate )
|
||||
|
||||
|
||||
def _ShouldTakeResponsibilityForEnter( self ):
|
||||
|
||||
# when the user has quickly typed something in and the results are not yet in
|
||||
|
@ -1210,18 +1244,34 @@ class BufferedWindow( wx.Window ):
|
|||
|
||||
self._canvas_bmp = wx.EmptyBitmap( x, y, 24 )
|
||||
|
||||
else: self._canvas_bmp = wx.EmptyBitmap( 20, 20, 24 )
|
||||
else:
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( 20, 20, 24 )
|
||||
|
||||
|
||||
self._dirty = True
|
||||
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
|
||||
def GetDC( self ): return wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
def _Draw( self, dc ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
def EventPaint( self, event ):
|
||||
|
||||
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
if self._dirty:
|
||||
|
||||
self._Draw( dc )
|
||||
|
||||
|
||||
|
||||
def EventResize( self, event ):
|
||||
|
||||
|
@ -1229,7 +1279,14 @@ class BufferedWindow( wx.Window ):
|
|||
|
||||
( current_bmp_width, current_bmp_height ) = self._canvas_bmp.GetSize()
|
||||
|
||||
if my_width != current_bmp_width or my_height != current_bmp_height: self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
|
||||
if my_width != current_bmp_width or my_height != current_bmp_height:
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
|
||||
|
||||
self._dirty = True
|
||||
|
||||
self.Refresh()
|
||||
|
||||
|
||||
|
||||
class BufferedWindowIcon( BufferedWindow ):
|
||||
|
@ -1240,7 +1297,8 @@ class BufferedWindowIcon( BufferedWindow ):
|
|||
|
||||
self._bmp = bmp
|
||||
|
||||
dc = self.GetDC()
|
||||
|
||||
def _Draw( self, dc ):
|
||||
|
||||
background_colour = self.GetParent().GetBackgroundColour()
|
||||
|
||||
|
@ -1248,7 +1306,9 @@ class BufferedWindowIcon( BufferedWindow ):
|
|||
|
||||
dc.Clear()
|
||||
|
||||
dc.DrawBitmap( bmp, 0, 0 )
|
||||
dc.DrawBitmap( self._bmp, 0, 0 )
|
||||
|
||||
self._dirty = False
|
||||
|
||||
|
||||
class BetterChoice( wx.Choice ):
|
||||
|
@ -2042,6 +2102,8 @@ class ListBook( wx.Panel ):
|
|||
|
||||
class ListBox( wx.ScrolledWindow ):
|
||||
|
||||
delete_key_activates = False
|
||||
|
||||
def __init__( self, parent, min_height = 250 ):
|
||||
|
||||
wx.ScrolledWindow.__init__( self, parent, style = wx.VSCROLL | wx.BORDER_DOUBLE )
|
||||
|
@ -2351,7 +2413,7 @@ class ListBox( wx.ScrolledWindow ):
|
|||
|
||||
key_code = event.GetKeyCode()
|
||||
|
||||
if key_code in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
if key_code in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ) or ( self.delete_key_activates and key_code in ( wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE ) ):
|
||||
|
||||
self._Activate()
|
||||
|
||||
|
@ -3241,6 +3303,7 @@ class ListBoxTagsStrings( ListBoxTags ):
|
|||
|
||||
class ListBoxTagsPredicates( ListBoxTags ):
|
||||
|
||||
delete_key_activates = True
|
||||
has_counts = False
|
||||
|
||||
def __init__( self, parent, page_key, initial_predicates = None ):
|
||||
|
@ -3606,6 +3669,8 @@ class ListBoxTagsSelectionManagementPanel( ListBoxTagsSelection ):
|
|||
|
||||
class ListBoxTagsSelectionTagsDialog( ListBoxTagsSelection ):
|
||||
|
||||
delete_key_activates = True
|
||||
|
||||
def __init__( self, parent, callable ):
|
||||
|
||||
ListBoxTagsSelection.__init__( self, parent, include_counts = True, collapse_siblings = False )
|
||||
|
@ -4306,9 +4371,9 @@ class PopupMessageManager( wx.Frame ):
|
|||
|
||||
text = 'The popup message manager experienced a fatal error and will now stop working! Please restart the client as soon as possible! If this keeps happening, please email the details and your client.log to the hydrus developer.'
|
||||
|
||||
print( text )
|
||||
HydrusData.Print( text )
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
wx.MessageBox( text )
|
||||
|
||||
|
@ -4328,7 +4393,7 @@ class PopupMessageManager( wx.Frame ):
|
|||
|
||||
self._CheckPending()
|
||||
|
||||
except: print( traceback.format_exc() )
|
||||
except: HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
|
||||
def CleanBeforeDestroy( self ):
|
||||
|
@ -4376,23 +4441,36 @@ class PopupMessageManager( wx.Frame ):
|
|||
|
||||
def TIMEREvent( self, event ):
|
||||
|
||||
if HydrusGlobals.view_shutdown:
|
||||
try:
|
||||
|
||||
self.Destroy()
|
||||
if HydrusGlobals.view_shutdown:
|
||||
|
||||
self.Destroy()
|
||||
|
||||
return
|
||||
|
||||
|
||||
return
|
||||
sizer_items = self._message_vbox.GetChildren()
|
||||
|
||||
|
||||
sizer_items = self._message_vbox.GetChildren()
|
||||
|
||||
for sizer_item in sizer_items:
|
||||
for sizer_item in sizer_items:
|
||||
|
||||
message_window = sizer_item.GetWindow()
|
||||
|
||||
message_window.Update()
|
||||
|
||||
|
||||
message_window = sizer_item.GetWindow()
|
||||
self._SizeAndPositionAndShow()
|
||||
|
||||
message_window.Update()
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
self._SizeAndPositionAndShow()
|
||||
|
||||
|
||||
class RatingLike( wx.Window ):
|
||||
|
@ -4418,13 +4496,11 @@ class RatingLike( wx.Window ):
|
|||
self._dirty = True
|
||||
|
||||
|
||||
def _Draw( self ):
|
||||
def _Draw( self, dc ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def GetDC( self ): return wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventLeftDown( self, event ):
|
||||
|
@ -4434,13 +4510,13 @@ class RatingLike( wx.Window ):
|
|||
|
||||
def EventPaint( self, event ):
|
||||
|
||||
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
if self._dirty:
|
||||
|
||||
self._Draw()
|
||||
self._Draw( dc )
|
||||
|
||||
|
||||
wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
|
||||
def EventRightDown( self, event ):
|
||||
|
||||
|
@ -4461,9 +4537,7 @@ class RatingLikeDialog( RatingLike ):
|
|||
self._rating_state = ClientRatings.NULL
|
||||
|
||||
|
||||
def _Draw( self ):
|
||||
|
||||
dc = self.GetDC()
|
||||
def _Draw( self, dc ):
|
||||
|
||||
dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
|
||||
|
||||
|
@ -4530,9 +4604,7 @@ class RatingLikeCanvas( RatingLike ):
|
|||
HydrusGlobals.client_controller.sub( self, 'SetDisplayMedia', 'canvas_new_display_media' )
|
||||
|
||||
|
||||
def _Draw( self ):
|
||||
|
||||
dc = self.GetDC()
|
||||
def _Draw( self, dc ):
|
||||
|
||||
dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
|
||||
|
||||
|
@ -4608,7 +4680,14 @@ class RatingLikeCanvas( RatingLike ):
|
|||
|
||||
self._current_media = media
|
||||
|
||||
self._hashes = self._current_media.GetHashes()
|
||||
if self._current_media is None:
|
||||
|
||||
self._hashes = set()
|
||||
|
||||
else:
|
||||
|
||||
self._hashes = self._current_media.GetHashes()
|
||||
|
||||
|
||||
self._dirty = True
|
||||
|
||||
|
@ -4646,7 +4725,7 @@ class RatingNumerical( wx.Window ):
|
|||
self._dirty = True
|
||||
|
||||
|
||||
def _Draw( self ):
|
||||
def _Draw( self, dc ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -4688,8 +4767,6 @@ class RatingNumerical( wx.Window ):
|
|||
return None
|
||||
|
||||
|
||||
def GetDC( self ): return wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventLeftDown( self, event ):
|
||||
|
@ -4699,13 +4776,13 @@ class RatingNumerical( wx.Window ):
|
|||
|
||||
def EventPaint( self, event ):
|
||||
|
||||
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
if self._dirty:
|
||||
|
||||
self._Draw()
|
||||
self._Draw( dc )
|
||||
|
||||
|
||||
wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
|
||||
def EventRightDown( self, event ):
|
||||
|
||||
|
@ -4727,9 +4804,7 @@ class RatingNumericalDialog( RatingNumerical ):
|
|||
self._rating = None
|
||||
|
||||
|
||||
def _Draw( self ):
|
||||
|
||||
dc = self.GetDC()
|
||||
def _Draw( self, dc ):
|
||||
|
||||
dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
|
||||
|
||||
|
@ -4814,9 +4889,7 @@ class RatingNumericalCanvas( RatingNumerical ):
|
|||
HydrusGlobals.client_controller.sub( self, 'SetDisplayMedia', 'canvas_new_display_media' )
|
||||
|
||||
|
||||
def _Draw( self ):
|
||||
|
||||
dc = self.GetDC()
|
||||
def _Draw( self, dc ):
|
||||
|
||||
dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
|
||||
|
||||
|
@ -4893,7 +4966,14 @@ class RatingNumericalCanvas( RatingNumerical ):
|
|||
|
||||
self._current_media = media
|
||||
|
||||
self._hashes = self._current_media.GetHashes()
|
||||
if self._current_media is None:
|
||||
|
||||
self._hashes = set()
|
||||
|
||||
else:
|
||||
|
||||
self._hashes = self._current_media.GetHashes()
|
||||
|
||||
|
||||
self._dirty = True
|
||||
|
||||
|
@ -5662,7 +5742,7 @@ class ShowKeys( Frame ):
|
|||
|
||||
path = HydrusData.ToUnicode( dlg.GetPath() )
|
||||
|
||||
with open( path, 'wb' ) as f: f.write( self._text )
|
||||
with open( path, 'wb' ) as f: f.write( HydrusData.ToByteString( self._text ) )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@ class Dialog( wx.Dialog ):
|
|||
wx.Dialog.SetInitialSize( self, ( width, height ) )
|
||||
|
||||
min_width = min( 240, width )
|
||||
min_height = min( 80, height )
|
||||
min_height = min( 240, height )
|
||||
|
||||
self.SetMinSize( ( min_width, min_height ) )
|
||||
|
||||
|
@ -4486,7 +4486,7 @@ class DialogSetupExport( Dialog ):
|
|||
|
||||
with open( txt_path, 'wb' ) as f:
|
||||
|
||||
f.write( os.linesep.join( tags ) )
|
||||
f.write( HydrusData.ToByteString( os.linesep.join( tags ) ) )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2716,7 +2716,9 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
|
|||
|
||||
( name, path, mimes, import_file_options, import_tag_options, actions, action_locations, period, open_popup, paused ) = self._import_folder.ToTuple()
|
||||
|
||||
self._folder_box = ClientGUICommon.StaticBox( self, 'folder options' )
|
||||
self._panel = wx.ScrolledWindow( self )
|
||||
|
||||
self._folder_box = ClientGUICommon.StaticBox( self._panel, 'folder options' )
|
||||
|
||||
self._name = wx.TextCtrl( self._folder_box )
|
||||
|
||||
|
@ -2734,7 +2736,7 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
|
|||
|
||||
#
|
||||
|
||||
self._file_box = ClientGUICommon.StaticBox( self, 'file options' )
|
||||
self._file_box = ClientGUICommon.StaticBox( self._panel, 'file options' )
|
||||
|
||||
self._mimes = ClientGUIOptionsPanels.OptionsPanelMimes( self._file_box, HC.ALLOWED_MIMES )
|
||||
|
||||
|
@ -2882,13 +2884,24 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
|
|||
|
||||
vbox.AddF( self._folder_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
vbox.AddF( self._file_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
self._panel.SetSizer( vbox )
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
vbox.AddF( self._panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
( x, y ) = self.GetEffectiveMinSize()
|
||||
|
||||
x = max( 720, x )
|
||||
( max_x, max_y ) = wx.GetDisplaySize()
|
||||
|
||||
x = min( x + 25, max_x )
|
||||
y = min( y + 25, max_y )
|
||||
|
||||
self._panel.SetScrollRate( 20, 20 )
|
||||
|
||||
self.SetInitialSize( ( x, y ) )
|
||||
|
||||
|
@ -3057,6 +3070,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
self._listbook.AddPage( 'sort/collect', self._SortCollectPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'shortcuts', self._ShortcutsPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'downloading', self._DownloadingPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'tags', self._TagsPanel( self._listbook, self._new_options ) )
|
||||
|
||||
self._ok = wx.Button( self, id = wx.ID_OK, label = 'Save' )
|
||||
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
|
||||
|
@ -3879,17 +3893,6 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._gui_capitalisation = wx.CheckBox( self )
|
||||
|
||||
self._gui_show_all_tags_in_autocomplete = wx.CheckBox( self )
|
||||
|
||||
self._default_tag_sort = wx.Choice( self )
|
||||
|
||||
self._default_tag_sort.Append( 'lexicographic (a-z)', CC.SORT_BY_LEXICOGRAPHIC_ASC )
|
||||
self._default_tag_sort.Append( 'lexicographic (z-a)', CC.SORT_BY_LEXICOGRAPHIC_DESC )
|
||||
self._default_tag_sort.Append( 'incidence (desc)', CC.SORT_BY_INCIDENCE_DESC )
|
||||
self._default_tag_sort.Append( 'incidence (asc)', CC.SORT_BY_INCIDENCE_ASC )
|
||||
|
||||
self._default_tag_repository = ClientGUICommon.BetterChoice( self )
|
||||
|
||||
self._tag_dialog_size = wx.CheckBox( self )
|
||||
self._tag_dialog_position = wx.CheckBox( self )
|
||||
self._rating_dialog_position = wx.CheckBox( self )
|
||||
|
@ -3918,21 +3921,6 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._gui_capitalisation.SetValue( HC.options[ 'gui_capitalisation' ] )
|
||||
|
||||
self._gui_show_all_tags_in_autocomplete.SetValue( HC.options[ 'show_all_tags_in_autocomplete' ] )
|
||||
|
||||
if HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_ASC: self._default_tag_sort.Select( 0 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_DESC: self._default_tag_sort.Select( 1 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_DESC: self._default_tag_sort.Select( 2 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_ASC: self._default_tag_sort.Select( 3 )
|
||||
|
||||
services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ) )
|
||||
|
||||
for service in services: self._default_tag_repository.Append( service.GetName(), service.GetServiceKey() )
|
||||
|
||||
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
|
||||
|
||||
self._default_tag_repository.SelectClientData( default_tag_repository_key )
|
||||
|
||||
( remember, size ) = HC.options[ 'tag_dialog_size' ]
|
||||
|
||||
self._tag_dialog_size.SetValue( remember )
|
||||
|
@ -3968,18 +3956,9 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
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:' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._default_tag_sort, CC.FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self, label = 'Capitalise gui: ' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._gui_capitalisation, CC.FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self, label = 'By default, search non-local tags in write-autocomplete: ' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._gui_show_all_tags_in_autocomplete, CC.FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self, label = 'Remember manage tags dialog size: ' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._tag_dialog_size, CC.FLAGS_MIXED )
|
||||
|
||||
|
@ -4003,9 +3982,6 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
HC.options[ 'confirm_archive' ] = self._confirm_archive.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()
|
||||
HC.options[ 'default_tag_sort' ] = self._default_tag_sort.GetClientData( self._default_tag_sort.GetSelection() )
|
||||
|
||||
( remember, size ) = HC.options[ 'tag_dialog_size' ]
|
||||
|
||||
|
@ -4600,6 +4576,78 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
|
||||
|
||||
class _TagsPanel( wx.Panel ):
|
||||
|
||||
def __init__( self, parent, new_options ):
|
||||
|
||||
wx.Panel.__init__( self, parent )
|
||||
|
||||
self._new_options = new_options
|
||||
|
||||
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
||||
|
||||
self._default_tag_sort = wx.Choice( self )
|
||||
|
||||
self._default_tag_sort.Append( 'lexicographic (a-z)', CC.SORT_BY_LEXICOGRAPHIC_ASC )
|
||||
self._default_tag_sort.Append( 'lexicographic (z-a)', CC.SORT_BY_LEXICOGRAPHIC_DESC )
|
||||
self._default_tag_sort.Append( 'incidence (desc)', CC.SORT_BY_INCIDENCE_DESC )
|
||||
self._default_tag_sort.Append( 'incidence (asc)', CC.SORT_BY_INCIDENCE_ASC )
|
||||
|
||||
self._default_tag_repository = ClientGUICommon.BetterChoice( self )
|
||||
|
||||
self._show_all_tags_in_autocomplete = wx.CheckBox( self )
|
||||
|
||||
self._apply_all_parents_to_all_services = wx.CheckBox( self )
|
||||
|
||||
#
|
||||
|
||||
if HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_ASC: self._default_tag_sort.Select( 0 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_DESC: self._default_tag_sort.Select( 1 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_DESC: self._default_tag_sort.Select( 2 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_ASC: self._default_tag_sort.Select( 3 )
|
||||
|
||||
services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ) )
|
||||
|
||||
for service in services: self._default_tag_repository.Append( service.GetName(), service.GetServiceKey() )
|
||||
|
||||
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
|
||||
|
||||
self._default_tag_repository.SelectClientData( default_tag_repository_key )
|
||||
|
||||
self._show_all_tags_in_autocomplete.SetValue( HC.options[ 'show_all_tags_in_autocomplete' ] )
|
||||
|
||||
self._apply_all_parents_to_all_services.SetValue( self._new_options.GetBoolean( 'apply_all_parents_to_all_services' ) )
|
||||
#
|
||||
|
||||
gridbox = wx.FlexGridSizer( 0, 2 )
|
||||
|
||||
gridbox.AddGrowableCol( 1, 1 )
|
||||
|
||||
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:' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._default_tag_sort, CC.FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self, label = 'By default, search non-local tags in write-autocomplete: ' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._show_all_tags_in_autocomplete, CC.FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self, label = 'Apply all parents to all services: ' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._apply_all_parents_to_all_services, CC.FLAGS_MIXED )
|
||||
|
||||
self.SetSizer( gridbox )
|
||||
|
||||
|
||||
def UpdateOptions( self ):
|
||||
|
||||
HC.options[ 'default_tag_repository' ] = self._default_tag_repository.GetChoice()
|
||||
HC.options[ 'default_tag_sort' ] = self._default_tag_sort.GetClientData( self._default_tag_sort.GetSelection() )
|
||||
HC.options[ 'show_all_tags_in_autocomplete' ] = self._show_all_tags_in_autocomplete.GetValue()
|
||||
|
||||
self._new_options.SetBoolean( 'apply_all_parents_to_all_services', self._apply_all_parents_to_all_services.GetValue() )
|
||||
|
||||
|
||||
|
||||
def EventOK( self, event ):
|
||||
|
||||
for ( name, page ) in self._listbook.GetNamesToActivePages().items():
|
||||
|
@ -8858,126 +8906,208 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
self.SetSizer( vbox )
|
||||
|
||||
|
||||
def _AddTag( self, tag, only_add = False, only_remove = False, reason = None ):
|
||||
def _AddTags( self, tags, only_add = False, only_remove = False, forced_reason = None ):
|
||||
|
||||
if not self._i_am_local_tag_service and self._account.HasPermission( HC.RESOLVE_PETITIONS ):
|
||||
|
||||
forced_reason = 'admin'
|
||||
|
||||
|
||||
tag_managers = [ m.GetTagsManager() for m in self._media ]
|
||||
|
||||
num_files = len( self._media )
|
||||
|
||||
num_current = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetCurrent( self._tag_service_key ) ] )
|
||||
sets_of_choices = []
|
||||
|
||||
choices = []
|
||||
potential_num_reasons_needed = 0
|
||||
|
||||
sibling_tag = HydrusGlobals.client_controller.GetManager( 'tag_siblings' ).GetSibling( tag )
|
||||
|
||||
if sibling_tag is not None:
|
||||
for tag in tags:
|
||||
|
||||
num_sibling_current = len( [ 1 for tag_manager in tag_managers if sibling_tag in tag_manager.GetCurrent( self._tag_service_key ) ] )
|
||||
num_current = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetCurrent( self._tag_service_key ) ] )
|
||||
|
||||
|
||||
if self._i_am_local_tag_service:
|
||||
choices = []
|
||||
|
||||
if not only_remove:
|
||||
sibling_tag = HydrusGlobals.client_controller.GetManager( 'tag_siblings' ).GetSibling( tag )
|
||||
|
||||
if sibling_tag is not None:
|
||||
|
||||
if num_current < num_files:
|
||||
|
||||
choices.append( ( 'add ' + tag + ' to ' + HydrusData.ConvertIntToPrettyString( num_files - num_current ) + ' files', ( HC.CONTENT_UPDATE_ADD, tag ) ) )
|
||||
|
||||
|
||||
if sibling_tag is not None and num_sibling_current < num_files:
|
||||
|
||||
choices.append( ( 'add ' + sibling_tag + ' to ' + HydrusData.ConvertIntToPrettyString( num_files - num_current ) + ' files', ( HC.CONTENT_UPDATE_ADD, sibling_tag ) ) )
|
||||
|
||||
num_sibling_current = len( [ 1 for tag_manager in tag_managers if sibling_tag in tag_manager.GetCurrent( self._tag_service_key ) ] )
|
||||
|
||||
|
||||
if not only_add:
|
||||
if self._i_am_local_tag_service:
|
||||
|
||||
if num_current > 0:
|
||||
if not only_remove:
|
||||
|
||||
choices.append( ( 'delete ' + tag + ' from ' + HydrusData.ConvertIntToPrettyString( num_current ) + ' files', ( HC.CONTENT_UPDATE_DELETE, tag ) ) )
|
||||
|
||||
|
||||
|
||||
else:
|
||||
|
||||
num_pending = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetPending( self._tag_service_key ) ] )
|
||||
num_petitioned = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetPetitioned( self._tag_service_key ) ] )
|
||||
|
||||
if not only_remove:
|
||||
|
||||
if num_current + num_pending < num_files: choices.append( ( 'pend ' + tag + ' to ' + HydrusData.ConvertIntToPrettyString( num_files - ( num_current + num_pending ) ) + ' files', ( HC.CONTENT_UPDATE_PEND, tag ) ) )
|
||||
|
||||
if sibling_tag is not None:
|
||||
|
||||
num_sibling_pending = len( [ 1 for tag_manager in tag_managers if sibling_tag in tag_manager.GetPending( self._tag_service_key ) ] )
|
||||
|
||||
if num_sibling_current + num_sibling_pending < num_files:
|
||||
if num_current < num_files:
|
||||
|
||||
choices.append( ( 'pend ' + sibling_tag + ' to ' + HydrusData.ConvertIntToPrettyString( num_files - num_current ) + ' files', ( HC.CONTENT_UPDATE_PEND, sibling_tag ) ) )
|
||||
choices.append( ( 'add ' + tag + ' to ' + HydrusData.ConvertIntToPrettyString( num_files - num_current ) + ' files', ( HC.CONTENT_UPDATE_ADD, tag ) ) )
|
||||
|
||||
|
||||
if sibling_tag is not None and num_sibling_current < num_files:
|
||||
|
||||
choices.append( ( 'add ' + sibling_tag + ' to ' + HydrusData.ConvertIntToPrettyString( num_files - num_current ) + ' files', ( HC.CONTENT_UPDATE_ADD, sibling_tag ) ) )
|
||||
|
||||
|
||||
|
||||
if not only_add:
|
||||
|
||||
if num_current > 0:
|
||||
|
||||
choices.append( ( 'delete ' + tag + ' from ' + HydrusData.ConvertIntToPrettyString( num_current ) + ' files', ( HC.CONTENT_UPDATE_DELETE, tag ) ) )
|
||||
|
||||
|
||||
|
||||
else:
|
||||
|
||||
num_pending = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetPending( self._tag_service_key ) ] )
|
||||
num_petitioned = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetPetitioned( self._tag_service_key ) ] )
|
||||
|
||||
if not only_remove:
|
||||
|
||||
if num_current + num_pending < num_files: choices.append( ( 'pend ' + tag + ' to ' + HydrusData.ConvertIntToPrettyString( num_files - ( num_current + num_pending ) ) + ' files', ( HC.CONTENT_UPDATE_PEND, tag ) ) )
|
||||
|
||||
if sibling_tag is not None:
|
||||
|
||||
num_sibling_pending = len( [ 1 for tag_manager in tag_managers if sibling_tag in tag_manager.GetPending( self._tag_service_key ) ] )
|
||||
|
||||
if num_sibling_current + num_sibling_pending < num_files:
|
||||
|
||||
choices.append( ( 'pend ' + sibling_tag + ' to ' + HydrusData.ConvertIntToPrettyString( num_files - num_current ) + ' files', ( HC.CONTENT_UPDATE_PEND, sibling_tag ) ) )
|
||||
|
||||
|
||||
|
||||
|
||||
if not only_add:
|
||||
|
||||
if num_current > num_petitioned and not only_add:
|
||||
|
||||
choices.append( ( 'petition ' + tag + ' from ' + HydrusData.ConvertIntToPrettyString( num_current - num_petitioned ) + ' files', ( HC.CONTENT_UPDATE_PETITION, tag ) ) )
|
||||
|
||||
potential_num_reasons_needed += 1
|
||||
|
||||
|
||||
if num_pending > 0 and not only_add:
|
||||
|
||||
choices.append( ( 'rescind pending ' + tag + ' from ' + HydrusData.ConvertIntToPrettyString( num_pending ) + ' files', ( HC.CONTENT_UPDATE_RESCIND_PEND, tag ) ) )
|
||||
|
||||
|
||||
|
||||
if not only_remove:
|
||||
|
||||
if num_petitioned > 0:
|
||||
|
||||
choices.append( ( 'rescind petitioned ' + tag + ' from ' + HydrusData.ConvertIntToPrettyString( num_petitioned ) + ' files', ( HC.CONTENT_UPDATE_RESCIND_PETITION, tag ) ) )
|
||||
|
||||
|
||||
|
||||
|
||||
if not only_add:
|
||||
if len( choices ) == 0:
|
||||
|
||||
if num_current > num_petitioned and not only_add: choices.append( ( 'petition ' + tag + ' from ' + HydrusData.ConvertIntToPrettyString( num_current - num_petitioned ) + ' files', ( HC.CONTENT_UPDATE_PETITION, tag ) ) )
|
||||
if num_pending > 0 and not only_add: choices.append( ( 'rescind pending ' + tag + ' from ' + HydrusData.ConvertIntToPrettyString( num_pending ) + ' files', ( HC.CONTENT_UPDATE_RESCIND_PEND, tag ) ) )
|
||||
continue
|
||||
|
||||
|
||||
if not only_remove:
|
||||
|
||||
if num_petitioned > 0: choices.append( ( 'rescind petitioned ' + tag + ' from ' + HydrusData.ConvertIntToPrettyString( num_petitioned ) + ' files', ( HC.CONTENT_UPDATE_RESCIND_PETITION, tag ) ) )
|
||||
|
||||
sets_of_choices.append( choices )
|
||||
|
||||
|
||||
if len( choices ) == 0: return
|
||||
elif len( choices ) > 1:
|
||||
if forced_reason is None and potential_num_reasons_needed > 1:
|
||||
|
||||
intro = 'What would you like to do?'
|
||||
no_user_choices = True not in ( len( choices ) > 1 for choices in sets_of_choices )
|
||||
|
||||
with ClientGUIDialogs.DialogButtonChoice( self, intro, choices ) as dlg:
|
||||
if no_user_choices:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK: choice = dlg.GetData()
|
||||
else: return
|
||||
message = 'You are about to petition more than one tag.'
|
||||
|
||||
else:
|
||||
|
||||
message = 'You might be about to petition more than one tag.'
|
||||
|
||||
|
||||
else: [ ( text, choice ) ] = choices
|
||||
|
||||
( choice_action, choice_tag ) = choice
|
||||
|
||||
if choice_action == HC.CONTENT_UPDATE_ADD: media_to_affect = ( m for m in self._media if choice_tag not in m.GetTagsManager().GetCurrent( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_DELETE: media_to_affect = ( m for m in self._media if choice_tag in m.GetTagsManager().GetCurrent( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_PEND: media_to_affect = ( m for m in self._media if choice_tag not in m.GetTagsManager().GetCurrent( self._tag_service_key ) and choice_tag not in m.GetTagsManager().GetPending( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_PETITION: media_to_affect = ( m for m in self._media if choice_tag in m.GetTagsManager().GetCurrent( self._tag_service_key ) and choice_tag not in m.GetTagsManager().GetPetitioned( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_RESCIND_PEND: media_to_affect = ( m for m in self._media if choice_tag in m.GetTagsManager().GetPending( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_RESCIND_PETITION: media_to_affect = ( m for m in self._media if choice_tag in m.GetTagsManager().GetPetitioned( self._tag_service_key ) )
|
||||
|
||||
hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media_to_affect ) ) )
|
||||
|
||||
if choice_action == HC.CONTENT_UPDATE_PETITION:
|
||||
message += os.linesep * 2
|
||||
message += 'To save you time, would you like to use the same reason for all the petitions?'
|
||||
|
||||
if reason is None:
|
||||
with ClientGUIDialogs.DialogYesNo( self, message ) as yn_dlg:
|
||||
|
||||
if self._account.HasPermission( HC.RESOLVE_PETITIONS ): reason = 'admin'
|
||||
else:
|
||||
if yn_dlg.ShowModal() == wx.ID_YES:
|
||||
|
||||
message = 'Enter a reason for this tag to be removed. A janitor will review your petition.'
|
||||
message = 'Please enter your common petition reason here:'
|
||||
|
||||
with ClientGUIDialogs.DialogTextEntry( self, message ) as text_dlg:
|
||||
|
||||
if text_dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
forced_reason = text_dlg.GetValue()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
for choices in sets_of_choices:
|
||||
|
||||
if len( choices ) == 1:
|
||||
|
||||
[ ( text_gumpf, choice ) ] = choices
|
||||
|
||||
else:
|
||||
|
||||
intro = 'What would you like to do?'
|
||||
|
||||
with ClientGUIDialogs.DialogButtonChoice( self, intro, choices ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
choice = dlg.GetData()
|
||||
|
||||
else:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
|
||||
|
||||
( choice_action, choice_tag ) = choice
|
||||
|
||||
if choice_action == HC.CONTENT_UPDATE_ADD: media_to_affect = ( m for m in self._media if choice_tag not in m.GetTagsManager().GetCurrent( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_DELETE: media_to_affect = ( m for m in self._media if choice_tag in m.GetTagsManager().GetCurrent( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_PEND: media_to_affect = ( m for m in self._media if choice_tag not in m.GetTagsManager().GetCurrent( self._tag_service_key ) and choice_tag not in m.GetTagsManager().GetPending( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_PETITION: media_to_affect = ( m for m in self._media if choice_tag in m.GetTagsManager().GetCurrent( self._tag_service_key ) and choice_tag not in m.GetTagsManager().GetPetitioned( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_RESCIND_PEND: media_to_affect = ( m for m in self._media if choice_tag in m.GetTagsManager().GetPending( self._tag_service_key ) )
|
||||
elif choice_action == HC.CONTENT_UPDATE_RESCIND_PETITION: media_to_affect = ( m for m in self._media if choice_tag in m.GetTagsManager().GetPetitioned( self._tag_service_key ) )
|
||||
|
||||
hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media_to_affect ) ) )
|
||||
|
||||
if choice_action == HC.CONTENT_UPDATE_PETITION:
|
||||
|
||||
if forced_reason is None:
|
||||
|
||||
message = 'Enter a reason for ' + choice_tag + ' to be removed. A janitor will review your petition.'
|
||||
|
||||
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
|
||||
else: return
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
reason = dlg.GetValue()
|
||||
|
||||
else:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
|
||||
|
||||
else:
|
||||
|
||||
reason = forced_reason
|
||||
|
||||
|
||||
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, choice_action, ( choice_tag, hashes, reason ) )
|
||||
|
||||
else: content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, choice_action, ( choice_tag, hashes ) )
|
||||
|
||||
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, choice_action, ( choice_tag, hashes, reason ) )
|
||||
for m in self._media: m.GetMediaResult().ProcessContentUpdate( self._tag_service_key, content_update )
|
||||
|
||||
self._content_updates.append( content_update )
|
||||
|
||||
else: content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, choice_action, ( choice_tag, hashes ) )
|
||||
|
||||
for m in self._media: m.GetMediaResult().ProcessContentUpdate( self._tag_service_key, content_update )
|
||||
|
||||
self._content_updates.append( content_update )
|
||||
|
||||
self._tags_box.SetTagsByMedia( self._media, force_reload = True )
|
||||
|
||||
|
@ -8991,14 +9121,11 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
if len( tags ) > 0:
|
||||
|
||||
for tag in tags:
|
||||
|
||||
self._AddTag( tag )
|
||||
|
||||
self._AddTags( tags )
|
||||
|
||||
for parent in parents:
|
||||
if len( parents ) > 0:
|
||||
|
||||
self._AddTag( parent, only_add = True )
|
||||
self._AddTags( parents, only_add = True )
|
||||
|
||||
|
||||
|
||||
|
@ -9051,10 +9178,7 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
tags = HydrusTags.CleanTags( tags )
|
||||
|
||||
for tag in tags:
|
||||
|
||||
self._AddTag( tag, only_add = True )
|
||||
|
||||
self._AddTags( tags, only_add = True )
|
||||
|
||||
except: wx.MessageBox( 'I could not understand what was in the clipboard' )
|
||||
|
||||
|
@ -9068,42 +9192,12 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
removable_tags = set()
|
||||
|
||||
for tag_manager in tag_managers:
|
||||
|
||||
|
||||
removable_tags.update( tag_manager.GetCurrent( self._tag_service_key ) )
|
||||
removable_tags.update( tag_manager.GetPending( self._tag_service_key ) )
|
||||
|
||||
|
||||
reason = None
|
||||
|
||||
if not self._i_am_local_tag_service:
|
||||
|
||||
if self._account.HasPermission( HC.RESOLVE_PETITIONS ):
|
||||
|
||||
reason = 'admin'
|
||||
|
||||
else:
|
||||
|
||||
message = 'Enter a reason for all these tags to be removed. A janitor will review your petition.'
|
||||
|
||||
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
reason = dlg.GetValue()
|
||||
|
||||
else:
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
for tag in removable_tags:
|
||||
|
||||
self._AddTag( tag, only_remove = True, reason = reason )
|
||||
|
||||
|
||||
self._AddTags( removable_tags, only_remove = True )
|
||||
|
||||
|
||||
def EventShowDeleted( self, event ):
|
||||
|
|
|
@ -76,66 +76,79 @@ class FullscreenHoverFrame( wx.Frame ):
|
|||
|
||||
def TIMEREventCheckIfShouldShow( self, event ):
|
||||
|
||||
if self._current_media is None:
|
||||
try:
|
||||
|
||||
self.Hide()
|
||||
|
||||
else:
|
||||
|
||||
( mouse_x, mouse_y ) = wx.GetMousePosition()
|
||||
|
||||
( my_width, my_height ) = self.GetSize()
|
||||
|
||||
( should_resize, ( my_ideal_width, my_ideal_height ), ( my_ideal_x, my_ideal_y ) ) = self._GetIdealSizeAndPosition()
|
||||
|
||||
if my_ideal_width == -1: my_ideal_width = my_width
|
||||
if my_ideal_height == -1: my_ideal_height = my_height
|
||||
|
||||
in_x = my_ideal_x <= mouse_x and mouse_x <= my_ideal_x + my_ideal_width
|
||||
in_y = my_ideal_y <= mouse_y and mouse_y <= my_ideal_y + my_ideal_height
|
||||
|
||||
no_dialogs_open = True
|
||||
|
||||
tlps = wx.GetTopLevelWindows()
|
||||
|
||||
for tlp in tlps:
|
||||
if self._current_media is None:
|
||||
|
||||
if isinstance( tlp, wx.Dialog ): no_dialogs_open = False
|
||||
self.Hide()
|
||||
|
||||
else:
|
||||
|
||||
( mouse_x, mouse_y ) = wx.GetMousePosition()
|
||||
|
||||
( my_width, my_height ) = self.GetSize()
|
||||
|
||||
( should_resize, ( my_ideal_width, my_ideal_height ), ( my_ideal_x, my_ideal_y ) ) = self._GetIdealSizeAndPosition()
|
||||
|
||||
if my_ideal_width == -1: my_ideal_width = my_width
|
||||
if my_ideal_height == -1: my_ideal_height = my_height
|
||||
|
||||
in_x = my_ideal_x <= mouse_x and mouse_x <= my_ideal_x + my_ideal_width
|
||||
in_y = my_ideal_y <= mouse_y and mouse_y <= my_ideal_y + my_ideal_height
|
||||
|
||||
no_dialogs_open = True
|
||||
|
||||
tlps = wx.GetTopLevelWindows()
|
||||
|
||||
for tlp in tlps:
|
||||
|
||||
if isinstance( tlp, wx.Dialog ): no_dialogs_open = False
|
||||
|
||||
|
||||
mime = self._current_media.GetMime()
|
||||
|
||||
in_position = in_x and in_y
|
||||
|
||||
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()
|
||||
|
||||
tlp = wx.GetTopLevelParent( current_focus )
|
||||
|
||||
my_parent_in_focus_tree = False
|
||||
|
||||
while tlp is not None:
|
||||
|
||||
if tlp == self.GetParent(): my_parent_in_focus_tree = True
|
||||
|
||||
tlp = tlp.GetParent()
|
||||
|
||||
|
||||
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:
|
||||
|
||||
self._SizeAndPosition()
|
||||
|
||||
self.Show()
|
||||
|
||||
elif ready_to_hide: self.Hide()
|
||||
|
||||
|
||||
mime = self._current_media.GetMime()
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
in_position = in_x and in_y
|
||||
self._timer_check_show.Stop()
|
||||
|
||||
mouse_is_over_interactable_media = mime == HC.APPLICATION_FLASH and self.GetParent().MouseIsOverMedia()
|
||||
except:
|
||||
|
||||
mouse_is_near_animation_bar = self.GetParent().MouseIsNearAnimationBar()
|
||||
self._timer_check_show.Stop()
|
||||
|
||||
mouse_is_over_something_important = mouse_is_over_interactable_media or mouse_is_near_animation_bar
|
||||
|
||||
current_focus = wx.Window.FindFocus()
|
||||
|
||||
tlp = wx.GetTopLevelParent( current_focus )
|
||||
|
||||
my_parent_in_focus_tree = False
|
||||
|
||||
while tlp is not None:
|
||||
|
||||
if tlp == self.GetParent(): my_parent_in_focus_tree = True
|
||||
|
||||
tlp = tlp.GetParent()
|
||||
|
||||
|
||||
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:
|
||||
|
||||
self._SizeAndPosition()
|
||||
|
||||
self.Show()
|
||||
|
||||
elif ready_to_hide: self.Hide()
|
||||
raise
|
||||
|
||||
|
||||
|
||||
|
@ -613,9 +626,12 @@ class FullscreenHoverFrameRatings( FullscreenHoverFrame ):
|
|||
|
||||
if True in ( my_hash in content_update.GetHashes() for content_update in content_updates ):
|
||||
|
||||
do_redraw = True
|
||||
|
||||
break
|
||||
if True in ( content_update.IsInboxRelated() for content_update in content_updates ):
|
||||
|
||||
do_redraw = True
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ def GenerateDumpMultipartFormDataCTAndBody( fields ):
|
|||
|
||||
return m.get()
|
||||
|
||||
class CaptchaControl( wx.Panel ):
|
||||
'''class CaptchaControl( wx.Panel ):
|
||||
|
||||
def __init__( self, parent, captcha_type, default ):
|
||||
|
||||
|
@ -242,9 +242,7 @@ class CaptchaControl( wx.Panel ):
|
|||
else: self._captcha_entry.SetValue( entry )
|
||||
|
||||
|
||||
def _DrawMain( self ):
|
||||
|
||||
dc = self._captcha_panel.GetDC()
|
||||
def _DrawMain( self, dc ):
|
||||
|
||||
if self._captcha_challenge is None:
|
||||
|
||||
|
@ -409,11 +407,31 @@ class CaptchaControl( wx.Panel ):
|
|||
|
||||
def TIMEREvent( self, event ):
|
||||
|
||||
if HydrusData.TimeHasPassed( self._captcha_runs_out ): self.Enable()
|
||||
else: self._DrawMain()
|
||||
try:
|
||||
|
||||
if HydrusData.TimeHasPassed( self._captcha_runs_out ):
|
||||
|
||||
self.Enable()
|
||||
|
||||
else:
|
||||
|
||||
self._DrawMain()
|
||||
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
self._timer.Stop()
|
||||
|
||||
except:
|
||||
|
||||
self._timer.Stop()
|
||||
|
||||
raise
|
||||
|
||||
|
||||
|
||||
class Comment( wx.Panel ):
|
||||
'''
|
||||
|
||||
'''class Comment( wx.Panel ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
|
||||
|
@ -476,7 +494,7 @@ class Comment( wx.Panel ):
|
|||
|
||||
event.Skip()
|
||||
|
||||
|
||||
'''
|
||||
class ManagementController( HydrusSerialisable.SerialisableBase ):
|
||||
|
||||
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_MANAGEMENT_CONTROLLER
|
||||
|
|
|
@ -1648,6 +1648,21 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
# accelerator tables can't handle escape key in windows, gg
|
||||
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE: self._Select( 'none' )
|
||||
if event.GetKeyCode() in ( wx.WXK_PAGEUP, wx.WXK_PAGEDOWN ):
|
||||
|
||||
if event.GetKeyCode() == wx.WXK_PAGEUP:
|
||||
|
||||
direction = -1
|
||||
|
||||
elif event.GetKeyCode() == wx.WXK_PAGEDOWN:
|
||||
|
||||
direction = 1
|
||||
|
||||
|
||||
shift = event.ShiftDown()
|
||||
|
||||
self._MoveFocussedThumbnail( self._num_rows_per_canvas_page * direction, 0, shift )
|
||||
|
||||
else: event.Skip()
|
||||
|
||||
|
||||
|
@ -2349,7 +2364,7 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
( wx.ACCEL_CTRL, wx.WXK_SPACE, ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'ctrl-space' ) )
|
||||
]
|
||||
|
||||
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( action ) ) for ( key, action ) in key_dict.items() ] )
|
||||
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( action ) ) for ( key, action ) in key_dict.items() if action not in ( 'previous', 'next' ) ] )
|
||||
|
||||
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
|
||||
|
||||
|
@ -2398,99 +2413,112 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
def TIMEREventAnimation( self, event ):
|
||||
|
||||
started = HydrusData.GetNowPrecise()
|
||||
|
||||
( thumbnail_span_width, thumbnail_span_height ) = self._GetThumbnailSpanDimensions()
|
||||
|
||||
all_info = self._thumbnails_being_faded_in.items()
|
||||
|
||||
random.shuffle( all_info )
|
||||
|
||||
dcs = {}
|
||||
|
||||
for ( hash, ( original_bmp, alpha_bmp, thumbnail_index, thumbnail, num_frames_rendered ) ) in all_info:
|
||||
try:
|
||||
|
||||
num_frames_rendered += 1
|
||||
started = HydrusData.GetNowPrecise()
|
||||
|
||||
page_index = self._GetPageIndexFromThumbnailIndex( thumbnail_index )
|
||||
( thumbnail_span_width, thumbnail_span_height ) = self._GetThumbnailSpanDimensions()
|
||||
|
||||
delete_entry = False
|
||||
all_info = self._thumbnails_being_faded_in.items()
|
||||
|
||||
try: expected_thumbnail = self._sorted_media[ thumbnail_index ]
|
||||
except: expected_thumbnail = None
|
||||
random.shuffle( all_info )
|
||||
|
||||
if expected_thumbnail != thumbnail:
|
||||
dcs = {}
|
||||
|
||||
for ( hash, ( original_bmp, alpha_bmp, thumbnail_index, thumbnail, num_frames_rendered ) ) in all_info:
|
||||
|
||||
delete_entry = True
|
||||
num_frames_rendered += 1
|
||||
|
||||
elif page_index not in self._clean_canvas_pages:
|
||||
page_index = self._GetPageIndexFromThumbnailIndex( thumbnail_index )
|
||||
|
||||
delete_entry = True
|
||||
delete_entry = False
|
||||
|
||||
else:
|
||||
try: expected_thumbnail = self._sorted_media[ thumbnail_index ]
|
||||
except: expected_thumbnail = None
|
||||
|
||||
if num_frames_rendered >= 9:
|
||||
if expected_thumbnail != thumbnail:
|
||||
|
||||
bmp_to_use = original_bmp
|
||||
delete_entry = True
|
||||
|
||||
elif page_index not in self._clean_canvas_pages:
|
||||
|
||||
delete_entry = True
|
||||
|
||||
else:
|
||||
|
||||
bmp_to_use = alpha_bmp
|
||||
if num_frames_rendered >= 9:
|
||||
|
||||
bmp_to_use = original_bmp
|
||||
|
||||
delete_entry = True
|
||||
|
||||
else:
|
||||
|
||||
bmp_to_use = alpha_bmp
|
||||
|
||||
self._thumbnails_being_faded_in[ hash ] = ( original_bmp, alpha_bmp, thumbnail_index, thumbnail, num_frames_rendered )
|
||||
|
||||
|
||||
self._thumbnails_being_faded_in[ hash ] = ( original_bmp, alpha_bmp, thumbnail_index, thumbnail, num_frames_rendered )
|
||||
thumbnail_col = thumbnail_index % self._num_columns
|
||||
|
||||
thumbnail_row = thumbnail_index / self._num_columns
|
||||
|
||||
x = thumbnail_col * thumbnail_span_width + CC.THUMBNAIL_MARGIN
|
||||
|
||||
y = ( thumbnail_row - ( page_index * self._num_rows_per_canvas_page ) ) * thumbnail_span_height + CC.THUMBNAIL_MARGIN
|
||||
|
||||
if page_index not in dcs:
|
||||
|
||||
canvas_bmp = self._clean_canvas_pages[ page_index ]
|
||||
|
||||
dc = wx.MemoryDC( canvas_bmp )
|
||||
|
||||
dcs[ page_index ] = dc
|
||||
|
||||
|
||||
dc = dcs[ page_index ]
|
||||
|
||||
dc.DrawBitmap( bmp_to_use, x, y, True )
|
||||
|
||||
|
||||
thumbnail_col = thumbnail_index % self._num_columns
|
||||
|
||||
thumbnail_row = thumbnail_index / self._num_columns
|
||||
|
||||
x = thumbnail_col * thumbnail_span_width + CC.THUMBNAIL_MARGIN
|
||||
|
||||
y = ( thumbnail_row - ( page_index * self._num_rows_per_canvas_page ) ) * thumbnail_span_height + CC.THUMBNAIL_MARGIN
|
||||
|
||||
if page_index not in dcs:
|
||||
if delete_entry:
|
||||
|
||||
canvas_bmp = self._clean_canvas_pages[ page_index ]
|
||||
del self._thumbnails_being_faded_in[ hash ]
|
||||
|
||||
dc = wx.MemoryDC( canvas_bmp )
|
||||
|
||||
dcs[ page_index ] = dc
|
||||
wx.CallAfter( original_bmp.Destroy )
|
||||
wx.CallAfter( alpha_bmp.Destroy )
|
||||
|
||||
|
||||
dc = dcs[ page_index ]
|
||||
|
||||
dc.DrawBitmap( bmp_to_use, x, y, True )
|
||||
if HydrusData.GetNowPrecise() - started > 0.016:
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
if delete_entry:
|
||||
if len( self._thumbnails_being_faded_in ) > 0:
|
||||
|
||||
del self._thumbnails_being_faded_in[ hash ]
|
||||
finished = HydrusData.GetNowPrecise()
|
||||
|
||||
wx.CallAfter( original_bmp.Destroy )
|
||||
wx.CallAfter( alpha_bmp.Destroy )
|
||||
time_this_took_in_ms = ( finished - started ) * 1000
|
||||
|
||||
ms = max( 1, int( round( 16.7 - time_this_took_in_ms ) ) )
|
||||
|
||||
self._timer_animation.Start( ms, wx.TIMER_ONE_SHOT )
|
||||
|
||||
|
||||
if HydrusData.GetNowPrecise() - started > 0.016:
|
||||
|
||||
break
|
||||
|
||||
self.Refresh()
|
||||
|
||||
|
||||
if len( self._thumbnails_being_faded_in ) > 0:
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
finished = HydrusData.GetNowPrecise()
|
||||
self._timer_animation.Stop()
|
||||
|
||||
time_this_took_in_ms = ( finished - started ) * 1000
|
||||
except:
|
||||
|
||||
ms = max( 1, int( round( 16.7 - time_this_took_in_ms ) ) )
|
||||
self._timer_animation.Stop()
|
||||
|
||||
self._timer_animation.Start( ms, wx.TIMER_ONE_SHOT )
|
||||
raise
|
||||
|
||||
|
||||
self.Refresh()
|
||||
|
||||
|
||||
def WaterfallThumbnail( self, page_key, thumbnail ):
|
||||
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
import numpy.core.multiarray # important this comes before cv!
|
||||
import cv2
|
||||
import HydrusImageHandling
|
||||
|
||||
if cv2.__version__.startswith( '2' ):
|
||||
|
||||
IMREAD_UNCHANGED = cv2.CV_LOAD_IMAGE_UNCHANGED
|
||||
|
||||
else:
|
||||
|
||||
IMREAD_UNCHANGED = cv2.IMREAD_UNCHANGED
|
||||
|
||||
def EfficientlyResizeNumpyImage( numpy_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_y, im_x, depth ) = numpy_image.shape
|
||||
|
||||
if target_x >= im_x and target_y >= im_y: return numpy_image
|
||||
|
||||
result = numpy_image
|
||||
|
||||
# this seems to slow things down a lot, at least for cv!
|
||||
#if im_x > 2 * target_x and im_y > 2 * target_y: result = cv2.resize( numpy_image, ( 2 * target_x, 2 * target_y ), interpolation = cv2.INTER_NEAREST )
|
||||
|
||||
return cv2.resize( result, ( target_x, target_y ), interpolation = cv2.INTER_LINEAR )
|
||||
|
||||
def EfficientlyThumbnailNumpyImage( numpy_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_y, im_x, depth ) = numpy_image.shape
|
||||
|
||||
if target_x >= im_x and target_y >= im_y: return numpy_image
|
||||
|
||||
( target_x, target_y ) = HydrusImageHandling.GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) )
|
||||
|
||||
return cv2.resize( numpy_image, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
|
||||
|
||||
def GenerateNumpyImage( path ):
|
||||
|
||||
numpy_image = cv2.imread( path, flags = -1 ) # flags = -1 loads alpha channel, if present
|
||||
|
||||
( width, height, depth ) = numpy_image.shape
|
||||
|
||||
if width * height * depth != len( numpy_image.data ): raise Exception( 'CV could not understand this image; it was probably an unusual png!' )
|
||||
|
||||
if depth == 4: raise Exception( 'CV is bad at alpha!' )
|
||||
else: numpy_image = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2RGB )
|
||||
|
||||
return numpy_image
|
||||
|
||||
def GenerateNumPyImageFromPILImage( pil_image ):
|
||||
|
||||
if pil_image.mode == 'RGBA' or ( pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ) ):
|
||||
|
||||
if pil_image.mode == 'P': pil_image = pil_image.convert( 'RGBA' )
|
||||
|
||||
else:
|
||||
|
||||
if pil_image.mode != 'RGB': pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
|
||||
( w, h ) = pil_image.size
|
||||
|
||||
s = pil_image.tobytes()
|
||||
|
||||
return numpy.fromstring( s, dtype = 'uint8' ).reshape( ( h, w, len( s ) // ( w * h ) ) )
|
||||
|
||||
def GeneratePerceptualHash( path ):
|
||||
|
||||
numpy_image = cv2.imread( path, IMREAD_UNCHANGED )
|
||||
|
||||
( y, x, depth ) = numpy_image.shape
|
||||
|
||||
if depth == 4:
|
||||
|
||||
# create a white greyscale canvas
|
||||
|
||||
white = numpy.ones( ( x, y ) ) * 255
|
||||
|
||||
# create weight and transform numpy_image to greyscale
|
||||
|
||||
numpy_alpha = numpy_image[ :, :, 3 ]
|
||||
|
||||
numpy_image_bgr = numpy_image[ :, :, :3 ]
|
||||
|
||||
numpy_image_gray = cv2.cvtColor( numpy_image_bgr, cv2.COLOR_BGR2GRAY )
|
||||
|
||||
numpy_image_result = numpy.empty( ( y, x ), numpy.float32 )
|
||||
|
||||
# paste greyscale onto the white
|
||||
|
||||
# can't think of a better way to do this!
|
||||
# cv2.addWeighted only takes a scalar for weight!
|
||||
for i in range( y ):
|
||||
|
||||
for j in range( x ):
|
||||
|
||||
opacity = float( numpy_alpha[ i, j ] ) / 255.0
|
||||
|
||||
grey_part = numpy_image_gray[ i, j ] * opacity
|
||||
white_part = 255 * ( 1 - opacity )
|
||||
|
||||
pixel = grey_part + white_part
|
||||
|
||||
numpy_image_result[ i, j ] = pixel
|
||||
|
||||
|
||||
|
||||
numpy_image_gray = numpy_image_result
|
||||
|
||||
# use 255 for white weight, alpha for image weight
|
||||
|
||||
else:
|
||||
|
||||
numpy_image_gray = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2GRAY )
|
||||
|
||||
|
||||
numpy_image_tiny = cv2.resize( numpy_image_gray, ( 32, 32 ), interpolation = cv2.INTER_AREA )
|
||||
|
||||
# convert to float and calc dct
|
||||
|
||||
numpy_image_tiny_float = numpy.float32( numpy_image_tiny )
|
||||
|
||||
dct = cv2.dct( numpy_image_tiny_float )
|
||||
|
||||
# take top left 8x8 of dct
|
||||
|
||||
dct_88 = dct[:8,:8]
|
||||
|
||||
# get mean of dct, excluding [0,0]
|
||||
|
||||
mask = numpy.ones( ( 8, 8 ) )
|
||||
|
||||
mask[0,0] = 0
|
||||
|
||||
average = numpy.average( dct_88, weights = mask )
|
||||
|
||||
# make a monochromatic, 64-bit hash of whether the entry is above or below the mean
|
||||
|
||||
bytes = []
|
||||
|
||||
for i in range( 8 ):
|
||||
|
||||
byte = 0
|
||||
|
||||
for j in range( 8 ):
|
||||
|
||||
byte <<= 1 # shift byte one left
|
||||
|
||||
value = dct_88[i,j]
|
||||
|
||||
if value > average: byte |= 1
|
||||
|
||||
|
||||
bytes.append( byte )
|
||||
|
||||
|
||||
answer = str( bytearray( bytes ) )
|
||||
|
||||
# we good
|
||||
|
||||
return answer
|
||||
|
|
@ -236,7 +236,7 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
error_text = traceback.format_exc()
|
||||
|
||||
print( error_text )
|
||||
HydrusData.Print( error_text )
|
||||
|
||||
status = CC.STATUS_FAILED
|
||||
|
||||
|
@ -710,7 +710,7 @@ class HDDImport( HydrusSerialisable.SerialisableBase ):
|
|||
except Exception as e:
|
||||
|
||||
error_text = traceback.format_exc()
|
||||
print( error_text )
|
||||
HydrusData.Print( error_text )
|
||||
|
||||
status = CC.STATUS_FAILED
|
||||
|
||||
|
@ -1051,8 +1051,8 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
error_text = traceback.format_exc()
|
||||
|
||||
print( 'A file failed to import from import folder ' + self._name + ':' )
|
||||
print( error_text )
|
||||
HydrusData.Print( 'A file failed to import from import folder ' + self._name + ':' )
|
||||
HydrusData.Print( error_text )
|
||||
|
||||
self._path_cache.UpdateSeedStatus( path, CC.STATUS_FAILED, note = error_text )
|
||||
|
||||
|
@ -1244,7 +1244,7 @@ class PageOfImagesImport( HydrusSerialisable.SerialisableBase ):
|
|||
except Exception:
|
||||
|
||||
error_text = traceback.format_exc()
|
||||
print( error_text )
|
||||
HydrusData.Print( error_text )
|
||||
|
||||
status = CC.STATUS_FAILED
|
||||
|
||||
|
@ -2025,7 +2025,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
except Exception as e:
|
||||
|
||||
error_text = traceback.format_exc()
|
||||
print( error_text )
|
||||
HydrusData.Print( error_text )
|
||||
|
||||
status = CC.STATUS_FAILED
|
||||
|
||||
|
@ -2447,7 +2447,7 @@ class ThreadWatcherImport( HydrusSerialisable.SerialisableBase ):
|
|||
except Exception as e:
|
||||
|
||||
error_text = traceback.format_exc()
|
||||
print( error_text )
|
||||
HydrusData.Print( error_text )
|
||||
|
||||
status = CC.STATUS_FAILED
|
||||
|
||||
|
|
|
@ -536,7 +536,10 @@ class HTTPConnection( object ):
|
|||
|
||||
self._last_request_time = HydrusData.GetNow()
|
||||
|
||||
if response.status == 200: return ( parsed_response, None, size_of_response, response_headers, cookies )
|
||||
if response.status == 200:
|
||||
|
||||
return ( parsed_response, None, size_of_response, response_headers, cookies )
|
||||
|
||||
elif response.status in ( 301, 302, 303, 307 ):
|
||||
|
||||
location = response.getheader( 'Location' )
|
||||
|
@ -549,9 +552,9 @@ class HTTPConnection( object ):
|
|||
if ' ' in url:
|
||||
|
||||
# some booru is giving daft redirect responses
|
||||
print( url )
|
||||
HydrusData.Print( url )
|
||||
url = urllib.quote( HydrusData.ToByteString( url ), safe = '/?=&' )
|
||||
print( url )
|
||||
HydrusData.Print( url )
|
||||
|
||||
|
||||
if not url.startswith( self._scheme ):
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import ClientFiles
|
||||
import ClientImageHandling
|
||||
import ClientVideoHandling
|
||||
import HydrusConstants as HC
|
||||
import HydrusData
|
||||
import HydrusExceptions
|
||||
|
@ -7,7 +9,6 @@ import HydrusGlobals
|
|||
import HydrusThreading
|
||||
import HydrusVideoHandling
|
||||
import lz4
|
||||
import numpy
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
@ -17,7 +18,7 @@ def GenerateHydrusBitmap( path, compressed = True ):
|
|||
|
||||
try:
|
||||
|
||||
numpy_image = HydrusImageHandling.GenerateNumpyImage( path )
|
||||
numpy_image = ClientImageHandling.GenerateNumpyImage( path )
|
||||
|
||||
return GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = compressed )
|
||||
|
||||
|
@ -101,9 +102,9 @@ class RasterContainerImage( RasterContainer ):
|
|||
|
||||
try:
|
||||
|
||||
numpy_image = HydrusImageHandling.GenerateNumpyImage( self._path )
|
||||
numpy_image = ClientImageHandling.GenerateNumpyImage( self._path )
|
||||
|
||||
resized_numpy_image = HydrusImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
|
||||
resized_numpy_image = ClientImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
|
||||
|
||||
hydrus_bitmap = GenerateHydrusBitmapFromNumPyImage( resized_numpy_image )
|
||||
|
||||
|
@ -182,13 +183,13 @@ class RasterContainerVideo( RasterContainer ):
|
|||
|
||||
self._durations = HydrusImageHandling.GetGIFFrameDurations( self._path )
|
||||
|
||||
self._renderer = HydrusVideoHandling.GIFRenderer( path, num_frames, target_resolution )
|
||||
self._renderer = ClientVideoHandling.GIFRenderer( path, num_frames, target_resolution )
|
||||
|
||||
else:
|
||||
|
||||
try:
|
||||
|
||||
self._frame_duration = HydrusVideoHandling.GetVideoFrameDuration( self._path )
|
||||
self._frame_duration = ClientVideoHandling.GetVideoFrameDuration( self._path )
|
||||
|
||||
except HydrusExceptions.CantRenderWithCVException:
|
||||
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
import numpy.core.multiarray # important this comes before cv!
|
||||
import cv2
|
||||
import ClientImageHandling
|
||||
import HydrusExceptions
|
||||
import HydrusImageHandling
|
||||
|
||||
if cv2.__version__.startswith( '2' ):
|
||||
|
||||
CAP_PROP_FRAME_COUNT = cv2.cv.CV_CAP_PROP_FRAME_COUNT
|
||||
CAP_PROP_FPS = cv2.cv.CV_CAP_PROP_FPS
|
||||
CAP_PROP_FRAME_WIDTH = cv2.cv.CV_CAP_PROP_FRAME_WIDTH
|
||||
CAP_PROP_FRAME_HEIGHT = cv2.cv.CV_CAP_PROP_FRAME_HEIGHT
|
||||
CAP_PROP_CONVERT_RGB = cv2.cv.CV_CAP_PROP_CONVERT_RGB
|
||||
CAP_PROP_POS_FRAMES = cv2.cv.CV_CAP_PROP_POS_FRAMES
|
||||
|
||||
else:
|
||||
|
||||
CAP_PROP_FRAME_COUNT = cv2.CAP_PROP_FRAME_COUNT
|
||||
CAP_PROP_FPS = cv2.CAP_PROP_FPS
|
||||
CAP_PROP_FRAME_WIDTH = cv2.CAP_PROP_FRAME_WIDTH
|
||||
CAP_PROP_FRAME_HEIGHT = cv2.CAP_PROP_FRAME_HEIGHT
|
||||
CAP_PROP_CONVERT_RGB = cv2.CAP_PROP_CONVERT_RGB
|
||||
CAP_PROP_POS_FRAMES = cv2.CAP_PROP_POS_FRAMES
|
||||
|
||||
def GetCVVideoProperties( path ):
|
||||
|
||||
capture = cv2.VideoCapture( path )
|
||||
|
||||
num_frames = int( capture.get( CAP_PROP_FRAME_COUNT ) )
|
||||
|
||||
fps = capture.get( CAP_PROP_FPS )
|
||||
|
||||
length_in_seconds = num_frames / fps
|
||||
|
||||
length_in_ms = int( length_in_seconds * 1000 )
|
||||
|
||||
duration = length_in_ms
|
||||
|
||||
width = int( capture.get( CAP_PROP_FRAME_WIDTH ) )
|
||||
|
||||
height = int( capture.get( CAP_PROP_FRAME_HEIGHT ) )
|
||||
|
||||
return ( ( width, height ), duration, num_frames )
|
||||
|
||||
def GetVideoFrameDuration( path ):
|
||||
|
||||
cv_video = cv2.VideoCapture( path )
|
||||
|
||||
fps = cv_video.get( CAP_PROP_FPS )
|
||||
|
||||
if fps == 0: raise HydrusExceptions.CantRenderWithCVException()
|
||||
|
||||
return 1000.0 / fps
|
||||
|
||||
# the cv code was initially written by @fluffy_cub
|
||||
class GIFRenderer( object ):
|
||||
|
||||
def __init__( self, path, num_frames, target_resolution ):
|
||||
|
||||
self._path = path
|
||||
self._num_frames = num_frames
|
||||
self._target_resolution = target_resolution
|
||||
|
||||
if cv2.__version__.startswith( '2' ):
|
||||
|
||||
self._InitialisePIL()
|
||||
|
||||
else:
|
||||
|
||||
self._InitialiseCV()
|
||||
|
||||
|
||||
|
||||
def _GetCurrentFrame( self ):
|
||||
|
||||
if self._cv_mode:
|
||||
|
||||
( retval, numpy_image ) = self._cv_video.read()
|
||||
|
||||
if not retval:
|
||||
|
||||
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
|
||||
|
||||
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + str( self._next_render_index - 1 ) + '.' )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
if self._pil_image.mode == 'P' and 'transparency' in self._pil_image.info:
|
||||
|
||||
# The gif problems seem to be here.
|
||||
# I think that while some transparent animated gifs expect their frames to be pasted over each other, the others expect them to be fresh every time.
|
||||
# Determining which is which doesn't seem to be available in PIL, and PIL's internal calculations seem to not be 100% correct.
|
||||
# Just letting PIL try to do it on its own with P rather than converting to RGBA sometimes produces artifacts
|
||||
|
||||
current_frame = self._pil_image.convert( 'RGBA' )
|
||||
|
||||
if self._pil_canvas is None: self._pil_canvas = current_frame
|
||||
else: self._pil_canvas.paste( current_frame, None, current_frame ) # use the rgba image as its own mask
|
||||
|
||||
else: self._pil_canvas = self._pil_image
|
||||
|
||||
numpy_image = ClientImageHandling.GenerateNumPyImageFromPILImage( self._pil_canvas )
|
||||
|
||||
|
||||
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
|
||||
|
||||
if self._next_render_index == 0:
|
||||
|
||||
self._RewindGIF()
|
||||
|
||||
else:
|
||||
|
||||
if not self._cv_mode:
|
||||
|
||||
self._pil_image.seek( self._next_render_index )
|
||||
|
||||
if self._pil_global_palette is not None and self._pil_image.palette == self._pil_global_palette: # for some reason, when pil falls back from local palette to global palette, a bunch of important variables reset!
|
||||
|
||||
self._pil_image.palette.dirty = self._pil_dirty
|
||||
self._pil_image.palette.mode = self._pil_mode
|
||||
self._pil_image.palette.rawmode = self._pil_rawmode
|
||||
|
||||
|
||||
|
||||
|
||||
return numpy_image
|
||||
|
||||
|
||||
def _InitialiseCV( self ):
|
||||
|
||||
self._cv_mode = True
|
||||
|
||||
self._cv_video = cv2.VideoCapture( self._path )
|
||||
|
||||
self._cv_video.set( CAP_PROP_CONVERT_RGB, True )
|
||||
|
||||
self._next_render_index = 0
|
||||
self._last_frame = None
|
||||
|
||||
|
||||
def _InitialisePIL( self ):
|
||||
|
||||
self._cv_mode = False
|
||||
|
||||
self._pil_image = HydrusImageHandling.GeneratePILImage( self._path )
|
||||
|
||||
self._pil_canvas = None
|
||||
|
||||
self._pil_global_palette = self._pil_image.palette
|
||||
|
||||
if self._pil_global_palette is not None:
|
||||
|
||||
self._pil_dirty = self._pil_image.palette.dirty
|
||||
self._pil_mode = self._pil_image.palette.mode
|
||||
self._pil_rawmode = self._pil_image.palette.rawmode
|
||||
|
||||
|
||||
self._next_render_index = 0
|
||||
self._last_frame = None
|
||||
|
||||
# believe it or not, doing this actually fixed a couple of gifs!
|
||||
self._pil_image.seek( 1 )
|
||||
self._pil_image.seek( 0 )
|
||||
|
||||
|
||||
def _RenderCurrentFrame( self ):
|
||||
|
||||
if self._cv_mode:
|
||||
|
||||
try:
|
||||
|
||||
numpy_image = self._GetCurrentFrame()
|
||||
|
||||
numpy_image = ClientImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
|
||||
|
||||
numpy_image = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2RGB )
|
||||
|
||||
except HydrusExceptions.CantRenderWithCVException:
|
||||
|
||||
if self._last_frame is None:
|
||||
|
||||
self._InitialisePIL()
|
||||
|
||||
numpy_image = self._RenderCurrentFrame()
|
||||
|
||||
else: numpy_image = self._last_frame
|
||||
|
||||
|
||||
else:
|
||||
|
||||
numpy_image = self._GetCurrentFrame()
|
||||
|
||||
numpy_image = ClientImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
|
||||
|
||||
|
||||
self._last_frame = numpy_image
|
||||
|
||||
return numpy_image
|
||||
|
||||
|
||||
def _RewindGIF( self ):
|
||||
|
||||
if self._cv_mode:
|
||||
|
||||
self._cv_video.release()
|
||||
self._cv_video.open( self._path )
|
||||
|
||||
#self._cv_video.set( CAP_PROP_POS_FRAMES, 0.0 )
|
||||
|
||||
else:
|
||||
|
||||
self._pil_image.seek( 0 )
|
||||
|
||||
|
||||
self._next_render_index = 0
|
||||
|
||||
|
||||
def read_frame( self ): return self._RenderCurrentFrame()
|
||||
|
||||
def set_position( self, index ):
|
||||
|
||||
if index == self._next_render_index: return
|
||||
elif index < self._next_render_index: self._RewindGIF()
|
||||
|
||||
while self._next_render_index < index: self._GetCurrentFrame()
|
||||
|
||||
#self._cv_video.set( CV_CAP_PROP_POS_FRAMES, index )
|
||||
|
||||
|
|
@ -27,7 +27,6 @@ CLIENT_FILES_DIR = os.path.join( DB_DIR, 'client_files' )
|
|||
SERVER_FILES_DIR = os.path.join( DB_DIR, 'server_files' )
|
||||
CLIENT_THUMBNAILS_DIR = os.path.join( DB_DIR, 'client_thumbnails' )
|
||||
SERVER_THUMBNAILS_DIR = os.path.join( DB_DIR, 'server_thumbnails' )
|
||||
SERVER_MESSAGES_DIR = os.path.join( DB_DIR, 'server_messages' )
|
||||
CLIENT_UPDATES_DIR = os.path.join( DB_DIR, 'client_updates' )
|
||||
SERVER_UPDATES_DIR = os.path.join( DB_DIR, 'server_updates' )
|
||||
LOGS_DIR = os.path.join( BASE_DIR, 'logs' )
|
||||
|
@ -52,7 +51,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 17
|
||||
SOFTWARE_VERSION = 181
|
||||
SOFTWARE_VERSION = 182
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ class HydrusDB( object ):
|
|||
|
||||
def _AnalyzeAfterUpdate( self ):
|
||||
|
||||
print( 'Analyzing db after update...' )
|
||||
HydrusData.Print( 'Analyzing db after update...' )
|
||||
|
||||
self._c.execute( 'ANALYZE' )
|
||||
|
||||
|
@ -218,7 +218,7 @@ class HydrusDB( object ):
|
|||
|
||||
def _ReportStatus( self, text ):
|
||||
|
||||
print( text )
|
||||
HydrusData.Print( text )
|
||||
|
||||
|
||||
def _UpdateDB( self, version ):
|
||||
|
|
|
@ -169,7 +169,21 @@ def ConvertIntToPixels( i ):
|
|||
|
||||
def ConvertIntToPrettyString( num ):
|
||||
|
||||
return locale.format( u'%d', num, grouping = True )
|
||||
# don't feed this a unicode string u'%d'--locale can't handle it
|
||||
text = locale.format( '%d', num, grouping = True )
|
||||
|
||||
try:
|
||||
|
||||
text = text.decode( locale.getpreferredencoding() )
|
||||
|
||||
text = ToUnicode( text )
|
||||
|
||||
except:
|
||||
|
||||
text = ToUnicode( text )
|
||||
|
||||
|
||||
return text
|
||||
|
||||
def ConvertIntToUnit( unit ):
|
||||
|
||||
|
@ -255,6 +269,31 @@ def ConvertPrettyStringsToUglyNamespaces( pretty_strings ):
|
|||
|
||||
return result
|
||||
|
||||
def ConvertTimeDeltaToPrettyString( seconds ):
|
||||
|
||||
if seconds > 1:
|
||||
|
||||
result = '%.1f' % seconds + ' seconds'
|
||||
|
||||
elif seconds > 0.1:
|
||||
|
||||
result = '%d' % ( seconds * 1000 ) + ' milliseconds'
|
||||
|
||||
elif seconds > 0.01:
|
||||
|
||||
result = '%.1f' % ( seconds * 1000 ) + ' milliseconds'
|
||||
|
||||
elif seconds > 0.001:
|
||||
|
||||
result = '%.2f' % ( seconds * 1000 ) + ' milliseconds'
|
||||
|
||||
else:
|
||||
|
||||
result = '%d' % ( seconds * 1000000 ) + ' microseconds'
|
||||
|
||||
|
||||
return result
|
||||
|
||||
def ConvertStatusToPrefix( status ):
|
||||
|
||||
if status == HC.CURRENT: return ''
|
||||
|
@ -526,20 +565,23 @@ def ConvertValueRangeToPrettyString( value, range ):
|
|||
|
||||
def DebugPrint( debug_info ):
|
||||
|
||||
print( debug_info )
|
||||
Print( debug_info )
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
def DeletePath( path ):
|
||||
|
||||
if os.path.isdir( path ):
|
||||
if os.path.exists( path ):
|
||||
|
||||
shutil.rmtree( path )
|
||||
|
||||
else:
|
||||
|
||||
os.remove( path )
|
||||
if os.path.isdir( path ):
|
||||
|
||||
shutil.rmtree( path )
|
||||
|
||||
else:
|
||||
|
||||
os.remove( path )
|
||||
|
||||
|
||||
|
||||
def DeserialisePrettyTags( text ):
|
||||
|
@ -732,6 +774,10 @@ def MergeKeyToListDicts( key_to_list_dicts ):
|
|||
|
||||
return result
|
||||
|
||||
def Print( text ):
|
||||
|
||||
print( ToByteString( text ) )
|
||||
|
||||
def RecordRunningStart( instance ):
|
||||
|
||||
path = os.path.join( HC.BASE_DIR, instance + '_running' )
|
||||
|
@ -753,7 +799,7 @@ def RecordRunningStart( instance ):
|
|||
|
||||
with open( path, 'wb' ) as f:
|
||||
|
||||
f.write( record_string )
|
||||
f.write( ToByteString( record_string ) )
|
||||
|
||||
|
||||
def RecyclePath( path ):
|
||||
|
@ -772,7 +818,7 @@ def RecyclePath( path ):
|
|||
|
||||
except:
|
||||
|
||||
print( 'Trying to prepare a file for recycling created this error:' )
|
||||
Print( 'Trying to prepare a file for recycling created this error:' )
|
||||
traceback.print_exc()
|
||||
|
||||
return
|
||||
|
@ -780,18 +826,21 @@ def RecyclePath( path ):
|
|||
|
||||
|
||||
|
||||
try:
|
||||
if os.path.exists( path ):
|
||||
|
||||
send2trash.send2trash( path )
|
||||
|
||||
except:
|
||||
|
||||
print( 'Trying to recycle a file created this error:' )
|
||||
traceback.print_exc()
|
||||
|
||||
print( 'It has been fully deleted instead.' )
|
||||
|
||||
DeletePath( original_path )
|
||||
try:
|
||||
|
||||
send2trash.send2trash( path )
|
||||
|
||||
except:
|
||||
|
||||
Print( 'Trying to recycle a file created this error:' )
|
||||
traceback.print_exc()
|
||||
|
||||
Print( 'It has been fully deleted instead.' )
|
||||
|
||||
DeletePath( original_path )
|
||||
|
||||
|
||||
|
||||
def ShowExceptionDefault( e ):
|
||||
|
@ -811,24 +860,16 @@ def ShowExceptionDefault( e ):
|
|||
|
||||
message = ToUnicode( etype.__name__ ) + ': ' + ToUnicode( value ) + os.linesep + ToUnicode( trace )
|
||||
|
||||
print( '' )
|
||||
print( 'The following exception occured at ' + ConvertTimestampToPrettyTime( GetNow() ) + ':' )
|
||||
Print( '' )
|
||||
Print( 'Exception:' )
|
||||
|
||||
try: print( message )
|
||||
except: print( repr( message ) )
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
DebugPrint( message )
|
||||
|
||||
time.sleep( 1 )
|
||||
|
||||
ShowException = ShowExceptionDefault
|
||||
|
||||
def ShowTextDefault( text ):
|
||||
|
||||
print( text )
|
||||
|
||||
ShowText = ShowTextDefault
|
||||
ShowText = Print
|
||||
|
||||
def SplayListForDB( xs ): return '(' + ','.join( ( str( x ) for x in xs ) ) + ')'
|
||||
|
||||
|
@ -902,7 +943,7 @@ def ToUnicode( text_producing_object ):
|
|||
|
||||
except:
|
||||
|
||||
pass
|
||||
text = repr( text ).decode( 'utf-8' )
|
||||
|
||||
|
||||
|
||||
|
@ -1538,7 +1579,15 @@ class ContentUpdate( object ):
|
|||
return hashes
|
||||
|
||||
|
||||
def ToTuple( self ): return ( self._data_type, self._action, self._row )
|
||||
def IsInboxRelated( self ):
|
||||
|
||||
return self._action in ( HC.CONTENT_UPDATE_ARCHIVE, HC.CONTENT_UPDATE_INBOX )
|
||||
|
||||
|
||||
def ToTuple( self ):
|
||||
|
||||
return ( self._data_type, self._action, self._row )
|
||||
|
||||
|
||||
class EditLogAction( object ):
|
||||
|
||||
|
|
|
@ -130,7 +130,10 @@ def EncryptAESFile( path, preface = '' ):
|
|||
|
||||
aes_key_text = AESKeyToText( aes_key, iv )
|
||||
|
||||
with open( path + '.key', 'wb' ) as f: f.write( aes_key_text )
|
||||
with open( path + '.key', 'wb' ) as f:
|
||||
|
||||
f.write( aes_key_text )
|
||||
|
||||
|
||||
def EncryptPKCS( public_key, message ):
|
||||
|
||||
|
|
|
@ -189,13 +189,12 @@ def GetMime( path ):
|
|||
|
||||
if mime == HC.UNDETERMINED_WM:
|
||||
|
||||
try:
|
||||
|
||||
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetCVVideoProperties( path )
|
||||
if HydrusVideoHandling.HasVideoStream( path ):
|
||||
|
||||
return HC.VIDEO_WMV
|
||||
|
||||
except: pass # we'll catch wma later
|
||||
|
||||
# we'll catch and verify wma later
|
||||
|
||||
else: return mime
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import cStringIO
|
||||
import numpy.core.multiarray # important this comes before cv!
|
||||
import cv2
|
||||
import HydrusConstants as HC
|
||||
import HydrusExceptions
|
||||
import HydrusThreading
|
||||
import lz4
|
||||
import numpy
|
||||
import os
|
||||
from PIL import _imaging
|
||||
from PIL import Image as PILImage
|
||||
|
@ -18,14 +15,6 @@ import HydrusData
|
|||
import HydrusGlobals
|
||||
import HydrusPaths
|
||||
|
||||
if cv2.__version__.startswith( '2' ):
|
||||
|
||||
IMREAD_UNCHANGED = cv2.CV_LOAD_IMAGE_UNCHANGED
|
||||
|
||||
else:
|
||||
|
||||
IMREAD_UNCHANGED = cv2.IMREAD_UNCHANGED
|
||||
|
||||
def ConvertToPngIfBmp( path ):
|
||||
|
||||
with open( path, 'rb' ) as f: header = f.read( 2 )
|
||||
|
@ -54,19 +43,6 @@ def ConvertToPngIfBmp( path ):
|
|||
|
||||
|
||||
|
||||
def EfficientlyResizeNumpyImage( numpy_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_y, im_x, depth ) = numpy_image.shape
|
||||
|
||||
if target_x >= im_x and target_y >= im_y: return numpy_image
|
||||
|
||||
result = numpy_image
|
||||
|
||||
# this seems to slow things down a lot, at least for cv!
|
||||
#if im_x > 2 * target_x and im_y > 2 * target_y: result = cv2.resize( numpy_image, ( 2 * target_x, 2 * target_y ), interpolation = cv2.INTER_NEAREST )
|
||||
|
||||
return cv2.resize( result, ( target_x, target_y ), interpolation = cv2.INTER_LINEAR )
|
||||
|
||||
def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_x, im_y ) = pil_image.size
|
||||
|
@ -80,16 +56,6 @@ def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ):
|
|||
|
||||
return pil_image.resize( ( target_x, target_y ), PILImage.ANTIALIAS )
|
||||
|
||||
def EfficientlyThumbnailNumpyImage( numpy_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_y, im_x, depth ) = numpy_image.shape
|
||||
|
||||
if target_x >= im_x and target_y >= im_y: return numpy_image
|
||||
|
||||
( target_x, target_y ) = GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) )
|
||||
|
||||
return cv2.resize( numpy_image, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
|
||||
|
||||
def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_x, im_y ) = pil_image.size
|
||||
|
@ -101,132 +67,6 @@ def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
|
|||
|
||||
pil_image.thumbnail( ( target_x, target_y ), PILImage.ANTIALIAS )
|
||||
|
||||
def GenerateNumpyImage( path ):
|
||||
|
||||
numpy_image = cv2.imread( path, flags = -1 ) # flags = -1 loads alpha channel, if present
|
||||
|
||||
( width, height, depth ) = numpy_image.shape
|
||||
|
||||
if width * height * depth != len( numpy_image.data ): raise Exception( 'CV could not understand this image; it was probably an unusual png!' )
|
||||
|
||||
if depth == 4: raise Exception( 'CV is bad at alpha!' )
|
||||
else: numpy_image = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2RGB )
|
||||
|
||||
return numpy_image
|
||||
|
||||
def GenerateNumPyImageFromPILImage( pil_image ):
|
||||
|
||||
if pil_image.mode == 'RGBA' or ( pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ) ):
|
||||
|
||||
if pil_image.mode == 'P': pil_image = pil_image.convert( 'RGBA' )
|
||||
|
||||
else:
|
||||
|
||||
if pil_image.mode != 'RGB': pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
|
||||
( w, h ) = pil_image.size
|
||||
|
||||
s = pil_image.tobytes()
|
||||
|
||||
return numpy.fromstring( s, dtype = 'uint8' ).reshape( ( h, w, len( s ) // ( w * h ) ) )
|
||||
|
||||
def GeneratePerceptualHash( path ):
|
||||
|
||||
numpy_image = cv2.imread( path, IMREAD_UNCHANGED )
|
||||
|
||||
( y, x, depth ) = numpy_image.shape
|
||||
|
||||
if depth == 4:
|
||||
|
||||
# create a white greyscale canvas
|
||||
|
||||
white = numpy.ones( ( x, y ) ) * 255
|
||||
|
||||
# create weight and transform numpy_image to greyscale
|
||||
|
||||
numpy_alpha = numpy_image[ :, :, 3 ]
|
||||
|
||||
numpy_image_bgr = numpy_image[ :, :, :3 ]
|
||||
|
||||
numpy_image_gray = cv2.cvtColor( numpy_image_bgr, cv2.COLOR_BGR2GRAY )
|
||||
|
||||
numpy_image_result = numpy.empty( ( y, x ), numpy.float32 )
|
||||
|
||||
# paste greyscale onto the white
|
||||
|
||||
# can't think of a better way to do this!
|
||||
# cv2.addWeighted only takes a scalar for weight!
|
||||
for i in range( y ):
|
||||
|
||||
for j in range( x ):
|
||||
|
||||
opacity = float( numpy_alpha[ i, j ] ) / 255.0
|
||||
|
||||
grey_part = numpy_image_gray[ i, j ] * opacity
|
||||
white_part = 255 * ( 1 - opacity )
|
||||
|
||||
pixel = grey_part + white_part
|
||||
|
||||
numpy_image_result[ i, j ] = pixel
|
||||
|
||||
|
||||
|
||||
numpy_image_gray = numpy_image_result
|
||||
|
||||
# use 255 for white weight, alpha for image weight
|
||||
|
||||
else:
|
||||
|
||||
numpy_image_gray = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2GRAY )
|
||||
|
||||
|
||||
numpy_image_tiny = cv2.resize( numpy_image_gray, ( 32, 32 ), interpolation = cv2.INTER_AREA )
|
||||
|
||||
# convert to float and calc dct
|
||||
|
||||
numpy_image_tiny_float = numpy.float32( numpy_image_tiny )
|
||||
|
||||
dct = cv2.dct( numpy_image_tiny_float )
|
||||
|
||||
# take top left 8x8 of dct
|
||||
|
||||
dct_88 = dct[:8,:8]
|
||||
|
||||
# get mean of dct, excluding [0,0]
|
||||
|
||||
mask = numpy.ones( ( 8, 8 ) )
|
||||
|
||||
mask[0,0] = 0
|
||||
|
||||
average = numpy.average( dct_88, weights = mask )
|
||||
|
||||
# make a monochromatic, 64-bit hash of whether the entry is above or below the mean
|
||||
|
||||
bytes = []
|
||||
|
||||
for i in range( 8 ):
|
||||
|
||||
byte = 0
|
||||
|
||||
for j in range( 8 ):
|
||||
|
||||
byte <<= 1 # shift byte one left
|
||||
|
||||
value = dct_88[i,j]
|
||||
|
||||
if value > average: byte |= 1
|
||||
|
||||
|
||||
bytes.append( byte )
|
||||
|
||||
|
||||
answer = str( bytearray( bytes ) )
|
||||
|
||||
# we good
|
||||
|
||||
return answer
|
||||
|
||||
def GeneratePILImage( path ):
|
||||
|
||||
pil_image = PILImage.open( path )
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import HydrusConstants as HC
|
||||
import HydrusData
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
# I am having unreliable problems with stdout on Windows when I launch client.pyw with pythonw.exe, hence the except IOError business
|
||||
# I guess I am sending bad characters or something to the 'windowised' environment of pythonw
|
||||
class HydrusLogger( object ):
|
||||
|
||||
def __init__( self, log_filename ):
|
||||
|
@ -15,6 +18,8 @@ class HydrusLogger( object ):
|
|||
self._previous_sys_stdout = sys.stdout
|
||||
self._previous_sys_stderr = sys.stderr
|
||||
|
||||
self._problem_with_previous_stdout = False
|
||||
|
||||
sys.stdout = self
|
||||
sys.stderr = self
|
||||
|
||||
|
@ -35,7 +40,18 @@ class HydrusLogger( object ):
|
|||
|
||||
def flush( self ):
|
||||
|
||||
self._previous_sys_stdout.flush()
|
||||
if not self._problem_with_previous_stdout:
|
||||
|
||||
try:
|
||||
|
||||
self._previous_sys_stdout.flush()
|
||||
|
||||
except IOError:
|
||||
|
||||
self._problem_with_previous_stdout = True
|
||||
|
||||
|
||||
|
||||
self._log_file.flush()
|
||||
|
||||
|
||||
|
@ -50,9 +66,20 @@ class HydrusLogger( object ):
|
|||
prefix = time.strftime( '%Y/%m/%d %H:%M:%S: ', time.localtime() )
|
||||
|
||||
|
||||
message = prefix + value
|
||||
message = HydrusData.ToByteString( prefix + value )
|
||||
|
||||
if not self._problem_with_previous_stdout:
|
||||
|
||||
try:
|
||||
|
||||
self._previous_sys_stdout.write( message )
|
||||
|
||||
except IOError:
|
||||
|
||||
self._problem_with_previous_stdout = True
|
||||
|
||||
|
||||
|
||||
self._previous_sys_stdout.write( message )
|
||||
self._log_file.write( message )
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ def GetUPnPMappings():
|
|||
|
||||
except Exception as e:
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
raise Exception( 'Problem while trying to parse UPnP mappings:' + os.linesep * 2 + HydrusData.ToUnicode( e ) )
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ def CleanUpTempPath( os_file_handle, temp_path ):
|
|||
|
||||
except OSError:
|
||||
|
||||
print( 'Could not close the temporary file ' + temp_path )
|
||||
HydrusData.Print( 'Could not close the temporary file ' + temp_path )
|
||||
|
||||
return
|
||||
|
||||
|
@ -42,7 +42,7 @@ def CleanUpTempPath( os_file_handle, temp_path ):
|
|||
|
||||
except OSError:
|
||||
|
||||
print( 'Could not delete the temporary file ' + temp_path )
|
||||
HydrusData.Print( 'Could not delete the temporary file ' + temp_path )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import traceback
|
|||
from twisted.web.server import Request, Site
|
||||
from twisted.web.resource import Resource
|
||||
import HydrusData
|
||||
import time
|
||||
|
||||
eris = '''<html><head><title>hydrus</title></head><body><pre>
|
||||
<font color="red">8888 8888888</font>
|
||||
|
@ -199,12 +200,31 @@ class HydrusRequest( Request ):
|
|||
|
||||
Request.__init__( self, *args, **kwargs )
|
||||
|
||||
self.start_time = time.clock()
|
||||
self.is_hydrus_client = True
|
||||
self.hydrus_args = None
|
||||
self.hydrus_response_context = None
|
||||
self.hydrus_request_data_usage = 0
|
||||
|
||||
|
||||
def finish( self ):
|
||||
|
||||
Request.finish( self )
|
||||
|
||||
host = self.getHost()
|
||||
|
||||
status_text = '200'
|
||||
|
||||
if self.hydrus_response_context is not None:
|
||||
|
||||
status_text = HydrusData.ToUnicode( self.hydrus_response_context.GetStatusCode() )
|
||||
|
||||
|
||||
message = str( host.port ) + ' ' + HydrusData.ToUnicode( self.method ) + ' ' + HydrusData.ToUnicode( self.path ) + ' ' + status_text + ' in ' + HydrusData.ConvertTimeDeltaToPrettyString( time.clock() - self.start_time )
|
||||
|
||||
HydrusData.Print( message )
|
||||
|
||||
|
||||
class HydrusRequestRestricted( HydrusRequest ):
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
|
|
|
@ -61,7 +61,7 @@ class HydrusAMP( amp.AMP ):
|
|||
|
||||
def _errbackHandleError( self, failure ):
|
||||
|
||||
print( failure.getTraceback() )
|
||||
HydrusData.Print( failure.getTraceback() )
|
||||
|
||||
normal_errors = []
|
||||
|
||||
|
|
|
@ -358,7 +358,10 @@ class HydrusResourceCommand( Resource ):
|
|||
|
||||
self._recordDataUsage( request )
|
||||
|
||||
if do_finish: request.finish()
|
||||
if do_finish:
|
||||
|
||||
request.finish()
|
||||
|
||||
|
||||
|
||||
def _callbackDoGETJob( self, request ):
|
||||
|
@ -440,7 +443,7 @@ class HydrusResourceCommand( Resource ):
|
|||
try: self._CleanUpTempFile( request )
|
||||
except: pass
|
||||
|
||||
try: print( failure.getTraceback() )
|
||||
try: HydrusData.DebugPrint( failure.getTraceback() )
|
||||
except: pass
|
||||
|
||||
try: request.write( failure.getTraceback() )
|
||||
|
@ -482,7 +485,7 @@ class HydrusResourceCommand( Resource ):
|
|||
elif failure.type == HydrusExceptions.SessionException: response_context = ResponseContext( 419, mime = default_mime, body = default_encoding( failure.value ) )
|
||||
else:
|
||||
|
||||
print( failure.getTraceback() )
|
||||
HydrusData.DebugPrint( failure.getTraceback() )
|
||||
|
||||
response_context = ResponseContext( 500, mime = default_mime, body = default_encoding( 'The repository encountered an error it could not handle! Here is a dump of what happened, which will also be written to your client.log file. If it persists, please forward it to hydrus.admin@gmail.com:' + os.linesep * 2 + failure.getTraceback() ) )
|
||||
|
||||
|
|
|
@ -75,60 +75,6 @@ class HydrusMessagingSessionManagerServer( object ):
|
|||
return session_key
|
||||
|
||||
|
||||
class HydrusSessionManagerClient( object ):
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
existing_sessions = HydrusGlobals.controller.Read( 'hydrus_sessions' )
|
||||
|
||||
self._service_keys_to_sessions = { service_key : ( session_key, expires ) for ( service_key, session_key, expires ) in existing_sessions }
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
|
||||
def DeleteSessionKey( self, service_key ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
HydrusGlobals.controller.Write( 'delete_hydrus_session_key', service_key )
|
||||
|
||||
if service_key in self._service_keys_to_sessions: del self._service_keys_to_sessions[ service_key ]
|
||||
|
||||
|
||||
|
||||
def GetSessionKey( self, service_key ):
|
||||
|
||||
now = HydrusData.GetNow()
|
||||
|
||||
with self._lock:
|
||||
|
||||
if service_key in self._service_keys_to_sessions:
|
||||
|
||||
( session_key, expires ) = self._service_keys_to_sessions[ service_key ]
|
||||
|
||||
if now + 600 > expires: del self._service_keys_to_sessions[ service_key ]
|
||||
else: return session_key
|
||||
|
||||
|
||||
# session key expired or not found
|
||||
|
||||
service = HydrusGlobals.controller.GetServicesManager().GetService( service_key )
|
||||
|
||||
( response_gumpf, cookies ) = service.Request( HC.GET, 'session_key', return_cookies = True )
|
||||
|
||||
try: session_key = cookies[ 'session_key' ].decode( 'hex' )
|
||||
except: raise Exception( 'Service did not return a session key!' )
|
||||
|
||||
expires = now + HYDRUS_SESSION_LIFETIME
|
||||
|
||||
self._service_keys_to_sessions[ service_key ] = ( session_key, expires )
|
||||
|
||||
HydrusGlobals.controller.Write( 'hydrus_session', service_key, session_key, expires )
|
||||
|
||||
return session_key
|
||||
|
||||
|
||||
|
||||
class HydrusSessionManagerServer( object ):
|
||||
|
||||
def __init__( self ):
|
||||
|
|
|
@ -129,7 +129,7 @@ def CleanTag( tag ):
|
|||
|
||||
except Exception as e:
|
||||
|
||||
text = 'Was unable to parse the tag: ' + repr( tag )
|
||||
text = 'Was unable to parse the tag: ' + HydrusData.ToUnicode( tag )
|
||||
text += os.linesep * 2
|
||||
text += str( e )
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#import numpy.core.multiarray # important this comes before cv!
|
||||
import cv2
|
||||
from flvlib import tags as flv_tags
|
||||
import HydrusConstants as HC
|
||||
import HydrusData
|
||||
|
@ -17,47 +15,23 @@ import traceback
|
|||
import threading
|
||||
import time
|
||||
|
||||
if HC.PLATFORM_LINUX: FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg' )
|
||||
elif HC.PLATFORM_OSX: FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg' )
|
||||
elif HC.PLATFORM_WINDOWS: FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg.exe' )
|
||||
|
||||
if cv2.__version__.startswith( '2' ):
|
||||
if HC.PLATFORM_LINUX or HC.PLATFORM_OSX:
|
||||
|
||||
CAP_PROP_FRAME_COUNT = cv2.cv.CV_CAP_PROP_FRAME_COUNT
|
||||
CAP_PROP_FPS = cv2.cv.CV_CAP_PROP_FPS
|
||||
CAP_PROP_FRAME_WIDTH = cv2.cv.CV_CAP_PROP_FRAME_WIDTH
|
||||
CAP_PROP_FRAME_HEIGHT = cv2.cv.CV_CAP_PROP_FRAME_HEIGHT
|
||||
CAP_PROP_CONVERT_RGB = cv2.cv.CV_CAP_PROP_CONVERT_RGB
|
||||
CAP_PROP_POS_FRAMES = cv2.cv.CV_CAP_PROP_POS_FRAMES
|
||||
FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg' )
|
||||
|
||||
else:
|
||||
if not os.path.exists( FFMPEG_PATH ):
|
||||
|
||||
FFMPEG_PATH = 'ffmpeg'
|
||||
|
||||
|
||||
CAP_PROP_FRAME_COUNT = cv2.CAP_PROP_FRAME_COUNT
|
||||
CAP_PROP_FPS = cv2.CAP_PROP_FPS
|
||||
CAP_PROP_FRAME_WIDTH = cv2.CAP_PROP_FRAME_WIDTH
|
||||
CAP_PROP_FRAME_HEIGHT = cv2.CAP_PROP_FRAME_HEIGHT
|
||||
CAP_PROP_CONVERT_RGB = cv2.CAP_PROP_CONVERT_RGB
|
||||
CAP_PROP_POS_FRAMES = cv2.CAP_PROP_POS_FRAMES
|
||||
elif HC.PLATFORM_WINDOWS:
|
||||
|
||||
def GetCVVideoProperties( path ):
|
||||
FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg.exe' )
|
||||
|
||||
capture = cv2.VideoCapture( path )
|
||||
|
||||
num_frames = int( capture.get( CAP_PROP_FRAME_COUNT ) )
|
||||
|
||||
fps = capture.get( CAP_PROP_FPS )
|
||||
|
||||
length_in_seconds = num_frames / fps
|
||||
|
||||
length_in_ms = int( length_in_seconds * 1000 )
|
||||
|
||||
duration = length_in_ms
|
||||
|
||||
width = int( capture.get( CAP_PROP_FRAME_WIDTH ) )
|
||||
|
||||
height = int( capture.get( CAP_PROP_FRAME_HEIGHT ) )
|
||||
|
||||
return ( ( width, height ), duration, num_frames )
|
||||
if not os.path.exists( FFMPEG_PATH ):
|
||||
|
||||
FFMPEG_PATH = 'ffmpeg.exe'
|
||||
|
||||
|
||||
def GetFFMPEGVideoProperties( path ):
|
||||
|
||||
|
@ -112,16 +86,6 @@ def GetFLVProperties( path ):
|
|||
return ( ( width, height ), duration, num_frames )
|
||||
|
||||
|
||||
def GetVideoFrameDuration( path ):
|
||||
|
||||
cv_video = cv2.VideoCapture( path )
|
||||
|
||||
fps = cv_video.get( CAP_PROP_FPS )
|
||||
|
||||
if fps == 0: raise HydrusExceptions.CantRenderWithCVException()
|
||||
|
||||
return 1000.0 / fps
|
||||
|
||||
def GetMatroskaOrWebm( path ):
|
||||
|
||||
tags = matroska.parse( path )
|
||||
|
@ -162,6 +126,12 @@ def GetMatroskaOrWebMProperties( path ):
|
|||
|
||||
return ( ( width, height ), duration, num_frames )
|
||||
|
||||
def HasVideoStream( path ):
|
||||
|
||||
info = Hydrusffmpeg_parse_infos( path )
|
||||
|
||||
return info[ 'video_found' ]
|
||||
|
||||
# this is cribbed from moviepy
|
||||
def Hydrusffmpeg_parse_infos(filename, print_infos=False):
|
||||
"""Get file infos using ffmpeg.
|
||||
|
@ -195,7 +165,7 @@ def Hydrusffmpeg_parse_infos(filename, print_infos=False):
|
|||
|
||||
if print_infos:
|
||||
# print the whole info text returned by FFMPEG
|
||||
print( infos )
|
||||
HydrusData.Print( infos )
|
||||
|
||||
lines = infos.splitlines()
|
||||
if "No such file or directory" in lines[-1]:
|
||||
|
@ -284,7 +254,7 @@ def Hydrusffmpeg_parse_infos(filename, print_infos=False):
|
|||
|
||||
# This was built from moviepy's FFMPEG_VideoReader
|
||||
class VideoRendererFFMPEG( object ):
|
||||
|
||||
|
||||
def __init__( self, path, mime, duration, num_frames, target_resolution, pix_fmt = "rgb24" ):
|
||||
|
||||
self._path = path
|
||||
|
@ -423,180 +393,4 @@ class VideoRendererFFMPEG( object ):
|
|||
if rewind or jump_a_long_way_ahead: self.initialize( pos )
|
||||
else: self.skip_frames( pos - self.pos )
|
||||
|
||||
|
||||
# the cv code was initially written by @fluffy_cub
|
||||
class GIFRenderer( object ):
|
||||
|
||||
def __init__( self, path, num_frames, target_resolution ):
|
||||
|
||||
self._path = path
|
||||
self._num_frames = num_frames
|
||||
self._target_resolution = target_resolution
|
||||
|
||||
if cv2.__version__.startswith( '2' ):
|
||||
|
||||
self._InitialisePIL()
|
||||
|
||||
else:
|
||||
|
||||
self._InitialiseCV()
|
||||
|
||||
|
||||
|
||||
def _GetCurrentFrame( self ):
|
||||
|
||||
if self._cv_mode:
|
||||
|
||||
( retval, numpy_image ) = self._cv_video.read()
|
||||
|
||||
if not retval:
|
||||
|
||||
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
|
||||
|
||||
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + str( self._next_render_index - 1 ) + '.' )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
if self._pil_image.mode == 'P' and 'transparency' in self._pil_image.info:
|
||||
|
||||
# The gif problems seem to be here.
|
||||
# I think that while some transparent animated gifs expect their frames to be pasted over each other, the others expect them to be fresh every time.
|
||||
# Determining which is which doesn't seem to be available in PIL, and PIL's internal calculations seem to not be 100% correct.
|
||||
# Just letting PIL try to do it on its own with P rather than converting to RGBA sometimes produces artifacts
|
||||
|
||||
current_frame = self._pil_image.convert( 'RGBA' )
|
||||
|
||||
if self._pil_canvas is None: self._pil_canvas = current_frame
|
||||
else: self._pil_canvas.paste( current_frame, None, current_frame ) # use the rgba image as its own mask
|
||||
|
||||
else: self._pil_canvas = self._pil_image
|
||||
|
||||
numpy_image = HydrusImageHandling.GenerateNumPyImageFromPILImage( self._pil_canvas )
|
||||
|
||||
|
||||
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
|
||||
|
||||
if self._next_render_index == 0:
|
||||
|
||||
self._RewindGIF()
|
||||
|
||||
else:
|
||||
|
||||
if not self._cv_mode:
|
||||
|
||||
self._pil_image.seek( self._next_render_index )
|
||||
|
||||
if self._pil_global_palette is not None and self._pil_image.palette == self._pil_global_palette: # for some reason, when pil falls back from local palette to global palette, a bunch of important variables reset!
|
||||
|
||||
self._pil_image.palette.dirty = self._pil_dirty
|
||||
self._pil_image.palette.mode = self._pil_mode
|
||||
self._pil_image.palette.rawmode = self._pil_rawmode
|
||||
|
||||
|
||||
|
||||
|
||||
return numpy_image
|
||||
|
||||
|
||||
def _InitialiseCV( self ):
|
||||
|
||||
self._cv_mode = True
|
||||
|
||||
self._cv_video = cv2.VideoCapture( self._path )
|
||||
|
||||
self._cv_video.set( CAP_PROP_CONVERT_RGB, True )
|
||||
|
||||
self._next_render_index = 0
|
||||
self._last_frame = None
|
||||
|
||||
|
||||
def _InitialisePIL( self ):
|
||||
|
||||
self._cv_mode = False
|
||||
|
||||
self._pil_image = HydrusImageHandling.GeneratePILImage( self._path )
|
||||
|
||||
self._pil_canvas = None
|
||||
|
||||
self._pil_global_palette = self._pil_image.palette
|
||||
|
||||
if self._pil_global_palette is not None:
|
||||
|
||||
self._pil_dirty = self._pil_image.palette.dirty
|
||||
self._pil_mode = self._pil_image.palette.mode
|
||||
self._pil_rawmode = self._pil_image.palette.rawmode
|
||||
|
||||
|
||||
self._next_render_index = 0
|
||||
self._last_frame = None
|
||||
|
||||
# believe it or not, doing this actually fixed a couple of gifs!
|
||||
self._pil_image.seek( 1 )
|
||||
self._pil_image.seek( 0 )
|
||||
|
||||
|
||||
def _RenderCurrentFrame( self ):
|
||||
|
||||
if self._cv_mode:
|
||||
|
||||
try:
|
||||
|
||||
numpy_image = self._GetCurrentFrame()
|
||||
|
||||
numpy_image = HydrusImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
|
||||
|
||||
numpy_image = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2RGB )
|
||||
|
||||
except HydrusExceptions.CantRenderWithCVException:
|
||||
|
||||
if self._last_frame is None:
|
||||
|
||||
self._InitialisePIL()
|
||||
|
||||
numpy_image = self._RenderCurrentFrame()
|
||||
|
||||
else: numpy_image = self._last_frame
|
||||
|
||||
|
||||
else:
|
||||
|
||||
numpy_image = self._GetCurrentFrame()
|
||||
|
||||
numpy_image = HydrusImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
|
||||
|
||||
|
||||
self._last_frame = numpy_image
|
||||
|
||||
return numpy_image
|
||||
|
||||
|
||||
def _RewindGIF( self ):
|
||||
|
||||
if self._cv_mode:
|
||||
|
||||
self._cv_video.release()
|
||||
self._cv_video.open( self._path )
|
||||
|
||||
#self._cv_video.set( CAP_PROP_POS_FRAMES, 0.0 )
|
||||
|
||||
else:
|
||||
|
||||
self._pil_image.seek( 0 )
|
||||
|
||||
|
||||
self._next_render_index = 0
|
||||
|
||||
|
||||
def read_frame( self ): return self._RenderCurrentFrame()
|
||||
|
||||
def set_position( self, index ):
|
||||
|
||||
if index == self._next_render_index: return
|
||||
elif index < self._next_render_index: self._RewindGIF()
|
||||
|
||||
while self._next_render_index < index: self._GetCurrentFrame()
|
||||
|
||||
#self._cv_video.set( CV_CAP_PROP_POS_FRAMES, index )
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ def GetStartingAction():
|
|||
|
||||
else:
|
||||
|
||||
print( 'The server is already running. Would you like to [s]top it, or [r]estart it?' )
|
||||
HydrusData.Print( 'The server is already running. Would you like to [s]top it, or [r]estart it?' )
|
||||
|
||||
answer = raw_input()
|
||||
|
||||
|
@ -146,7 +146,7 @@ def ShutdownSiblingInstance():
|
|||
|
||||
port_found = True
|
||||
|
||||
print( 'Sending shut down instruction...' )
|
||||
HydrusData.Print( 'Sending shut down instruction...' )
|
||||
|
||||
connection.request( 'POST', '/shutdown' )
|
||||
|
||||
|
@ -186,7 +186,7 @@ def ShutdownSiblingInstance():
|
|||
raise HydrusExceptions.PermissionException( 'The existing server did not have an administration service!' )
|
||||
|
||||
|
||||
print( 'The existing server is shut down!' )
|
||||
HydrusData.Print( 'The existing server is shut down!' )
|
||||
|
||||
class Controller( HydrusController.HydrusController ):
|
||||
|
||||
|
@ -247,7 +247,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
except Exception as e:
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
|
||||
|
||||
|
@ -293,19 +293,17 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
def Exit( self ):
|
||||
|
||||
print( 'Shutting down daemons and services...' )
|
||||
HydrusData.Print( 'Shutting down daemons and services...' )
|
||||
|
||||
self.ShutdownView()
|
||||
|
||||
print( 'Shutting down db...' )
|
||||
HydrusData.Print( 'Shutting down db...' )
|
||||
|
||||
self.ShutdownModel()
|
||||
|
||||
|
||||
def InitModel( self ):
|
||||
|
||||
print( 'Initialising db...' )
|
||||
|
||||
HydrusController.HydrusController.InitModel( self )
|
||||
|
||||
self._managers[ 'restricted_services_sessions' ] = HydrusSessions.HydrusSessionManagerServer()
|
||||
|
@ -318,8 +316,6 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
def InitView( self ):
|
||||
|
||||
print( 'Initialising daemons and services...' )
|
||||
|
||||
HydrusController.HydrusController.InitView( self )
|
||||
|
||||
self._daemons.append( HydrusThreading.DAEMONQueue( self, 'FlushRequestsMade', ServerDaemons.DAEMONFlushRequestsMade, 'request_made', period = 60 ) )
|
||||
|
@ -345,15 +341,19 @@ class Controller( HydrusController.HydrusController ):
|
|||
self.CallToThread( self.ProcessPubSub )
|
||||
|
||||
|
||||
def Run( self,):
|
||||
def Run( self ):
|
||||
|
||||
HydrusData.RecordRunningStart( 'server' )
|
||||
|
||||
HydrusData.Print( 'Initialising db...' )
|
||||
|
||||
self.InitModel()
|
||||
|
||||
HydrusData.Print( 'Initialising daemons and services...' )
|
||||
|
||||
self.InitView()
|
||||
|
||||
print( 'Server is running. Press Ctrl+C to quit.' )
|
||||
HydrusData.Print( 'Server is running. Press Ctrl+C to quit.' )
|
||||
|
||||
interrupt_received = False
|
||||
|
||||
|
@ -369,7 +369,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
interrupt_received = True
|
||||
|
||||
print( 'Received a keyboard interrupt...' )
|
||||
HydrusData.Print( 'Received a keyboard interrupt...' )
|
||||
|
||||
def do_it():
|
||||
|
||||
|
@ -381,7 +381,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
|
||||
|
||||
print( 'Shutting down controller...' )
|
||||
HydrusData.Print( 'Shutting down controller...' )
|
||||
|
||||
|
||||
def ShutdownView( self ):
|
||||
|
@ -395,7 +395,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
def ShutdownFromServer( self ):
|
||||
|
||||
print( 'Received a server shut down request...' )
|
||||
HydrusData.Print( 'Received a server shut down request...' )
|
||||
|
||||
def do_it():
|
||||
|
||||
|
|
|
@ -167,7 +167,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
thumbnail = file_dict[ 'thumbnail' ]
|
||||
|
||||
with open( thumbnail_dest_path, 'wb' ) as f: f.write( thumbnail )
|
||||
with open( thumbnail_dest_path, 'wb' ) as f:
|
||||
|
||||
f.write( thumbnail )
|
||||
|
||||
|
||||
|
||||
if self._c.execute( 'SELECT 1 FROM files_info WHERE hash_id = ?;', ( hash_id, ) ).fetchone() is None:
|
||||
|
@ -528,14 +531,14 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
def _CreateDB( self ):
|
||||
|
||||
dirs = ( HC.SERVER_FILES_DIR, HC.SERVER_THUMBNAILS_DIR, HC.SERVER_UPDATES_DIR, HC.SERVER_MESSAGES_DIR )
|
||||
dirs = ( HC.SERVER_FILES_DIR, HC.SERVER_THUMBNAILS_DIR, HC.SERVER_UPDATES_DIR )
|
||||
|
||||
for dir in dirs:
|
||||
|
||||
if not os.path.exists( dir ): os.mkdir( dir )
|
||||
|
||||
|
||||
dirs = ( HC.SERVER_FILES_DIR, HC.SERVER_THUMBNAILS_DIR, HC.SERVER_MESSAGES_DIR )
|
||||
dirs = ( HC.SERVER_FILES_DIR, HC.SERVER_THUMBNAILS_DIR )
|
||||
|
||||
hex_chars = '0123456789abcdef'
|
||||
|
||||
|
@ -888,7 +891,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
network_string = content_update_package.DumpToNetworkString()
|
||||
|
||||
with open( path, 'wb' ) as f: f.write( network_string )
|
||||
with open( path, 'wb' ) as f:
|
||||
|
||||
f.write( network_string )
|
||||
|
||||
|
||||
subindex += 1
|
||||
weight = 0
|
||||
|
@ -906,7 +912,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
network_string = content_update_package.DumpToNetworkString()
|
||||
|
||||
with open( path, 'wb' ) as f: f.write( network_string )
|
||||
with open( path, 'wb' ) as f:
|
||||
|
||||
f.write( network_string )
|
||||
|
||||
|
||||
subindex += 1
|
||||
|
||||
|
@ -926,7 +935,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
network_string = service_update_package.DumpToNetworkString()
|
||||
|
||||
with open( path, 'wb' ) as f: f.write( network_string )
|
||||
with open( path, 'wb' ) as f:
|
||||
|
||||
f.write( network_string )
|
||||
|
||||
|
||||
|
||||
def _GetAccessKey( self, registration_key ):
|
||||
|
@ -955,8 +967,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
def _GetAccountFileInfo( self, service_id, account_id ):
|
||||
|
||||
( num_deleted_files, ) = self._c.execute( 'SELECT COUNT( * ) FROM file_petitions WHERE service_id = ? AND account_id = ? AND status = ?;', ( service_id, account_id, HC.DELETED ) ).fetchone()
|
||||
|
||||
( num_files, num_files_bytes ) = self._c.execute( 'SELECT COUNT( * ), SUM( size ) FROM file_map, files_info USING ( hash_id ) WHERE service_id = ? AND account_id = ?;', ( service_id, account_id ) ).fetchone()
|
||||
|
||||
if num_files_bytes is None: num_files_bytes = 0
|
||||
|
@ -970,7 +980,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
account_info = {}
|
||||
|
||||
account_info[ 'num_deleted_files' ] = num_deleted_files
|
||||
account_info[ 'num_files' ] = num_files
|
||||
account_info[ 'num_files_bytes' ] = num_files_bytes
|
||||
account_info[ 'petition_score' ] = petition_score
|
||||
|
@ -1112,8 +1121,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
def _GetAccountMappingInfo( self, service_id, account_id ):
|
||||
|
||||
num_deleted_mappings = len( self._c.execute( 'SELECT COUNT( * ) FROM mapping_petitions WHERE service_id = ? AND account_id = ? AND status = ? LIMIT 5000;', ( service_id, account_id, HC.DELETED ) ).fetchall() )
|
||||
|
||||
num_mappings = len( self._c.execute( 'SELECT 1 FROM mappings WHERE service_id = ? AND account_id = ? LIMIT 5000;', ( service_id, account_id ) ).fetchall() )
|
||||
|
||||
result = self._c.execute( 'SELECT score FROM account_scores WHERE service_id = ? AND account_id = ? AND score_type = ?;', ( service_id, account_id, HC.SCORE_PETITION ) ).fetchone()
|
||||
|
@ -1126,7 +1133,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
account_info = {}
|
||||
|
||||
account_info[ 'num_deleted_mappings' ] = num_deleted_mappings
|
||||
account_info[ 'num_mappings' ] = num_mappings
|
||||
account_info[ 'petition_score' ] = petition_score
|
||||
account_info[ 'num_petitions' ] = num_petitions
|
||||
|
@ -1739,8 +1745,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._c.execute( 'COMMIT' )
|
||||
|
||||
HydrusData.Print( 'backing up: vacuum' )
|
||||
self._c.execute( 'VACUUM' )
|
||||
|
||||
HydrusData.Print( 'backing up: analyze' )
|
||||
self._c.execute( 'ANALYZE' )
|
||||
|
||||
self._c.close()
|
||||
|
@ -1750,17 +1758,26 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._c.execute( 'BEGIN IMMEDIATE' )
|
||||
|
||||
shutil.copy( self._db_path, self._db_path + '.backup' )
|
||||
backup_path = os.path.join( HC.DB_DIR, 'server_backup' )
|
||||
|
||||
HydrusData.RecyclePath( HC.SERVER_FILES_DIR + '_backup' )
|
||||
HydrusData.RecyclePath( HC.SERVER_THUMBNAILS_DIR + '_backup' )
|
||||
HydrusData.RecyclePath( HC.SERVER_MESSAGES_DIR + '_backup' )
|
||||
HydrusData.RecyclePath( HC.SERVER_UPDATES_DIR + '_backup' )
|
||||
HydrusData.Print( 'backing up: deleting old backup' )
|
||||
HydrusData.RecyclePath( backup_path )
|
||||
|
||||
shutil.copytree( HC.SERVER_FILES_DIR, HC.SERVER_FILES_DIR + '_backup' )
|
||||
shutil.copytree( HC.SERVER_THUMBNAILS_DIR, HC.SERVER_THUMBNAILS_DIR + '_backup' )
|
||||
shutil.copytree( HC.SERVER_MESSAGES_DIR, HC.SERVER_MESSAGES_DIR + '_backup' )
|
||||
shutil.copytree( HC.SERVER_UPDATES_DIR, HC.SERVER_UPDATES_DIR + '_backup' )
|
||||
os.mkdir( backup_path )
|
||||
|
||||
HydrusData.Print( 'backing up: copying db file' )
|
||||
shutil.copy( self._db_path, os.path.join( backup_path, self.DB_NAME + '.db' ) )
|
||||
|
||||
HydrusData.Print( 'backing up: copying files' )
|
||||
shutil.copytree( HC.SERVER_FILES_DIR, os.path.join( backup_path, 'server_files' ) )
|
||||
|
||||
HydrusData.Print( 'backing up: copying thumbnails' )
|
||||
shutil.copytree( HC.SERVER_THUMBNAILS_DIR, os.path.join( backup_path, 'server_thumbnails' ) )
|
||||
|
||||
HydrusData.Print( 'backing up: copying updates' )
|
||||
shutil.copytree( HC.SERVER_UPDATES_DIR, os.path.join( backup_path, 'server_updates' ) )
|
||||
|
||||
HydrusData.Print( 'backing up: done!' )
|
||||
|
||||
|
||||
def _ManageDBError( self, job, e ):
|
||||
|
@ -2300,7 +2317,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
with open( os.path.join( HC.BASE_DIR, 'update to v132 new access keys.txt' ), 'wb' ) as f:
|
||||
|
||||
f.write( account_log_text )
|
||||
f.write( HydrusData.ToByteString( account_log_text ) )
|
||||
|
||||
|
||||
|
||||
|
@ -2330,7 +2347,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if version == 132:
|
||||
|
||||
dirs = ( HC.SERVER_FILES_DIR, HC.SERVER_THUMBNAILS_DIR, HC.SERVER_MESSAGES_DIR )
|
||||
dirs = ( HC.SERVER_FILES_DIR, HC.SERVER_THUMBNAILS_DIR )
|
||||
|
||||
hex_chars = '0123456789abcdef'
|
||||
|
||||
|
@ -2442,7 +2459,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if version == 179:
|
||||
|
||||
print( 'moving updates about' )
|
||||
HydrusData.Print( 'moving updates about' )
|
||||
|
||||
for filename in os.listdir( HC.SERVER_UPDATES_DIR ):
|
||||
|
||||
|
@ -2469,7 +2486,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
print( 'The server has updated to version ' + str( version + 1 ) )
|
||||
HydrusData.Print( 'The server has updated to version ' + str( version + 1 ) )
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ def GetExpectedPath( file_type, hash ):
|
|||
|
||||
if file_type == 'file': directory = HC.SERVER_FILES_DIR
|
||||
elif file_type == 'thumbnail': directory = HC.SERVER_THUMBNAILS_DIR
|
||||
elif file_type == 'message': directory = HC.SERVER_MESSAGES_DIR
|
||||
|
||||
hash_encoded = hash.encode( 'hex' )
|
||||
|
||||
|
@ -72,7 +71,6 @@ def IterateAllPaths( file_type ):
|
|||
|
||||
if file_type == 'file': directory = HC.SERVER_FILES_DIR
|
||||
elif file_type == 'thumbnail': directory = HC.SERVER_THUMBNAILS_DIR
|
||||
elif file_type == 'message': directory = HC.SERVER_MESSAGES_DIR
|
||||
|
||||
hex_chars = '0123456789abcdef'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import ClientImageHandling
|
||||
import collections
|
||||
import HydrusConstants as HC
|
||||
import HydrusImageHandling
|
||||
import os
|
||||
import TestConstants
|
||||
import unittest
|
||||
|
@ -9,7 +9,7 @@ class TestImageHandling( unittest.TestCase ):
|
|||
|
||||
def test_phash( self ):
|
||||
|
||||
phash = HydrusImageHandling.GeneratePerceptualHash( os.path.join( HC.STATIC_DIR, 'hydrus.png' ) )
|
||||
phash = ClientImageHandling.GeneratePerceptualHash( os.path.join( HC.STATIC_DIR, 'hydrus.png' ) )
|
||||
|
||||
self.assertEqual( phash, '\xb0\x08\x83\xb2\x08\x0b8\x08' )
|
||||
|
33
server.py
33
server.py
|
@ -11,7 +11,9 @@ try:
|
|||
try: locale.setlocale( locale.LC_ALL, '' )
|
||||
except: pass
|
||||
|
||||
from include import HydrusExceptions
|
||||
from include import HydrusConstants as HC
|
||||
from include import HydrusData
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -20,7 +22,6 @@ try:
|
|||
from include import ServerController
|
||||
import threading
|
||||
from twisted.internet import reactor
|
||||
from include import HydrusExceptions
|
||||
from include import HydrusGlobals
|
||||
from include import HydrusLogger
|
||||
import traceback
|
||||
|
@ -31,18 +32,18 @@ try:
|
|||
|
||||
if action == 'help':
|
||||
|
||||
print( 'This is the hydrus server. It accepts these commands:' )
|
||||
print( '' )
|
||||
print( 'server start - runs the server' )
|
||||
print( 'server stop - stops an existing instance of this server' )
|
||||
print( 'server restart - stops an existing instance of this 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 running server just by hitting Ctrl+C.')
|
||||
HydrusData.Print( 'This is the hydrus server. It accepts these commands:' )
|
||||
HydrusData.Print( '' )
|
||||
HydrusData.Print( 'server start - runs the server' )
|
||||
HydrusData.Print( 'server stop - stops an existing instance of this server' )
|
||||
HydrusData.Print( 'server restart - stops an existing instance of this server, then runs itself' )
|
||||
HydrusData.Print( '' )
|
||||
HydrusData.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.' )
|
||||
HydrusData.Print( 'You can also stop the running server just by hitting Ctrl+C.')
|
||||
|
||||
else:
|
||||
|
||||
with HydrusLogger.HydrusLogger( 'server.log' ):
|
||||
with HydrusLogger.HydrusLogger( 'server.log' ) as logger:
|
||||
|
||||
error_occured = False
|
||||
|
||||
|
@ -55,7 +56,7 @@ try:
|
|||
|
||||
if action in ( 'start', 'restart' ):
|
||||
|
||||
print( 'Initialising controller...' )
|
||||
HydrusData.Print( 'Initialising controller...' )
|
||||
|
||||
threading.Thread( target = reactor.run, kwargs = { 'installSignalHandlers' : 0 } ).start()
|
||||
|
||||
|
@ -69,16 +70,16 @@ try:
|
|||
error_occured = True
|
||||
error = str( e )
|
||||
|
||||
print( error )
|
||||
HydrusData.Print( error )
|
||||
|
||||
except:
|
||||
|
||||
error_occured = True
|
||||
error = traceback.format_exc()
|
||||
|
||||
print( 'Hydrus server failed' )
|
||||
HydrusData.Print( 'Hydrus server failed' )
|
||||
|
||||
print( traceback.format_exc() )
|
||||
HydrusData.Print( traceback.format_exc() )
|
||||
|
||||
finally:
|
||||
|
||||
|
@ -95,13 +96,13 @@ try:
|
|||
|
||||
except HydrusExceptions.PermissionException as e:
|
||||
|
||||
print( e )
|
||||
HydrusData.Print( e )
|
||||
|
||||
except:
|
||||
|
||||
import traceback
|
||||
|
||||
print( 'Critical error occured! Details written to crash.log!' )
|
||||
HydrusData.Print( 'Critical error occured! Details written to crash.log!' )
|
||||
|
||||
with open( 'crash.log', 'wb' ) as f: f.write( traceback.format_exc() )
|
||||
|
6
test.py
6
test.py
|
@ -20,7 +20,7 @@ from include import TestDialogs
|
|||
from include import TestDB
|
||||
from include import TestFunctions
|
||||
from include import TestHydrusEncryption
|
||||
from include import TestHydrusImageHandling
|
||||
from include import TestClientImageHandling
|
||||
from include import TestHydrusNATPunch
|
||||
from include import TestHydrusServer
|
||||
from include import TestHydrusSessions
|
||||
|
@ -89,7 +89,7 @@ class Controller( object ):
|
|||
|
||||
self._services_manager = ClientData.ServicesManager()
|
||||
|
||||
self._managers[ 'hydrus_sessions' ] = HydrusSessions.HydrusSessionManagerClient()
|
||||
self._managers[ 'hydrus_sessions' ] = ClientCaches.HydrusSessionManagerClient()
|
||||
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager()
|
||||
self._managers[ 'tag_siblings' ] = ClientCaches.TagSiblingsManager()
|
||||
self._managers[ 'tag_parents' ] = ClientCaches.TagParentsManager()
|
||||
|
@ -179,7 +179,7 @@ class Controller( object ):
|
|||
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 == 'image': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImageHandling ) )
|
||||
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 ) )
|
||||
|
|
Loading…
Reference in New Issue