Skip to content
Snippets Groups Projects
Verified Commit 49f8b4f6 authored by Stefan Hynek's avatar Stefan Hynek :drooling_face:
Browse files

feat(textgrid_dav_provider): provide streamed write to resources with...

feat(textgrid_dav_provider): provide streamed write to resources with FileLikeQueue and threaded worker

uses a modified version of FileLikeQueue; also, prepare for wsgidav 4 compatibility
parent 1b551e9b
No related branches found
No related tags found
1 merge request!8Resolve "integrate with tgclients lib"
......@@ -2,23 +2,21 @@
"""
import io
import logging
import threading
from pprint import pformat
from tgclients.crud import TextgridCRUD
from tgclients.config import TextgridConfig
from tgclients.metadata import TextgridMetadata
from tgclients.crud import TextgridCRUD
from wsgidav.dav_provider import DAVCollection, DAVNonCollection, DAVProvider
from wsgidav.stream_tools import FileLikeQueue
from wsgidav.util import join_uri, pop_path
from repdav.stream_tools import FileLikeQueue
from .tgapi import TextgridAuth, TextgridSearch
_logger = logging.getLogger(__name__)
# TODO think about caching with the session ID as key
class TextgridRoot(DAVCollection):
"""Top level collection that incorporates Textgrid projects.
......@@ -42,20 +40,32 @@ class TextgridRoot(DAVCollection):
_logger.debug("Called TextgridRoot.get_member(self, %s).", name)
return TextgridProject(join_uri(self.path, name), self.environ)
def support_etag(self):
"""Return True, if this resource supports ETags."""
return False
def get_etag(self):
"""
See http://www.webdav.org/specs/rfc4918.html#PROPERTY_getetag
This method SHOULD be implemented, especially by non-collections.
"""
return None
# temporary override for debugging
def resolve(self, script_name, path_info):
"""Return a _DAVResource object for the path (None, if not found).
`path_info`: is a URL relative to this object.
"""
_logger.debug("Called TextgridRoot.resolve(self, %s, %s).",
script_name, path_info)
_logger.debug(
"Called TextgridRoot.resolve(self, %s, %s).", script_name, path_info
)
if path_info in ("", "/"):
return self
assert path_info.startswith("/")
name, rest = pop_path(path_info)
res = self.get_member(name)
_logger.debug("TextgridRoot_NAME: %s, REST: %s, RES: %s",
name, rest, res)
_logger.debug("TextgridRoot_NAME: %s, REST: %s, RES: %s", name, rest, res)
if res is None or rest in ("", "/"):
return res
_logger.debug("RES: %s", res)
......@@ -64,8 +74,7 @@ class TextgridRoot(DAVCollection):
class TextgridProject(DAVCollection):
def __init__(self, path, environ):
_logger.debug(
"Called TextgridProject.__init__(self, %s, environ).", path)
_logger.debug("Called TextgridProject.__init__(self, %s, environ).", path)
DAVCollection.__init__(self, path, environ)
self._sid = environ["wsgidav.auth.user_name"]
......@@ -88,8 +97,8 @@ class TextgridProject(DAVCollection):
#
# path resolution has to be rewritten before we can work with resource titles
resources = TextgridSearch().get_project_contents(
self._sid, self.path.split("/")[-1])
#_logger.debug("RESOURCES: %s", resources)
self._sid, self.path.split("/")[-1]
)
return resources.keys()
def get_member(self, name):
......@@ -106,31 +115,44 @@ class TextgridProject(DAVCollection):
def copy_move_single(self, dest_path, is_move):
pass
def support_etag(self):
"""Return True, if this resource supports ETags."""
return False
def get_etag(self):
"""
See http://www.webdav.org/specs/rfc4918.html#PROPERTY_getetag
This method SHOULD be implemented, especially by non-collections.
"""
return None
# temporary override for debugging
def resolve(self, script_name, path_info):
"""Return a _DAVResource object for the path (None, if not found).
`path_info`: is a URL relative to this object.
"""
_logger.debug(
"Called TextgridProject.resolve(self, %s, %s).", script_name, path_info)
"Called TextgridProject.resolve(self, %s, %s).", script_name, path_info
)
if path_info in ("", "/"):
return self
assert path_info.startswith("/")
name, rest = pop_path(path_info)
res = self.get_member(name)
_logger.debug(
"TextgridProject_NAME: %s, REST: %s, RES: %s", name, rest, res)
_logger.debug("TextgridProject_NAME: %s, REST: %s, RES: %s", name, rest, res)
if res is None or rest in ("", "/"):
return res
return res.resolve(join_uri(script_name, name), rest)
# TODO: merge TextgridProject with TextgridAggregation and/or derive from a common base class.
# TODO: merge TextgridProject with TextgridAggregation
# and/or derive from a common base class.
class TextgridAggregation(DAVCollection):
def __init__(self, path, environ, info):
_logger.debug(
"Called TextgridAggregation.__init__(self, %s, environ).", path)
_logger.debug("Called TextgridAggregation.__init__(self, %s, environ, info).", path)
DAVCollection.__init__(self, path, environ)
self._sid = environ["wsgidav.auth.user_name"]
self._info = info
......@@ -147,8 +169,9 @@ class TextgridAggregation(DAVCollection):
def get_member_names(self):
_logger.debug("Called TextgridAggregation.get_member_names(self).")
resources = TextgridSearch().get_aggregation_contents(
self._sid, self.path.split("/")[-1])
#_logger.debug("RESOURCES: %s", resources)
self._sid, self.path.split("/")[-1]
)
# _logger.debug("RESOURCES: %s", resources)
return resources.keys()
def get_member(self, name):
......@@ -165,20 +188,34 @@ class TextgridAggregation(DAVCollection):
def copy_move_single(self, dest_path, is_move):
pass
def support_etag(self):
"""Return True, if this resource supports ETags."""
return False
def get_etag(self):
"""
See http://www.webdav.org/specs/rfc4918.html#PROPERTY_getetag
This method SHOULD be implemented, especially by non-collections.
"""
return None
# temporary override for debugging
def resolve(self, script_name, path_info):
"""Return a _DAVResource object for the path (None, if not found).
`path_info`: is a URL relative to this object.
"""
_logger.debug(
"Called TextgridAggregation.resolve(self, %s, %s).", script_name, path_info)
"Called TextgridAggregation.resolve(self, %s, %s).", script_name, path_info
)
if path_info in ("", "/"):
return self
assert path_info.startswith("/")
name, rest = pop_path(path_info)
res = self.get_member(name)
_logger.debug(
"TextgridAggregation_NAME: %s, REST: %s, RES: %s", name, rest, res)
"TextgridAggregation_NAME: %s, REST: %s, RES: %s", name, rest, res
)
if res is None or rest in ("", "/"):
return res
return res.resolve(join_uri(script_name, name), rest)
......@@ -188,11 +225,12 @@ class TextgridResource(DAVNonCollection):
"""Non-Aggregation resources."""
def __init__(self, path, environ, info):
_logger.debug(
"Called TextgridResource.__init__(self, %s, environ).", path)
_logger.debug("Called TextgridResource.__init__(self, %s, environ).", path)
DAVNonCollection.__init__(self, path, environ)
self._size = environ.get("CONTENT_LENGTH")
self._sid = environ["wsgidav.auth.user_name"]
self._info = info
self.upload_thread = None
def get_content_length(self):
_logger.debug("Called TextgridResource.get_content_length(self).")
......@@ -213,20 +251,43 @@ class TextgridResource(DAVNonCollection):
def begin_write(self, content_type=None):
_logger.debug(
"Called TextgridResource.begin_write(self, content_type=%s).", content_type)
config = TextgridConfig()
"Called TextgridResource.begin_write(self, content_type=%s).", content_type
)
queue = FileLikeQueue(int(self._size))
config = TextgridConfig("http://textgridlab.org/")
crud = TextgridCRUD(config.crud)
queue = FileLikeQueue(max_size=1)
# create metadata or update?
metadata = TextgridMetadata.create(
self.get_content_title(), self.get_content_type())
crud.update_resource(
self._sid, self.path.split("/")[-1], queue, metadata)
metadata = crud.read_metadata(self.path.split("/")[-1], self._sid).content
def worker():
_logger.debug("Called TextgridResource.begin_write.worker().")
crud.update_resource(self._sid, self.path.split("/")[-1], queue, metadata)
thread = threading.Thread(target=worker)
thread.setDaemon(True)
thread.start()
self.upload_thread = thread
return queue
def end_write(self, with_errors):
_logger.debug(
"Called TextgridResource.end_write(self, with_errors=%s)", with_errors)
"Called TextgridResource.end_write(self, with_errors=%s)", with_errors
)
if self.upload_thread:
self.upload_thread.join()
self.upload_thread = None
def support_etag(self):
"""Return True, if this resource supports ETags."""
return False
def get_etag(self):
"""
See http://www.webdav.org/specs/rfc4918.html#PROPERTY_getetag
This method SHOULD be implemented, especially by non-collections.
"""
return None
# temporary override for debugging
def resolve(self, script_name, path_info):
......@@ -234,7 +295,8 @@ class TextgridResource(DAVNonCollection):
`path_info`: is a URL relative to this object.
"""
_logger.debug(
"Called TextgridResource.resolve(self, %s, %s).", script_name, path_info)
"Called TextgridResource.resolve(self, %s, %s).", script_name, path_info
)
if path_info in ("", "/"):
return self
return None
......@@ -244,12 +306,14 @@ class TextgridResource(DAVNonCollection):
# TextgridResourceProvider
# ============================================================================
class TextgridResourceProvider(DAVProvider):
"""DAV provider that serves Textgrid resources.
"""
"""DAV provider that serves Textgrid resources."""
def get_resource_inst(self, path, environ):
_logger.debug(
"Called TextgridResourceProvider.get_resource_inst(self, %s, %s).", path, pformat(environ))
"Called TextgridResourceProvider.get_resource_inst(self, %s, %s).",
path,
pformat(environ),
)
self._count_get_resource_inst += 1
root = TextgridRoot("/", environ)
# an instance of _DAVResource (i.e. either DAVCollection or DAVNonCollection)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment