Пример #1
0
 def makeChange(self, item, change):
     event = EventStamp(item)
     changeType = change[1]
     if changeType == 'set':
         EventStamp(item).changeThis(change[2], change[3])
         return change[2]
     elif changeType in ('addStamp', 'removeStamp'):
         event.changeThis()
         method = getattr(change[0], change[1])
         return method(item, *change[2:])
     elif changeType == 'add' and change[2] is schema.ns("osaf.pim", item.itsView).trashCollection:
         event.deleteThis()
     elif changeType in ('add', 'append', 'remove'):
         attrName, newValue = _multiChange(item, change)
         EventStamp(item).changeThis(attrName, newValue)
         return attrName
Пример #2
0
    def appendChange(self, desc, op, attr, *args):
        super(RecurrenceProxy, self).appendChange(desc, op, attr, *args)
        item = self.proxiedItem

        if (not stamping.has_stamp(item, EventStamp)
                or EventStamp(item).rruleset is None):
            super(RecurrenceProxy, self).makeChanges()
        elif self.changing is not None:
            self.makeChanges()
Пример #3
0
 def makeChange(self, item, change):
     # easy for set, not too bad for stamp addition, del is maybe
     # tricky, and add/remove require duplicating reflists
     event = EventStamp(item)
     changeType = change[1]
     if changeType == 'set':
         attr = change[2]
         self._updateEdited(
             item for item in event.modifications
                 if not item.hasModifiedAttribute(attr)
         )
         event.changeAll(attr, change[3])
         return attr
     elif changeType == 'addStamp':
         self._updateEdited(
             item for item in event.modifications
                 if not stamping.has_stamp(item, change[2])
         )
         event.addStampToAll(change[2].stamp_type)
         return stamping.Stamp.stamp_types.name
     elif changeType == 'removeStamp':
         self._updateEdited(
             item for item in event.modifications
                 if stamping.has_stamp(item, change[2])
         )
         event.removeStampFromAll(change[2].stamp_type)
         return stamping.Stamp.stamp_types.name
     elif changeType in ('add', 'remove', 'append'):
         attr = change[0].descriptor.name
         self._updateEdited(
             item for item in event.modifications
                 if not item.hasModifiedAttribute(attr)
         )
         masterItem = event.getMaster().itsItem
         attrName, newValue = _multiChange(masterItem, change)
         with EventStamp(masterItem).noRecurrenceChanges():
             setattr(masterItem, attrName, newValue)
         return attrName
     elif (changeType == 'delete' and
           change[0].descriptor.name == EventStamp.rruleset.name):
           event.removeRecurrence()
           return EventStamp.rruleset.name
     else:
         assert False
Пример #4
0
    def markProxyEdited(self, proxy, attrs):
        if self.editedItems:
            for item in self.editedItems:
                if not reminders.isDead(item):
                    with EventStamp(item).noRecurrenceChanges():
                        for attr in self.EDIT_ATTRIBUTES:
                            if item.hasLocalAttributeValue(attr):
                                delattr(item, attr)

            del self.editedItems

        super(CHANGE_ALL, self).markProxyEdited(proxy, attrs)
Пример #5
0
 def makeChange(self, item, change):
     event = EventStamp(item)
     changeType = change[1]
     if changeType == 'set':
         EventStamp(item).changeThis(change[2], change[3])
         return change[2]
     elif changeType in ('addStamp', 'removeStamp'):
         event.changeThis()
         method = getattr(change[0], change[1])
         return method(item, *change[2:])
     elif changeType == 'add' and change[2] is schema.ns(
             "osaf.pim", item.itsView).trashCollection:
         event.deleteThis()
     elif changeType in ('add', 'append', 'remove'):
         attrName, newValue = _multiChange(item, change)
         EventStamp(item).changeThis(attrName, newValue)
         return attrName
Пример #6
0
 def makeChange(self, item, change):
     event = EventStamp(item)
     changeType = change[1]
     # as above, for CHANGE_ALL
     if changeType == 'set':
         attr = change[2]
         event.changeThisAndFuture(attr, change[3])
         self._updateEdited(item for item in event.modifications
                            if not item.hasModifiedAttribute(attr))
         return attr
     elif changeType == 'add' and change[2] is schema.ns(
             "osaf.pim", item.itsView).trashCollection:
         event.deleteThisAndFuture()
     elif changeType in ('addStamp', 'removeStamp', 'add', 'remove',
                         'append'):
         event.changeThisAndFuture()
         return super(CHANGE_FUTURE, self).makeChange(item, change)
     else:
         assert False
