def _makeRecurrenceRuleSet(self, until=None, freq='daily'): ruleItem = RecurrenceRule(None, itsParent=self.sandbox) ruleItem.freq = freq if until is not None: ruleItem.until = until ruleSetItem = RecurrenceRuleSet(None, itsParent=self.sandbox) ruleSetItem.addRule(ruleItem) return ruleSetItem
def testExDate(self): ruleSet = dateutil.rrule.rruleset() for freq in 'weekly', 'monthly': ruleSet.rrule(self._createBasicDateUtil(freq)) ruleSet.exdate(self.start) ruleSetItem = RecurrenceRuleSet(None, itsView=self.view) ruleSetItem.setRuleFromDateUtil(ruleSet) identityTransformed = ruleSetItem.createDateUtilFromRule(self.start) self.assertNotEqual(self.start, identityTransformed[0])
def testExDate(self): ruleSet = dateutil.rrule.rruleset() for freq in 'weekly', 'monthly': ruleSet.rrule(self._createBasicDateUtil(freq)) ruleSet.exdate(self.start) ruleSetItem = RecurrenceRuleSet(None, view=self.rep.view) ruleSetItem.setRuleFromDateUtil(ruleSet) identityTransformed = ruleSetItem.createDateUtilFromRule(self.start) self.assertNotEqual(self.start, identityTransformed[0])
def _makeRecurrenceRuleSet(self, until=None, freq='daily', byweekday=None): ruleItem = RecurrenceRule(None, itsView=self.view) ruleItem.freq = freq if byweekday is not None: ruleItem.byweekday = byweekday if until is not None: ruleItem.until = until ruleSetItem = RecurrenceRuleSet(None, itsView=self.view) ruleSetItem.addRule(ruleItem) return ruleSetItem
def _createRuleSetItem(self, freq): ruleItem = RecurrenceRule(None, view=self.rep.view) ruleItem.until = getattr(self, freq)['end'] ruleItem.untilIsDate = False if freq == 'weekly': self.assertEqual(ruleItem.freq, 'weekly', "freq should default to weekly") else: ruleItem.freq = freq ruleSetItem = RecurrenceRuleSet(None, view=self.rep.view) ruleSetItem.addRule(ruleItem) return ruleSetItem
def testExRule(self): ruleSet = dateutil.rrule.rruleset() for freq in 'weekly', 'monthly': ruleSet.rrule(self._createBasicDateUtil(freq)) exrule = dateutil.rrule.rrule(WEEKLY, count=10, dtstart=self.start) ruleSet.exrule(exrule) ruleSetItem = RecurrenceRuleSet(None, view=self.rep.view) ruleSetItem.setRuleFromDateUtil(ruleSet) identityTransformed = ruleSetItem.createDateUtilFromRule(self.start) # The monthly rule dates aren't in the exclusion rule self.assertEqual(identityTransformed[0], self.start + timedelta(days=31)) self.assertEqual(identityTransformed.count(), self.weekly['count'] + self.monthly['count'] - 1 - 10)
def testExRule(self): ruleSet = dateutil.rrule.rruleset() for freq in 'weekly', 'monthly': ruleSet.rrule(self._createBasicDateUtil(freq)) exrule = dateutil.rrule.rrule(WEEKLY, count=10, dtstart=self.start) ruleSet.exrule(exrule) ruleSetItem = RecurrenceRuleSet(None, itsView=self.view) ruleSetItem.setRuleFromDateUtil(ruleSet) identityTransformed = ruleSetItem.createDateUtilFromRule(self.start) # The monthly rule dates aren't in the exclusion rule self.assertEqual(identityTransformed[0],self.start + timedelta(days=31)) self.assertEqual(identityTransformed.count(), self.weekly['count'] + self.monthly['count'] - 1 - 10)
def testRDate(self): ruleSet = dateutil.rrule.rruleset() for freq in 'weekly', 'monthly': ruleSet.rrule(self._createBasicDateUtil(freq)) ruleSet.rdate(self.start + timedelta(days=1)) ruleSet.rdate(self.start + timedelta(days=2)) ruleSetItem = RecurrenceRuleSet(None, itsView=self.view) ruleSetItem.setRuleFromDateUtil(ruleSet) self.assertEqual(ruleSetItem.rdates[0], self.start + timedelta(days=1)) identityTransformed = ruleSetItem.createDateUtilFromRule(self.start) self.assertEqual(identityTransformed[2], self.start + timedelta(days=2)) self.assertEqual(identityTransformed.count(), self.weekly['count'] + self.monthly['count'] - 1 + 2)
def testRDate(self): ruleSet = dateutil.rrule.rruleset() for freq in 'weekly', 'monthly': ruleSet.rrule(self._createBasicDateUtil(freq)) ruleSet.rdate(self.start + timedelta(days=1)) ruleSet.rdate(self.start + timedelta(days=2)) ruleSetItem = RecurrenceRuleSet(None, view=self.rep.view) ruleSetItem.setRuleFromDateUtil(ruleSet) self.assertEqual(ruleSetItem.rdates[0], self.start + timedelta(days=1)) identityTransformed = ruleSetItem.createDateUtilFromRule(self.start) self.assertEqual(identityTransformed[2], self.start + timedelta(days=2)) self.assertEqual(identityTransformed.count(), self.weekly['count'] + self.monthly['count'] - 1 + 2)
def addEventStamp(item, recur=False): es = EventStamp(item) es.add() es.summary = uw("Test Event Summary") tzinfo = item.itsView.tzinfo.floating # Choose random days, hours startDelta = timedelta(days=random.randint(0, 30), hours=random.randint(0, 24)) now = datetime.now(tzinfo) closeToNow = datetime(now.year, now.month, now.day, now.hour, int(now.minute / 30) * 30, tzinfo=now.tzinfo) es.startTime = closeToNow + startDelta es.anyTime = True # Choose random minutes es.duration = timedelta(minutes=60) es.location = Calendar.Location.getLocation(view, uw("My House")) es.itsItem.importance = random.choice(pim.ImportanceEnum.values) es.itsItem.setTriageStatus(randomEnum(pim.TriageEnum)) if recur: rule = RecurrenceRule(itsView=view) rule.freq = 'daily' rule.until = datetime(2008, 9, 14, 19, tzinfo=view.tzinfo.default) rule.untilIsDate = False ruleSet = RecurrenceRuleSet(itsView=view) ruleSet.addRule(rule) es.rruleset = ruleSet return es
def testTwoRuleSet(self): """Test two RecurrenceRules composed into a RuleSet.""" ruleSetItem = RecurrenceRuleSet(None, view=self.rep.view) ruleItem = self._createBasicItem('weekly') ruleSetItem.addRule(ruleItem) ruleSet = ruleSetItem.createDateUtilFromRule(self.start) #rrulesets support the rrule interface self._testRRule('weekly', ruleSet) ruleItem = self._createBasicItem('monthly') ruleSetItem.addRule(ruleItem) self._testCombined(ruleSetItem.createDateUtilFromRule(self.start))
def addEventStamp(item, recur=False): es = EventStamp(item) es.add() es.summary = uw("Test Event Summary") tzinfo = view.tzinfo.floating # Choose random days, hours startDelta = timedelta(days=random.randint(0, 30), hours=random.randint(0, 24)) now = datetime.now(tzinfo) closeToNow = datetime(now.year, now.month, now.day, now.hour, int(now.minute/30) * 30, tzinfo=now.tzinfo) es.startTime = closeToNow + startDelta es.anyTime = True # Choose random minutes es.duration = timedelta(minutes=60) es.location = Calendar.Location.getLocation(view, uw("My House")) es.itsItem.importance = random.choice(pim.ImportanceEnum.values) es.itsItem.setTriageStatus(randomEnum(pim.TriageEnum)) if recur: rule = RecurrenceRule(itsView=view) rule.freq = 'daily' rule.until = datetime(2008, 9, 14, 19, tzinfo=view.tzinfo.default) rule.untilIsDate = False ruleSet = RecurrenceRuleSet(itsView=view) ruleSet.addRule(rule) es.rruleset = ruleSet return es
def testRuleSetFromDateUtil(self): ruleSet = dateutil.rrule.rruleset() for freq in 'weekly', 'monthly': ruleSet.rrule(self._createBasicDateUtil(freq)) ruleSetItem = RecurrenceRuleSet(None, view=self.rep.view) ruleSetItem.setRuleFromDateUtil(ruleSet) self._testCombined(ruleSetItem.createDateUtilFromRule(self.start)) # test setting a rule instead of a ruleset ruleSetItem.setRuleFromDateUtil(self._createBasicDateUtil('weekly')) self._testRRule('weekly', ruleSetItem.createDateUtilFromRule(self.start)) # test raising an exception when setting a non-rrule or rruleset self.assertRaises(TypeError, ruleSetItem.setRuleFromDateUtil, 0)
def testTwoRuleSet(self): """Test two RecurrenceRules composed into a RuleSet.""" ruleSetItem = RecurrenceRuleSet(None, itsView=self.view) ruleItem = self._createBasicItem('weekly') ruleSetItem.addRule(ruleItem) ruleSet = ruleSetItem.createDateUtilFromRule(self.start) #rrulesets support the rrule interface self._testRRule('weekly', ruleSet) ruleItem = self._createBasicItem('monthly') ruleSetItem.addRule(ruleItem) self._testCombined(ruleSetItem.createDateUtilFromRule(self.start))
def testRuleSetFromDateUtil(self): ruleSet = dateutil.rrule.rruleset() for freq in 'weekly', 'monthly': ruleSet.rrule(self._createBasicDateUtil(freq)) ruleSetItem = RecurrenceRuleSet(None, itsView=self.view) ruleSetItem.setRuleFromDateUtil(ruleSet) self._testCombined(ruleSetItem.createDateUtilFromRule(self.start)) # test setting a rule instead of a ruleset ruleSetItem.setRuleFromDateUtil(self._createBasicDateUtil('weekly')) self._testRRule('weekly',ruleSetItem.createDateUtilFromRule(self.start)) # test raising an exception when setting a non-rrule or rruleset self.assertRaises(TypeError, ruleSetItem.setRuleFromDateUtil, 0)
def GenerateCalendarEvent(view, args): """ Generate one calendarEvent item """ event = Calendar.CalendarEvent(itsView=view) # displayName if args[0]=='*': # semi-random data event.displayName = random.choice(HEADLINES) elif not args[0]=='': event.displayName = u"%s" %args[0] else: event.displayName = u'untitled' if TEST_I18N: event.displayName = uw(event.displayName) #startTime (startDate + startTime) + TimeZone event.startTime = ReturnCompleteDatetime(view, args[2], args[3], tz=args[12]) #anyTime if args[4]=='*': # semi-random data r = random.randint(0,100) if r < 95: # 95% chance that we'll turn anyTime off event.anyTime = False else: event.anyTime = True elif args[4]=='TRUE' : event.anyTime = True else: event.anyTime = False #allDay if args[5]=='*': # semi-random data r = random.randint(0,100) if r < 5: # 5% chance of allDay event.allDay = True else: event.allDay = False elif args[5]=='TRUE' : event.allDay = True else: event.allDay = False #duration if args[6]=='*': # semi-random data event.duration = timedelta(minutes=random.choice(DURATIONS)) elif not args[6]=='': event.duration = timedelta(minutes=string.atoi(args[6])) else: event.duration = timedelta(minutes=60) #default value 1h #reminders if not args[7]=='': if args[7]=='*': # semi-random data reminderInterval = random.choice(REMINDERS) else: reminderInterval = string.atoi(args[7]) event.userReminderInterval = timedelta(minutes=-reminderInterval) #location if args[8]=='*': # semi-random data event.location = Calendar.Location.getLocation(view, random.choice(LOCATIONS)) elif not args[8]=='': event.location = Calendar.Location.getLocation(view,u"%s"%args[8]) if TEST_I18N: event.location = uw(event.location) #status (only 3 values allowed : 'Confirmed','Tentative','fyi') if args[9]=='*': # semi-random data event.transparency = random.choice(STATUS) elif string.lower(args[9]) in STATUS: event.transparency = string.lower(args[9]) else: # default value (normal) event.transparency = 'confirmed' #recurrence ('daily','weekly','monthly','yearly') + recurrence end date ruleItem = RecurrenceRule(None, itsView=view) ruleSetItem = RecurrenceRuleSet(None, itsView=view) if not args[11] == '': ruleItem.until = ReturnCompleteDatetime(view, args[11]) if args[10]=='*': # semi-random data ruleItem.freq = random.choice(RECURRENCES) ruleSetItem.addRule(ruleItem) event.rruleset = ruleSetItem elif string.lower(args[10]) in RECURRENCES: ruleItem.freq = string.lower(args[10]) ruleSetItem.addRule(ruleItem) event.rruleset = ruleSetItem #collection if args[1]=='*': # semi-random data if not len(collectionsDict) == 0: collectionsDict.values()[random.randint(0,len(collectionsDict)-1)].add(event) elif not args[1]=='': collectionNames = string.split(args[1], ';') for name in collectionNames: if collectionsDict.has_key(name): collectionsDict[name].add(event) else: GenerateCollection(view, [name]) collectionsDict[name].add(event) return event
def createItems(paramDict): if bool(wx.GetApp()): view = QAUITestAppLib.App_ns.itsView else: # when running unit tests there is no app view = NullRepositoryView(verify=True) tzinfo = view.tzinfo.getDefault() totalItems = int(paramDict['textCtrlTotalItems']) #get titles to use TITLES = getDataFromFile(paramDict['textCtrlTitleSourceFile']) #create collections to use collectionNames = addRandomWithoutDups( getDataFromFile(paramDict['textCtrlCollectionFileName']), int(paramDict['textCtrlCollectionCount'])) sidebarCollections = createCollections(collectionNames, view) #determine how many collections each item should participate in collectionMembershipIndex = createMembershipIndex( paramDict['textCtrlCollectionMembership'], range(totalItems)) collectionMembershipDict = {} ##fill collectionMembershipDict with correct number of collections for each item index for i in range(totalItems): collectionMembershipDict[i] = addRandomWithoutDups( sidebarCollections, collectionMembershipIndex[i]) #get locations to use LOCATIONS = getDataFromFile(paramDict['textCtrlLocationSourceFilePath']) #get note field text NOTES = getDataFromFile(paramDict['textCtrlNoteSourceFilePath'], note=True) itemTypes = percentsToCount( totalItems, { 'mail': int(paramDict['choicePercentMail']), 'task': int(paramDict['choicePercentTask']), 'event': int(paramDict['choicePercentEvent']) }) triageStatus = percentsToCount( totalItems, { 'unassigned': int(paramDict['choicePercentUnassignedStatus']), 'now': int(paramDict['choicePercentNow']), 'later': int(paramDict['choicePercentLater']), 'done': int(paramDict['choicePercentDone']) }, randomize=True) triageStatusAssignments = percentsToAssignments(triageStatus) #convert the string triage value to the triageEnum that can be used to set it triageEnums = { 'now': pim.TriageEnum.now, 'later': pim.TriageEnum.later, 'done': pim.TriageEnum.done } triageStatusUnassigned = [] for index, status in triageStatusAssignments.iteritems(): if status == 'unassigned': triageStatusUnassigned.append(index) else: triageStatusAssignments[index] = triageEnums[status] recurTypes = percentsToCount( itemTypes['event'], { 'non': int(paramDict['choicePercentNonRecurring']), 'daily': int(paramDict['choicePercentDaily']), 'weekly': int(paramDict['choicePercentWeekly']), 'biweekly': int(paramDict['choicePercentBiWeekly']), 'monthly': int(paramDict['choicePercentMonthly']), 'yearly': int(paramDict['choicePercentYearly']) }, randomize=True) durationTypes = percentsToCount( itemTypes['event'], { 'allDay': int(paramDict['choicePercentAllDay']), 'atTime': int(paramDict['choicePercentAtTime']), 'anyTime': int(paramDict['choicePercentAnyTime']), 'duration': int(paramDict['choicePercentDuration']) }, randomize=True) eventStatus = percentsToCount( durationTypes['duration'], { 'confirmed': int(paramDict['choicePercentConfirmed']), 'FYI': int(paramDict['choicePercentFYI']), 'tentative': int(paramDict['choicePercentTentative']) }, randomize=True) eventStatusAssignment = percentsToAssignments(eventStatus) global startTimes startTimes = createStartTimeRange(paramDict['textCtrlTimeOfDay'], itemTypes['event']) startDateText = paramDict['textCtrlStartDate'] if startDateText: y, m, d = startDateText.split(',') startDate = datetime(int(y), int(m), int(d)) y, m, d = paramDict['textCtrlEndDate'].split(',') endDate = datetime(int(y), int(m), int(d)) global dates dates = createDateRange(startDate, endDate) durationsTimed = createDurationIndex(paramDict['textCtrlDuration'], durationTypes['duration']) itemsWithDuration = durationsTimed.keys() relativeReminders, absoluteReminders = createAlarmIndex( paramDict['textCtrlAlarmSpec'], itemTypes['event'], totalItems, tzinfo) allRecurring = recurTypes['daily'] + recurTypes['weekly'] + recurTypes[ 'biweekly'] + recurTypes['monthly'] + recurTypes['yearly'] recurenceEndDate = createEndDateIndex( paramDict['textCtrlRecurrenceEndDates'], allRecurring) # email stuff emailAddresses = [] for firstName in firstNames: for lastName in lastNames: for domain in domainList: emailAddresses.append(firstName + lastName + '@' + domain) # get address sources if paramDict['textCtrlToFile'] == '': toAddresses = emailAddresses else: toAddresses = getDataFromFile(paramDict['textCtrlToFile']) if paramDict['textCtrlCCFileName'] == '': ccAddresses = emailAddresses else: ccAddresses = getDataFromFile(paramDict['textCtrlCCFileName']) if paramDict['textCtrlBCCFileName'] == '': bccAddresses = emailAddresses else: bccAddresses = getDataFromFile(paramDict['textCtrlBCCFileName']) # make email indexes from specs toIndex = createAddressIndex(itemTypes['mail'], paramDict['textCtrlToSpec'], toAddresses) ccIndex = createAddressIndex(itemTypes['mail'], paramDict['textCtrlCCSpec'], ccAddresses) bccIndex = createAddressIndex(itemTypes['mail'], paramDict['textCtrlBCCSpec'], bccAddresses) created = [] for i in range(totalItems): created.append(pim.notes.Note(itsView=view)) item = created[i] item.displayName = random.choice(TITLES) item.body = random.choice(NOTES) if i not in triageStatusUnassigned: item.setTriageStatus(triageStatusAssignments[i]) if i in absoluteReminders.keys(): item.setUserReminderTime( datetime.combine(date.today(), absoluteReminders[i])) if i in itemTypes['mail']: mailStampedItem = pim.MailStamp(item) mailStampedItem.add() if i in toIndex.keys(): for address in toIndex[i]: mailStampedItem.toAddress.append( Mail.EmailAddress.getEmailAddress(view, address)) if i in ccIndex.keys(): for address in ccIndex[i]: mailStampedItem.ccAddress.append( Mail.EmailAddress.getEmailAddress(view, address)) if i in bccIndex.keys(): for address in bccIndex[i]: mailStampedItem.bccAddress.append( Mail.EmailAddress.getEmailAddress(view, address)) if i in itemTypes['task']: pim.TaskStamp(item).add() if i in itemTypes['event']: eventStampedItem = pim.EventStamp(item) eventStampedItem.add() if i in itemsWithDuration: #if its an event with duration then assign status eventStampedItem.transparency = eventStatusAssignment[i].lower( ) eventStampedItem.location = Calendar.Location.getLocation( view, random.choice(LOCATIONS)) eventStampedItem.anyTime = i in durationTypes['anyTime'] eventStampedItem.allDay = i in durationTypes['allDay'] eventStampedItem.startTime = createStartTime(i, tzinfo) if i in durationTypes['atTime']: eventStampedItem.duration = timedelta(0) else: if i in itemsWithDuration: eventStampedItem.duration = durationsTimed[i] if i in relativeReminders.keys(): eventStampedItem.setUserReminderInterval(relativeReminders[i]) if i in allRecurring: ruleItem = RecurrenceRule(None, itsView=view) ruleSetItem = RecurrenceRuleSet(None, itsView=view) recurFreq = rangeName(i, recurTypes) #specail case biweekly if recurFreq == 'biweekly': ruleItem.freq = 'weekly' ruleItem.interval = 2 else: ruleItem.freq = recurFreq #add end date if i in recurenceEndDate and recurenceEndDate[ i]: # zero means don't use an end date endDateDate = calcEndDate(eventStampedItem.startTime, recurenceEndDate[i], recurFreq) ruleItem.until = endDateDate ruleSetItem.addRule(ruleItem) eventStampedItem.rruleset = ruleSetItem for collection in collectionMembershipDict[i]: collection.add(item) return created #return value used for testing in TestCreateItems.py:TestItemCreation
def testChange(self): from osaf import sharing from osaf.pim import ListCollection self.tzprefs.showUI = False gayParee = self.view.tzinfo.getInstance("Europe/Paris") master = CalendarEvent(itsView=self.view, anyTime=False, startTime=datetime( 2007, 2, 7, 11, 30, tzinfo=self.view.tzinfo.floating), duration=timedelta(hours=1)) master.rruleset = RecurrenceRuleSet( itsView=self.view, rrules=[RecurrenceRule(itsView=self.view, freq='daily')]) ordinary = CalendarEvent(itsView=self.view, anyTime=False, startTime=datetime( 2007, 2, 7, 11, 30, tzinfo=self.view.tzinfo.floating), duration=timedelta(hours=1)) sharedFloating = CalendarEvent(itsView=self.view, anyTime=False, startTime=datetime( 2002, 12, 22, tzinfo=self.view.tzinfo.floating)) share = sharing.Share(itsView=self.view, hidden=False) item = sharing.SharedItem(sharedFloating) item.add() item.sharedIn = item.shares = [share] nonFloatingOccurrence = master.getNextOccurrence( after=datetime(2007, 5, 2, tzinfo=self.view.tzinfo.floating)) nonFloatingOccurrence.changeThis( EventStamp.startTime.name, nonFloatingOccurrence.startTime.replace(tzinfo=gayParee)) titleMod = nonFloatingOccurrence.getNextOccurrence() titleMod.itsItem.summary = "yabba dabba doo" self.tzprefs.showUI = True tzItem = TimeZoneInfo.get(self.view) # Make sure that floating is no longer the default self.failIfEqual(self.view.tzinfo.default, self.view.tzinfo.floating) self.failIfEqual(tzItem.default, self.view.tzinfo.floating) self.failUnlessEqual(tzItem.default, self.view.tzinfo.default) # Make sure the ordinary and master events acquired the default tz self.failUnlessEqual(ordinary.startTime.tzinfo, self.view.tzinfo.default) self.failUnlessEqual(master.startTime.tzinfo, self.view.tzinfo.default) # Make sure the non-floating occurrence didn't have its tz changed self.failUnlessEqual(nonFloatingOccurrence.startTime.tzinfo, gayParee) # Check the master's occurrences ... for event in map(EventStamp, master.occurrences): # Everything but the modification we just made should have # the default timezone set for startTime ... if event != nonFloatingOccurrence: self.failUnlessEqual(event.startTime.tzinfo, self.view.tzinfo.default) # and recurrenceIDs should always have the master's tzinfo self.failUnlessEqual(event.recurrenceID.tzinfo, self.view.tzinfo.default) # ... and the shared item's tzinfo should not have changed self.failUnlessEqual(sharedFloating.startTime.tzinfo, self.view.tzinfo.floating) self.tzprefs.showUI = False self.failUnlessEqual(tzItem.default, self.view.tzinfo.floating) self.failIfEqual(self.view.tzinfo.floating, self.view.tzinfo.default)
def _importOneVObject(vobj, filters, coerceTzinfo, promptForTimezone, newItemParent): view = newItemParent.itsView itemIsNew = False newStamps = [] # by default, we'll create a new item, not change existing items itemChangeCallback = None # store up all attributes in a dictionary ... changesDict = {} # ... and define a shorthand for updating it def change(attr, value): changesDict[attr.name] = value # rruleset and userReminderInterval/userReminderTime must # be set last.... changeLast = [] # values that apply to VEVENT and VTODO ... summary = vobj.getChildValue("summary", u"") description = vobj.getChildValue("description") status = vobj.getChildValue("status", "").lower() duration = vobj.getChildValue("duration") uid = vobj.getChildValue("uid") rruleset = vobj.rruleset recurrenceID = vobj.getChildValue("recurrence_id") # ... uh, sorta completed = vobj.getChildValue("completed") def convertDatetime(dt): # coerce timezones based on coerceTzinfo if coerceTzinfo is not None: dt = TimeZone.coerceTimeZone(view, dt, coerceTzinfo) # ... and make sure we return something with an ICUtzinfo return convertToICUtzinfo(view, dt) reminderDelta = None reminderAbsoluteTime = None try: reminderValue = vobj.valarm.trigger.value except AttributeError: pass else: if type(reminderValue) is datetime.datetime: reminderAbsoluteTime = convertDatetime(reminderValue) else: assert type(reminderValue) is datetime.timedelta reminderDelta = reminderValue if vobj.name == "VEVENT": if DEBUG: logger.debug("got VEVENT %s", vobj) newStamps.append(EventStamp) dtstart = vobj.getChildValue("dtstart") if status in ("confirmed", "tentative"): pass elif status == "cancelled": # Chandler doesn't have CANCELLED status = "fyi" else: status = "confirmed" if EventStamp.transparency.name not in filters: change(EventStamp.transparency, status) location = vobj.getChildValue("location") if location: change(EventStamp.location, Calendar.Location.getLocation(view, location)) elif vobj.name == "VTODO": if DEBUG: logger.debug("got VEVENT %s", vobj) newStamps.append(TaskStamp) # VTODO with a DUE ==> EventTask due = vobj.getChildValue("due") if due is not None: newStamps.append(EventStamp) dtstart = due else: assert False, "vobj %s should always be VEVENT or VTODO" % (vobj,) # Save changes applicable to both events & tasks .... # SUMMARY <-> {EventStamp,TaskStamp}.summary if summary is not None: change(newStamps[0].summary, summary) # DESCRIPTION <-> body if description is not None: change(Note.body, description) # Absolute time reminders if reminderAbsoluteTime is not None and Remindable.reminders.name not in filters: changeLast.append(lambda item: setattr(item, Remindable.userReminderTime.name, reminderAbsoluteTime)) # Custom properties/parameters ignoredProperties = {} ignoredParameters = {} for line in vobj.lines(): name = line.name.lower() if name not in attributesUnderstood: line.transformFromNative() if not line.encoded and line.behavior: line.behavior.encode(line) ignoredProperties[name] = line.value params = u"" for key, paramvals in line.params.iteritems(): if key.lower() not in parametersUnderstood: vals = map(vobject.base.dquoteEscape, paramvals) params += ";" + key + "=" + ",".join(vals) if len(params) > 0: ignoredParameters[name] = params change(Note.icalendarProperties, ignoredProperties) change(Note.icalendarParameters, ignoredParameters) # See if we have a corresponding item already item = utility.findUID(view, uid) if item is not None: if DEBUG: logger.debug("matched UID %s with %s", uid, item) else: try: # See if uid is a valid repository UUID, if so we'll # go ahead and use it for the new item's UUID. uuid = UUID(uid) except ValueError: # Not in valid UUID format, so hash the icaluid to # generate a 16-byte string we can use for uuid uuid = UUID(md5.new(uid).digest()) logger.info("Converted icalUID '%s' to UUID '%s'", uid, str(uuid)) # If there is already an item with this UUID, use it, # otherwise we'll create one later item = view.findUUID(uuid) if item is not None: item.icalUID = unicode(uuid) if EventStamp in newStamps: dtend = vobj.getChildValue("dtend") isDate = type(dtstart) == date # 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. anyTime = getattr(dtstart, "x_osaf_anytime_param", None) == "TRUE" if duration is None: def getDifference(left, right): leftIsDate = type(left) == date rightIsDate = type(right) == date if leftIsDate: if rightIsDate: return left - right else: left = TimeZone.forceToDateTime(view, left) elif rightIsDate: right = TimeZone.forceToDateTime(view, right) return makeNaiveteMatch(view, left, right.tzinfo) - right if dtend is not None: duration = getDifference(dtend, dtstart) elif anyTime or isDate: duration = oneDay else: duration = datetime.timedelta(0) if isDate: dtstart = TimeZone.forceToDateTime(view, dtstart) # convert to Chandler's notion of all day duration duration -= oneDay elif dtstart.tzinfo is not None and promptForTimezone: # got a timezoned event, prompt (non-modally) to turn on # timezones app = wx.GetApp() if app is not None: def ShowTimezoneDialogCallback(): ShowTurnOnTimezonesDialog(view=app.UIRepositoryView) app.PostAsyncEvent(ShowTimezoneDialogCallback) promptForTimezone = False dtstart = convertDatetime(dtstart) tzinfo = dtstart.tzinfo if anyTime: change(EventStamp.anyTime, True) change(EventStamp.allDay, False) elif isDate: # allDay events should have anyTime True, so if the user # unselects allDay, the time isn't set to midnight change(EventStamp.anyTime, True) change(EventStamp.allDay, True) else: change(EventStamp.allDay, False) change(EventStamp.anyTime, False) change(EventStamp.startTime, dtstart) change(EventStamp.duration, duration) if (reminderDelta is not None) and (Remindable.reminders.name not in filters): changeLast.append(lambda item: setattr(item, EventStamp.userReminderInterval.name, reminderDelta)) if item is not None: event = EventStamp(item) if recurrenceID: if type(recurrenceID) == date: recurrenceID = datetime.datetime.combine(recurrenceID, time(tzinfo=tzinfo)) else: recurrenceID = convertToICUtzinfo(view, makeNaiveteMatch(view, recurrenceID, tzinfo)) masterEvent = EventStamp(item) event = masterEvent.getRecurrenceID(recurrenceID) if event is None and hasattr(masterEvent, "startTime"): # Some calendars, notably Oracle, serialize # recurrence-id as UTC, which wreaks havoc with # noTZ mode. So move recurrenceID to the same tzinfo # as the master's dtstart, bug 6830 masterTzinfo = masterEvent.startTime.tzinfo tweakedID = recurrenceID.astimezone(masterTzinfo) event = masterEvent.getRecurrenceID(tweakedID) if event is None: # just in case the previous didn't work tweakedID = recurrenceID.astimezone(tzinfo) event = masterEvent.getRecurrenceID(tweakedID) if event is None: # our recurrenceID didn't match an item we know # about. This may be because the item is created # by a later modification, a case we're not dealing # with. For now, just skip it. logger.info("RECURRENCE-ID '%s' didn't match rule.", recurrenceID) return (None, None, promptForTimezone) item = event.itsItem recurrenceLine = vobj.contents["recurrence-id"][0] range = recurrenceLine.params.get("RANGE", ["THIS"])[0] if range == "THISANDPRIOR": # ignore THISANDPRIOR changes for now logger.info("RECURRENCE-ID RANGE of THISANDPRIOR " "not supported") return (None, None, promptForTimezone) elif range == "THIS": itemChangeCallback = event.changeThis # check if this is a modification to a master event # if so, avoid changing the master's UUID when # creating a modification if event.getMaster() == event: mod = event._cloneEvent() mod.modificationFor = mod.occurrenceFor = event.itsItem if item.hasLocalAttributeValue(EventStamp.occurrenceFor.name): del event.occurrenceFor event = mod item = event.itsItem elif range == "THISANDFUTURE": itemChangeCallback = event.changeThisAndFuture else: logger.info("RECURRENCE-ID RANGE not recognized. " "RANGE = %s" % range) return (None, None, promptForTimezone) else: if event.rruleset is not None: # re-creating a recurring item from scratch, delete # old recurrence information # item might not be the master, though, so # get the master, or eventItem will be a deleted # event event = event.getMaster() item = event.itsItem # delete modifications the master has, to avoid # changing the master to a modification with a # different UUID for mod in itertools.imap(EventStamp, event.modifications): # [Bug 7019] # We need to del these because, in the deferred # delete case, we would have deferred items # living on, in the manner of the undead, in # master.modifications (and occurrences). This # would ultimately cause .getNextOccurrence() # to terminate prematurely. del mod.modificationFor del mod.occurrenceFor mod.itsItem.delete() event.removeRecurrence() itemChangeCallback = event.changeThis if DEBUG: logger.debug("Changing event: %s" % str(event)) assert itemChangeCallback is not None, "Must set itemChangeCallback for EventStamp imports" if rruleset is not None: # fix for Bug 6994, exdate and rdate timezones need to be # converted to ICUtzinfo instances for typ in "_rdate", "_exdate": setattr(rruleset, typ, [convertDatetime(d) for d in getattr(rruleset, typ, [])]) ruleSetItem = RecurrenceRuleSet(None, itsView=view) ruleSetItem.setRuleFromDateUtil(rruleset) changeLast.append(lambda item: setattr(item, EventStamp.rruleset.name, ruleSetItem)) if TaskStamp in newStamps: if Note._triageStatus.name not in filters: # Translate status from iCalendar to TaskStamp/ContentItem triageStatus = TriageEnum.now if status == "completed": triageStatus = TriageEnum.done elif status == "needs-action": change(Note.needsReply, True) elif status in ("", "in-process"): change(Note.needsReply, False) elif status == "cancelled": triageStatus = TriageEnum.later # @@@ Jeffrey: This may not be right... # Set triageStatus and triageStatusChanged together. if completed is not None: if type(completed) == date: completed = TimeZone.forceToDateTime(view, completed) changeLast.append(lambda item: item.setTriageStatus(triageStatus, when=completed)) itemIsNew = item is None if itemIsNew: # create a new item change(Note.icalUID, uid) kind = Note.getKind(view) item = kind.instantiateItem(None, newItemParent, uuid, withInitialValues=True) itemChangeCallback = item.__setattr__ else: if itemChangeCallback is None: itemChangeCallback = item.__setattr__ # update an existing item if rruleset is None and recurrenceID is None and EventStamp(item).rruleset is not None: # no recurrenceId or rruleset, but the existing item # may have recurrence, so delete it EventStamp(item).removeRecurrence() for attr, val in changesDict.iteritems(): # Only change a datetime if it's really different # from what the item already has: if type(val) is datetime.datetime: oldValue = getattr(item, attr, None) if oldValue is not None and oldValue == val and oldValue.tzinfo == val.tzinfo: continue itemChangeCallback(attr, val) # ... make sure the stamps are right for stamp in EventStamp, TaskStamp: if not stamp in newStamps: if has_stamp(item, stamp): stamp(item).remove() else: if not has_stamp(item, stamp): stamp(item).add() # ... and do the final set of changes for cb in changeLast: cb(item) return item, itemIsNew, promptForTimezone
def _importOneVObject(vobj, filters, coerceTzinfo, promptForTimezone, newItemParent): view = newItemParent.itsView itemIsNew = False newStamps = [] # by default, we'll create a new item, not change existing items itemChangeCallback = None # store up all attributes in a dictionary ... changesDict = {} # ... and define a shorthand for updating it def change(attr, value): changesDict[attr.name] = value # rruleset and userReminderInterval/userReminderTime must # be set last.... changeLast = [] # values that apply to VEVENT and VTODO ... summary = vobj.getChildValue('summary', u"") description = vobj.getChildValue('description') status = vobj.getChildValue('status', "").lower() duration = vobj.getChildValue('duration') uid = vobj.getChildValue('uid') rruleset = vobj.rruleset recurrenceID = vobj.getChildValue('recurrence_id') # ... uh, sorta completed = vobj.getChildValue('completed') def convertDatetime(dt): # coerce timezones based on coerceTzinfo if coerceTzinfo is not None: dt = TimeZone.coerceTimeZone(view, dt, coerceTzinfo) # ... and make sure we return something with an ICUtzinfo return convertToICUtzinfo(view, dt) reminderDelta = None reminderAbsoluteTime = None try: reminderValue = vobj.valarm.trigger.value except AttributeError: pass else: if type(reminderValue) is datetime.datetime: reminderAbsoluteTime = convertDatetime(reminderValue) else: assert type(reminderValue) is datetime.timedelta reminderDelta = reminderValue if vobj.name == "VEVENT": if DEBUG: logger.debug("got VEVENT %s", vobj) newStamps.append(EventStamp) dtstart = vobj.getChildValue('dtstart') if status in ('confirmed', 'tentative'): pass elif status == 'cancelled': #Chandler doesn't have CANCELLED status = 'fyi' else: status = 'confirmed' if EventStamp.transparency.name not in filters: change(EventStamp.transparency, status) location = vobj.getChildValue('location') if location: change(EventStamp.location, Calendar.Location.getLocation(view, location)) elif vobj.name == "VTODO": if DEBUG: logger.debug("got VEVENT %s", vobj) newStamps.append(TaskStamp) # VTODO with a DUE ==> EventTask due = vobj.getChildValue('due') if due is not None: newStamps.append(EventStamp) dtstart = due else: assert False, "vobj %s should always be VEVENT or VTODO" % ( vobj,) # Save changes applicable to both events & tasks .... # SUMMARY <-> {EventStamp,TaskStamp}.summary if summary is not None: change(newStamps[0].summary, summary) # DESCRIPTION <-> body if description is not None: change(Note.body, description) # Absolute time reminders if (reminderAbsoluteTime is not None and Remindable.reminders.name not in filters): changeLast.append(lambda item: setattr(item, Remindable.userReminderTime.name, reminderAbsoluteTime)) # Custom properties/parameters ignoredProperties = {} ignoredParameters = {} for line in vobj.lines(): name = line.name.lower() if name not in attributesUnderstood: line.transformFromNative() if not line.encoded and line.behavior: line.behavior.encode(line) ignoredProperties[name] = line.value params=u'' for key, paramvals in line.params.iteritems(): if key.lower() not in parametersUnderstood: vals = map(vobject.base.dquoteEscape, paramvals) params += ';' + key + '=' + ','.join(vals) if len(params) > 0: ignoredParameters[name] = params change(Note.icalendarProperties, ignoredProperties) change(Note.icalendarParameters, ignoredParameters) # See if we have a corresponding item already item = utility.findUID(view, uid) if item is not None: if DEBUG: logger.debug("matched UID %s with %s", uid, item) else: try: # See if uid is a valid repository UUID, if so we'll # go ahead and use it for the new item's UUID. uuid = UUID(uid) except ValueError: # Not in valid UUID format, so hash the icaluid to # generate a 16-byte string we can use for uuid uuid = UUID(md5.new(uid).digest()) logger.info("Converted icalUID '%s' to UUID '%s'", uid, str(uuid)) # If there is already an item with this UUID, use it, # otherwise we'll create one later item = view.findUUID(uuid) if item is not None: item.icalUID = unicode(uuid) if EventStamp in newStamps: dtend = vobj.getChildValue('dtend') isDate = type(dtstart) == date # 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. anyTime = (getattr(dtstart, 'x_osaf_anytime_param', None) == 'TRUE') if duration is None: def getDifference(left, right): leftIsDate = (type(left) == date) rightIsDate = (type(right) == date) if leftIsDate: if rightIsDate: return left - right else: left = TimeZone.forceToDateTime(view, left) elif rightIsDate: right = TimeZone.forceToDateTime(view, right) return makeNaiveteMatch(view, left, right.tzinfo) - right if dtend is not None: duration = getDifference(dtend, dtstart) elif anyTime or isDate: duration = oneDay else: duration = datetime.timedelta(0) if isDate: dtstart = TimeZone.forceToDateTime(view, dtstart) # convert to Chandler's notion of all day duration duration -= oneDay elif dtstart.tzinfo is not None and promptForTimezone: # got a timezoned event, prompt (non-modally) to turn on # timezones app = wx.GetApp() if app is not None: def ShowTimezoneDialogCallback(): ShowTurnOnTimezonesDialog(view=app.UIRepositoryView) app.PostAsyncEvent(ShowTimezoneDialogCallback) promptForTimezone = False dtstart = convertDatetime(dtstart) tzinfo = dtstart.tzinfo if anyTime: change(EventStamp.anyTime, True) change(EventStamp.allDay, False) elif isDate: # allDay events should have anyTime True, so if the user # unselects allDay, the time isn't set to midnight change(EventStamp.anyTime, True) change(EventStamp.allDay, True) else: change(EventStamp.allDay, False) change(EventStamp.anyTime, False) change(EventStamp.startTime, dtstart) change(EventStamp.duration, duration) if ((reminderDelta is not None) and (Remindable.reminders.name not in filters)): changeLast.append( lambda item:setattr(item, EventStamp.userReminderInterval.name, reminderDelta)) if item is not None: event = EventStamp(item) if recurrenceID: if type(recurrenceID) == date: recurrenceID = datetime.datetime.combine( recurrenceID, time(tzinfo=tzinfo)) else: recurrenceID = convertToICUtzinfo(view, makeNaiveteMatch(view, recurrenceID, tzinfo)) masterEvent = EventStamp(item) event = masterEvent.getRecurrenceID(recurrenceID) if event is None and hasattr(masterEvent, 'startTime'): # Some calendars, notably Oracle, serialize # recurrence-id as UTC, which wreaks havoc with # noTZ mode. So move recurrenceID to the same tzinfo # as the master's dtstart, bug 6830 masterTzinfo = masterEvent.startTime.tzinfo tweakedID = recurrenceID.astimezone(masterTzinfo) event = masterEvent.getRecurrenceID(tweakedID) if event is None: # just in case the previous didn't work tweakedID = recurrenceID.astimezone(tzinfo) event = masterEvent.getRecurrenceID(tweakedID) if event is None: # our recurrenceID didn't match an item we know # about. This may be because the item is created # by a later modification, a case we're not dealing # with. For now, just skip it. logger.info("RECURRENCE-ID '%s' didn't match rule.", recurrenceID) return (None, None, promptForTimezone) item = event.itsItem recurrenceLine = vobj.contents['recurrence-id'][0] range = recurrenceLine.params.get('RANGE', ['THIS'])[0] if range == 'THISANDPRIOR': # ignore THISANDPRIOR changes for now logger.info("RECURRENCE-ID RANGE of THISANDPRIOR " \ "not supported") return (None, None, promptForTimezone) elif range == 'THIS': itemChangeCallback = event.changeThis # check if this is a modification to a master event # if so, avoid changing the master's UUID when # creating a modification if event.getMaster() == event: mod = event._cloneEvent() mod.modificationFor = mod.occurrenceFor = event.itsItem if item.hasLocalAttributeValue( EventStamp.occurrenceFor.name): del event.occurrenceFor event = mod item = event.itsItem elif range == 'THISANDFUTURE': itemChangeCallback = event.changeThisAndFuture else: logger.info("RECURRENCE-ID RANGE not recognized. " \ "RANGE = %s" % range) return (None, None, promptForTimezone) else: if event.rruleset is not None: # re-creating a recurring item from scratch, delete # old recurrence information # item might not be the master, though, so # get the master, or eventItem will be a deleted # event event = event.getMaster() item = event.itsItem # delete modifications the master has, to avoid # changing the master to a modification with a # different UUID for mod in itertools.imap(EventStamp, event.modifications): # [Bug 7019] # We need to del these because, in the deferred # delete case, we would have deferred items # living on, in the manner of the undead, in # master.modifications (and occurrences). This # would ultimately cause .getNextOccurrence() # to terminate prematurely. del mod.modificationFor del mod.occurrenceFor mod.itsItem.delete() event.removeRecurrence() itemChangeCallback = event.changeThis if DEBUG: logger.debug("Changing event: %s" % str(event)) assert itemChangeCallback is not None, \ "Must set itemChangeCallback for EventStamp imports" if rruleset is not None: # fix for Bug 6994, exdate and rdate timezones need to be # converted to ICUtzinfo instances for typ in '_rdate', '_exdate': setattr(rruleset, typ, [convertDatetime(d) for d in getattr(rruleset, typ, []) ]) ruleSetItem = RecurrenceRuleSet(None, itsView=view) ruleSetItem.setRuleFromDateUtil(rruleset) changeLast.append(lambda item: setattr(item, EventStamp.rruleset.name, ruleSetItem)) if TaskStamp in newStamps: if Note._triageStatus.name not in filters: # Translate status from iCalendar to TaskStamp/ContentItem triageStatus=TriageEnum.now if status == "completed": triageStatus = TriageEnum.done elif status == "needs-action": change(Note.needsReply, True) elif status in ("", "in-process"): change(Note.needsReply, False) elif status == "cancelled": triageStatus = TriageEnum.later # @@@ Jeffrey: This may not be right... # Set triageStatus and triageStatusChanged together. if completed is not None: if type(completed) == date: completed = TimeZone.forceToDateTime(view, completed) changeLast.append(lambda item: item.setTriageStatus(triageStatus, when=completed)) itemIsNew = (item is None) if itemIsNew: # create a new item change(Note.icalUID, uid) kind = Note.getKind(view) item = kind.instantiateItem(None, newItemParent, uuid, withInitialValues=True) itemChangeCallback = item.__setattr__ else: if itemChangeCallback is None: itemChangeCallback = item.__setattr__ # update an existing item if (rruleset is None and recurrenceID is None and EventStamp(item).rruleset is not None): # no recurrenceId or rruleset, but the existing item # may have recurrence, so delete it EventStamp(item).removeRecurrence() for attr, val in changesDict.iteritems(): # Only change a datetime if it's really different # from what the item already has: if type(val) is datetime.datetime: oldValue = getattr(item, attr, None) if (oldValue is not None and oldValue == val and oldValue.tzinfo == val.tzinfo): continue itemChangeCallback(attr, val) # ... make sure the stamps are right for stamp in EventStamp, TaskStamp: if not stamp in newStamps: if has_stamp(item, stamp): stamp(item).remove() else: if not has_stamp(item, stamp): stamp(item).add() # ... and do the final set of changes for cb in changeLast: cb(item) return item, itemIsNew, promptForTimezone