[CalendarServer-changes] [6847] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Tue Feb 1 18:05:44 PST 2011
Revision: 6847
http://trac.macosforge.org/projects/calendarserver/changeset/6847
Author: sagen at apple.com
Date: 2011-02-01 18:05:44 -0800 (Tue, 01 Feb 2011)
Log Message:
-----------
Adds calendarserver_purge_attachments
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tools/purge.py
CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py
CalendarServer/trunk/doc/calendarserver_purge_events.8
CalendarServer/trunk/setup.py
CalendarServer/trunk/support/Makefile.Apple
CalendarServer/trunk/txdav/common/datastore/sql.py
Added Paths:
-----------
CalendarServer/trunk/bin/calendarserver_purge_attachments
CalendarServer/trunk/doc/calendarserver_purge_attachments.8
Added: CalendarServer/trunk/bin/calendarserver_purge_attachments
===================================================================
--- CalendarServer/trunk/bin/calendarserver_purge_attachments (rev 0)
+++ CalendarServer/trunk/bin/calendarserver_purge_attachments 2011-02-02 02:05:44 UTC (rev 6847)
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2011 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.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+ if "PYTHONPATH" in globals():
+ sys.path.insert(0, PYTHONPATH)
+ else:
+ from os.path import dirname, abspath, join
+ from subprocess import Popen, PIPE
+
+ home = dirname(dirname(abspath(__file__)))
+ run = join(home, "run")
+
+ child = Popen((run, "-p"), stdout=PIPE)
+ path, stderr = child.communicate()
+
+ path = path.rstrip("\n")
+
+ if child.wait() == 0:
+ sys.path[0:0] = path.split(":")
+
+ sys.argv[1:1] = ["-f", join(home, "conf", "caldavd-dev.plist")]
+
+ from calendarserver.tools.purge import main_purge_orphaned_attachments
+ main_purge_orphaned_attachments()
Property changes on: CalendarServer/trunk/bin/calendarserver_purge_attachments
___________________________________________________________________
Added: svn:executable
+ *
Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py 2011-02-02 01:09:18 UTC (rev 6846)
+++ CalendarServer/trunk/calendarserver/tools/purge.py 2011-02-02 02:05:44 UTC (rev 6847)
@@ -64,6 +64,27 @@
else:
sys.exit(0)
+def usage_purge_orphaned_attachments(e=None):
+
+ name = os.path.basename(sys.argv[0])
+ print "usage: %s [options]" % (name,)
+ print ""
+ print " Remove orphaned attachments from the calendar server"
+ print ""
+ print "options:"
+ print " -b --batch <number>: number of attachments to remove in each transaction (default=100)"
+ print " -f --config <path>: Specify caldavd.plist configuration path"
+ print " -h --help: print this help and exit"
+ print " -n --dry-run: only calculate how many attachments to purge"
+ print " -v --verbose: print progress information"
+ print ""
+
+ if e:
+ sys.stderr.write("%s\n" % (e,))
+ sys.exit(64)
+ else:
+ sys.exit(0)
+
def usage_purge_principal(e=None):
name = os.path.basename(sys.argv[0])
@@ -111,6 +132,28 @@
reactor.stop()
+class PurgeOrphanedAttachmentsService(Service):
+
+ batchSize = None
+ dryrun = False
+ verbose = False
+
+ def __init__(self, store):
+ self._store = store
+
+ @inlineCallbacks
+ def startService(self):
+ try:
+ (yield purgeOrphanedAttachments(self._store, self.batchSize,
+ verbose=self.verbose, dryrun=self.dryrun))
+ except Exception, e:
+ print "Error:", e
+ raise
+
+ finally:
+ reactor.stop()
+
+
class PurgePrincipalService(Service):
guids = None
@@ -234,6 +277,68 @@
)
+def main_purge_orphaned_attachments():
+
+ try:
+ (optargs, args) = getopt(
+ sys.argv[1:], "d:b:f:hnv", [
+ "batch=",
+ "dry-run",
+ "config=",
+ "help",
+ "verbose",
+ ],
+ )
+ except GetoptError, e:
+ usage_purge_orphaned_attachments(e)
+
+ #
+ # Get configuration
+ #
+ configFileName = None
+ batchSize = 100
+ dryrun = False
+ verbose = False
+
+ for opt, arg in optargs:
+ if opt in ("-h", "--help"):
+ usage_purge_orphaned_attachments()
+
+ elif opt in ("-b", "--batch"):
+ try:
+ batchSize = int(arg)
+ except ValueError, e:
+ print "Invalid value for --batch: %s" % (arg,)
+ usage_purge_orphaned_attachments(e)
+
+ elif opt in ("-v", "--verbose"):
+ verbose = True
+
+ elif opt in ("-n", "--dry-run"):
+ dryrun = True
+
+ elif opt in ("-f", "--config"):
+ configFileName = arg
+
+ else:
+ raise NotImplementedError(opt)
+
+ if args:
+ usage_purge_orphaned_attachments("Too many arguments: %s" % (args,))
+
+ if dryrun:
+ verbose = True
+
+ PurgeOrphanedAttachmentsService.batchSize = batchSize
+ PurgeOrphanedAttachmentsService.dryrun = dryrun
+ PurgeOrphanedAttachmentsService.verbose = verbose
+
+ shared_main(
+ configFileName,
+ PurgeOrphanedAttachmentsService,
+ )
+
+
def main_purge_principals():
try:
@@ -342,73 +447,54 @@
returnValue(totalRemoved)
- """
- oldEvents = (yield store.eventsOlderThan(date))
+ at inlineCallbacks
+def purgeOrphanedAttachments(store, batchSize, verbose=False, dryrun=False):
- request = FakeRequest(root, None, None)
- request.checkedSACL = True
- eventCount = 0
- prevHomeName = None
+ if dryrun:
+ if verbose:
+ print "(Dry run) Searching for orphaned attachments..."
+ txn = store.newTransaction(label="Find orphaned attachments")
+ orphans = (yield txn.orphanedAttachments())
+ orphanCount = len(orphans)
+ if verbose:
+ if orphanCount == 0:
+ print "No orphaned attachments"
+ elif orphanCount == 1:
+ print "1 orphaned attachment"
+ else:
+ print "%d orphaned attachments" % (eventCount,)
+ returnValue(orphanCount)
- for homeName, calendarName, eventName, maxDate in oldEvents:
- if homeName != prevHomeName:
+ if verbose:
+ print "Removing orphaned attachments..."
- # Retrieve the calendar home
- record = directory.recordWithGUID(homeName)
- if record is None:
- # The user has already been removed from the directory service. We
- # need to fashion a temporary, fake record
+ numOrphansRemoved = -1
+ totalRemoved = 0
+ while numOrphansRemoved:
+ txn = store.newTransaction(label="Remove orphaned attachments")
+ numOrphansRemoved = (yield txn.removeOrphanedAttachments(batchSize=batchSize))
+ (yield txn.commit())
+ if numOrphansRemoved:
+ totalRemoved += numOrphansRemoved
+ if verbose:
+ print "%d," % (totalRemoved,),
- # FIXME: probaby want a more elegant way to accomplish this,
- # since it requires the aggregate directory to examine these first:
- record = DirectoryRecord(directory, "users", homeName, shortNames=(homeName,),
- enabledForCalendaring=True)
- record.enabled = True
- directory._tmpRecords["shortNames"][homeName] = record
- directory._tmpRecords["guids"][homeName] = record
+ if verbose:
+ print
+ if totalRemoved == 0:
+ print "No orphaned attachments were removed"
+ elif totalRemoved == 1:
+ print "1 orphaned attachment was removed in total"
+ else:
+ print "%d orphaned attachments were removed in total" % (totalRemoved,)
- principalCollection = directory.principalCollection
- principal = principalCollection.principalForRecord(record)
- calendarHome = (yield principal.calendarHome(request))
- prevHomeName = homeName
+ returnValue(totalRemoved)
- eventCount += 1
- if verbose:
- print "%s %s/%s/%s ends: %s" % (record.shortNames[0], homeName,
- calendarName, eventName, maxDate)
- if not dryrun:
- calendar = (yield calendarHome.getChild(calendarName))
- event = (yield calendar.getChild(eventName))
- uri = "/calendars/__uids__/%s/%s/%s" % (
- homeName,
- calendarName,
- eventName
- )
- request.authnUser = request.authzUser = davxml.Principal(
- davxml.HRef.fromString("/principals/__uids__/%s/" % (homeName,))
- )
- request.path = uri
- request._rememberResource(event, uri)
- if verbose:
- print "Deleting %s/%s/%s" % (homeName, calendarName, eventName)
- result = (yield event.storeRemove(request, True, uri))
- if result != NO_CONTENT:
- print "Error deleting %s/%s/%s: %s" % (homeName, calendarName,
- eventName, result)
- if eventCount and not dryrun:
- if verbose:
- print "Committing changes"
- txn = request._newStoreTransaction
- (yield txn.commit())
- returnValue(eventCount)
- """
-
-
@inlineCallbacks
def purgeGUIDs(directory, root, guids, verbose=False, dryrun=False):
total = 0
Modified: CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py 2011-02-02 01:09:18 UTC (rev 6846)
+++ CalendarServer/trunk/calendarserver/tools/test/test_purge_old_events.py 2011-02-02 02:05:44 UTC (rev 6847)
@@ -19,12 +19,13 @@
"""
from twisted.trial import unittest
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twext.web2.http_headers import MimeType
from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
from calendarserver.tap.util import getRootResource
-from calendarserver.tools.purge import purgeOldEvents, purgeGUID
+from calendarserver.tools.purge import purgeOldEvents, purgeGUID, purgeOrphanedAttachments
from twistedcaldav.config import config
from vobject.icalendar import utc
@@ -81,6 +82,56 @@
END:VCALENDAR
""".replace("\n", "\r\n")
+OLD_ATTACHMENT_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYMONTH=10;BYDAY=-1SU
+DTSTART:19621028T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYMONTH=4;BYDAY=1SU
+DTSTART:19870405T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:57A5D1F6-9A57-4F74-9520-25C617F54B88
+TRANSP:OPAQUE
+SUMMARY:Ancient event with attachment
+DTSTART;TZID=US/Pacific:20000308T111500
+DTEND;TZID=US/Pacific:20000308T151500
+DTSTAMP:20100303T181220Z
+X-APPLE-DROPBOX:/calendars/__uids__/user01/dropbox/57A5D1F6-9A57-4F74-95
+ 20-25C617F54B88.dropbox
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
ENDLESS_ICS = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.1//EN
@@ -260,6 +311,7 @@
"calendar1" : {
"old.ics" : OLD_ICS,
"endless.ics" : ENDLESS_ICS,
+ "oldattachment.ics" : OLD_ATTACHMENT_ICS,
}
},
"home2" : {
@@ -303,6 +355,7 @@
self.assertEquals(results,
[
['home1', 'calendar1', 'old.ics', '2000-03-07 23:15:00'],
+ ['home1', 'calendar1', 'oldattachment.ics', '2000-03-08 23:15:00'],
['home2', 'calendar3', 'repeating_awhile.ics', '2002-03-09 23:15:00'],
['home2', 'calendar2', 'recent.ics', '2010-03-04 22:15:00'],
]
@@ -327,6 +380,7 @@
results = (yield txn.eventsOlderThan(cutoff))
self.assertEquals(results,
[
+ ['home1', 'calendar1', 'oldattachment.ics', '2000-03-08 23:15:00'],
['home2', 'calendar3', 'repeating_awhile.ics', '2002-03-09 23:15:00'],
['home2', 'calendar2', 'recent.ics', '2010-03-04 22:15:00'],
]
@@ -334,7 +388,7 @@
# Remove remaining oldest events
count = (yield txn.removeOldEvents(cutoff))
- self.assertEquals(count, 2)
+ self.assertEquals(count, 3)
results = (yield txn.eventsOlderThan(cutoff))
self.assertEquals(results, [ ])
@@ -342,7 +396,64 @@
count = (yield txn.removeOldEvents(cutoff))
self.assertEquals(count, 0)
+
@inlineCallbacks
+ def _addAttachment(self):
+
+ txn = self._sqlCalendarStore.newTransaction()
+
+ # Create an event with an attachment
+ home = (yield txn.calendarHomeWithUID("home1"))
+ calendar = (yield home.calendarWithName("calendar1"))
+ event = (yield calendar.calendarObjectWithName("oldattachment.ics"))
+ attachment = (yield event.createAttachmentWithName("oldattachment.ics"))
+ t = attachment.store(MimeType("text", "x-fixture"))
+ t.write("old attachment")
+ t.write(" text")
+ (yield t.loseConnection())
+ (yield txn.commit())
+
+ returnValue(attachment)
+
+ @inlineCallbacks
+ def test_removeOrphanedAttachments(self):
+ attachment = (yield self._addAttachment())
+ txn = self._sqlCalendarStore.newTransaction()
+ attachmentPath = attachment._path.path
+ self.assertTrue(os.path.exists(attachmentPath))
+
+ orphans = (yield txn.orphanedAttachments())
+ self.assertEquals(len(orphans), 0)
+
+ count = (yield txn.removeOrphanedAttachments(batchSize=100))
+ self.assertEquals(count, 0)
+
+ # File still exists
+ self.assertTrue(os.path.exists(attachmentPath))
+
+ # Delete all old events (including the event containing the attachment)
+ cutoff = datetime.datetime(2010, 4, 1)
+ count = (yield txn.removeOldEvents(cutoff))
+
+ # Just look for orphaned attachments but don't delete
+ orphans = (yield txn.orphanedAttachments())
+ self.assertEquals(len(orphans), 1)
+
+ # Remove orphaned attachments, should be 1
+ count = (yield txn.removeOrphanedAttachments(batchSize=100))
+ self.assertEquals(count, 1)
+
+ # Remove orphaned attachments, shouldn't be any
+ count = (yield txn.removeOrphanedAttachments())
+ self.assertEquals(count, 0)
+
+ # File isn't actually removed until after commit
+ (yield txn.commit())
+
+ # Verify the file itself is gone
+ self.assertFalse(os.path.exists(attachmentPath))
+
+ @inlineCallbacks
def test_purgeOldEvents(self):
self.patch(config.DirectoryService.params, "xmlFile",
os.path.join(
@@ -362,12 +473,12 @@
total = (yield purgeOldEvents(self._sqlCalendarStore, directory,
rootResource, datetime.datetime(2010, 4, 1), 2, dryrun=True,
verbose=False))
- self.assertEquals(total, 3)
+ self.assertEquals(total, 4)
# Actually remove
total = (yield purgeOldEvents(self._sqlCalendarStore, directory,
rootResource, datetime.datetime(2010, 4, 1), 2, verbose=False))
- self.assertEquals(total, 3)
+ self.assertEquals(total, 4)
# There should be no more left
total = (yield purgeOldEvents(self._sqlCalendarStore, directory,
@@ -393,3 +504,43 @@
verbose=False, proxies=False,
when=datetime.datetime(2010, 4, 1, 12, 0, 0, 0, utc)))
self.assertEquals(total, 1)
+
+
+ @inlineCallbacks
+ def test_purgeOrphanedAttachments(self):
+
+ (yield self._addAttachment())
+
+ self.patch(config.DirectoryService.params, "xmlFile",
+ os.path.join(
+ os.path.dirname(__file__), "purge", "accounts.xml"
+ )
+ )
+ self.patch(config.ResourceService.params, "xmlFile",
+ os.path.join(
+ os.path.dirname(__file__), "purge", "resources.xml"
+ )
+ )
+ self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
+ rootResource = getRootResource(config, self._sqlCalendarStore)
+ directory = rootResource.getDirectory()
+
+ # Remove old events first
+ total = (yield purgeOldEvents(self._sqlCalendarStore, directory,
+ rootResource, datetime.datetime(2010, 4, 1), 2, verbose=False))
+ self.assertEquals(total, 4)
+
+ # Dry run
+ total = (yield purgeOrphanedAttachments(self._sqlCalendarStore, 2,
+ dryrun=True, verbose=False))
+ self.assertEquals(total, 1)
+
+ # Actually remove
+ total = (yield purgeOrphanedAttachments(self._sqlCalendarStore, 2,
+ dryrun=False, verbose=False))
+ self.assertEquals(total, 1)
+
+ # There should be no more left
+ total = (yield purgeOrphanedAttachments(self._sqlCalendarStore, 2,
+ dryrun=False, verbose=False))
+ self.assertEquals(total, 0)
Added: CalendarServer/trunk/doc/calendarserver_purge_attachments.8
===================================================================
--- CalendarServer/trunk/doc/calendarserver_purge_attachments.8 (rev 0)
+++ CalendarServer/trunk/doc/calendarserver_purge_attachments.8 2011-02-02 02:05:44 UTC (rev 6847)
@@ -0,0 +1,54 @@
+.\"
+.\" Copyright (c) 2006-2011 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.
+.\"
+.\" The following requests are required for all man pages.
+.Dd June 17, 2009
+.Dt CALENDARSERVER_PURGE_ATTACHMENTS 8
+.Os
+.Sh NAME
+.Nm calendarserver_purge_attachments
+.Nd Darwin Calendar Server orphaned attachments clean-up utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl -config Ar file
+.Op Fl -dry-run
+.Op Fl -verbose
+.Op Fl -help
+.Sh DESCRIPTION
+.Nm
+is a tool for removing attachments that are no longer referenced by any calendar events.
+.Pp
+.Nm
+should be run as a user with the same priviledges as the Calendar
+Server itself, as it needs to read and write data that belongs to the
+server.
+.Sh OPTIONS
+.Bl -tag -width flag
+.It Fl h, -help
+Display usage information
+.It Fl f, -config Ar FILE
+Use the Calendar Server configuration specified in the given file. Defaults to /etc/caldavd/caldavd.plist.
+.It Fl n, -dry-run
+Calculate and display how many orphaned attachments would be removed, but don't actually remove them.
+.It Fl v, -verbose
+Print progress information.
+.El
+.Sh FILES
+.Bl -tag -width flag
+.It /etc/caldavd/caldavd.plist
+The Calendar Server configuration file.
+.El
+.Sh SEE ALSO
+.Xr caldavd 8
Modified: CalendarServer/trunk/doc/calendarserver_purge_events.8
===================================================================
--- CalendarServer/trunk/doc/calendarserver_purge_events.8 2011-02-02 01:09:18 UTC (rev 6846)
+++ CalendarServer/trunk/doc/calendarserver_purge_events.8 2011-02-02 02:05:44 UTC (rev 6847)
@@ -44,7 +44,7 @@
.It Fl n, -dry-run
Calculate and display how many events would be removed, but don't actually remove them.
.It Fl v, -verbose
-Print progress information including purged events per calendar home.
+Print progress information.
.It Fl d, -days Ar NUMBER
Specify how many days in the past to retain. Defaults to 365 days.
.El
Modified: CalendarServer/trunk/setup.py
===================================================================
--- CalendarServer/trunk/setup.py 2011-02-02 01:09:18 UTC (rev 6846)
+++ CalendarServer/trunk/setup.py 2011-02-02 02:05:44 UTC (rev 6847)
@@ -121,6 +121,7 @@
"bin/calendarserver_manage_principals",
"bin/calendarserver_command_gateway",
"bin/calendarserver_purge_events",
+ "bin/calendarserver_purge_attachments",
"bin/calendarserver_purge_principals",
"bin/calendarserver_migrate_resources",
],
Modified: CalendarServer/trunk/support/Makefile.Apple
===================================================================
--- CalendarServer/trunk/support/Makefile.Apple 2011-02-02 01:09:18 UTC (rev 6846)
+++ CalendarServer/trunk/support/Makefile.Apple 2011-02-02 02:05:44 UTC (rev 6847)
@@ -88,6 +88,7 @@
$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_migrate_resources.8" "$(DSTROOT)$(MANDIR)/man8"
$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_command_gateway.8" "$(DSTROOT)$(MANDIR)/man8"
$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_purge_events.8" "$(DSTROOT)$(MANDIR)/man8"
+ $(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_purge_attachments.8" "$(DSTROOT)$(MANDIR)/man8"
$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_purge_principals.8" "$(DSTROOT)$(MANDIR)/man8"
$(_v) gzip -9 -f "$(DSTROOT)$(MANDIR)/man8/"*.[0-9]
@echo "Installing launchd config..."
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2011-02-02 01:09:18 UTC (rev 6846)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2011-02-02 02:05:44 UTC (rev 6847)
@@ -258,6 +258,9 @@
"""
Return up to the oldest batchSize events which exist completely earlier
than "cutoff" (datetime)
+
+ Returns a deferred to a list of (uid, calendarName, eventName, maxDate)
+ tuples.
"""
query = """
@@ -308,8 +311,51 @@
count += 1
returnValue(count)
+ def orphanedAttachments(self, batchSize=None):
+ """
+ Find attachments no longer referenced by any events.
+ Returns a deferred to a list of (dropbox_id, path) tuples.
+ """
+ query = """
+ select
+ at.DROPBOX_ID,
+ at.PATH
+ from
+ ATTACHMENT at
+ left outer join
+ CALENDAR_OBJECT co
+ on
+ at.DROPBOX_ID = co.DROPBOX_ID
+ where
+ co.DROPBOX_ID is null
+ """
+ args = []
+ if batchSize is not None:
+ query += "limit %s"
+ args.append(batchSize)
+
+ return self.execSQL(query, args)
+
+ @inlineCallbacks
+ def removeOrphanedAttachments(self, batchSize=None):
+ """
+ Remove attachments that no longer have any references to them
+ """
+
+ # TODO: see if there is a better way to import Attachment
+ from txdav.caldav.datastore.sql import Attachment
+
+ results = (yield self.orphanedAttachments(batchSize=batchSize))
+ count = 0
+ for dropboxID, path in results:
+ attachment = Attachment(self, dropboxID, path)
+ (yield attachment.remove( ))
+ count += 1
+ returnValue(count)
+
+
class CommonHome(LoggingMixIn):
# All these need to be initialized by derived classes for each store type
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110201/db76ad1b/attachment-0001.html>
More information about the calendarserver-changes
mailing list