diff --git a/src/repdav/textgrid_dav_provider.py b/src/repdav/textgrid_dav_provider.py
index 557e849e83560ce583fa017e7ecb230c44b46a36..844df4f3499d7108715d9b5a2c58f9fbc18f0137 100644
--- a/src/repdav/textgrid_dav_provider.py
+++ b/src/repdav/textgrid_dav_provider.py
@@ -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)