[CalendarServer-changes] [9801] CalendarServer/branches/users/gaya/inviteclean

source_changes at macosforge.org source_changes at macosforge.org
Mon Sep 10 16:38:13 PDT 2012


Revision: 9801
          http://trac.macosforge.org/projects/calendarserver/changeset/9801
Author:   gaya at apple.com
Date:     2012-09-10 16:38:10 -0700 (Mon, 10 Sep 2012)
Log Message:
-----------
update from trunk

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/inviteclean/calendarserver/tools/backup_pg.py
    CalendarServer/branches/users/gaya/inviteclean/contrib/migration/calendarmigrator.py
    CalendarServer/branches/users/gaya/inviteclean/contrib/migration/test/test_migrator.py
    CalendarServer/branches/users/gaya/inviteclean/support/submit
    CalendarServer/branches/users/gaya/inviteclean/twext/web2/dav/test/test_xattrprops.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/peruserdata.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/directory/xmlaccountsparser.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/ical.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/method/get.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/method/report_common.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/resource.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/imip.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/ischeduleservers.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/itip.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/processing.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/test/test_icaldiff.py
    CalendarServer/branches/users/gaya/inviteclean/txdav/caldav/datastore/util.py
    CalendarServer/branches/users/gaya/inviteclean/txdav/xml/__init__.py
    CalendarServer/branches/users/gaya/inviteclean/txdav/xml/parser.py

Added Paths:
-----------
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/hiddeninstance.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/test/test_hiddeninstances.py
    CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/test/test_ischeduleservers.py
    CalendarServer/branches/users/gaya/inviteclean/txdav/xml/parser_etree.py
    CalendarServer/branches/users/gaya/inviteclean/txdav/xml/rfc6578.py

Removed Paths:
-------------
    CalendarServer/branches/users/gaya/inviteclean/txdav/xml/draft_sync.py

Property Changed:
----------------
    CalendarServer/branches/users/gaya/inviteclean/


Property changes on: CalendarServer/branches/users/gaya/inviteclean
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:9408-9784
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:9408-9800

Modified: CalendarServer/branches/users/gaya/inviteclean/calendarserver/tools/backup_pg.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/calendarserver/tools/backup_pg.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/calendarserver/tools/backup_pg.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -21,8 +21,6 @@
 import subprocess
 import sys
 import tarfile
-from tempfile import mkstemp
-from shutil import rmtree
 
 from twistedcaldav.config import config
 from calendarserver.tools.util import loadConfig
@@ -162,38 +160,31 @@
 
     serverRoot = config.ServerRoot
     dataRoot = config.DataRoot
-    docRoot = config.DocumentRoot
-    configRoot = config.ConfigRoot
+    dumpPath = os.path.join(serverRoot, DUMPFILENAME)
 
     if command == "backup":
 
-        fd, tmpPath = mkstemp(suffix=".dbdump")
-
         try:
-            dumpData(tmpPath, verbose=verbose)
+            dumpData(dumpPath, verbose=verbose)
 
             if verbose:
                 print "Creating %s" % (filename,)
             tar = tarfile.open(filename, "w:gz")
+
             if verbose:
-                print "Adding %s" % (dataRoot,)
-            tar.add(dataRoot, "Data")
-            if verbose:
-                print "Adding %s" % (docRoot,)
-            tar.add(docRoot, "Documents")
-            if verbose:
-                print "Adding %s" % (configRoot,)
-            tar.add(configRoot, "Config")
-            if verbose:
-                print "Adding %s" % (tmpPath,)
-            tar.add(tmpPath, DUMPFILENAME)
+                print "Adding %s" % (serverRoot,)
+            tar.add(serverRoot)
+
+            if not dataRoot.startswith(serverRoot):
+                # DataRoot is not contained within ServerRoot (i.e, it's on
+                # another volume)
+                if verbose:
+                    print "Adding %s" % (dataRoot,)
+                tar.add(dataRoot)
+
             tar.close()
 
             if verbose:
-                print "Removing %s" % (tmpPath,)
-            os.remove(tmpPath)
-
-            if verbose:
                 print "Done"
         except BackupError, e:
             error("Failed to dump database; error: %s" % (e,))
@@ -202,32 +193,16 @@
 
         try:
             tar = tarfile.open(filename, "r:gz")
-            os.chdir(serverRoot)
 
-            if os.path.exists(dataRoot):
-                if verbose:
-                    print "Removing old DataRoot: %s" % (dataRoot,)
-                rmtree(dataRoot)
-
-            if os.path.exists(docRoot):
-                if verbose:
-                    print "Removing old DocumentRoot: %s" % (docRoot,)
-                rmtree(docRoot)
-
-            if os.path.exists(configRoot):
-                if verbose:
-                    print "Removing old ConfigRoot: %s" % (configRoot,)
-                rmtree(configRoot)
-
             if verbose:
                 print "Extracting from backup file: %s" % (filename,)
-            tar.extractall()
+            tar.extractall(path="/")
 
-            loadData(DUMPFILENAME, verbose=verbose)
+            loadData(dumpPath, verbose=verbose)
 
             if verbose:
-                print "Cleaning up database dump file: %s" % (DUMPFILENAME,)
-            os.remove(DUMPFILENAME)
+                print "Cleaning up database dump file: %s" % (dumpPath,)
+            os.remove(dumpPath)
 
         except BackupError, e:
             error("Failed to dump database; error: %s" % (e,))

Modified: CalendarServer/branches/users/gaya/inviteclean/contrib/migration/calendarmigrator.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/contrib/migration/calendarmigrator.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/contrib/migration/calendarmigrator.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -375,7 +375,6 @@
     newCalDAVDPlist["EnableCalDAV"] = enableCalDAV
     newCalDAVDPlist["EnableCardDAV"] = enableCardDAV
 
-
     log("Writing %s" % (newConfigFile,))
     writePlist(newCalDAVDPlist, newConfigFile)
 
@@ -483,6 +482,9 @@
     # New DSN value for server-specific Postgres
     combined["DSN"] = "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::"
 
+    # ConfigRoot is now always "Config"
+    combined["ConfigRoot"] = "Config"
+
     return adminChanges
 
 

