def FilterServiceKeysToStatusesToTags( self, service_keys_to_statuses_to_tags ): filtered_service_keys_to_statuses_to_tags = collections.defaultdict( HydrusData.default_dict_set ) for ( service_key, statuses_to_tags ) in service_keys_to_statuses_to_tags.items(): for service_key_lookup in ( CC.COMBINED_TAG_SERVICE_KEY, service_key ): if service_key_lookup in self._service_keys_to_predicates: combined_predicate = self._service_keys_to_predicates[ service_key_lookup ] new_statuses_to_tags = HydrusData.default_dict_set() for ( status, tags ) in statuses_to_tags.items(): new_statuses_to_tags[ status ] = { tag for tag in tags if combined_predicate( tag ) } statuses_to_tags = new_statuses_to_tags filtered_service_keys_to_statuses_to_tags[ service_key ] = statuses_to_tags return filtered_service_keys_to_statuses_to_tags
def BuildSimpleChildrenToParents( pairs ): simple_children_to_parents = HydrusData.default_dict_set() for ( child, parent ) in pairs: if child == parent: continue if LoopInSimpleChildrenToParents( simple_children_to_parents, child, parent ): continue simple_children_to_parents[ child ].add( parent ) return simple_children_to_parents
def _RefreshParents(self): service_keys_to_statuses_to_pairs = HydrusGlobals.client_controller.Read("tag_parents") # first collapse siblings sibling_manager = HydrusGlobals.client_controller.GetManager("tag_siblings") collapsed_service_keys_to_statuses_to_pairs = collections.defaultdict(HydrusData.default_dict_set) for (service_key, statuses_to_pairs) in service_keys_to_statuses_to_pairs.items(): if service_key == CC.COMBINED_TAG_SERVICE_KEY: continue for (status, pairs) in statuses_to_pairs.items(): pairs = sibling_manager.CollapsePairs(pairs) collapsed_service_keys_to_statuses_to_pairs[service_key][status] = pairs # now collapse current and pending service_keys_to_pairs_flat = HydrusData.default_dict_set() for (service_key, statuses_to_pairs) in collapsed_service_keys_to_statuses_to_pairs.items(): pairs_flat = statuses_to_pairs[HC.CURRENT].union(statuses_to_pairs[HC.PENDING]) service_keys_to_pairs_flat[service_key] = pairs_flat # now create the combined tag service combined_pairs_flat = set() for pairs_flat in service_keys_to_pairs_flat.values(): combined_pairs_flat.update(pairs_flat) service_keys_to_pairs_flat[CC.COMBINED_TAG_SERVICE_KEY] = combined_pairs_flat # service_keys_to_simple_children_to_parents = BuildServiceKeysToSimpleChildrenToParents( service_keys_to_pairs_flat ) self._service_keys_to_children_to_parents = BuildServiceKeysToChildrenToParents( service_keys_to_simple_children_to_parents )
def RefreshAllAccounts( self ): with self._lock: self._service_keys_to_sessions = collections.defaultdict( dict ) self._account_keys_to_session_keys = HydrusData.default_dict_set() self._account_keys_to_accounts = {} # existing_sessions = HydrusGlobals.controller.Read( 'sessions' ) for ( session_key, service_key, account, expires ) in existing_sessions: account_key = account.GetAccountKey() self._service_keys_to_sessions[ service_key ][ session_key ] = ( account, expires ) self._account_keys_to_session_keys[ account_key ].add( session_key ) self._account_keys_to_accounts[ account_key ] = account
def ConvertParsableContentToPrettyString(parsable_content, include_veto=False): pretty_strings = [] content_type_to_additional_infos = HydrusData.BuildKeyToSetDict( ((content_type, additional_infos) for (name, content_type, additional_infos) in parsable_content)) for (content_type, additional_infos) in content_type_to_additional_infos.items(): if content_type == HC.CONTENT_TYPE_MAPPINGS: namespaces = [ namespace for namespace in additional_infos if namespace != '' ] if '' in additional_infos: namespaces.append('unnamespaced') pretty_strings.append('tags: ' + ', '.join(namespaces)) elif content_type == HC.CONTENT_TYPE_VETO: if include_veto: pretty_strings.append('veto') if len(pretty_strings) == 0: return 'nothing' else: return ', '.join(pretty_strings)
def wait_for_free_slot(controller, subs_jobs, max_simultaneous_subscriptions): time.sleep(0.1) while True: p1 = controller.options['pause_subs_sync'] p2 = HydrusThreading.IsThreadShuttingDown() if p1 or p2: if HG.subscription_report_mode: HydrusData.ShowText( 'Subscriptions cancelling. Global sub pause is ' + str(p1) + ' and sub daemon thread shutdown status is ' + str(p2) + '.') if p2: for (thread, job) in subs_jobs: HydrusThreading.ShutdownThread(thread) raise HydrusExceptions.CancelledException( 'subs cancelling or thread shutting down') filter_finished_jobs(subs_jobs) if len(subs_jobs) < max_simultaneous_subscriptions: return time.sleep(1.0)
def CheckMouseIdle(self): mouse_position = wx.GetMousePosition() if self._last_mouse_position is None: self._last_mouse_position = mouse_position elif mouse_position != self._last_mouse_position: idle_before_position_update = self.CurrentlyIdle() self._timestamps['last_mouse_action'] = HydrusData.GetNow() self._last_mouse_position = mouse_position idle_after_position_update = self.CurrentlyIdle() move_knocked_us_out_of_idle = ( not idle_before_position_update) and idle_after_position_update if move_knocked_us_out_of_idle: self.pub('refresh_status')
def GetAccount(self, service_key, session_key): with self._lock: session_keys_to_sessions = self._service_keys_to_session_keys_to_sessions[ service_key] if session_key in session_keys_to_sessions: (account_key, expires) = session_keys_to_sessions[session_key] if HydrusData.TimeHasPassed(expires): del session_keys_to_sessions[session_key] else: account = self._service_keys_to_account_keys_to_accounts[ service_key][account_key] return account raise HydrusExceptions.SessionException( 'Did not find that session! Try again!')
def Export(self): width = self._width.GetValue() (payload_description, payload_string) = ClientSerialisable.GetPayloadDescriptionAndString( self._payload_obj) title = self._title.GetValue() text = self._text.GetValue() path = HydrusData.ToUnicode(self._filepicker.GetPath()) if not path.endswith('.png'): path += '.png' ClientSerialisable.DumpToPng(width, payload_string, title, payload_description, text, path) self._export.SetLabelText('done!') HG.client_controller.CallLaterWXSafe(self._export, 2.0, self._export.SetLabelText, 'export')
def FilterFreePaths( paths ): free_paths = [] for path in paths: if HydrusThreading.IsThreadShuttingDown(): raise HydrusExceptions.ShutdownException() try: os.rename( path, path ) # rename a path to itself free_paths.append( path ) except OSError as e: # 'already in use by another process' HydrusData.Print( 'Already in use: ' + path ) return free_paths
def HasSpaceForDBTransaction(db_dir, num_bytes): temp_dir = tempfile.gettempdir() temp_disk_free_space = GetFreeSpace(temp_dir) a = GetDevice(temp_dir) b = GetDevice(db_dir) if GetDevice(temp_dir) == GetDevice(db_dir): space_needed = int(num_bytes * 2.2) if temp_disk_free_space < space_needed: return ( False, 'I believe you need about ' + HydrusData.ConvertIntToBytes(space_needed) + ' on your db\'s partition, which I think also holds your temporary path, but you only seem to have ' + HydrusData.ConvertIntToBytes(temp_disk_free_space) + '.') else: space_needed = int(num_bytes * 1.1) if temp_disk_free_space < space_needed: return (False, 'I believe you need about ' + HydrusData.ConvertIntToBytes(space_needed) + ' on your temporary path\'s partition, which I think is ' + temp_dir + ', but you only seem to have ' + HydrusData.ConvertIntToBytes(temp_disk_free_space) + '.') db_disk_free_space = GetFreeSpace(db_dir) if db_disk_free_space < space_needed: return (False, 'I believe you need about ' + HydrusData.ConvertIntToBytes(space_needed) + ' on your db\'s partition, but you only seem to have ' + HydrusData.ConvertIntToBytes(db_disk_free_space) + '.') return (True, 'You seem to have enough space!')
def __init__(self, pausable=False, cancellable=False, only_when_idle=False, only_start_if_unbusy=False, stop_time=None, cancel_on_shutdown=True): self._key = HydrusData.GenerateKey() self._creation_time = HydrusData.GetNowFloat() self._pausable = pausable self._cancellable = cancellable self._only_when_idle = only_when_idle self._only_start_if_unbusy = only_start_if_unbusy self._stop_time = stop_time self._cancel_on_shutdown = cancel_on_shutdown self._start_time = HydrusData.GetNow() self._deleted = threading.Event() self._deletion_time = None self._begun = threading.Event() self._done = threading.Event() self._cancelled = threading.Event() self._paused = threading.Event() self._yield_pause_period = 10 self._next_yield_pause = HydrusData.GetNow() + self._yield_pause_period self._bigger_pause_period = 100 self._next_bigger_pause = HydrusData.GetNow( ) + self._bigger_pause_period self._longer_pause_period = 1000 self._next_longer_pause = HydrusData.GetNow( ) + self._longer_pause_period self._urls = [] self._variable_lock = threading.Lock() self._variables = dict()
def MaintainMemorySlow( self ): HydrusController.HydrusController.MaintainMemorySlow( self ) if HydrusData.TimeHasPassed( self._timestamps[ 'last_page_change' ] + 30 * 60 ): self.pub( 'delete_old_closed_pages' ) self._timestamps[ 'last_page_change' ] = HydrusData.GetNow() disk_cache_maintenance_mb = self.new_options.GetNoneableInteger( 'disk_cache_maintenance_mb' ) if disk_cache_maintenance_mb is not None: if self.CurrentlyVeryIdle(): cache_period = 3600 disk_cache_stop_time = HydrusData.GetNow() + 30 elif self.CurrentlyIdle(): cache_period = 1800 disk_cache_stop_time = HydrusData.GetNow() + 10 else: cache_period = 240 disk_cache_stop_time = HydrusData.GetNow() + 2 if HydrusData.TimeHasPassed( self._timestamps[ 'last_disk_cache_population' ] + cache_period ): self.Read( 'load_into_disk_cache', stop_time = disk_cache_stop_time, caller_limit = disk_cache_maintenance_mb * 1024 * 1024 ) self._timestamps[ 'last_disk_cache_population' ] = HydrusData.GetNow()
def Run( self ): HydrusData.RecordRunningStart( self._db_dir, 'server' ) HydrusData.Print( u'Initialising db\u2026' ) self.InitModel() HydrusData.Print( u'Initialising daemons and services\u2026' ) self.InitView() HydrusData.Print( 'Server is running. Press Ctrl+C to quit.' ) interrupt_received = False while not self._model_shutdown: try: time.sleep( 1 ) except KeyboardInterrupt: if not interrupt_received: interrupt_received = True HydrusData.Print( u'Received a keyboard interrupt\u2026' ) def do_it(): self.Exit() self.CallToThread( do_it ) HydrusData.Print( u'Shutting down controller\u2026' )
def WaitIfNeeded(self): if HydrusData.TimeHasPassed(self._next_yield_pause): time.sleep(0.1) self._next_yield_pause = HydrusData.GetNow( ) + self._yield_pause_period if HydrusData.TimeHasPassed(self._next_bigger_pause): time.sleep(1) self._next_bigger_pause = HydrusData.GetNow( ) + self._bigger_pause_period if HydrusData.TimeHasPassed(self._longer_pause_period): time.sleep(10) self._next_longer_pause = HydrusData.GetNow( ) + self._longer_pause_period i_paused = False should_quit = False while self.IsPaused(): i_paused = True time.sleep(0.1) if self.IsDone(): break if self.IsCancelled(): should_quit = True return (i_paused, should_quit)
def do_it( launch_path ): if HC.PLATFORM_WINDOWS and launch_path is None: os.startfile( path ) else: if launch_path is None: launch_path = GetDefaultLaunchPath() cmd = launch_path.replace( '%path%', path ) if HC.PLATFORM_WINDOWS: preexec_fn = None else: # setsid call un-childs this new process preexec_fn = os.setsid cmd = shlex.split( cmd ) if HG.callto_report_mode: message = 'Attempting to launch ' + path + ' using command ' + repr( cmd ) + '.' try: process = subprocess.Popen( cmd, preexec_fn = preexec_fn, startupinfo = HydrusData.GetHideTerminalSubprocessStartupInfo() ) process.wait() ( stdout, stderr ) = process.communicate() if HG.callto_report_mode: HydrusData.ShowText( message ) if stdout is None and stderr is None: HydrusData.ShowText( 'No stdout or stderr came back.' ) if stdout is not None: HydrusData.ShowText( 'stdout: ' + repr( stdout ) ) if stderr is not None: HydrusData.ShowText( 'stderr: ' + repr( stderr ) ) except Exception as e: HydrusData.ShowText( 'Could not launch a file! Command used was:' + os.linesep + HydrusData.ToUnicode( cmd ) ) HydrusData.ShowException( e )
def do_it(): if HC.PLATFORM_WINDOWS: os.startfile( path ) else: if HC.PLATFORM_OSX: cmd = 'open' elif HC.PLATFORM_LINUX: cmd = 'xdg-open' # setsid call un-childs this new process process = subprocess.Popen( [ cmd, path ], preexec_fn = os.setsid, startupinfo = HydrusData.GetHideTerminalSubprocessStartupInfo() ) process.wait() process.communicate()
def RestartBooru( self ): service = self.services_manager.GetService( CC.LOCAL_BOORU_SERVICE_KEY ) port = service.GetPort() def TWISTEDRestartServer(): def StartServer( *args, **kwargs ): try: try: connection = HydrusNetworking.GetLocalConnection( port ) connection.close() text = 'The client\'s booru server could not start because something was already bound to port ' + str( port ) + '.' text += os.linesep * 2 text += 'This usually means another hydrus client is already running and occupying that port. It could be a previous instantiation of this client that has yet to shut itself down.' text += os.linesep * 2 text += 'You can change the port this client tries to host its local server on in services->manage services.' HydrusData.ShowText( text ) except: import ClientLocalServer self._booru_port_connection = reactor.listenTCP( port, ClientLocalServer.HydrusServiceBooru( service ) ) try: connection = HydrusNetworking.GetLocalConnection( port ) connection.close() except Exception as e: text = 'Tried to bind port ' + str( port ) + ' for the local booru, but it failed:' text += os.linesep * 2 text += HydrusData.ToUnicode( e ) HydrusData.ShowText( text ) except Exception as e: wx.CallAfter( HydrusData.ShowException, e ) if self._booru_port_connection is None: if port is not None: StartServer() else: deferred = defer.maybeDeferred( self._booru_port_connection.stopListening ) if port is not None: deferred.addCallback( StartServer ) if HG.twisted_is_broke: HydrusData.ShowText( 'Twisted failed to import, so could not restart the booru! Please contact hydrus dev!' ) else: reactor.callFromThread( TWISTEDRestartServer )
def ResetPageChangeTimer( self ): self._timestamps[ 'last_page_change' ] = HydrusData.GetNow()
def ResetIdleTimer( self ): self._timestamps[ 'last_user_action' ] = HydrusData.GetNow()
def MaintainDB( self, stop_time = None ): if self.new_options.GetBoolean( 'maintain_similar_files_duplicate_pairs_during_idle' ): phashes_stop_time = stop_time if phashes_stop_time is None: phashes_stop_time = HydrusData.GetNow() + 15 self.WriteInterruptable( 'maintain_similar_files_phashes', stop_time = phashes_stop_time ) tree_stop_time = stop_time if tree_stop_time is None: tree_stop_time = HydrusData.GetNow() + 30 self.WriteInterruptable( 'maintain_similar_files_tree', stop_time = tree_stop_time, abandon_if_other_work_to_do = True ) search_distance = self.new_options.GetInteger( 'similar_files_duplicate_pairs_search_distance' ) search_stop_time = stop_time if search_stop_time is None: search_stop_time = HydrusData.GetNow() + 60 self.WriteInterruptable( 'maintain_similar_files_duplicate_pairs', search_distance, stop_time = search_stop_time, abandon_if_other_work_to_do = True ) if stop_time is None or not HydrusData.TimeHasPassed( stop_time ): self.WriteInterruptable( 'vacuum', stop_time = stop_time ) if stop_time is None or not HydrusData.TimeHasPassed( stop_time ): self.WriteInterruptable( 'analyze', stop_time = stop_time ) if stop_time is None or not HydrusData.TimeHasPassed( stop_time ): if HydrusData.TimeHasPassed( self._timestamps[ 'last_service_info_cache_fatten' ] + ( 60 * 20 ) ): self.pub( 'splash_set_status_text', 'fattening service info' ) services = self.services_manager.GetServices() for service in services: self.pub( 'splash_set_status_subtext', service.GetName() ) try: self.Read( 'service_info', service.GetServiceKey() ) except: pass # sometimes this breaks when a service has just been removed and the client is closing, so ignore the error self._timestamps[ 'last_service_info_cache_fatten' ] = HydrusData.GetNow()
def InitView( self ): if self.options[ 'password' ] is not None: self.pub( 'splash_set_status_text', 'waiting for password' ) def wx_code_password(): while True: with wx.PasswordEntryDialog( self._splash, 'Enter your password', 'Enter password' ) as dlg: if dlg.ShowModal() == wx.ID_OK: # this can produce unicode with cyrillic or w/e keyboards, which hashlib can't handle password = HydrusData.ToByteString( dlg.GetValue() ) if hashlib.sha256( password ).digest() == self.options[ 'password' ]: break else: raise HydrusExceptions.PermissionException( 'Bad password check' ) self.CallBlockingToWx( wx_code_password ) self.pub( 'splash_set_title_text', u'booting gui\u2026' ) def wx_code_gui(): self.gui = ClientGUI.FrameGUI( self ) self.ResetIdleTimer() self.CallBlockingToWx( wx_code_gui ) # ShowText will now popup as a message, as popup message manager has overwritten the hooks HydrusController.HydrusController.InitView( self ) self._booru_port_connection = None self.RestartBooru() if not self._no_daemons: self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckMouseIdle', ClientDaemons.DAEMONCheckMouseIdle, period = 10 ) ) self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseAccounts', ClientDaemons.DAEMONSynchroniseAccounts, ( 'notify_unknown_accounts', ) ) ) self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SaveDirtyObjects', ClientDaemons.DAEMONSaveDirtyObjects, ( 'important_dirt_to_clean', ), period = 30 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'DownloadFiles', ClientDaemons.DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseSubscriptions', ClientDaemons.DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ), period = 14400, init_wait = 60, pre_call_wait = 3 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'CheckImportFolders', ClientDaemons.DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'CheckExportFolders', ClientDaemons.DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'MaintainTrash', ClientDaemons.DAEMONMaintainTrash, init_wait = 120 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseRepositories', ClientDaemons.DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ), period = 4 * 3600, pre_call_wait = 1 ) ) self._daemons.append( HydrusThreading.DAEMONBackgroundWorker( self, 'UPnP', ClientDaemons.DAEMONUPnP, ( 'notify_new_upnp_mappings', ), init_wait = 120, pre_call_wait = 6 ) ) if self.db.IsFirstStart(): message = 'Hi, this looks like the first time you have started the hydrus client.' message += os.linesep * 2 message += 'Don\'t forget to check out the help if you haven\'t already.' message += os.linesep * 2 message += 'To dismiss popup messages like this, right-click them.' HydrusData.ShowText( message ) if self.db.IsDBUpdated(): HydrusData.ShowText( 'The client has updated to version ' + str( HC.SOFTWARE_VERSION ) + '!' ) for message in self.db.GetInitialMessages(): HydrusData.ShowText( message )
def GetUnicode(self, with_count=True, sibling_service_key=None, render_for_user=False): count_text = u'' if with_count: if self._min_current_count > 0: number_text = HydrusData.ConvertIntToPrettyString( self._min_current_count) if self._max_current_count is not None: number_text += u'-' + HydrusData.ConvertIntToPrettyString( self._max_current_count) count_text += u' (' + number_text + u')' if self._min_pending_count > 0: number_text = HydrusData.ConvertIntToPrettyString( self._min_pending_count) if self._max_pending_count is not None: number_text += u'-' + HydrusData.ConvertIntToPrettyString( self._max_pending_count) count_text += u' (+' + number_text + u')' if self._predicate_type in HC.SYSTEM_PREDICATES: if self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_EVERYTHING: base = u'everything' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_INBOX: base = u'inbox' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_ARCHIVE: base = u'archive' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_UNTAGGED: base = u'untagged' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_LOCAL: base = u'local' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_NOT_LOCAL: base = u'not local' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_DIMENSIONS: base = u'dimensions' elif self._predicate_type in (HC.PREDICATE_TYPE_SYSTEM_NUM_TAGS, HC.PREDICATE_TYPE_SYSTEM_WIDTH, HC.PREDICATE_TYPE_SYSTEM_HEIGHT, HC.PREDICATE_TYPE_SYSTEM_NUM_WORDS): if self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_NUM_TAGS: base = u'number of tags' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_WIDTH: base = u'width' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_HEIGHT: base = u'height' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_NUM_WORDS: base = u'number of words' if self._value is not None: (operator, value) = self._value base += u' ' + operator + u' ' + HydrusData.ConvertIntToPrettyString( value) elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_DURATION: base = u'duration' if self._value is not None: (operator, value) = self._value base += u' ' + operator + u' ' + HydrusData.ConvertMillisecondsToPrettyTime( value) elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_RATIO: base = u'ratio' if self._value is not None: (operator, ratio_width, ratio_height) = self._value base += u' ' + operator + u' ' + str( ratio_width) + u':' + str(ratio_height) elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_SIZE: base = u'size' if self._value is not None: (operator, size, unit) = self._value base += u' ' + operator + u' ' + str( size) + HydrusData.ConvertIntToUnit(unit) elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_LIMIT: base = u'limit' if self._value is not None: value = self._value base += u' is ' + HydrusData.ConvertIntToPrettyString( value) elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_AGE: base = u'age' if self._value is not None: (operator, years, months, days, hours) = self._value base += u' ' + operator + u' ' + str(years) + u'y' + str( months) + u'm' + str(days) + u'd' + str(hours) + u'h' elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_NUM_PIXELS: base = u'num_pixels' if self._value is not None: (operator, num_pixels, unit) = self._value base += u' ' + operator + u' ' + str( num_pixels) + ' ' + HydrusData.ConvertIntToPixels(unit) elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_HASH: base = u'hash' if self._value is not None: (hash, hash_type) = self._value base = hash_type + ' hash is ' + hash.encode('hex') elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_MIME: base = u'mime' if self._value is not None: mimes = self._value if set(mimes) == set(HC.SEARCHABLE_MIMES): mime_text = 'anything' elif set(mimes) == set(HC.SEARCHABLE_MIMES).intersection( set(HC.APPLICATIONS)): mime_text = 'application' elif set(mimes) == set(HC.SEARCHABLE_MIMES).intersection( set(HC.AUDIO)): mime_text = 'audio' elif set(mimes) == set(HC.SEARCHABLE_MIMES).intersection( set(HC.IMAGES)): mime_text = 'image' elif set(mimes) == set(HC.SEARCHABLE_MIMES).intersection( set(HC.VIDEO)): mime_text = 'video' else: mime_text = ', '.join( [HC.mime_string_lookup[mime] for mime in mimes]) base += u' is ' + mime_text elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_RATING: base = u'rating' if self._value is not None: (operator, value, service_key) = self._value service = HydrusGlobals.client_controller.GetServicesManager( ).GetService(service_key) base += u' for ' + service.GetName( ) + u' ' + operator + u' ' + HydrusData.ToUnicode(value) elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_SIMILAR_TO: base = u'similar to' if self._value is not None: (hash, max_hamming) = self._value base += u' ' + hash.encode( 'hex') + u' using max hamming of ' + str(max_hamming) elif self._predicate_type == HC.PREDICATE_TYPE_SYSTEM_FILE_SERVICE: if self._value is None: base = 'file service' else: (operator, current_or_pending, service_key) = self._value if operator == True: base = u'is' else: base = u'is not' if current_or_pending == HC.CONTENT_STATUS_PENDING: base += u' pending to ' else: base += u' currently in ' service = HydrusGlobals.client_controller.GetServicesManager( ).GetService(service_key) base += service.GetName() base += count_text base = HydrusTags.CombineTag('system', base) elif self._predicate_type == HC.PREDICATE_TYPE_TAG: tag = self._value if not self._inclusive: base = u'-' else: base = u'' base += tag base += count_text if sibling_service_key is not None: siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings') sibling = siblings_manager.GetSibling(sibling_service_key, tag) if sibling is not None: sibling = ClientTags.RenderTag(sibling, render_for_user) base += u' (will display as ' + sibling + ')' elif self._predicate_type == HC.PREDICATE_TYPE_PARENT: base = ' ' tag = self._value base += tag base += count_text elif self._predicate_type == HC.PREDICATE_TYPE_NAMESPACE: namespace = self._value if not self._inclusive: base = u'-' else: base = u'' rendered_tag = HydrusTags.CombineTag(namespace, '*anything*') base += rendered_tag elif self._predicate_type == HC.PREDICATE_TYPE_WILDCARD: wildcard = self._value if not self._inclusive: base = u'-' else: base = u'' base += wildcard base = ClientTags.RenderTag(base, render_for_user) return base
def __init__(self, system_predicates): self._inbox = False self._archive = False self._local = False self._not_local = False self._common_info = {} self._limit = None self._similar_to = None self._file_services_to_include_current = [] self._file_services_to_include_pending = [] self._file_services_to_exclude_current = [] self._file_services_to_exclude_pending = [] self._ratings_predicates = [] new_options = HydrusGlobals.client_controller.GetNewOptions() forced_search_limit = new_options.GetNoneableInteger( 'forced_search_limit') if forced_search_limit is not None: self._limit = forced_search_limit for predicate in system_predicates: predicate_type = predicate.GetType() value = predicate.GetValue() if predicate_type == HC.PREDICATE_TYPE_SYSTEM_INBOX: self._inbox = True if predicate_type == HC.PREDICATE_TYPE_SYSTEM_ARCHIVE: self._archive = True if predicate_type == HC.PREDICATE_TYPE_SYSTEM_LOCAL: self._local = True if predicate_type == HC.PREDICATE_TYPE_SYSTEM_NOT_LOCAL: self._not_local = True if predicate_type == HC.PREDICATE_TYPE_SYSTEM_HASH: (hash, hash_type) = value self._common_info['hash'] = (hash, hash_type) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_AGE: (operator, years, months, days, hours) = value age = (((((( (years * 12) + months) * 30) + days) * 24) + hours) * 3600) now = HydrusData.GetNow() # this is backwards because we are talking about age, not timestamp if operator == '<': self._common_info['min_timestamp'] = now - age elif operator == '>': self._common_info['max_timestamp'] = now - age elif operator == u'\u2248': self._common_info['min_timestamp'] = now - int(age * 1.15) self._common_info['max_timestamp'] = now - int(age * 0.85) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_MIME: mimes = value if isinstance(mimes, int): mimes = (mimes, ) self._common_info['mimes'] = mimes if predicate_type == HC.PREDICATE_TYPE_SYSTEM_DURATION: (operator, duration) = value if operator == '<': self._common_info['max_duration'] = duration elif operator == '>': self._common_info['min_duration'] = duration elif operator == '=': self._common_info['duration'] = duration elif operator == u'\u2248': if duration == 0: self._common_info['duration'] = 0 else: self._common_info['min_duration'] = int(duration * 0.85) self._common_info['max_duration'] = int(duration * 1.15) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_RATING: (operator, value, service_key) = value self._ratings_predicates.append((operator, value, service_key)) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_RATIO: (operator, ratio_width, ratio_height) = value if operator == '=': self._common_info['ratio'] = (ratio_width, ratio_height) elif operator == 'wider than': self._common_info['min_ratio'] = (ratio_width, ratio_height) elif operator == 'taller than': self._common_info['max_ratio'] = (ratio_width, ratio_height) elif operator == u'\u2248': self._common_info['min_ratio'] = (ratio_width * 0.85, ratio_height) self._common_info['max_ratio'] = (ratio_width * 1.15, ratio_height) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_SIZE: (operator, size, unit) = value size = size * unit if operator == '<': self._common_info['max_size'] = size elif operator == '>': self._common_info['min_size'] = size elif operator == '=': self._common_info['size'] = size elif operator == u'\u2248': self._common_info['min_size'] = int(size * 0.85) self._common_info['max_size'] = int(size * 1.15) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_NUM_TAGS: (operator, num_tags) = value if operator == '<': self._common_info['max_num_tags'] = num_tags elif operator == '=': self._common_info['num_tags'] = num_tags elif operator == '>': self._common_info['min_num_tags'] = num_tags if predicate_type == HC.PREDICATE_TYPE_SYSTEM_WIDTH: (operator, width) = value if operator == '<': self._common_info['max_width'] = width elif operator == '>': self._common_info['min_width'] = width elif operator == '=': self._common_info['width'] = width elif operator == u'\u2248': if width == 0: self._common_info['width'] = 0 else: self._common_info['min_width'] = int(width * 0.85) self._common_info['max_width'] = int(width * 1.15) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_NUM_PIXELS: (operator, num_pixels, unit) = value num_pixels = num_pixels * unit if operator == '<': self._common_info['max_num_pixels'] = num_pixels elif operator == '>': self._common_info['min_num_pixels'] = num_pixels elif operator == '=': self._common_info['num_pixels'] = num_pixels elif operator == u'\u2248': self._common_info['min_num_pixels'] = int(num_pixels * 0.85) self._common_info['max_num_pixels'] = int(num_pixels * 1.15) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_HEIGHT: (operator, height) = value if operator == '<': self._common_info['max_height'] = height elif operator == '>': self._common_info['min_height'] = height elif operator == '=': self._common_info['height'] = height elif operator == u'\u2248': if height == 0: self._common_info['height'] = 0 else: self._common_info['min_height'] = int(height * 0.85) self._common_info['max_height'] = int(height * 1.15) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_NUM_WORDS: (operator, num_words) = value if operator == '<': self._common_info['max_num_words'] = num_words elif operator == '>': self._common_info['min_num_words'] = num_words elif operator == '=': self._common_info['num_words'] = num_words elif operator == u'\u2248': if num_words == 0: self._common_info['num_words'] = 0 else: self._common_info['min_num_words'] = int(num_words * 0.85) self._common_info['max_num_words'] = int(num_words * 1.15) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_LIMIT: limit = value if self._limit is None: self._limit = limit else: self._limit = min(limit, self._limit) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_FILE_SERVICE: (operator, current_or_pending, service_key) = value if operator == True: if current_or_pending == HC.CONTENT_STATUS_CURRENT: self._file_services_to_include_current.append( service_key) else: self._file_services_to_include_pending.append( service_key) else: if current_or_pending == HC.CONTENT_STATUS_CURRENT: self._file_services_to_exclude_current.append( service_key) else: self._file_services_to_exclude_pending.append( service_key) if predicate_type == HC.PREDICATE_TYPE_SYSTEM_SIMILAR_TO: (hash, max_hamming) = value self._similar_to = (hash, max_hamming)
def GetMime( path ): with open( path, 'rb' ) as f: f.seek( 0 ) bit_to_check = f.read( 256 ) for ( offset, header, mime ) in header_and_mime: offset_bit_to_check = bit_to_check[ offset: ] if offset_bit_to_check.startswith( header ): if mime == HC.UNDETERMINED_WM: if HydrusVideoHandling.HasVideoStream( path ): return HC.VIDEO_WMV # we'll catch and verify wma later elif mime == HC.UNDETERMINED_PNG: return HC.IMAGE_PNG # atm (Feb 2016), ffmpeg doesn't report duration for apngs, so can't do this just yet. # #if HydrusVideoHandling.HasVideoStream( path ): # # return HC.VIDEO_APNG # #else: # # return HC.IMAGE_PNG # else: return mime try: mime = HydrusVideoHandling.GetMimeFromFFMPEG( path ) if mime != HC.APPLICATION_UNKNOWN: return mime except HydrusExceptions.MimeException: HydrusData.Print( 'FFMPEG couldn\'t figure out the mime for: ' + path ) except Exception as e: HydrusData.Print( 'FFMPEG couldn\'t figure out the mime for: ' + path ) HydrusData.PrintException( e, do_wait = False ) hsaudio_object = hsaudiotag.auto.File( path ) if hsaudio_object.valid: if isinstance( hsaudio_object.original, hsaudiotag.mpeg.Mpeg ): return HC.AUDIO_MP3 elif isinstance( hsaudio_object.original, hsaudiotag.flac.FLAC ): return HC.AUDIO_FLAC elif isinstance( hsaudio_object.original, hsaudiotag.ogg.Vorbis ): return HC.AUDIO_OGG elif isinstance( hsaudio_object.original, hsaudiotag.wma.WMADecoder ): return HC.AUDIO_WMA return HC.APPLICATION_UNKNOWN
def MergeTree( source, dest, text_update_hook = None ): pauser = HydrusData.BigJobPauser() if not os.path.exists( dest ): try: shutil.move( source, dest ) except OSError: # if there were read only files in source and this was partition to partition, the copy2 goes ok but the subsequent source unlink fails # so, if it seems this has happened, let's just try a walking mergetree, which should be able to deal with these readonlies on a file-by-file basis if os.path.exists( dest ): MergeTree( source, dest, text_update_hook = text_update_hook ) else: if len( os.listdir( dest ) ) == 0: for filename in os.listdir( source ): source_path = os.path.join( source, filename ) dest_path = os.path.join( dest, filename ) if not os.path.isdir( source_path ): MakeFileWritable( source_path ) shutil.move( source_path, dest_path ) else: num_errors = 0 for ( root, dirnames, filenames ) in os.walk( source ): if text_update_hook is not None: text_update_hook( 'Copying ' + root + '.' ) dest_root = root.replace( source, dest ) for dirname in dirnames: pauser.Pause() source_path = os.path.join( root, dirname ) dest_path = os.path.join( dest_root, dirname ) MakeSureDirectoryExists( dest_path ) shutil.copystat( source_path, dest_path ) for filename in filenames: if num_errors > 5: raise Exception( 'Too many errors, directory move abandoned.' ) pauser.Pause() source_path = os.path.join( root, filename ) dest_path = os.path.join( dest_root, filename ) ok = MergeFile( source_path, dest_path ) if not ok: num_errors += 1 if num_errors == 0: DeletePath( source )
def MirrorTree( source, dest, text_update_hook = None, is_cancelled_hook = None ): pauser = HydrusData.BigJobPauser() MakeSureDirectoryExists( dest ) num_errors = 0 for ( root, dirnames, filenames ) in os.walk( source ): if is_cancelled_hook is not None and is_cancelled_hook(): return if text_update_hook is not None: text_update_hook( 'Copying ' + root + '.' ) dest_root = root.replace( source, dest ) surplus_dest_paths = { os.path.join( dest_root, dest_filename ) for dest_filename in os.listdir( dest_root ) } for dirname in dirnames: pauser.Pause() source_path = os.path.join( root, dirname ) dest_path = os.path.join( dest_root, dirname ) surplus_dest_paths.discard( dest_path ) MakeSureDirectoryExists( dest_path ) shutil.copystat( source_path, dest_path ) for filename in filenames: if num_errors > 5: raise Exception( 'Too many errors, directory copy abandoned.' ) pauser.Pause() source_path = os.path.join( root, filename ) dest_path = os.path.join( dest_root, filename ) surplus_dest_paths.discard( dest_path ) ok = MirrorFile( source_path, dest_path ) if not ok: num_errors += 1 for dest_path in surplus_dest_paths: pauser.Pause() DeletePath( dest_path )
def GetThumbnailPath(hash, full_size=True): if not full_size: options = HydrusGlobals.client_controller.GetOptions() thumbnail_dimensions = options['thumbnail_dimensions'] if tuple(thumbnail_dimensions) == HC.UNSCALED_THUMBNAIL_DIMENSIONS: full_size = True path = GetExpectedThumbnailPath(hash, full_size) if not os.path.exists(path): if full_size: client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager( ) try: file_path = client_files_manager.GetFilePath(hash) except HydrusExceptions.FileMissingException: raise HydrusExceptions.FileMissingException( 'The thumbnail for file ' + hash.encode('hex') + ' was missing. It could not be regenerated because the original file was also missing. This event could indicate hard drive corruption or an unplugged external drive. Please check everything is ok.' ) try: thumbnail = HydrusFileHandling.GenerateThumbnail(file_path) except Exception as e: HydrusData.ShowException(e) raise HydrusExceptions.FileMissingException( 'The thumbnail for file ' + hash.encode('hex') + ' was missing. It could not be regenerated from the original file for the above reason. This event could indicate hard drive corruption. Please check everything is ok.' ) try: with open(path, 'wb') as f: f.write(thumbnail) except Exception as e: HydrusData.ShowException(e) raise HydrusExceptions.FileMissingException( 'The thumbnail for file ' + hash.encode('hex') + ' was missing. It was regenerated from the original file, but hydrus could not write it to the location ' + path + ' for the above reason. This event could indicate hard drive corruption, and it also suggests that hydrus does not have permission to write to its thumbnail folder. Please check everything is ok.' ) HydrusData.ShowText( 'The thumbnail for file ' + hash.encode('hex') + ' was missing. It has been regenerated from the original file, but this event could indicate hard drive corruption. Please check everything is ok.' ) else: full_size_path = GetThumbnailPath(hash, True) try: thumbnail_resized = HydrusFileHandling.GenerateThumbnail( full_size_path, thumbnail_dimensions) except: try: os.remove(full_size_path) except: raise HydrusExceptions.FileMissingException( 'The thumbnail for file ' + hash.encode('hex') + ' was found, but it would not render. An attempt to delete it was made, but that failed as well. This event could indicate hard drive corruption, and it also suggests that hydrus does not have permission to write to its thumbnail folder. Please check everything is ok.' ) full_size_path = GetThumbnailPath(hash, True) thumbnail_resized = HydrusFileHandling.GenerateThumbnail( full_size_path, thumbnail_dimensions) with open(path, 'wb') as f: f.write(thumbnail_resized) return path
def EventShowMenu(self, event): seed_cache = self._seed_cache_get_callable() menu_items = [] num_failures = seed_cache.GetSeedCount(CC.STATUS_FAILED) if num_failures > 0: menu_items.append( ('normal', 'retry ' + HydrusData.ConvertIntToPrettyString(num_failures) + ' failures', 'Tell this cache to reattempt all its failures.', self._RetryFailures)) num_unknown = seed_cache.GetSeedCount(CC.STATUS_UNKNOWN) num_processed = len(seed_cache) - num_unknown if num_processed > 0: menu_items.append(( 'normal', 'delete ' + HydrusData.ConvertIntToPrettyString(num_processed) + ' \'processed\' files from the queue', 'Tell this cache to clear out processed files, reducing the size of the queue.', self._ClearProcessed)) if len(menu_items) > 0: menu = wx.Menu() for (item_type, title, description, data) in menu_items: if item_type == 'normal': func = data ClientGUIMenus.AppendMenuItem(self, menu, title, description, func) elif item_type == 'check': check_manager = data current_value = check_manager.GetCurrentValue() func = check_manager.Invert if current_value is not None: ClientGUIMenus.AppendMenuCheckItem( self, menu, title, description, current_value, func) elif item_type == 'separator': ClientGUIMenus.AppendSeparator(menu) HG.client_controller.PopupMenu(self, menu) else: event.Skip()
def DoWork(self): if HydrusData.TimeHasPassed(self._last_checked + self._period): folder_path = HydrusData.ToUnicode(self._name) if os.path.exists(folder_path) and os.path.isdir(folder_path): query_hash_ids = HydrusGlobals.client_controller.Read( 'file_query_ids', self._file_search_context) media_results = [] i = 0 base = 256 while i < len(query_hash_ids): if HC.options['pause_export_folders_sync']: return if i == 0: (last_i, i) = (0, base) else: (last_i, i) = (i, i + base) sub_query_hash_ids = query_hash_ids[last_i:i] more_media_results = HydrusGlobals.client_controller.Read( 'media_results_from_ids', sub_query_hash_ids) media_results.extend(more_media_results) # terms = ParseExportPhrase(self._phrase) previous_filenames = set(os.listdir(folder_path)) sync_filenames = set() client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager( ) num_copied = 0 for media_result in media_results: hash = media_result.GetHash() mime = media_result.GetMime() size = media_result.GetSize() source_path = client_files_manager.GetFilePath(hash, mime) filename = GenerateExportFilename(media_result, terms) dest_path = os.path.join(folder_path, filename) do_copy = True if filename in sync_filenames: do_copy = False elif HydrusPaths.PathsHaveSameSizeAndDate( source_path, dest_path): do_copy = False if do_copy: shutil.copy2(source_path, dest_path) num_copied += 1 try: os.chmod(dest_path, stat.S_IWRITE | stat.S_IREAD) except: pass sync_filenames.add(filename) if num_copied > 0: HydrusData.Print( 'Export folder ' + self._name + ' exported ' + HydrusData.ConvertIntToPrettyString(num_copied) + ' files.') if self._export_type == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE: deletee_filenames = previous_filenames.difference( sync_filenames) for deletee_filename in deletee_filenames: deletee_path = os.path.join(folder_path, deletee_filename) ClientData.DeletePath(deletee_path) if len(deletee_filenames) > 0: HydrusData.Print('Export folder ' + self._name + ' deleted ' + HydrusData.ConvertIntToPrettyString( len(deletee_filenames)) + ' files.') self._last_checked = HydrusData.GetNow() HydrusGlobals.client_controller.WriteSynchronous( 'serialisable', self)
def __repr__(self): return 'Predicate: ' + HydrusData.ToUnicode( (self._predicate_type, self._value, self._inclusive, self.GetCount()))
def ImportFromHTA( parent, hta_path, tag_service_key, hashes ): hta = HydrusTagArchive.HydrusTagArchive( hta_path ) potential_namespaces = list( hta.GetNamespaces() ) potential_namespaces.sort() hash_type = hta.GetHashType() # this tests if the hta can produce a hashtype del hta service = HG.client_controller.services_manager.GetService( tag_service_key ) service_type = service.GetServiceType() can_delete = True if service_type == HC.TAG_REPOSITORY: if service.HasPermission( HC.CONTENT_TYPE_MAPPINGS, HC.PERMISSION_ACTION_OVERRULE ): can_delete = False if can_delete: text = 'Would you like to add or delete the archive\'s tags?' with ClientGUIDialogs.DialogYesNo( parent, text, title = 'Add or delete?', yes_label = 'add', no_label = 'delete' ) as dlg_add: result = dlg_add.ShowModal() if result == wx.ID_YES: adding = True elif result == wx.ID_NO: adding = False else: return else: text = 'You cannot quickly delete tags from this service, so I will assume you want to add tags.' wx.MessageBox( text ) adding = True text = 'Choose which namespaces to ' if adding: text += 'add.' else: text += 'delete.' list_of_tuples = [ ( HydrusData.ConvertUglyNamespaceToPrettyString( namespace ), namespace, False ) for namespace in potential_namespaces ] with ClientGUIDialogs.DialogCheckFromList( parent, text, list_of_tuples ) as dlg_namespaces: if dlg_namespaces.ShowModal() == wx.ID_OK: namespaces = dlg_namespaces.GetChecked() if hash_type == HydrusTagArchive.HASH_TYPE_SHA256: text = 'This tag archive can be fully merged into your database, but this may be more than you want.' text += os.linesep * 2 text += 'Would you like to import the tags only for files you actually have, or do you want absolutely everything?' with ClientGUIDialogs.DialogYesNo( parent, text, title = 'How much do you want?', yes_label = 'just for my local files', no_label = 'everything' ) as dlg_add: result = dlg_add.ShowModal() if result == wx.ID_YES: file_service_key = CC.LOCAL_FILE_SERVICE_KEY elif result == wx.ID_NO: file_service_key = CC.COMBINED_FILE_SERVICE_KEY else: return else: file_service_key = CC.LOCAL_FILE_SERVICE_KEY text = 'Are you absolutely sure you want to ' if adding: text += 'add' else: text += 'delete' text += ' the namespaces:' text += os.linesep * 2 text += os.linesep.join( HydrusData.ConvertUglyNamespacesToPrettyStrings( namespaces ) ) text += os.linesep * 2 file_service = HG.client_controller.services_manager.GetService( file_service_key ) text += 'For ' if hashes is None: text += 'all' else: text += HydrusData.ToHumanInt( len( hashes ) ) text += ' files in \'' + file_service.GetName() + '\'' if adding: text += ' to ' else: text += ' from ' text += '\'' + service.GetName() + '\'?' with ClientGUIDialogs.DialogYesNo( parent, text ) as dlg_final: if dlg_final.ShowModal() == wx.ID_YES: HG.client_controller.pub( 'sync_to_tag_archive', hta_path, tag_service_key, file_service_key, adding, namespaces, hashes )
def test_undo(self): hash_1 = HydrusData.GenerateKey() hash_2 = HydrusData.GenerateKey() hash_3 = HydrusData.GenerateKey() command_1 = { CC.COMBINED_LOCAL_FILE_SERVICE_KEY: [ HydrusData.ContentUpdate(HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, {hash_1}) ] } command_2 = { CC.COMBINED_LOCAL_FILE_SERVICE_KEY: [ HydrusData.ContentUpdate(HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_INBOX, {hash_2}) ] } command_3 = { CC.COMBINED_LOCAL_FILE_SERVICE_KEY: [ HydrusData.ContentUpdate(HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, {hash_1, hash_3}) ] } command_1_inverted = { CC.COMBINED_LOCAL_FILE_SERVICE_KEY: [ HydrusData.ContentUpdate(HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_INBOX, {hash_1}) ] } command_2_inverted = { CC.COMBINED_LOCAL_FILE_SERVICE_KEY: [ HydrusData.ContentUpdate(HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, {hash_2}) ] } undo_manager = ClientCaches.UndoManager(HG.client_controller) # undo_manager.AddCommand('content_updates', command_1) self.assertEqual((u'undo archive 1 files', None), undo_manager.GetUndoRedoStrings()) undo_manager.AddCommand('content_updates', command_2) self.assertEqual((u'undo inbox 1 files', None), undo_manager.GetUndoRedoStrings()) undo_manager.Undo() self.assertEqual((u'undo archive 1 files', u'redo inbox 1 files'), undo_manager.GetUndoRedoStrings()) self.assertEqual(HG.test_controller.GetWrite('content_updates'), [((command_2_inverted, ), {})]) undo_manager.Redo() self.assertEqual(HG.test_controller.GetWrite('content_updates'), [((command_2, ), {})]) self.assertEqual((u'undo inbox 1 files', None), undo_manager.GetUndoRedoStrings()) undo_manager.Undo() self.assertEqual(HG.test_controller.GetWrite('content_updates'), [((command_2_inverted, ), {})]) undo_manager.Undo() self.assertEqual(HG.test_controller.GetWrite('content_updates'), [((command_1_inverted, ), {})]) self.assertEqual((None, u'redo archive 1 files'), undo_manager.GetUndoRedoStrings()) undo_manager.AddCommand('content_updates', command_3) self.assertEqual((u'undo archive 2 files', None), undo_manager.GetUndoRedoStrings())
def Exit( self ): if HG.emergency_exit: self.ShutdownView() self.ShutdownModel() HydrusData.CleanRunningFile( self.db_dir, 'client' ) else: try: idle_shutdown_action = self.options[ 'idle_shutdown' ] if idle_shutdown_action in ( CC.IDLE_ON_SHUTDOWN, CC.IDLE_ON_SHUTDOWN_ASK_FIRST ): idle_shutdown_max_minutes = self.options[ 'idle_shutdown_max_minutes' ] time_to_stop = HydrusData.GetNow() + ( idle_shutdown_max_minutes * 60 ) if self.ThereIsIdleShutdownWorkDue( time_to_stop ): if idle_shutdown_action == CC.IDLE_ON_SHUTDOWN_ASK_FIRST: text = 'Is now a good time for the client to do up to ' + HydrusData.ConvertIntToPrettyString( idle_shutdown_max_minutes ) + ' minutes\' maintenance work? (Will auto-no in 15 seconds)' with ClientGUIDialogs.DialogYesNo( self._splash, text, title = 'Maintenance is due' ) as dlg_yn: job = self.CallLaterWXSafe( dlg_yn, 15, dlg_yn.EndModal, wx.ID_NO ) try: if dlg_yn.ShowModal() == wx.ID_YES: HG.do_idle_shutdown_work = True finally: job.Cancel() else: HG.do_idle_shutdown_work = True self.CallToThreadLongRunning( self.THREADExitEverything ) except: self._DestroySplash() HydrusData.DebugPrint( traceback.format_exc() ) HG.emergency_exit = True self.Exit()