web store sessions in the database

This commit is contained in:
dave 2019-07-06 13:49:16 -07:00
parent 3aa2264be4
commit 2de4a5d5ae
5 changed files with 86 additions and 21 deletions

View File

@ -1,4 +1,4 @@
FROM ubuntu:bionic FROM ubuntu:bionic AS frontend
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y wget software-properties-common && \ apt-get install -y wget software-properties-common && \
@ -13,20 +13,21 @@ RUN cd /tmp/code && \
npm install && \ npm install && \
./node_modules/.bin/grunt ./node_modules/.bin/grunt
FROM ubuntu:disco FROM ubuntu:disco AS app
ADD . /tmp/code/ ADD . /tmp/code/
COPY --from=0 /tmp/code/styles/dist/style.css /tmp/code/styles/dist/style.css
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y python3-pip apt-get install -y python3-pip
COPY --from=frontend /tmp/code/styles/dist/style.css /tmp/code/styles/dist/style.css
RUN pip3 install -U pip && \ RUN pip3 install -U pip && \
cd /tmp/code && \ cd /tmp/code && \
pip3 install -r requirements.txt && \ pip3 install -r requirements.txt && \
python3 setup.py install && \ python3 setup.py install && \
useradd --uid 1000 app useradd --uid 1000 app && \
rm -rf /tmp/code
VOLUME /srv/library VOLUME /srv/library
VOLUME /srv/cache VOLUME /srv/cache

View File

@ -115,31 +115,19 @@ This would ingest all the files listed in `shas.txt` that aren't already in the
Roadmap Roadmap
------- -------
- Stateless aka docker support - Stateless aka docker support
- ~~Photo storage~~ done
- ~~Abstract the storage api~~ done
- ~~Standardize on API ingest~~ done
- ~~Display and retrieval of images from the abstracted image store~~ done
- ~~Thumbnail gen~~ done
- ~~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~~
- ~~Tune certain databases if their uri is detected (sqlite and threads lol)~~
- ~~Cache~~ done
- ~~Using the local fs seems fine?~~ done
- Migration path - Migration path
- open database - open database
- copy files table to memory - copy files table to memory
- recreate files table - recreate files table
- insert into the new table, with replaced paths, generating a list of files moves at the same time - insert into the new table, with replaced paths, generating a list of files moves at the same time
- migrate files to the new storage according to the list - migrate files to the new storage according to the list
- Storage option for cherrypy sessions - Redis?
- Flesh out CLI: - Flesh out CLI:
- Config that is saved somewhere - Config that is saved somewhere
- Support additional fields on upload like title description tags etc - Support additional fields on upload like title description tags etc
- delete features - delete features
- tag features - tag features
- modify features (tags & images) - modify features (tags & images)
- Longer term ideas: - Longer term ideas:
- "fast ingest" method that touches the db/storage directly. This would scale better than the API ingest. - "fast ingest" method that touches the db/storage directly. This would scale better than the API ingest.
- Dynamic svg placeholder for images we can't open - Dynamic svg placeholder for images we can't open
- Proactive thumbnail generation

View File

@ -6,6 +6,7 @@ from urllib.parse import urlparse
from datetime import datetime, timedelta from datetime import datetime, timedelta
from photoapp.thumb import ThumbGenerator from photoapp.thumb import ThumbGenerator
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User
from photoapp.dbsession import DatabaseSession
from photoapp.common import pwhash from photoapp.common import pwhash
from photoapp.api import PhotosApi, LibraryManager from photoapp.api import PhotosApi, LibraryManager
from photoapp.dbutils import SAEnginePlugin, SATool, db, get_db_engine from photoapp.dbutils import SAEnginePlugin, SATool, db, get_db_engine
@ -473,7 +474,8 @@ def main():
# Setup and mount API # Setup and mount API
api = PhotosApi(library_manager) api = PhotosApi(library_manager)
cherrypy.tree.mount(api, '/api', {'/': {'tools.trailing_slash.on': False, cherrypy.tree.mount(api, '/api', {'/': {'tools.sessions.on': False,
'tools.trailing_slash.on': False,
'tools.auth_basic.on': True, 'tools.auth_basic.on': True,
'tools.auth_basic.realm': 'photolib', 'tools.auth_basic.realm': 'photolib',
'tools.auth_basic.checkpassword': validate_password, 'tools.auth_basic.checkpassword': validate_password,
@ -481,6 +483,7 @@ def main():
# General config options # General config options
cherrypy.config.update({ cherrypy.config.update({
'tools.sessions.storage_class': DatabaseSession,
'tools.sessions.on': True, 'tools.sessions.on': True,
'tools.sessions.locking': 'explicit', 'tools.sessions.locking': 'explicit',
'tools.sessions.timeout': 525600, 'tools.sessions.timeout': 525600,

73
photoapp/dbsession.py Normal file
View File

@ -0,0 +1,73 @@
import cherrypy
from photoapp.types import Base
from sqlalchemy import Column, String, DateTime, Boolean, BLOB
try:
import cPickle as pickle
except ImportError:
import pickle
class Session(Base):
__tablename__ = 'sessions'
session_id = Column(String(128), nullable=False, primary_key=True)
# user_id = Column(Integer, ForeignKey('users.user_id'))
expiration = Column(DateTime, nullable=False)
data = Column(BLOB)
is_valid = Column(Boolean, default=True, nullable=False)
class DatabaseSession(cherrypy.lib.sessions.Session):
"""
Sqlalchemy-backed backed for session storage. Note that we don't implement any of the locking methods because the
underlying database connection is being uses transaction anyway, which would prevent concurrent updates.
"""
@classmethod
def setup(cls, **kwargs):
for k, v in kwargs.items():
setattr(cls, k, v)
cls.cached = None
def _exists(self):
if not self.cached:
self._load()
return bool(self.cached)
def _load(self):
if not self.cached:
self.cached = cherrypy.request.db.query(Session).filter(Session.session_id == self.id). \
with_for_update().first()
if self.cached:
return pickle.loads(self.cached.data)
return None
def _save(self, expiration_time):
data = pickle.dumps(
(self._data, expiration_time),
pickle.HIGHEST_PROTOCOL)
if not self.cached:
self.cached = Session(session_id=self.id,
expiration=expiration_time)
cherrypy.request.db.add(self.cached)
self.cached.data = data
def _delete(self):
if not self.cached:
self._load()
if self.cached:
cherrypy.request.db.delete(self.cached)
def acquire_lock(self):
pass
def release_lock(self):
pass
def clean_up(self):
pass #TODO

View File

@ -67,7 +67,7 @@ class SATool(cherrypy.Tool):
def __init__(self): def __init__(self):
cherrypy.Tool.__init__(self, 'before_request_body', cherrypy.Tool.__init__(self, 'before_request_body',
self.bind_session, self.bind_session,
priority=100) priority=49) # slightly earlier than Sessions tool, which is 50 or 60
self.session = sqlalchemy.orm.scoped_session( self.session = sqlalchemy.orm.scoped_session(
sqlalchemy.orm.sessionmaker(autoflush=True, autocommit=False)) sqlalchemy.orm.sessionmaker(autoflush=True, autocommit=False))
@ -88,4 +88,4 @@ class SATool(cherrypy.Tool):
self.session.rollback() self.session.rollback()
raise raise
finally: finally:
self.session.remove() self.session.remove()