Modified: CalendarServer/branches/users/gaya/inviteclean/contrib/migration/test/test_migrator.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/contrib/migration/test/test_migrator.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/contrib/migration/test/test_migrator.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -89,6 +89,7 @@
         expected = {
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : True,
             "HTTPPort": 8008,
@@ -127,6 +128,7 @@
         expected = {
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,
@@ -165,6 +167,7 @@
         expected = {
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : True,
             "HTTPPort": 8008,
@@ -203,6 +206,7 @@
         expected = {
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : True,
             "HTTPPort": 8008,
@@ -241,6 +245,7 @@
         expected = {
             "BindHTTPPorts": [1111, 2222, 4444, 5555, 7777, 8888],
             "BindSSLPorts": [3333, 6666, 9999, 11111],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : True,
             "HTTPPort": 8888,
@@ -276,6 +281,7 @@
         expected = {
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,
@@ -306,6 +312,7 @@
         expected = {
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : True,
             "HTTPPort": 8008,
@@ -327,6 +334,7 @@
         expected = {
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,
@@ -374,6 +382,7 @@
             },
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,
@@ -413,6 +422,7 @@
             },
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,
@@ -465,6 +475,7 @@
             },
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,
@@ -506,6 +517,7 @@
             },
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,
@@ -547,6 +559,7 @@
             },
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,
@@ -582,6 +595,7 @@
             },
             "BindHTTPPorts": [8008, 8800],
             "BindSSLPorts": [8443, 8843],
+            "ConfigRoot" : "Config",
             "DSN" : "/Library/Server/PostgreSQL For Server Services/Socket:caldav:caldav:::",
             "EnableSSL" : False,
             "HTTPPort": 8008,

Modified: CalendarServer/branches/users/gaya/inviteclean/support/submit
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/support/submit	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/support/submit	2012-09-10 23:38:10 UTC (rev 9801)
@@ -40,8 +40,6 @@
 ignore_uncommitted_changes=false;
     build_no_verify_source=false;
 
-
-
 usage ()
 {
   program="$(basename "$0")";

Modified: CalendarServer/branches/users/gaya/inviteclean/twext/web2/dav/test/test_xattrprops.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twext/web2/dav/test/test_xattrprops.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twext/web2/dav/test/test_xattrprops.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -201,7 +201,7 @@
         self._setValue(document, originalValue)
         self._checkValue(document)
         self.assertEquals(
-            decompress(self._getValue(document)), originalValue)
+            decompress(self._getValue(document)), document.root_element.toxml(pretty=False))
 
 
     def test_getUpgradeCompressedPickle(self):
@@ -214,7 +214,7 @@
         self._setValue(document, compress(dumps(document)))
         self._checkValue(document)
         self.assertEquals(
-            decompress(self._getValue(document)), document.toxml())
+            decompress(self._getValue(document)), document.root_element.toxml(pretty=False))
 
 
     def test_getInvalid(self):
@@ -243,7 +243,7 @@
         document = self._makeValue()
         self.propertyStore.set(document.root_element)
         self.assertEquals(
-            decompress(self._getValue(document)), document.toxml())
+            decompress(self._getValue(document)), document.root_element.toxml(pretty=False))
 
 
     def test_delete(self):
@@ -396,7 +396,7 @@
             document = self._makeValue(uid)
             self.propertyStore.set(document.root_element, uid=uid)
             self.assertEquals(
-                decompress(self._getValue(document, uid)), document.toxml())
+                decompress(self._getValue(document, uid)), document.root_element.toxml(pretty=False))
 
     def test_delete_uids(self):
         """
@@ -415,7 +415,7 @@
                     continue
                 document = self._makeValue(uid)
                 self.assertEquals(
-                    decompress(self._getValue(document, uid)), document.toxml())
+                    decompress(self._getValue(document, uid)), document.root_element.toxml(pretty=False))
         
     def test_contains_uids(self):
         """

Copied: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/hiddeninstance.py (from rev 9800, CalendarServer/trunk/twistedcaldav/datafilters/hiddeninstance.py)
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/hiddeninstance.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/hiddeninstance.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -0,0 +1,65 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.datafilters.filter import CalendarFilter
+from twistedcaldav.ical import Component, ignoredComponents, Property
+
+__all__ = [
+    "HiddenInstanceFilter",
+]
+
+class HiddenInstanceFilter(CalendarFilter):
+    """
+    Filter overridden components in an event marked by a specific property to remove the component and add
+    a matching EXDATE.
+    """
+
+    def filter(self, ical):
+        """
+        Filter the supplied iCalendar object using the request information.
+
+        @param ical: iCalendar object
+        @type ical: L{Component} or C{str}
+        
+        @return: L{Component} for the filtered calendar data
+        """
+        
+        master = ical.masterComponent()
+        if master is None:
+            return ical
+        for component in tuple(ical.subcomponents()):
+            if component.name() in ignoredComponents:
+                continue
+            rid = component.getRecurrenceIDUTC()
+            if rid is None:
+                continue
+            if component.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY):
+                rid = component.getRecurrenceIDUTC()
+                ical.removeComponent(component)
+                
+                # Add EXDATE and try to preserve same timezone as DTSTART
+                dtstart = master.getProperty("DTSTART")
+                if dtstart is not None and not dtstart.value().isDateOnly() and dtstart.value().local():
+                    rid.adjustTimezone(dtstart.value().getTimezone())
+                master.addProperty(Property("EXDATE", [rid,]))
+        
+        return ical
+   
+    def merge(self, icalnew, icalold):
+        """
+        Private event merging does not happen
+        """
+        raise NotImplementedError

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/peruserdata.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/peruserdata.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/peruserdata.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -65,6 +65,7 @@
 
     PERUSER_PROPERTIES    = ("TRANSP",)
     PERUSER_SUBCOMPONENTS = ("VALARM",)
