354 lines
13 KiB
Python
354 lines
13 KiB
Python
import itertools
|
|
import sqlite3
|
|
import typing
|
|
|
|
from hydrus.core import HydrusConstants as HC
|
|
from hydrus.core import HydrusData
|
|
from hydrus.core import HydrusExceptions
|
|
from hydrus.core import HydrusSerialisable
|
|
|
|
from hydrus.client import ClientConstants as CC
|
|
from hydrus.client import ClientLocation
|
|
from hydrus.client import ClientSearch
|
|
from hydrus.client import ClientServices
|
|
from hydrus.client.db import ClientDBModule
|
|
|
|
class FileSearchContextLeaf( object ):
|
|
|
|
def __init__( self, file_service_id: int, tag_service_id: int ):
|
|
|
|
# special thing about a leaf is that it has a specific current domain in the caches
|
|
# no all known files or deleted files here. leaf might not be file cross-referenced, but it does cover something we can search fast
|
|
|
|
# it should get tag display type at some point, maybe current/pending too
|
|
|
|
self.file_service_id = file_service_id
|
|
self.tag_service_id = tag_service_id
|
|
|
|
|
|
class FileSearchContextBranch( object ):
|
|
|
|
def __init__( self, file_search_context: ClientSearch.FileSearchContext, file_service_ids: typing.Collection[ int ], tag_service_ids: typing.Collection[ int ], file_location_is_cross_referenced: bool ):
|
|
|
|
self.file_search_context = file_search_context
|
|
|
|
self.file_service_ids = file_service_ids
|
|
self.tag_service_ids = tag_service_ids
|
|
self.file_location_is_cross_referenced = file_location_is_cross_referenced
|
|
|
|
|
|
def FileLocationIsCrossReferenced( self ) -> bool:
|
|
|
|
return self.file_location_is_cross_referenced
|
|
|
|
|
|
def GetFileSearchContext( self ) -> ClientSearch.FileSearchContext:
|
|
|
|
return self.file_search_context
|
|
|
|
|
|
def IterateLeaves( self ):
|
|
|
|
for ( file_service_id, tag_service_id ) in itertools.product( self.file_service_ids, self.tag_service_ids ):
|
|
|
|
yield FileSearchContextLeaf( file_service_id, tag_service_id )
|
|
|
|
|
|
|
|
def IterateTableIdPairs( self ):
|
|
|
|
for ( file_service_id, tag_service_id ) in itertools.product( self.file_service_ids, self.tag_service_ids ):
|
|
|
|
yield ( file_service_id, tag_service_id )
|
|
|
|
|
|
|
|
class ClientDBMasterServices( ClientDBModule.ClientDBModule ):
|
|
|
|
def __init__( self, cursor: sqlite3.Cursor ):
|
|
|
|
ClientDBModule.ClientDBModule.__init__( self, 'client services master', cursor )
|
|
|
|
self._service_ids_to_services = {}
|
|
self._service_keys_to_service_ids = {}
|
|
|
|
self.local_update_service_id = None
|
|
self.trash_service_id = None
|
|
self.combined_local_file_service_id = None
|
|
self.combined_local_media_service_id = None
|
|
self.combined_file_service_id = None
|
|
self.combined_deleted_file_service_id = None
|
|
self.combined_tag_service_id = None
|
|
|
|
self._InitCaches()
|
|
|
|
|
|
def _GetCriticalTableNames( self ) -> typing.Collection[ str ]:
|
|
|
|
return {
|
|
'main.services'
|
|
}
|
|
|
|
|
|
def _GetInitialTableGenerationDict( self ) -> dict:
|
|
|
|
return {
|
|
'main.services' : ( 'CREATE TABLE IF NOT EXISTS {} ( service_id INTEGER PRIMARY KEY AUTOINCREMENT, service_key BLOB_BYTES UNIQUE, service_type INTEGER, name TEXT, dictionary_string TEXT );', 400 )
|
|
}
|
|
|
|
|
|
def _InitCaches( self ):
|
|
|
|
if self._Execute( 'SELECT 1 FROM sqlite_master WHERE name = ?;', ( 'services', ) ).fetchone() is not None:
|
|
|
|
all_data = self._Execute( 'SELECT service_id, service_key, service_type, name, dictionary_string FROM services;' ).fetchall()
|
|
|
|
for ( service_id, service_key, service_type, name, dictionary_string ) in all_data:
|
|
|
|
dictionary = HydrusSerialisable.CreateFromString( dictionary_string )
|
|
|
|
service = ClientServices.GenerateService( service_key, service_type, name, dictionary )
|
|
|
|
self._service_ids_to_services[ service_id ] = service
|
|
|
|
self._service_keys_to_service_ids[ service_key ] = service_id
|
|
|
|
|
|
self.local_update_service_id = self.GetServiceId( CC.LOCAL_UPDATE_SERVICE_KEY )
|
|
self.trash_service_id = self.GetServiceId( CC.TRASH_SERVICE_KEY )
|
|
self.combined_local_file_service_id = self.GetServiceId( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
|
|
self.combined_file_service_id = self.GetServiceId( CC.COMBINED_FILE_SERVICE_KEY )
|
|
|
|
try:
|
|
|
|
self.combined_deleted_file_service_id = self.GetServiceId( CC.COMBINED_DELETED_FILE_SERVICE_KEY )
|
|
self.combined_local_media_service_id = self.GetServiceId( CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY )
|
|
|
|
except HydrusExceptions.DataMissing:
|
|
|
|
# version 465/486 it might not be in yet
|
|
|
|
pass
|
|
|
|
|
|
self.combined_tag_service_id = self.GetServiceId( CC.COMBINED_TAG_SERVICE_KEY )
|
|
|
|
|
|
|
|
def AddService( self, service_key, service_type, name, dictionary: HydrusSerialisable.SerialisableBase ) -> int:
|
|
|
|
dictionary_string = dictionary.DumpToString()
|
|
|
|
self._Execute( 'INSERT INTO services ( service_key, service_type, name, dictionary_string ) VALUES ( ?, ?, ?, ? );', ( sqlite3.Binary( service_key ), service_type, name, dictionary_string ) )
|
|
|
|
service_id = self._GetLastRowId()
|
|
|
|
service = ClientServices.GenerateService( service_key, service_type, name, dictionary )
|
|
|
|
self._service_ids_to_services[ service_id ] = service
|
|
self._service_keys_to_service_ids[ service_key ] = service_id
|
|
|
|
if service_key == CC.LOCAL_UPDATE_SERVICE_KEY:
|
|
|
|
self.local_update_service_id = service_id
|
|
|
|
elif service_key == CC.TRASH_SERVICE_KEY:
|
|
|
|
self.trash_service_id = service_id
|
|
|
|
elif service_key == CC.COMBINED_LOCAL_FILE_SERVICE_KEY:
|
|
|
|
self.combined_local_file_service_id = service_id
|
|
|
|
elif service_key == CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY:
|
|
|
|
self.combined_local_media_service_id = service_id
|
|
|
|
elif service_key == CC.COMBINED_FILE_SERVICE_KEY:
|
|
|
|
self.combined_file_service_id = service_id
|
|
|
|
elif service_key == CC.COMBINED_DELETED_FILE_SERVICE_KEY:
|
|
|
|
self.combined_deleted_file_service_id = service_id
|
|
|
|
elif service_key == CC.COMBINED_TAG_SERVICE_KEY:
|
|
|
|
self.combined_tag_service_id = service_id
|
|
|
|
|
|
return service_id
|
|
|
|
|
|
def DeleteService( self, service_id ):
|
|
|
|
if service_id in self._service_ids_to_services:
|
|
|
|
service_key = self._service_ids_to_services[ service_id ].GetServiceKey()
|
|
|
|
del self._service_ids_to_services[ service_id ]
|
|
|
|
if service_key in self._service_keys_to_service_ids:
|
|
|
|
del self._service_keys_to_service_ids[ service_key ]
|
|
|
|
|
|
|
|
self._Execute( 'DELETE FROM services WHERE service_id = ?;', ( service_id, ) )
|
|
|
|
|
|
def FileServiceIsCoveredByAllLocalFiles( self, service_id ) -> bool:
|
|
|
|
service_type = self.GetService( service_id ).GetServiceType()
|
|
|
|
return service_type in HC.FILE_SERVICES_COVERED_BY_COMBINED_LOCAL_FILE
|
|
|
|
|
|
def GetFileSearchContextBranch( self, file_search_context: ClientSearch.FileSearchContext ) -> FileSearchContextBranch:
|
|
|
|
location_context = file_search_context.GetLocationContext()
|
|
tag_context = file_search_context.GetTagContext()
|
|
|
|
( file_service_keys, file_location_is_cross_referenced ) = location_context.GetCoveringCurrentFileServiceKeys()
|
|
|
|
search_file_service_ids = []
|
|
|
|
for file_service_key in file_service_keys:
|
|
|
|
try:
|
|
|
|
search_file_service_id = self.GetServiceId( file_service_key )
|
|
|
|
except HydrusExceptions.DataMissing:
|
|
|
|
HydrusData.ShowText( 'A query was run for a file service that does not exist! If you just removed a service, you might want to try checking the search and/or restarting the client.' )
|
|
|
|
continue
|
|
|
|
|
|
search_file_service_ids.append( search_file_service_id )
|
|
|
|
|
|
if tag_context.IsAllKnownTags():
|
|
|
|
search_tag_service_ids = self.GetServiceIds( HC.REAL_TAG_SERVICES )
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
search_tag_service_ids = ( self.GetServiceId( tag_context.service_key ), )
|
|
|
|
except HydrusExceptions.DataMissing:
|
|
|
|
HydrusData.ShowText( 'A query was run for a tag service that does not exist! If you just removed a service, you might want to try checking the search and/or restarting the client.' )
|
|
|
|
search_tag_service_ids = []
|
|
|
|
|
|
|
|
return FileSearchContextBranch( file_search_context, search_file_service_ids, search_tag_service_ids, file_location_is_cross_referenced )
|
|
|
|
|
|
def GetNonDupeName( self, name ) -> str:
|
|
|
|
existing_names = { service.GetName() for service in self._service_ids_to_services.values() }
|
|
|
|
return HydrusData.GetNonDupeName( name, existing_names )
|
|
|
|
|
|
def GetService( self, service_id ) -> ClientServices.Service:
|
|
|
|
if service_id in self._service_ids_to_services:
|
|
|
|
return self._service_ids_to_services[ service_id ]
|
|
|
|
|
|
raise HydrusExceptions.DataMissing( 'Service id error in database: id "{}" does not exist!'.format( service_id ) )
|
|
|
|
|
|
def GetServices( self, limited_types = HC.ALL_SERVICES ):
|
|
|
|
return [ service for service in self._service_ids_to_services.values() if service.GetServiceType() in limited_types ]
|
|
|
|
|
|
def GetServiceId( self, service_key: bytes ) -> int:
|
|
|
|
if service_key in self._service_keys_to_service_ids:
|
|
|
|
return self._service_keys_to_service_ids[ service_key ]
|
|
|
|
|
|
raise HydrusExceptions.DataMissing( 'Service id error in database: key "{}" does not exist!'.format( service_key.hex() ) )
|
|
|
|
|
|
def GetServiceIds( self, service_types ) -> typing.Set[ int ]:
|
|
|
|
return { service_id for ( service_id, service ) in self._service_ids_to_services.items() if service.GetServiceType() in service_types }
|
|
|
|
|
|
def GetServiceIdsToServiceKeys( self ) -> typing.Dict[ int, bytes ]:
|
|
|
|
return { service_id : service_key for ( service_key, service_id ) in self._service_keys_to_service_ids.items() }
|
|
|
|
|
|
def GetServiceKey( self, service_id: int ) -> bytes:
|
|
|
|
return self.GetService( service_id ).GetServiceKey()
|
|
|
|
|
|
def GetServiceKeys( self ) -> typing.Set[ bytes ]:
|
|
|
|
return set( self._service_keys_to_service_ids.keys() )
|
|
|
|
|
|
def GetServiceType( self, service_id ) -> ClientServices.Service:
|
|
|
|
if service_id in self._service_ids_to_services:
|
|
|
|
return self._service_ids_to_services[ service_id ].GetServiceType()
|
|
|
|
|
|
raise HydrusExceptions.DataMissing( 'Service id error in database: id "{}" does not exist!'.format( service_id ) )
|
|
|
|
|
|
def GetTablesAndColumnsThatUseDefinitions( self, content_type: int ) -> typing.List[ typing.Tuple[ str, str ] ]:
|
|
|
|
return []
|
|
|
|
|
|
def LocationContextIsCoveredByCombinedLocalFiles( self, location_context: ClientLocation.LocationContext ):
|
|
|
|
if location_context.IncludesDeleted():
|
|
|
|
return False
|
|
|
|
|
|
service_ids = { self.GetServiceId( service_key ) for service_key in location_context.current_service_keys }
|
|
|
|
for service_id in service_ids:
|
|
|
|
if not self.FileServiceIsCoveredByAllLocalFiles( service_id ):
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
def UpdateService( self, service: ClientServices.Service ):
|
|
|
|
( service_key, service_type, name, dictionary ) = service.ToTuple()
|
|
|
|
service_id = self.GetServiceId( service_key )
|
|
|
|
dictionary_string = dictionary.DumpToString()
|
|
|
|
self._Execute( 'UPDATE services SET name = ?, dictionary_string = ? WHERE service_id = ?;', ( name, dictionary_string, service_id ) )
|
|
|
|
self._service_ids_to_services[ service_id ] = service
|
|
|
|
service.SetClean()
|
|
|