From 1b310f0c4a882f0fdcce77d7192b2d8b6bbe10ef Mon Sep 17 00:00:00 2001 From: dave Date: Sat, 29 Jun 2019 12:43:13 -0700 Subject: [PATCH] api upload framework --- photoapp/api.py | 128 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 12 deletions(-) diff --git a/photoapp/api.py b/photoapp/api.py index 72f6c4c..42edec5 100644 --- a/photoapp/api.py +++ b/photoapp/api.py @@ -15,6 +15,98 @@ from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slug from photoapp.dbutils import db +class StorageAdapter(object): + """ + Abstract interface for working with photo file storage. All paths are relative to the storage adapter's root parameter. + """ + + def file_exists(self, path): + # TODO return true/false if the file path exists + raise NotImplementedError() + + def open(self, path, mode): + # TODO return a handle to the path + # TODO this should work as a context manager + raise NotImplementedError() + + def delete(self, path): + # TODO erase the path + raise NotImplementedError() + + +class FilesystemAdapter(StorageAdapter): + def file_exists(self, path): + # TODO return true/false if the file path exists + raise NotImplementedError() + + def open(self, path, mode): + # TODO return a handle to the path. this should work as a context manager + raise NotImplementedError() + + def delete(self, path): + # TODO erase the path + raise NotImplementedError() + + +class S3Adapter(StorageAdapter): + def file_exists(self, path): + # TODO return true/false if the file path exists + raise NotImplementedError() + + def open(self, path, mode): + # TODO return a handle to the path. this should work as a context manager + raise NotImplementedError() + + def delete(self, path): + # TODO erase the path + raise NotImplementedError() + + +class GfapiAdapter(StorageAdapter): + pass + + +#This is largely duplicated from library.py, but written with intent for later refactoring to support abstract storage. +class LibraryManager(object): + def __init__(self): + pass + + def add_photoset(self, photoset): + """ + Commit a populated photoset object to the library. The paths in the photoset's file list entries will be updated + as the file is moved to the library path. + """ + # Create target directory + path = os.path.join(self.path, self.get_datedir_path(photoset.date)) + os.makedirs(path, exist_ok=True) + + moves = [] # Track files moved. If the sql transaction files, we'll undo these + + for file in photoset.files: + dest = os.path.join(path, os.path.basename(file.path)) + + # Check if the name is already in use, rename new file if needed + dupe_rename = 1 + while os.path.exists(dest): + fname = os.path.basename(file.path).split(".") + fname[-2] += "_{}".format(dupe_rename) + dest = os.path.join(path, '.'.join(fname)) + dupe_rename += 1 + os.rename(file.path, dest) + moves.append((file.path, dest)) + file.path = dest.lstrip(self.path) + + s = self.session() + s.add(photoset) + try: + s.commit() + except IntegrityError: + # Commit failed, undo the moves + for move in moves: + os.rename(move[1], move[0]) + raise + + class PhotosApi(object): def __init__(self): self.v1 = PhotosApiV1() @@ -22,7 +114,7 @@ class PhotosApi(object): class PhotosApiV1(object): def __init__(self): - pass + self.lib = LibraryManager() @cherrypy.expose def index(self): @@ -41,23 +133,35 @@ class PhotosApiV1(object): for file in files: print("File name:", file.filename) - import hashlib - sha = hashlib.sha256() - total = 0 - while True: - b = file.file.read(1024) - if not b: - break - sha.update(b) - total += len(b) - print("Read length:", total) - print("Read sha256:", sha.hexdigest()) + # import hashlib + # sha = hashlib.sha256() + # total = 0 + # while True: + # b = file.file.read(1024) + # if not b: + # break + # sha.update(b) + # total += len(b) + # print("Read length:", total) + # print("Read sha256:", sha.hexdigest()) if str(file.filename) not in meta["files"].keys(): raise cherrypy.HTTPError(400, f"no mdatadata provided for filename '{file.filename}'") print("we have metadata for this file:", meta["files"][file.filename]) + # create database objects based on the request + # self.lib.add_photoset(set_, photos) + + # build file path (yyyy/mm/dd/yyyy-mm_hh.MM.ss_x.jpg) (incrmenting X if the key already exists etc) + # copy to storage + # check if sha256 exists already + # delete if dupe, raise error + # (see file rewind code in ingest.py) + # create records + # commit + # respond with list of uuids of the sets + print("____") @cherrypy.expose