+    IGNORE_X_PROPERTIES   = ("X-CALENDARSERVER-HIDDEN-INSTANCE",)
 
     def __init__(self, uid):
         """
@@ -239,7 +240,7 @@
 
             # Transfer per-user properties from main component to per-instance component
             for property in tuple(component.properties()):
-                if property.name() in PerUserDataFilter.PERUSER_PROPERTIES or property.name().startswith("X-"):
+                if property.name() in PerUserDataFilter.PERUSER_PROPERTIES or property.name().startswith("X-") and property.name() not in PerUserDataFilter.IGNORE_X_PROPERTIES:
                     if self.uid:
                         perinstance_component.addProperty(property)
                     component.removeProperty(property)

Copied: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/test/test_hiddeninstances.py (from rev 9800, CalendarServer/trunk/twistedcaldav/datafilters/test/test_hiddeninstances.py)
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/test/test_hiddeninstances.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/datafilters/test/test_hiddeninstances.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -0,0 +1,435 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
+from twistedcaldav.ical import Component
+import twistedcaldav.test.util
+
+class HiddenInstanceFilterTest (twistedcaldav.test.util.TestCase):
+
+    def test_public_default(self):
+        
+        data = (
+            (
+                "Nothing hidden, no recurrence",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "Nothing hidden, recurrence",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "One hidden",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20080603T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "Two hidden",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20080602T120000Z
+EXDATE:20080603T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "One hidden with timezone",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=US/Eastern:20080601T120000
+DTEND;TZID=US/Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;TZID=US/Eastern:20080602T120000
+DTSTART;TZID=US/Eastern:20080602T123000
+DTEND;TZID=US/Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;TZID=US/Eastern:20080603T120000
+DTSTART;TZID=US/Eastern:20080603T123000
+DTEND;TZID=US/Eastern:20080601T133000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=US/Eastern:20080601T120000
+DTEND;TZID=US/Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+EXDATE;TZID=US/Eastern:20080603T120000
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;TZID=US/Eastern:20080602T120000
+DTSTART;TZID=US/Eastern:20080602T123000
+DTEND;TZID=US/Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "No master, no hidden",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "No master, one hidden - not really",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+        )
+        
+        for title, test, result in data:
+            ics = Component.fromString(test.replace("\n", "\r\n"))
+            self.assertEqual(str(HiddenInstanceFilter().filter(ics)), result.replace("\n", "\r\n"), msg="Failed: %s" % (title,))

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/directory/xmlaccountsparser.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/directory/xmlaccountsparser.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/directory/xmlaccountsparser.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -23,14 +23,13 @@
     "XMLAccountsParser",
 ]
 
-import xml.dom.minidom
-
 from twext.python.filepath import CachingFilePath as FilePath
 
 from twext.python.log import Logger
 
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.directory.util import normalizeUUID
+from twistedcaldav.xmlutil import readXML
 
 import re
 import hashlib
@@ -88,15 +87,10 @@
             self.items[recordType] = {}
 
         # Read in XML
-        fd = open(self.xmlFile.path, "r")
-        doc = xml.dom.minidom.parse(fd)
-        fd.close()
-
-        # Verify that top-level element is correct
-        accounts_node = doc._get_documentElement()
-        if accounts_node._get_localName() != ELEMENT_ACCOUNTS:
-            log.error("Ignoring file %r because it is not a repository builder file" % (self.xmlFile,))
-            return
+        try:
+            _ignore_tree, accounts_node = readXML(self.xmlFile.path, ELEMENT_ACCOUNTS)
+        except ValueError, e:
+            log.error("XML parse error for '%s' because: %s" % (self.xmlFile, e,), raiseException=RuntimeError)
         self._parseXML(accounts_node)
 
     def _parseXML(self, node):
@@ -104,8 +98,7 @@
         Parse the XML root node from the accounts configuration document.
         @param node: the L{Node} to parse.
         """
-        if node.hasAttribute(ATTRIBUTE_REALM):
-            self.realm = node.getAttribute(ATTRIBUTE_REALM).encode("utf-8")
+        self.realm = node.get(ATTRIBUTE_REALM, "").encode("utf-8")
 
         def updateMembership(group):
             # Update group membership
@@ -114,20 +107,13 @@
                 if item is not None:
                     item.groups.add(group.shortNames[0])
 
-        for child in node._get_childNodes():
-            child_name = child._get_localName()
-            if child_name is None:
-                continue
-
+        for child in node.getchildren():
             try:
-                recordType = RECORD_TYPES[child_name]
+                recordType = RECORD_TYPES[child.tag]
             except KeyError:
-                raise RuntimeError("Unknown account type: %s" % (child_name,))
+                raise RuntimeError("Unknown account type: %s" % (child.tag,))
 
-            if child.hasAttribute(ATTRIBUTE_REPEAT):
-                repeat = int(child.getAttribute(ATTRIBUTE_REPEAT))
-            else:
-                repeat = 0
+            repeat = int(child.get(ATTRIBUTE_REPEAT, 0))
 
             principal = XMLAccountRecord(recordType)
             principal.parseXML(child)
@@ -249,58 +235,39 @@
         return result
 
     def parseXML(self, node):
-        for child in node._get_childNodes():
-            child_name = child._get_localName()
-            if child_name is None:
-                continue
-            elif child_name == ELEMENT_SHORTNAME:
-                if child.firstChild is not None:
-                    self.shortNames.append(child.firstChild.data.encode("utf-8"))
-            elif child_name == ELEMENT_GUID:
-                if child.firstChild is not None:
-                    self.guid = normalizeUUID(
-                        child.firstChild.data.encode("utf-8")
-                    )
-                    if len(self.guid) < 4:
-                        self.guid += "?" * (4 - len(self.guid))
-            elif child_name == ELEMENT_PASSWORD:
-                if child.firstChild is not None:
-                    self.password = child.firstChild.data.encode("utf-8")
-            elif child_name == ELEMENT_NAME:
-                if child.firstChild is not None:
-                    self.fullName = child.firstChild.data.encode("utf-8")
-            elif child_name == ELEMENT_FIRST_NAME:
-                if child.firstChild is not None:
-                    self.firstName = child.firstChild.data.encode("utf-8")
-            elif child_name == ELEMENT_LAST_NAME:
-                if child.firstChild is not None:
-                    self.lastName = child.firstChild.data.encode("utf-8")
-            elif child_name == ELEMENT_EMAIL_ADDRESS:
-                if child.firstChild is not None:
-                    self.emailAddresses.add(child.firstChild.data.encode("utf-8").lower())
-            elif child_name == ELEMENT_MEMBERS:
+        for child in node.getchildren():
+            if child.tag == ELEMENT_SHORTNAME:
+                self.shortNames.append(child.text.encode("utf-8"))
+            elif child.tag == ELEMENT_GUID:
+                self.guid = normalizeUUID(child.text.encode("utf-8"))
+                if len(self.guid) < 4:
+                    self.guid += "?" * (4 - len(self.guid))
+            elif child.tag == ELEMENT_PASSWORD:
+                self.password = child.text.encode("utf-8")
+            elif child.tag == ELEMENT_NAME:
+                self.fullName = child.text.encode("utf-8")
+            elif child.tag == ELEMENT_FIRST_NAME:
+                self.firstName = child.text.encode("utf-8")
+            elif child.tag == ELEMENT_LAST_NAME:
+                self.lastName = child.text.encode("utf-8")
+            elif child.tag == ELEMENT_EMAIL_ADDRESS:
+                self.emailAddresses.add(child.text.encode("utf-8").lower())
+            elif child.tag == ELEMENT_MEMBERS:
                 self._parseMembers(child, self.members)
-            elif child_name == ELEMENT_EXTRAS:
+            elif child.tag == ELEMENT_EXTRAS:
                 self._parseExtras(child, self.extras)
             else:
-                raise RuntimeError("Unknown account attribute: %s" % (child_name,))
+                raise RuntimeError("Unknown account attribute: %s" % (child.tag,))
 
         if not self.shortNames:
             self.shortNames.append(self.guid)
 
     def _parseMembers(self, node, addto):
