markup/webdav.py: mostly functional

This commit is contained in:
2020-02-26 16:30:02 -08:00
parent 012c65cc7c
commit 136f9808df

View File

@ -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"<MarkupDavResource '{self.user}' '{self.path}'>"
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