Пример #7
0
 def makeChange(self, item, change):
     event = EventStamp(item)
     changeType = change[1]
     # as above, for CHANGE_ALL
     if changeType == 'set':
         attr = change[2]
         event.changeThisAndFuture(attr, change[3])
         self._updateEdited(
             item for item in event.modifications
                 if not item.hasModifiedAttribute(attr)
         )
         return attr
     elif changeType == 'add' and change[2] is schema.ns("osaf.pim", item.itsView).trashCollection:
         event.deleteThisAndFuture()        
     elif changeType in ('addStamp', 'removeStamp', 'add', 'remove', 'append'):
         event.changeThisAndFuture()
         return super(CHANGE_FUTURE, self).makeChange(item, change)
     else:
         assert False
Пример #8
0
def installParcel(parcel, oldVersion=None):
    view = parcel.itsView

    # Create our one collection of indexDefinition mappings; when each gets
    # created, its __init__ will add it to this collection automagically.
    AllIndexDefinitions.update(parcel, "allIndexDefinitions")
    Reference.update(parcel, "currentContact")

    MailPreferences.update(parcel, "MailPrefs")
    Reference.update(parcel, "currentMeEmailAddress")

    cur = Reference.update(parcel, "currentIncomingAccount")
    cur1 = Reference.update(parcel, "currentOutgoingAccount")

    if cur.item is None:
        cur.item = IMAPAccount(
            itsView=view,
            displayName=_(u"Incoming Mail"),
            replyToAddress=EmailAddress(itsView=view),
            password=password.Password(itsView=view),
        )

    if cur1.item is None:
        cur1.item = SMTPAccount(itsView=view, displayName=_(u"Outgoing Mail"), password=password.Password(itsView=view))

    trashCollection = ListCollection.update(parcel, "trashCollection", displayName=_(u"Trash"))

    notes = KindCollection.update(parcel, "noteCollection", kind=Note.getKind(view), recursive=True)

    mine = UnionCollection.update(parcel, "mine")

    # it would be nice to get rid of these intermediate fully-fledged
    # item collections, and replace them with lower level Set objects
    mineNotes = IntersectionCollection.update(parcel, "mineNotes", sources=[mine, notes])

    nonOccurrenceFilter = NonOccurrenceFilter(None, parcel)

    nonRecurringNotes = FilteredCollection.update(
        parcel,
        "nonRecurringNotes",
        source=mineNotes,
        filterMethod=(nonOccurrenceFilter, "isNonOccurrence"),
        filterAttributes=[EventStamp.occurrenceFor.name, EventStamp.modificationFor.name, EventStamp.occurrences.name],
    )
    nonRecurringNotes.addIndex("__adhoc__", "numeric")

    allContentItems = KindCollection.update(parcel, "allContentItems", kind=ContentItem.getKind(view), recursive=True)

    contentItems = FilteredCollection.update(
        parcel,
        "contentItems",
        source=allContentItems,
        filterMethod=(nonOccurrenceFilter, "isNotPureOccurrence"),
        filterAttributes=[EventStamp.occurrenceFor.name, EventStamp.modificationFor.name],
    )

    allReminders = KindCollection.update(parcel, "allReminders", kind=Reminder.getKind(view), recursive=True)

    allFutureReminders = FilteredCollection.update(
        parcel,
        "allFutureReminders",
        source=allReminders,
        filterMethod=(UnexpiredFilter(None, parcel), "notExpired"),
        filterAttributes=[UnexpiredFilter.findValuePair[0]],
    )

    allFutureReminders.addIndex(
        "reminderPoll",
        "method",
        method=(UnexpiredFilter(None, parcel), "compare"),
        monitor=[UnexpiredFilter.findValuePair[0]],
    )

    # the "All" / "My" collection
    allCollection = SmartCollection.update(
        parcel,
        "allCollection",
        displayName=_(u"Dashboard"),
        source=nonRecurringNotes,
        exclusions=trashCollection,
        trash=None,
    )
    # kludge to improve on bug 4144 (not a good long term fix but fine for 0.6)
    allCollection.addIndex("__adhoc__", "numeric")

    events = EventStamp.getCollection(view)
    eventComparator = EventComparator.update(parcel, "eventComparator")

    EventStamp.addIndex(
        events,
        "effectiveStart",
        "method",
        method=(eventComparator, "cmpStartTime"),
        monitor=(EventStamp.startTime, EventStamp.allDay, EventStamp.anyTime),
        kind=ContentItem.getKind(view),
    )
    EventStamp.addIndex(
        events,
        "effectiveEnd",
        "method",
        method=(eventComparator, "cmpEndTime"),
        monitor=(EventStamp.startTime, EventStamp.allDay, EventStamp.anyTime, EventStamp.duration),
        kind=ContentItem.getKind(view),
    )

    # floatingEvents need to be reindexed in effectiveStart and effectiveEnd
    # when the floating timezone changes
    filterAttributes = [entry[0] for entry in _FILTER_ATTRIBUTES]
    floatingEvents = FilteredCollection.update(
        parcel,
        "floatingEvents",
        source=events,
        filterMethod=(FloatingEventFilter(None, parcel), "isFloatingEvent"),
        filterAttributes=filterAttributes,
    )
    floatingEvents.addIndex("__adhoc__", "numeric")

    # UTCEvents need to be reindexed in effectiveStartNoTZ and effectiveEndNoTZ
    # when the floating timezone changes, because UTC events are treated
    # specially
    UTCEvents = FilteredCollection.update(
        parcel,
        "UTCEvents",
        source=events,
        filterMethod=(UTCEventFilter(None, parcel), "isUTCEvent"),
        filterAttributes=filterAttributes,
    )
    UTCEvents.addIndex("__adhoc__", "numeric")

    longEvents = FilteredCollection.update(
        parcel,
        "longEvents",
        source=events,
        filterMethod=(LongEventFilter(None, parcel), "isLongEvent"),
        filterAttributes=[EventStamp.duration.name],
    )
    longEvents.addIndex("effectiveStart", "subindex", superindex=(events, events.__collection__, "effectiveStart"))
    longEvents.addIndex("effectiveEnd", "subindex", superindex=(events, events.__collection__, "effectiveEnd"))

    filterAttributes = (EventStamp.rruleset.name, EventStamp.occurrences.name)
    masterFilter = "view.hasTrueValues(uuid, '%s', '%s')" % filterAttributes
    nonMasterFilter = "not " + masterFilter

    masterEvents = FilteredCollection.update(
        parcel, "masterEvents", source=events, filterExpression=masterFilter, filterAttributes=list(filterAttributes)
    )

    nonMasterEvents = FilteredCollection.update(
        parcel,
        "nonMasterEvents",
        source=events,
        filterExpression=nonMasterFilter,
        filterAttributes=list(filterAttributes),
    )

    MasterEventWatcher.update(parcel, "masterEventWatcher", targetCollection=masterEvents)

    EventStamp.addIndex(
        masterEvents,
        "recurrenceEnd",
        "method",
        method=(eventComparator, "cmpRecurEnd"),
        monitor=(EventStamp.recurrenceEnd,),
    )

    EventStamp.addIndex(
        masterEvents, "effectiveStart", "subindex", superindex=(events, events.__collection__, "effectiveStart")
    )

    locations = KindCollection.update(parcel, "locations", kind=Location.getKind(view), recursive=True)

    locations.addIndex("locationName", "value", attribute="displayName", nodefer=True)

    mailCollection = mail.MailStamp.getCollection(view)

    kind = mail.EmailAddress.getKind(view)
    emailAddressCollection = KindCollection.update(parcel, "emailAddressCollection", kind=kind, recursive=True)
    emailComparator = EmailComparator.update(parcel, "emailComparator")
    emailAddressCollection.addIndex(
        "emailAddress", "method", method=(emailComparator, "cmpAddress"), monitor="emailAddress", nodefer=True
    )
    emailAddressCollection.addIndex(
        "fullName", "method", method=(emailComparator, "cmpFullName"), monitor="fullName", nodefer=True
    )
    emailAddressCollection.addIndex(
        "both",
        "method",
        method=(emailComparator, "cmpBoth"),
        monitor=("emailAddress", "fullName"),
        nodefer=True,
        kind=kind,
    )

    # Contains all current me addresses (that is, referenced by an account)
    currentMeEmailAddresses = ListCollection.update(parcel, "currentMeEmailAddresses")

    currentMeEmailAddresses.addIndex(
        "emailAddress", "method", method=(emailComparator, "cmpAddress"), monitor="emailAddress"
    )

    # Contains all current and former me addresses
    meEmailAddressCollection = ListCollection.update(parcel, "meEmailAddressCollection")

    meEmailAddressCollection.addIndex(
        "emailAddress", "method", method=(emailComparator, "cmpAddress"), monitor="emailAddress"
    )

    inSource = ToMeFilter.makeCollection(parcel, "inSource", mailCollection)
    # this index must be added to shield from the duplicate
    # source (mailCollection) that is going to be in mine
    inSource.addIndex("__adhoc__", "numeric")

    # The "In" collection
    inCollection = SmartCollection.update(
        parcel, "inCollection", displayName=_(u"In"), source=inSource, trash=trashCollection, visible=True
    )
    mine.addSource(inCollection)

    outSource = FromMeFilter.makeCollection(parcel, "outSource", mailCollection)
    # this index must be added to shield from the duplicate
    # source (mailCollection) that is going to be in mine
    outSource.addIndex("__adhoc__", "numeric")

    # The "Out" collection
    outCollection = SmartCollection.update(
        parcel, "outCollection", displayName=_(u"Out"), visible=True, source=outSource, trash=trashCollection
    )
    mine.addSource(outCollection)

    allEventsCollection = IntersectionCollection.update(parcel, "allEventsCollection", sources=[allCollection, events])

    searchResultsUnfiltered = SmartCollection.update(parcel, "searchResultsUnfiltered", displayName=messages.UNTITLED)

    searchResults = DifferenceCollection.update(
        parcel, "searchResults", sources=[searchResultsUnfiltered, masterEvents], displayName=messages.UNTITLED
    )

    TriageStatusReminder.update(parcel, "triageStatusReminder")
    startup.Startup.update(parcel, "installWatchers", invoke=__name__ + ".installWatchers")

    tzInstallParcel(parcel)
