Use voussoirkit/worms.

This commit is contained in:
voussoir 2021-09-15 22:41:38 -07:00
parent 1f7e299821
commit f510bb88c0
No known key found for this signature in database
GPG key ID: 5F7554F8C26DACCB
5 changed files with 353 additions and 779 deletions

View file

@ -4,13 +4,6 @@ import warnings
from . import exceptions from . import exceptions
def _get_relevant_photodb(instance):
if isinstance(instance, objects.ObjectBase):
photodb = instance.photodb
else:
photodb = instance
return photodb
def required_feature(features): def required_feature(features):
''' '''
Declare that the photodb or object method requires certain 'enable_*' Declare that the photodb or object method requires certain 'enable_*'
@ -22,7 +15,7 @@ def required_feature(features):
def wrapper(method): def wrapper(method):
@functools.wraps(method) @functools.wraps(method)
def wrapped_required_feature(self, *args, **kwargs): def wrapped_required_feature(self, *args, **kwargs):
photodb = _get_relevant_photodb(self) photodb = self._photodb
config = photodb.config['enable_feature'] config = photodb.config['enable_feature']
# Using the received string like "photo.new", try to navigate the # Using the received string like "photo.new", try to navigate the
@ -67,42 +60,3 @@ def time_me(function):
print(f'{function.__name__}: {duration:0.8f}') print(f'{function.__name__}: {duration:0.8f}')
return result return result
return timed_function return timed_function
def transaction(method):
'''
Open a savepoint before running the method.
If the method fails, roll back to that savepoint.
'''
@functools.wraps(method)
def wrapped_transaction(self, *args, **kwargs):
if isinstance(self, objects.ObjectBase):
self.assert_not_deleted()
photodb = _get_relevant_photodb(self)
commit = kwargs.pop('commit', False)
is_root = len(photodb.savepoints) == 0
savepoint_id = photodb.savepoint(message=method.__qualname__)
try:
result = method(self, *args, **kwargs)
except Exception as exc:
photodb.log.debug(f'{method} raised {repr(exc)}.')
photodb.rollback(savepoint=savepoint_id)
raise
if commit:
photodb.commit(message=method.__qualname__)
elif not is_root:
photodb.release_savepoint(savepoint=savepoint_id)
return result
return wrapped_transaction
# Circular dependency.
# I would like to un-circularize this, but as long as objects and photodb are
# using the same decorators, and the decorator needs to follow the photodb
# instance of the object...
# I'd rather not create separate decorators, or write hasattr-based decisions.
from . import objects

View file

