Version 203
This commit is contained in:
parent
596667f655
commit
4ec034b181
|
@ -8,6 +8,24 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 203</h3></li>
|
||||
<ul>
|
||||
<li>thumbnail resize now happens on the fly--feel free to change it as often as you like, it takes no time at all</li>
|
||||
<li>added 'lexicopgrahic (grouped by namespace)' tag sorting to the selection tags box</li>
|
||||
<li>added an option to disable OpenCV for static images under the media options page</li>
|
||||
<li>panning with the shortcut keys now pans by a twelfth of the media size or canvas size, whichever is smaller</li>
|
||||
<li>cleared the analyze timestamp cache for both client and server, which will force a reanalyze of the new db files on the next db maintenance run</li>
|
||||
<li>improved some misc analyze code--primary keys are analyzed again</li>
|
||||
<li>added an explicit analyze command to the client's database maintenance menu</li>
|
||||
<li>the server now analyzes attached database files</li>
|
||||
<li>improved an unhelpful server thumbnail error message</li>
|
||||
<li>added an invalid NULL check-and-skip to hash cross-referencing</li>
|
||||
<li>fixed some invalid a/c write dropdown search domain initialisation</li>
|
||||
<li>fixed some borked zoom calculation code that was sometimes lagging the media viewer and leading to 100% zoomed images being sent unneccessarily and then being nastily scaled down</li>
|
||||
<li>fixed a file import error if a synced tag archive is missing</li>
|
||||
<li>updated sqlite for windows</li>
|
||||
<li>misc cleanup</li>
|
||||
</ul>
|
||||
<li><h3>version 202</h3></li>
|
||||
<ul>
|
||||
<li>fixed a problem with the v198->v199 update step</li>
|
||||
|
|
|
@ -1059,6 +1059,35 @@ class ThumbnailCache( object ):
|
|||
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( path )
|
||||
|
||||
options = HydrusGlobals.client_controller.GetOptions()
|
||||
|
||||
( media_x, media_y ) = display_media.GetResolution()
|
||||
( actual_x, actual_y ) = hydrus_bitmap.GetSize()
|
||||
( desired_x, desired_y ) = options[ 'thumbnail_dimensions' ]
|
||||
|
||||
too_large = actual_x > desired_x or actual_y > desired_y
|
||||
|
||||
small_original_image = actual_x == media_x and actual_y == media_y
|
||||
|
||||
too_small = actual_x < desired_x and actual_y < desired_y
|
||||
|
||||
if too_large or ( too_small and not small_original_image ):
|
||||
|
||||
if not from_error: # If we get back here with an error, just return the badly sized bitmap--it'll probably get sorted next session
|
||||
|
||||
del hydrus_bitmap
|
||||
|
||||
try:
|
||||
|
||||
os.remove( path ) # Sometimes, the image library doesn't release this fast enough, so this fails
|
||||
|
||||
finally:
|
||||
|
||||
hydrus_bitmap = self._GetResizedHydrusBitmapFromHardDrive( display_media, from_error = True )
|
||||
|
||||
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if from_error:
|
||||
|
|
|
@ -220,6 +220,8 @@ SORT_BY_LEXICOGRAPHIC_ASC = 8
|
|||
SORT_BY_LEXICOGRAPHIC_DESC = 9
|
||||
SORT_BY_INCIDENCE_ASC = 10
|
||||
SORT_BY_INCIDENCE_DESC = 11
|
||||
SORT_BY_LEXICOGRAPHIC_NAMESPACE_ASC = 12
|
||||
SORT_BY_LEXICOGRAPHIC_NAMESPACE_DESC = 13
|
||||
|
||||
SORT_CHOICES = []
|
||||
|
||||
|
|
|
@ -606,7 +606,9 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
self.pub( 'splash_set_status_text', 'analyzing' )
|
||||
|
||||
self.WriteInterruptable( 'analyze', stop_time )
|
||||
only_when_idle = self.CurrentlyIdle()
|
||||
|
||||
self.WriteInterruptable( 'analyze', stop_time = stop_time, only_when_idle = only_when_idle )
|
||||
|
||||
if self._timestamps[ 'last_service_info_cache_fatten' ] == 0:
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import ClientData
|
||||
import ClientDBACCache
|
||||
import ClientDefaults
|
||||
import ClientFiles
|
||||
import ClientImporting
|
||||
|
@ -1224,11 +1223,18 @@ class DB( HydrusDB.HydrusDB ):
|
|||
f.write( thumbnail )
|
||||
|
||||
|
||||
phash = ClientImageHandling.GeneratePerceptualHash( thumbnail_path )
|
||||
|
||||
hash_id = self._GetHashId( hash )
|
||||
|
||||
self._c.execute( 'INSERT OR REPLACE INTO perceptual_hashes ( hash_id, phash ) VALUES ( ?, ? );', ( hash_id, sqlite3.Binary( phash ) ) )
|
||||
try:
|
||||
|
||||
phash = ClientImageHandling.GeneratePerceptualHash( thumbnail_path )
|
||||
|
||||
hash_id = self._GetHashId( hash )
|
||||
|
||||
self._c.execute( 'INSERT OR REPLACE INTO perceptual_hashes ( hash_id, phash ) VALUES ( ?, ? );', ( hash_id, sqlite3.Binary( phash ) ) )
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
self._c.execute( 'DELETE FROM service_info WHERE info_type = ?;', ( HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL, ) )
|
||||
|
@ -1243,7 +1249,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
self._c.execute( 'REPLACE INTO web_sessions ( name, cookies, expiry ) VALUES ( ?, ?, ? );', ( name, cookies, expires ) )
|
||||
|
||||
|
||||
def _Analyze( self, stop_time ):
|
||||
def _Analyze( self, stop_time = None, only_when_idle = False, force_reanalyze = False ):
|
||||
|
||||
stale_time_delta = 14 * 86400
|
||||
|
||||
|
@ -1251,14 +1257,21 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
db_names = [ name for ( index, name, path ) in self._c.execute( 'PRAGMA database_list;' ) if name not in ( 'mem', 'temp' ) ]
|
||||
|
||||
all_index_names = set()
|
||||
all_names = set()
|
||||
|
||||
for db_name in db_names:
|
||||
|
||||
all_index_names.update( ( name for ( name, ) in self._c.execute( 'SELECT name FROM ' + db_name + '.sqlite_master WHERE type = ?;', ( 'index', ) ) ) )
|
||||
all_names.update( ( name for ( name, ) in self._c.execute( 'SELECT name FROM ' + db_name + '.sqlite_master;' ) ) )
|
||||
|
||||
|
||||
names_to_analyze = [ name for name in all_index_names if name not in existing_names_to_timestamps or HydrusData.TimeHasPassed( existing_names_to_timestamps[ name ] + stale_time_delta ) ]
|
||||
if force_reanalyze:
|
||||
|
||||
names_to_analyze = list( all_names )
|
||||
|
||||
else:
|
||||
|
||||
names_to_analyze = [ name for name in all_names if name not in existing_names_to_timestamps or HydrusData.TimeHasPassed( existing_names_to_timestamps[ name ] + stale_time_delta ) ]
|
||||
|
||||
|
||||
random.shuffle( names_to_analyze )
|
||||
|
||||
|
@ -1277,7 +1290,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
HydrusData.Print( 'Analyzed ' + name + ' in ' + HydrusData.ConvertTimeDeltaToPrettyString( time_took ) )
|
||||
|
||||
|
||||
if HydrusData.TimeHasPassed( stop_time ) or not self._controller.CurrentlyIdle():
|
||||
p1 = stop_time is not None and HydrusData.TimeHasPassed( stop_time )
|
||||
p2 = only_when_idle and not self._controller.CurrentlyIdle()
|
||||
|
||||
if p1 or p2:
|
||||
|
||||
break
|
||||
|
||||
|
@ -2827,6 +2843,11 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for given_hash in given_hashes:
|
||||
|
||||
if given_hash is None:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
result = self._c.execute( 'SELECT hash_id FROM local_hashes WHERE ' + given_hash_type + ' = ?;', ( sqlite3.Binary( given_hash ), ) ).fetchone()
|
||||
|
||||
if result is not None:
|
||||
|
@ -2961,6 +2982,11 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for hash in hashes:
|
||||
|
||||
if hash is None:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
result = self._c.execute( 'SELECT hash_id FROM hashes WHERE hash = ?;', ( sqlite3.Binary( hash ), ) ).fetchone()
|
||||
|
||||
if result is None:
|
||||
|
@ -4659,15 +4685,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
def _GetThumbnail( self, hash, full_size = False ):
|
||||
|
||||
path = ClientFiles.GetThumbnailPath( hash, full_size )
|
||||
|
||||
with open( path, 'rb' ) as f: thumbnail = f.read()
|
||||
|
||||
return thumbnail
|
||||
|
||||
|
||||
def _GetURLStatus( self, url ):
|
||||
|
||||
result = self._c.execute( 'SELECT hash_id FROM urls WHERE url = ?;', ( url, ) ).fetchone()
|
||||
|
@ -4872,11 +4889,14 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for ( archive_name, namespaces ) in tag_archive_sync.items():
|
||||
|
||||
( hta_path, hta ) = self._tag_archives[ archive_name ]
|
||||
adding = True
|
||||
|
||||
try: self._SyncHashesToTagArchive( [ hash ], hta_path, adding, namespaces, service_key )
|
||||
except: pass
|
||||
if archive_name in self._tag_archives:
|
||||
|
||||
( hta_path, hta ) = self._tag_archives[ archive_name ]
|
||||
adding = True
|
||||
|
||||
try: self._SyncHashesToTagArchive( [ hash ], hta_path, adding, namespaces, service_key )
|
||||
except: pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -5865,27 +5885,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if resize_thumbs:
|
||||
|
||||
prefix = 'deleting old resized thumbnails: '
|
||||
|
||||
job_key = ClientThreading.JobKey()
|
||||
|
||||
job_key.SetVariable( 'popup_text_1', prefix + 'initialising' )
|
||||
|
||||
self._controller.pub( 'message', job_key )
|
||||
|
||||
thumbnail_paths = ( path for path in ClientFiles.IterateAllThumbnailPaths() if path.endswith( '_resized' ) )
|
||||
|
||||
for ( i, path ) in enumerate( thumbnail_paths ):
|
||||
|
||||
ClientData.DeletePath( path )
|
||||
|
||||
job_key.SetVariable( 'popup_text_1', prefix + 'done ' + HydrusData.ConvertIntToPrettyString( i ) )
|
||||
|
||||
|
||||
self.pub_after_commit( 'thumbnail_resize' )
|
||||
|
||||
job_key.SetVariable( 'popup_text_1', prefix + 'done!' )
|
||||
|
||||
|
||||
self.pub_after_commit( 'notify_new_options' )
|
||||
|
||||
|
@ -7280,6 +7281,11 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
if version == 202:
|
||||
|
||||
self._c.execute( 'DELETE FROM analyze_timestamps;' )
|
||||
|
||||
|
||||
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
|
|
@ -1,416 +0,0 @@
|
|||
import ClientData
|
||||
import ClientDefaults
|
||||
import ClientFiles
|
||||
import ClientImporting
|
||||
import ClientMedia
|
||||
import ClientRatings
|
||||
import ClientThreading
|
||||
import collections
|
||||
import hashlib
|
||||
import httplib
|
||||
import itertools
|
||||
import json
|
||||
import HydrusConstants as HC
|
||||
import HydrusDB
|
||||
import ClientDownloading
|
||||
import ClientImageHandling
|
||||
import HydrusEncryption
|
||||
import HydrusExceptions
|
||||
import HydrusFileHandling
|
||||
import HydrusImageHandling
|
||||
import HydrusNATPunch
|
||||
import HydrusPaths
|
||||
import HydrusSerialisable
|
||||
import HydrusTagArchive
|
||||
import HydrusTags
|
||||
import HydrusThreading
|
||||
import ClientConstants as CC
|
||||
import lz4
|
||||
import os
|
||||
import Queue
|
||||
import random
|
||||
import shutil
|
||||
import sqlite3
|
||||
import stat
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import wx
|
||||
import yaml
|
||||
import HydrusData
|
||||
import ClientSearch
|
||||
import HydrusGlobals
|
||||
|
||||
class SpecificServicesDB( HydrusDB.HydrusDB ):
|
||||
|
||||
READ_WRITE_ACTIONS = []
|
||||
UPDATE_WAIT = 0
|
||||
|
||||
def _AddFiles( self, hash_ids ):
|
||||
|
||||
self._c.executemany( 'INSERT OR IGNORE INTO current_files ( hash_id ) VALUES ( ? );', ( ( hash_id, ) for hash_id in hash_ids ) )
|
||||
|
||||
|
||||
def _AddMappings( self, mappings_ids ):
|
||||
|
||||
for ( namespace_id, tag_id, hash_ids ) in mappings_ids:
|
||||
|
||||
hash_ids = self._FilterFiles( hash_ids )
|
||||
|
||||
if len( hash_ids ) > 0:
|
||||
|
||||
# direct copy of rescind pending, so we don't filter twice
|
||||
self._c.execute( 'DELETE FROM pending_mappings WHERE hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ' AND namespace_id = ? AND tag_id = ?;', ( namespace_id, tag_id ) )
|
||||
|
||||
num_deleted = self._GetRowCount()
|
||||
|
||||
if num_deleted > 0:
|
||||
|
||||
self._c.execute( 'UPDATE ac_cache SET pending_count = pending_count - ? WHERE namespace_id = ? AND tag_id = ?;', ( num_deleted, namespace_id, tag_id ) )
|
||||
|
||||
self._c.execute( 'DELETE FROM ac_cache WHERE namespace_id = ? AND tag_id = ? AND current_count = ? AND pending_count = ?;', ( namespace_id, tag_id, 0, 0 ) )
|
||||
|
||||
|
||||
#
|
||||
|
||||
self._c.executemany( 'INSERT OR IGNORE INTO current_mappings ( hash_id, namespace_id, tag_id ) VALUES ( ?, ?, ? );', ( ( hash_id, namespace_id, tag_id ) for hash_id in hash_ids ) )
|
||||
|
||||
num_new = self._GetRowCount()
|
||||
|
||||
if num_new > 0:
|
||||
|
||||
self._c.execute( 'INSERT OR IGNORE INTO ac_cache ( namespace_id, tag_id, current_count, pending_count ) VALUES ( ?, ?, ?, ? );', ( namespace_id, tag_id, 0, 0 ) )
|
||||
|
||||
self._c.execute( 'UPDATE ac_cache SET current_count = current_count + ? WHERE namespace_id = ? AND tag_id = ?;', ( num_new, namespace_id, tag_id ) )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _Analyze( self, stale_time_delta, stop_time ):
|
||||
|
||||
all_names = [ name for ( name, ) in self._c.execute( 'SELECT name FROM sqlite_master;' ) ]
|
||||
|
||||
existing_names_to_timestamps = dict( self._c.execute( 'SELECT name, timestamp FROM analyze_timestamps;' ).fetchall() )
|
||||
|
||||
names_to_analyze = [ name for name in all_names if name not in existing_names_to_timestamps or HydrusData.TimeHasPassed( existing_names_to_timestamps[ name ] + stale_time_delta ) ]
|
||||
|
||||
random.shuffle( names_to_analyze )
|
||||
|
||||
while len( names_to_analyze ) > 0:
|
||||
|
||||
name = names_to_analyze.pop()
|
||||
|
||||
started = HydrusData.GetNowPrecise()
|
||||
|
||||
self._c.execute( 'ANALYZE ' + name + ';' )
|
||||
|
||||
self._c.execute( 'REPLACE INTO analyze_timestamps ( name, timestamp ) VALUES ( ?, ? );', ( name, HydrusData.GetNow() ) )
|
||||
|
||||
time_took = HydrusData.GetNowPrecise() - started
|
||||
|
||||
if HydrusData.TimeHasPassed( stop_time ) or not self._controller.CurrentlyIdle():
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
self._c.execute( 'ANALYZE sqlite_master;' ) # this reloads the current stats into the query planner
|
||||
|
||||
still_more_to_do = len( names_to_analyze ) > 0
|
||||
|
||||
return still_more_to_do
|
||||
|
||||
|
||||
def _CreateDB( self ):
|
||||
|
||||
HydrusDB.SetupDBCreatePragma( self._c, no_wal = self._no_wal )
|
||||
|
||||
try: self._c.execute( 'BEGIN IMMEDIATE' )
|
||||
except Exception as e:
|
||||
|
||||
raise HydrusExceptions.DBAccessException( HydrusData.ToUnicode( e ) )
|
||||
|
||||
|
||||
self._c.execute( 'CREATE TABLE current_files ( hash_id INTEGER PRIMARY KEY );' )
|
||||
|
||||
self._c.execute( 'CREATE TABLE current_mappings ( hash_id INTEGER, namespace_id INTEGER, tag_id INTEGER, PRIMARY KEY( hash_id, namespace_id, tag_id ) );' )
|
||||
self._c.execute( 'CREATE TABLE pending_mappings ( hash_id INTEGER, namespace_id INTEGER, tag_id INTEGER, PRIMARY KEY( hash_id, namespace_id, tag_id ) );' )
|
||||
|
||||
self._c.execute( 'CREATE TABLE ac_cache ( namespace_id INTEGER, tag_id INTEGER, current_count INTEGER, pending_count INTEGER, PRIMARY KEY( namespace_id, tag_id ) );' )
|
||||
|
||||
self._c.execute( 'CREATE TABLE analyze_timestamps ( name TEXT, timestamp INTEGER );' )
|
||||
self._c.execute( 'CREATE TABLE maintenance_timestamps ( name TEXT, timestamp INTEGER );' )
|
||||
self._c.execute( 'CREATE TABLE version ( version INTEGER );' )
|
||||
|
||||
self._c.execute( 'INSERT INTO version ( version ) VALUES ( ? );', ( HC.SOFTWARE_VERSION, ) )
|
||||
|
||||
self._c.execute( 'COMMIT' )
|
||||
|
||||
|
||||
def _DeleteFiles( self, hash_ids ):
|
||||
|
||||
for hash_id in hash_ids:
|
||||
|
||||
hash_id_set = { hash_id }
|
||||
|
||||
pending_mappings_ids = [ ( namespace_id, tag_id, hash_id_set ) for ( namespace_id, tag_id ) in self._c.execute( 'SELECT namespace_id, tag_id FROM pending_mappings WHERE hash_id = ?;', ( hash_id, ) ) ]
|
||||
|
||||
self._RescindPendingMappings( pending_mappings_ids )
|
||||
|
||||
current_mappings_ids = [ ( namespace_id, tag_id, hash_id_set ) for ( namespace_id, tag_id ) in self._c.execute( 'SELECT namespace_id, tag_id FROM current_mappings WHERE hash_id = ?;', ( hash_id, ) ) ]
|
||||
|
||||
self._DeleteMappings( current_mappings_ids )
|
||||
|
||||
|
||||
self._c.execute( 'DELETE FROM current_files WHERE hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ';' )
|
||||
|
||||
|
||||
def _DeleteMappings( self, mappings_ids ):
|
||||
|
||||
for ( namespace_id, tag_id, hash_ids ) in mappings_ids:
|
||||
|
||||
hash_ids = self._FilterFiles( hash_ids )
|
||||
|
||||
if len( hash_ids ) > 0:
|
||||
|
||||
self._c.execute( 'DELETE FROM current_mappings WHERE hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ' AND namespace_id = ? AND tag_id = ?;', ( namespace_id, tag_id ) )
|
||||
|
||||
num_deleted = self._GetRowCount()
|
||||
|
||||
if num_deleted > 0:
|
||||
|
||||
self._c.execute( 'UPDATE ac_cache SET current_count = current_count - ? WHERE namespace_id = ? AND tag_id = ?;', ( num_deleted, namespace_id, tag_id ) )
|
||||
|
||||
self._c.execute( 'DELETE FROM ac_cache WHERE namespace_id = ? AND tag_id = ? AND current_count = ? AND pending_count = ?;', ( namespace_id, tag_id, 0, 0 ) )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _GetAutocompleteCounts( self, namespace_ids_to_tag_ids ):
|
||||
|
||||
results = []
|
||||
|
||||
for ( namespace_id, tag_ids ) in namespace_ids_to_tag_ids.items():
|
||||
|
||||
results.extend( ( ( namespace_id, tag_id, current_count, pending_count ) for ( tag_id, current_count, pending_count ) in self._c.execute( 'SELECT tag_id, current_count, pending_count FROM ac_cache WHERE namespace_id = ? AND tag_id IN ' + HydrusData.SplayListForDB( tag_ids ) + ';', ( namespace_id, ) ) ) )
|
||||
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _FilterFiles( self, hash_ids ):
|
||||
|
||||
return [ hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM current_files WHERE hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ';' ) ]
|
||||
|
||||
|
||||
def _HasFile( self, hash_id ):
|
||||
|
||||
result = self._c.execute( 'SELECT 1 FROM current_files WHERE hash_id = ?;', ( hash_id, ) ).fetchone()
|
||||
|
||||
if result is None:
|
||||
|
||||
return False
|
||||
|
||||
else:
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def _ManageDBError( self, job, e ):
|
||||
|
||||
( exception_type, value, tb ) = sys.exc_info()
|
||||
|
||||
new_e = type( e )( os.linesep.join( traceback.format_exception( exception_type, value, tb ) ) )
|
||||
|
||||
job.PutResult( new_e )
|
||||
|
||||
|
||||
def _PendMappings( self, mappings_ids ):
|
||||
|
||||
for ( namespace_id, tag_id, hash_ids ) in mappings_ids:
|
||||
|
||||
hash_ids = self._FilterFiles( hash_ids )
|
||||
|
||||
if len( hash_ids ) > 0:
|
||||
|
||||
self._c.executemany( 'INSERT OR IGNORE INTO pending_mappings ( hash_id, namespace_id, tag_id ) VALUES ( ?, ?, ? );', ( ( hash_id, namespace_id, tag_id ) for hash_id in hash_ids ) )
|
||||
|
||||
num_new = self._GetRowCount()
|
||||
|
||||
if num_new > 0:
|
||||
|
||||
self._c.execute( 'INSERT OR IGNORE INTO ac_cache ( namespace_id, tag_id, current_count, pending_count ) VALUES ( ?, ?, ?, ? );', ( namespace_id, tag_id, 0, 0 ) )
|
||||
|
||||
self._c.execute( 'UPDATE ac_cache SET pending_count = pending_count + ? WHERE namespace_id = ? AND tag_id = ?;', ( num_new, namespace_id, tag_id ) )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _RescindPendingMappings( self, mappings_ids ):
|
||||
|
||||
for ( namespace_id, tag_id, hash_ids ) in mappings_ids:
|
||||
|
||||
hash_ids = self._FilterFiles( hash_ids )
|
||||
|
||||
if len( hash_ids ) > 0:
|
||||
|
||||
self._c.execute( 'DELETE FROM pending_mappings WHERE hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ' AND namespace_id = ? AND tag_id = ?;', ( namespace_id, tag_id ) )
|
||||
|
||||
num_deleted = self._GetRowCount()
|
||||
|
||||
if num_deleted > 0:
|
||||
|
||||
self._c.execute( 'UPDATE ac_cache SET pending_count = pending_count - ? WHERE namespace_id = ? AND tag_id = ?;', ( num_deleted, namespace_id, tag_id ) )
|
||||
|
||||
self._c.execute( 'DELETE FROM ac_cache WHERE namespace_id = ? AND tag_id = ? AND current_count = ? AND pending_count = ?;', ( namespace_id, tag_id, 0, 0 ) )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _Read( self, action, *args, **kwargs ):
|
||||
|
||||
if action == 'ac_counts': result = self._GetAutocompleteCounts( *args, **kwargs )
|
||||
else: raise Exception( 'db received an unknown read command: ' + action )
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _UpdateDB( self, version ):
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
||||
|
||||
def _Write( self, action, *args, **kwargs ):
|
||||
|
||||
if action == 'add_files': result = self._AddFiles( *args, **kwargs )
|
||||
elif action == 'add_mappings': result = self._AddMappings( *args, **kwargs )
|
||||
elif action == 'analyze': result = self._Analyze( *args, **kwargs )
|
||||
elif action == 'delete_files': result = self._DeleteFiles( *args, **kwargs )
|
||||
elif action == 'delete_mappings': result = self._DeleteMappings( *args, **kwargs )
|
||||
elif action == 'pend_mappings': result = self._PendMappings( *args, **kwargs )
|
||||
elif action == 'rescind_pending_mappings': result = self._RescindPendingMappings( *args, **kwargs )
|
||||
else: raise Exception( 'db received an unknown write command: ' + action )
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class CombinedFilesDB( HydrusDB.HydrusDB ):
|
||||
|
||||
READ_WRITE_ACTIONS = []
|
||||
UPDATE_WAIT = 0
|
||||
|
||||
def _Analyze( self, stale_time_delta, stop_time ):
|
||||
|
||||
all_names = [ name for ( name, ) in self._c.execute( 'SELECT name FROM sqlite_master;' ) ]
|
||||
|
||||
existing_names_to_timestamps = dict( self._c.execute( 'SELECT name, timestamp FROM analyze_timestamps;' ).fetchall() )
|
||||
|
||||
names_to_analyze = [ name for name in all_names if name not in existing_names_to_timestamps or HydrusData.TimeHasPassed( existing_names_to_timestamps[ name ] + stale_time_delta ) ]
|
||||
|
||||
random.shuffle( names_to_analyze )
|
||||
|
||||
while len( names_to_analyze ) > 0:
|
||||
|
||||
name = names_to_analyze.pop()
|
||||
|
||||
started = HydrusData.GetNowPrecise()
|
||||
|
||||
self._c.execute( 'ANALYZE ' + name + ';' )
|
||||
|
||||
self._c.execute( 'REPLACE INTO analyze_timestamps ( name, timestamp ) VALUES ( ?, ? );', ( name, HydrusData.GetNow() ) )
|
||||
|
||||
time_took = HydrusData.GetNowPrecise() - started
|
||||
|
||||
if HydrusData.TimeHasPassed( stop_time ) or not self._controller.CurrentlyIdle():
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
self._c.execute( 'ANALYZE sqlite_master;' ) # this reloads the current stats into the query planner
|
||||
|
||||
still_more_to_do = len( names_to_analyze ) > 0
|
||||
|
||||
return still_more_to_do
|
||||
|
||||
|
||||
def _CreateDB( self ):
|
||||
|
||||
HydrusDB.SetupDBCreatePragma( self._c, no_wal = self._no_wal )
|
||||
|
||||
try: self._c.execute( 'BEGIN IMMEDIATE' )
|
||||
except Exception as e:
|
||||
|
||||
raise HydrusExceptions.DBAccessException( HydrusData.ToUnicode( e ) )
|
||||
|
||||
|
||||
self._c.execute( 'CREATE TABLE ac_cache ( namespace_id INTEGER, tag_id INTEGER, current_count INTEGER, pending_count INTEGER, PRIMARY KEY( namespace_id, tag_id ) );' )
|
||||
|
||||
self._c.execute( 'CREATE TABLE analyze_timestamps ( name TEXT, timestamp INTEGER );' )
|
||||
self._c.execute( 'CREATE TABLE maintenance_timestamps ( name TEXT, timestamp INTEGER );' )
|
||||
self._c.execute( 'CREATE TABLE version ( version INTEGER );' )
|
||||
|
||||
self._c.execute( 'INSERT INTO version ( version ) VALUES ( ? );', ( HC.SOFTWARE_VERSION, ) )
|
||||
|
||||
self._c.execute( 'COMMIT' )
|
||||
|
||||
|
||||
def _GetAutocompleteCounts( self, namespace_ids_to_tag_ids ):
|
||||
|
||||
results = []
|
||||
|
||||
for ( namespace_id, tag_ids ) in namespace_ids_to_tag_ids.items():
|
||||
|
||||
results.extend( ( ( namespace_id, tag_id, current_count, pending_count ) for ( tag_id, current_count, pending_count ) in self._c.execute( 'SELECT tag_id, current_count, pending_count FROM ac_cache WHERE namespace_id = ? AND tag_id IN ' + HydrusData.SplayListForDB( tag_ids ) + ';', ( namespace_id, ) ) ) )
|
||||
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _ManageDBError( self, job, e ):
|
||||
|
||||
( exception_type, value, tb ) = sys.exc_info()
|
||||
|
||||
new_e = type( e )( os.linesep.join( traceback.format_exception( exception_type, value, tb ) ) )
|
||||
|
||||
job.PutResult( new_e )
|
||||
|
||||
|
||||
def _UpdateCounts( self, count_ids ):
|
||||
|
||||
self._c.executemany( 'INSERT OR IGNORE INTO ac_cache ( namespace_id, tag_id, current_count, pending_count ) VALUES ( ?, ?, ?, ? );', ( ( namespace_id, tag_id, 0, 0 ) for ( namespace_id, tag_id, current_delta, pending_delta ) in count_ids ) )
|
||||
|
||||
self._c.executemany( 'UPDATE ac_cache SET current_count = current_count + ?, pending_count = pending_count + ? WHERE namespace_id = ? AND tag_id = ?;', ( ( current_delta, pending_delta, namespace_id, tag_id ) for ( namespace_id, tag_id, current_delta, pending_delta ) in count_ids ) )
|
||||
|
||||
self._c.executemany( 'DELETE FROM ac_cache WHERE namespace_id = ? AND tag_id = ? AND current_count = ? AND pending_count = ?;', ( ( namespace_id, tag_id, 0, 0 ) for ( namespace_id, tag_id, current_delta, pending_delta ) in count_ids ) )
|
||||
|
||||
|
||||
def _Read( self, action, *args, **kwargs ):
|
||||
|
||||
if action == 'ac_counts': result = self._GetAutocompleteCounts( *args, **kwargs )
|
||||
else: raise Exception( 'db received an unknown read command: ' + action )
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _UpdateDB( self, version ):
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
||||
|
||||
def _Write( self, action, *args, **kwargs ):
|
||||
|
||||
if action == 'update_counts': result = self._UpdateCounts( *args, **kwargs )
|
||||
elif action == 'analyze': result = self._Analyze( *args, **kwargs )
|
||||
else: raise Exception( 'db received an unknown write command: ' + action )
|
||||
|
||||
return result
|
||||
|
||||
|
|
@ -325,6 +325,43 @@ def ShowTextClient( text ):
|
|||
|
||||
HydrusGlobals.client_controller.pub( 'message', job_key )
|
||||
|
||||
def SortTagsList( tags, sort_type ):
|
||||
|
||||
if sort_type in ( CC.SORT_BY_INCIDENCE_ASC, CC.SORT_BY_INCIDENCE_DESC ):
|
||||
|
||||
sort_type = CC.SORT_BY_INCIDENCE_ASC
|
||||
|
||||
|
||||
if sort_type in ( CC.SORT_BY_LEXICOGRAPHIC_DESC, CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_DESC ):
|
||||
|
||||
reverse = True
|
||||
|
||||
else:
|
||||
|
||||
reverse = False
|
||||
|
||||
|
||||
if sort_type in ( CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_ASC, CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_DESC ):
|
||||
|
||||
def key( tag ):
|
||||
|
||||
if ':' in tag:
|
||||
|
||||
return tag.split( ':', 1 )
|
||||
|
||||
else:
|
||||
|
||||
return ( '{', tag ) # '{' is above 'z' in ascii, so this works for most situations
|
||||
|
||||
|
||||
|
||||
else:
|
||||
|
||||
key = None
|
||||
|
||||
|
||||
tags.sort( key = key, reverse = reverse )
|
||||
|
||||
def WaitPolitely( page_key = None ):
|
||||
|
||||
if page_key is not None:
|
||||
|
@ -405,6 +442,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
self._dictionary[ 'booleans' ][ 'show_thumbnail_title_banner' ] = True
|
||||
self._dictionary[ 'booleans' ][ 'show_thumbnail_page' ] = True
|
||||
|
||||
self._dictionary[ 'booleans' ][ 'disable_cv_for_static_images' ] = False
|
||||
|
||||
self._dictionary[ 'noneable_integers' ] = {}
|
||||
|
||||
self._dictionary[ 'noneable_integers' ][ 'forced_search_limit' ] = None
|
||||
|
|
|
@ -185,6 +185,31 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
|
||||
|
||||
def _AnalyzeDatabase( self ):
|
||||
|
||||
message = 'This will gather statistical information on the database\'s indices, helping the query planner design efficient queries.'
|
||||
message += os.linesep * 2
|
||||
message += 'A \'soft\' analyze will only reanalyze those indices that are due for a check in the normal db maintenance cycle. This will typically take less than a second, but if it needs to do work, it will attempt not to take more that a few minutes, during which time your database will be locked and your gui may hang.'
|
||||
message += os.linesep * 2
|
||||
message += 'A \'full\' analyze will force a run over every index in the database. This can take substantially longer. If you do not have a specific reason to select this, it is probably pointless.'
|
||||
|
||||
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose how thorough your analyze will be.', yes_label = 'soft', no_label = 'full' ) as dlg:
|
||||
|
||||
result = dlg.ShowModal()
|
||||
|
||||
if result == wx.ID_YES:
|
||||
|
||||
stop_time = HydrusData.GetNow() + 120
|
||||
|
||||
self._controller.Write( 'analyze', stop_time = stop_time )
|
||||
|
||||
elif result == wx.ID_NO:
|
||||
|
||||
self._controller.Write( 'analyze', force_reanalyze = True )
|
||||
|
||||
|
||||
|
||||
|
||||
def _AutoRepoSetup( self ):
|
||||
|
||||
def do_it():
|
||||
|
@ -861,6 +886,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
submenu = wx.Menu()
|
||||
|
||||
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'vacuum_db' ), p( '&Vacuum' ), p( 'Rebuild the Database.' ) )
|
||||
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'analyze_db' ), p( '&Analyze' ), p( 'Reanalyze the Database.' ) )
|
||||
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'rebalance_client_files' ), p( '&Rebalance File Storage' ), p( 'Move your files around your chosen storage directories until they satisfy the weights you have set in the options.' ) )
|
||||
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'regenerate_thumbnails' ), p( '&Regenerate All Thumbnails' ), p( 'Delete all thumbnails and regenerate from original files.' ) )
|
||||
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetPermanentId( 'file_integrity' ), p( '&Check File Integrity' ), p( 'Review and fix all local file records.' ) )
|
||||
|
@ -2271,6 +2297,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
( command, data ) = action
|
||||
|
||||
if command == 'account_info': self._AccountInfo( data )
|
||||
elif command == 'analyze_db': self._AnalyzeDatabase()
|
||||
elif command == 'auto_repo_setup': self._AutoRepoSetup()
|
||||
elif command == 'auto_server_setup': self._AutoServerSetup()
|
||||
elif command == 'backup_database': self._controller.BackupDatabase()
|
||||
|
|
|
@ -1217,52 +1217,49 @@ class Canvas( wx.Window ):
|
|||
|
||||
HydrusGlobals.client_controller.ResetIdleTimer()
|
||||
|
||||
with wx.FrozenWindow( self ):
|
||||
self._current_media = media
|
||||
self._current_display_media = None
|
||||
self._total_drag_delta = ( 0, 0 )
|
||||
self._last_drag_coordinates = None
|
||||
|
||||
if self._media_container is not None:
|
||||
|
||||
self._current_media = media
|
||||
self._current_display_media = None
|
||||
self._total_drag_delta = ( 0, 0 )
|
||||
self._last_drag_coordinates = None
|
||||
self._media_container.Hide()
|
||||
|
||||
if self._media_container is not None:
|
||||
wx.CallAfter( self._media_container.Destroy )
|
||||
|
||||
self._media_container = None
|
||||
|
||||
|
||||
if self._current_media is not None:
|
||||
|
||||
self._current_display_media = self._current_media.GetDisplayMedia()
|
||||
|
||||
self._RecalcZoom()
|
||||
|
||||
( initial_size, initial_position ) = self._GetMediaContainerSizeAndPosition()
|
||||
|
||||
( initial_width, initial_height ) = initial_size
|
||||
|
||||
if self._current_display_media.GetLocationsManager().HasLocal() and initial_width > 0 and initial_height > 0:
|
||||
|
||||
self._media_container.Hide()
|
||||
self._media_container = MediaContainer( self, self._image_cache, self._current_display_media, initial_size, initial_position )
|
||||
|
||||
wx.CallAfter( self._media_container.Destroy )
|
||||
if self._claim_focus: self._media_container.SetFocus()
|
||||
|
||||
self._media_container = None
|
||||
self._PrefetchNeighbours()
|
||||
|
||||
else:
|
||||
|
||||
self._current_media = None
|
||||
|
||||
|
||||
if self._current_media is not None:
|
||||
|
||||
self._current_display_media = self._current_media.GetDisplayMedia()
|
||||
|
||||
( initial_size, initial_position ) = self._GetMediaContainerSizeAndPosition()
|
||||
|
||||
( initial_width, initial_height ) = initial_size
|
||||
|
||||
if self._current_display_media.GetLocationsManager().HasLocal() and initial_width > 0 and initial_height > 0:
|
||||
|
||||
self._RecalcZoom()
|
||||
|
||||
self._media_container = MediaContainer( self, self._image_cache, self._current_display_media, initial_size, initial_position )
|
||||
|
||||
if self._claim_focus: self._media_container.SetFocus()
|
||||
|
||||
self._PrefetchNeighbours()
|
||||
|
||||
else:
|
||||
|
||||
self._current_media = None
|
||||
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
@ -1335,16 +1332,7 @@ class CanvasWithDetails( Canvas ):
|
|||
|
||||
tags_i_want_to_display = list( tags_i_want_to_display )
|
||||
|
||||
if HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_DESC:
|
||||
|
||||
reverse = True
|
||||
|
||||
else:
|
||||
|
||||
reverse = False
|
||||
|
||||
|
||||
tags_i_want_to_display.sort( reverse = reverse )
|
||||
ClientData.SortTagsList( tags_i_want_to_display, HC.options[ 'default_tag_sort' ] )
|
||||
|
||||
current_y = 3
|
||||
|
||||
|
@ -1767,7 +1755,16 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithDetails ):
|
|||
self.GetParent().Close()
|
||||
|
||||
|
||||
def _DoManualPan( self, delta_x, delta_y ):
|
||||
def _DoManualPan( self, delta_x_step, delta_y_step ):
|
||||
|
||||
( my_x, my_y ) = self.GetClientSize()
|
||||
( media_x, media_y ) = self._media_container.GetClientSize()
|
||||
|
||||
x_pan_distance = min( my_x / 12, media_x / 12 )
|
||||
y_pan_distance = min( my_y / 12, media_y / 12 )
|
||||
|
||||
delta_x = delta_x_step * x_pan_distance
|
||||
delta_y = delta_y_step * y_pan_distance
|
||||
|
||||
( old_delta_x, old_delta_y ) = self._total_drag_delta
|
||||
|
||||
|
@ -2304,12 +2301,10 @@ class CanvasMediaListFilter( CanvasMediaList ):
|
|||
elif command == 'manage_tags': wx.CallAfter( self._ManageTags )
|
||||
elif command in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ):
|
||||
|
||||
distance = 20
|
||||
|
||||
if command == 'pan_up': self._DoManualPan( 0, -distance )
|
||||
elif command == 'pan_down': self._DoManualPan( 0, distance )
|
||||
elif command == 'pan_left': self._DoManualPan( -distance, 0 )
|
||||
elif command == 'pan_right': self._DoManualPan( distance, 0 )
|
||||
if command == 'pan_up': self._DoManualPan( 0, -1 )
|
||||
elif command == 'pan_down': self._DoManualPan( 0, 1 )
|
||||
elif command == 'pan_left': self._DoManualPan( -1, 0 )
|
||||
elif command == 'pan_right': self._DoManualPan( 1, 0 )
|
||||
|
||||
elif command == 'zoom_in': self._ZoomIn()
|
||||
elif command == 'zoom_out': self._ZoomOut()
|
||||
|
@ -2616,12 +2611,10 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
|
|||
elif command == 'open_externally': self._OpenExternally()
|
||||
elif command in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ):
|
||||
|
||||
distance = 20
|
||||
|
||||
if command == 'pan_up': self._DoManualPan( 0, -distance )
|
||||
elif command == 'pan_down': self._DoManualPan( 0, distance )
|
||||
elif command == 'pan_left': self._DoManualPan( -distance, 0 )
|
||||
elif command == 'pan_right': self._DoManualPan( distance, 0 )
|
||||
if command == 'pan_up': self._DoManualPan( 0, -1 )
|
||||
elif command == 'pan_down': self._DoManualPan( 0, 1 )
|
||||
elif command == 'pan_left': self._DoManualPan( -1, 0 )
|
||||
elif command == 'pan_right': self._DoManualPan( 1, 0 )
|
||||
|
||||
elif command == 'remove': self._Remove()
|
||||
elif command == 'slideshow': wx.CallLater( 1, self._StartSlideshow, data )
|
||||
|
@ -2911,12 +2904,10 @@ class CanvasMediaListCustomFilter( CanvasMediaListNavigable ):
|
|||
elif data == 'manage_tags': wx.CallLater( 1, self._ManageTags )
|
||||
elif data in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ):
|
||||
|
||||
distance = 20
|
||||
|
||||
if data == 'pan_up': self._DoManualPan( 0, -distance )
|
||||
elif data == 'pan_down': self._DoManualPan( 0, distance )
|
||||
elif data == 'pan_left': self._DoManualPan( -distance, 0 )
|
||||
elif data == 'pan_right': self._DoManualPan( distance, 0 )
|
||||
if data == 'pan_up': self._DoManualPan( 0, -1 )
|
||||
elif data == 'pan_down': self._DoManualPan( 0, 1 )
|
||||
elif data == 'pan_left': self._DoManualPan( -1, 0 )
|
||||
elif data == 'pan_right': self._DoManualPan( 1, 0 )
|
||||
|
||||
elif data == 'first': self._ShowFirst()
|
||||
elif data == 'last': self._ShowLast()
|
||||
|
|
|
@ -1172,7 +1172,10 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
|
|||
self._expand_parents = expand_parents
|
||||
self._null_entry_callable = null_entry_callable
|
||||
|
||||
if HC.options[ 'show_all_tags_in_autocomplete' ]: file_service_key = CC.COMBINED_FILE_SERVICE_KEY
|
||||
if tag_service_key != CC.COMBINED_TAG_SERVICE_KEY and HC.options[ 'show_all_tags_in_autocomplete' ]:
|
||||
|
||||
file_service_key = CC.COMBINED_FILE_SERVICE_KEY
|
||||
|
||||
|
||||
AutoCompleteDropdownTags.__init__( self, parent, file_service_key, tag_service_key )
|
||||
|
||||
|
@ -3714,7 +3717,7 @@ class ListBoxTagsSelection( ListBoxTags ):
|
|||
|
||||
self._sort = HC.options[ 'default_tag_sort' ]
|
||||
|
||||
if not include_counts and self._sort not in ( CC.SORT_BY_LEXICOGRAPHIC_ASC, CC.SORT_BY_LEXICOGRAPHIC_DESC ):
|
||||
if not include_counts and self._sort in ( CC.SORT_BY_INCIDENCE_ASC, CC.SORT_BY_INCIDENCE_DESC ):
|
||||
|
||||
self._sort = CC.SORT_BY_LEXICOGRAPHIC_ASC
|
||||
|
||||
|
@ -3851,19 +3854,7 @@ class ListBoxTagsSelection( ListBoxTags ):
|
|||
|
||||
def _SortTags( self ):
|
||||
|
||||
if self._sort == CC.SORT_BY_LEXICOGRAPHIC_ASC:
|
||||
|
||||
key = None
|
||||
|
||||
reverse = False
|
||||
|
||||
elif self._sort == CC.SORT_BY_LEXICOGRAPHIC_DESC:
|
||||
|
||||
key = None
|
||||
|
||||
reverse = True
|
||||
|
||||
elif self._sort in ( CC.SORT_BY_INCIDENCE_ASC, CC.SORT_BY_INCIDENCE_DESC ):
|
||||
if self._sort in ( CC.SORT_BY_INCIDENCE_ASC, CC.SORT_BY_INCIDENCE_DESC ):
|
||||
|
||||
tags_to_count = collections.Counter()
|
||||
|
||||
|
@ -3891,8 +3882,12 @@ class ListBoxTagsSelection( ListBoxTags ):
|
|||
reverse = False
|
||||
|
||||
|
||||
|
||||
self._ordered_strings.sort( key = key, reverse = reverse )
|
||||
self._ordered_strings.sort( key = key, reverse = reverse )
|
||||
|
||||
else:
|
||||
|
||||
ClientData.SortTagsList( self._ordered_strings, self._sort )
|
||||
|
||||
|
||||
self._TextsHaveChanged()
|
||||
|
||||
|
@ -6063,13 +6058,17 @@ class StaticBoxSorterForListBoxTags( StaticBox ):
|
|||
|
||||
self._sorter.Append( 'lexicographic (a-z)', CC.SORT_BY_LEXICOGRAPHIC_ASC )
|
||||
self._sorter.Append( 'lexicographic (z-a)', CC.SORT_BY_LEXICOGRAPHIC_DESC )
|
||||
self._sorter.Append( 'lexicographic (a-z) (grouped by namespace)', CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_ASC )
|
||||
self._sorter.Append( 'lexicographic (z-a) (grouped by namespace)', CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_DESC )
|
||||
self._sorter.Append( 'incidence (desc)', CC.SORT_BY_INCIDENCE_DESC )
|
||||
self._sorter.Append( 'incidence (asc)', CC.SORT_BY_INCIDENCE_ASC )
|
||||
|
||||
if HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_ASC: self._sorter.Select( 0 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_DESC: self._sorter.Select( 1 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_DESC: self._sorter.Select( 2 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_ASC: self._sorter.Select( 3 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_ASC: self._sorter.Select( 2 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_DESC: self._sorter.Select( 3 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_DESC: self._sorter.Select( 4 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_ASC: self._sorter.Select( 5 )
|
||||
|
||||
self._sorter.Bind( wx.EVT_CHOICE, self.EventSort )
|
||||
|
||||
|
|
|
@ -804,7 +804,7 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
self._none_panel = ClientGUICommon.StaticBox( self, 'non-service actions' )
|
||||
|
||||
self._none_actions = wx.Choice( self._none_panel, choices = [ 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'delete', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right' ] )
|
||||
self._none_actions = wx.Choice( self._none_panel, choices = [ 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'delete', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'remove' ] )
|
||||
|
||||
self._ok_none = wx.Button( self._none_panel, label = 'ok' )
|
||||
self._ok_none.Bind( wx.EVT_BUTTON, self.EventOKNone )
|
||||
|
@ -2098,7 +2098,7 @@ class DialogInputShortcut( Dialog ):
|
|||
|
||||
self._shortcut = ClientGUICommon.Shortcut( self, modifier, key )
|
||||
|
||||
self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', 'frame_back', 'frame_next', 'manage_ratings', 'manage_tags', 'new_page', 'refresh', 'set_media_focus', 'set_search_focus', 'show_hide_splitters', 'synchronised_wait_switch', 'previous', 'next', 'first', 'last', 'undo', 'redo', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right' ] )
|
||||
self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', 'frame_back', 'frame_next', 'manage_ratings', 'manage_tags', 'new_page', 'refresh', 'set_media_focus', 'set_search_focus', 'show_hide_splitters', 'synchronised_wait_switch', 'previous', 'next', 'first', 'last', 'undo', 'redo', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'remove' ] )
|
||||
|
||||
self._ok = wx.Button( self, id= wx.ID_OK, label = 'Ok' )
|
||||
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
|
||||
|
@ -4424,7 +4424,7 @@ class DialogShortcuts( Dialog ):
|
|||
|
||||
for ( key, action ) in key_dict.items():
|
||||
|
||||
if action in ( 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'fullscreen_switch', 'frame_back', 'frame_next', 'previous', 'next', 'first', 'last', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'open_externally' ):
|
||||
if action in ( 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'fullscreen_switch', 'frame_back', 'frame_next', 'previous', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'remove' ):
|
||||
|
||||
service_key = None
|
||||
|
||||
|
|
|
@ -4357,9 +4357,13 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
||||
|
||||
self._new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
self._fit_to_canvas = wx.CheckBox( self, label = '' )
|
||||
self._animation_start_position = wx.SpinCtrl( self, min = 0, max = 100 )
|
||||
|
||||
self._disable_cv_for_static_images = wx.CheckBox( self, label = '' )
|
||||
|
||||
self._mime_media_viewer_panel = ClientGUICommon.StaticBox( self, 'media viewer mime handling' )
|
||||
|
||||
self._mime_media_viewer_actions = {}
|
||||
|
@ -4386,6 +4390,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._fit_to_canvas.SetValue( HC.options[ 'fit_to_canvas' ] )
|
||||
self._animation_start_position.SetValue( int( HC.options[ 'animation_start_position' ] * 100.0 ) )
|
||||
self._disable_cv_for_static_images.SetValue( self._new_options.GetBoolean( 'disable_cv_for_static_images' ) )
|
||||
|
||||
gridbox = wx.FlexGridSizer( 0, 2 )
|
||||
|
||||
|
@ -4413,6 +4418,9 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
gridbox.AddF( wx.StaticText( self, label = 'Start animations this % in: ' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._animation_start_position, CC.FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self, label = 'Disable OpenCV for static images: ' ), CC.FLAGS_MIXED )
|
||||
gridbox.AddF( self._disable_cv_for_static_images, CC.FLAGS_MIXED )
|
||||
|
||||
vbox.AddF( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
vbox.AddF( self._mime_media_viewer_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
@ -4433,6 +4441,8 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
HC.options[ 'mime_media_viewer_actions' ] = mime_media_viewer_actions
|
||||
|
||||
self._new_options.SetBoolean( 'disable_cv_for_static_images', self._disable_cv_for_static_images.GetValue() )
|
||||
|
||||
|
||||
|
||||
class _ServerPanel( wx.Panel ):
|
||||
|
@ -4926,17 +4936,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
new_thumbnail_dimensions = [ self._thumbnail_width.GetValue(), self._thumbnail_height.GetValue() ]
|
||||
|
||||
if new_thumbnail_dimensions != HC.options[ 'thumbnail_dimensions' ]:
|
||||
|
||||
text = 'You have changed the thumbnail dimensions, which will mean deleting all the old resized thumbnails right now, during which time the database will be locked. If you have tens or hundreds of thousands of files, this could take a long time.'
|
||||
text += os.linesep * 2
|
||||
text += 'Are you sure you want to change your thumbnail dimensions?'
|
||||
|
||||
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_YES: HC.options[ 'thumbnail_dimensions' ] = new_thumbnail_dimensions
|
||||
|
||||
|
||||
HC.options[ 'thumbnail_dimensions' ] = new_thumbnail_dimensions
|
||||
|
||||
HC.options[ 'thumbnail_cache_size' ] = self._thumbnail_cache_size.GetValue() * 1048576
|
||||
HC.options[ 'preview_cache_size' ] = self._preview_cache_size.GetValue() * 1048576
|
||||
|
@ -4972,6 +4972,8 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
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( 'lexicographic (a-z) (grouped by namespace)', CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_ASC )
|
||||
self._default_tag_sort.Append( 'lexicographic (z-a) (grouped by namespace)', CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_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 )
|
||||
|
||||
|
@ -4985,6 +4987,8 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
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_LEXICOGRAPHIC_NAMESPACE_ASC: self._default_tag_sort.Select( 2 )
|
||||
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_DESC: self._default_tag_sort.Select( 3 )
|
||||
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 )
|
||||
|
||||
|
|
|
@ -3032,7 +3032,7 @@ class Thumbnail( Selectable ):
|
|||
|
||||
else:
|
||||
|
||||
volumes_sorted = HydrusTags.SortTags( volumes )
|
||||
volumes_sorted = HydrusTags.SortNumericTags( volumes )
|
||||
|
||||
collections_string_append = 'v' + str( volumes_sorted[0] ) + '-' + str( volumes_sorted[-1] )
|
||||
|
||||
|
@ -3048,7 +3048,7 @@ class Thumbnail( Selectable ):
|
|||
|
||||
else:
|
||||
|
||||
chapters_sorted = HydrusTags.SortTags( chapters )
|
||||
chapters_sorted = HydrusTags.SortNumericTags( chapters )
|
||||
|
||||
collections_string_append = 'c' + str( chapters_sorted[0] ) + '-' + str( chapters_sorted[-1] )
|
||||
|
||||
|
@ -3067,7 +3067,7 @@ class Thumbnail( Selectable ):
|
|||
|
||||
else:
|
||||
|
||||
pages_sorted = HydrusTags.SortTags( pages )
|
||||
pages_sorted = HydrusTags.SortNumericTags( pages )
|
||||
|
||||
collections_string_append = 'p' + str( pages_sorted[0] ) + '-' + str( pages_sorted[-1] )
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import numpy.core.multiarray # important this comes before cv!
|
||||
import cv2
|
||||
import HydrusImageHandling
|
||||
import HydrusGlobals
|
||||
|
||||
if cv2.__version__.startswith( '2' ):
|
||||
|
||||
|
@ -33,6 +34,13 @@ def EfficientlyThumbnailNumpyImage( numpy_image, ( target_x, target_y ) ):
|
|||
|
||||
def GenerateNumpyImage( path ):
|
||||
|
||||
new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
if new_options.GetBoolean( 'disable_cv_for_static_images' ):
|
||||
|
||||
raise Exception( 'Cannot read image--OpenCV for images is currently disabled.' )
|
||||
|
||||
|
||||
numpy_image = cv2.imread( path, flags = -1 ) # flags = -1 loads alpha channel, if present
|
||||
|
||||
if numpy_image is None:
|
||||
|
@ -77,6 +85,13 @@ def GenerateNumPyImageFromPILImage( pil_image ):
|
|||
|
||||
def GeneratePerceptualHash( path ):
|
||||
|
||||
new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
if new_options.GetBoolean( 'disable_cv_for_static_images' ):
|
||||
|
||||
raise Exception( 'Cannot generate perceptual hash--OpenCV for images is currently disabled.' )
|
||||
|
||||
|
||||
numpy_image = cv2.imread( path, IMREAD_UNCHANGED )
|
||||
|
||||
( y, x, depth ) = numpy_image.shape
|
||||
|
|
|
@ -1173,7 +1173,7 @@ class MediaSingleton( Media ):
|
|||
|
||||
else:
|
||||
|
||||
volumes_sorted = HydrusTags.SortTags( volumes )
|
||||
volumes_sorted = HydrusTags.SortNumericTags( volumes )
|
||||
|
||||
title_string_append = 'volumes ' + str( volumes_sorted[0] ) + '-' + str( volumes_sorted[-1] )
|
||||
|
||||
|
@ -1192,7 +1192,7 @@ class MediaSingleton( Media ):
|
|||
|
||||
else:
|
||||
|
||||
chapters_sorted = HydrusTags.SortTags( chapters )
|
||||
chapters_sorted = HydrusTags.SortNumericTags( chapters )
|
||||
|
||||
title_string_append = 'chapters ' + str( chapters_sorted[0] ) + '-' + str( chapters_sorted[-1] )
|
||||
|
||||
|
@ -1211,7 +1211,7 @@ class MediaSingleton( Media ):
|
|||
|
||||
else:
|
||||
|
||||
pages_sorted = HydrusTags.SortTags( pages )
|
||||
pages_sorted = HydrusTags.SortNumericTags( pages )
|
||||
|
||||
title_string_append = 'pages ' + str( pages_sorted[0] ) + '-' + str( pages_sorted[-1] )
|
||||
|
||||
|
@ -1508,7 +1508,7 @@ class TagsManagerSimple( object ):
|
|||
|
||||
tags = [ tag.split( ':', 1 )[1] for tag in tags ]
|
||||
|
||||
tags = HydrusTags.SortTags( tags )
|
||||
tags = HydrusTags.SortNumericTags( tags )
|
||||
|
||||
tags = tuple( ( HydrusTags.ConvertTagToSortable( tag ) for tag in tags ) )
|
||||
|
||||
|
|
|
@ -16,25 +16,36 @@ import wx
|
|||
|
||||
def GenerateHydrusBitmap( path, compressed = True ):
|
||||
|
||||
numpy_image = None
|
||||
new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
try:
|
||||
|
||||
numpy_image = ClientImageHandling.GenerateNumpyImage( path )
|
||||
|
||||
return GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = compressed )
|
||||
|
||||
except:
|
||||
|
||||
if numpy_image is not None:
|
||||
|
||||
del numpy_image
|
||||
|
||||
if new_options.GetBoolean( 'disable_cv_for_static_images' ):
|
||||
|
||||
pil_image = HydrusImageHandling.GeneratePILImage( path )
|
||||
|
||||
return GenerateHydrusBitmapFromPILImage( pil_image, compressed = compressed )
|
||||
|
||||
else:
|
||||
|
||||
numpy_image = None
|
||||
|
||||
try:
|
||||
|
||||
numpy_image = ClientImageHandling.GenerateNumpyImage( path )
|
||||
|
||||
return GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = compressed )
|
||||
|
||||
except:
|
||||
|
||||
if numpy_image is not None:
|
||||
|
||||
del numpy_image
|
||||
|
||||
|
||||
pil_image = HydrusImageHandling.GeneratePILImage( path )
|
||||
|
||||
return GenerateHydrusBitmapFromPILImage( pil_image, compressed = compressed )
|
||||
|
||||
|
||||
|
||||
def GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = True ):
|
||||
|
||||
|
@ -119,15 +130,9 @@ class RasterContainerImage( RasterContainer ):
|
|||
|
||||
time.sleep( 0.00001 )
|
||||
|
||||
try:
|
||||
|
||||
numpy_image = ClientImageHandling.GenerateNumpyImage( self._path )
|
||||
|
||||
resized_numpy_image = ClientImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
|
||||
|
||||
hydrus_bitmap = GenerateHydrusBitmapFromNumPyImage( resized_numpy_image )
|
||||
|
||||
except:
|
||||
new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
if new_options.GetBoolean( 'disable_cv_for_static_images' ):
|
||||
|
||||
pil_image = HydrusImageHandling.GeneratePILImage( self._path )
|
||||
|
||||
|
@ -135,6 +140,25 @@ class RasterContainerImage( RasterContainer ):
|
|||
|
||||
hydrus_bitmap = GenerateHydrusBitmapFromPILImage( resized_pil_image )
|
||||
|
||||
else:
|
||||
|
||||
try:
|
||||
|
||||
numpy_image = ClientImageHandling.GenerateNumpyImage( self._path )
|
||||
|
||||
resized_numpy_image = ClientImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
|
||||
|
||||
hydrus_bitmap = GenerateHydrusBitmapFromNumPyImage( resized_numpy_image )
|
||||
|
||||
except:
|
||||
|
||||
pil_image = HydrusImageHandling.GeneratePILImage( self._path )
|
||||
|
||||
resized_pil_image = HydrusImageHandling.EfficientlyResizePILImage( pil_image, self._target_resolution )
|
||||
|
||||
hydrus_bitmap = GenerateHydrusBitmapFromPILImage( resized_pil_image )
|
||||
|
||||
|
||||
|
||||
self._hydrus_bitmap = hydrus_bitmap
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 17
|
||||
SOFTWARE_VERSION = 202
|
||||
SOFTWARE_VERSION = 203
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -76,8 +76,16 @@ def ParseFileArguments( path ):
|
|||
|
||||
if mime in HC.MIMES_WITH_THUMBNAILS:
|
||||
|
||||
try: thumbnail = HydrusFileHandling.GenerateThumbnail( path )
|
||||
except: raise HydrusExceptions.ForbiddenException( 'Could not generate thumbnail from that file.' )
|
||||
try:
|
||||
|
||||
thumbnail = HydrusFileHandling.GenerateThumbnail( path )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
tb = traceback.format_exc()
|
||||
|
||||
raise HydrusExceptions.ForbiddenException( 'Could not generate thumbnail from that file:' + os.linesep + tb )
|
||||
|
||||
|
||||
args[ 'thumbnail' ] = thumbnail
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ def FilterNamespaces( tags, namespaces ):
|
|||
|
||||
return result
|
||||
|
||||
def SortTags( tags ):
|
||||
def SortNumericTags( tags ):
|
||||
|
||||
tags = list( tags )
|
||||
|
||||
|
|
|
@ -312,10 +312,17 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
stale_time_delta = 30 * 86400
|
||||
|
||||
all_names = [ name for ( name, ) in self._c.execute( 'SELECT name FROM sqlite_master;' ) ]
|
||||
|
||||
existing_names_to_timestamps = dict( self._c.execute( 'SELECT name, timestamp FROM analyze_timestamps;' ).fetchall() )
|
||||
|
||||
db_names = [ name for ( index, name, path ) in self._c.execute( 'PRAGMA database_list;' ) if name not in ( 'mem', 'temp' ) ]
|
||||
|
||||
all_names = set()
|
||||
|
||||
for db_name in db_names:
|
||||
|
||||
all_names.update( ( name for ( name, ) in self._c.execute( 'SELECT name FROM ' + db_name + '.sqlite_master;' ) ) )
|
||||
|
||||
|
||||
names_to_analyze = [ name for name in all_names if name not in existing_names_to_timestamps or HydrusData.TimeHasPassed( existing_names_to_timestamps[ name ] + stale_time_delta ) ]
|
||||
|
||||
random.shuffle( names_to_analyze )
|
||||
|
@ -325,9 +332,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
HydrusGlobals.server_busy = True
|
||||
|
||||
|
||||
while len( names_to_analyze ) > 0:
|
||||
|
||||
name = names_to_analyze.pop()
|
||||
for name in names_to_analyze:
|
||||
|
||||
started = HydrusData.GetNowPrecise()
|
||||
|
||||
|
@ -350,12 +355,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._c.execute( 'ANALYZE sqlite_master;' ) # this reloads the current stats into the query planner
|
||||
|
||||
still_more_to_do = len( names_to_analyze ) > 0
|
||||
|
||||
HydrusGlobals.server_busy = False
|
||||
|
||||
return still_more_to_do
|
||||
|
||||
|
||||
def _ApproveFilePetition( self, service_id, account_id, hash_ids, reason_id ):
|
||||
|
||||
|
@ -2562,6 +2563,11 @@ class DB( HydrusDB.HydrusDB ):
|
|||
self._c.execute( 'BEGIN IMMEDIATE;' )
|
||||
|
||||
|
||||
if version == 202:
|
||||
|
||||
self._c.execute( 'DELETE FROM analyze_timestamps;' )
|
||||
|
||||
|
||||
HydrusData.Print( 'The server has updated to version ' + str( version + 1 ) )
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
|
Loading…
Reference in New Issue