-        for child in node._get_childNodes():
-            if child._get_localName() == ELEMENT_MEMBER:
-                if child.hasAttribute(ATTRIBUTE_RECORDTYPE):
-                    recordType = child.getAttribute(ATTRIBUTE_RECORDTYPE).encode("utf-8")
-                else:
-                    recordType = DirectoryService.recordType_users
-                if child.firstChild is not None:
-                    addto.add((recordType, child.firstChild.data.encode("utf-8")))
+        for child in node.getchildren():
+            if child.tag == ELEMENT_MEMBER:
+                recordType = child.get(ATTRIBUTE_RECORDTYPE, DirectoryService.recordType_users)
+                addto.add((recordType, child.text.encode("utf-8")))
 
     def _parseExtras(self, node, addto):
-        for child in node._get_childNodes():
-            key = child._get_localName()
-            if key:
-                value = child.firstChild.data.encode("utf-8")
-                addto[key.encode("utf-8")] = value
+        for child in node.getchildren():
+            addto[child.tag] = child.text.encode("utf-8")

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/ical.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/ical.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -325,6 +325,9 @@
     }
     extraRestrictedProperties = ("SUMMARY", "LOCATION",)
 
+    # Hidden instance.
+    HIDDEN_INSTANCE_PROPERTY = "X-CALENDARSERVER-HIDDEN-INSTANCE"
+
     @classmethod
     def allFromString(clazz, string):
         """
@@ -762,7 +765,17 @@
                 return (range == "THISANDFUTURE")
 
         return False
-            
+    
+    def getExdates(self):
+        """
+        Get the set of all EXDATEs in this (master) component.
+        """
+        exdates = set()
+        for property in self.properties("EXDATE"):
+            for exdate in property.value():
+                exdates.add(exdate.getValue())
+        return exdates
+
     def getTriggerDetails(self):
         """
         Return the trigger information for the specified alarm component.

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/method/get.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/method/get.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -32,6 +32,7 @@
 
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
     CalDAVResource
@@ -79,6 +80,9 @@
     
                 caldata = (yield self.iCalendarForUser(request))
     
+                # Filter any attendee hidden instances        
+                caldata = HiddenInstanceFilter().filter(caldata)
+
                 if self.accessMode:
             
                     # Non DAV:owner's have limited access to the data
@@ -86,7 +90,7 @@
                     
                     # Now "filter" the resource calendar data
                     caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)
-        
+
                 response = Response()
                 response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference))
                 response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/method/report_common.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/method/report_common.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -56,6 +56,7 @@
 from twistedcaldav.carddavxml import AddressData
 from twistedcaldav.config import config
 from twistedcaldav.datafilters.calendardata import CalendarDataFilter
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.datafilters.addressdata import AddressDataFilter
 from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap,\
@@ -344,7 +345,8 @@
             # Handle private events access restrictions
             if calendar is None:
                 calendar = (yield resource.iCalendarForUser(request))
-            filtered = PrivateEventFilter(resource.accessMode, isowner).filter(calendar)
+            filtered = HiddenInstanceFilter().filter(calendar)
+            filtered = PrivateEventFilter(resource.accessMode, isowner).filter(filtered)
             filtered = CalendarDataFilter(property, timezone).filter(filtered)
             propvalue = CalendarData().fromCalendar(filtered)
             properties_by_status[responsecode.OK].append(propvalue)

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/resource.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/resource.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -65,6 +65,7 @@
 from twistedcaldav.carddavxml import carddav_namespace
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.directory.internal import InternalDirectoryRecord
@@ -1525,11 +1526,11 @@
     def iCalendarFiltered(self, isowner, accessUID=None):
 
         # Now "filter" the resource calendar data
-        caldata = PrivateEventFilter(self.accessMode, isowner).filter(
-            (yield self.iCalendar())
-        )
+        caldata = (yield self.iCalendar())
         if accessUID:
             caldata = PerUserDataFilter(accessUID).filter(caldata)
+        caldata = HiddenInstanceFilter().filter(caldata)
+        caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)
         returnValue(caldata)
 
 

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/icaldiff.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/icaldiff.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -323,9 +323,17 @@
                 # Attendee may decline by EXDATE'ing an instance - we need to handle that
                 if exdatesnew is None or rid in exdatesnew:
                     # Mark Attendee as DECLINED in the server instance
-                    if self._attendeeDecline(returnCalendar.overriddenComponent(rid)):
+                    overridden = returnCalendar.overriddenComponent(rid)
+                    if self._attendeeDecline(overridden):
                         changeCausesReply = True
                         changedRids.append(rid.getText() if rid else "")
+                        
+                    # When a master component is present we keep the missing override in place but mark it as hidden.
+                    # When no master is present we remove the override,
+                    if exdatesnew is not None:
+                        overridden.replaceProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+                    else:
+                        returnCalendar.removeComponent(overridden)
                 else:
                     # We used to generate a 403 here - but instead we now ignore this error and let the server data
                     # override the client
@@ -411,10 +419,15 @@
             if not overridden:
                 overridden = returnCalendar.deriveInstance(decline)
                 if overridden:
-                    returnCalendar.addComponent(overridden)
                     if self._attendeeDecline(overridden):
                         changeCausesReply = True
                         changedRids.append(decline.getText() if decline else "")
+                        
+                    # When a master component is present we keep the missing override in place but mark it as hidden.
+                    # When no master is present we remove the override,
+                    if exdatesnew is not None:
+                        overridden.replaceProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+                        returnCalendar.addComponent(overridden)
                 else:
                     self._logDiffError("attendeeMerge: Unable to override an instance to mark as DECLINED: %s" % (decline,))
                     return False, False, (), None

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/imip.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/imip.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/imip.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -44,6 +44,7 @@
 
 log = Logger()
 
+
 class ScheduleViaIMip(DeliveryService):
     
     @classmethod
@@ -52,12 +53,23 @@
 
     @inlineCallbacks
     def generateSchedulingResponses(self):
+        def failForRecipient(recipient):
+            err = HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (caldav_namespace, "recipient-failed"),
+                "iMIP request failed",
+            ))
+            self.responses.add(
+                recipient.cuaddr,
+                Failure(exc_value=err),
+                reqstatus=iTIPRequestStatus.SERVICE_UNAVAILABLE
+            )
         
         # Generate an HTTP client request
         try:
             # We do not do freebusy requests via iMIP
             if self.freebusy:
