<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[14518] CalendarServer/branches/users/sagen/trashcan-5</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/14518">14518</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2015-03-06 11:34:30 -0800 (Fri, 06 Mar 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Checkpoint of trash recovery command line tool</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserssagentrashcan5calendarserverpushnotifierpy">CalendarServer/branches/users/sagen/trashcan-5/calendarserver/push/notifier.py</a></li>
<li><a href="#CalendarServerbranchesuserssagentrashcan5calendarservertoolscmdlinepy">CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/cmdline.py</a></li>
<li><a href="#CalendarServerbranchesuserssagentrashcan5calendarservertoolstrashpy">CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/trash.py</a></li>
<li><a href="#CalendarServerbranchesuserssagentrashcan5txdavcaldavdatastoresqlpy">CalendarServer/branches/users/sagen/trashcan-5/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserssagentrashcan5txdavcommondatastoresqlpy">CalendarServer/branches/users/sagen/trashcan-5/txdav/common/datastore/sql.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserssagentrashcan5calendarserverpushnotifierpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/trashcan-5/calendarserver/push/notifier.py (14517 => 14518)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/trashcan-5/calendarserver/push/notifier.py        2015-03-06 01:01:12 UTC (rev 14517)
+++ CalendarServer/branches/users/sagen/trashcan-5/calendarserver/push/notifier.py        2015-03-06 19:34:30 UTC (rev 14518)
</span><span class="lines">@@ -204,7 +204,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def pushKeyForId(self, prefix, id):
</span><del>-        return &quot;/%s/%s/%s/&quot; % (prefix, self.hostname, id)
</del><ins>+        key = &quot;/%s/%s/%s/&quot; % (prefix, self.hostname, id)
+        return key[:255]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagentrashcan5calendarservertoolscmdlinepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/cmdline.py (14517 => 14518)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/cmdline.py        2015-03-06 01:01:12 UTC (rev 14517)
+++ CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/cmdline.py        2015-03-06 19:34:30 UTC (rev 14518)
</span><span class="lines">@@ -33,13 +33,14 @@
</span><span class="cx"> from calendarserver.tap.util import getRootResource
</span><span class="cx"> from errno import ENOENT, EACCES
</span><span class="cx"> from twext.enterprise.jobqueue import NonPerformingQueuer
</span><ins>+from twistedcaldav.timezones import TimezoneCache
</ins><span class="cx"> 
</span><span class="cx"> # TODO: direct unit tests for these functions.
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> def utilityMain(
</span><span class="cx">     configFileName, serviceClass, reactor=None, serviceMaker=None,
</span><del>-    patchConfig=None, onShutdown=None, verbose=False
</del><ins>+    patchConfig=None, onShutdown=None, verbose=False, loadTimezones=False
</ins><span class="cx"> ):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Shared main-point for utilities.
</span><span class="lines">@@ -124,6 +125,10 @@
</span><span class="cx">         if onShutdown is not None:
</span><span class="cx">             reactor.addSystemEventTrigger(&quot;before&quot;, &quot;shutdown&quot;, onShutdown)
</span><span class="cx"> 
</span><ins>+        if loadTimezones:
+            TimezoneCache.create()
+
+
</ins><span class="cx">     except (ConfigurationError, OSError), e:
</span><span class="cx">         sys.stderr.write(&quot;Error: %s\n&quot; % (e,))
</span><span class="cx">         return
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagentrashcan5calendarservertoolstrashpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/trash.py (14517 => 14518)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/trash.py        2015-03-06 01:01:12 UTC (rev 14517)
+++ CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/trash.py        2015-03-06 19:34:30 UTC (rev 14518)
</span><span class="lines">@@ -17,48 +17,27 @@
</span><span class="cx"> ##
</span><span class="cx"> from __future__ import print_function
</span><span class="cx"> 
</span><del>-import collections
</del><ins>+from argparse import ArgumentParser
</ins><span class="cx"> import datetime
</span><del>-from getopt import getopt, GetoptError
-import os
-import sys
</del><span class="cx"> 
</span><del>-from calendarserver.tools import tables
</del><span class="cx"> from calendarserver.tools.cmdline import utilityMain, WorkerService
</span><del>-
-from pycalendar.datetime import DateTime
-
-from twext.enterprise.dal.record import fromTable
-from twext.enterprise.dal.syntax import Delete, Select, Union
-from twext.enterprise.jobqueue import WorkItem, RegeneratingWorkItem
</del><ins>+from calendarserver.tools.util import prettyRecord
</ins><span class="cx"> from twext.python.log import Logger
</span><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
+from txdav.base.propertystore.base import PropertyName
+from txdav.xml import element
</ins><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
-
-from twistedcaldav import caldavxml
-from twistedcaldav.config import config
-
-from txdav.caldav.datastore.query.filter import Filter
-from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_NORMAL
-from txdav.caldav.datastore.sql import CalendarStoreFeatures
-
-from argparse import ArgumentParser
-
-
</del><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class TrashRestorationService(WorkerService):
</span><span class="cx"> 
</span><del>-    principals = []
</del><ins>+    operation = None
+    operationArgs = []
</ins><span class="cx"> 
</span><span class="cx">     def doWork(self):
</span><del>-        rootResource = self.rootResource()
-        directory = rootResource.getDirectory()
-        return restoreFromTrash(
-            self.store, directory, rootResource, self.principals
-        )
</del><ins>+        return self.operation(self.store, *self.operationArgs)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -67,41 +46,277 @@
</span><span class="cx">     parser = ArgumentParser(description='Restore events from trash')
</span><span class="cx">     parser.add_argument('-f', '--config', dest='configFileName', metavar='CONFIGFILE', help='caldavd.plist configuration file path')
</span><span class="cx">     parser.add_argument('-d', '--debug', action='store_true', help='show debug logging')
</span><del>-    parser.add_argument('principal', help='one or more principals to restore', nargs='+')  # Required
</del><ins>+    parser.add_argument('-p', '--principal', dest='principal', help='the principal to use (uid)')
+    parser.add_argument('-e', '--events', action='store_true', help='list trashed events')
+    parser.add_argument('-c', '--collections', action='store_true', help='list trashed collections for principal (uid)')
+    parser.add_argument('-r', '--recover', dest='resourceID', type=int, help='recover trashed collection or event (by resource ID)')
+    parser.add_argument('--empty', action='store_true', help='empty the principal\'s trash')
+    parser.add_argument('--days', type=int, default=0, help='number of past days to retain')
+
</ins><span class="cx">     args = parser.parse_args()
</span><span class="cx"> 
</span><del>-    TrashRestorationService.principals = args.principal
</del><ins>+    if not args.principal:
+        print(&quot;--principal missing&quot;)
+        return
</ins><span class="cx"> 
</span><ins>+    if args.empty:
+        operation = emptyTrashForPrincipal
+        operationArgs = [args.principal, args.days]
+    elif args.collections:
+        if args.resourceID:
+            operation = restoreTrashedCollection
+            operationArgs = [args.principal, args.resourceID]
+        else:
+            operation = listTrashedCollectionsForPrincipal
+            operationArgs = [args.principal]
+    elif args.events:
+        if args.resourceID:
+            operation = restoreTrashedEvent
+            operationArgs = [args.principal, args.resourceID]
+        else:
+            operation = listTrashedEventsForPrincipal
+            operationArgs = [args.principal]
+    else:
+        operation = listTrashedCollectionsForPrincipal
+        operationArgs = [args.principal]
+
+    TrashRestorationService.operation = operation
+    TrashRestorationService.operationArgs = operationArgs
+
</ins><span class="cx">     utilityMain(
</span><span class="cx">         args.configFileName,
</span><span class="cx">         TrashRestorationService,
</span><span class="cx">         verbose=args.debug,
</span><ins>+        loadTimezones=True
</ins><span class="cx">     )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
+def listTrashedCollectionsForPrincipal(service, store, principalUID):
+    directory = store.directoryService()
+    record = yield directory.recordWithUID(principalUID)
+    if record is None:
+        print(&quot;No record found for:&quot;, principalUID)
+        returnValue(None)
</ins><span class="cx"> 
</span><ins>+    txn = store.newTransaction(label=&quot;List trashed collections&quot;)
+    home = yield txn.calendarHomeWithUID(principalUID)
+    if home is None:
+        print(&quot;No home for principal&quot;)
+        returnValue(None)
+
+    trash = yield home.childWithName(&quot;trash&quot;)
+
+    trashedCollections = yield home.children(onlyInTrash=True)
+    if len(trashedCollections) == 0:
+        print(&quot;No trashed collections for:&quot;, prettyRecord(record))
+        returnValue(None)
+
+    print(&quot;Listing trashed collections for:&quot;, prettyRecord(record))
+    for collection in trashedCollections:
+        displayName = displayNameForCollection(collection)
+        print(
+            &quot;Collection = \&quot;{}\&quot;, trashed = {}, id = {}&quot;.format(
+                displayName.encode(&quot;utf-8&quot;), collection.whenTrashed(),
+                collection._resourceID
+            )
+        )
+        startTime = collection.whenTrashed() - datetime.timedelta(minutes=5)
+        children = yield trash.trashForCollection(
+            collection._resourceID, start=startTime
+        )
+        print(&quot; ...containing events:&quot;)
+        for child in children:
+            component = yield child.component()
+            summary = component.mainComponent().propertyValue(&quot;SUMMARY&quot;, &quot;&lt;no title&gt;&quot;)
+            whenTrashed = yield child.whenTrashed()
+            print(&quot; \&quot;{}\&quot;, trashed = {}&quot;.format(summary.encode(&quot;utf-8&quot;), whenTrashed))
+
+    yield txn.commit()
+
+
</ins><span class="cx"> @inlineCallbacks
</span><del>-def restoreFromTrash(store, directory, root, principals):
</del><ins>+def listTrashedEventsForPrincipal(service, store, principalUID):
+    directory = store.directoryService()
+    record = yield directory.recordWithUID(principalUID)
+    if record is None:
+        print(&quot;No record found for:&quot;, principalUID)
+        returnValue(None)
</ins><span class="cx"> 
</span><del>-    for principalUID in principals:
-        txn = store.newTransaction(label=&quot;Restore trashed events&quot;)
-        home = yield txn.calendarHomeWithUID(principalUID)
-        if home is None:
</del><ins>+    txn = store.newTransaction(label=&quot;List trashed collections&quot;)
+    home = yield txn.calendarHomeWithUID(principalUID)
+    if home is None:
+        print(&quot;No home for principal&quot;)
+        returnValue(None)
+
+    trash = yield home.childWithName(&quot;trash&quot;)
+
+    untrashedCollections = yield home.children(onlyInTrash=False)
+    if len(untrashedCollections) == 0:
+        print(&quot;No untrashed collections for:&quot;, prettyRecord(record))
+        returnValue(None)
+
+    # print(&quot;Listing trashed collections for:&quot;, prettyRecord(record))
+    for collection in untrashedCollections:
+        displayName = displayNameForCollection(collection)
+        children = yield trash.trashForCollection(collection._resourceID)
+        if len(children) == 0:
</ins><span class="cx">             continue
</span><del>-        trash = yield home.childWithName(&quot;trash&quot;)
-        names = yield trash.listObjectResources()
-        for name in names:
-            cobj = yield trash.calendarObjectWithName(name)
-            print(name, cobj)
</del><span class="cx"> 
</span><del>-            if cobj is not None:
-                # If it's still in the trash, restore it from trash
-                if (yield cobj.isInTrash()):
-                    print(&quot;Restoring:&quot;, name)
-                    yield cobj.fromTrash()
</del><ins>+        print(&quot;Collection = \&quot;{}\&quot;&quot;.format(displayName.encode(&quot;utf-8&quot;)))
+        for child in children:
+            component = yield child.component()
+            summary = component.mainComponent().propertyValue(&quot;SUMMARY&quot;, &quot;&lt;no title&gt;&quot;)
+            whenTrashed = yield child.whenTrashed()
+            print(
+                &quot; \&quot;{}\&quot;, trashed = {}, id = {}&quot;.format(
+                    summary.encode(&quot;utf-8&quot;), whenTrashed, child._resourceID
+                )
+            )
</ins><span class="cx"> 
</span><del>-        yield txn.commit()
</del><ins>+    yield txn.commit()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+@inlineCallbacks
+def restoreTrashedCollection(service, store, principalUID, resourceID):
+    directory = store.directoryService()
+    record = yield directory.recordWithUID(principalUID)
+    if record is None:
+        print(&quot;No record found for:&quot;, principalUID)
+        returnValue(None)
+
+    txn = store.newTransaction(label=&quot;Restore trashed collection&quot;)
+    home = yield txn.calendarHomeWithUID(principalUID)
+    if home is None:
+        print(&quot;No home for principal&quot;)
+        returnValue(None)
+
+    collection = yield home.childWithID(resourceID, onlyInTrash=True)
+    if collection is None:
+        print(&quot;Collection {} is not in the trash&quot;.format(resourceID))
+        returnValue(None)
+
+    yield collection.fromTrash(
+        restoreChildren=True, delta=datetime.timedelta(minutes=5), verbose=True
+    )
+
+    yield txn.commit()
+
+
+@inlineCallbacks
+def restoreTrashedEvent(service, store, principalUID, resourceID):
+    directory = store.directoryService()
+    record = yield directory.recordWithUID(principalUID)
+    if record is None:
+        print(&quot;No record found for:&quot;, principalUID)
+        returnValue(None)
+
+    txn = store.newTransaction(label=&quot;Restore trashed collection&quot;)
+    home = yield txn.calendarHomeWithUID(principalUID)
+    if home is None:
+        print(&quot;No home for principal&quot;)
+        returnValue(None)
+
+    trash = yield home.childWithName(&quot;trash&quot;)
+    child = yield trash.objectResourceWithID(resourceID)
+    if child is None:
+        print(&quot;Event not found&quot;)
+        returnValue(None)
+
+    component = yield child.component()
+    summary = component.mainComponent().propertyValue(&quot;SUMMARY&quot;, &quot;&lt;no title&gt;&quot;)
+    print(&quot;Restoring \&quot;{}\&quot;&quot;.format(summary.encode(&quot;utf-8&quot;)))
+    yield child.fromTrash()
+
+    yield txn.commit()
+
+
+
+@inlineCallbacks
+def emptyTrashForPrincipal(service, store, principalUID, days):
+    directory = store.directoryService()
+    record = yield directory.recordWithUID(principalUID)
+    if record is None:
+        print(&quot;No record found for:&quot;, principalUID)
+        returnValue(None)
+
+    txn = store.newTransaction(label=&quot;List trashed collections&quot;)
+    home = yield txn.calendarHomeWithUID(principalUID)
+    if home is None:
+        print(&quot;No home for principal&quot;)
+        returnValue(None)
+
+    trash = yield home.childWithName(&quot;trash&quot;)
+
+    untrashedCollections = yield home.children(onlyInTrash=False)
+    if len(untrashedCollections) == 0:
+        print(&quot;No untrashed collections for:&quot;, prettyRecord(record))
+        returnValue(None)
+
+    endTime = datetime.datetime.utcnow() - datetime.timedelta(days=-days)
+    # print(&quot;Listing trashed collections for:&quot;, prettyRecord(record))
+    for collection in untrashedCollections:
+        displayName = displayNameForCollection(collection)
+        children = yield trash.trashForCollection(
+            collection._resourceID, end=endTime
+        )
+        if len(children) == 0:
+            continue
+
+        print(&quot;Collection = \&quot;{}\&quot;&quot;.format(displayName.encode(&quot;utf-8&quot;)))
+        for child in children:
+            component = yield child.component()
+            summary = component.mainComponent().propertyValue(&quot;SUMMARY&quot;, &quot;&lt;no title&gt;&quot;)
+            whenTrashed = yield child.whenTrashed()
+            print(
+                &quot; \&quot;{}\&quot;, trashed = {}, id = {}&quot;.format(
+                    summary.encode(&quot;utf-8&quot;), whenTrashed, child._resourceID
+                )
+            )
+            print(&quot;Removing...&quot;)
+            yield child.reallyRemove()
+
+    yield txn.commit()
+
+# @inlineCallbacks
+# def restoreFromTrash(store, directory, root, principals):
+
+#     for principalUID in principals:
+#         txn = store.newTransaction(label=&quot;Restore trashed events&quot;)
+#         home = yield txn.calendarHomeWithUID(principalUID)
+#         if home is None:
+#             continue
+#         trashedCollections = yield home.children(onlyInTrash=True)
+#         for collection in trashedCollections:
+#             displayName = displayNameForCollection(collection)
+#             print(&quot;Restoring collection&quot;, displayName, collection._resourceID)
+#             yield collection.fromTrash(restoreChildren=True)
+#         # This code is for untrashing all objects:
+#         # names = yield trash.listObjectResources()
+#         # for name in names:
+#         #     cobj = yield trash.calendarObjectWithName(name)
+#         #     print(name, cobj)
+
+#         #     if cobj is not None:
+#         #         # If it's still in the trash, restore it from trash
+#         #         if (yield cobj.isInTrash()):
+#         #             print(&quot;Restoring:&quot;, name)
+#         #             yield cobj.fromTrash()
+
+#         yield txn.commit()
+
+
+def displayNameForCollection(collection):
+    try:
+        displayName = collection.properties()[
+            PropertyName.fromElement(element.DisplayName)
+        ]
+        displayName = displayName.toString()
+    except:
+        displayName = collection.name()
+
+    return displayName
+
+
</ins><span class="cx"> if __name__ == &quot;__main__&quot;:
</span><span class="cx">     main()
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagentrashcan5txdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/trashcan-5/txdav/caldav/datastore/sql.py (14517 => 14518)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/trashcan-5/txdav/caldav/datastore/sql.py        2015-03-06 01:01:12 UTC (rev 14517)
+++ CalendarServer/branches/users/sagen/trashcan-5/txdav/caldav/datastore/sql.py        2015-03-06 19:34:30 UTC (rev 14518)
</span><span class="lines">@@ -4927,6 +4927,7 @@
</span><span class="cx">                 splitter = iCalSplitter()
</span><span class="cx">                 willSplit = splitter.willSplit(caldata)
</span><span class="cx">                 if willSplit:
</span><ins>+                    log.debug(&quot;Splitting scheduled event being recovered by organizer from trash&quot;)
</ins><span class="cx">                     yield self.split(
</span><span class="cx">                         coercePartstatsInExistingResource=True,
</span><span class="cx">                         splitter=splitter
</span><span class="lines">@@ -4946,6 +4947,7 @@
</span><span class="cx">                     instances = sorted(instances.instances.values(), key=lambda x: x.start)
</span><span class="cx">                     if instances[0].start &gt;= now:
</span><span class="cx">                         # future
</span><ins>+                        log.debug(&quot;Scheduled event being recovered by organizer from trash, fully in the future&quot;)
</ins><span class="cx">                         newdata = caldata.duplicate()
</span><span class="cx">                         newdata.bumpiTIPInfo(doSequence=True)
</span><span class="cx">                         for attendee in newdata.getAllAttendeeProperties():
</span><span class="lines">@@ -4959,6 +4961,7 @@
</span><span class="cx"> 
</span><span class="cx">                     else:
</span><span class="cx">                         # past
</span><ins>+                        log.debug(&quot;Scheduled event being recovered by organizer from trash, fully in the past&quot;)
</ins><span class="cx">                         yield ImplicitScheduler().refreshAllAttendeesExceptSome(
</span><span class="cx">                             self._txn,
</span><span class="cx">                             self,
</span><span class="lines">@@ -4966,10 +4969,13 @@
</span><span class="cx"> 
</span><span class="cx">             else:
</span><span class="cx">                 # If an ATTENDEE is moving the event from trash
</span><ins>+                log.debug(&quot;Scheduled event being recovered by attendee from trash&quot;)
</ins><span class="cx">                 yield ImplicitScheduler().sendAttendeeReply(
</span><span class="cx">                     self._txn,
</span><span class="cx">                     self
</span><span class="cx">                 )
</span><ins>+        else:
+            log.debug(&quot;Recovered un-scheduled event from trash&quot;)
</ins><span class="cx"> 
</span><span class="cx">         returnValue(name)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserssagentrashcan5txdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/sagen/trashcan-5/txdav/common/datastore/sql.py (14517 => 14518)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/sagen/trashcan-5/txdav/common/datastore/sql.py        2015-03-06 01:01:12 UTC (rev 14517)
+++ CalendarServer/branches/users/sagen/trashcan-5/txdav/common/datastore/sql.py        2015-03-06 19:34:30 UTC (rev 14518)
</span><span class="lines">@@ -6168,7 +6168,7 @@
</span><span class="cx">         yield self._updateIsInTrashQuery.on(
</span><span class="cx">             self._txn, isInTrash=True, trashed=whenTrashed, resourceID=self._resourceID
</span><span class="cx">         )
</span><del>-        newName = &quot;{}-{}&quot;.format(self._name[:200], str(uuid4()))
</del><ins>+        newName = &quot;{}-{}&quot;.format(self._name[:36], str(uuid4()))
</ins><span class="cx">         yield self.rename(newName)
</span><span class="cx">         # yield self.notifyPropertyChanged()
</span><span class="cx">         # yield self.invalidateQueryCache()
</span><span class="lines">@@ -6192,9 +6192,10 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def fromTrash(self, restoreChildren=True, delta=datetime.timedelta(minutes=3)):
-
-        # print(&quot;XYZZY collection fromTrash&quot;)
</del><ins>+    def fromTrash(
+        self, restoreChildren=True, delta=datetime.timedelta(minutes=5),
+        verbose=False
+    ):
</ins><span class="cx">         if not self._isInTrash:
</span><span class="cx">             returnValue(None)
</span><span class="cx"> 
</span><span class="lines">@@ -6202,7 +6203,6 @@
</span><span class="cx">         if delta is not None:
</span><span class="cx">             startTime = startTime - delta
</span><span class="cx"> 
</span><del>-
</del><span class="cx">         yield self._updateIsInTrashQuery.on(
</span><span class="cx">             self._txn, isInTrash=False, trashed=None, resourceID=self._resourceID
</span><span class="cx">         )
</span><span class="lines">@@ -6231,7 +6231,11 @@
</span><span class="cx">                 self._resourceID, start=startTime
</span><span class="cx">             )
</span><span class="cx">             for child in childrenToRestore:
</span><del>-                # print(&quot;Restoring&quot;, child)
</del><ins>+                if verbose:
+                    component = yield child.component()
+                    summary = component.mainComponent().propertyValue(&quot;SUMMARY&quot;, &quot;&lt;no title&gt;&quot;)
+                    print(&quot;Recovering \&quot;{}\&quot;&quot;.format(summary.encode(&quot;utf-8&quot;)))
+
</ins><span class="cx">                 yield child.fromTrash()
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>