Commit by: jeffrey
Modified files:
chandler/parcels/osaf/framework/sharing/ICalendar.py 1.2 1.3
chandler/parcels/osaf/views/main/Main.py 1.104 1.105
chandler/parcels/osaf/framework/sharing/tests/TestImportICalendar.py 1.4 1.5

Log message:
- Improved ICalendarFormat's import code to use timezones and recurrence
- Changed Import menu to use ICalendarFormat
- Added a bit to the ImportICalendar test case
- Got exportProcess working for ICalendarFormat

ViewCVS links:
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/sharing/ICalendar.py.diff?r1=text&tr1=1.2&r2=text&tr2=1.3
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/views/main/Main.py.diff?r1=text&tr1=1.104&r2=text&tr2=1.105
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/sharing/tests/TestImportICalendar.py.diff?r1=text&tr1=1.4&r2=text&tr2=1.5

Index: chandler/parcels/osaf/framework/sharing/ICalendar.py
diff -u chandler/parcels/osaf/framework/sharing/ICalendar.py:1.2 
chandler/parcels/osaf/framework/sharing/ICalendar.py:1.3
--- chandler/parcels/osaf/framework/sharing/ICalendar.py:1.2    Thu Jan 20 
00:49:34 2005
+++ chandler/parcels/osaf/framework/sharing/ICalendar.py        Sun Jan 23 
22:55:32 2005
@@ -7,16 +7,87 @@
 import vobject
 import logging
 import mx
+import dateutil.tz
+import datetime
+import itertools
 
 logger = logging.getLogger('ICalendar')
 logger.setLevel(logging.INFO)
 
+localtime = dateutil.tz.tzlocal()
+utc = dateutil.tz.tzutc()
+
+MAXRECUR = 10
+
+def convertToMX(dt, tz=None):
+    """Convert the given datetime into an mxDateTime.
+    
+    Convert dt to tz if it has a timezone.  tz defaults to localtime.
+    
+    >>> import datetime, mx.DateTime
+    >>> dt = datetime.datetime(2004, 12, 20, 18, tzinfo=utc)
+    >>> mxdt = convertToMX(dt, pacific)
+    >>> print mxdt
+    2004-12-20 10:00:00.00
+    >>> type(mxdt)
+    <type 'DateTime'>
+    
+    """
+    if not tz: tz = localtime
+    if getattr(dt, 'tzinfo', None): dt = dt.astimezone(tz)
+    return mx.DateTime.mktime(dt.timetuple())
+
+def convertToUTC(dt, tz = None):
+    """Convert the given mxDateTime (without tz) into datetime with tzinfo=UTC.
+    
+    >>> import datetime, mx.DateTime
+    >>> mxdt = mx.DateTime.DateTime(2004, 12, 20, 12)
+    >>> dt = convertToUTC(mxdt, pacific)
+    >>> print dt
+    2004-12-20 20:00:00+00:00
+    
+    """
+    if not tz: tz = localtime
+    args = (dt.year, dt.month, dt.day, dt.hour, dt.minute, int(dt.second))
+    return datetime.datetime(*args).replace(tzinfo=tz).astimezone(utc)
+
+def eventsToVObject(items, cal=None):
+    """Iterate through items, add to cal, create a new vcalendar if needed.
+
+    Chandler doesn't do recurrence yet, so for now we don't worry
+    about timezones.
+
+    """
+    if cal is None:
+        cal = vobject.iCalendar()
+    for event in items:
+        vevent = cal.add('vevent')
+        vevent.add('uid').value = unicode(event.itsUUID)
+        try:
+            vevent.add('summary').value = event.displayName
+        except AttributeError:
+            pass
+        try:
+            vevent.add('dtstart').value = convertToUTC(event.startTime)
+        except AttributeError:
+            pass
+        try:
+            vevent.add('dtend').value = convertToUTC(event.endTime)
+        except AttributeError:
+            pass
+        try:
+            vevent.add('description').value = event.body.getReader().read()
+        except AttributeError:
+            pass
+    return cal
+
 class ICalendarFormat(Sharing.ImportExportFormat):
     myKindID = None
     myKindPath = "//parcels/osaf/framework/sharing/ICalendarFormat"
 
     __calendarEventPath = "//parcels/osaf/contentmodel/calendar/CalendarEvent"
-
+    __lobPath = "//Schema/Core/Lob"
+    
     def fileStyle(self):
         return self.STYLE_SINGLE
 
@@ -44,12 +115,13 @@
             #@@@MOR Raise something
 
         # @@@MOR Total hack
-        newtext = []
-        for c in text:
-            if ord(c) > 127:
-                c = " "
-            newtext.append(c)
-        text = "".join(newtext)
+        # this shouldn't be necessary anymore
+        #newtext = []
+        #for c in text:
+        #    if ord(c) > 127:
+        #        c = " "
+        #    newtext.append(c)
+        #text = "".join(newtext)
 
         input = StringIO.StringIO(text)
         calendar = vobject.readComponents(input, validate=True).next()