-                raise ValueError("iMIP VFREEBUSY REQUESTs not supported.")
+                raise ValueError("iMIP VFREEBUSY requests not supported.")
 
             method = self.scheduler.calendar.propertyValue("METHOD") 
             if method not in (
@@ -97,17 +109,8 @@
         
                 except Exception, e:
                     # Generated failed response for this recipient
-                    log.err("Could not do server-to-imip request : %s %s" % (self, e))
-                    err = HTTPError(ErrorResponse(
-                        responsecode.FORBIDDEN,
-                        (caldav_namespace, "recipient-failed"),
-                        "iMIP request failed",
-                    ))
-                    self.responses.add(
-                        recipient.cuaddr,
-                        Failure(exc_value=err),
-                        reqstatus=iTIPRequestStatus.SERVICE_UNAVAILABLE
-                    )
+                    log.debug("iMIP request %s failed for recipient %s: %s" % (self, recipient, e))
+                    failForRecipient(recipient)
                 
                 else:
                     self.responses.add(
@@ -118,18 +121,9 @@
 
         except Exception, e:
             # Generated failed responses for each recipient
-            log.err("Could not do server-to-imip request : %s %s" % (self, e))
+            log.debug("iMIP request %s failed: %s" % (self, e))
             for recipient in self.recipients:
-                err = HTTPError(ErrorResponse(
-                    responsecode.FORBIDDEN,
-                    (caldav_namespace, "recipient-failed"),
-                    "iMIP request failed",
-                ))
-                self.responses.add(
-                    recipient.cuaddr,
-                    Failure(exc_value=err),
-                    reqstatus=iTIPRequestStatus.SERVICE_UNAVAILABLE
-                )
+                failForRecipient(recipient)
 
     def postToGateway(self, fromAddr, toAddr, caldata, reactor=None):
         if reactor is None:

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/implicit.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/implicit.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -958,8 +958,14 @@
                 log.debug("Implicit - attendee '%s' is removing cancelled UID: '%s'" % (self.attendee, self.uid))
                 # Nothing else to do
             elif doScheduling:
-                log.debug("Implicit - attendee '%s' is cancelling UID: '%s'" % (self.attendee, self.uid))
-                yield self.scheduleCancelWithOrganizer()
+                # If attendee is already marked as declined in all components - nothing to do
+                attendees = self.calendar.getAttendeeProperties((self.attendee,))
+                if all([attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED" for attendee in attendees]):
+                    log.debug("Implicit - attendee '%s' is removing fully declined UID: '%s'" % (self.attendee, self.uid))
+                    # Nothing else to do
+                else:
+                    log.debug("Implicit - attendee '%s' is cancelling UID: '%s'" % (self.attendee, self.uid))
+                    yield self.scheduleCancelWithOrganizer()
             else:
                 log.debug("Implicit - attendee '%s' is removing UID without server scheduling: '%s'" % (self.attendee, self.uid))
                 # Nothing else to do

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/ischeduleservers.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/ischeduleservers.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/ischeduleservers.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -20,9 +20,8 @@
 
 from twistedcaldav.config import config, fullServerPath
 from twistedcaldav.scheduling.delivery import DeliveryService
+from twistedcaldav.xmlutil import readXML
 
-import xml.dom.minidom
-
 """
 XML based iSchedule configuration file handling.
 """
@@ -100,15 +99,11 @@
         self.servers = []
         
         # Read in XML
-        fd = open(xmlFile.path, "r")
-        doc = xml.dom.minidom.parse(fd)
-        fd.close()
+        try:
+            _ignore_tree, servers_node = readXML(xmlFile.path, ELEMENT_SERVERS)
+        except ValueError:
+            log.error("Ignoring file %r because it is not a server-to-server config file" % (xmlFile,))
 
-        # Verify that top-level element is correct
-        servers_node = doc._get_documentElement()
-        if servers_node._get_localName() != ELEMENT_SERVERS:
-            log.error("Ignoring file %r because it is not a server-to-server config file" % (self.xmlFile,))
-            return
         self._parseXML(servers_node)
         
     def _parseXML(self, node):
@@ -116,12 +111,8 @@
         Parse the XML root node from the server-to-server configuration document.
         @param node: the L{Node} to parse.
         """
-
-        for child in node._get_childNodes():
-            child_name = child._get_localName()
-            if child_name is None:
-                continue
-            elif child_name == ELEMENT_SERVER:
+        for child in node.getchildren():
+            if child.tag == ELEMENT_SERVER:
                 self.servers.append(IScheduleServerRecord())
                 self.servers[-1].parseXML(child)
                 
@@ -147,49 +138,39 @@
             self._parseDetails()
 
     def parseXML(self, node):
-        for child in node._get_childNodes():
-            child_name = child._get_localName()
-            if child_name is None:
-                continue
-            elif child_name == ELEMENT_URI:
-                if child.firstChild is not None:
-                    self.uri = child.firstChild.data.encode("utf-8")
-            elif child_name == ELEMENT_AUTHENTICATION:
+        for child in node.getchildren():
+            if child.tag == ELEMENT_URI:
+                self.uri = child.text
+            elif child.tag == ELEMENT_AUTHENTICATION:
                 self._parseAuthentication(child)
-            elif child_name == ELEMENT_ALLOW_REQUESTS_FROM:
+            elif child.tag == ELEMENT_ALLOW_REQUESTS_FROM:
                 self.allow_from = True
-            elif child_name == ELEMENT_ALLOW_REQUESTS_TO:
+            elif child.tag == ELEMENT_ALLOW_REQUESTS_TO:
                 self.allow_to = True
-            elif child_name == ELEMENT_DOMAINS:
+            elif child.tag == ELEMENT_DOMAINS:
                 self._parseList(child, ELEMENT_DOMAIN, self.domains)
-            elif child_name == ELEMENT_CLIENT_HOSTS:
+            elif child.tag == ELEMENT_CLIENT_HOSTS:
                 self._parseList(child, ELEMENT_HOST, self.client_hosts)
             else:
-                raise RuntimeError("[%s] Unknown attribute: %s" % (self.__class__, child_name,))
+                raise RuntimeError("[%s] Unknown attribute: %s" % (self.__class__, child.tag,))
         
         self._parseDetails()
 
     def _parseList(self, node, element_name, appendto):
-        for child in node._get_childNodes():
-            if child._get_localName() == element_name:
-                if child.firstChild is not None:
-                    appendto.append(child.firstChild.data.encode("utf-8"))
+        for child in node.getchildren():
+            if child.tag == element_name:
+                appendto.append(child.text)
 
     def _parseAuthentication(self, node):
-        if node.hasAttribute(ATTRIBUTE_TYPE):
-            atype = node.getAttribute(ATTRIBUTE_TYPE).encode("utf-8")
-            if atype != ATTRIBUTE_BASICAUTH:
-                return
-        else:
+        atype = node.getAttribute(ATTRIBUTE_TYPE, "")
+        if atype != ATTRIBUTE_BASICAUTH:
             return
 
-        for child in node._get_childNodes():
-            if child._get_localName() == ELEMENT_USER:
-                if child.firstChild is not None:
-                    user = child.firstChild.data.encode("utf-8")
-            elif child._get_localName() == ELEMENT_PASSWORD:
-                if child.firstChild is not None:
-                    password = child.firstChild.data.encode("utf-8")
+        for child in node.getchildren():
+            if child.tag == ELEMENT_USER:
+                user = child.text
+            elif child.tag == ELEMENT_PASSWORD:
+                password = child.text
         
         self.authentication = ("basic", user, password,)
 

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/itip.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/itip.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -47,7 +47,7 @@
 class iTipProcessing(object):
 
     @staticmethod
-    def processNewRequest(itip_message, recipient=None):
+    def processNewRequest(itip_message, recipient=None, creating=False):
         """
         Process a METHOD=REQUEST for a brand new calendar object.
         
@@ -65,6 +65,20 @@
 
         if recipient:
             iTipProcessing.addTranspForNeedsAction(calendar.subcomponents(), recipient)
+                
+            # Check for incoming DECLINED
+            if creating:
+                master = calendar.masterComponent()
+                for component in tuple(calendar.subcomponents()):
+                    if component in ignoredComponents or component is master:
+                        continue
+                    attendee = component.getAttendeeProperty((recipient,))
+                    if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
+                        # Mark as hidden if we have a master, otherwise remove
+                        if master is not None:
+                            component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+                        else:
+                            calendar.removeComponent(component)
 
         return calendar
         
@@ -131,12 +145,12 @@
                 if organizer:
                     organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
                 
-            # Now try to match recurrences
-            for component in new_calendar.subcomponents():
+            # Now try to match recurrences in the new calendar
+            for component in tuple(new_calendar.subcomponents()):
                 if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
-                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component)
+                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component, recipient)
             
-            # Now try to match recurrences
+            # Now try to match recurrences from the old calendar
             for component in calendar.subcomponents():
                 if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
                     rid = component.getRecurrenceIDUTC()
@@ -145,7 +159,7 @@
                         new_component = new_calendar.deriveInstance(rid, allowCancelled=allowCancelled)
                         if new_component:
                             new_calendar.addComponent(new_component)
-                            iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, new_component)
+                            iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, new_component, recipient)
             
             # Replace the entire object
             return new_calendar, rids
