[CalendarServer-changes] [7526] CalendarServer/branches/users/cdaboo/timezones
source_changes at macosforge.org
source_changes at macosforge.org
Wed May 25 11:46:31 PDT 2011
Revision: 7526
http://trac.macosforge.org/projects/calendarserver/changeset/7526
Author: cdaboo at apple.com
Date: 2011-05-25 11:46:30 -0700 (Wed, 25 May 2011)
Log Message:
-----------
Add a separate tool for managing the timezone db. Support aliases. Hook up the secondary service into actual caldavd.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/util.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/stdconfig.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/timezones.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/timezonestdservice.py
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_manage_timezones
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/managetimezones.py
Added: CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_manage_timezones
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_manage_timezones (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_manage_timezones 2011-05-25 18:46:30 UTC (rev 7526)
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 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, _ignore_stderr = child.communicate()
+
+ path = path.rstrip("\n")
+
+ if child.wait() == 0:
+ sys.path[0:0] = path.split(":")
+
+ from calendarserver.tools.managetimezones import main
+ main()
Property changes on: CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_manage_timezones
___________________________________________________________________
Added: svn:executable
+ *
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/util.py 2011-05-25 18:45:21 UTC (rev 7525)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/util.py 2011-05-25 18:46:30 UTC (rev 7526)
@@ -526,6 +526,12 @@
root,
)
root.putChild("stdtimezones", timezoneStdService)
+
+ # TODO: we only want the master to do this
+ if _reactor._started:
+ _reactor.callLater(0, timezoneStdService.onStartup)
+ else:
+ addSystemEventTrigger("after", "startup", timezoneStdService.onStartup)
# iSchedule service is optional
if config.Scheduling.iSchedule.Enabled:
Added: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/managetimezones.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/managetimezones.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/managetimezones.py 2011-05-25 18:46:30 UTC (rev 7526)
@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 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.
+##
+
+from twistedcaldav.timezonestdservice import PrimaryTimezoneDatabase,\
+ SecondaryTimezoneDatabase
+from sys import stdout, stderr
+import getopt
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+from twisted.python.log import addObserver, removeObserver
+import sys
+import os
+
+def _doPrimaryActions(action, tzpath, xmlfile, changed):
+
+ tzdb = PrimaryTimezoneDatabase(tzpath, xmlfile)
+ if action == "create":
+ print "Creating new XML file at: %s" % (xmlfile, )
+ tzdb.createNewDatabase()
+ print "Current total: %d" % (len(tzdb.timezones), )
+
+ elif action == "update":
+ print "Updating XML file at: %s" % (xmlfile, )
+ tzdb.readDatabase()
+ tzdb.updateDatabase()
+ print "Current total: %d" % (len(tzdb.timezones), )
+ print "Total Changed: %d" % (tzdb.changeCount, )
+ if tzdb.changeCount:
+ print "Changed:"
+ for k in sorted(tzdb.changed):
+ print " %s" % (k, )
+
+ elif action == "list":
+ print "Listing XML file at: %s" % (xmlfile, )
+ tzdb.readDatabase()
+ print "Current timestamp: %s" % (tzdb.dtstamp, )
+ print "Timezones:"
+ for k in sorted(tzdb.timezones.keys()):
+ print " %s" % (k, )
+
+ elif action == "changed":
+ print "Changes from XML file at: %s" % (xmlfile, )
+ tzdb.readDatabase()
+ print "Check timestamp: %s" % (changed, )
+ print "Current timestamp: %s" % (tzdb.dtstamp, )
+ results = [k for k, v in tzdb.timezones.items() if v.dtstamp > changed]
+ print "Total Changed: %d" % (len(results), )
+ if results:
+ print "Changed:"
+ for k in sorted(results):
+ print " %s" % (k, )
+ else:
+ usage("Invalid action: %s" % (action, ))
+
+class StandardIOObserver (object):
+ """
+ Log observer that writes to standard I/O.
+ """
+ def emit(self, eventDict):
+ text = None
+
+ if eventDict["isError"]:
+ output = stderr
+ if "failure" in eventDict:
+ text = eventDict["failure"].getTraceback()
+ else:
+ output = stdout
+
+ if not text:
+ text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
+
+ output.write(text)
+ output.flush()
+
+ def start(self):
+ addObserver(self.emit)
+
+ def stop(self):
+ removeObserver(self.emit)
+
+ at inlineCallbacks
+def _runInReactor(tzdb):
+
+ try:
+ new, changed = yield tzdb.syncWithServer()
+ print "New: %d" % (new, )
+ print "Changed: %d" % (changed, )
+ print "Current total: %d" % (len(tzdb.timezones), )
+ except Exception, e:
+ print "Could not sync with server: %s" % (str(e),)
+ finally:
+ reactor.stop()
+
+def _doSecondaryActions(action, tzpath, xmlfile, url):
+
+ tzdb = SecondaryTimezoneDatabase(tzpath, xmlfile, url)
+ try:
+ tzdb.readDatabase()
+ except:
+ pass
+ if action == "cache":
+ print "Caching from secondary server: %s" % (url, )
+
+ observer = StandardIOObserver()
+ observer.start()
+ reactor.callLater(0, _runInReactor, tzdb)
+ reactor.run()
+ else:
+ usage("Invalid action: %s" % (action, ))
+
+def usage(error_msg=None):
+ if error_msg:
+ print error_msg
+ print
+
+ print """Usage: timezonestdservice [options]
+Options:
+ -h Print this help and exit
+ -f XML file path
+ -z zoneinfo file path
+
+ # Primary service
+ --create create new XML file
+ --update update XML file
+ --list list timezones in XML file
+ --changed changed since timestamp
+
+ # Secondary service
+ --url URL or domain of service
+ --cache Cache data from service
+
+Description:
+ This utility will create, update or list an XML timezone database
+ summary file.
+
+"""
+
+ if error_msg:
+ raise ValueError(error_msg)
+ else:
+ sys.exit(0)
+
+def main():
+ primary = False
+ secondary = False
+ action = None
+ tzpath = None
+ xmlfile = None
+ changed = None
+ url = None
+
+ # Get options
+ options, _ignore_args = getopt.getopt(
+ sys.argv[1:],
+ "hf:z:",
+ [
+ "create",
+ "update",
+ "list",
+ "changed=",
+ "url=",
+ "cache",
+ ]
+ )
+
+ for option, value in options:
+ if option == "-h":
+ usage()
+ elif option == "-f":
+ xmlfile = value
+ elif option == "-z":
+ tzpath = value
+ elif option == "--create":
+ action = "create"
+ primary = True
+ elif option == "--update":
+ action = "update"
+ primary = True
+ elif option == "--list":
+ action = "list"
+ primary = True
+ elif option == "--changed":
+ action = "changed"
+ primary = True
+ changed = value
+ elif option == "--url":
+ url = value
+ secondary = True
+ elif option == "--cache":
+ action = "cache"
+ secondary = True
+ else:
+ usage("Unrecognized option: %s" % (option,))
+
+ if action is None:
+ action = "list"
+ primary = True
+ if tzpath is None:
+ try:
+ import pkg_resources
+ except ImportError:
+ tzpath = os.path.join(os.path.dirname(__file__), "zoneinfo")
+ else:
+ tzpath = pkg_resources.resource_filename("twistedcaldav", "zoneinfo") #@UndefinedVariable
+ if xmlfile is None:
+ xmlfile = os.path.join(tzpath, "timezones.xml")
+
+ if primary and not os.path.isdir(tzpath):
+ usage("Invalid zoneinfo path: %s" % (tzpath,))
+ if primary and not os.path.isfile(xmlfile) and action != "create":
+ usage("Invalid XML file path: %s" % (xmlfile,))
+
+ if primary and secondary:
+ usage("Cannot use primary and secondary options together")
+
+ if primary:
+ _doPrimaryActions(action, tzpath, xmlfile, changed)
+ else:
+ _doSecondaryActions(action, tzpath, xmlfile, url)
+
+if __name__ == '__main__':
+ main()
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/managetimezones.py
___________________________________________________________________
Added: svn:executable
+ *
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/stdconfig.py 2011-05-25 18:45:21 UTC (rev 7525)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/stdconfig.py 2011-05-25 18:46:30 UTC (rev 7526)
@@ -453,7 +453,8 @@
"BasePath" : None, # Path to zoneinfo - if None use default package path
# secondary service MUST define its own writeable path
"XMLInfoPath" : None, # Path to db cache info - if None use default package path
- # secondary service MUST define its own writeable path
+ # secondary service MUST define its own writeable path if
+ # not None
"SecondaryService" : {
# Only one of these should be used when a secondary service is used
@@ -824,6 +825,7 @@
("ServerRoot", "RunRoot"),
("DataRoot", "DatabaseRoot"),
("DataRoot", "AttachmentsRoot"),
+ ("DataRoot", ("TimezoneService", "BasePath",)),
("ConfigRoot", "SudoersFile"),
("LogRoot", "AccessLogFile"),
("LogRoot", "ErrorLogFile"),
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/timezones.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/timezones.py 2011-05-25 18:45:21 UTC (rev 7525)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/timezones.py 2011-05-25 18:46:30 UTC (rev 7526)
@@ -94,6 +94,14 @@
readVTZ(tzid)
return True
+def addVTZ(tzid, tzcal):
+ """
+ Add a VTIMEZONE component to the cache.
+ """
+ if tzid not in cachedVTZs:
+ cachedVTZs[tzid] = tzcal
+ cachedTZs[tzid] = str(tzcal)
+
def readVTZ(tzid):
"""
Try to load the specified TZID as a calendar object from the database. Raise if not found.
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/timezonestdservice.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/timezonestdservice.py 2011-05-25 18:45:21 UTC (rev 7525)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/timezonestdservice.py 2011-05-25 18:46:30 UTC (rev 7526)
@@ -37,9 +37,7 @@
from twext.web2.http_headers import MimeType
from twext.web2.stream import MemoryStream
-from twisted.internet import reactor
from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from twisted.python.log import addObserver, removeObserver
from twistedcaldav import timezonexml, xmlutil
from twistedcaldav.client.geturl import getURL
@@ -49,19 +47,17 @@
DAVResourceWithoutChildrenMixin
from twistedcaldav.ical import tzexpandlocal
from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
-from twistedcaldav.timezones import TimezoneException, TimezoneCache, readVTZ
+from twistedcaldav.timezones import TimezoneException, TimezoneCache, readVTZ,\
+ addVTZ
from twistedcaldav.xmlutil import addSubElement, readXMLString
from pycalendar.calendar import PyCalendar
from pycalendar.datetime import PyCalendarDateTime
from pycalendar.exceptions import PyCalendarInvalidData
-from sys import stdout, stderr
-import getopt
import hashlib
import itertools
import os
-import sys
log = Logger()
@@ -72,6 +68,7 @@
Extends L{DAVResource} to provide timezone service functionality.
"""
+
def __init__(self, parent):
"""
@param parent: the parent resource of this one.
@@ -83,6 +80,16 @@
self.parent = parent
self.expandcache = {}
+ if config.TimezoneService.Mode == "primary":
+ log.info("Using primary Timezone Service")
+ self._initPrimaryService()
+ elif config.TimezoneService.Mode == "secondary":
+ log.info("Using secondary Timezone Service")
+ self._initSecondaryService()
+ else:
+ raise ValueError("Invalid TimezoneService mode: %s" % (config.TimezoneService.Mode,))
+
+ def _initPrimaryService(self):
tzpath = TimezoneCache.getDBPath()
xmlfile = os.path.join(tzpath, "timezones.xml")
self.timezones = PrimaryTimezoneDatabase(tzpath, xmlfile)
@@ -91,6 +98,22 @@
else:
self.timezones.readDatabase()
+ def _initSecondaryService(self):
+
+ # Must have writeable paths
+ tzpath = TimezoneCache.getDBPath()
+ xmlfile = config.TimezoneService.XMLInfoPath
+ if not xmlfile:
+ xmlfile = os.path.join(tzpath, "timezones.xml")
+ self.timezones = SecondaryTimezoneDatabase(tzpath, xmlfile, None)
+ try:
+ self.timezones.readDatabase()
+ except:
+ pass
+
+ def onStartup(self):
+ return self.timezones.onStartup()
+
def deadProperties(self):
if not hasattr(self, "_dead_properties"):
self._dead_properties = NonePropertyStore(self)
@@ -285,13 +308,12 @@
timezones = []
- for _ignore, tz in sorted(self.timezones.timezones.items(), key=lambda x:x[0]):
- if changesince and tz.dtstamp <= changesince:
- continue
+ for tz in self.timezones.listTimezones(changesince):
timezones.append(
timezonexml.Summary(
timezonexml.Tzid.fromString(tz.tzid),
timezonexml.LastModified.fromString(tz.dtstamp),
+ *tuple([timezonexml.Alias.fromString(alias) for alias in tz.aliases])
)
)
result = timezonexml.TimezoneList(
@@ -306,18 +328,11 @@
"""
tzids = request.args.get("tzid", ())
- if len(tzids) == 0:
+ if len(tzids) != 1:
raise HTTPError(StatusResponse(
responsecode.BAD_REQUEST,
"Invalid tzid query parameter",
))
- if "*" in tzids:
- if len(tzids) != 1:
- raise HTTPError(StatusResponse(
- responsecode.BAD_REQUEST,
- "Invalid tzid query parameter",
- ))
- tzids = self.timezones.timezones.keys()
format = request.args.get("format", ("text/calendar",))
if len(format) != 1 or format[0] not in ("text/calendar", "text/plain",):
@@ -327,7 +342,14 @@
))
format = format[0]
- calendar = self.timezones.getTimezones(tzids)
+ calendar = self.timezones.getTimezone(tzids[0])
+ if calendar is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.NOT_FOUND,
+ (calendarserver_namespace, "invalid-tzid"),
+ "Tzid could not be found",
+ ))
+
tzdata = calendar.getText()
response = Response()
@@ -341,7 +363,7 @@
"""
tzids = request.args.get("tzid", ())
- if len(tzids) == 0:
+ if len(tzids) != 1:
raise HTTPError(StatusResponse(
responsecode.BAD_REQUEST,
"Invalid tzid query parameter",
@@ -386,30 +408,33 @@
results = []
- for tzid in tzids:
+ tzid = tzids[0]
+ tzdata = self.timezones.getTimezone(tzid)
+ if tzdata is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.NOT_FOUND,
+ (calendarserver_namespace, "invalid-tzid"),
+ "Tzid could not be found",
+ ))
- tzdata = self.timezones.getTimezones((tzid,))
- if tzdata is None:
- continue
+ # Now do the expansion (but use a cache to avoid re-calculating TZs)
+ observances = self.expandcache.get((tzid, start, end), None)
+ if observances is None:
+ observances = tzexpandlocal(tzdata, start, end)
+ self.expandcache[(tzid, start, end)] = observances
- # Now do the expansion (but use a expandcache to avoid re-calculating TZs)
- observances = self.expandcache.get((tzid, start, end), None)
- if observances is None:
- observances = tzexpandlocal(tzdata, start, end)
- self.expandcache[(tzid, start, end)] = observances
-
- # Turn into XML
- results.append(timezonexml.Tzdata(
- timezonexml.Tzid.fromString(tzid),
- *[
- timezonexml.Observance(
- timezonexml.Name(name),
- timezonexml.Onset(onset),
- timezonexml.UTCOffsetFrom(utc_offset_from),
- timezonexml.UTCOffsetTo(utc_offset_to),
- ) for onset, utc_offset_from, utc_offset_to, name in observances
- ]
- ))
+ # Turn into XML
+ results.append(timezonexml.Tzdata(
+ timezonexml.Tzid.fromString(tzid),
+ *[
+ timezonexml.Observance(
+ timezonexml.Name(name),
+ timezonexml.Onset(onset),
+ timezonexml.UTCOffsetFrom(utc_offset_from),
+ timezonexml.UTCOffsetTo(utc_offset_to),
+ ) for onset, utc_offset_from, utc_offset_to, name in observances
+ ]
+ ))
result = timezonexml.Timezones(
timezonexml.Dtstamp.fromString(self.timezones.dtstamp),
@@ -422,8 +447,9 @@
Maintains information from an on-disk store of timezone files.
"""
- def __init__(self, tzid, dtstamp, md5):
+ def __init__(self, tzid, aliases, dtstamp, md5):
self.tzid = tzid
+ self.aliases = aliases
self.dtstamp = dtstamp
self.md5 = md5
@@ -436,8 +462,9 @@
return None
tzid = node.findtext("tzid")
dtstamp = node.findtext("dtstamp")
+ aliases = tuple([alias_node.text for alias_node in node.findall("alias")])
md5 = node.findtext("md5")
- return cls(tzid, dtstamp, md5)
+ return cls(tzid, aliases, dtstamp, md5)
def generateXML(self, parent):
"""
@@ -446,6 +473,8 @@
node = xmlutil.addSubElement(parent, "timezone")
xmlutil.addSubElement(node, "tzid", self.tzid)
xmlutil.addSubElement(node, "dtstamp", self.dtstamp)
+ for alias in self.aliases:
+ xmlutil.addSubElement(node, "alias", alias)
xmlutil.addSubElement(node, "md5", self.md5)
class CommonTimezoneDatabase(object):
@@ -458,7 +487,11 @@
self.xmlfile = xmlfile
self.dtstamp = None
self.timezones = {}
+ self.aliases = {}
+ def onStartup(self):
+ return succeed(None)
+
def readDatabase(self):
"""
Read in XML data.
@@ -470,19 +503,51 @@
tz = TimezoneInfo.readXML(child)
if tz:
self.timezones[tz.tzid] = tz
+ for alias in tz.aliases:
+ self.aliases[alias] = tz.tzid
- def getTimezones(self, tzids):
+ def listTimezones(self, changesince):
"""
- Generate a PyCalendar containing the requested timezones.
+ List timezones (not aliases) possibly changed since a particular dtstamp.
"""
+
+ for tzid, tzinfo in sorted(self.timezones.items(), key=lambda x:x[0]):
+ # Ignore those that are aliases
+ if tzid in self.aliases:
+ continue
+
+ # Detect timestamp changes
+ if changesince and tzinfo.dtstamp <= changesince:
+ continue
+
+ yield tzinfo
+
+ def getTimezone(self, tzid):
+ """
+ Generate a PyCalendar containing the requested timezone.
+ """
+ # We will just use our existing TimezoneCache here
calendar = PyCalendar()
- for tzid in sorted(tzids):
- # We will just use our existing TimezoneCache here
- try:
- vtz = readVTZ(tzid)
- calendar.addComponent(vtz.getComponents()[0].duplicate())
- except TimezoneException:
- pass
+ try:
+ vtz = readVTZ(tzid)
+ calendar.addComponent(vtz.getComponents()[0].duplicate())
+ except TimezoneException:
+
+ # Check if an alias exists and create data for that
+ if tzid in self.aliases:
+ try:
+ vtz = readVTZ(self.aliases[tzid])
+ except TimezoneException:
+ log.error("Failed to find timezone data for alias: %s" % (tzid,))
+ return None
+ else:
+ vtz = vtz.duplicate()
+ vtz.getComponents()[0].getProperties("TZID")[0].setValue(tzid)
+ addVTZ(tzid, vtz)
+ calendar.addComponent(vtz.getComponents()[0].duplicate())
+ else:
+ log.error("Failed to find timezone data for: %s" % (tzid,))
+ return None
return calendar
@@ -493,17 +558,24 @@
for _ignore,v in sorted(self.timezones.items(), key=lambda x:x[0]):
v.generateXML(root)
xmlutil.writeXML(self.xmlfile, root)
+
+ def _buildAliases(self):
+ """
+ Rebuild aliases mappings from current tzinfo.
+ """
+ self.aliases = {}
+ for tzinfo in self.timezones.values():
+ for alias in tzinfo.aliases:
+ self.aliases[alias] = tzinfo.tzid
+
class PrimaryTimezoneDatabase(CommonTimezoneDatabase):
"""
Maintains the database of timezones read from an XML file.
"""
def __init__(self, basepath, xmlfile):
- self.basepath = basepath
- self.xmlfile = xmlfile
- self.dtstamp = None
- self.timezones = {}
+ super(PrimaryTimezoneDatabase, self).__init__(basepath, xmlfile)
def createNewDatabase(self):
"""
@@ -535,7 +607,29 @@
continue
self.changeCount += 1
self.changed.add(tzid)
- self.timezones[tzid] = TimezoneInfo(tzid, self.dtstamp, md5)
+ self.timezones[tzid] = TimezoneInfo(tzid, (), self.dtstamp, md5)
+
+ # Try links (aliases) file
+ try:
+ aliases = open(os.path.join(self.basepath, "links.txt")).read()
+ except IOError, e:
+ log.error("Unable to read links.txt file: %s" % (str(e),))
+ aliases = ""
+
+ try:
+ for alias in aliases.splitlines():
+ alias_from, alias_to = alias.split()
+ tzinfo = self.timezones.get(alias_to)
+ if tzinfo:
+ if alias_from != alias_to:
+ if alias_from not in tzinfo.aliases:
+ tzinfo.aliases += (alias_from,)
+ self.aliases[alias_from] = alias_to
+ else:
+ log.error("Missing alias from '%s' to '%s'" % (alias_from, alias_to,))
+ except ValueError:
+ log.error("Unable to parse links.txt file: %s" % (str(e),))
+
def updateDatabase(self):
"""
@@ -554,17 +648,23 @@
"""
def __init__(self, basepath, xmlfile, uri):
- self.basepath = basepath
- self.xmlfile = xmlfile
+ super(SecondaryTimezoneDatabase, self).__init__(basepath, xmlfile)
self.uri = uri
self.discovered = False
- self.dtstamp = None
- self.timezones = {}
self._url = None
if not os.path.exists(self.basepath):
os.makedirs(self.basepath)
+
+ # Paths need to be writeable
+ if not os.access(basepath, os.W_OK):
+ raise ValueError("Secondary Timezone Service needs writeable zoneinfo path at: %s" % (basepath,))
+ if os.path.exists(xmlfile) and not os.access(xmlfile, os.W_OK):
+ raise ValueError("Secondary Timezone Service needs writeable xmlfile path at: %s" % (xmlfile,))
+ def onStartup(self):
+ return self.syncWithServer()
+
@inlineCallbacks
def syncWithServer(self):
"""
@@ -594,6 +694,7 @@
self.dtstamp = newdtstamp
self._dumpTZs()
+ self._buildAliases()
returnValue((len(newtzids), len(changedtzids),))
@@ -658,7 +759,8 @@
for summary in etroot.findall(timezonexml.Summary.sname()):
tzid = summary.findtext(timezonexml.Tzid.sname())
lastmod = summary.findtext(timezonexml.LastModified.sname())
- timezones[tzid] = TimezoneInfo(tzid, lastmod, None)
+ aliases = tuple([alias_node.text for alias_node in summary.findall(timezonexml.Alias.sname())])
+ timezones[tzid] = TimezoneInfo(tzid, aliases, lastmod, None)
returnValue((dtstamp, timezones,))
@@ -705,213 +807,3 @@
del self.timezones[tzid]
except IOError, e:
log.error("Unable to write calendar file for %s: %s" % (tzid, str(e),))
-
-def _doPrimaryActions(action, tzpath, xmlfile, changed):
-
- tzdb = PrimaryTimezoneDatabase(tzpath, xmlfile)
- if action == "create":
- print "Creating new XML file at: %s" % (xmlfile, )
- tzdb.createNewDatabase()
- print "Current total: %d" % (len(tzdb.timezones), )
-
- elif action == "update":
- print "Updating XML file at: %s" % (xmlfile, )
- tzdb.readDatabase()
- tzdb.updateDatabase()
- print "Current total: %d" % (len(tzdb.timezones), )
- print "Total Changed: %d" % (tzdb.changeCount, )
- if tzdb.changeCount:
- print "Changed:"
- for k in sorted(tzdb.changed):
- print " %s" % (k, )
-
- elif action == "list":
- print "Listing XML file at: %s" % (xmlfile, )
- tzdb.readDatabase()
- print "Current timestamp: %s" % (tzdb.dtstamp, )
- print "Timezones:"
- for k in sorted(tzdb.timezones.keys()):
- print " %s" % (k, )
-
- elif action == "changed":
- print "Changes from XML file at: %s" % (xmlfile, )
- tzdb.readDatabase()
- print "Check timestamp: %s" % (changed, )
- print "Current timestamp: %s" % (tzdb.dtstamp, )
- results = [k for k, v in tzdb.timezones.items() if v.dtstamp > changed]
- print "Total Changed: %d" % (len(results), )
- if results:
- print "Changed:"
- for k in sorted(results):
- print " %s" % (k, )
- else:
- usage("Invalid action: %s" % (action, ))
-
-class StandardIOObserver (object):
- """
- Log observer that writes to standard I/O.
- """
- def emit(self, eventDict):
- text = None
-
- if eventDict["isError"]:
- output = stderr
- if "failure" in eventDict:
- text = eventDict["failure"].getTraceback()
- else:
- output = stdout
-
- if not text:
- text = " ".join([str(m) for m in eventDict["message"]]) + "\n"
-
- output.write(text)
- output.flush()
-
- def start(self):
- addObserver(self.emit)
-
- def stop(self):
- removeObserver(self.emit)
-
- at inlineCallbacks
-def _runInReactor(tzdb):
-
- try:
- new, changed = yield tzdb.syncWithServer()
- print "New: %d" % (new, )
- print "Changed: %d" % (changed, )
- print "Current total: %d" % (len(tzdb.timezones), )
- except Exception, e:
- print "Could not sync with server: %s" % (str(e),)
- finally:
- reactor.stop()
-
-def _doSecondaryActions(action, tzpath, xmlfile, url):
-
- tzdb = SecondaryTimezoneDatabase(tzpath, xmlfile, url)
- try:
- tzdb.readDatabase()
- except:
- pass
- if action == "cache":
- print "Caching from secondary server: %s" % (url, )
-
- observer = StandardIOObserver()
- observer.start()
- reactor.callLater(0, _runInReactor, tzdb)
- reactor.run()
- else:
- usage("Invalid action: %s" % (action, ))
-
-def usage(error_msg=None):
- if error_msg:
- print error_msg
- print
-
- print """Usage: timezonestdservice [options]
-Options:
- -h Print this help and exit
- -v Be verbose
- -f XML file path
- -z zoneinfo file path
-
- # Primary service
- --create create new XML file
- --update update XML file
- --list list timezones in XML file
- --changed changed since timestamp
-
- # Secondary service
- --url URL or domain of service
- --cache Cache data from service
-
-Description:
- This utility will create, update or list an XML timezone database
- summary file.
-
-"""
-
- if error_msg:
- raise ValueError(error_msg)
- else:
- sys.exit(0)
-
-if __name__ == '__main__':
-
- primary = False
- secondary = False
- action = None
- tzpath = None
- xmlfile = None
- changed = None
- url = None
-
- # Get options
- options, args = getopt.getopt(
- sys.argv[1:],
- "hvf:z:",
- [
- "create",
- "update",
- "list",
- "changed=",
- "url=",
- "cache",
- ]
- )
-
- for option, value in options:
- if option == "-h":
- usage()
- elif option == "-v":
- verbose = True
- elif option == "-f":
- xmlfile = value
- elif option == "-z":
- tzpath = value
- elif option == "--create":
- action = "create"
- primary = True
- elif option == "--update":
- action = "update"
- primary = True
- elif option == "--list":
- action = "list"
- primary = True
- elif option == "--changed":
- action = "changed"
- primary = True
- changed = value
- elif option == "--url":
- url = value
- secondary = True
- elif option == "--cache":
- action = "cache"
- secondary = True
- else:
- usage("Unrecognized option: %s" % (option,))
-
- if action is None:
- action = "list"
- primary = True
- if tzpath is None:
- try:
- import pkg_resources
- except ImportError:
- tzpath = os.path.join(os.path.dirname(__file__), "zoneinfo")
- else:
- tzpath = pkg_resources.resource_filename("twistedcaldav", "zoneinfo") #@UndefinedVariable
- xmlfile = os.path.expanduser("~/tz.xml")
-
- if primary and not os.path.isdir(tzpath):
- usage("Invalid zoneinfo path: %s" % (tzpath,))
- if primary and not os.path.isfile(xmlfile) and action != "create":
- usage("Invalid XML file path: %s" % (xmlfile,))
-
- if primary and secondary:
- usage("Cannot use primary and secondary options together")
-
- if primary:
- _doPrimaryActions(action, tzpath, xmlfile, changed)
- else:
- _doSecondaryActions(action, tzpath, xmlfile, url)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110525/033e2ab8/attachment-0001.html>
More information about the calendarserver-changes
mailing list