From 136f9808dfe00c524508bb2a9d60d075f4c86574 Mon Sep 17 00:00:00 2001 From: Seth Ladygo Date: Wed, 26 Feb 2020 16:30:02 -0800 Subject: [PATCH] markup/webdav.py: mostly functional --- markup/webdav.py | 324 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 230 insertions(+), 94 deletions(-) diff --git a/markup/webdav.py b/markup/webdav.py index 72b0843..0f6ad0a 100644 --- a/markup/webdav.py +++ b/markup/webdav.py @@ -1,22 +1,28 @@ import datetime -import os -import shutil import logging +import os +import re +import shutil + +from os.path import dirname +from pathlib import Path from sys import getfilesystemencoding +from django.core.exceptions import PermissionDenied from django.utils.decorators import method_decorator +from django.utils.functional import cached_property from django_http_auth.decorators import http_basic_auth -from djangodav.acls import FullAcl +from djangodav.acls import DavAcl, ReadOnlyAcl, FullAcl from djangodav.base.resources import BaseDavResource, MetaEtagMixIn +from djangodav.fs.resources import DummyFSDAVResource from djangodav.locks import DummyLock from djangodav.utils import url_join from djangodav.views import DavView from procat2.models import Catalog - -# from procat2.settings import ASSET_DIR +from procat2.settings import ASSET_DIR log = logging.getLogger(__name__) @@ -27,122 +33,174 @@ fs_encoding = getfilesystemencoding() class MarkupDavView(DavView): def __init__(self): - super(DavView, self).__init__(resource_class=MarkupDavResource, lock_class=DummyLock, acl_class=FullAcl) + super().__init__(resource_class=resource_factory, lock_class=DummyLock, acl_class=FullAcl) + + def get_access(self, resource): + return resource.get_acl() + + +def resource_factory(**kwargs): + # log.info(f"resource_factory '{kwargs}'") + path = kwargs['path'] + if path == RootFolderResource.PATH: + return RootFolderResource(**kwargs) + elif path == CatalogFolderResource.PATH: + return CatalogFolderResource(**kwargs) + elif is_my_cats_catalog(path): + return CatalogResource(**kwargs) + elif is_markup_catalog(path): + return MarkupResource(**kwargs) + else: + return NonsensicalResource(**kwargs) + +def is_my_cats_catalog(path): + return re.search(fr'^{CatalogFolderResource.PATH}.*-([0-9]+)\.pdf', path, re.I) + +def is_markup_catalog(path): + return re.search(fr'^{MarkupResource.PATH}', path, re.I) class MarkupDavResource(MetaEtagMixIn, BaseDavResource): - ROOT_FOLDER = '' - MY_CATS_FOLDER = 'My Catalogs' - MARKUP_SUBMIT_FOLDER = 'For Markup' - #root = '/opt/imagebank/mkbeta/webdav' # TODO replace with settings var - def __init__(self, path=None, user=None): - super().__init__(path) - self.user = user + # def __init__(self, path=None, user=None): + # super().__init__(path) + # self.user = user def __str__(self): - return f"" + return f"<{type(self).__name__} '{self.user}' '{self.path}'>" + + def get_acl(self): + return ReadOnlyAcl() + # return DavAcl(read=True, write=False, delete=False, full=None) + + +class RootFolderResource(MarkupDavResource): + NAME = '' + PATH = '/' + + # def get_parent_path(self): + # path = self.path[:-1] + # return "/" + "/".join(path) + "/" if path else "" + + def get_parent(self): + return None def get_children(self): - """Return an iterator of all direct children of this resource.""" - # make sure the current object is a directory - path = self.get_path() - log.info(f"get_children of '{path}'") - - children = [] - if path == '/': - children = [self.MY_CATS_FOLDER, self.MARKUP_SUBMIT_FOLDER] - elif path == f'/{self.MY_CATS_FOLDER}/': - children = self.user_catalogs() - elif path == f'/{self.MARKUP_SUBMIT_FOLDER}/': - children = None - # else: - # path = self.get_abs_path() - # if os.path.isdir(path): - # children = os.listdir(path) - + children = [CatalogFolderResource(CatalogFolderResource.PATH, self.user), + MarkupResource(MarkupResource.PATH, self.user)] for child in children: - is_unicode = isinstance(child, str) - if not is_unicode: - child = child.decode(fs_encoding) - child_resource = self.clone(path=url_join(*(self.path + [child])), user=self.user) - log.info(f'returning kid {child_resource}') - yield child_resource - - def user_catalogs(self): - cats = Catalog.objects.filter(owner=self.user).order_by('-updated') - return [c.name for c in cats] - - def get_abs_path(self): - """Return the absolute path of the resource. Used internally to interface with - an actual file system. If you override all other methods, this one will not - be used.""" - return os.path.join(self.root, *self.path) + yield child @property def getcontentlength(self): - """Return the size of the resource in bytes.""" - if self.is_collection: - return 0 - else: - return os.path.getsize(self.get_abs_path()) + return 2 def get_created(self): - """Return the create time as datetime object.""" - if self.is_collection: - return datetime.datetime.now() # TODO last created time of object in there? - else: - return datetime.datetime.fromtimestamp(os.stat(self.get_abs_path()).st_ctime) + return datetime.datetime.now() # TODO last created time of object in there? def get_modified(self): - """Return the modified time as datetime object.""" - if self.is_collection: - return datetime.datetime.now() # TODO last modified time of object in there? - else: - return datetime.datetime.fromtimestamp(os.stat(self.get_abs_path()).st_mtime) + return datetime.datetime.now() # TODO last modified time of object in there? @property def is_collection(self): - """Return True if this resource is a directory (collection in WebDAV parlance).""" - path = '/'.join(self.path) - # log.info(f"is_collection {self.path}") - - if path in [self.ROOT_FOLDER, self.MY_CATS_FOLDER, self.MARKUP_SUBMIT_FOLDER]: - return True - - return False - # return os.path.isdir(self.get_abs_path()) + return True @property def is_object(self): - """Return True if this resource is a file (resource in WebDAV parlance).""" - path = '/'.join(self.path) - log.info(f"is_object {path}") - - if self.is_collection: - return False - - # TODO test catalog pdf - return True - #return os.path.isfile(self.get_abs_path()) + return False @property def exists(self): - """Return True if this resource exists.""" - path = '/'.join(self.path) - log.info(f"exists {path}") + return True - if self.is_collection: - return True - # TODO test catalog pdf - return os.path.exists(self.get_abs_path()) +class CatalogFolderResource(MarkupDavResource): + NAME = 'My Catalogs' + PATH = f'/{NAME}/' + MARKUP_SUBMIT_NAME = 'For Markup' + MARKUP_SUBMIT_PATH = f'/{MARKUP_SUBMIT_NAME}/' - # def write(self, content, temp_file=None, range_start=None): - # raise NotImplementedError + def get_parent(self): + return RootFolderResource(user=self.user) - # def read(self): - # raise NotImplementedError + def get_children(self): + for child in self.user_catalogs: + yield CatalogResource(path=url_join(*(self.path + [child])), user=self.user) + + @cached_property + def user_catalogs(self): + cats = Catalog.objects.filter(owner=self.user).order_by('-updated') + return [c.pdf_name() for c in cats if c.pdf_exists()] + + @property + def getcontentlength(self): + return 2 # TODO based on contents + + def get_created(self): + return datetime.datetime.now() # TODO last created time of object in there? + + def get_modified(self): + return datetime.datetime.now() # TODO last modified time of object in there? + + @property + def is_collection(self): + return True + + @property + def is_object(self): + return False + + @property + def exists(self): + return True + + +class CatalogResource(MarkupDavResource): + + def get_parent(self): + return CatalogFolderResource(path=CatalogFolderResource.PATH, user=self.user) + + # def get_children(self): + # return + # yield + + @cached_property + def catalog(self): + id = self.id_from_pdf_name(self.path) + return Catalog.objects.get(id=id) + + def id_from_pdf_name(self, path): + result = re.search(r'-([0-9]+)\.pdf', path[-1], re.I) + if result and result.groups(): + return result.group(1) + else: + return None + + @property + def getcontentlength(self): + if self.catalog.pdf_exists(): + return os.stat(self.catalog.pdf_file()).st_size + else: + return 0 + + def get_created(self): + return self.catalog.created + + def get_modified(self): + return self.catalog.updated + + @property + def is_collection(self): + return False + + @property + def is_object(self): + return True + + @property + def exists(self): + log.info(f'exists? {self.catalog.pdf_exists()}') + return self.catalog.pdf_exists() # def delete(self): # """Delete the resource, recursive is implied.""" @@ -163,8 +221,11 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): # def move_object(self, destination): # os.rename(self.get_abs_path(), destination.get_abs_path()) - # def read(self): - # return open(self.get_abs_path(), 'rb') + def read(self): + if self.catalog.pdf_exists(): + return open(self.catalog.pdf_file(), 'rb') + else: + return None # def write(self, request, temp_file=None, range_start=None): # if temp_file: @@ -179,3 +240,78 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource): # with open(self.get_abs_path(), 'r+b') as dst: # dst.seek(range_start) # shutil.copyfileobj(request, dst) + + +class MarkupResource(MarkupDavResource, DummyFSDAVResource): + NAME = 'For Markup' + PATH = f'/{NAME}/' + + def get_parent(self): + parent_path = self.path[:-1] + if len(parent_path): + log.info(f"markup folder get parent for {self.user} / {self.path}: {parent_path} - PARENT") + return MarkupResource(path=self.construct_path(parent_path, True), user=self.user) + else: + log.info(f"markup folder get parent for {self.user} / {self.path}: {parent_path} - ROOT") + return RootFolderResource(user=self.user) + + def get_children(self): + path = self.get_abs_path() + log.info(f"markup folder get_children '{path}' user: {self.user}") + if os.path.isdir(path): + for child in os.listdir(path): + is_unicode = isinstance(child, str) + if not is_unicode: + child = child.decode(fs_encoding) + yield MarkupResource(path=url_join(*(self.path + [child])), user=self.user) + + def get_abs_path(self): + path = os.path.join(self.get_markup_user_path(), *self.path[1:]) + # log.debug(f'markup folder user is {self.user} and path {self.path}: {path}') + return path + + def get_markup_user_path(self): + log.debug(f'get_markup_user_path for {self} has user {self.user}') + return os.path.join(f'{ASSET_DIR}/webdav/', 'markup', self.user.username) + + def write(self, request, temp_file=None, range_start=None): + # make sure dest dir exists first + dest_dir = dirname(self.get_abs_path()) + Path(dest_dir).mkdir(parents=True, exist_ok=True) + super().write(request, temp_file=temp_file, range_start=range_start) + + def get_acl(self): + return FullAcl() + + +class NonsensicalResource(MarkupDavResource): + + def get_parent(self): + log.debug(f'nonsense parent of {self.path}') + return RootFolderResource(path=RootFolderResource.PATH, user=self.user) + + def get_children(self): + return + yield + + @property + def getcontentlength(self): + return 0 + + def get_created(self): + return datetime.datetime.now() + + def get_modified(self): + return datetime.datetime.now() + + @property + def is_collection(self): + return False + + @property + def is_object(self): + return True + + @property + def exists(self): + return False