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 @@ ...@@ -2,23 +2,21 @@
""" """
import io import io
import logging import logging
import threading
from pprint import pformat from pprint import pformat
from tgclients.crud import TextgridCRUD
from tgclients.config import TextgridConfig 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.dav_provider import DAVCollection, DAVNonCollection, DAVProvider
from wsgidav.stream_tools import FileLikeQueue
from wsgidav.util import join_uri, pop_path from wsgidav.util import join_uri, pop_path
from repdav.stream_tools import FileLikeQueue
from .tgapi import TextgridAuth, TextgridSearch from .tgapi import TextgridAuth, TextgridSearch
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
# TODO think about caching with the session ID as key
class TextgridRoot(DAVCollection): class TextgridRoot(DAVCollection):
"""Top level collection that incorporates Textgrid projects. """Top level collection that incorporates Textgrid projects.
...@@ -42,20 +40,32 @@ class TextgridRoot(DAVCollection): ...@@ -42,20 +40,32 @@ class TextgridRoot(DAVCollection):
_logger.debug("Called TextgridRoot.get_member(self, %s).", name) _logger.debug("Called TextgridRoot.get_member(self, %s).", name)
return TextgridProject(join_uri(self.path, name), self.environ) 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 # temporary override for debugging
def resolve(self, script_name, path_info): def resolve(self, script_name, path_info):
"""Return a _DAVResource object for the path (None, if not found). """Return a _DAVResource object for the path (None, if not found).
`path_info`: is a URL relative to this object. `path_info`: is a URL relative to this object.
""" """
_logger.debug("Called TextgridRoot.resolve(self, %s, %s).", _logger.debug(
script_name, path_info) "Called TextgridRoot.resolve(self, %s, %s).", script_name, path_info
)
if path_info in ("", "/"): if path_info in ("", "/"):
return self return self
assert path_info.startswith("/") assert path_info.startswith("/")
name, rest = pop_path(path_info) name, rest = pop_path(path_info)
res = self.get_member(name) res = self.get_member(name)
_logger.debug("TextgridRoot_NAME: %s, REST: %s, RES: %s", _logger.debug("TextgridRoot_NAME: %s, REST: %s, RES: %s", name, rest, res)
name, rest, res)
if res is None or rest in ("", "/"): if res is None or rest in ("", "/"):
return res return res
_logger.debug("RES: %s", res) _logger.debug("RES: %s", res)
...@@ -64,8 +74,7 @@ class TextgridRoot(DAVCollection): ...@@ -64,8 +74,7 @@ class TextgridRoot(DAVCollection):
class TextgridProject(DAVCollection): class TextgridProject(DAVCollection):
def __init__(self, path, environ): def __init__(self, path, environ):
_logger.debug( _logger.debug("Called TextgridProject.__init__(self, %s, environ).", path)
"Called TextgridProject.__init__(self, %s, environ).", path)
DAVCollection.__init__(self, path, environ) DAVCollection.__init__(self, path, environ)
self._sid = environ["wsgidav.auth.user_name"] self._sid = environ["wsgidav.auth.user_name"]
...@@ -88,8 +97,8 @@ class TextgridProject(DAVCollection): ...@@ -88,8 +97,8 @@ class TextgridProject(DAVCollection):
# #
# path resolution has to be rewritten before we can work with resource titles # path resolution has to be rewritten before we can work with resource titles
resources = TextgridSearch().get_project_contents( resources = TextgridSearch().get_project_contents(
self._sid, self.path.split("/")[-1]) self._sid, self.path.split("/")[-1]
#_logger.debug("RESOURCES: %s", resources) )
return resources.keys() return resources.keys()
def get_member(self, name): def get_member(self, name):
...@@ -106,31 +115,44 @@ class TextgridProject(DAVCollection): ...@@ -106,31 +115,44 @@ class TextgridProject(DAVCollection):
def copy_move_single(self, dest_path, is_move): def copy_move_single(self, dest_path, is_move):
pass 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 # temporary override for debugging
def resolve(self, script_name, path_info): def resolve(self, script_name, path_info):
"""Return a _DAVResource object for the path (None, if not found). """Return a _DAVResource object for the path (None, if not found).
`path_info`: is a URL relative to this object. `path_info`: is a URL relative to this object.
""" """
_logger.debug( _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 ("", "/"): if path_info in ("", "/"):
return self return self
assert path_info.startswith("/") assert path_info.startswith("/")
name, rest = pop_path(path_info) name, rest = pop_path(path_info)
res = self.get_member(name) res = self.get_member(name)
_logger.debug( _logger.debug("TextgridProject_NAME: %s, REST: %s, RES: %s", name, rest, res)
"TextgridProject_NAME: %s, REST: %s, RES: %s", name, rest, res)
if res is None or rest in ("", "/"): if res is None or rest in ("", "/"):
return res return res
return res.resolve(join_uri(script_name, name), rest) 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): class TextgridAggregation(DAVCollection):
def __init__(self, path, environ, info): def __init__(self, path, environ, info):
_logger.debug( _logger.debug("Called TextgridAggregation.__init__(self, %s, environ, info).", path)
"Called TextgridAggregation.__init__(self, %s, environ).", path)
DAVCollection.__init__(self, path, environ) DAVCollection.__init__(self, path, environ)
self._sid = environ["wsgidav.auth.user_name"] self._sid = environ["wsgidav.auth.user_name"]
self._info = info self._info = info
...@@ -147,8 +169,9 @@ class TextgridAggregation(DAVCollection): ...@@ -147,8 +169,9 @@ class TextgridAggregation(DAVCollection):
def get_member_names(self): def get_member_names(self):
_logger.debug("Called TextgridAggregation.get_member_names(self).") _logger.debug("Called TextgridAggregation.get_member_names(self).")
resources = TextgridSearch().get_aggregation_contents( resources = TextgridSearch().get_aggregation_contents(
self._sid, self.path.split("/")[-1]) self._sid, self.path.split("/")[-1]
#_logger.debug("RESOURCES: %s", resources) )
# _logger.debug("RESOURCES: %s", resources)
return resources.keys() return resources.keys()
def get_member(self, name): def get_member(self, name):
...@@ -165,20 +188,34 @@ class TextgridAggregation(DAVCollection): ...@@ -165,20 +188,34 @@ class TextgridAggregation(DAVCollection):
def copy_move_single(self, dest_path, is_move): def copy_move_single(self, dest_path, is_move):
pass 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 # temporary override for debugging
def resolve(self, script_name, path_info): def resolve(self, script_name, path_info):
"""Return a _DAVResource object for the path (None, if not found). """Return a _DAVResource object for the path (None, if not found).
`path_info`: is a URL relative to this object. `path_info`: is a URL relative to this object.
""" """
_logger.debug( _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 ("", "/"): if path_info in ("", "/"):
return self return self
assert path_info.startswith("/") assert path_info.startswith("/")
name, rest = pop_path(path_info) name, rest = pop_path(path_info)
res = self.get_member(name) res = self.get_member(name)
_logger.debug( _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 ("", "/"): if res is None or rest in ("", "/"):
return res return res
return res.resolve(join_uri(script_name, name), rest) return res.resolve(join_uri(script_name, name), rest)
...@@ -188,11 +225,12 @@ class TextgridResource(DAVNonCollection): ...@@ -188,11 +225,12 @@ class TextgridResource(DAVNonCollection):
"""Non-Aggregation resources.""" """Non-Aggregation resources."""
def __init__(self, path, environ, info): def __init__(self, path, environ, info):
_logger.debug( _logger.debug("Called TextgridResource.__init__(self, %s, environ).", path)
"Called TextgridResource.__init__(self, %s, environ).", path)
DAVNonCollection.__init__(self, path, environ) DAVNonCollection.__init__(self, path, environ)
self._size = environ.get("CONTENT_LENGTH")
self._sid = environ["wsgidav.auth.user_name"] self._sid = environ["wsgidav.auth.user_name"]
self._info = info self._info = info
self.upload_thread = None
def get_content_length(self): def get_content_length(self):
_logger.debug("Called TextgridResource.get_content_length(self).") _logger.debug("Called TextgridResource.get_content_length(self).")
...@@ -213,20 +251,43 @@ class TextgridResource(DAVNonCollection): ...@@ -213,20 +251,43 @@ class TextgridResource(DAVNonCollection):
def begin_write(self, content_type=None): def begin_write(self, content_type=None):
_logger.debug( _logger.debug(
"Called TextgridResource.begin_write(self, content_type=%s).", content_type) "Called TextgridResource.begin_write(self, content_type=%s).", content_type
config = TextgridConfig() )
queue = FileLikeQueue(int(self._size))
config = TextgridConfig("http://textgridlab.org/")
crud = TextgridCRUD(config.crud) crud = TextgridCRUD(config.crud)
queue = FileLikeQueue(max_size=1) metadata = crud.read_metadata(self.path.split("/")[-1], self._sid).content
# create metadata or update?
metadata = TextgridMetadata.create( def worker():
self.get_content_title(), self.get_content_type()) _logger.debug("Called TextgridResource.begin_write.worker().")
crud.update_resource( crud.update_resource(self._sid, self.path.split("/")[-1], queue, metadata)
self._sid, self.path.split("/")[-1], queue, metadata)
thread = threading.Thread(target=worker)
thread.setDaemon(True)
thread.start()
self.upload_thread = thread
return queue return queue
def end_write(self, with_errors): def end_write(self, with_errors):
_logger.debug( _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 # temporary override for debugging
def resolve(self, script_name, path_info): def resolve(self, script_name, path_info):
...@@ -234,7 +295,8 @@ class TextgridResource(DAVNonCollection): ...@@ -234,7 +295,8 @@ class TextgridResource(DAVNonCollection):
`path_info`: is a URL relative to this object. `path_info`: is a URL relative to this object.
""" """
_logger.debug( _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 ("", "/"): if path_info in ("", "/"):
return self return self
return None return None
...@@ -244,12 +306,14 @@ class TextgridResource(DAVNonCollection): ...@@ -244,12 +306,14 @@ class TextgridResource(DAVNonCollection):
# TextgridResourceProvider # TextgridResourceProvider
# ============================================================================ # ============================================================================
class TextgridResourceProvider(DAVProvider): class TextgridResourceProvider(DAVProvider):
"""DAV provider that serves Textgrid resources. """DAV provider that serves Textgrid resources."""
"""
def get_resource_inst(self, path, environ): def get_resource_inst(self, path, environ):
_logger.debug( _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 self._count_get_resource_inst += 1
root = TextgridRoot("/", environ) root = TextgridRoot("/", environ)
# an instance of _DAVResource (i.e. either DAVCollection or DAVNonCollection) # 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