Separate cursors for every transaction

This commit is contained in:
voussoir 2017-01-29 17:47:59 -08:00
parent 13ae208a06
commit 109d5feef1
4 changed files with 132 additions and 85 deletions

View file

@ -1,4 +1,5 @@
import converter import converter
import logging
import string import string
import traceback import traceback
@ -117,6 +118,8 @@ ADDITIONAL_MIMETYPES = {
DEFAULT_DATADIR = '.\\_etiquette' DEFAULT_DATADIR = '.\\_etiquette'
DEFAULT_CONFIGURATION = { DEFAULT_CONFIGURATION = {
'log_level': logging.DEBUG,
'min_tag_name_length': 1, 'min_tag_name_length': 1,
'max_tag_name_length': 32, 'max_tag_name_length': 32,
'valid_tag_chars': string.ascii_lowercase + string.digits + '_', 'valid_tag_chars': string.ascii_lowercase + string.digits + '_',

View file

@ -13,14 +13,14 @@ else:
if port == 443: if port == 443:
http = gevent.pywsgi.WSGIServer( http = gevent.pywsgi.WSGIServer(
listener=('', port), listener=('0.0.0.0', port),
application=etiquette.site, application=etiquette.site,
keyfile='C:\\git\\etiquette\\etiquette\\https\\etiquette.key', keyfile='C:\\git\\etiquette\\etiquette\\https\\etiquette.key',
certfile='C:\\git\\etiquette\\etiquette\\https\\etiquette.crt', certfile='C:\\git\\etiquette\\etiquette\\https\\etiquette.crt',
) )
else: else:
http = gevent.pywsgi.WSGIServer( http = gevent.pywsgi.WSGIServer(
listener=('', port), listener=('0.0.0.0', port),
application=etiquette.site, application=etiquette.site,
) )

View file

@ -43,8 +43,9 @@ class GroupableMixin:
# Groupables are only allowed to have 1 parent. # Groupables are only allowed to have 1 parent.
# Unlike photos which can exist in multiple albums. # Unlike photos which can exist in multiple albums.
self.photodb.cur.execute('SELECT * FROM tag_group_rel WHERE memberid == ?', [member.id]) cur = self.photodb.sql.cursor()
fetch = self.photodb.cur.fetchone() cur.execute('SELECT * FROM tag_group_rel WHERE memberid == ?', [member.id])
fetch = cur.fetchone()
if fetch is not None: if fetch is not None:
parent_id = fetch[constants.SQL_TAGGROUP['parentid']] parent_id = fetch[constants.SQL_TAGGROUP['parentid']]
if parent_id == self.id: if parent_id == self.id:
@ -58,14 +59,15 @@ class GroupableMixin:
raise exceptions.RecursiveGrouping('%s is an ancestor of %s' % (member.name, self.name)) raise exceptions.RecursiveGrouping('%s is an ancestor of %s' % (member.name, self.name))
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
self.photodb.cur.execute('INSERT INTO tag_group_rel VALUES(?, ?)', [self.id, member.id]) cur.execute('INSERT INTO tag_group_rel VALUES(?, ?)', [self.id, member.id])
if commit: if commit:
self.photodb.log.debug('Commiting - add to group') self.photodb.log.debug('Commiting - add to group')
self.photodb.commit() self.photodb.commit()
def children(self): def children(self):
self.photodb.cur.execute('SELECT * FROM tag_group_rel WHERE parentid == ?', [self.id]) cur = self.photodb.sql.cursor()
fetch = self.photodb.cur.fetchall() cur.execute('SELECT * FROM tag_group_rel WHERE parentid == ?', [self.id])
fetch = cur.fetchall()
results = [] results = []
for f in fetch: for f in fetch:
memberid = f[constants.SQL_TAGGROUP['memberid']] memberid = f[constants.SQL_TAGGROUP['memberid']]
@ -91,6 +93,7 @@ class GroupableMixin:
Otherwise they'll just be raised up one level. Otherwise they'll just be raised up one level.
''' '''
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
cur = self.photodb.sql.cursor()
if delete_children: if delete_children:
for child in self.children(): for child in self.children():
child.delete(delete_children=delete_children, commit=False) child.delete(delete_children=delete_children, commit=False)
@ -99,15 +102,15 @@ class GroupableMixin:
parent = self.parent() parent = self.parent()
if parent is None: if parent is None:
# Since this group was a root, children become roots by removing the row. # Since this group was a root, children become roots by removing the row.
self.photodb.cur.execute('DELETE FROM tag_group_rel WHERE parentid == ?', [self.id]) cur.execute('DELETE FROM tag_group_rel WHERE parentid == ?', [self.id])
else: else:
# Since this group was a child, its parent adopts all its children. # Since this group was a child, its parent adopts all its children.
self.photodb.cur.execute( cur.execute(
'UPDATE tag_group_rel SET parentid == ? WHERE parentid == ?', 'UPDATE tag_group_rel SET parentid == ? WHERE parentid == ?',
[parent.id, self.id] [parent.id, self.id]
) )
# Note that this part comes after the deletion of children to prevent issues of recursion. # Note that this part comes after the deletion of children to prevent issues of recursion.
self.photodb.cur.execute('DELETE FROM tag_group_rel WHERE memberid == ?', [self.id]) cur.execute('DELETE FROM tag_group_rel WHERE memberid == ?', [self.id])
if commit: if commit:
self.photodb.log.debug('Committing - delete tag') self.photodb.log.debug('Committing - delete tag')
self.photodb.commit() self.photodb.commit()
@ -117,8 +120,9 @@ class GroupableMixin:
Return the group of which this is a member, or None. Return the group of which this is a member, or None.
Returned object will be of the same type as calling object. Returned object will be of the same type as calling object.
''' '''
self.photodb.cur.execute('SELECT * FROM tag_group_rel WHERE memberid == ?', [self.id]) cur = self.photodb.sql.cursor()
fetch = self.photodb.cur.fetchone() cur.execute('SELECT * FROM tag_group_rel WHERE memberid == ?', [self.id])
fetch = cur.fetchone()
if fetch is None: if fetch is None:
return None return None
@ -144,8 +148,9 @@ class GroupableMixin:
''' '''
Leave the current group and become independent. Leave the current group and become independent.
''' '''
cur = self.photodb.sql.cursor()
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
self.photodb.cur.execute('DELETE FROM tag_group_rel WHERE memberid == ?', [self.id]) cur.execute('DELETE FROM tag_group_rel WHERE memberid == ?', [self.id])
if commit: if commit:
self.photodb.log.debug('Committing - leave group') self.photodb.log.debug('Committing - leave group')
self.photodb.commit() self.photodb.commit()
@ -184,7 +189,8 @@ class Album(ObjectBase, GroupableMixin):
raise ValueError('Not the same PhotoDB') raise ValueError('Not the same PhotoDB')
if self.has_photo(photo): if self.has_photo(photo):
return return
self.photodb.cur.execute('INSERT INTO album_photo_rel VALUES(?, ?)', [self.id, photo.id]) cur = self.photodb.sql.cursor()
cur.execute('INSERT INTO album_photo_rel VALUES(?, ?)', [self.id, photo.id])
if commit: if commit:
self.photodb.log.debug('Committing - add photo to album') self.photodb.log.debug('Committing - add photo to album')
self.photodb.commit() self.photodb.commit()
@ -205,8 +211,9 @@ class Album(ObjectBase, GroupableMixin):
def delete(self, *, delete_children=False, commit=True): def delete(self, *, delete_children=False, commit=True):
self.photodb.log.debug('Deleting album {album:r}'.format(album=self)) self.photodb.log.debug('Deleting album {album:r}'.format(album=self))
GroupableMixin.delete(self, delete_children=delete_children, commit=False) GroupableMixin.delete(self, delete_children=delete_children, commit=False)
self.photodb.cur.execute('DELETE FROM albums WHERE id == ?', [self.id]) cur = self.photodb.sql.cursor()
self.photodb.cur.execute('DELETE FROM album_photo_rel WHERE albumid == ?', [self.id]) cur.execute('DELETE FROM albums WHERE id == ?', [self.id])
cur.execute('DELETE FROM album_photo_rel WHERE albumid == ?', [self.id])
if commit: if commit:
self.photodb.log.debug('Committing - delete album') self.photodb.log.debug('Committing - delete album')
self.photodb.commit() self.photodb.commit()
@ -216,7 +223,7 @@ class Album(ObjectBase, GroupableMixin):
title = self.title title = self.title
if description is None: if description is None:
description = self.description description = self.description
self.photodb.cur.execute( cur.execute(
'UPDATE albums SET title=?, description=? WHERE id == ?', 'UPDATE albums SET title=?, description=? WHERE id == ?',
[title, description, self.id] [title, description, self.id]
) )
@ -229,11 +236,12 @@ class Album(ObjectBase, GroupableMixin):
def has_photo(self, photo): def has_photo(self, photo):
if not isinstance(photo, Photo): if not isinstance(photo, Photo):
raise TypeError('Must be a %s' % Photo) raise TypeError('Must be a %s' % Photo)
self.photodb.cur.execute( cur = self.photodb.sql.cursor()
cur.execute(
'SELECT * FROM album_photo_rel WHERE albumid == ? AND photoid == ?', 'SELECT * FROM album_photo_rel WHERE albumid == ? AND photoid == ?',
[self.id, photo.id] [self.id, photo.id]
) )
return self.photodb.cur.fetchone() is not None return cur.fetchone() is not None
def photos(self): def photos(self):
photos = [] photos = []
@ -252,7 +260,8 @@ class Album(ObjectBase, GroupableMixin):
def remove_photo(self, photo, *, commit=True): def remove_photo(self, photo, *, commit=True):
if not self.has_photo(photo): if not self.has_photo(photo):
return return
self.photodb.cur.execute( cur = self.photodb.sql.cursor()
cur.execute(
'DELETE FROM album_photo_rel WHERE albumid == ? AND photoid == ?', 'DELETE FROM album_photo_rel WHERE albumid == ? AND photoid == ?',
[self.id, photo.id] [self.id, photo.id]
) )
@ -309,8 +318,9 @@ class Photo(ObjectBase):
''' '''
Reload the row from the database and do __init__ with them. Reload the row from the database and do __init__ with them.
''' '''
self.photodb.cur.execute('SELECT * FROM photos WHERE id == ?', [self.id]) cur = self.photodb.sql.cursor()
row = self.photodb.cur.fetchone() cur.execute('SELECT * FROM photos WHERE id == ?', [self.id])
row = cur.fetchone()
self.__init__(self.photodb, row) self.__init__(self.photodb, row)
def __repr__(self): def __repr__(self):
@ -338,8 +348,9 @@ class Photo(ObjectBase):
self.photodb.log.debug('Applying tag {tag:s} to photo {pho:s}'.format(tag=tag, pho=self)) self.photodb.log.debug('Applying tag {tag:s} to photo {pho:s}'.format(tag=tag, pho=self))
now = int(helpers.now()) now = int(helpers.now())
self.photodb.cur.execute('INSERT INTO photo_tag_rel VALUES(?, ?)', [self.id, tag.id]) cur = self.photodb.sql.cursor()
self.photodb.cur.execute('UPDATE photos SET tagged_at = ? WHERE id == ?', [now, self.id]) cur.execute('INSERT INTO photo_tag_rel VALUES(?, ?)', [self.id, tag.id])
cur.execute('UPDATE photos SET tagged_at = ? WHERE id == ?', [now, self.id])
if commit: if commit:
self.photodb.log.debug('Committing - add photo tag') self.photodb.log.debug('Committing - add photo tag')
self.photodb.commit() self.photodb.commit()
@ -348,8 +359,9 @@ class Photo(ObjectBase):
''' '''
Return the albums of which this photo is a member. Return the albums of which this photo is a member.
''' '''
self.photodb.cur.execute('SELECT albumid FROM album_photo_rel WHERE photoid == ?', [self.id]) cur = self.photodb.sql.cursor()
fetch = self.photodb.cur.fetchall() cur.execute('SELECT albumid FROM album_photo_rel WHERE photoid == ?', [self.id])
fetch = cur.fetchall()
albums = [self.photodb.get_album(f[0]) for f in fetch] albums = [self.photodb.get_album(f[0]) for f in fetch]
return albums return albums
@ -368,9 +380,10 @@ class Photo(ObjectBase):
Delete the Photo and its relation to any tags and albums. Delete the Photo and its relation to any tags and albums.
''' '''
self.photodb.log.debug('Deleting photo {photo:r}'.format(photo=self)) self.photodb.log.debug('Deleting photo {photo:r}'.format(photo=self))
self.photodb.cur.execute('DELETE FROM photos WHERE id == ?', [self.id]) cur = self.photodb.sql.cursor()
self.photodb.cur.execute('DELETE FROM photo_tag_rel WHERE photoid == ?', [self.id]) cur.execute('DELETE FROM photos WHERE id == ?', [self.id])
self.photodb.cur.execute('DELETE FROM album_photo_rel WHERE photoid == ?', [self.id]) cur.execute('DELETE FROM photo_tag_rel WHERE photoid == ?', [self.id])
cur.execute('DELETE FROM album_photo_rel WHERE photoid == ?', [self.id])
if delete_file: if delete_file:
path = self.real_path.absolute_path path = self.real_path.absolute_path
@ -453,7 +466,8 @@ class Photo(ObjectBase):
if return_filepath != self.thumbnail: if return_filepath != self.thumbnail:
self.photodb.cur.execute( cur = self.photodb.sql.cursor()
cur.execute(
'UPDATE photos SET thumbnail = ? WHERE id == ?', 'UPDATE photos SET thumbnail = ? WHERE id == ?',
[return_filepath, self.id] [return_filepath, self.id]
) )
@ -480,12 +494,13 @@ class Photo(ObjectBase):
else: else:
tags = [tag] tags = [tag]
cur = self.photodb.sql.cursor()
for tag in tags: for tag in tags:
self.photodb.cur.execute( cur.execute(
'SELECT * FROM photo_tag_rel WHERE photoid == ? AND tagid == ?', 'SELECT * FROM photo_tag_rel WHERE photoid == ? AND tagid == ?',
[self.id, tag.id] [self.id, tag.id]
) )
if self.photodb.cur.fetchone() is not None: if cur.fetchone() is not None:
return tag return tag
return False return False
@ -545,7 +560,8 @@ class Photo(ObjectBase):
self.area = self.width * self.height self.area = self.width * self.height
self.ratio = round(self.width / self.height, 2) self.ratio = round(self.width / self.height, 2)
self.photodb.cur.execute( cur = self.photodb.sql.cursor()
cur.execute(
'UPDATE photos SET width=?, height=?, area=?, ratio=?, duration=?, bytes=? WHERE id==?', 'UPDATE photos SET width=?, height=?, area=?, ratio=?, duration=?, bytes=? WHERE id==?',
[self.width, self.height, self.area, self.ratio, self.duration, self.bytes, self.id], [self.width, self.height, self.area, self.ratio, self.duration, self.bytes, self.id],
) )
@ -558,13 +574,15 @@ class Photo(ObjectBase):
self.photodb.log.debug('Removing tag {t} from photo {p}'.format(t=repr(tag), p=repr(self))) self.photodb.log.debug('Removing tag {t} from photo {p}'.format(t=repr(tag), p=repr(self)))
tags = list(tag.walk_children()) tags = list(tag.walk_children())
cur = self.photodb.sql.cursor()
for tag in tags: for tag in tags:
self.photodb.cur.execute( cur.execute(
'DELETE FROM photo_tag_rel WHERE photoid == ? AND tagid == ?', 'DELETE FROM photo_tag_rel WHERE photoid == ? AND tagid == ?',
[self.id, tag.id] [self.id, tag.id]
) )
now = int(helpers.now()) now = int(helpers.now())
self.photodb.cur.execute('UPDATE photos SET tagged_at = ? WHERE id == ?', [now, self.id]) cur.execute('UPDATE photos SET tagged_at = ? WHERE id == ?', [now, self.id])
if commit: if commit:
self.photodb.log.debug('Committing - remove photo tag') self.photodb.log.debug('Committing - remove photo tag')
self.photodb.commit() self.photodb.commit()
@ -604,7 +622,8 @@ class Photo(ObjectBase):
except OSError: except OSError:
spinal.copy_file(old_path, new_path) spinal.copy_file(old_path, new_path)
self.photodb.cur.execute( cur = self.photodb.sql.cursor()
cur.execute(
'UPDATE photos SET filepath = ? WHERE filepath == ?', 'UPDATE photos SET filepath = ? WHERE filepath == ?',
[new_path.absolute_path, old_path.absolute_path] [new_path.absolute_path, old_path.absolute_path]
) )
@ -687,7 +706,8 @@ class Tag(ObjectBase, GroupableMixin):
raise exceptions.TagExists(synname) raise exceptions.TagExists(synname)
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
self.photodb.cur.execute('INSERT INTO tag_synonyms VALUES(?, ?)', [synname, self.name]) cur = self.photodb.sql.cursor()
cur.execute('INSERT INTO tag_synonyms VALUES(?, ?)', [synname, self.name])
if commit: if commit:
self.photodb.log.debug('Committing - add synonym') self.photodb.log.debug('Committing - add synonym')
@ -707,7 +727,8 @@ class Tag(ObjectBase, GroupableMixin):
# Migrate the old tag's synonyms to the new one # Migrate the old tag's synonyms to the new one
# UPDATE is safe for this operation because there is no chance of duplicates. # UPDATE is safe for this operation because there is no chance of duplicates.
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
self.photodb.cur.execute( cur = self.photodb.sql.cursor()
cur.execute(
'UPDATE tag_synonyms SET mastername = ? WHERE mastername == ?', 'UPDATE tag_synonyms SET mastername = ? WHERE mastername == ?',
[mastertag.name, self.name] [mastertag.name, self.name]
) )
@ -722,10 +743,10 @@ class Tag(ObjectBase, GroupableMixin):
for relationship in generator: for relationship in generator:
photoid = relationship[constants.SQL_PHOTOTAG['photoid']] photoid = relationship[constants.SQL_PHOTOTAG['photoid']]
query = 'SELECT * FROM photo_tag_rel WHERE photoid == ? AND tagid == ?' query = 'SELECT * FROM photo_tag_rel WHERE photoid == ? AND tagid == ?'
self.photodb.cur.execute(query, [photoid, mastertag.id]) cur.execute(query, [photoid, mastertag.id])
if self.photodb.cur.fetchone() is None: if cur.fetchone() is None:
query = 'INSERT INTO photo_tag_rel VALUES(?, ?)' query = 'INSERT INTO photo_tag_rel VALUES(?, ?)'
self.photodb.cur.execute(query, [photoid, mastertag.id]) cur.execute(query, [photoid, mastertag.id])
# Then delete the relationships with the old tag # Then delete the relationships with the old tag
self.delete() self.delete()
@ -740,9 +761,10 @@ class Tag(ObjectBase, GroupableMixin):
self.photodb.log.debug('Deleting tag {tag:r}'.format(tag=self)) self.photodb.log.debug('Deleting tag {tag:r}'.format(tag=self))
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
GroupableMixin.delete(self, delete_children=delete_children, commit=False) GroupableMixin.delete(self, delete_children=delete_children, commit=False)
self.photodb.cur.execute('DELETE FROM tags WHERE id == ?', [self.id]) cur = self.photodb.sql.cursor()
self.photodb.cur.execute('DELETE FROM photo_tag_rel WHERE tagid == ?', [self.id]) cur.execute('DELETE FROM tags WHERE id == ?', [self.id])
self.photodb.cur.execute('DELETE FROM tag_synonyms WHERE mastername == ?', [self.name]) cur.execute('DELETE FROM photo_tag_rel WHERE tagid == ?', [self.id])
cur.execute('DELETE FROM tag_synonyms WHERE mastername == ?', [self.name])
if commit: if commit:
self.photodb.log.debug('Committing - delete tag') self.photodb.log.debug('Committing - delete tag')
self.photodb.commit() self.photodb.commit()
@ -766,13 +788,14 @@ class Tag(ObjectBase, GroupableMixin):
they always resolve to the master tag before application. they always resolve to the master tag before application.
''' '''
synname = self.photodb.normalize_tagname(synname) synname = self.photodb.normalize_tagname(synname)
self.photodb.cur.execute('SELECT * FROM tag_synonyms WHERE name == ?', [synname]) cur = self.photodb.sql.cursor()
fetch = self.photodb.cur.fetchone() cur.execute('SELECT * FROM tag_synonyms WHERE name == ?', [synname])
fetch = cur.fetchone()
if fetch is None: if fetch is None:
raise exceptions.NoSuchSynonym(synname) raise exceptions.NoSuchSynonym(synname)
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
self.photodb.cur.execute('DELETE FROM tag_synonyms WHERE name == ?', [synname]) cur.execute('DELETE FROM tag_synonyms WHERE name == ?', [synname])
if commit: if commit:
self.photodb.log.debug('Committing - remove synonym') self.photodb.log.debug('Committing - remove synonym')
self.photodb.commit() self.photodb.commit()
@ -794,9 +817,10 @@ class Tag(ObjectBase, GroupableMixin):
self._cached_qualified_name = None self._cached_qualified_name = None
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
self.photodb.cur.execute('UPDATE tags SET name = ? WHERE id == ?', [new_name, self.id]) cur = self.photodb.sql.cursor()
cur.execute('UPDATE tags SET name = ? WHERE id == ?', [new_name, self.id])
if apply_to_synonyms: if apply_to_synonyms:
self.photodb.cur.execute( cur.execute(
'UPDATE tag_synonyms SET mastername = ? WHERE mastername = ?', 'UPDATE tag_synonyms SET mastername = ? WHERE mastername = ?',
[new_name, self.name] [new_name, self.name]
) )
@ -807,8 +831,9 @@ class Tag(ObjectBase, GroupableMixin):
self.photodb.commit() self.photodb.commit()
def synonyms(self): def synonyms(self):
self.photodb.cur.execute('SELECT name FROM tag_synonyms WHERE mastername == ?', [self.name]) cur = self.photodb.sql.cursor()
fetch = self.photodb.cur.fetchall() cur.execute('SELECT name FROM tag_synonyms WHERE mastername == ?', [self.name])
fetch = cur.fetchall()
fetch = [f[0] for f in fetch] fetch = [f[0] for f in fetch]
fetch.sort() fetch.sort()
return fetch return fetch

View file

@ -320,8 +320,9 @@ class PDBAlbumMixin:
Return the album with the `associated_directory` of this value, NOT case-sensitive. Return the album with the `associated_directory` of this value, NOT case-sensitive.
''' '''
filepath = os.path.abspath(filepath) filepath = os.path.abspath(filepath)
self.cur.execute('SELECT * FROM albums WHERE associated_directory == ?', [filepath]) cur = self.sql.cursor()
fetch = self.cur.fetchone() cur.execute('SELECT * FROM albums WHERE associated_directory == ?', [filepath])
fetch = cur.fetchone()
if fetch is None: if fetch is None:
raise exceptions.NoSuchAlbum(filepath) raise exceptions.NoSuchAlbum(filepath)
return self.get_album(fetch[constants.SQL_ALBUM['id']]) return self.get_album(fetch[constants.SQL_ALBUM['id']])
@ -363,7 +364,8 @@ class PDBAlbumMixin:
(qmarks, bindings) = helpers.binding_filler(constants.SQL_ALBUM_COLUMNS, data) (qmarks, bindings) = helpers.binding_filler(constants.SQL_ALBUM_COLUMNS, data)
query = 'INSERT INTO albums VALUES(%s)' % qmarks query = 'INSERT INTO albums VALUES(%s)' % qmarks
self.cur.execute(query, bindings) cur = self.sql.cursor()
cur.execute(query, bindings)
album = objects.Album(self, data) album = objects.Album(self, data)
if photos: if photos:
@ -383,8 +385,9 @@ class PDBPhotoMixin:
def get_photo_by_path(self, filepath): def get_photo_by_path(self, filepath):
filepath = os.path.abspath(filepath) filepath = os.path.abspath(filepath)
self.cur.execute('SELECT * FROM photos WHERE filepath == ?', [filepath]) cur = self.sql.cursor()
fetch = self.cur.fetchone() cur.execute('SELECT * FROM photos WHERE filepath == ?', [filepath])
fetch = cur.fetchone()
if fetch is None: if fetch is None:
raise_no_such_thing(exceptions.NoSuchPhoto, thing_name=filepath) raise_no_such_thing(exceptions.NoSuchPhoto, thing_name=filepath)
photo = objects.Photo(self, fetch) photo = objects.Photo(self, fetch)
@ -398,10 +401,10 @@ class PDBPhotoMixin:
return return
# We're going to use a second cursor because the first one may # We're going to use a second cursor because the first one may
# get used for something else, deactivating this query. # get used for something else, deactivating this query.
temp_cur = self.sql.cursor() cur = self.sql.cursor()
temp_cur.execute('SELECT * FROM photos ORDER BY created DESC') cur.execute('SELECT * FROM photos ORDER BY created DESC')
while True: while True:
fetch = temp_cur.fetchone() fetch = cur.fetchone()
if fetch is None: if fetch is None:
break break
photo = objects.Photo(self, fetch) photo = objects.Photo(self, fetch)
@ -485,7 +488,8 @@ class PDBPhotoMixin:
(qmarks, bindings) = helpers.binding_filler(constants.SQL_PHOTO_COLUMNS, data) (qmarks, bindings) = helpers.binding_filler(constants.SQL_PHOTO_COLUMNS, data)
query = 'INSERT INTO photos VALUES(%s)' % qmarks query = 'INSERT INTO photos VALUES(%s)' % qmarks
self.cur.execute(query, bindings) cur = self.sql.cursor()
cur.execute(query, bindings)
photo = objects.Photo(self, data) photo = objects.Photo(self, data)
if do_metadata: if do_metadata:
@ -673,6 +677,10 @@ class PDBPhotoMixin:
print(query) print(query)
generator = helpers.select_generator(self.sql, query) generator = helpers.select_generator(self.sql, query)
if orderby is None:
giveback_orderby = None
else:
giveback_orderby = [term.replace('RANDOM()', 'random') for term in orderby]
if give_back_parameters: if give_back_parameters:
parameters = { parameters = {
'area': area, 'area': area,
@ -694,7 +702,7 @@ class PDBPhotoMixin:
'tag_expression': tag_expression, 'tag_expression': tag_expression,
'limit': limit, 'limit': limit,
'offset': offset, 'offset': offset,
'orderby': [term.replace('RANDOM()', 'random') for term in orderby], 'orderby': giveback_orderby,
} }
yield parameters yield parameters
@ -851,15 +859,16 @@ class PDBTagMixin:
tagname = tagname.split('.')[-1].split('+')[0] tagname = tagname.split('.')[-1].split('+')[0]
tagname = self.normalize_tagname(tagname) tagname = self.normalize_tagname(tagname)
cur = self.sql.cursor()
while True: while True:
# Return if it's a toplevel, or resolve the synonym and try that. # Return if it's a toplevel, or resolve the synonym and try that.
self.cur.execute('SELECT * FROM tags WHERE name == ?', [tagname]) cur.execute('SELECT * FROM tags WHERE name == ?', [tagname])
fetch = self.cur.fetchone() fetch = cur.fetchone()
if fetch is not None: if fetch is not None:
return objects.Tag(self, fetch) return objects.Tag(self, fetch)
self.cur.execute('SELECT * FROM tag_synonyms WHERE name == ?', [tagname]) cur.execute('SELECT * FROM tag_synonyms WHERE name == ?', [tagname])
fetch = self.cur.fetchone() fetch = cur.fetchone()
if fetch is None: if fetch is None:
# was not a top tag or synonym # was not a top tag or synonym
raise_no_such_thing(exceptions.NoSuchTag, thing_name=tagname) raise_no_such_thing(exceptions.NoSuchTag, thing_name=tagname)
@ -882,7 +891,8 @@ class PDBTagMixin:
tagid = self.generate_id('tags') tagid = self.generate_id('tags')
self._cached_frozen_children = None self._cached_frozen_children = None
self.cur.execute('INSERT INTO tags VALUES(?, ?)', [tagid, tagname]) cur = self.sql.cursor()
cur.execute('INSERT INTO tags VALUES(?, ?)', [tagid, tagname])
if commit: if commit:
self.log.debug('Commiting - new_tag') self.log.debug('Commiting - new_tag')
self.commit() self.commit()
@ -916,12 +926,13 @@ class PDBUserMixin:
so they get their own method. so they get their own method.
''' '''
possible = string.digits + string.ascii_uppercase possible = string.digits + string.ascii_uppercase
cur = self.sql.cursor()
for retry in range(20): for retry in range(20):
user_id = [random.choice(possible) for x in range(self.config['id_length'])] user_id = [random.choice(possible) for x in range(self.config['id_length'])]
user_id = ''.join(user_id) user_id = ''.join(user_id)
self.cur.execute('SELECT * FROM users WHERE id == ?', [user_id]) cur.execute('SELECT * FROM users WHERE id == ?', [user_id])
if self.cur.fetchone() is None: if cur.fetchone() is None:
break break
else: else:
raise Exception('Failed to create user id after 20 tries.') raise Exception('Failed to create user id after 20 tries.')
@ -932,20 +943,22 @@ class PDBUserMixin:
if not helpers.is_xor(id, username): if not helpers.is_xor(id, username):
raise exceptions.NotExclusive('One and only one of `id`, `username` must be passed.') raise exceptions.NotExclusive('One and only one of `id`, `username` must be passed.')
cur = self.sql.cursor()
if username is not None: if username is not None:
self.cur.execute('SELECT * FROM users WHERE username == ?', [username]) cur.execute('SELECT * FROM users WHERE username == ?', [username])
else: else:
self.cur.execute('SELECT * FROM users WHERE id == ?', [id]) cur.execute('SELECT * FROM users WHERE id == ?', [id])
fetch = self.cur.fetchone() fetch = cur.fetchone()
if fetch is not None: if fetch is not None:
return objects.User(self, fetch) return objects.User(self, fetch)
else: else:
raise exceptions.NoSuchUser(username) raise exceptions.NoSuchUser(username)
def login(self, user_id, password): def login(self, user_id, password):
self.cur.execute('SELECT * FROM users WHERE id == ?', [user_id]) cur = self.sql.cursor()
fetch = self.cur.fetchone() cur.execute('SELECT * FROM users WHERE id == ?', [user_id])
fetch = cur.fetchone()
if fetch is None: if fetch is None:
raise exceptions.WrongLogin() raise exceptions.WrongLogin()
@ -978,8 +991,9 @@ class PDBUserMixin:
if len(password) < self.config['min_password_length']: if len(password) < self.config['min_password_length']:
raise exceptions.PasswordTooShort raise exceptions.PasswordTooShort
self.cur.execute('SELECT * FROM users WHERE username == ?', [username]) cur = self.sql.cursor()
if self.cur.fetchone() is not None: cur.execute('SELECT * FROM users WHERE username == ?', [username])
if cur.fetchone() is not None:
raise exceptions.UserExists(username) raise exceptions.UserExists(username)
user_id = self.generate_user_id() user_id = self.generate_user_id()
@ -995,7 +1009,7 @@ class PDBUserMixin:
(qmarks, bindings) = helpers.binding_filler(constants.SQL_USER_COLUMNS, data) (qmarks, bindings) = helpers.binding_filler(constants.SQL_USER_COLUMNS, data)
query = 'INSERT INTO users VALUES(%s)' % qmarks query = 'INSERT INTO users VALUES(%s)' % qmarks
self.cur.execute(query, bindings) cur.execute(query, bindings)
if commit: if commit:
self.log.debug('Committing - register user') self.log.debug('Committing - register user')
@ -1069,6 +1083,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
statements = DB_INIT.split(';') statements = DB_INIT.split(';')
for statement in statements: for statement in statements:
self.cur.execute(statement) self.cur.execute(statement)
self.sql.commit()
# CONFIG # CONFIG
self.config_file = self.data_directory.with_child('config.json') self.config_file = self.data_directory.with_child('config.json')
@ -1088,6 +1103,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
# OTHER # OTHER
self.log = logging.getLogger(__name__) self.log = logging.getLogger(__name__)
self.log.setLevel(self.config['log_level'])
self.on_commit_queue = [] self.on_commit_queue = []
self._cached_frozen_children = None self._cached_frozen_children = None
@ -1305,8 +1321,9 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
if table not in ['photos', 'tags', 'groups']: if table not in ['photos', 'tags', 'groups']:
raise ValueError('Invalid table requested: %s.', table) raise ValueError('Invalid table requested: %s.', table)
self.cur.execute('SELECT * FROM id_numbers WHERE tab == ?', [table]) cur = self.sql.cursor()
fetch = self.cur.fetchone() cur.execute('SELECT * FROM id_numbers WHERE tab == ?', [table])
fetch = cur.fetchone()
if fetch is None: if fetch is None:
# Register new value # Register new value
new_id_int = 1 new_id_int = 1
@ -1318,9 +1335,9 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
new_id = str(new_id_int).rjust(self.config['id_length'], '0') new_id = str(new_id_int).rjust(self.config['id_length'], '0')
if do_insert: if do_insert:
self.cur.execute('INSERT INTO id_numbers VALUES(?, ?)', [table, new_id]) cur.execute('INSERT INTO id_numbers VALUES(?, ?)', [table, new_id])
else: else:
self.cur.execute('UPDATE id_numbers SET last_id = ? WHERE tab == ?', [new_id, table]) cur.execute('UPDATE id_numbers SET last_id = ? WHERE tab == ?', [new_id, table])
return new_id return new_id
def get_thing_by_id(self, thing_type, thing_id): def get_thing_by_id(self, thing_type, thing_id):
@ -1330,8 +1347,9 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
thing_id = thing_id.id thing_id = thing_id.id
query = 'SELECT * FROM %s WHERE id == ?' % thing_map['table'] query = 'SELECT * FROM %s WHERE id == ?' % thing_map['table']
self.cur.execute(query, [thing_id]) cur = self.sql.cursor()
thing = self.cur.fetchone() cur.execute(query, [thing_id])
thing = cur.fetchone()
if thing is None: if thing is None:
return raise_no_such_thing(thing_map['exception'], thing_id=thing_id) return raise_no_such_thing(thing_map['exception'], thing_id=thing_id)
thing = thing_map['class'](self, thing) thing = thing_map['class'](self, thing)
@ -1340,12 +1358,13 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin, PDBUserMixin):
def get_things(self, thing_type, orderby=None): def get_things(self, thing_type, orderby=None):
thing_map = _THING_CLASSES[thing_type] thing_map = _THING_CLASSES[thing_type]
cur = self.sql.cursor()
if orderby: if orderby:
self.cur.execute('SELECT * FROM %s ORDER BY %s' % (thing_map['table'], orderby)) cur.execute('SELECT * FROM %s ORDER BY %s' % (thing_map['table'], orderby))
else: else:
self.cur.execute('SELECT * FROM %s' % thing_map['table']) cur.execute('SELECT * FROM %s' % thing_map['table'])
things = self.cur.fetchall() things = cur.fetchall()
for thing in things: for thing in things:
thing = thing_map['class'](self, row_tuple=thing) thing = thing_map['class'](self, row_tuple=thing)
yield thing yield thing