web store sessions in the database
This commit is contained in:
parent
3aa2264be4
commit
2de4a5d5ae
11
Dockerfile
11
Dockerfile
|
@ -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
|
||||||
|
|
14
README.md
14
README.md
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue