daemon to use newer db access api

This commit is contained in:
dave 2019-07-04 17:46:26 -07:00
parent 022f4a90a7
commit 6589f71052
3 changed files with 55 additions and 77 deletions

View File

@ -112,6 +112,7 @@ Roadmap
- ~~Standardize on API ingest~~ done - ~~Standardize on API ingest~~ done
- Display and retrieval of images from the abstracted image store - Display and retrieval of images from the abstracted image store
- Database support - Database support
- Get the web UI code (daemon.py) using the same db access method as the api
- Support any connection URI sqlalchemy is happy with - Support any connection URI sqlalchemy is happy with
- Tune certain databases if their uri is detected (sqlite and threads lol) - Tune certain databases if their uri is detected (sqlite and threads lol)
- ~~Cache~~ done - ~~Cache~~ done

View File

@ -79,7 +79,6 @@ class GfapiAdapter(StorageAdapter):
pass # TODO gluster storage backend pass # TODO gluster storage backend
# This is largely duplicated from library.py, but written with intent for later refactoring to support abstract storage.
class LibraryManager(object): class LibraryManager(object):
def __init__(self, storage): def __init__(self, storage):
assert isinstance(storage, StorageAdapter) assert isinstance(storage, StorageAdapter)
@ -98,7 +97,8 @@ class PhotosApiV1(object):
@cherrypy.expose @cherrypy.expose
def index(self): def index(self):
yield f"<plaintext>hello, this is the api. my database is: {db}\n" cherrypy.response.headers["Content-type"] = "text/plain"
return "Hello! This is the Photolib V1 API.\n"
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()

View File

