[CalendarServer-changes] [14518] CalendarServer/branches/users/sagen/trashcan-5
source_changes at macosforge.org
source_changes at macosforge.org
Fri Mar 6 11:34:31 PST 2015
Revision: 14518
http://trac.calendarserver.org//changeset/14518
Author: sagen at apple.com
Date: 2015-03-06 11:34:30 -0800 (Fri, 06 Mar 2015)
Log Message:
-----------
Checkpoint of trash recovery command line tool
Modified Paths:
--------------
CalendarServer/branches/users/sagen/trashcan-5/calendarserver/push/notifier.py
CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/cmdline.py
CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/trash.py
CalendarServer/branches/users/sagen/trashcan-5/txdav/caldav/datastore/sql.py
CalendarServer/branches/users/sagen/trashcan-5/txdav/common/datastore/sql.py
Modified: CalendarServer/branches/users/sagen/trashcan-5/calendarserver/push/notifier.py
===================================================================
--- 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)
@@ -204,7 +204,8 @@
def pushKeyForId(self, prefix, id):
- return "/%s/%s/%s/" % (prefix, self.hostname, id)
+ key = "/%s/%s/%s/" % (prefix, self.hostname, id)
+ return key[:255]
Modified: CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/cmdline.py
===================================================================
--- 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)
@@ -33,13 +33,14 @@
from calendarserver.tap.util import getRootResource
from errno import ENOENT, EACCES
from twext.enterprise.jobqueue import NonPerformingQueuer
+from twistedcaldav.timezones import TimezoneCache
# TODO: direct unit tests for these functions.
def utilityMain(
configFileName, serviceClass, reactor=None, serviceMaker=None,
- patchConfig=None, onShutdown=None, verbose=False
+ patchConfig=None, onShutdown=None, verbose=False, loadTimezones=False
):
"""
Shared main-point for utilities.
@@ -124,6 +125,10 @@
if onShutdown is not None:
reactor.addSystemEventTrigger("before", "shutdown", onShutdown)
+ if loadTimezones:
+ TimezoneCache.create()
+
+
except (ConfigurationError, OSError), e:
sys.stderr.write("Error: %s\n" % (e,))
return
Modified: CalendarServer/branches/users/sagen/trashcan-5/calendarserver/tools/trash.py
===================================================================
--- 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)
@@ -17,48 +17,27 @@
##
from __future__ import print_function
-import collections
+from argparse import ArgumentParser
import datetime
-from getopt import getopt, GetoptError
-import os
-import sys
-from calendarserver.tools import tables
from calendarserver.tools.cmdline import utilityMain, WorkerService
-
-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
+from calendarserver.tools.util import prettyRecord
from twext.python.log import Logger
+from twisted.internet.defer import inlineCallbacks, returnValue
+from txdav.base.propertystore.base import PropertyName
+from txdav.xml import element
-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
-
-
log = Logger()
class TrashRestorationService(WorkerService):
- principals = []
+ operation = None
+ operationArgs = []
def doWork(self):
- rootResource = self.rootResource()
- directory = rootResource.getDirectory()
- return restoreFromTrash(
- self.store, directory, rootResource, self.principals
- )
+ return self.operation(self.store, *self.operationArgs)
@@ -67,41 +46,277 @@
parser = ArgumentParser(description='Restore events from trash')
parser.add_argument('-f', '--config', dest='configFileName', metavar='CONFIGFILE', help='caldavd.plist configuration file path')
parser.add_argument('-d', '--debug', action='store_true', help='show debug logging')
- parser.add_argument('principal', help='one or more principals to restore', nargs='+') # Required
+ 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')
+
args = parser.parse_args()
- TrashRestorationService.principals = args.principal
+ if not args.principal:
+ print("--principal missing")
+ return
+ 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
+
utilityMain(
args.configFileName,
TrashRestorationService,
verbose=args.debug,
+ loadTimezones=True
)
+ at 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)
+ 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()
+
+
@inlineCallbacks
-def restoreFromTrash(store, directory, root, principals):
+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)
- for principalUID in principals:
- txn = store.newTransaction(label="Restore trashed events")
- home = yield txn.calendarHomeWithUID(principalUID)
- if home is 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)
+
+ # print("Listing trashed collections for:", prettyRecord(record))
+ for collection in untrashedCollections:
+ displayName = displayNameForCollection(collection)
+ children = yield trash.trashForCollection(collection._resourceID)
+ if len(children) == 0:
continue
- trash = yield home.childWithName("trash")
- 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()
+ 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
+ )
+ )
- yield txn.commit()
+ yield txn.commit()
+ at 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()
+
+
+ at 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()
+
+
+
+ at 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
+
+
if __name__ == "__main__":
main()
Modified: CalendarServer/branches/users/sagen/trashcan-5/txdav/caldav/datastore/sql.py
===================================================================
--- 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)
@@ -4927,6 +4927,7 @@
splitter = iCalSplitter()
willSplit = splitter.willSplit(caldata)
if willSplit:
+ log.debug("Splitting scheduled event being recovered by organizer from trash")
yield self.split(
coercePartstatsInExistingResource=True,
splitter=splitter
@@ -4946,6 +4947,7 @@
instances = sorted(instances.instances.values(), key=lambda x: x.start)
if instances[0].start >= now:
# future
+ log.debug("Scheduled event being recovered by organizer from trash, fully in the future")
newdata = caldata.duplicate()
newdata.bumpiTIPInfo(doSequence=True)
for attendee in newdata.getAllAttendeeProperties():
@@ -4959,6 +4961,7 @@
else:
# past
+ log.debug("Scheduled event being recovered by organizer from trash, fully in the past")
yield ImplicitScheduler().refreshAllAttendeesExceptSome(
self._txn,
self,
@@ -4966,10 +4969,13 @@
else:
# If an ATTENDEE is moving the event from trash
+ log.debug("Scheduled event being recovered by attendee from trash")
yield ImplicitScheduler().sendAttendeeReply(
self._txn,
self
)
+ else:
+ log.debug("Recovered un-scheduled event from trash")
returnValue(name)
Modified: CalendarServer/branches/users/sagen/trashcan-5/txdav/common/datastore/sql.py
===================================================================
--- 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)
@@ -6168,7 +6168,7 @@
yield self._updateIsInTrashQuery.on(
self._txn, isInTrash=True, trashed=whenTrashed, resourceID=self._resourceID
)
- newName = "{}-{}".format(self._name[:200], str(uuid4()))
+ newName = "{}-{}".format(self._name[:36], str(uuid4()))
yield self.rename(newName)
# yield self.notifyPropertyChanged()
# yield self.invalidateQueryCache()
@@ -6192,9 +6192,10 @@
@inlineCallbacks
- def fromTrash(self, restoreChildren=True, delta=datetime.timedelta(minutes=3)):
-
- # print("XYZZY collection fromTrash")
+ def fromTrash(
+ self, restoreChildren=True, delta=datetime.timedelta(minutes=5),
+ verbose=False
+ ):
if not self._isInTrash:
returnValue(None)
@@ -6202,7 +6203,6 @@
if delta is not None:
startTime = startTime - delta
-
yield self._updateIsInTrashQuery.on(
self._txn, isInTrash=False, trashed=None, resourceID=self._resourceID
)
@@ -6231,7 +6231,11 @@
self._resourceID, start=startTime
)
for child in childrenToRestore:
- # print("Restoring", child)
+ if verbose:
+ component = yield child.component()
+ summary = component.mainComponent().propertyValue("SUMMARY", "<no title>")
+ print("Recovering \"{}\"".format(summary.encode("utf-8")))
+
yield child.fromTrash()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150306/4755b4f8/attachment-0001.html>
More information about the calendarserver-changes
mailing list