Version 182

This commit is contained in:
Hydrus 2015-11-18 16:44:07 -06:00
parent 3fce11362f
commit facd3ded44
47 changed files with 1828 additions and 1193 deletions

BIN
bin/swfrender_linux Normal file

Binary file not shown.

BIN
bin/swfrender_osx Normal file

Binary file not shown.

BIN
bin/swfrender_win32.exe Normal file

Binary file not shown.

View File

@ -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() )

View File

@ -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>

View File

@ -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>

View File

@ -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 ]

View File

@ -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()

View File

@ -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 )

View File

@ -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() )

View File

@ -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 + ':' )

View File

@ -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:

View File

@ -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 )

View File

@ -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

View File

@ -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

View File

@ -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 ) )

View File

@ -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 ) ) )

View File

@ -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 ):

View File

@ -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

View File

@ -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

View File

@ -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 ):

View File

@ -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

View File

@ -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

View File

@ -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 ):

View File

@ -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:

View File

@ -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 )

View File

@ -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 )

View File

@ -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 ):

View File

@ -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 ):

View File

@ -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 ):

View File

@ -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

View File

@ -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 )

View File

@ -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 )

View File

@ -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 ) )

View File

@ -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 )

View File

@ -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 ):

View File

@ -61,7 +61,7 @@ class HydrusAMP( amp.AMP ):
def _errbackHandleError( self, failure ):
print( failure.getTraceback() )
HydrusData.Print( failure.getTraceback() )
normal_errors = []

View File

@ -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() ) )

View File

@ -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 ):

View File

@ -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 )

View File

@ -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 )

View File

@ -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():

View File

@ -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, ) )

View File

@ -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'

View File

@ -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' )

View File

@ -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() )

View File

@ -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 ) )