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 datetime
import os
import shutil
import logging import logging
import os
import re
import shutil
from os.path import dirname
from pathlib import Path
from sys import getfilesystemencoding from sys import getfilesystemencoding
from django.core.exceptions import PermissionDenied
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django_http_auth.decorators import http_basic_auth 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.base.resources import BaseDavResource, MetaEtagMixIn
from djangodav.fs.resources import DummyFSDAVResource
from djangodav.locks import DummyLock from djangodav.locks import DummyLock
from djangodav.utils import url_join from djangodav.utils import url_join
from djangodav.views import DavView from djangodav.views import DavView
from procat2.models import Catalog from procat2.models import Catalog
from procat2.settings import ASSET_DIR
# from procat2.settings import ASSET_DIR
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -27,122 +33,174 @@ fs_encoding = getfilesystemencoding()
class MarkupDavView(DavView): class MarkupDavView(DavView):
def __init__(self): 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): 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): # def __init__(self, path=None, user=None):
super().__init__(path) # super().__init__(path)
self.user = user # self.user = user
def __str__(self): 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): def get_children(self):
"""Return an iterator of all direct children of this resource.""" children = [CatalogFolderResource(CatalogFolderResource.PATH, self.user),
# make sure the current object is a directory MarkupResource(MarkupResource.PATH, self.user)]
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)
for child in children: for child in children:
is_unicode = isinstance(child, str) yield child
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)
@property @property
def getcontentlength(self): def getcontentlength(self):
"""Return the size of the resource in bytes.""" return 2
if self.is_collection:
return 0
else:
return os.path.getsize(self.get_abs_path())
def get_created(self): def get_created(self):
"""Return the create time as datetime object.""" return datetime.datetime.now() # TODO last created time of object in there?
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)
def get_modified(self): def get_modified(self):
"""Return the modified time as datetime object.""" return datetime.datetime.now() # TODO last modified time of object in there?
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)
@property @property
def is_collection(self): def is_collection(self):
"""Return True if this resource is a directory (collection in WebDAV parlance).""" return True
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())
@property @property
def is_object(self): def is_object(self):
"""Return True if this resource is a file (resource in WebDAV parlance).""" return False
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())
@property @property
def exists(self): def exists(self):
"""Return True if this resource exists.""" return True
path = '/'.join(self.path)
log.info(f"exists {path}")
if self.is_collection:
return True
# TODO test catalog pdf class CatalogFolderResource(MarkupDavResource):
return os.path.exists(self.get_abs_path()) 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): def get_parent(self):
# raise NotImplementedError return RootFolderResource(user=self.user)
# def read(self): def get_children(self):
# raise NotImplementedError 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): # def delete(self):
# """Delete the resource, recursive is implied.""" # """Delete the resource, recursive is implied."""
@ -163,8 +221,11 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource):
# def move_object(self, destination): # def move_object(self, destination):
# os.rename(self.get_abs_path(), destination.get_abs_path()) # os.rename(self.get_abs_path(), destination.get_abs_path())
# def read(self): def read(self):
# return open(self.get_abs_path(), 'rb') 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): # def write(self, request, temp_file=None, range_start=None):
# if temp_file: # if temp_file:
@ -179,3 +240,78 @@ class MarkupDavResource(MetaEtagMixIn, BaseDavResource):
# with open(self.get_abs_path(), 'r+b') as dst: # with open(self.get_abs_path(), 'r+b') as dst:
# dst.seek(range_start) # dst.seek(range_start)
# shutil.copyfileobj(request, dst) # 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