Пример #9
0
def installParcel(parcel, oldVersion=None):
    view = parcel.itsView

    # Create our one collection of indexDefinition mappings; when each gets
    # created, its __init__ will add it to this collection automagically.
    AllIndexDefinitions.update(parcel, "allIndexDefinitions")
    Reference.update(parcel, 'currentContact')

    MailPreferences.update(parcel, 'MailPrefs')
    Reference.update(parcel, 'currentMeEmailAddress')

    cur = Reference.update(parcel, 'currentIncomingAccount')
    cur1 = Reference.update(parcel, 'currentOutgoingAccount')

    if cur.item is None:
        cur.item = IMAPAccount(itsView=view,
                               displayName=_(u'Incoming Mail'),
                               replyToAddress=EmailAddress(itsView=view),
                               password=password.Password(itsView=view))

    if cur1.item is None:
        cur1.item = SMTPAccount(
            itsView=view,
            displayName=_(u'Outgoing Mail'),
            password=password.Password(itsView=view),
        )

    trashCollection = ListCollection.update(parcel,
                                            'trashCollection',
                                            displayName=_(u"Trash"))

    notes = KindCollection.update(parcel,
                                  'noteCollection',
                                  kind=Note.getKind(view),
                                  recursive=True)

    mine = UnionCollection.update(parcel, 'mine')

    # it would be nice to get rid of these intermediate fully-fledged
    # item collections, and replace them with lower level Set objects
    mineNotes = IntersectionCollection.update(parcel,
                                              'mineNotes',
                                              sources=[mine, notes])

    nonOccurrenceFilter = NonOccurrenceFilter(None, parcel)

    nonRecurringNotes = FilteredCollection.update(
        parcel,
        'nonRecurringNotes',
        source=mineNotes,
        filterMethod=(nonOccurrenceFilter, 'isNonOccurrence'),
        filterAttributes=[
            EventStamp.occurrenceFor.name, EventStamp.modificationFor.name,
            EventStamp.occurrences.name
        ])
    nonRecurringNotes.addIndex('__adhoc__', 'numeric')

    allContentItems = KindCollection.update(parcel,
                                            'allContentItems',
                                            kind=ContentItem.getKind(view),
                                            recursive=True)

    contentItems = FilteredCollection.update(
        parcel,
        'contentItems',
        source=allContentItems,
        filterMethod=(nonOccurrenceFilter, 'isNotPureOccurrence'),
        filterAttributes=[
            EventStamp.occurrenceFor.name, EventStamp.modificationFor.name
        ])

    allReminders = KindCollection.update(parcel,
                                         'allReminders',
                                         kind=Reminder.getKind(view),
                                         recursive=True)

    allFutureReminders = FilteredCollection.update(
        parcel,
        'allFutureReminders',
        source=allReminders,
        filterMethod=(UnexpiredFilter(None, parcel), 'notExpired'),
        filterAttributes=[UnexpiredFilter.findValuePair[0]],
    )

    allFutureReminders.addIndex('reminderPoll',
                                'method',
                                method=(UnexpiredFilter(None,
                                                        parcel), 'compare'),
                                monitor=[UnexpiredFilter.findValuePair[0]])

    # the "All" / "My" collection
    allCollection = SmartCollection.update(
        parcel,
        'allCollection',
        displayName=_(u"Dashboard"),
        source=nonRecurringNotes,
        exclusions=trashCollection,
        trash=None,
    )
    # kludge to improve on bug 4144 (not a good long term fix but fine for 0.6)
    allCollection.addIndex('__adhoc__', 'numeric')

    events = EventStamp.getCollection(view)
    eventComparator = EventComparator.update(parcel, 'eventComparator')

    EventStamp.addIndex(events,
                        'effectiveStart',
                        'method',
                        method=(eventComparator, 'cmpStartTime'),
                        monitor=(EventStamp.startTime, EventStamp.allDay,
                                 EventStamp.anyTime),
                        kind=ContentItem.getKind(view))
    EventStamp.addIndex(events,
                        'effectiveEnd',
                        'method',
                        method=(eventComparator, 'cmpEndTime'),
                        monitor=(EventStamp.startTime, EventStamp.allDay,
                                 EventStamp.anyTime, EventStamp.duration),
                        kind=ContentItem.getKind(view))

    # floatingEvents need to be reindexed in effectiveStart and effectiveEnd
    # when the floating timezone changes
    filterAttributes = [entry[0] for entry in _FILTER_ATTRIBUTES]
    floatingEvents = FilteredCollection.update(
        parcel,
        'floatingEvents',
        source=events,
        filterMethod=(FloatingEventFilter(None, parcel), 'isFloatingEvent'),
        filterAttributes=filterAttributes)
    floatingEvents.addIndex('__adhoc__', 'numeric')

    # UTCEvents need to be reindexed in effectiveStartNoTZ and effectiveEndNoTZ
    # when the floating timezone changes, because UTC events are treated
    # specially
    UTCEvents = FilteredCollection.update(parcel,
                                          'UTCEvents',
                                          source=events,
                                          filterMethod=(UTCEventFilter(
                                              None, parcel), 'isUTCEvent'),
                                          filterAttributes=filterAttributes)
    UTCEvents.addIndex('__adhoc__', 'numeric')

    longEvents = FilteredCollection.update(
        parcel,
        'longEvents',
        source=events,
        filterMethod=(LongEventFilter(None, parcel), 'isLongEvent'),
        filterAttributes=[EventStamp.duration.name])
    longEvents.addIndex('effectiveStart',
                        'subindex',
                        superindex=(events, events.__collection__,
                                    'effectiveStart'))
    longEvents.addIndex('effectiveEnd',
                        'subindex',
                        superindex=(events, events.__collection__,
                                    'effectiveEnd'))

    filterAttributes = (EventStamp.rruleset.name, EventStamp.occurrences.name)
    masterFilter = "view.hasTrueValues(uuid, '%s', '%s')" % filterAttributes
    nonMasterFilter = "not " + masterFilter

    masterEvents = FilteredCollection.update(
        parcel,
        'masterEvents',
        source=events,
        filterExpression=masterFilter,
        filterAttributes=list(filterAttributes))

    nonMasterEvents = FilteredCollection.update(
        parcel,
        'nonMasterEvents',
        source=events,
        filterExpression=nonMasterFilter,
        filterAttributes=list(filterAttributes))

    MasterEventWatcher.update(parcel,
                              'masterEventWatcher',
                              targetCollection=masterEvents)

    EventStamp.addIndex(masterEvents,
                        "recurrenceEnd",
                        'method',
                        method=(eventComparator, 'cmpRecurEnd'),
                        monitor=(EventStamp.recurrenceEnd, ))

    EventStamp.addIndex(masterEvents,
                        'effectiveStart',
                        'subindex',
                        superindex=(events, events.__collection__,
                                    'effectiveStart'))

    locations = KindCollection.update(parcel,
                                      'locations',
                                      kind=Location.getKind(view),
                                      recursive=True)

    locations.addIndex('locationName',
                       'value',
                       attribute='displayName',
                       nodefer=True)

    mailCollection = mail.MailStamp.getCollection(view)

    kind = mail.EmailAddress.getKind(view)
    emailAddressCollection = \
        KindCollection.update(parcel, 'emailAddressCollection',
                              kind=kind,
                              recursive=True)
    emailComparator = EmailComparator.update(parcel, 'emailComparator')
    emailAddressCollection.addIndex('emailAddress',
                                    'method',
                                    method=(emailComparator, 'cmpAddress'),
                                    monitor='emailAddress',
                                    nodefer=True)
    emailAddressCollection.addIndex('fullName',
                                    'method',
                                    method=(emailComparator, 'cmpFullName'),
                                    monitor='fullName',
                                    nodefer=True)
    emailAddressCollection.addIndex('both',
                                    'method',
                                    method=(emailComparator, 'cmpBoth'),
                                    monitor=('emailAddress', 'fullName'),
                                    nodefer=True,
                                    kind=kind)

    # Contains all current me addresses (that is, referenced by an account)
    currentMeEmailAddresses = ListCollection.update(parcel,
                                                    'currentMeEmailAddresses')

    currentMeEmailAddresses.addIndex('emailAddress',
                                     'method',
                                     method=(emailComparator, 'cmpAddress'),
                                     monitor='emailAddress')

    # Contains all current and former me addresses
    meEmailAddressCollection = ListCollection.update(
        parcel, 'meEmailAddressCollection')

    meEmailAddressCollection.addIndex('emailAddress',
                                      'method',
                                      method=(emailComparator, 'cmpAddress'),
                                      monitor='emailAddress')

    inSource = ToMeFilter.makeCollection(parcel, 'inSource', mailCollection)
    # this index must be added to shield from the duplicate
    # source (mailCollection) that is going to be in mine
    inSource.addIndex('__adhoc__', 'numeric')

    # The "In" collection
    inCollection = SmartCollection.update(parcel,
                                          'inCollection',
                                          displayName=_(u"In"),
                                          source=inSource,
                                          trash=trashCollection,
                                          visible=True)
    mine.addSource(inCollection)

    outSource = FromMeFilter.makeCollection(parcel, 'outSource',
                                            mailCollection)
    # this index must be added to shield from the duplicate
    # source (mailCollection) that is going to be in mine
    outSource.addIndex('__adhoc__', 'numeric')

    # The "Out" collection
    outCollection = SmartCollection.update(
        parcel,
        'outCollection',
        displayName=_(u"Out"),
        visible=True,
        source=outSource,
        trash=trashCollection,
    )
    mine.addSource(outCollection)

    allEventsCollection = IntersectionCollection.update(
        parcel, 'allEventsCollection', sources=[allCollection, events])

    searchResultsUnfiltered = SmartCollection.update(
        parcel, 'searchResultsUnfiltered', displayName=messages.UNTITLED)

    searchResults = DifferenceCollection.update(
        parcel,
        'searchResults',
        sources=[searchResultsUnfiltered, masterEvents],
        displayName=messages.UNTITLED)

    TriageStatusReminder.update(parcel, 'triageStatusReminder')
    startup.Startup.update(parcel,
                           "installWatchers",
                           invoke=__name__ + ".installWatchers")

    tzInstallParcel(parcel)