@ -13,6 +13,7 @@ from photoapp.dbutils import SAEnginePlugin, SATool
import math import math
from urllib.parse import urlparse from urllib.parse import urlparse
from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slugify from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slugify
from photoapp.dbutils import db
APPROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) APPROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
@ -45,14 +46,13 @@ class PhotosWeb(object):
""" """
Return a dict containing variables expected to be on every page Return a dict containing variables expected to be on every page
""" """
s = self.session()
# all tags / albums with photos visible under the current auth context # all tags / albums with photos visible under the current auth context
tagq = s.query(Tag).join(TagItem).join(PhotoSet) tagq = db.query(Tag).join(TagItem).join(PhotoSet)
if not auth(): if not auth():
tagq = tagq.filter(PhotoSet.status == PhotoStatus.public) tagq = tagq.filter(PhotoSet.status == PhotoStatus.public)
tagq = tagq.filter(Tag.is_album == False).order_by(Tag.name).all() # pragma: manual auth tagq = tagq.filter(Tag.is_album == False).order_by(Tag.name).all() # pragma: manual auth
albumq = s.query(Tag).join(TagItem).join(PhotoSet) albumq = db.query(Tag).join(TagItem).join(PhotoSet)
if not auth(): if not auth():
albumq = albumq.filter(PhotoSet.status == PhotoStatus.public) albumq = albumq.filter(PhotoSet.status == PhotoStatus.public)
albumq = albumq.filter(Tag.is_album == True).order_by(Tag.name).all() # pragma: manual auth albumq = albumq.filter(Tag.is_album == True).order_by(Tag.name).all() # pragma: manual auth
@ -64,15 +64,8 @@ class PhotosWeb(object):
"auth": auth(), "auth": auth(),
"PhotoStatus": PhotoStatus "PhotoStatus": PhotoStatus
} }
s.close()
return ret return ret
def session(self):
"""
Get a database session
"""
return self.library.session()
@cherrypy.expose @cherrypy.expose
def index(self): def index(self):
""" """
@ -85,10 +78,9 @@ class PhotosWeb(object):
""" """
/feed - main photo feed - show photos sorted by date, newest first /feed - main photo feed - show photos sorted by date, newest first
""" """
s = self.session()
page, pgsize = int(page), int(pgsize) page, pgsize = int(page), int(pgsize)
total_sets = photo_auth_filter(s.query(func.count(PhotoSet.id))).first()[0] total_sets = photo_auth_filter(db.query(func.count(PhotoSet.id))).first()[0]
images = photo_auth_filter(s.query(PhotoSet)).order_by(PhotoSet.date.desc()). \ images = photo_auth_filter(db.query(PhotoSet)).order_by(PhotoSet.date.desc()). \
offset(pgsize * page).limit(pgsize).all() offset(pgsize * page).limit(pgsize).all()
yield self.render("feed.html", images=[i for i in images], page=page, pgsize=int(pgsize), total_sets=total_sets) yield self.render("feed.html", images=[i for i in images], page=page, pgsize=int(pgsize), total_sets=total_sets)
@ -97,12 +89,11 @@ class PhotosWeb(object):
""" """
/stats - show server statistics /stats - show server statistics
""" """
s = self.session() images = photo_auth_filter(db.query(func.count(PhotoSet.uuid),
images = photo_auth_filter(s.query(func.count(PhotoSet.uuid), func.strftime('%Y', PhotoSet.date).label('year'),
func.strftime('%Y', PhotoSet.date).label('year'), func.strftime('%m', PhotoSet.date).label('month'))). \
func.strftime('%m', PhotoSet.date).label('month'))). \
group_by('year', 'month').order_by(desc('year'), desc('month')).all() group_by('year', 'month').order_by(desc('year'), desc('month')).all()
tsize = photo_auth_filter(s.query(func.sum(Photo.size)).join(PhotoSet)).scalar() # pragma: manual auth tsize = photo_auth_filter(db.query(func.sum(Photo.size)).join(PhotoSet)).scalar() # pragma: manual auth
yield self.render("monthly.html", images=images, tsize=tsize) yield self.render("monthly.html", images=images, tsize=tsize)
@cherrypy.expose @cherrypy.expose
@ -112,8 +103,7 @@ class PhotosWeb(object):
the given tag. the given tag.
TODO using so many coordinates is slow in the browser. dedupe them somehow. TODO using so many coordinates is slow in the browser. dedupe them somehow.
""" """
s = self.session() query = photo_auth_filter(db.query(PhotoSet)).filter(PhotoSet.lat != 0, PhotoSet.lon != 0)
query = photo_auth_filter(s.query(PhotoSet)).filter(PhotoSet.lat != 0, PhotoSet.lon != 0)
if a: if a:
query = query.join(TagItem).join(Tag).filter(Tag.uuid == a) query = query.join(TagItem).join(Tag).filter(Tag.uuid == a)
if i: if i:
@ -131,44 +121,43 @@ class PhotosWeb(object):
:param remove: target photos will have the tag specified by this uuid removed :param remove: target photos will have the tag specified by this uuid removed
:param newtag: new tag name to create :param newtag: new tag name to create
""" """
s = self.session()
def get_photos(): def get_photos():
if fromdate: if fromdate:
dt = datetime.strptime(fromdate, "%Y-%m-%d") dt = datetime.strptime(fromdate, "%Y-%m-%d")
dt_end = dt + timedelta(days=1) dt_end = dt + timedelta(days=1)
photos = s.query(PhotoSet).filter(and_(PhotoSet.date >= dt, photos = db.query(PhotoSet).filter(and_(PhotoSet.date >= dt,
PhotoSet.date < dt_end)).order_by(PhotoSet.date) PhotoSet.date < dt_end)).order_by(PhotoSet.date)
num_photos = s.query(func.count(PhotoSet.id)). \ num_photos = db.query(func.count(PhotoSet.id)). \
filter(and_(PhotoSet.date >= dt, PhotoSet.date < dt_end)).order_by(PhotoSet.date).scalar() filter(and_(PhotoSet.date >= dt, PhotoSet.date < dt_end)).order_by(PhotoSet.date).scalar()
if uuid: if uuid:
photos = s.query(PhotoSet).filter(PhotoSet.uuid == uuid) photos = db.query(PhotoSet).filter(PhotoSet.uuid == uuid)
num_photos = s.query(func.count(PhotoSet.id)).filter(PhotoSet.uuid == uuid).scalar() num_photos = db.query(func.count(PhotoSet.id)).filter(PhotoSet.uuid == uuid).scalar()
return photos, num_photos return photos, num_photos
if remove: if remove:
rmtag = s.query(Tag).filter(Tag.uuid == remove).first() rmtag = db.query(Tag).filter(Tag.uuid == remove).first()
photoq, _ = get_photos() photoq, _ = get_photos()
for photo in photoq: for photo in photoq:
s.query(TagItem).filter(TagItem.tag_id == rmtag.id, TagItem.set_id == photo.id).delete() db.query(TagItem).filter(TagItem.tag_id == rmtag.id, TagItem.set_id == photo.id).delete()
s.commit() db.commit()
if newtag: if newtag:
s.add(Tag(title=newtag.capitalize(), name=newtag, slug=slugify(newtag))) db.add(Tag(title=newtag.capitalize(), name=newtag, slug=slugify(newtag)))
s.commit() db.commit()
photos, num_photos = get_photos() photos, num_photos = get_photos()
if tag: # Create the tag on all the photos if tag: # Create the tag on all the photos
tag = s.query(Tag).filter(Tag.uuid == tag).first() tag = db.query(Tag).filter(Tag.uuid == tag).first()
for photo in photos.all(): for photo in photos.all():
if 0 == s.query(func.count(TagItem.id)).filter(TagItem.tag_id == tag.id, if 0 == db.query(func.count(TagItem.id)).filter(TagItem.tag_id == tag.id,
TagItem.set_id == photo.id).scalar(): TagItem.set_id == photo.id).scalar():
s.add(TagItem(tag_id=tag.id, set_id=photo.id)) db.add(TagItem(tag_id=tag.id, set_id=photo.id))
s.commit() db.commit()
alltags = s.query(Tag).order_by(Tag.name).all() alltags = db.query(Tag).order_by(Tag.name).all()
yield self.render("create_tags.html", images=photos, alltags=alltags, yield self.render("create_tags.html", images=photos, alltags=alltags,
num_photos=num_photos, fromdate=fromdate, uuid=uuid) num_photos=num_photos, fromdate=fromdate, uuid=uuid)
@ -207,21 +196,20 @@ class DateView(object):
@cherrypy.expose @cherrypy.expose
def index(self, date=None, page=0): def index(self, date=None, page=0):
s = self.master.session()
if date: if date:
page = int(page) page = int(page)
pgsize = 100 pgsize = 100
dt = datetime.strptime(date, "%Y-%m-%d") dt = datetime.strptime(date, "%Y-%m-%d")
dt_end = dt + timedelta(days=1) dt_end = dt + timedelta(days=1)
total_sets = photo_auth_filter(s.query(func.count(PhotoSet.id))). \ total_sets = photo_auth_filter(db.query(func.count(PhotoSet.id))). \
filter(and_(PhotoSet.date >= dt, PhotoSet.date < dt_end)).first()[0] filter(and_(PhotoSet.date >= dt, PhotoSet.date < dt_end)).first()[0]
images = photo_auth_filter(s.query(PhotoSet)).filter(and_(PhotoSet.date >= dt, images = photo_auth_filter(db.query(PhotoSet)).filter(and_(PhotoSet.date >= dt,
PhotoSet.date < dt_end)).order_by(PhotoSet.date). \ PhotoSet.date < dt_end)).order_by(PhotoSet.date). \
offset(page * pgsize).limit(pgsize).all() offset(page * pgsize).limit(pgsize).all()
yield self.master.render("date.html", page=page, pgsize=pgsize, total_sets=total_sets, yield self.master.render("date.html", page=page, pgsize=pgsize, total_sets=total_sets,
images=[i for i in images], date=dt) images=[i for i in images], date=dt)
return return
images = photo_auth_filter(s.query(PhotoSet, func.strftime('%Y-%m-%d', images = photo_auth_filter(db.query(PhotoSet, func.strftime('%Y-%m-%d',
PhotoSet.date).label('gdate'), PhotoSet.date).label('gdate'),
func.count('photos.id'), func.count('photos.id'),
func.strftime('%Y', PhotoSet.date).label('year'), func.strftime('%Y', PhotoSet.date).label('year'),
@ -242,9 +230,7 @@ class ThumbnailView(object):
@cherrypy.expose @cherrypy.expose
def index(self, item_type, thumb_size, uuid): def index(self, item_type, thumb_size, uuid):
uuid = uuid.split(".")[0] uuid = uuid.split(".")[0]
s = self.master.session() query = photo_auth_filter(db.query(Photo).join(PhotoSet))
query = photo_auth_filter(s.query(Photo).join(PhotoSet))
query = query.filter(Photo.set.has(uuid=uuid)) if item_type == "set" \ query = query.filter(Photo.set.has(uuid=uuid)) if item_type == "set" \
else query.filter(Photo.uuid == uuid) if item_type == "one" \ else query.filter(Photo.uuid == uuid) if item_type == "one" \
@ -284,10 +270,8 @@ class DownloadView(object):
@cherrypy.expose @cherrypy.expose
def index(self, item_type, uuid, preview=False): def index(self, item_type, uuid, preview=False):
uuid = uuid.split(".")[0] uuid = uuid.split(".")[0]
s = self.master.session()
query = None if item_type == "set" \ query = None if item_type == "set" \
else photo_auth_filter(s.query(Photo)).filter(Photo.uuid == uuid) if item_type == "one" \ else photo_auth_filter(db.query(Photo)).filter(Photo.uuid == uuid) if item_type == "one" \
else None # TODO set download query else None # TODO set download query
item = query.first() item = query.first()
@ -311,8 +295,7 @@ class PhotoView(object):
@cherrypy.expose @cherrypy.expose
def index(self, uuid): def index(self, uuid):
# uuid = uuid.split(".")[0] # uuid = uuid.split(".")[0]
s = self.master.session() photo = photo_auth_filter(db.query(PhotoSet)).filter(or_(PhotoSet.uuid == uuid, PhotoSet.slug == uuid)).first()
photo = photo_auth_filter(s.query(PhotoSet)).filter(or_(PhotoSet.uuid == uuid, PhotoSet.slug == uuid)).first()
if not photo: if not photo:
raise cherrypy.HTTPError(404) raise cherrypy.HTTPError(404)
yield self.master.render("photo.html", image=photo) yield self.master.render("photo.html", image=photo)
@ -327,8 +310,7 @@ class PhotoView(object):
* "Make private": * "Make private":
* "Save": update the photo's title, description, and date_offset fields * "Save": update the photo's title, description, and date_offset fields
""" """
s = self.master.session() photo = db.query(PhotoSet).filter(PhotoSet.uuid == uuid).first()
photo = s.query(PhotoSet).filter(PhotoSet.uuid == uuid).first()
if op == "Make public": if op == "Make public":
photo.status = PhotoStatus.public photo.status = PhotoStatus.public
elif op == "Make private": elif op == "Make private":
@ -338,14 +320,13 @@ class PhotoView(object):
photo.description = description photo.description = description
photo.slug = slugify(title) or None photo.slug = slugify(title) or None
photo.date_offset = int(offset) if offset else 0 photo.date_offset = int(offset) if offset else 0
s.commit() db.commit()
raise cherrypy.HTTPRedirect('/photo/{}'.format(photo.slug or photo.uuid), 302) raise cherrypy.HTTPRedirect('/photo/{}'.format(photo.slug or photo.uuid), 302)
@cherrypy.expose @cherrypy.expose
@require_auth @require_auth
def edit(self, uuid): def edit(self, uuid):
s = self.master.session() photo = photo_auth_filter(db.query(PhotoSet)).filter(PhotoSet.uuid == uuid).first()
photo = photo_auth_filter(s.query(PhotoSet)).filter(PhotoSet.uuid == uuid).first()
yield self.master.render("photo_edit.html", image=photo) yield self.master.render("photo_edit.html", image=photo)
@ -361,20 +342,18 @@ class TagView(object):
def index(self, uuid, page=0): def index(self, uuid, page=0):
page = int(page) page = int(page)
pgsize = 100 pgsize = 100
s = self.master.session()
if uuid == "untagged": if uuid == "untagged":
numphotos = photo_auth_filter(s.query(func.count(PhotoSet.id))). \ numphotos = photo_auth_filter(db.query(func.count(PhotoSet.id))). \
filter(~PhotoSet.id.in_(s.query(TagItem.set_id))).scalar() filter(~PhotoSet.id.in_(db.query(TagItem.set_id))).scalar()
photos = photo_auth_filter(s.query(PhotoSet)).filter(~PhotoSet.id.in_(s.query(TagItem.set_id))).\ photos = photo_auth_filter(db.query(PhotoSet)).filter(~PhotoSet.id.in_(db.query(TagItem.set_id))).\
offset(page * pgsize). \ offset(page * pgsize). \
limit(pgsize).all() limit(pgsize).all()
yield self.master.render("untagged.html", images=photos, total_items=numphotos, pgsize=pgsize, page=page) yield self.master.render("untagged.html", images=photos, total_items=numphotos, pgsize=pgsize, page=page)
else: else:
tag = s.query(Tag).filter(or_(Tag.uuid == uuid, Tag.slug == uuid)).first() tag = db.query(Tag).filter(or_(Tag.uuid == uuid, Tag.slug == uuid)).first()
numphotos = photo_auth_filter(s.query(func.count(Tag.id)).join(TagItem).join(PhotoSet)). \ numphotos = photo_auth_filter(db.query(func.count(Tag.id)).join(TagItem).join(PhotoSet)). \
filter(Tag.id == tag.id).scalar() filter(Tag.id == tag.id).scalar()
photos = photo_auth_filter(s.query(PhotoSet)).join(TagItem).join(Tag). \ photos = photo_auth_filter(db.query(PhotoSet)).join(TagItem).join(Tag). \
filter(Tag.id == tag.id). \ filter(Tag.id == tag.id). \
order_by(PhotoSet.date.desc()). \ order_by(PhotoSet.date.desc()). \
offset(page * pgsize). \ offset(page * pgsize). \
@ -393,24 +372,23 @@ class TagView(object):
- Make all public: mark all photos under this tag as public - Make all public: mark all photos under this tag as public
- Make all private: mark all photos under this tag as private - Make all private: mark all photos under this tag as private
""" """
s = self.master.session() tag = db.query(Tag).filter(or_(Tag.uuid == uuid, Tag.slug == uuid)).first()
tag = s.query(Tag).filter(or_(Tag.uuid == uuid, Tag.slug == uuid)).first()
if op == "Demote to tag": if op == "Demote to tag":
tag.is_album = 0 tag.is_album = 0
elif op == "Promote to album": elif op == "Promote to album":
tag.is_album = 1 tag.is_album = 1
elif op == "Delete tag": elif op == "Delete tag":
s.query(TagItem).filter(TagItem.tag_id == tag.id).delete() db.query(TagItem).filter(TagItem.tag_id == tag.id).delete()
s.delete(tag) db.delete(tag)
s.commit() db.commit()
raise cherrypy.HTTPRedirect('/', 302) raise cherrypy.HTTPRedirect('/', 302)
elif op == "Make all public": elif op == "Make all public":
# TODO smarter query # TODO smarter query
for photo in s.query(PhotoSet).join(TagItem).join(Tag).filter(Tag.id == tag.id).all(): for photo in db.query(PhotoSet).join(TagItem).join(Tag).filter(Tag.id == tag.id).all():
photo.status = PhotoStatus.public photo.status = PhotoStatus.public
elif op == "Make all private": elif op == "Make all private":
# TODO smarter query # TODO smarter query
for photo in s.query(PhotoSet).join(TagItem).join(Tag).filter(Tag.id == tag.id).all(): for photo in db.query(PhotoSet).join(TagItem).join(Tag).filter(Tag.id == tag.id).all():
photo.status = PhotoStatus.private photo.status = PhotoStatus.private
elif op == "Save": elif op == "Save":
tag.title = title tag.title = title
@ -418,14 +396,13 @@ class TagView(object):
tag.slug = slugify(title) tag.slug = slugify(title)
else: else:
raise Exception("Invalid op: '{}'".format(op)) raise Exception("Invalid op: '{}'".format(op))
s.commit() db.commit()
raise cherrypy.HTTPRedirect('/tag/{}'.format(tag.slug or tag.uuid), 302) raise cherrypy.HTTPRedirect('/tag/{}'.format(tag.slug or tag.uuid), 302)
@cherrypy.expose @cherrypy.expose
@require_auth @require_auth
def edit(self, uuid): def edit(self, uuid):
s = self.master.session() tag = db.query(Tag).filter(Tag.uuid == uuid).first()
tag = s.query(Tag).filter(Tag.uuid == uuid).first()
yield self.master.render("tag_edit.html", tag=tag) yield self.master.render("tag_edit.html", tag=tag)
@ -453,12 +430,12 @@ def main():
web = PhotosWeb(library, tpl_dir) web = PhotosWeb(library, tpl_dir)
def validate_password(realm, username, password): def validate_password(realm, username, password):
s = library.session() if db.query(User).filter(User.name == username, User.password == pwhash(password)).first():
if s.query(User).filter(User.name == username, User.password == pwhash(password)).first():
return True return True
return False return False
cherrypy.tree.mount(web, '/', {'/': {'tools.trailing_slash.on': False, cherrypy.tree.mount(web, '/', {'/': {'tools.trailing_slash.on': False,
'tools.db.on': True,
'error_page.403': web.error, 'error_page.403': web.error,
'error_page.404': web.error}, 'error_page.404': web.error},
'/static': {"tools.staticdir.on": True, '/static': {"tools.staticdir.on": True,