From baf32f857de8b182aed2aa379bd9e80455145f8a Mon Sep 17 00:00:00 2001 From: Mark Cotton Date: Wed, 6 Sep 2023 16:49:42 -0500 Subject: [PATCH] WIP, most basic version is writing data to database --- .gitignore | 2 +- app.py | 70 ++++++++++++++++++- models.py | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 models.py diff --git a/.gitignore b/.gitignore index 1216b75..01f27c1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ my_settings.py flask_session/ git-version.txt settings.py - +*.db diff --git a/app.py b/app.py index 8e2621a..8b7dbf2 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,7 @@ from matplotlib.figure import Figure import matplotlib.dates as mdates import base64 from tqdm import tqdm - +import datetime from io import BytesIO import logging @@ -58,6 +58,21 @@ app.config.from_object(__name__) Session(app) + + +from flask_sqlalchemy import SQLAlchemy +from models import * + +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db" +db.init_app(app) + +with app.app_context(): + db.create_all() + + + + + # $ flask routes -s rule # INFO:root:Using EagleEyev3 version 0.0.17 # Endpoint Methods Rule @@ -185,9 +200,30 @@ def login_callback(): oauth_object = een.login_tokens(code) logging.debug(oauth_object) + + # let's try resaving this to see if it fixs the missing access_token on first request after login session['een'] = een + + een.get_current_user() + + try: + user = User.query.filter(User.email == een.current_user['email']).first() + if user is None: + user = User(account_id=een.current_user['accountId'],\ + user_id=een.current_user['id'],\ + email=een.current_user['email'],\ + timezone=een.current_user['timeZone']['timeZone'],\ + access_token=een.access_token,\ + refresh_token=een.refresh_token,\ + last_login=None) + user.save() + except Exception as e: + logging.warn(e) + + + return redirect(url_for('index')) @@ -213,6 +249,17 @@ def cameras(): logging.debug(een.get_list_of_cameras()) + for c in een.cameras: + try: + check_camera = Camera.query.filter(Camera.device_id == c.id).first() + if check_camera is None: + new_camera = Camera(device_id=c.id,\ + timezone=None, \ + name=c.name) + new_camera.save() + except Exception as e: + logging.warn(e) + values = { "current_user": een.current_user, "cameras": een.cameras, @@ -329,6 +376,25 @@ def camera_list_of_videos(esn=None): camera = een.get_camera_by_id(esn) logging.debug(camera.get_list_of_videos(start_timestamp=een.time_before(hours=24), end_timestamp=een.time_now())) + db_camera = Camera.query.filter(Camera.device_id == esn).first() + if db_camera: + + for v in camera.videos: + try: + check_video = Download.query.filter(Download.camera_id == db_camera.id)\ + .filter(Download.url == v['mp4Url'])\ + .first() + if check_video is None: + new_video = Download(url=v['mp4Url'],\ + camera_id=db_camera.id,\ + start_timestamp=datetime.fromisoformat(v['startTimestamp']),\ + end_timestamp=datetime.fromisoformat(v['endTimestamp']),\ + status='new', + error=None) + new_video.save() + except Exception as e: + logging.warn(f"saving videos to db: {e}") + values = { "current_user": een.current_user, "camera": { 'id': camera.id, 'name': camera.name }, @@ -348,4 +414,4 @@ def camera_list_of_videos(esn=None): if __name__ == '__main__': - app.run(host=config.server_host, port=config.server_port) + app.run(host=config['server_host'], port=config['server_port']) diff --git a/models.py b/models.py new file mode 100644 index 0000000..2666bae --- /dev/null +++ b/models.py @@ -0,0 +1,202 @@ + + +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import exc +from settings import * + +db = SQLAlchemy() + + +__all__ = ['db', 'save_row', 'delete_row', 'User', 'Camera', 'Download' ] + + +# +# helper function to reduce repeated code +# +def commit_or_rollback(db, obj): + try: + db.session.add(obj) + db.session.commit() + except exc.IntegrityError as e: + print(f"caught an exception {e}") + db.session.rollback() + except exc.OperationalError as e: + print(f"caught an exception {e}") + db.session.rollback() + +def delete_or_rollback(db, obj): + try: + db.session.delete(obj) + db.session.commit() + except exc.IntegrityError as e: + print(f"caught an exception {e}") + db.session.rollback() + except exc.OperationalError as e: + print(f"caught an exception {e}") + db.session.rollback() + +def save_row(obj): + commit_or_rollback(db, obj) + +def delete_row(obj): + delete_or_rollback(db, obj) + + + + +# trying out making my own BaseModel, follow this blogpost +# https://dev.to/chidioguejiofor/making-sqlalchemy-models-simpler-by-creating-a-basemodel-3m9c + +class BaseModel(db.Model): + __abstract__ = True + + id = db.Column(db.Integer, primary_key=True) + created = db.Column(db.DateTime, server_default=db.func.now()) + updated = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now()) + + def before_save(self, *args, **kwargs): + pass + + def after_save(self, *args, **kwargs): + pass + + def save(self, commit=True): + self.before_save() + db.session.add(self) + if commit: + try: + db.session.commit() + except Exception as e: + db.session.rollback() + raise e + + self.after_save() + + + def before_update(self, *args, **kwargs): + pass + + def after_update(self, *args, **kwargs): + pass + + def update(self, *args, **kwargs): + self.before_update(*args, **kwargs) + db.session.commit() + self.after_update(*args, **kwargs) + + def delete(self, commit=True): + db.session.delete(self) + if commit: + db.session.commit() + + @classmethod + def eager(cls, *args): + cols = [orm.joinedload(arg) for arg in args] + return cls.query.options(*cols) + + + @classmethod + def before_bulk_create(cls, iterable, *args, **kwargs): + pass + + @classmethod + def after_bulk_create(cls, model_objs, *args, **kwargs): + pass + + + @classmethod + def bulk_create(cls, iterable, *args, **kwargs): + cls.before_bulk_create(iterable, *args, **kwargs) + model_objs = [] + for data in iterable: + if not isinstance(data, cls): + data = cls(**data) + model_objs.append(data) + + db.session.bulk_save_objects(model_objs) + if kwargs.get('commit', True) is True: + db.session.commit() + cls.after_bulk_create(model_objs, *args, **kwargs) + return model_objs + + + @classmethod + def bulk_create_or_none(cls, iterable, *args, **kwargs): + try: + return cls.bulk_create(iterable, *args, **kwargs) + except exc.IntegrityError as e: + db.session.rollback() + return None + + +class User(BaseModel): + ''' + The User object stores basic data from EagleEye API along with cameras they have access to, and stores the access/refresh tokens. + ''' + account_id = db.Column(db.String(8), nullable=True) + user_id = db.Column(db.String(8), nullable=True) + email = db.Column(db.String(), nullable=True) + timezone = db.Column(db.String(), nullable=True) + access_token = db.Column(db.String(), nullable=True) + refresh_token = db.Column(db.String(), nullable=True) + last_login = db.Column(db.DateTime, nullable=True) + cameras = db.relationship('Camera', backref=db.backref('user', lazy=True)) + + def __repr__(self): + return f"[User] {email}" + + def to_dict(self): + return { + "account_id": self.account_id, + "user_id": self.user_id, + "email": self.email, + "timezone": self.timezone, + "access_token": self.access_token, + "refresh_token": self.refresh_token, + "last_login": self.last_login, + "cameras": self.cameras + } + + + +class Camera(BaseModel): + ''' + The Camera object holds the list of recordings (Downloads) and some basics about the device + ''' + device_id = db.Column(db.String(8), nullable=True) + timezone = db.Column(db.String(), nullable=True) + name = db.Column(db.String(), nullable=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) + downloads = db.relationship('Download', backref=db.backref('camera', lazy=True)) + + def __repr__(self): + return f"[Camera] {device_id} {name}" + + def get_newest_download(self, status=None): + return None + + def get_oldest_download(self, status=None): + return None + + + +class Download(BaseModel): + ''' + Downloads are the actual recording that should be downloaded. + It keeps its own state/progress/errors so it can be reported on later. + Downloads belong to a camera which by extension belongs to a user + ''' + url = db.Column(db.String(), nullable=True) + camera_id = db.Column(db.Integer, db.ForeignKey('camera.id'), nullable=True) + start_timestamp = db.Column(db.DateTime, nullable=True) + end_timestamp = db.Column(db.DateTime, nullable=True) + status = db.Column(db.String(), nullable=True) + error = db.Column(db.String(), nullable=True) + + def __repr__(self): + return f"[Download] {id} {camera_id} {status} {error}" + + + + +