markup/webdav.py: mostly functional
This commit is contained in:
324
markup/webdav.py
324
markup/webdav.py
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user