diff --git a/etiquette/constants.py b/etiquette/constants.py index aa514ac..8d94b2c 100644 --- a/etiquette/constants.py +++ b/etiquette/constants.py @@ -31,11 +31,14 @@ SQL_LASTID_COLUMNS = [ 'table', 'last_id', ] +SQL_ALBUM_DIRECTORY_COLUMNS = [ + 'albumid', + 'directory', +] SQL_ALBUM_COLUMNS = [ 'id', 'title', 'description', - 'associated_directory', ] SQL_BOOKMARK_COLUMNS = [ 'id', @@ -92,6 +95,7 @@ SQL_USER_COLUMNS = [ _sql_dictify = lambda columns: {key:index for (index, key) in enumerate(columns)} SQL_ALBUM = _sql_dictify(SQL_ALBUM_COLUMNS) +SQL_ALBUM_DIRECTORY = _sql_dictify(SQL_ALBUM_DIRECTORY_COLUMNS) SQL_ALBUMGROUP = _sql_dictify(SQL_ALBUMGROUP_COLUMNS) SQL_BOOKMARK = _sql_dictify(SQL_BOOKMARK_COLUMNS) SQL_ALBUMPHOTO = _sql_dictify(SQL_ALBUMPHOTO_COLUMNS) diff --git a/etiquette/exceptions.py b/etiquette/exceptions.py index d6a7654..71f07e0 100644 --- a/etiquette/exceptions.py +++ b/etiquette/exceptions.py @@ -54,6 +54,13 @@ class NoSuchUser(NoSuch): # EXISTS +@with_error_type +class AlbumExists(WithFormat): + error_message = 'Album "{}" already exists.' + def __init__(self, album): + self.album = album + WithFormat.__init__(self, album.id) + @with_error_type class GroupExists(WithFormat): error_message = '{member} already in group {group}' diff --git a/etiquette/photodb.py b/etiquette/photodb.py index 9fad894..f243a78 100644 --- a/etiquette/photodb.py +++ b/etiquette/photodb.py @@ -29,23 +29,64 @@ logging.getLogger('PIL.PngImagePlugin').setLevel(logging.WARNING) # Note: Setting user_version pragma in init sequence is safe because it only # happens after the out-of-date check occurs, so no chance of accidentally # overwriting it. -DATABASE_VERSION = 6 +DATABASE_VERSION = 7 DB_INIT = ''' PRAGMA count_changes = OFF; PRAGMA cache_size = 10000; PRAGMA user_version = {user_version}; + +---------------------------------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS album_associated_directories( + albumid TEXT, + directory TEXT COLLATE NOCASE +); +CREATE INDEX IF NOT EXISTS index_album_associated_directories_albumid on + album_associated_directories(albumid); +CREATE INDEX IF NOT EXISTS index_album_associated_directories_directory on + album_associated_directories(directory); +---------------------------------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS album_group_rel( + parentid TEXT, + memberid TEXT +); +CREATE INDEX IF NOT EXISTS index_album_group_rel_parentid on album_group_rel(parentid); +CREATE INDEX IF NOT EXISTS index_album_group_rel_memberid on album_group_rel(memberid); +---------------------------------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS album_photo_rel( + albumid TEXT, + photoid TEXT +); +CREATE INDEX IF NOT EXISTS index_album_photo_rel_albumid on album_photo_rel(albumid); +CREATE INDEX IF NOT EXISTS index_album_photo_rel_photoid on album_photo_rel(photoid); +---------------------------------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS albums( id TEXT, title TEXT, - description TEXT, - associated_directory TEXT COLLATE NOCASE + description TEXT ); +CREATE INDEX IF NOT EXISTS index_albums_id on albums(id); +---------------------------------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS bookmarks( id TEXT, title TEXT, url TEXT, author_id TEXT ); +CREATE INDEX IF NOT EXISTS index_bookmarks_id on bookmarks(id); +CREATE INDEX IF NOT EXISTS index_bookmarks_author on bookmarks(author_id); +---------------------------------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS id_numbers( + tab TEXT, + last_id TEXT +); +---------------------------------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS photo_tag_rel( + photoid TEXT, + tagid TEXT +); +CREATE INDEX IF NOT EXISTS index_photo_tag_rel_photoid on photo_tag_rel(photoid); +CREATE INDEX IF NOT EXISTS index_photo_tag_rel_tagid on photo_tag_rel(tagid); +---------------------------------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS photos( id TEXT, filepath TEXT COLLATE NOCASE, @@ -62,82 +103,42 @@ CREATE TABLE IF NOT EXISTS photos( tagged_at INT, author_id TEXT ); -CREATE TABLE IF NOT EXISTS tags( - id TEXT, - name TEXT -); -CREATE TABLE IF NOT EXISTS album_photo_rel( - albumid TEXT, - photoid TEXT -); -CREATE TABLE IF NOT EXISTS album_group_rel( - parentid TEXT, - memberid TEXT -); -CREATE TABLE IF NOT EXISTS photo_tag_rel( - photoid TEXT, - tagid TEXT -); +CREATE INDEX IF NOT EXISTS index_photos_id on photos(id); +CREATE INDEX IF NOT EXISTS index_photos_filepath on photos(filepath COLLATE NOCASE); +CREATE INDEX IF NOT EXISTS index_photos_override_filename on + photos(override_filename COLLATE NOCASE); +CREATE INDEX IF NOT EXISTS index_photos_created on photos(created); +CREATE INDEX IF NOT EXISTS index_photos_extension on photos(extension); +CREATE INDEX IF NOT EXISTS index_photos_author_id on photos(author_id); +---------------------------------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS tag_group_rel( parentid TEXT, memberid TEXT ); +CREATE INDEX IF NOT EXISTS index_tag_group_rel_parentid on tag_group_rel(parentid); +CREATE INDEX IF NOT EXISTS index_tag_group_rel_memberid on tag_group_rel(memberid); +---------------------------------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS tag_synonyms( name TEXT, mastername TEXT ); -CREATE TABLE IF NOT EXISTS id_numbers( - tab TEXT, - last_id TEXT +CREATE INDEX IF NOT EXISTS index_tag_synonyms_name on tag_synonyms(name); +---------------------------------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS tags( + id TEXT, + name TEXT ); +CREATE INDEX IF NOT EXISTS index_tags_id on tags(id); +CREATE INDEX IF NOT EXISTS index_tags_name on tags(name); +---------------------------------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS users( id TEXT, username TEXT COLLATE NOCASE, password BLOB, created INT ); - --- Album -CREATE INDEX IF NOT EXISTS index_album_id on albums(id); - --- Album-photo relation -CREATE INDEX IF NOT EXISTS index_albumrel_albumid on album_photo_rel(albumid); -CREATE INDEX IF NOT EXISTS index_albumrel_photoid on album_photo_rel(photoid); - --- Album-group relation -CREATE INDEX IF NOT EXISTS index_albumgroup_parentid on album_group_rel(parentid); -CREATE INDEX IF NOT EXISTS index_albumgroup_memberid on album_group_rel(memberid); - --- Bookmark -CREATE INDEX IF NOT EXISTS index_bookmark_id on bookmarks(id); -CREATE INDEX IF NOT EXISTS index_bookmark_author on bookmarks(author_id); - --- Photo -CREATE INDEX IF NOT EXISTS index_photo_id on photos(id); -CREATE INDEX IF NOT EXISTS index_photo_path on photos(filepath COLLATE NOCASE); -CREATE INDEX IF NOT EXISTS index_photo_fakepath on photos(override_filename COLLATE NOCASE); -CREATE INDEX IF NOT EXISTS index_photo_created on photos(created); -CREATE INDEX IF NOT EXISTS index_photo_extension on photos(extension); -CREATE INDEX IF NOT EXISTS index_photo_author on photos(author_id); - --- Tag -CREATE INDEX IF NOT EXISTS index_tag_id on tags(id); -CREATE INDEX IF NOT EXISTS index_tag_name on tags(name); - --- Photo-tag relation -CREATE INDEX IF NOT EXISTS index_tagrel_photoid on photo_tag_rel(photoid); -CREATE INDEX IF NOT EXISTS index_tagrel_tagid on photo_tag_rel(tagid); - --- Tag-synonym relation -CREATE INDEX IF NOT EXISTS index_tagsyn_name on tag_synonyms(name); - --- Tag-group relation -CREATE INDEX IF NOT EXISTS index_taggroup_parentid on tag_group_rel(parentid); -CREATE INDEX IF NOT EXISTS index_taggroup_memberid on tag_group_rel(memberid); - --- User -CREATE INDEX IF NOT EXISTS index_user_id on users(id); -CREATE INDEX IF NOT EXISTS index_user_username on users(username COLLATE NOCASE); +CREATE INDEX IF NOT EXISTS index_users_id on users(id); +CREATE INDEX IF NOT EXISTS index_users_username on users(username COLLATE NOCASE); '''.format(user_version=DATABASE_VERSION) @@ -260,11 +261,12 @@ class PDBAlbumMixin: ''' filepath = os.path.abspath(filepath) cur = self.sql.cursor() - cur.execute('SELECT * FROM albums WHERE associated_directory == ?', [filepath]) + cur.execute('SELECT * FROM album_associated_directories WHERE directory == ?', [filepath]) fetch = cur.fetchone() if fetch is None: raise exceptions.NoSuchAlbum(filepath) - return self.get_album(fetch[constants.SQL_ALBUM['id']]) + album_id = fetch[constants.SQL_ALBUM_DIRECTORY['albumid']] + return self.get_album(album_id) def get_albums(self): yield from self.get_things(thing_type='album') @@ -287,29 +289,45 @@ class PDBAlbumMixin: albumid = self.generate_id('albums') title = title or '' description = description or '' - if associated_directory is not None: - associated_directory = os.path.abspath(associated_directory) - if not isinstance(title, str): raise TypeError('Title must be string, not %s' % type(title)) if not isinstance(description, str): raise TypeError('Description must be string, not %s' % type(description)) + cur = self.sql.cursor() + if associated_directory is not None: + associated_directory = os.path.abspath(associated_directory) + cur.execute( + 'SELECT * FROM album_associated_directories WHERE directory == ?', + [associated_directory] + ) + fetch = cur.fetchone() + if fetch is not None: + album = self.get_album(fetch[constants.SQL_ALBUM_DIRECTORY['albumid']]) + raise exceptions.AlbumExists(album) + self.log.debug('New Album: %s' % title) data = { 'id': albumid, 'title': title, 'description': description, - 'associated_directory': associated_directory, } (qmarks, bindings) = helpers.binding_filler(constants.SQL_ALBUM_COLUMNS, data) query = 'INSERT INTO albums VALUES(%s)' % qmarks - cur = self.sql.cursor() cur.execute(query, bindings) - album = objects.Album(self, data) + + if associated_directory is not None: + data = { + 'albumid': albumid, + 'directory': associated_directory, + } + (qmarks, bindings) = helpers.binding_filler(constants.SQL_ALBUM_DIRECTORY_COLUMNS, data) + query = 'INSERT INTO album_associated_directories VALUES(%s)' % qmarks + cur.execute(query, bindings) + if photos: for photo in photos: photo = self.get_photo(photo) diff --git a/utilities/database_upgrader.py b/utilities/database_upgrader.py index d4ed75f..a1a1160 100644 --- a/utilities/database_upgrader.py +++ b/utilities/database_upgrader.py @@ -103,6 +103,41 @@ def upgrade_5_to_6(sql): [album_id, album_id] ) +def upgrade_6_to_7(sql): + ''' + Most of the indices were renamed, so delete them and let them regenerate + next time. + + Albums lost their `associated_directory` column, and it has been moved to a + separate table `album_associated_directories`, so that we can have albums + which load from multiple directories. + ''' + cur = sql.cursor() + cur.execute('SELECT name FROM sqlite_master WHERE type == "index"') + indices = [x[0] for x in cur.fetchall()] + for index in indices: + cur.execute('DROP INDEX %s' % index) + + + cur.execute(''' + CREATE TABLE album_associated_directories( + albumid TEXT, + directory TEXT COLLATE NOCASE + )''') + cur.execute('ALTER TABLE albums RENAME TO deleting_albums') + cur.execute(''' + CREATE TABLE albums( + id TEXT, + title TEXT, + description TEXT + )''') + cur.execute('INSERT INTO albums SELECT id, title, description FROM deleting_albums') + cur.execute(''' + INSERT INTO album_associated_directories SELECT id, associated_directory FROM deleting_albums + ''') + cur.execute('DROP TABLE deleting_albums') + + def upgrade_all(database_filename): ''' Given the filename of a phototagger database, apply all of the needed