@ -71,6 +71,9 @@ class NoSuchUser(NoSuch):
# EXISTS ########################################################################################### # EXISTS ###########################################################################################
# The following inits store a copy of the object so that the exception catcher
# can do something with it. It's not related to the string formatting.
class Exists(EtiquetteException): class Exists(EtiquetteException):
pass pass
@ -160,14 +163,6 @@ class Unauthorized(EtiquetteException):
class WrongLogin(EtiquetteException): class WrongLogin(EtiquetteException):
error_message = 'Wrong username-password combination.' error_message = 'Wrong username-password combination.'
# SQL ERRORS #######################################################################################
class BadSQL(EtiquetteException):
pass
class BadTable(BadSQL):
error_message = 'Table "{}" does not exist.'
# GENERAL ERRORS ################################################################################### # GENERAL ERRORS ###################################################################################
class BadDataDirectory(EtiquetteException): class BadDataDirectory(EtiquetteException):

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,7 @@ class Migrator:
query = 'SELECT name, sql FROM sqlite_master WHERE type == "table"' query = 'SELECT name, sql FROM sqlite_master WHERE type == "table"'
self.tables = { self.tables = {
name: {'create': sql, 'transfer': f'INSERT INTO {name} SELECT * FROM {name}_old'} name: {'create': sql, 'transfer': f'INSERT INTO {name} SELECT * FROM {name}_old'}
for (name, sql) in self.photodb.sql_select(query) for (name, sql) in self.photodb.select(query)
} }
# The user may be adding entirely new tables derived from the data of # The user may be adding entirely new tables derived from the data of
@ -37,7 +37,7 @@ class Migrator:
self.existing_tables = set(self.tables) self.existing_tables = set(self.tables)
query = 'SELECT name, sql FROM sqlite_master WHERE type == "index" AND name NOT LIKE "sqlite_%"' query = 'SELECT name, sql FROM sqlite_master WHERE type == "index" AND name NOT LIKE "sqlite_%"'
self.indices = list(self.photodb.sql_select(query)) self.indices = list(self.photodb.select(query))
def go(self): def go(self):
# This loop is split in many parts, because otherwise if table A # This loop is split in many parts, because otherwise if table A
@ -45,33 +45,33 @@ class Migrator:
# be pointing to the version of B which has not been reconstructed yet, # be pointing to the version of B which has not been reconstructed yet,
# which is about to get renamed to B_old and then A's reference will be # which is about to get renamed to B_old and then A's reference will be
# broken. # broken.
self.photodb.sql_execute('PRAGMA foreign_keys = OFF') self.photodb.execute('PRAGMA foreign_keys = OFF')
self.photodb.sql_execute('BEGIN') self.photodb.execute('BEGIN')
for (name, table) in self.tables.items(): for (name, table) in self.tables.items():
if name not in self.existing_tables: if name not in self.existing_tables:
continue continue
self.photodb.sql_execute(f'ALTER TABLE {name} RENAME TO {name}_old') self.photodb.execute(f'ALTER TABLE {name} RENAME TO {name}_old')
for (name, table) in self.tables.items(): for (name, table) in self.tables.items():
self.photodb.sql_execute(table['create']) self.photodb.execute(table['create'])
for (name, table) in self.tables.items(): for (name, table) in self.tables.items():
self.photodb.sql_execute(table['transfer']) self.photodb.execute(table['transfer'])
for (name, query) in self.tables.items(): for (name, query) in self.tables.items():
if name not in self.existing_tables: if name not in self.existing_tables:
continue continue
self.photodb.sql_execute(f'DROP TABLE {name}_old') self.photodb.execute(f'DROP TABLE {name}_old')
for (name, query) in self.indices: for (name, query) in self.indices:
self.photodb.sql_execute(query) self.photodb.execute(query)
def upgrade_1_to_2(photodb): def upgrade_1_to_2(photodb):
''' '''
In this version, a column `tagged_at` was added to the Photos table, to keep In this version, a column `tagged_at` was added to the Photos table, to keep
track of the last time the photo's tags were edited (added or removed). track of the last time the photo's tags were edited (added or removed).
''' '''
photodb.sql_executescript(''' photodb.executescript('''
BEGIN; BEGIN;
ALTER TABLE photos ADD COLUMN tagged_at INT; ALTER TABLE photos ADD COLUMN tagged_at INT;
''') ''')
@ -82,7 +82,7 @@ def upgrade_2_to_3(photodb):
with id, username, password hash, and a timestamp. with id, username, password hash, and a timestamp.
Plus some indices. Plus some indices.
''' '''
photodb.sql_executescript(''' photodb.executescript('''
BEGIN; BEGIN;
CREATE TABLE users( CREATE TABLE users(
@ -101,7 +101,7 @@ def upgrade_3_to_4(photodb):
''' '''
Add an `author_id` column to Photos. Add an `author_id` column to Photos.
''' '''
photodb.sql_executescript(''' photodb.executescript('''
BEGIN; BEGIN;
ALTER TABLE photos ADD COLUMN author_id TEXT; ALTER TABLE photos ADD COLUMN author_id TEXT;
@ -113,7 +113,7 @@ def upgrade_4_to_5(photodb):
''' '''
Add table `bookmarks` and its indices. Add table `bookmarks` and its indices.
''' '''
photodb.sql_executescript(''' photodb.executescript('''
BEGIN; BEGIN;
CREATE TABLE bookmarks( CREATE TABLE bookmarks(
@ -140,15 +140,15 @@ def upgrade_5_to_6(photodb):
into `album_group_rel` into `album_group_rel`
- Gives albums their own last_id value, starting with the current tag value. - Gives albums their own last_id value, starting with the current tag value.
''' '''
photodb.sql_execute('BEGIN') photodb.execute('BEGIN')
# 1. Start the id_numbers.albums value at the tags value so that the number # 1. Start the id_numbers.albums value at the tags value so that the number
# can continue to increment safely and separately, instead of starting at # can continue to increment safely and separately, instead of starting at
# zero and bumping into existing albums. # zero and bumping into existing albums.
last_id = photodb.sql_select_one('SELECT last_id FROM id_numbers WHERE tab == "tags"')[0] last_id = photodb.select_one('SELECT last_id FROM id_numbers WHERE tab == "tags"')[0]
photodb.sql_execute('INSERT INTO id_numbers VALUES("albums", ?)', [last_id]) photodb.execute('INSERT INTO id_numbers VALUES("albums", ?)', [last_id])
# 2. Now's a good chance to rename 'index_grouprel' to 'index_taggroup'. # 2. Now's a good chance to rename 'index_grouprel' to 'index_taggroup'.
photodb.sql_executescript(''' photodb.executescript('''
DROP INDEX IF EXISTS index_grouprel_parentid; DROP INDEX IF EXISTS index_grouprel_parentid;
DROP INDEX IF EXISTS index_grouprel_memberid; DROP INDEX IF EXISTS index_grouprel_memberid;
CREATE INDEX index_taggroup_parentid ON tag_group_rel(parentid); CREATE INDEX index_taggroup_parentid ON tag_group_rel(parentid);
@ -157,27 +157,27 @@ def upgrade_5_to_6(photodb):
# 3. All of the album group relationships need to be moved into their # 3. All of the album group relationships need to be moved into their
# own table, out of tag_group_rel # own table, out of tag_group_rel
photodb.sql_executescript(''' photodb.executescript('''
CREATE TABLE album_group_rel(parentid TEXT, memberid TEXT); CREATE TABLE album_group_rel(parentid TEXT, memberid TEXT);
CREATE INDEX index_albumgroup_parentid ON album_group_rel(parentid); CREATE INDEX index_albumgroup_parentid ON album_group_rel(parentid);
CREATE INDEX index_albumgroup_memberid ON album_group_rel(memberid); CREATE INDEX index_albumgroup_memberid ON album_group_rel(memberid);
''') ''')
album_ids = [id for (id,) in photodb.sql_select('SELECT id FROM albums')] album_ids = list(photodb.select_column('SELECT id FROM albums'))
for album_id in album_ids: for album_id in album_ids:
query = 'SELECT * FROM tag_group_rel WHERE parentid == ? OR memberid == ?' query = 'SELECT * FROM tag_group_rel WHERE parentid == ? OR memberid == ?'
bindings = [album_id, album_id] bindings = [album_id, album_id]
grouprels = list(photodb.sql_select(query, bindings)) grouprels = list(photodb.select(query, bindings))
if not grouprels: if not grouprels:
continue continue
for grouprel in grouprels: for grouprel in grouprels:
photodb.sql_execute('INSERT INTO album_group_rel VALUES(?, ?)', grouprel) photodb.execute('INSERT INTO album_group_rel VALUES(?, ?)', grouprel)
query = 'DELETE FROM tag_group_rel WHERE parentid == ? OR memberid == ?' query = 'DELETE FROM tag_group_rel WHERE parentid == ? OR memberid == ?'
bindings = [album_id, album_id] bindings = [album_id, album_id]
photodb.sql_execute(query, bindings) photodb.execute(query, bindings)
def upgrade_6_to_7(photodb): def upgrade_6_to_7(photodb):
''' '''
@ -187,12 +187,11 @@ def upgrade_6_to_7(photodb):
Most of the indices were renamed. Most of the indices were renamed.
''' '''
photodb.sql_execute('BEGIN') photodb.execute('BEGIN')
query = 'SELECT name FROM sqlite_master WHERE type == "index" AND name NOT LIKE "sqlite_%"' query = 'SELECT name FROM sqlite_master WHERE type == "index" AND name NOT LIKE "sqlite_%"'
indices = photodb.sql_select(query) indices = photodb.select_column(query)
indices = [name for (name,) in indices]
for index in indices: for index in indices:
photodb.sql_execute(f'DROP INDEX {index}') photodb.execute(f'DROP INDEX {index}')
m = Migrator(photodb) m = Migrator(photodb)
m.tables['album_associated_directories']['create'] = ''' m.tables['album_associated_directories']['create'] = '''
@ -226,7 +225,7 @@ def upgrade_6_to_7(photodb):
m.go() m.go()
photodb.sql_executescript(''' photodb.executescript('''
CREATE INDEX IF NOT EXISTS index_album_associated_directories_albumid on CREATE INDEX IF NOT EXISTS index_album_associated_directories_albumid on
album_associated_directories(albumid); album_associated_directories(albumid);
CREATE INDEX IF NOT EXISTS index_album_associated_directories_directory on CREATE INDEX IF NOT EXISTS index_album_associated_directories_directory on
@ -260,7 +259,7 @@ def upgrade_7_to_8(photodb):
''' '''
Give the Tags table a description field. Give the Tags table a description field.
''' '''
photodb.sql_executescript(''' photodb.executescript('''
BEGIN; BEGIN;
ALTER TABLE tags ADD COLUMN description TEXT; ALTER TABLE tags ADD COLUMN description TEXT;
''') ''')
@ -269,7 +268,7 @@ def upgrade_8_to_9(photodb):
''' '''
Give the Photos table a searchhidden field. Give the Photos table a searchhidden field.
''' '''
photodb.sql_executescript(''' photodb.executescript('''
BEGIN; BEGIN;
ALTER TABLE photos ADD COLUMN searchhidden INT; ALTER TABLE photos ADD COLUMN searchhidden INT;
@ -286,7 +285,7 @@ def upgrade_9_to_10(photodb):
Previously, the stored path was unnecessarily high and contained the PDB's Previously, the stored path was unnecessarily high and contained the PDB's
data_directory, reducing portability. data_directory, reducing portability.
''' '''
photodb.sql_execute('BEGIN') photodb.execute('BEGIN')
photos = list(photodb.search(has_thumbnail=True, is_searchhidden=None, yield_albums=False)) photos = list(photodb.search(has_thumbnail=True, is_searchhidden=None, yield_albums=False))
# Since we're doing it all at once, I'm going to cheat and skip the # Since we're doing it all at once, I'm going to cheat and skip the
@ -296,7 +295,7 @@ def upgrade_9_to_10(photodb):
new_thumbnail_path = photo.make_thumbnail_filepath() new_thumbnail_path = photo.make_thumbnail_filepath()
new_thumbnail_path = new_thumbnail_path.absolute_path new_thumbnail_path = new_thumbnail_path.absolute_path
new_thumbnail_path = '.' + new_thumbnail_path.replace(thumbnail_dir, '') new_thumbnail_path = '.' + new_thumbnail_path.replace(thumbnail_dir, '')
photodb.sql_execute( photodb.execute(
'UPDATE photos SET thumbnail = ? WHERE id == ?', 'UPDATE photos SET thumbnail = ? WHERE id == ?',
[new_thumbnail_path, photo.id] [new_thumbnail_path, photo.id]
) )
@ -352,7 +351,7 @@ def upgrade_11_to_12(photodb):
improve the speed of individual relation searching, important for the improve the speed of individual relation searching, important for the
new intersection-based search. new intersection-based search.
''' '''
photodb.sql_executescript(''' photodb.executescript('''
BEGIN; BEGIN;
CREATE INDEX IF NOT EXISTS index_photo_tag_rel_photoid_tagid on photo_tag_rel(photoid, tagid); CREATE INDEX IF NOT EXISTS index_photo_tag_rel_photoid_tagid on photo_tag_rel(photoid, tagid);
@ -439,7 +438,7 @@ def upgrade_14_to_15(photodb):
m.go() m.go()
photodb.sql_execute('CREATE INDEX index_photos_dev_ino ON photos(dev_ino);') photodb.execute('CREATE INDEX index_photos_dev_ino ON photos(dev_ino);')
for photo in photodb.get_photos_by_recent(): for photo in photodb.get_photos_by_recent():
if not photo.real_path.is_file: if not photo.real_path.is_file:
@ -449,7 +448,7 @@ def upgrade_14_to_15(photodb):
if dev == 0 or ino == 0: if dev == 0 or ino == 0:
continue continue
dev_ino = f'{dev},{ino}' dev_ino = f'{dev},{ino}'
photodb.sql_execute('UPDATE photos SET dev_ino = ? WHERE id == ?', [dev_ino, photo.id]) photodb.execute('UPDATE photos SET dev_ino = ? WHERE id == ?', [dev_ino, photo.id])
def upgrade_15_to_16(photodb): def upgrade_15_to_16(photodb):
''' '''
@ -502,9 +501,9 @@ def upgrade_15_to_16(photodb):
m.go() m.go()
for (id, filepath) in photodb.sql_select('SELECT id, filepath FROM photos'): for (id, filepath) in photodb.select('SELECT id, filepath FROM photos'):
basename = os.path.basename(filepath) basename = os.path.basename(filepath)
photodb.sql_execute('UPDATE photos SET basename = ? WHERE id == ?', [basename, id]) photodb.execute('UPDATE photos SET basename = ? WHERE id == ?', [basename, id])
def upgrade_16_to_17(photodb): def upgrade_16_to_17(photodb):
''' '''
@ -677,7 +676,7 @@ def upgrade_all(data_directory):
''' '''
photodb = etiquette.photodb.PhotoDB(data_directory, create=False, skip_version_check=True) photodb = etiquette.photodb.PhotoDB(data_directory, create=False, skip_version_check=True)
current_version = photodb.sql_execute('PRAGMA user_version').fetchone()[0] current_version = photodb.execute('PRAGMA user_version').fetchone()[0]
needed_version = etiquette.constants.DATABASE_VERSION needed_version = etiquette.constants.DATABASE_VERSION
if current_version == needed_version: if current_version == needed_version:
@ -690,7 +689,7 @@ def upgrade_all(data_directory):
upgrade_function = eval(upgrade_function) upgrade_function = eval(upgrade_function)
try: try:
photodb.sql_execute('PRAGMA foreign_keys = ON') photodb.execute('PRAGMA foreign_keys = ON')
upgrade_function(photodb) upgrade_function(photodb)
except Exception as exc: except Exception as exc:
photodb.rollback() photodb.rollback()