Hello again!

I've also been working on a patch to support this feature, just got it
mixed up when answering a few minutes ago.

This works for me to add multiple calendars as one account with
Caldav-sync on Android.

I would be willing to put a bit more work into this if there are any
issues but i would need some feedback in that case.

Please find attached a single patch file with all of my changes and a
tar ball of the individual commits during my development.

Again, best regards
/Ulrik Haugen

diff --git a/calypso/__init__.py b/calypso/__init__.py
index ba53288..f942d0b 100644
--- a/calypso/__init__.py
+++ b/calypso/__init__.py
@@ -229,6 +229,24 @@ class CollectionHTTPHandler(server.BaseHTTPRequestHandler):
             CollectionHTTPHandler.collections[path] = webdav.Collection(path)
         return CollectionHTTPHandler.collections[path]
 
+    @property
+    def _collections(self):
+        """The ``webdav.Collection`` objects corresponding to the given path
+        or its children."""
+        cpaths = paths.collections_from_path(self.path)
+        if not cpaths:
+            return []
+        # This could all be done in a single list comprehension using
+        # setdefault but this would negate the benefit of caching the
+        # webdav collections as arguments to setdefault are evaluated
+        # regardless of the keys existence.
+        for path in cpaths:
+            if path not in CollectionHTTPHandler.collections:
+                CollectionHTTPHandler.collections[path] = webdav.Collection(
+                    path)
+        return [ CollectionHTTPHandler.collections[path] for path in cpaths ]
+
+
     def _decode(self, text):
         """Try to decode text according to various parameters."""
         # List of charsets to try
@@ -372,8 +390,10 @@ class CollectionHTTPHandler(server.BaseHTTPRequestHandler):
         try:
             xml_request = self.xml_request
             log.debug("PROPFIND %s", xml_request)
+            is_event_or_calendar = paths.collection_from_path(self.path) is not None
+            collection_or_collections = [self._collection] if is_event_or_calendar else self._collections
             self._answer = xmlutils.propfind(
-                self.path, xml_request, self._collection,
+                self.path, xml_request, is_event_or_calendar, collection_or_collections,
                 self.headers.get("depth", "infinity"))
             log.debug("PROPFIND ANSWER %s", self._answer)
 
diff --git a/calypso/paths.py b/calypso/paths.py
index 11640aa..db5b702 100644
--- a/calypso/paths.py
+++ b/calypso/paths.py
@@ -59,9 +59,20 @@ def url_to_file(url):
     file = os.path.join(data_root(), tail)
     return file
 
+
+#
+# Given an absolute path name, convert it to a URL by
+# removing the storage folder name
+#
+
+def file_to_url(path):
+    head = data_root().rstrip('/')
+    assert(path.startswith(head))
+    return path[len(head) : ]
+
 #
-# Does the provided URL reference a collection? This
-# is done by seeing if the resulting path is a directory
+# Does the provided URL reference a collection? This is done by seeing
+# if the resulting path is a git controlled directory
 #
 
 def is_collection(url):
@@ -76,6 +87,24 @@ def is_collection(url):
         urlpath, stripped = os.path.split(urlpath)
 
 #
+# Does the provided URL reference a collection of collections? This is
+# done by seeing if the resulting path is a directory with git
+# controlled sub directories
+#
+
+def is_collection_collection(url):
+    urlpath = url_to_file(url)
+    if not os.path.isdir(urlpath):
+        return False
+    for childname in os.listdir(urlpath):
+        childdir = os.path.join(urlpath, childname)
+        if (os.path.isdir(childdir)
+            and os.path.isdir(os.path.join(childdir, '.git'))):
+            return True
+    return False
+
+
+#
 # Given a URL, return the parent URL by stripping off
 # the last path element
 #
@@ -86,14 +115,27 @@ def parent_url(path):
     new_path = "/" + "/".join(path_parent)
     return new_path
 
+
+#
+# Given a URL, return the children URLs by finding git controlled sub
+# directories
+#
+
+def children_urls(path):
+    urlpath = url_to_file(path)
+    return [ "/" + os.path.join(path, childdir)
+             for childdir in os.listdir(urlpath)
+             if (os.path.isdir(os.path.join(urlpath, childdir))
+                 and os.path.isdir(os.path.join(urlpath, childdir, '.git'))) ]
+
+
+
 #
 # If the given URL references a resource, then
 # return the name of that resource. Otherwise,
 # return None
 #
 
