Version 203

This commit is contained in:
Hydrus Network Developer 2016-04-27 14:20:37 -05:00
parent 596667f655
commit 4ec034b181
20 changed files with 358 additions and 604 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,7 +53,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 202
SOFTWARE_VERSION = 203
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

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

View File

@ -124,7 +124,7 @@ def FilterNamespaces( tags, namespaces ):
return result
def SortTags( tags ):
def SortNumericTags( tags ):
tags = list( tags )

View File

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