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 BASE_DIR, ASSET_DIR from .utils import clean_path, ensure_dir, set_file_perms 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) elif path == ReadmeResource.PATH: return ReadmeResource(**kwargs) elif is_autosave_path(path): return AutosaveResource(**kwargs) else: return MiscResource(**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) def is_autosave_path(path): return re.search(fr'^{AutosaveResource.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 __init__(self, path=PATH, user=None): super().__init__(path=path, user=user) def get_parent(self): return None def get_children(self): children = [CatalogFolderResource(user=self.user), MarkupResource(user=self.user), ReadmeResource(user=self.user), AutosaveResource(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(AutosaveResource.PATH, self.path[-1]) autosave = AutosaveResource(path=autosave_path, user=self.user) autosave_dir = autosave.get_abs_path() 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:]) 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 MiscResource(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) path = os.path.join(base_dir, *self.path[1:]) return path def get_parent(self): 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 class ReadmeResource(MarkupDavResource, DummyFSDAVResource): NAME = 'Quick Start.pdf' 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): return yield def get_abs_path(self): return os.path.join(BASE_DIR, 'markup', self.NAME) class AutosaveResource(MarkupDavResource, DummyFSDAVResource): NAME = 'Autosave' PATH = f'/{NAME}/' SUBDIR = 'autosave' 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 AutosaveResource(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 AutosaveResource(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:]) 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()