-log = logging.getLogger()
-
 def resource_from_path(path):
     """Return Calypso item name from ``path``."""
     if is_collection(path):
@@ -124,4 +166,31 @@ def collection_from_path(path):
 
     log.debug('Path %s results in collection: %s', path, collection)
     return collection
+
+#
+# Return the collection names for the given URL. That's just the URL
+# if it refers to a collection itself, otherwise it's the children or
+# the parent of the provided URL
+#
+
+def collections_from_path(path):
+    """Returns Calypso collection names from/in ``path``."""
+
+    collections = path
+    if not is_collection(collections):
+        if is_collection_collection(collections):
+            collections = children_urls(collections)
+        else:
+            collections = parent_url(collections)
+            if not is_collection(collections):
+                log.debug("No collection found for path %s", path)
+                return None
+
+    # unquote, strip off any trailing slash, then clean up /../ and // entries
+    collections = [ "/" + urllib.unquote(collection).strip("/")
+                    for collection in collections ]
+
+    log.debug('Path %s results in collection(s): %s',
+              path, ', '.join(collections))
+    return collections
     
diff --git a/calypso/webdav.py b/calypso/webdav.py
index 85b3bb9..050add5 100644
--- a/calypso/webdav.py
+++ b/calypso/webdav.py
@@ -461,7 +461,7 @@ class Collection(object):
                 self.destroy_file(old_item, context=context)
                 
     def replace(self, name, text, context):
-        """Replace content by ``text`` in objet named ``name`` in collection."""
+        """Replace content by ``text`` in object named ``name`` in collection."""
 
         path=None
         old_item = self.get_item(name)
diff --git a/calypso/xmlutils.py b/calypso/xmlutils.py
index a1e8c8c..740cc8e 100644
--- a/calypso/xmlutils.py
+++ b/calypso/xmlutils.py
@@ -86,16 +86,13 @@ def delete(path, collection, context):
     return ET.tostring(multistatus, config.get("encoding", "request"))
 
 
-def propfind(path, xml_request, collection, depth):
+def propfind(path, xml_request, is_event_or_calendar, collections, depth):
     """Read and answer PROPFIND requests.
 
     Read rfc4918-9.1 for info.
 
     """
 
-    item_name = paths.resource_from_path(path)
-    collection_name = paths.collection_from_path(path)
-
     # Reading request
     root = ET.fromstring(xml_request)
 
@@ -119,7 +116,10 @@ def propfind(path, xml_request, collection, depth):
     # Writing answer
     multistatus = ET.Element(_tag("D", "multistatus"))
 
-    if collection:
+    if is_event_or_calendar:
+        item_name = paths.resource_from_path(path)
+        collection_name = paths.collection_from_path(path)
+        (collection,) = collections
         if item_name:
             item = collection.get_item(item_name)
             print "item_name %s item %s" % (item_name, item)
@@ -135,7 +135,7 @@ def propfind(path, xml_request, collection, depth):
                 # we limit ourselves to depth == 1
                 items = [collection] + collection.items
     else:
-        items = []
+        items = collections[:]
 
     for item in items:
         is_collection = isinstance(item, webdav.Collection)
@@ -144,7 +144,8 @@ def propfind(path, xml_request, collection, depth):
         multistatus.append(response)
 
         href = ET.Element(_tag("D", "href"))
-        href.text = collection_name if is_collection else "/".join([collection_name, item.name])
+        href.text = paths.file_to_url(item.path)
+        log.debug("href text: %s", paths.file_to_url(item.path))
         response.append(href)
 
         propstat = ET.Element(_tag("D", "propstat"))
@@ -174,7 +175,7 @@ def propfind(path, xml_request, collection, depth):
             elif tag == _tag("D", "getetag"):
                 element.text = item.etag
             elif tag == _tag("D", "displayname") and is_collection:
-                element.text = collection.name
+                element.text = item.name
             elif tag == _tag("D", "principal-URL"):
                 # TODO: use a real principal URL, read rfc3744-4.2 for info
                 tag = ET.Element(_tag("D", "href"))

Attachment: list-support-patches.tgz
Description: list support commits

Reply via email to