<!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 "/%s/%s/%s/" % (prefix, self.hostname, id)
</del><ins>+ key = "/%s/%s/%s/" % (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"> """
</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("before", "shutdown", 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("Error: %s\n" % (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("--principal missing")
+ 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("No record found for:", principalUID)
+ returnValue(None)
</ins><span class="cx">
</span><ins>+ txn = store.newTransaction(label="List trashed collections")
+ home = yield txn.calendarHomeWithUID(principalUID)
+ if home is None:
+ print("No home for principal")
+ returnValue(None)
+
+ trash = yield home.childWithName("trash")
+
+ trashedCollections = yield home.children(onlyInTrash=True)
+ if len(trashedCollections) == 0:
+ print("No trashed collections for:", prettyRecord(record))
+ returnValue(None)
+
+ print("Listing trashed collections for:", prettyRecord(record))
+ for collection in trashedCollections:
+ displayName = displayNameForCollection(collection)
+ print(
+ "Collection = \"{}\", trashed = {}, id = {}".format(
+ displayName.encode("utf-8"), collection.whenTrashed(),
+ collection._resourceID
+ )
+ )
+ startTime = collection.whenTrashed() - datetime.timedelta(minutes=5)
+ children = yield trash.trashForCollection(
+ collection._resourceID, start=startTime
+ )
+ print(" ...containing events:")
+ for child in children:
+ component = yield child.component()
+ summary = component.mainComponent().propertyValue("SUMMARY", "<no title>")
+ whenTrashed = yield child.whenTrashed()
+ print(" \"{}\", trashed = {}".format(summary.encode("utf-8"), 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("No record found for:", principalUID)
+ returnValue(None)
</ins><span class="cx">
</span><del>- for principalUID in principals:
- txn = store.newTransaction(label="Restore trashed events")
- home = yield txn.calendarHomeWithUID(principalUID)
- if home is None:
</del><ins>+ txn = store.newTransaction(label="List trashed collections")
+ home = yield txn.calendarHomeWithUID(principalUID)
+ if home is None:
+ print("No home for principal")
+ returnValue(None)
+
+ trash = yield home.childWithName("trash")
+
+ untrashedCollections = yield home.children(onlyInTrash=False)
+ if len(untrashedCollections) == 0:
+ print("No untrashed collections for:", prettyRecord(record))
+ returnValue(None)
+
+ # print("Listing trashed collections for:", 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("trash")
- 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("Restoring:", name)
- yield cobj.fromTrash()
</del><ins>+ print("Collection = \"{}\"".format(displayName.encode("utf-8")))
+ for child in children:
+ component = yield child.component()
+ summary = component.mainComponent().propertyValue("SUMMARY", "<no title>")
+ whenTrashed = yield child.whenTrashed()
+ print(
+ " \"{}\", trashed = {}, id = {}".format(
+ summary.encode("utf-8"), 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("No record found for:", principalUID)
+ returnValue(None)
+
+ txn = store.newTransaction(label="Restore trashed collection")
+ home = yield txn.calendarHomeWithUID(principalUID)
+ if home is None:
+ print("No home for principal")
+ returnValue(None)
+
+ collection = yield home.childWithID(resourceID, onlyInTrash=True)
+ if collection is None:
+ print("Collection {} is not in the trash".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("No record found for:", principalUID)
+ returnValue(None)
+
+ txn = store.newTransaction(label="Restore trashed collection")
+ home = yield txn.calendarHomeWithUID(principalUID)
+ if home is None:
+ print("No home for principal")
+ returnValue(None)
+
+ trash = yield home.childWithName("trash")
+ child = yield trash.objectResourceWithID(resourceID)
+ if child is None:
+ print("Event not found")
+ returnValue(None)
+
+ component = yield child.component()
+ summary = component.mainComponent().propertyValue("SUMMARY", "<no title>")
+ print("Restoring \"{}\"".format(summary.encode("utf-8")))
+ 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("No record found for:", principalUID)
+ returnValue(None)
+
+ txn = store.newTransaction(label="List trashed collections")
+ home = yield txn.calendarHomeWithUID(principalUID)
+ if home is None:
+ print("No home for principal")
+ returnValue(None)
+
+ trash = yield home.childWithName("trash")
+
+ untrashedCollections = yield home.children(onlyInTrash=False)
+ if len(untrashedCollections) == 0:
+ print("No untrashed collections for:", prettyRecord(record))
+ returnValue(None)
+
+ endTime = datetime.datetime.utcnow() - datetime.timedelta(days=-days)
+ # print("Listing trashed collections for:", prettyRecord(record))
+ for collection in untrashedCollections:
+ displayName = displayNameForCollection(collection)
+ children = yield trash.trashForCollection(
+ collection._resourceID, end=endTime
+ )
+ if len(children) == 0:
+ continue
+
+ print("Collection = \"{}\"".format(displayName.encode("utf-8")))
+ for child in children:
+ component = yield child.component()
+ summary = component.mainComponent().propertyValue("SUMMARY", "<no title>")
+ whenTrashed = yield child.whenTrashed()
+ print(
+ " \"{}\", trashed = {}, id = {}".format(
+ summary.encode("utf-8"), whenTrashed, child._resourceID
+ )
+ )
+ print("Removing...")
+ yield child.reallyRemove()
+
+ yield txn.commit()
+
+# @inlineCallbacks
+# def restoreFromTrash(store, directory, root, principals):
+
+# for principalUID in principals:
+# txn = store.newTransaction(label="Restore trashed events")
+# home = yield txn.calendarHomeWithUID(principalUID)
+# if home is None:
+# continue
+# trashedCollections = yield home.children(onlyInTrash=True)
+# for collection in trashedCollections:
+# displayName = displayNameForCollection(collection)
+# print("Restoring collection", 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("Restoring:", 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__ == "__main__":
</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("Splitting scheduled event being recovered by organizer from trash")
</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 >= now:
</span><span class="cx"> # future
</span><ins>+ log.debug("Scheduled event being recovered by organizer from trash, fully in the future")
</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("Scheduled event being recovered by organizer from trash, fully in the past")
</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("Scheduled event being recovered by attendee from trash")
</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("Recovered un-scheduled event from trash")
</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 = "{}-{}".format(self._name[:200], str(uuid4()))
</del><ins>+ newName = "{}-{}".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("XYZZY collection fromTrash")
</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("Restoring", child)
</del><ins>+ if verbose:
+ component = yield child.component()
+ summary = component.mainComponent().propertyValue("SUMMARY", "<no title>")
+ print("Recovering \"{}\"".format(summary.encode("utf-8")))
+
</ins><span class="cx"> yield child.fromTrash()
</span><span class="cx">
</span><span class="cx">
</span></span></pre>
</div>
</div>
</body>
</html>