def ImportMessageChange(self, props, flags): if self.skip: raise MAPIError(SYNC_E_IGNORE) try: entryid = PpropFindProp(props, PR_ENTRYID) if self.importer.store: mapistore = self.importer.store.mapiobj else: store_entryid = PpropFindProp(props, PR_STORE_ENTRYID).Value store_entryid = WrapStoreEntryID(0, b'zarafa6client.dll', store_entryid[:-4]) + self.server.pseudo_url + b'\x00' mapistore = self.server._store2(store_entryid) item = _item.Item() item.server = self.server item.store = _store.Store(mapiobj=mapistore, server=self.server) try: item.mapiobj = _utils.openentry_raw(mapistore, entryid.Value, 0) props = item.mapiobj.GetProps([PR_EC_HIERARCHYID, PR_EC_PARENT_HIERARCHYID, PR_STORE_RECORD_KEY], 0) # XXX properties don't exist? item.docid = props[0].Value item.storeid = _benc(props[2].Value) if hasattr(self.importer, 'update'): self.importer.update(item, flags) except (MAPIErrorNotFound, MAPIErrorNoAccess): # XXX, mail already deleted, can we do this in a cleaner way? self.log.debug('received change for entryid %s, but it could not be opened', _benc(entryid.Value)) except Exception: self.log.error('could not process change for entryid %s (%r):', _benc(entryid.Value), props) self.log.error(traceback.format_exc()) if self.stats: self.stats['errors'] += 1 raise MAPIError(SYNC_E_IGNORE)
def _organizer_props(cal_item, item): has_organizer = False table = cal_item.mapiobj.OpenProperty( PR_MESSAGE_RECIPIENTS, IID_IMAPITable, MAPI_UNICODE, 0) for row in table.QueryRows(2147483647, 0): recipient_flags = PpropFindProp(row, PR_RECIPIENT_FLAGS) if (recipient_flags and \ recipient_flags.Value == (recipOrganizer | recipSendable)): has_organizer = True break if not has_organizer: orgprops = [ SPropValue(PR_ENTRYID, item.prop(PR_SENT_REPRESENTING_ENTRYID).value), SPropValue(PR_DISPLAY_NAME_W, item.prop(PR_SENT_REPRESENTING_NAME_W).value), SPropValue(PR_EMAIL_ADDRESS_W, item.prop(PR_SENT_REPRESENTING_EMAIL_ADDRESS_W).value), SPropValue(PR_RECIPIENT_TYPE, MAPI_TO), SPropValue(PR_RECIPIENT_DISPLAY_NAME_W, item.prop(PR_SENT_REPRESENTING_NAME_W).value), SPropValue(PR_ADDRTYPE_W, item.prop(PR_SENT_REPRESENTING_ADDRTYPE_W).value), # TODO php SPropValue(PR_RECIPIENT_TRACKSTATUS, 0), SPropValue(PR_RECIPIENT_FLAGS, (recipOrganizer | recipSendable)), ] # TODO not in exception message? repr_search_key = item.get(PR_SENT_REPRESENTING_SEARCH_KEY) if repr_search_key: orgprops.append(SPropValue(PR_SEARCH_KEY, repr_search_key)) return orgprops
def index(self, key): """Return key->row dictionary keyed on given column (proptag).""" d = {} for row in self.mapitable.QueryRows(2147483647, 0): d[PpropFindProp(row, key).Value] = \ dict((c.ulPropTag, c.Value) for c in row) return d
def _organizer_props(cal_item, item): has_organizer = False table = cal_item.mapiobj.OpenProperty(PR_MESSAGE_RECIPIENTS, IID_IMAPITable, MAPI_UNICODE, 0) for row in table.QueryRows(-1, 0): recipient_flags = PpropFindProp(row, PR_RECIPIENT_FLAGS) if recipient_flags and recipient_flags.Value == (recipOrganizer | recipSendable): # XXX has_organizer = True break if not has_organizer: return [ SPropValue(PR_ENTRYID, item.prop(PR_SENT_REPRESENTING_ENTRYID).value), SPropValue(PR_DISPLAY_NAME_W, item.prop(PR_SENT_REPRESENTING_NAME_W).value), SPropValue(PR_EMAIL_ADDRESS_W, item.prop(PR_SENT_REPRESENTING_EMAIL_ADDRESS_W).value), SPropValue(PR_RECIPIENT_TYPE, MAPI_TO), SPropValue(PR_RECIPIENT_DISPLAY_NAME_W, item.prop(PR_SENT_REPRESENTING_NAME_W).value), SPropValue( PR_ADDRTYPE_W, item.prop(PR_SENT_REPRESENTING_ADDRTYPE_W).value), # XXX php SPropValue(PR_RECIPIENT_TRACKSTATUS, 0), SPropValue(PR_RECIPIENT_FLAGS, (recipOrganizer | recipSendable)), SPropValue(PR_SEARCH_KEY, item.prop(PR_SENT_REPRESENTING_SEARCH_KEY).value), ]
def company(self): """Store :class:`company <Company>`.""" if self.server.multitenant: table = self.server.sa.OpenUserStoresTable(MAPI_UNICODE) table.Restrict(SPropertyRestriction(RELOP_EQ, PR_EC_STOREGUID, SPropValue(PR_EC_STOREGUID, _bdec(self.guid))), TBL_BATCH) for row in table.QueryRows(1, 0): storetype = PpropFindProp(row, PR_EC_STORETYPE) if storetype.Value == ECSTORE_TYPE_PUBLIC: companyname = PpropFindProp(row, PR_EC_USERNAME_W) # XXX bug in ECUserStoreTable.cpp? if not companyname: companyname = PpropFindProp(row, PR_EC_COMPANY_NAME_W) # XXX else: companyname = PpropFindProp(row, PR_EC_COMPANY_NAME_W) return self.server.company(companyname.Value) else: return next(self.server.companies())
def ImportFolderChange(self, props): if hasattr(self.importer, 'update'): eid = _benc(PpropFindProp(props, PR_ENTRYID).Value) try: folder = _folder.Folder(store=self.importer.store, entryid=eid) except NotFoundError: self.log.debug( "received folderchange but could not find folder with entryid: '%s'", eid) return self.importer.update(folder)
def type_(self): """Store type (*private*, *public*, *archive*).""" table = self.server.sa.OpenUserStoresTable(MAPI_UNICODE) table.Restrict(SPropertyRestriction(RELOP_EQ, PR_EC_STOREGUID, SPropValue(PR_EC_STOREGUID, _bdec(self.guid))), TBL_BATCH) for row in table.QueryRows(1, 0): storetype = PpropFindProp(row, PR_EC_STORETYPE) if storetype: return { ECSTORE_TYPE_PRIVATE: 'private', ECSTORE_TYPE_ARCHIVE: 'archive', ECSTORE_TYPE_PUBLIC: 'public', }[storetype.Value]
def _parse_rule(store): userids, deletion = [], False for rule in store.inbox.rules(): if PR_RULE_PROVIDER_W in rule.mapirow and PR_RULE_ACTIONS in rule.mapirow: if rule.mapirow[PR_RULE_PROVIDER_W] == u'Schedule+ EMS Interface': actions = rule.mapirow[PR_RULE_ACTIONS].lpAction if actions and actions[0].acttype == ACTTYPE.OP_DELEGATE: for addrentry in actions[0].actobj.lpadrlist: entryid = PpropFindProp(addrentry, PR_ENTRYID) if entryid: userids.append(entryid.Value) if len(actions) >= 2 and actions[1].acttype == ACTTYPE.OP_DELETE: deletion = True return userids, deletion
def stores(self): """Return all company :class:`stores <Store>`.""" if self.server.multitenant: table = self.server.sa.OpenUserStoresTable(MAPI_UNICODE) restriction = SPropertyRestriction(RELOP_EQ, PR_EC_COMPANY_NAME_W, SPropValue(PR_EC_COMPANY_NAME_W, self.name)) table.Restrict(restriction, TBL_BATCH) for row in table.QueryRows(-1, 0): prop = PpropFindProp(row, PR_EC_STOREGUID) if prop: yield _store.Store(_hex(prop.Value), self.server) else: for store in self.server.stores(): yield store
def ImportFolderChange(self, props): if hasattr(self.importer, 'update'): eid = _benc(PpropFindProp(props, PR_ENTRYID).Value) folder = _folder.Folder(store=self.importer.store, entryid=eid) self.importer.update(folder)
def _create_exception(self, basedate, item=None, copytags=None, merge=False, recips_from=None): cal_item = self.item # create embedded item message_flags = MSGFLAG_READ if item and item.get(PR_MESSAGE_FLAGS) == 0: # XXX wut/php compat message_flags |= MSGFLAG_UNSENT message = cal_item.create_item(message_flags, hidden=True) self._update_embedded(basedate, message, item, copytags, create=True) message[PidLidResponseStatus] = respDeclined | respOrganized # XXX php bug for merge case? if copytags: message[PidLidBusyStatus] = 0 # copy over recipients (XXX check php delta stuff..) item = item or recips_from if item: table = item.mapiobj.OpenProperty(PR_MESSAGE_RECIPIENTS, IID_IMAPITable, MAPI_UNICODE, 0) table.SetColumns(_meetingrequest.RECIP_PROPS, 0) recips = list(table.QueryRows(-1, 0)) for recip in recips: flags = PpropFindProp(recip, PR_RECIPIENT_FLAGS) if not flags: recip.append(SPropValue(PR_RECIPIENT_FLAGS, recipExceptionalResponse | recipSendable)) if copytags: for recip in recips: recip.append(SPropValue(PR_RECIPIENT_FLAGS, recipExceptionalDeleted | recipSendable)) recip.append(SPropValue(PR_RECIPIENT_TRACKSTATUS, 0)) organiser = _meetingrequest._organizer_props(cal_item, item) if organiser and not merge: # XXX merge -> initialize? recips.insert(0, organiser) message.mapiobj.ModifyRecipients(MODRECIP_ADD, recips) _utils._save(message.mapiobj) _utils._save(message._attobj) _utils._save(cal_item.mapiobj) # XXX attachments? # update blob self.deleted_instance_count += 1 deldate = _timezone._from_utc(basedate, self._tzinfo) deldate_val = _utils.unixtime_to_rectime(time.mktime(deldate.timetuple())) self._deleted_instance_dates.append(deldate_val) self._deleted_instance_dates.sort() self._modified_instance_count += 1 moddate = message.prop(PidLidAppointmentStartWhole).value daystart = moddate - datetime.timedelta(hours=moddate.hour, minutes=moddate.minute) # XXX different approach in php? seconds? localdaystart = _timezone._from_utc(daystart, self._tzinfo) moddate_val = _utils.unixtime_to_rectime(time.mktime(localdaystart.timetuple())) self._modified_instance_dates.append(moddate_val) self._modified_instance_dates.sort() exception = {} extended_exception = {} self._update_exception(cal_item, message, deldate_val, exception, extended_exception, copytags, create=True, orig_item=item) self._exceptions.append(exception) # no evidence of sorting self._extended_exceptions.append(extended_exception) self._save() # update calitem self._update_calitem()
def ImportFolderChange(self, props): eid = _benc(PpropFindProp(props, PR_ENTRYID).Value) folder = self.importer.store.folder(entryid=eid) self.importer.update(folder)
def dict_(self, key, value): d = {} for row in self.mapitable.QueryRows(-1, 0): d[PpropFindProp(row, key).Value] = PpropFindProp(row, value).Value return d
def process_response(self): """ Process meeting request response """ if not self.is_response: raise Error('item is not a meeting request response') if self._check_processed(): return cal_item = self.calendar_item basedate = self.basedate # modify calendar item or embedded message (in case of exception) attach = None message = None if basedate: if cal_item.recurring: recurrence = cal_item.recurrence recurrence._update_calitem( self.item) # XXX via create/modify exception if recurrence.is_exception(basedate): message = recurrence.exception_message(basedate) owner_appt_id = self.item.get_value(PR_OWNER_APPT_ID) if owner_appt_id is not None: message.set_value(PR_OWNER_APPT_ID, owner_appt_id) attach = message._attobj else: message = cal_item if not message: return # update recipient track status table = message.mapiobj.OpenProperty(PR_MESSAGE_RECIPIENTS, IID_IMAPITable, MAPI_UNICODE, 0) table.SetColumns(RECIP_PROPS, 0) # XXX why do things get lot witout this rows = table.QueryRows(-1, 0) for row in rows: if self._compare_ab_entryids( PpropFindProp(row, PR_ENTRYID).Value, self.item.prop(PR_SENT_REPRESENTING_ENTRYID).value): trackstatus = PpropFindProp(row, PR_RECIPIENT_TRACKSTATUS) trackstatus_time = PpropFindProp( row, PR_RECIPIENT_TRACKSTATUS_TIME) attendee_crit_change = self.item.get_prop( PidLidAttendeeCriticalChange) if trackstatus_time and attendee_crit_change and \ attendee_crit_change.mapiobj.Value <= trackstatus_time.Value: continue if trackstatus_time: trackstatus_time.Value = attendee_crit_change.mapiobj.Value else: row.append( SPropValue(PR_RECIPIENT_TRACKSTATUS_TIME, attendee_crit_change.mapiobj.Value)) if trackstatus: trackstatus.Value = self.track_status else: row.append( SPropValue(PR_RECIPIENT_TRACKSTATUS, self.track_status)) if self.item.get_value( PidLidAppointmentCounterProposal) is True: row.extend([ SPropValue(PR_PROPOSED_NEWTIME, True), SPropValue( PR_PROPOSED_NEWTIME_START, self.item.prop(PidLidAppointmentProposedStartWhole ).mapiobj.Value), SPropValue( PR_PROPOSED_NEWTIME_END, self.item.prop(PidLidAppointmentProposedEndWhole). mapiobj.Value), ]) message.mapiobj.ModifyRecipients(MODRECIP_MODIFY, rows) # counter proposal if self.item.get_value(PidLidAppointmentCounterProposal): message.set_value(PidLidAppointmentCounterProposal, True) # save all the things message.mapiobj.SaveChanges(KEEP_OPEN_READWRITE) if attach: attach.SaveChanges(KEEP_OPEN_READWRITE) cal_item.mapiobj.SaveChanges(KEEP_OPEN_READWRITE)
def _create_meetingrequest(cal_item, item, basedate=None): # TODO Update the calendar item, for tracking status # TODO Set the body of the message like WebApp / OL does. # TODO Whitelist properties? item2 = item.copy(item.store.outbox) cancel = item.canceled # remove meeting organizer TODO just copy correct ones? or why is the # organizer MAPI_TO? table = item2.mapiobj.OpenProperty( PR_MESSAGE_RECIPIENTS, IID_IMAPITable, MAPI_UNICODE, 0) table.SetColumns(RECIP_PROPS, 0) orgs = [] for row in table.QueryRows(2147483647, 0): recipient_flags = PpropFindProp(row, PR_RECIPIENT_FLAGS) if recipient_flags and recipient_flags.Value & recipOrganizer: orgs.append(row) item2.mapiobj.ModifyRecipients(MODRECIP_REMOVE, orgs) # set meeting request props if cancel: item2.message_class = 'IPM.Schedule.Meeting.Canceled' else: item2.message_class = 'IPM.Schedule.Meeting.Request' stateflags = ASF_MEETING | ASF_RECEIVED if cancel: stateflags |= ASF_CANCELED item2.subject = 'Canceled: '+item2.subject item2[PidLidAppointmentStateFlags] = stateflags # create appointment goid if not there cleangoid = cal_item.get(PidLidCleanGlobalObjectId) if cleangoid is None: cleangoid = _generate_goid() cal_item[PidLidGlobalObjectId] = cleangoid cal_item[PidLidCleanGlobalObjectId] = cleangoid # update sequence props sequence = cal_item.get(PidLidAppointmentSequence) if sequence is None: cal_item[PidLidAppointmentSequence] = 0 cal_item[PidLidAppointmentLastSequence] = 0 else: cal_item[PidLidAppointmentSequence] = sequence+1 cal_item[PidLidAppointmentLastSequence] = sequence+1 item2[PidLidAppointmentSequence] = \ cal_item[PidLidAppointmentSequence] item2[PidLidAppointmentLastSequence] = \ cal_item[PidLidAppointmentLastSequence] item2[PidLidResponseStatus] = respNotResponded # set item goids item2[PidLidCleanGlobalObjectId] = cleangoid if basedate: datefield = struct.pack('>H2B', basedate.year, basedate.month, basedate.day) goid = cleangoid[:16] + datefield + cleangoid[20:] item2[PidLidGlobalObjectId] = goid item2[PidLidRecurring] = False # update for non-exception TODO don't overwrite if exception if cancel: item2[PidLidAppointmentStartWhole] = basedate item2[PidLidAppointmentEndWhole] = \ basedate + (cal_item.end - cal_item.start) item2[PR_START_DATE] = basedate item2[PR_END_DATE] = basedate + (cal_item.end - cal_item.start) item2[PidLidExceptionReplaceTime] = \ datetime.datetime(basedate.year, basedate.month, basedate.day) return item2
def process_response(self): """Process meeting request response.""" if not self.is_response: raise Error('item is not a meeting request response') if self._check_processed(): self.log.warning('response already processed') return cal_item = self.calendar_item basedate = self.basedate # modify calendar item or embedded message (in case of exception) attach = None message = None if basedate: if cal_item.recurring: recurrence = cal_item.recurrence recurrence._update_calitem() # TODO via create/modify exception if recurrence._is_exception(basedate): message = recurrence._exception_message(basedate) owner_appt_id = self.item.get(PR_OWNER_APPT_ID) if owner_appt_id is not None: message[PR_OWNER_APPT_ID] = owner_appt_id attach = message._attobj else: message = cal_item if not message: self.log.debug('no appointment matches response') return # update recipient track status table = message.mapiobj.OpenProperty( PR_MESSAGE_RECIPIENTS, IID_IMAPITable, MAPI_UNICODE, 0) # TODO things seem to get lost without this table.SetColumns(RECIP_PROPS, 0) rows = table.QueryRows(2147483647, 0) for row in rows: if self._compare_ab_entryids( PpropFindProp(row, PR_ENTRYID).Value, self.item.prop(PR_SENT_REPRESENTING_ENTRYID).value ): trackstatus = PpropFindProp(row, PR_RECIPIENT_TRACKSTATUS) trackstatus_time = PpropFindProp(row, PR_RECIPIENT_TRACKSTATUS_TIME) attendee_crit_change = \ self.item.get_prop(PidLidAttendeeCriticalChange) if (trackstatus_time and \ attendee_crit_change and \ attendee_crit_change.mapiobj.Value <= \ trackstatus_time.Value): continue if trackstatus_time: trackstatus_time.Value = attendee_crit_change.mapiobj.Value else: row.append(SPropValue(PR_RECIPIENT_TRACKSTATUS_TIME, attendee_crit_change.mapiobj.Value)) if trackstatus: trackstatus.Value = self.track_status else: row.append(SPropValue( PR_RECIPIENT_TRACKSTATUS, self.track_status)) if self.item.get(PidLidAppointmentCounterProposal) is True: start = self.item.prop(PidLidAppointmentProposedStartWhole) end = self.item.prop(PidLidAppointmentProposedEndWhole) row.extend([ SPropValue(PR_PROPOSED_NEWTIME, True), SPropValue(PR_PROPOSED_NEWTIME_START, start.mapiobj.Value), SPropValue(PR_PROPOSED_NEWTIME_END, end.mapiobj.Value), ]) message.mapiobj.ModifyRecipients(MODRECIP_MODIFY, rows) # counter proposal if self.item.get(PidLidAppointmentCounterProposal): message[PidLidAppointmentCounterProposal] = True # save all the things _utils._save(message.mapiobj) if attach: _utils._save(attach) _utils._save(cal_item.mapiobj)
def _modify_exception(self, basedate, item=None, copytags=None, **kwargs): # XXX 'item' too MR specific cal_item = self.item # update embedded item for message in cal_item.items(): # XXX no cal_item? to helper replacetime = message.get(PidLidExceptionReplaceTime) if replacetime and replacetime.date() == basedate.date(): self._update_embedded(basedate, message, item, copytags, **kwargs) if item: icon_index = item.get(PR_ICON_INDEX) if not copytags and icon_index is not None: message[PR_ICON_INDEX] = icon_index _utils._save(message._attobj) break else: return # XXX exception if copytags: # XXX bug in php code? (setallrecipients, !empty..) message[PidLidBusyStatus] = 0 table = message.mapiobj.OpenProperty(PR_MESSAGE_RECIPIENTS, IID_IMAPITable, MAPI_UNICODE, 0) table.SetColumns(_meetingrequest.RECIP_PROPS, 0) recips = list(table.QueryRows(-1, 0)) for recip in recips: flags = PpropFindProp(recip, PR_RECIPIENT_FLAGS) if flags and flags.Value != (recipOrganizer | recipSendable): flags.Value = recipExceptionalDeleted | recipSendable trackstatus = PpropFindProp(recip, PR_RECIPIENT_TRACKSTATUS) if not trackstatus: recip.append(SPropValue(PR_RECIPIENT_TRACKSTATUS, 0)) message.mapiobj.ModifyRecipients(MODRECIP_MODIFY, recips) _utils._save(message.mapiobj) _utils._save(message._attobj) _utils._save(cal_item.mapiobj) # update blob basedate_val = _utils.unixtime_to_rectime(time.mktime(_timezone._from_utc(basedate, self._tzinfo).timetuple())) startdate = _timezone._from_utc(message.prop(PidLidAppointmentStartWhole).value, self._tzinfo) startdate_val = _utils.unixtime_to_rectime(time.mktime(startdate.timetuple())) for i, exception in enumerate(self._exceptions): if exception['original_start_date'] == basedate_val: # TODO offset, as below? current_startdate_val = exception['start_datetime'] - self._starttime_offset for j, val in enumerate(self._modified_instance_dates): if val == current_startdate_val: self._modified_instance_dates[j] = startdate_val - self._starttime_offset self._modified_instance_dates.sort() break extended_exception = self._extended_exceptions[i] self._update_exception(cal_item, message, basedate_val, exception, extended_exception, copytags, create=False, orig_item=item, **kwargs) self._save() # update calitem self._update_calitem()
def index(self, key): d = {} for row in self.mapitable.QueryRows(-1, 0): d[PpropFindProp(row, key).Value] = dict( (c.ulPropTag, c.Value) for c in row) return d
def modify_exception(self, basedate, item, copytags=None): # XXX 'item' too MR specific tz = item.get_value(PidLidTimeZoneStruct) cal_item = self.item # update embedded item for message in cal_item.items(): # XXX no cal_item? to helper replacetime = message.get_value(PidLidExceptionReplaceTime) if replacetime and replacetime.date() == basedate.date(): self._update_embedded(basedate, message, item, copytags) icon_index = item.get_value(PR_ICON_INDEX) if not copytags and icon_index is not None: message.set_value(PR_ICON_INDEX, icon_index) message._attobj.SaveChanges(KEEP_OPEN_READWRITE) break else: return # XXX exception if copytags: # XXX bug in php code? (setallrecipients, !empty..) message.set_value(PidLidBusyStatus, 0) table = message.mapiobj.OpenProperty(PR_MESSAGE_RECIPIENTS, IID_IMAPITable, MAPI_UNICODE, 0) table.SetColumns(_meetingrequest.RECIP_PROPS, 0) recips = list(table.QueryRows(-1, 0)) for recip in recips: flags = PpropFindProp(recip, PR_RECIPIENT_FLAGS) if flags and flags.Value != (recipOrganizer | recipSendable): flags.Value = recipExceptionalDeleted | recipSendable trackstatus = PpropFindProp(recip, PR_RECIPIENT_TRACKSTATUS) recip.append(SPropValue(PR_RECIPIENT_TRACKSTATUS, 0)) message.mapiobj.ModifyRecipients(MODRECIP_MODIFY, recips) message.mapiobj.SaveChanges(KEEP_OPEN_READWRITE) message._attobj.SaveChanges(KEEP_OPEN_READWRITE) cal_item.mapiobj.SaveChanges(KEEP_OPEN_READWRITE) # update blob basedate_val = _utils.unixtime_to_rectime( time.mktime(_utils._from_gmt(basedate, tz).timetuple())) startdate = _utils._from_gmt( message.prop(PidLidAppointmentStartWhole).value, tz) startdate_val = _utils.unixtime_to_rectime( time.mktime(startdate.timetuple())) enddate = _utils._from_gmt( message.prop(PidLidAppointmentEndWhole).value, tz) enddate_val = _utils.unixtime_to_rectime( time.mktime(enddate.timetuple())) for i, exception in enumerate(self.exceptions): if exception['original_start_date'] == basedate_val: current_startdate_val = exception[ 'start_datetime'] - self.starttime_offset for j, val in enumerate(self.modified_instance_dates): if val == current_startdate_val: self.modified_instance_dates[ j] = startdate_val - self.starttime_offset self.modified_instance_dates.sort() break extended_exception = self.extended_exceptions[i] self._update_exceptions(cal_item, message, startdate_val, enddate_val, basedate_val, exception, extended_exception, copytags, create=False) self._save() # update calitem self._update_calitem(item)