@@ -57,51 +129,108 @@
         countNew = 0
         countUpdated = 0
         eventKind = self.itsView.findPath(self.__calendarEventPath)
-
+        textKind  = self.itsView.findPath(self.__lobPath)
+        
         for event in calendar.vevent:
             # See if we have a corresponding item already, or create one
             uuid = UUID(event.uid[0].value[:36]) # @@@MOR, stripping "-RID"
-            eventItem = self.itsView.findUUID(uuid)
-            if eventItem is None:
-                # @@@MOR This needs to use the new defaultParent framework
-                # to determine the parent
-                eventItem = application.Parcel.NewItem(self.itsView,
-                 None, self.itsView.findPath("//userdata"),
-                 eventKind, uuid)
-                countNew += 1
-            else:
-                countUpdated += 1
+            # FIXME Why are we stripping to 36 characters?
+
+            # hack until recurrence set can be stored in Chandler with one UUID
+            # as it's modeled by iCalendar
+            uuidMatchItem = self.itsView.findUUID(uuid)
+
+            # For now we'll expand recurrence sets, first find attributes that
+            # will be constant across the recurrence set.
 
             try:
-                eventItem.displayName = event.summary[0].value
+                displayName = event.summary[0].value
             except AttributeError:
-                eventItem.displayName = ""
+                displayName = ""
 
             try:
-                eventItem.description = event.description[0].value
-                # print "Has a description:", eventItem.description
+                description = event.description[0].value
             except AttributeError:
-                eventItem.description = ""
-
-            dt = event.dtstart[0].value
-            eventItem.startTime = \
-             mx.DateTime.ISO.ParseDateTime(dt.isoformat())
+                description = None
 
+            # Commented out because VALARM won't work until vobject is updated
+            #try:
+            #    # FIXME assumes DURATION, not DATE-TIME
+            #    reminderDelta = test[0].trigger[0].value
+            #except AttributeError:
+            #    reminderDelta = None
+
+            # RFC2445 allows VEVENTs without DTSTART, but it's hard to guess
+            # what that would mean, so we won't catch an exception if there's 
no
+            # dtstart.
+            dtstart  = event.dtstart[0].value 
+            
             try:
-                dt = event.dtend[0].value
-                eventItem.endTime = \
-                 mx.DateTime.ISO.ParseDateTime(dt.isoformat())
-            except:
-                eventItem.duration = mx.DateTime.DateTimeDelta(0, 1)
-
-            item.add(eventItem)
-            # print "Imported", eventItem.displayName, eventItem.startTime,
-            #  eventItem.duration, eventItem.endTime
+                duration = event.duration[0].value
+            except AttributeError:
+                # note that duration = dtend - dtstart isn't strictly correct
+                # throughout a recurrence set, 1 hour differences might happen
+                # around DST, but we'll ignore that corner case for now
+                try:
+                    duration = event.dtend[0].value  - dtstart
+                # FIXME no end time or duration, Chandler's UI doesn't seem to
+                # like events with no duration, so for now we'll set a dummy
+                # duration of 1 hour
+                except AttributeError:
+                    duration = datetime.timedelta(hours=1)
+            # Iterate through recurrence set.  Infinite recurrence sets are
+            # common, something has to be done to avoid infinite loops.
+            # We'll arbitrarily limit ourselves to MAXRECUR recurrences.
+            first = True
+            for dt in itertools.islice(event.rruleset, MAXRECUR):
+                # Hack to deal with recurrence set having a single UID but
+                # needing to become multiple items with distinct UUIDs.  For 
the
+                # first item, use the right UUID (and the matching Item if it
+                # exists), for later items, create a new uuid.
+                if first and uuidMatchItem is not None:
+                    eventItem = uuidMatchItem
+                    countUpdated += 1
+                else:
+                    if not first:
+                        uuid = UUID()
+                    # @@@MOR This needs to use the new defaultParent framework
+                    # to determine the parent
+                    eventItem = application.Parcel.NewItem(self.itsView,
+                     None, self.itsView.findPath("//userdata"),
+                     eventKind, uuid)
+                    countNew += 1
+                
+                eventItem.displayName = displayName
+                eventItem.startTime   = convertToMX(dt)
+                eventItem.endTime     = convertToMX(dt + duration)
+                
+                # I think Item.description describes a Kind, not userdata, so
+                # I'm using DESCRIPTION <-> body  
+                if description is not None:
+                    eventItem.body = textKind.makeValue(description)
+                    
+                # Commented out because VALARM won't work until vobject is 
updated
+                #if reminderTime is not None:
+                #    eventItem.reminderTime = convertToMX(dt + reminderDelta)
+
+                item.add(eventItem)
+                first = False
+                logger.debug("Imported %s %s %s" % (eventItem.displayName,
+                 eventItem.startTime, eventItem.endTime))
+                 
         logger.info("...iCalendar import of %d new items, %d updated" % \
          (countNew, countUpdated))
 
         return item
 
     def exportProcess(self, item, depth=0):