Пример #10
0
 def makeChange(self, item, change):
     # easy for set, not too bad for stamp addition, del is maybe
     # tricky, and add/remove require duplicating reflists
     event = EventStamp(item)
     changeType = change[1]
     if changeType == 'set':
         attr = change[2]
         self._updateEdited(item for item in event.modifications
                            if not item.hasModifiedAttribute(attr))
         event.changeAll(attr, change[3])
         return attr
     elif changeType == 'addStamp':
         self._updateEdited(item for item in event.modifications
                            if not stamping.has_stamp(item, change[2]))
         event.addStampToAll(change[2].stamp_type)
         return stamping.Stamp.stamp_types.name
     elif changeType == 'removeStamp':
         self._updateEdited(item for item in event.modifications
                            if stamping.has_stamp(item, change[2]))
         event.removeStampFromAll(change[2].stamp_type)
         return stamping.Stamp.stamp_types.name
     elif changeType in ('add', 'remove', 'append'):
         attr = change[0].descriptor.name
         self._updateEdited(item for item in event.modifications
                            if not item.hasModifiedAttribute(attr))
         masterItem = event.getMaster().itsItem
         attrName, newValue = _multiChange(masterItem, change)
         with EventStamp(masterItem).noRecurrenceChanges():
             setattr(masterItem, attrName, newValue)
         return attrName
     elif (changeType == 'delete'
           and change[0].descriptor.name == EventStamp.rruleset.name):
         event.removeRecurrence()
         return EventStamp.rruleset.name
     else:
         assert False