@@ -162,10 +176,11 @@
                         calendar.addComponent(component)
                 else:
                     component = component.duplicate()
-                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component, remove_matched=True)
-                    calendar.addComponent(component)
-                    if recipient:
-                        iTipProcessing.addTranspForNeedsAction((component,), recipient)
+                    missingDeclined = iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component, recipient, remove_matched=True)
+                    if not missingDeclined:
+                        calendar.addComponent(component)
+                        if recipient:
+                            iTipProcessing.addTranspForNeedsAction((component,), recipient)
 
             # Write back the modified object
             return calendar, rids
@@ -468,11 +483,13 @@
         return attendee.value(), partstat_changed, private_comment_changed
 
     @staticmethod
-    def transferItems(from_calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, to_component, remove_matched=False):
+    def transferItems(from_calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, to_component, recipient, remove_matched=False):
         """
         Transfer properties from a calendar to a component by first trying to match the component in the original calendar and
         use the properties from that, or use the values provided as arguments (which have been derived from the original calendar's
         master component).
+        
+        @return: C{True} if an EXDATE match occurred requiring the incoming component to be removed.
         """
 
         rid = to_component.getRecurrenceIDUTC()
@@ -497,7 +514,19 @@
             if remove_matched:
                 from_calendar.removeComponent(matched)
                 
+            # Check for incoming DECLINED
+            attendee = to_component.getAttendeeProperty((recipient,))
+            if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
+                # If existing item has HIDDEN property copy that over
+                if matched.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY):
+                    to_component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+
         else:
+            # Check for incoming DECLINED
+            attendee = to_component.getAttendeeProperty((recipient,))
+            if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
+                return True
+                    
             # It is a new override - copy any valarms on the existing master component
             # into the new one.
             [to_component.addComponent(alarm) for alarm in master_valarms]
@@ -508,6 +537,8 @@
                 organizer = to_component.getProperty("ORGANIZER")
                 if organizer:
                     organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
+                
+        return False
     
     @staticmethod
     def addTranspForNeedsAction(components, recipient):

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/processing.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/processing.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -460,10 +460,20 @@
 
     @inlineCallbacks
     def doImplicitAttendeeRequest(self):
+        """
+        @return: C{tuple} of (processed, auto-processed, store inbox item, changes)
+        """
 
         # If there is no existing copy, then look for default calendar and copy it here
         if self.new_resource:
             
+            # Check if the incoming data has the recipient declined in all instances. In that case we will not create
+            # a new resource as chances are the recipient previously deleted the resource and we want to keep it deleted.
+            attendees = self.message.getAttendeeProperties((self.recipient.cuaddr,))
+            if all([attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED" for attendee in attendees]):
+                log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - ignoring all declined" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                returnValue((True, False, False, None,))
+            
             # Check for default calendar
             default = (yield self.recipient.inbox.defaultCalendar(self.request, self.message.mainType()))
             if default is None:
@@ -471,7 +481,7 @@
                 raise ImplicitProcessorException(iTIPRequestStatus.NO_USER_SUPPORT)
 
             log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - new processed" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
-            new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr)
+            new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr, creating=True)
             
             # Handle auto-reply behavior
             if self.recipient.principal.canAutoSchedule():

Modified: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/test/test_icaldiff.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/test/test_icaldiff.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/test/test_icaldiff.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -1738,6 +1738,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
 """)
@@ -1794,6 +1795,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1803,6 +1805,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
 """)
@@ -1868,6 +1871,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
 """)
@@ -1934,6 +1938,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1943,6 +1948,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
 """)
@@ -1992,15 +1998,6 @@
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 BEGIN:VEVENT
 UID:12345-67890
-RECURRENCE-ID:20080601T120000Z
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
 RECURRENCE-ID:20080604T120000Z
 DTSTART:20080604T130000Z
 DTEND:20080604T140000Z

Copied: CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/test/test_ischeduleservers.py (from rev 9800, CalendarServer/trunk/twistedcaldav/scheduling/test/test_ischeduleservers.py)
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/test/test_ischeduleservers.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/inviteclean/twistedcaldav/scheduling/test/test_ischeduleservers.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -0,0 +1,49 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.python.filepath import FilePath
+from twistedcaldav.scheduling.ischeduleservers import IScheduleServersParser
+import twistedcaldav.test.util
+
+class Test_IScheduleServersParser(twistedcaldav.test.util.TestCase):
+    """
+    Test L{IScheduleServersParser} implementation.
+    """
+
+    def test_readXML(self):
+
+        fp = FilePath(self.mktemp())
+        fp.open("w").write(
+"""<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE servers SYSTEM "servertoserver.dtd">
+<servers>
+  <server>
+    <uri>https://localhost:8543/inbox</uri>
+    <allow-requests-from/>
+    <allow-requests-to/>
+    <domains>
+        <domain>example.org</domain>
+    </domains>
+    <hosts>
+        <host>127.0.0.1</host>
+    </hosts>
+  </server>
+</servers>
+"""
+)
+        
+        parser = IScheduleServersParser(fp)
+        self.assertEqual(len(parser.servers), 1)