-        # item is the whole collection
-        pass
+        # item is the whole collection or it may be a single event
+        if isinstance(item, ItemCollection.ItemCollection):
+            events = [item]
+        else:
+            events = item.contents
+        
+        cal = eventsToVObject(events)
+        return cal.serialize()
+        
+        
\ No newline at end of file

Index: chandler/parcels/osaf/framework/sharing/tests/TestImportICalendar.py
diff -u 
chandler/parcels/osaf/framework/sharing/tests/TestImportICalendar.py:1.4 
chandler/parcels/osaf/framework/sharing/tests/TestImportICalendar.py:1.5
--- chandler/parcels/osaf/framework/sharing/tests/TestImportICalendar.py:1.4    
Wed Jan 19 15:52:33 2005
+++ chandler/parcels/osaf/framework/sharing/tests/TestImportICalendar.py        
Sun Jan 23 22:55:34 2005
@@ -1,8 +1,8 @@
 """
 A helper class which sets up and tears down dual RamDB repositories
 """
-__revision__  = "$Revision: 1.4 $"
-__date__      = "$Date: 2005/01/19 23:52:33 $"
+__revision__  = "$Revision: 1.5 $"
+__date__      = "$Date: 2005/01/24 06:55:34 $"
 __copyright__ = "Copyright (c) 2003-2004 Open Source Applications Foundation"
 __license__   = "http://osafoundation.org/Chandler_0.1_license_terms.htm";
 
@@ -78,6 +78,12 @@
         self.share.get()
 
         # @@@ Put some checking of the imported items here
+        
+        event=self.repo.findUUID('BED962E5-6042-11D9-BE74-000A95BB2738')
+        self.assert_(event.displayName == u'3 hour event',
+         "SUMMARY of first VEVENT not imported correctly, displayName is %s"
+         % event.displayName)
+        
         # Also, put in a test of updating from a modified ics file.
 
 

Index: chandler/parcels/osaf/views/main/Main.py
diff -u chandler/parcels/osaf/views/main/Main.py:1.104 
chandler/parcels/osaf/views/main/Main.py:1.105
--- chandler/parcels/osaf/views/main/Main.py:1.104      Sun Jan 23 14:42:54 2005
+++ chandler/parcels/osaf/views/main/Main.py    Sun Jan 23 22:55:33 2005
@@ -1,5 +1,5 @@
-__version__ = "$Revision: 1.104 $"
-__date__ = "$Date: 2005/01/23 22:42:54 $"
+__version__ = "$Revision: 1.105 $"
+__date__ = "$Date: 2005/01/24 06:55:33 $"
 __copyright__ = "Copyright (c) 2004 Open Source Applications Foundation"
 __license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm";
 
@@ -30,6 +30,7 @@
 from osaf.framework.blocks.Block import Block
 from osaf.contentmodel.ItemCollection import ItemCollection
 import osaf.framework.utils.imports.icalendar as ical
+import osaf.framework.sharing.ICalendar as ICalendar
 
 class MainView(View):
     """
@@ -298,16 +299,27 @@
 
     def onImportIcalendarEvent(self, event):
         # triggered from "Test | Import iCalendar" Menu
-        repository = Globals.repository
-        self.setStatusMessage ("Importing from " + ical.INFILE)
+        rep = Globals.repository
+        parent = rep.findPath("//userdata/contentitems")
+
+        self.setStatusMessage ("Importing from import.ics")
         try:
-            if ical.importFile(ical.INFILE, repository):
-                self.setStatusMessage ("Import completed")
-            else:
-                repository.logger.info("Failed importFile")
-                self.setStatusMessage("Import failed")
+            conduit = rep.findPath("//userdata/fsconduit")
+            if conduit is None:
+                conduit = Sharing.FileSystemConduit(name="fsconduit",
+                 parent=parent, sharePath=".", shareName="import.ics")
+            format = rep.findPath("//userdata/icalImportFormat")
+            if format is None:
+                format = ICalendar.ICalendarFormat(name="icalImportFormat",
+                 parent=parent)
+            share = rep.findPath("//userdata/icalImportShare")
+            if share is None:
+                share = Sharing.Share(name="icalImportShare", parent=parent,
+                 conduit=conduit, format=format)
+            share.get()
+            self.setStatusMessage ("Import completed")
         except Exception, e:
-            repository.logger.info("Failed importFile, caught exception " + 
str(e))
+            rep.logger.info("Failed importFile, caught exception " + str(e))
             self.setStatusMessage("Import failed")
 
     def onExportIcalendarEvent(self, event):

_______________________________________________
Commits mailing list
Commits@osafoundation.org
http://lists.osafoundation.org/mailman/listinfo/commits

Reply via email to