Files
procat2/markup/webdav.py

327 lines
8.9 KiB
Python

import datetime
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 django.http import HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseBadRequest, \
HttpResponseRedirect, Http404, HttpResponse, FileResponse
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 .utils import clean_path, ensure_dir, set_file_perms, WORKDIR
from .tasks import process_markup_pdf
log = logging.getLogger(__name__)
fs_encoding = getfilesystemencoding()
@method_decorator(http_basic_auth, name='dispatch')
class MarkupDavView(DavView):
def __init__(self):
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):
def __str__(self):
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(self):
return None
def get_children(self):
children = [CatalogFolderResource(user=self.user),
MarkupResource(user=self.user),
]
for child in children:
yield child
@property
def getcontentlength(self):
return 2
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 CatalogFolderResource(MarkupDavResource):
NAME = 'My Catalogs'
PATH = f'/{NAME}/'
def __init__(self, path=PATH, user=None):
super().__init__(path=path, user=user)
def get_parent(self):
return RootFolderResource(user=self.user)
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(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):
return self.catalog.pdf_exists()
def read(self):
if self.catalog.pdf_exists():
return open(self.catalog.pdf_file(), 'rb')
else:
return None
def delete(self):
pass
def copy_object(self, destination, depth=0):
dest = destination.get_abs_path()
dest_dir = dirname(dest)
Path(dest_dir).mkdir(parents=True, exist_ok=True)
shutil.copy(self.catalog.pdf_file(), dest)
def move_object(self, destination):
os.rename(self.get_abs_path(), destination.get_abs_path())
def write(self, request, temp_file=None, range_start=None):
autosave_path = url_join(MarkupResource.PATH, 'Autosave', self.path[-1])
autosave = MarkupResource(path=autosave_path, user=self.user)
autosave_dir = autosave.get_abs_path()
log.debug(f'autosave_dir: {autosave_dir}')
ensure_dir(dirname(autosave_dir))
return autosave.write(request, temp_file=None, range_start=None)
def get_acl(self):
return DavAcl(read=True, write=True, delete=False, full=None)
def get_markup_user_path(user):
return os.path.join(ASSET_DIR, 'markup', 'webdav', user.username if user else 'unknown_user')
class MarkupResource(MarkupDavResource, DummyFSDAVResource):
NAME = 'Markup'
PATH = f'/{NAME}/'
SUBDIR = 'markup'
def __init__(self, path=PATH, user=None):
super().__init__(path=path, user=user)
def get_parent(self):
parent_path = self.path[:-1]
if len(parent_path):
return MarkupResource(path=self.construct_path(parent_path, True), user=self.user)
else:
return RootFolderResource(user=self.user)
def get_children(self):
path = self.get_abs_path()
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):
base_dir = os.path.join(get_markup_user_path(self.user), self.SUBDIR)
ensure_dir(base_dir)
path = os.path.join(base_dir, *self.path[1:])
# log.debug(f'markup: get abs path for {self.path}: base {base_dir}')
return path
def write(self, request, temp_file=None, range_start=None):
super().write(request, temp_file=temp_file, range_start=range_start)
process_markup_pdf.delay(self.get_abs_path(), self.user.username)
def get_acl(self):
return FullAcl()
class NonsensicalResource(MarkupDavResource):
NAME = 'Misc'
PATH = f'/{NAME}/'
SUBDIR = 'misc'
def get_abs_path(self):
base_dir = os.path.join(get_markup_user_path(self.user), self.SUBDIR)
ensure_dir(base_dir)
log.debug(f'nonsense: get abs path for {self.path}: base {base_dir}')
path = os.path.join(base_dir, *self.path[1:])
return path
def get_parent(self):
log.debug(f'nonsense parent of {self.path} is root')
return RootFolderResource(path=RootFolderResource.PATH, user=self.user)
def get_children(self):
return
yield
def delete(self):
return HttpResponseForbidden()
def move(self):
return HttpResponseForbidden()
def copy(self, destination, depth=-1):
return HttpResponseForbidden()
@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