Modified: CalendarServer/branches/users/gaya/inviteclean/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/txdav/caldav/datastore/util.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/txdav/caldav/datastore/util.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -36,6 +36,7 @@
 from twext.python.vcomponent import InvalidICalendarDataError
 from twext.python.vcomponent import VComponent
 
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 
@@ -406,8 +407,11 @@
         calendar = self.calendar()
         isOwner = asAdmin or (calendar.owned() and
                               calendar.ownerCalendarHome().uid() == accessUID)
-        for data_filter in [PrivateEventFilter(self.accessMode, isOwner),
-                       PerUserDataFilter(accessUID)]:
+        for data_filter in [
+            PerUserDataFilter(accessUID),
+            HiddenInstanceFilter(),
+            PrivateEventFilter(self.accessMode, isOwner),
+        ]:
             component = data_filter.filter(component)
         returnValue(component)
 
@@ -501,7 +505,7 @@
                 fixes += 1
                 calprop.setValue(postval)
     for subc in component.subcomponents():
-        count, fixsubc = fixOneCalendarObject(subc)
+        count, _ignore_fixsubc = fixOneCalendarObject(subc)
         fixes += count
     return fixes, component
 

Modified: CalendarServer/branches/users/gaya/inviteclean/txdav/xml/__init__.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/txdav/xml/__init__.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/txdav/xml/__init__.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -33,6 +33,7 @@
 See RFC 3744: http://www.ietf.org/rfc/rfc3744.txt (WebDAV ACLs)
 See RFC 4331: http://www.ietf.org/rfc/rfc4331.txt (WebDAV Quota)
 See RFC 5842: http://www.ietf.org/rfc/rfc5842.txt (WebDAV Bind)
