diff --git a/etiquette/decorators.py b/etiquette/decorators.py index a01fe5c..9c7f378 100644 --- a/etiquette/decorators.py +++ b/etiquette/decorators.py @@ -55,3 +55,16 @@ def time_me(function): print('%s: %0.8f' % (function.__name__, end-start)) return result return timed_function + +def transaction(method): + @functools.wraps(method) + def wrapped(self, *args, **kwargs): + try: + ret = method(self, *args, **kwargs) + return ret + except Exception as e: + self.log.debug('Rolling back') + print(e) + self.sql.rollback() + raise + return wrapped diff --git a/etiquette/objects.py b/etiquette/objects.py index cb43cbb..fd8f60b 100644 --- a/etiquette/objects.py +++ b/etiquette/objects.py @@ -17,6 +17,14 @@ class ObjectBase: super().__init__() self.photodb = photodb + @property + def log(self): + return self.photodb.log + + @property + def sql(self): + return self.photodb.sql + def __eq__(self, other): return ( isinstance(other, type(self)) and @@ -39,6 +47,7 @@ class GroupableMixin: group_sql_index = None group_table = None + @decorators.transaction def add(self, member, *, commit=True): ''' Add a child object to this group. @@ -99,6 +108,7 @@ class GroupableMixin: results.sort(key=lambda x: x.id) return results + @decorators.transaction def delete(self, *, delete_children=False, commit=True): ''' Delete this object's relationships to other groupables. @@ -160,6 +170,7 @@ class GroupableMixin: parentid = fetch[self.group_sql_index['parentid']] return self.group_getter(id=parentid) + @decorators.transaction def join_group(self, group, *, commit=True): ''' Leave the current group, then call `group.add(self)`. @@ -175,6 +186,7 @@ class GroupableMixin: self.leave_group(commit=commit) group.add(self, commit=commit) + @decorators.transaction def leave_group(self, *, commit=True): ''' Leave the current group and become independent. @@ -228,6 +240,8 @@ class Album(ObjectBase, GroupableMixin): self._sum_bytes_photos = None self._sum_bytes_albums = None + @decorators.transaction + @decorators.transaction def add_photo(self, photo, *, commit=True): if self.photodb != photo.photodb: raise ValueError('Not the same PhotoDB') @@ -241,6 +255,7 @@ class Album(ObjectBase, GroupableMixin): self.photodb.log.debug('Committing - add photo to album') self.photodb.commit() + @decorators.transaction def add_tag_to_all(self, tag, *, nested_children=True, commit=True): ''' Add this tag to every photo in the album. Saves you from having to @@ -272,6 +287,7 @@ class Album(ObjectBase, GroupableMixin): directories = [pathclass.Path(x) for x in directories] return directories + @decorators.transaction def delete(self, *, delete_children=False, commit=True): self.photodb.log.debug('Deleting album {album:r}'.format(album=self)) GroupableMixin.delete(self, delete_children=delete_children, commit=False) @@ -291,6 +307,7 @@ class Album(ObjectBase, GroupableMixin): else: return self.id + @decorators.transaction def edit(self, title=None, description=None, *, commit=True): ''' Change the title or description. Leave None to keep current value. @@ -335,6 +352,7 @@ class Album(ObjectBase, GroupableMixin): photos.sort(key=lambda x: x.basename.lower()) return photos + @decorators.transaction def remove_photo(self, photo, *, commit=True): if not self.has_photo(photo): return @@ -386,12 +404,14 @@ class Bookmark(ObjectBase): def __repr__(self): return 'Bookmark:{id}'.format(id=self.id) + @decorators.transaction def delete(self, *, commit=True): cur = self.photodb.sql.cursor() cur.execute('DELETE FROM bookmarks WHERE id == ?', [self.id]) if commit: self.photodb.commit() + @decorators.transaction def edit(self, title=None, url=None, *, commit=True): if title is None and url is None: return @@ -468,6 +488,7 @@ class Photo(ObjectBase): def _uncache(self): self.photodb.caches['photo'].remove(self.id) + @decorators.transaction def add_tag(self, tag, *, commit=True): if not self.photodb.config['enable_photo_add_remove_tag']: raise exceptions.FeatureDisabled('photo.add_tag, photo.remove_tag') @@ -526,6 +547,7 @@ class Photo(ObjectBase): for tag in other_photo.tags(): self.add_tag(tag) + @decorators.transaction def delete(self, *, delete_file=False, commit=True): ''' Delete the Photo and its relation to any tags and albums. @@ -555,6 +577,7 @@ class Photo(ObjectBase): return helpers.seconds_to_hms(self.duration) #@decorators.time_me + @decorators.transaction def generate_thumbnail(self, *, commit=True, **special): ''' special: @@ -691,6 +714,7 @@ class Photo(ObjectBase): return hopeful_filepath #@decorators.time_me + @decorators.transaction def reload_metadata(self, *, commit=True): ''' Load the file's height, width, etc as appropriate for this type of file. @@ -749,6 +773,7 @@ class Photo(ObjectBase): self.photodb.log.debug('Committing - reload metadata') self.photodb.commit() + @decorators.transaction def relocate(self, new_filepath, *, allow_duplicates=False, commit=True): ''' Point the Photo object to a different filepath. @@ -781,6 +806,7 @@ class Photo(ObjectBase): self.photodb.log.debug('Commit - relocate photo') self.photodb.commit() + @decorators.transaction def remove_tag(self, tag, *, commit=True): if not self.photodb.config['enable_photo_add_remove_tag']: raise exceptions.FeatureDisabled('photo.add_tag, photo.remove_tag') @@ -802,6 +828,7 @@ class Photo(ObjectBase): self.photodb.log.debug('Committing - remove photo tag') self.photodb.commit() + @decorators.transaction def rename_file(self, new_filename, *, move=False, commit=True): ''' Rename the file on the disk as well as in the database. @@ -921,6 +948,7 @@ class Tag(ObjectBase, GroupableMixin): def _uncache(self): self.photodb.caches['tag'].remove(self.id) + @decorators.transaction def add_synonym(self, synname, *, commit=True): synname = self.photodb.normalize_tagname(synname) @@ -945,6 +973,7 @@ class Tag(ObjectBase, GroupableMixin): return synname + @decorators.transaction def convert_to_synonym(self, mastertag, *, commit=True): ''' Convert this tag into a synonym for a different tag. @@ -993,6 +1022,7 @@ class Tag(ObjectBase, GroupableMixin): self.photodb.log.debug('Committing - convert to synonym') self.photodb.commit() + @decorators.transaction def delete(self, *, delete_children=False, commit=True): self.photodb.log.debug('Deleting tag {tag:r}'.format(tag=self)) self.photodb._cached_frozen_children = None @@ -1018,6 +1048,7 @@ class Tag(ObjectBase, GroupableMixin): self._cached_qualified_name = qualname return qualname + @decorators.transaction def remove_synonym(self, synname, *, commit=True): ''' Delete a synonym. @@ -1043,6 +1074,7 @@ class Tag(ObjectBase, GroupableMixin): self.photodb.log.debug('Committing - remove synonym') self.photodb.commit() + @decorators.transaction def rename(self, new_name, *, apply_to_synonyms=True, commit=True): ''' Rename the tag. Does not affect its relation to Photos or tag groups. diff --git a/etiquette/photodb.py b/etiquette/photodb.py index 67f78f0..161184f 100644 --- a/etiquette/photodb.py +++ b/etiquette/photodb.py @@ -280,6 +280,7 @@ class PDBAlbumMixin: if album.parent() is None: yield album + @decorators.transaction def new_album( self, title=None, @@ -364,6 +365,7 @@ class PDBBookmarkMixin: def get_bookmarks(self): yield from self.get_things(thing_type='bookmark') + @decorators.transaction def new_bookmark(self, url, title=None, *, author=None, commit=True): if not self.config['enable_new_bookmark']: raise exceptions.FeatureDisabled('new_bookmark') @@ -438,6 +440,7 @@ class PDBPhotoMixin: if count <= 0: break + @decorators.transaction def new_photo( self, filepath, @@ -518,6 +521,7 @@ class PDBPhotoMixin: self.commit() return photo + @decorators.transaction def purge_deleted_files(self, photos=None, *, commit=True): ''' Remove Photo entries if their corresponding file is no longer found. @@ -536,6 +540,7 @@ class PDBPhotoMixin: self.log.debug('Committing - purge deleted photos') self.commit() + @decorators.transaction def purge_empty_albums(self, *, commit=True): albums = self.get_albums() for album in albums: @@ -949,6 +954,7 @@ class PDBTagMixin: def get_tags(self): yield from self.get_things(thing_type='tag') + @decorators.transaction def new_tag(self, tagname, *, commit=True): ''' Register a new tag and return the Tag object. @@ -1074,6 +1080,7 @@ class PDBUserMixin: return objects.User(self, fetch) + @decorators.transaction def register_user(self, username, password, commit=True): if not self.config['enable_new_user']: raise exceptions.FeatureDisabled('new_user') @@ -1216,6 +1223,7 @@ class PhotoDB(PDBAlbumMixin, PDBBookmarkMixin, PDBPhotoMixin, PDBTagMixin, PDBUs task['action'](*args, **kwargs) self.sql.commit() + @decorators.transaction def digest_directory( self, directory,