+See RFC 6578: http://www.ietf.org/rfc/rfc6578.txt (WebDAV Sync)
 """
 
 __all__ = [
@@ -45,10 +46,10 @@
 import txdav.xml.rfc3253
 import txdav.xml.rfc3744
 import txdav.xml.rfc4331
+import txdav.xml.rfc5397
 import txdav.xml.rfc5842
-import txdav.xml.rfc5397
 import txdav.xml.rfc5995
-import txdav.xml.draft_sync
+import txdav.xml.rfc6578
 import txdav.xml.extensions
 
 txdav # Shhh pyflakes

Deleted: CalendarServer/branches/users/gaya/inviteclean/txdav/xml/draft_sync.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/txdav/xml/draft_sync.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/txdav/xml/draft_sync.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -1,103 +0,0 @@
-##
-# Copyright (c) 2009-2012 Apple Computer, Inc. All rights reserved.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-# 
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-# 
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-##
-
-"""
-draft-daboo-webdav-sync (Collection Synchronization for WebDAV) XML
-Elements
-
-This module provides XML element definitions for use with WebDAV
-Synchronization.
-
-See draft-daboo-webdav-sync: http://tools.ietf.org/html/draft-daboo-webdav-sync
-
-Last draft referenced: -08
-"""
-
-__all__ = []
-
-
-from txdav.xml.base import WebDAVElement, WebDAVTextElement, dav_namespace
-from txdav.xml.element import registerElement, registerElementClass
-from txdav.xml.rfc2518 import MultiStatus
-
-
- at registerElement
- at registerElementClass
-class SyncCollection (WebDAVElement):
-    """
-    DAV report used to retrieve specific calendar component items via
-    their URIs.
-    """
-    name = "sync-collection"
-
-    # To allow for an empty element in a supported-report-set property we need
-    # to relax the child restrictions
-    allowed_children = {
-        (dav_namespace, "sync-token"): (0, 1), # When used in the REPORT this is required
-        (dav_namespace, "sync-level"): (0, 1), # When used in the REPORT this is required
-        (dav_namespace, "prop"      ): (0, 1),
-    }
-
-    def __init__(self, *children, **attributes):
-        super(SyncCollection, self).__init__(*children, **attributes)
-
-        self.property = None
-        self.sync_token = None
-        self.sync_level = None
-
-        for child in self.children:
-            qname = child.qname()
-
-            if qname == (dav_namespace, "sync-token"):
-                self.sync_token = str(child)
-
-            elif qname == (dav_namespace, "sync-level"):
-                self.sync_level = str(child)
-
-            elif qname == (dav_namespace, "prop"):
-                if self.property is not None:
-                    raise ValueError("Only one of DAV:prop allowed")
-                self.property = child
-
-
- at registerElement
- at registerElementClass
-class SyncToken (WebDAVTextElement):
-    """
-    Synchronization token used in report and as a property.
-    """
-    name = "sync-token"
-    hidden = True
-    protected = True
-
-
- at registerElement
- at registerElementClass
-class SyncLevel (WebDAVTextElement):
-    """
-    Synchronization level used in report.
-    """
-    name = "sync-level"
-
-
-# Extend MultiStatus, to add sync-token
-MultiStatus.allowed_children[(dav_namespace, "sync-token")] = (0, 1)

Modified: CalendarServer/branches/users/gaya/inviteclean/txdav/xml/parser.py
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/txdav/xml/parser.py	2012-09-10 20:28:46 UTC (rev 9800)
+++ CalendarServer/branches/users/gaya/inviteclean/txdav/xml/parser.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -32,4 +32,4 @@
     "WebDAVDocument",
 ]
 
-from txdav.xml.parser_sax import WebDAVDocument
+from txdav.xml.parser_etree import WebDAVDocument

Copied: CalendarServer/branches/users/gaya/inviteclean/txdav/xml/parser_etree.py (from rev 9800, CalendarServer/trunk/txdav/xml/parser_etree.py)
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/txdav/xml/parser_etree.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/inviteclean/txdav/xml/parser_etree.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -0,0 +1,160 @@
+##
+# Copyright (c) 2012 Apple Computer, Inc. All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+##
+
+"""
+ElementTree implementation of XML parser/generator for WebDAV documents.
+"""
+
+__all__ = [
+    "WebDAVDocument",
+]
+
+from xml.etree.ElementTree import TreeBuilder, XMLParser,\
+    _namespace_map
+from txdav.xml.base import WebDAVUnknownElement, PCDATAElement
+from txdav.xml.base import _elements_by_qname
+from txdav.xml.parser_base import AbstractWebDAVDocument
+
+try:
+    from xml.etree.ElementTree import ParseError as XMLParseError
+except ImportError:
+    from xml.parsers.expat import ExpatError as XMLParseError
+
+def QNameSplit(qname):
+    return tuple(qname[1:].split("}", 1)) if "}" in qname else ("", qname,)
+
+class WebDAVContentHandler (TreeBuilder):
+
+    def __init__(self):
+        TreeBuilder.__init__(self)
+        self._characterBuffer = None
+        
+        self.startDocument()
+
+    def startDocument(self):
+        self.stack = [{
+            "name"       : None,
+            "class"      : None,
+            "attributes" : None,
+            "children"   : [],
+        }]
+
+        # Keep a cache of the subclasses we create for unknown XML
+        # elements, so that we don't create multiple classes for the
+        # same element; it's fairly typical for elements to appear
+        # multiple times in a document.
+        self.unknownElementClasses = {}
+
+    def close(self):
+        top = self.stack[-1]
+
+        assert top["name"] is None
+        assert top["class"] is None
+        assert top["attributes"] is None
+        assert len(top["children"]) is 1, "Must have exactly one root element, got %d" % len(top["children"])
+
+        self.dom = WebDAVDocument(top["children"][0])
+        del(self.unknownElementClasses)
+        return self.dom
+
+    def data(self, data):
+        # Stash character data away in a list that we will "".join() when done
+        if self._characterBuffer is None:
+            self._characterBuffer = []
+        self._characterBuffer.append(data)
+
+    def start(self, tag, attrs):
+        name = QNameSplit(tag)
+
+        if self._characterBuffer is not None:
+            pcdata = PCDATAElement("".join(self._characterBuffer))
+            self.stack[-1]["children"].append(pcdata)
+            self._characterBuffer = None
+
+        # Need to convert a "full" namespace in an attribute QName to the form
+        # "%s:%s".
+        attributes_dict = {}
+        for aname, avalue in attrs.items():
+            anamespace, aname = QNameSplit(aname)
+            if anamespace:
+                anamespace = _namespace_map.get(anamespace, anamespace)
+                aname = "%s:%s" % (anamespace, aname,)
+            attributes_dict[aname] = avalue
+
+        tag_namespace, tag_name = name
+
+        if name in _elements_by_qname:
+            element_class = _elements_by_qname[name]
+        elif name in self.unknownElementClasses:
+            element_class = self.unknownElementClasses[name]
+        else:
+            def element_class(*args, **kwargs):
+                element = WebDAVUnknownElement(*args, **kwargs)
+                element.namespace = tag_namespace
+                element.name      = tag_name
+                return element
+            self.unknownElementClasses[name] = element_class
+
+        self.stack.append({
+            "name"       : name,
+            "class"      : element_class,
+            "attributes" : attributes_dict,
+            "children"   : [],
+        })
+
+    def end(self, tag):
+        name = QNameSplit(tag)
+
+        if self._characterBuffer is not None:
+            pcdata = PCDATAElement("".join(self._characterBuffer))
+            self.stack[-1]["children"].append(pcdata)
+            self._characterBuffer = None
+
+        # Pop the current element from the stack...
+        top = self.stack[-1]
+        del(self.stack[-1])
+
+        assert top["name"] == name, "Last item on stack is %s while closing %s" % (top["name"], name)
+
+        # ...then instantiate the element and add it to the parent's list of
+        # children.
+        element = top["class"](*top["children"], **top["attributes"])
+
+        self.stack[-1]["children"].append(element)
+
+
+class WebDAVDocument(AbstractWebDAVDocument):
+    @classmethod
+    def fromStream(cls, source):
+        parser  = XMLParser(target=WebDAVContentHandler())
+        try:
+            while 1:
+                data = source.read(65536)
+                if not data:
+                    break
+                parser.feed(data)
+        except XMLParseError, e:
+            raise ValueError(e)
+        return parser.close()
+        
+    def writeXML(self, output):
+        self.root_element.writeXML(output)

Copied: CalendarServer/branches/users/gaya/inviteclean/txdav/xml/rfc6578.py (from rev 9800, CalendarServer/trunk/txdav/xml/rfc6578.py)
===================================================================
--- CalendarServer/branches/users/gaya/inviteclean/txdav/xml/rfc6578.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/inviteclean/txdav/xml/rfc6578.py	2012-09-10 23:38:10 UTC (rev 9801)
@@ -0,0 +1,100 @@
+##
+# Copyright (c) 2009-2012 Apple Computer, Inc. All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+##
+
+"""
+RFC 6578 (Collection Synchronization for WebDAV) XML Elements
+
+This module provides XML element definitions for use with WebDAV
+Synchronization.
+
+See RFC 6578: http://www.ietf.org/rfc/rfc6578.txt
+"""
+
+__all__ = []
+
+
+from txdav.xml.base import WebDAVElement, WebDAVTextElement, dav_namespace
+from txdav.xml.element import registerElement, registerElementClass
+from txdav.xml.rfc2518 import MultiStatus
+
+
+ at registerElement
+ at registerElementClass
+class SyncCollection (WebDAVElement):
+    """
+    DAV report used to retrieve specific calendar component items via
+    their URIs.
+    """
+    name = "sync-collection"
+
+    # To allow for an empty element in a supported-report-set property we need
+    # to relax the child restrictions
+    allowed_children = {
+        (dav_namespace, "sync-token"): (0, 1), # When used in the REPORT this is required
+        (dav_namespace, "sync-level"): (0, 1), # When used in the REPORT this is required
+        (dav_namespace, "prop"      ): (0, 1),
+    }
+
+    def __init__(self, *children, **attributes):
+        super(SyncCollection, self).__init__(*children, **attributes)
+
+        self.property = None
+        self.sync_token = None
+        self.sync_level = None
+
+        for child in self.children:
+            qname = child.qname()
+
+            if qname == (dav_namespace, "sync-token"):
+                self.sync_token = str(child)
+
+            elif qname == (dav_namespace, "sync-level"):
+                self.sync_level = str(child)
+
+            elif qname == (dav_namespace, "prop"):
+                if self.property is not None:
+                    raise ValueError("Only one of DAV:prop allowed")
+                self.property = child
+
+
+ at registerElement
+ at registerElementClass
+class SyncToken (WebDAVTextElement):
+    """
+    Synchronization token used in report and as a property.
+    """
+    name = "sync-token"
+    hidden = True
+    protected = True
+
+
+ at registerElement
+ at registerElementClass
+class SyncLevel (WebDAVTextElement):
+    """
+    Synchronization level used in report.
+    """
+    name = "sync-level"
+
+
+# Extend MultiStatus, to add sync-token
+MultiStatus.allowed_children[(dav_namespace, "sync-token")] = (0, 1)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120910/05ab9136/attachment-0001.html>


More information about the calendarserver-changes mailing list