Пример #1
0
class ToolBar(Block.RectangularChild):

    colorStyle = schema.One(ColorStyle, defaultValue=None)
    toolSize = schema.One(SizeType)
    separatorWidth = schema.One(schema.Integer, defaultValue=5)
    buttons3D = schema.One(schema.Boolean, defaultValue=False)
    buttonsLabeled = schema.One(schema.Boolean, defaultValue=False)
    schema.addClouds(copying=schema.Cloud(byRef=[colorStyle]))

    def instantiateWidget(self):
        style = wx.TB_HORIZONTAL | wx.TB_MAC_NATIVE_SELECT
        if self.buttons3D:
            style |= wx.TB_3DBUTTONS
        else:
            style |= wx.TB_FLAT
        if self.buttonsLabeled:
            style |= wx.TB_TEXT

        return wxToolBar(self.parentBlock.widget,
                         self.getWidgetID(),
                         style=style)

    def pressed(self, itemName):
        toolBarItem = self.findBlockByName(itemName).widget
        return self.widget.GetToolState(toolBarItem.GetId())

    def press(self, itemName):
        toolBarItem = self.findBlockByName(itemName)
        return self.post(toolBarItem.event, {}, toolBarItem)
Пример #2
0
class ModifyContentsEvent(BlockEvent):
    items = schema.Sequence(schema.Item, initialValue=[])
    operation = schema.One(operationType, initialValue='add')
    copyItems = schema.One(schema.Boolean, initialValue=True)
    selectFirstItem = schema.One(schema.Boolean, initialValue=False)
    disambiguateItemNames = schema.One(schema.Boolean, initialValue=False)
    schema.addClouds(copying=schema.Cloud(byRef=[items]))
Пример #3
0
class BlockEvent(schema.Item):
    dispatchEnum = schema.One(
        dispatchEnumType,
        initialValue='SendToBlockByReference',
    )
    commitAfterDispatch = schema.One(schema.Boolean, initialValue=False)
    destinationBlockReference = schema.One(Block)
    dispatchToBlockName = schema.One(schema.String)
    methodName = schema.One(schema.String)
    blockName = schema.One(schema.String)
    schema.addClouds(copying=schema.Cloud(byCloud=[destinationBlockReference]))

    def __repr__(self):
        # useful for debugging that i've done.  i dunno if event.arguments
        # is guaranteed to be there?  -brendano

        if hasattr(self, "arguments"):
            try:
                name = self.blockName
            except AttributeError:
                name = self.itsUUID
            return "%s, arguments=%s" % (name, repr(self.arguments))

        else:
            return super(BlockEvent, self).__repr__()
Пример #4
0
class ContactName(items.ContentItem):
    "A very simple (and incomplete) representation of a person's name"

    firstName = schema.One(schema.String, initialValue="")
    lastName = schema.One(schema.String, initialValue="")
    contact = schema.One("Contact", inverse="contactName")

    schema.addClouds(sharing=schema.Cloud(firstName, lastName))
Пример #5
0
class BaseItem(Block.Block):
    title = schema.One(LocalizableString)
    helpString = schema.One(LocalizableString, defaultValue=u'')
    operation = schema.One(operationEnumType, defaultValue='None')
    location = schema.One(schema.Text, defaultValue=u'')
    itemLocation = schema.One(schema.Text, defaultValue=u'')
    event = schema.One(Block.BlockEvent,
                       inverse=Block.BlockEvent.menuOrToolForEvent)

    schema.addClouds(copying=schema.Cloud(byRef=[event]))
Пример #6
0
class MyKind1(pim.ContentItem):
    """An example content kind"""

    attr1 = schema.One(schema.Text)
    attr2 = schema.One(schema.Text)

    # Typical clouds include a "copying" cloud, and a "sharing" cloud

    schema.addClouds(sharing=schema.Cloud(
        literal=[attr1, attr2], byValue=[attr3], byCloud=[attr4]))
Пример #7
0
class ContactName(items.ContentItem):
    "A very simple (and incomplete) representation of a person's name"

    firstName = schema.One(schema.Text, initialValue=u"", indexed=True)
    lastName  = schema.One(schema.Text, initialValue=u"", indexed=True)
    contact = schema.One()

    schema.addClouds(
        sharing = schema.Cloud(
            literal = [firstName, lastName]
        )
    )
Пример #8
0
class UserCollection(schema.Annotation):
    schema.kindInfo(annotates=ContentCollection)

    renameable = schema.One(schema.Boolean, defaultValue=True)
    color = schema.One(ColorType)
    iconName = schema.One(schema.Text, defaultValue="")
    colorizeIcon = schema.One(schema.Boolean, defaultValue=True)
    dontDisplayAsCalendar = schema.One(schema.Boolean, defaultValue=False)
    outOfTheBoxCollection = schema.One(schema.Boolean, defaultValue=False)
    canAdd = schema.One(schema.Boolean, defaultValue=True)
    allowOverlay = schema.One(schema.Boolean, defaultValue=True)
    searchMatches = schema.One(schema.Integer, defaultValue=0)
    checked = schema.One(schema.Boolean, defaultValue=False)
    """
      preferredClass is used as a hint to the user-interface to choose the right
      view for the display, e.g. CalendarView for collections that have a
      preferredClass of EventStamp.
    """
    preferredClass = schema.One(schema.Class)

    schema.addClouds(copying=schema.Cloud(byRef=[preferredClass]), )

    def ensureColor(self):
        """
        Make sure the collection has a color. Pick up the next color in a predefined 
        list if none was set.
        """
        if not hasattr(self, 'color'):
            self.color = schema.ns(
                'osaf.usercollections',
                self.itsItem.itsView).collectionColors.nextColor()
        return self

    def setColor(self, colorname):
        """
        Set the collection color by name. Raises an error if colorname doesn't exist.
        """
        hue = None
        for colname, coltitle, colhue in collectionHues:
            if colname == colorname:
                hue = colhue
                break
        if hue is None:
            raise ValueError("Unknown color name")
        rgb = colorsys.hsv_to_rgb(hue / 360.0, 0.5, 1.0)
        self.color = ColorType(int(rgb[0] * 255), int(rgb[1] * 255),
                               int(rgb[2] * 255), 255)

    def setValues(self, **kwds):

        for attr, value in kwds.iteritems():
            setattr(self, attr, value)
Пример #9
0
class MenuItem (Block.Block, DynamicChild):

    menuItemKind = schema.One(menuItemKindEnumType, initialValue = 'Normal')
    accel = schema.One(schema.String, initialValue = '')
    event = schema.One(Block.BlockEvent)
    schema.addClouds(
        copying = schema.Cloud(byCloud = [event])
    )

    def instantiateWidget (self):
        # We'll need a dynamicParent's widget in order to instantiate
        try:
            if isinstance(self.dynamicParent.widget, wxMenu):
                return wxMenuItem(style=wxMenuItem.CalculateWXStyle(self))
        except AttributeError:
            return None
Пример #10
0
class FlickrPhotoMixin(PhotoMixin):
    """
    A mixin that adds flickr attributes to a Note item
    """
    flickrID = schema.One(schema.Text)
    imageURL = schema.One(schema.URL)
    datePosted = schema.One(schema.DateTime)
    tags = schema.Sequence()
    owner = schema.One(schema.Text, indexed=True)

    schema.addClouds(sharing=schema.Cloud(
        literal=[owner, flickrID, imageURL, tags]))

    def _setPhoto(self, photo):
        if photo is not None:
            self.flickrID = photo.id
            self.displayName = photo.title
            self.description = photo.description.encode('utf8')
            self.owner = photo.owner.username
            if (photo.owner.realname is not None
                    and photo.owner.realname.strip()):
                self.owner = photo.owner.realname

            self.imageURL = URL(photo.getURL(urlType="source"))
            self.datePosted = datetime.utcfromtimestamp(int(photo.dateposted))
            self.dateTaken = dateutil.parser.parse(
                photo.datetaken,
                default=datetime.now(tz=self.itsView.tzinfo.default))
            if photo.tags:
                self.tags = [
                    Tag.getTag(self.itsView, tag.text) for tag in photo.tags
                ]

    photo = property(None, _setPhoto)

    def __setup__(self):
        self.importFromURL(self.imageURL)

    @schema.observer(owner)
    def onOwnerChange(self, op, attr):
        self.updateDisplayWho(op, attr)

    def addDisplayWhos(self, whos):
        super(FlickrPhotoMixin, self).addDisplayWhos(whos)
        owner = getattr(self, 'owner', None)
        if owner is not None:
            whos.append((16, owner, 'owner'))
Пример #11
0
class DynamicContainer(RefCollectionDictionary, DynamicBlock):
    """
      A block whose children are built dynamically, when the
    Active View changes.
    This list of children is in "dynamicChildren" and the
    back pointer is in "dynamicParent".
    """

    dynamicChildren = schema.Sequence(
        Block.Block, otherName = 'dynamicParent',
    )

    collectionSpecifier = schema.One(redirectTo = 'dynamicChildren')

    schema.addClouds(
        copying = schema.Cloud(byCloud = [dynamicChildren])
    )

    def itemNameAccessor(self, item):
        """
          Use blockName for the accessor
        """
        return item.blockName
    
    def isDynamicContainer (self):
        return True

    def populateFromStaticChildren (self):
        # copy our static children as a useful starting point
        self.dynamicChildren.clear ()
        for block in self.childrenBlocks:
            self[block.blockName] = block

    def ensureDynamicChildren (self):
        """
          Make sure we have a DynamicChildren hierarchy, since all my
        subclasses use that hierarchy when they synchronize.
        If there is no DynamicChildren built, then initialize it from
        the childrenBlocks hierarchy.
        """
        try:
            children = len (self.dynamicChildren)
        except AttributeError:
            children = 0
        if not children:
            self.populateFromStaticChildren ()
Пример #12
0
class ColorStyle(Style):
    """ 
    Class for Color Style
    Attributes for backgroundColor and foregroundColor
    """

    foregroundColor = schema.One(
        ColorType, initialValue = ColorType(0, 0, 0, 255),
    )

    backgroundColor = schema.One(
        ColorType, initialValue = ColorType(255, 255, 255, 255),
    )

    schema.addClouds(
        sharing = schema.Cloud(foregroundColor, backgroundColor)
    )
Пример #13
0
class Toolbar(Block.RectangularChild, DynamicContainer):

    colorStyle = schema.One('osaf.framework.blocks.Styles.ColorStyle')
    toolSize = schema.One('osaf.framework.blocks.DocumentTypes.SizeType')
    separatorWidth = schema.One(schema.Integer, initialValue = 5)
    buttons3D = schema.One(schema.Boolean, initialValue = False)
    buttonsLabeled = schema.One(schema.Boolean, initialValue = False)
    schema.addClouds(
        copying = schema.Cloud(byRef=[colorStyle])
    )

    def instantiateWidget (self):
        self.ensureDynamicChildren ()
        # @@@DLD - remove this workaround for previous wxWidgets issues
        heightGutter = 9
        if self.buttonsLabeled:
            heightGutter += 14
        toolbar = wxToolbar(self.parentBlock.widget, 
                         Block.Block.getWidgetID(self),
                         wx.DefaultPosition,
                         (-1, self.toolSize.height+heightGutter),
                         style=self.calculate_wxStyle())
        # set the tool bitmap size right away
        toolbar.SetToolBitmapSize((self.toolSize.width, self.toolSize.height))

        """
        # @@@davids - debugging code - might eventually be useful to have UI items name themselves
        if (self.toolSize.width >= 26):
            toolbar.SetLabel("Main")
        else:
            toolbar.SetLabel("Markup")
        """

        return toolbar
    
    def calculate_wxStyle (self):
        style = wx.TB_HORIZONTAL
        if self.buttons3D:
            style |= wx.TB_3DBUTTONS
        else:
            style |= wx.TB_FLAT
        if self.buttonsLabeled:
            style |= wx.TB_TEXT
        return style
Пример #14
0
class TaskStamp(Stamp):
    """
    TaskStamp is the bag of Task-specific attributes.
    """

    schema.kindInfo(annotates = notes.Note)

    __use_collection__ = True
    
    requestor = schema.One(
        Contact,
        description =
            "Issues:\n"
            '   Type could be Contact, EmailAddress or String\n'
            '   Think about using the icalendar terminology\n',
        inverse = Contact.requestedTasks,
    )
    requestee = schema.Sequence(
        Contact,
        description =
            "Issues:\n"
            '   Type could be Contact, EmailAddress or String\n'
            '   Think about using the icalendar terminology\n',
        inverse = Contact.taskRequests,
    )

    # Redirections
    @apply
    def summary():
        def fget(self):
            return self.itsItem.displayName
        def fset(self, value):
            self.itsItem.displayName = value
        return schema.Calculated(schema.Text, (items.ContentItem.displayName,),
                                 fget, fset)

    schema.addClouds(
        copying = schema.Cloud(
            requestor, requestee
        )
    )

    def InitOutgoingAttributes (self):
        self.itsItem.InitOutgoingAttributes()
class PreviewArea(CalendarRangeBlock):
    timeCharacterStyle = schema.One(Styles.CharacterStyle)
    eventCharacterStyle = schema.One(Styles.CharacterStyle)
    linkCharacterStyle = schema.One(Styles.CharacterStyle)

    miniCalendar = schema.One(inverse=MiniCalendar.previewArea,
                              defaultValue=None)

    schema.addClouds(copying=schema.Cloud(
        byRef=[timeCharacterStyle, eventCharacterStyle, linkCharacterStyle],
        byCloud=[miniCalendar]))

    schema.initialValues(rangeIncrement=lambda self: one_day)

    def onSetContentsEvent(self, event):
        #We want to ignore, because view changes could come in here, and we
        #never want to change our collection
        pass

    def onSelectAllEventUpdateUI(self, event):
        event.arguments['Enable'] = False

    def instantiateWidget(self):
        if not self.getHasBeenRendered():
            self.setRange(datetime.now().date())
            self.setHasBeenRendered()
        if wx.Platform == '__WXMAC__':
            # on the Mac, borders around the minical and preview area look weird,
            # but we want one around our parent.  Modifying our parent is quite
            # a hack, but it works rather nicely.
            self.parentBlock.widget.SetWindowStyle(wx.BORDER_SIMPLE)
        return wxPreviewArea(self.parentBlock.widget,
                             self.getWidgetID(),
                             timeCharStyle=self.timeCharacterStyle,
                             eventCharStyle=self.eventCharacterStyle,
                             linkCharStyle=self.linkCharacterStyle)

    def onItemNotification(self, notificationType, data):
        # Delegate notifications to the block
        self.widget.onItemNotification(notificationType, data)

    def activeViewChanged(self):
        self.synchronizeWidget()
Пример #16
0
class MyKind1(ContentItem):
    """An example content kind"""
    
    attr1 = schema.One(schema.String, displayName="Attribute 1")
   
    schema.kindInfo(
        displayName = "Example Kind"
    )

    # redirection attributes
    who = schema.Role(redirectTo="attr1")

    attr2 = schema.One(schema.String, displayName="Attribute 2")
  
    # Typical clouds include a "copying" cloud, and a "sharing" cloud

    schema.addClouds(
        sharing = schema.Cloud(attr1, attr2)
    )
Пример #17
0
class IntersectionCollection(AbstractCollection):
    """
    """
    schema.kindInfo(
        displayName="IntersectionCollection"
    )

    left = schema.One(AbstractCollection, initialValue=None)
    right = schema.One(AbstractCollection, initialValue=None)

    schema.addClouds(
        copying = schema.Cloud(byCloud=[left, right]),
    )

    def onValueChanged(self, name):
        if name == "left" or name == "right":
            try:
                if self.left != None and self.right != None:
                    self.rep = Intersection((self.left, "rep"),(self.right, "rep"))
            except AttributeError:
                pass
Пример #18
0
class FilteredCollection(AbstractCollection):
    """
    """
    schema.kindInfo(
        displayName="FilteredCollection"
    )

    source = schema.One(AbstractCollection, initialValue=None)
    filterExpression = schema.One(schema.String, initialValue="")

    schema.addClouds(
        copying = schema.Cloud(byCloud=[source]),
    )

    def onValueChanged(self, name):
        if name == "source" or name == "filterExpression":
            try:
                if self.source != None and self.filterExpression != "":
                    s = "lambda item: %s" % self.filterExpression
                    self.rep = FilteredSet((self.source, "rep"), eval(s))
            except AttributeError, ae:
                print ae
                pass
Пример #19
0
class RecurrenceRule(items.ContentItem):
    """One rule defining recurrence for an item."""
    freq = schema.One(FrequencyEnum, defaultValue="weekly")
    isCount = schema.One(
        schema.Boolean,
        doc="If True, calculate and export count instead of until",
        defaultValue=False)
    until = schema.One(schema.DateTimeTZ, )
    untilIsDate = schema.One(
        schema.Boolean,
        doc="If True, treat until as an inclusive date, use until + 23:59 "
        "for until",
        defaultValue=True)
    interval = schema.One(schema.Integer, defaultValue=1)
    wkst = schema.One(WeekdayEnum, defaultValue=None)
    bysetpos = schema.Sequence(schema.Integer, defaultValue=None)
    bymonth = schema.Sequence(schema.Integer, defaultValue=None)
    bymonthday = schema.Sequence(schema.Integer, defaultValue=None)
    byyearday = schema.Sequence(schema.Integer, defaultValue=None)
    byweekno = schema.Sequence(schema.Integer, defaultValue=None)
    byweekday = schema.Sequence(WeekdayAndPositionStruct, defaultValue=None)
    byhour = schema.Sequence(schema.Integer, defaultValue=None)
    byminute = schema.Sequence(schema.Integer, defaultValue=None)
    bysecond = schema.Sequence(schema.Integer, defaultValue=None)
    rruleFor = schema.One()  # inverse of RecurrenceRuleSet.rruleset
    exruleFor = schema.One()  # inverse of RecurrenceRuleSet.exrules

    schema.addClouds(sharing=schema.Cloud(literal=[
        freq, isCount, until, untilIsDate, interval, wkst, bysetpos, bymonth,
        bymonthday, byyearday, byweekno, byweekday, byhour, byminute, bysecond
    ]))

    normalNames = "interval", "until"
    listNames = ("bysetpos", "bymonth", "bymonthday", "byyearday", "byweekno",
                 "byhour", "byminute", "bysecond")
    specialNames = "wkst", "byweekday", "freq"

    notSpecialNames = ("interval", "until", "bysetpos", "bymonth",
                       "bymonthday", "byyearday", "byweekno", "byhour",
                       "byminute", "bysecond")

    @schema.observer(interval, until, bysetpos, bymonth, bymonthday, byyearday,
                     byweekno, byhour, byminute, bysecond, wkst, byweekday,
                     freq)
    def onRecurrenceChanged(self, op, name):
        """If the rule changes, update any associated events."""
        for ruletype in ('rruleFor', 'exruleFor'):
            if self.hasLocalAttributeValue(ruletype):
                getattr(self, ruletype).onRuleSetChanged(op, 'rrules')

    # dateutil automatically sets these from dtstart, we don't want these
    # unless their length is greater than 1.
    interpretedNames = "byhour", "byminute", "bysecond"

    WEEKDAYS = ('monday', 'tuesday', 'wednesday', 'thursday', 'friday')

    def isWeekdayRule(self):
        if (self.freq == 'weekly' and self.interval == 1
                and len(self.byweekday or []) == len(self.WEEKDAYS)
                and set(self.WEEKDAYS) == set(
                    x.weekday for x in self.byweekday if x.selector == 0)):
            return True

    def calculatedUntil(self):
        """
        Return until or until + 23:59, depending on untilIsDate.
        Will return None if there's no 'until' (so don't assume you can
        compare this value with a datetime directly!)

        @rtype: C{datetime} or C{None}

        """
        try:
            until = self.until
        except AttributeError:
            return None

        if self.untilIsDate:
            return until.replace(hour=23, minute=59)
        else:
            return until

    def createDateUtilFromRule(self,
                               dtstart,
                               ignoreIsCount=True,
                               convertFloating=False):
        """Return an appropriate dateutil.rrule.rrule.

        @param dtstart: The start time for the recurrence rule
        @type  dtstart: C{datetime}

        @param ignoreIsCount: Whether the isCount flag should be used to convert
                              until endtimes to a count. Converting to count
                              takes extra cycles and is only necessary when
                              the rule is going to be serialized
        @type  ignoreIsCount: C{bool}

        @param convertFloating: Whether or not to allow view.tzinfo.floating
                                in datetimes of the rruleset. If C{True},
                                naive datetimes are used instead. This is
                                needed for exporting floating events to
                                icalendar format.
        @type  convertFloating: C{bool}

        @rtype: C{dateutil.rrule.rrule}

        """

        tzinfo = dtstart.tzinfo
        view = self.itsView

        def coerceIfDatetime(value):
            if isinstance(value, datetime):
                if convertFloating and tzinfo == view.tzinfo.floating:
                    value = coerceTimeZone(view, value,
                                           tzinfo).replace(tzinfo=None)
                else:
                    value = coerceTimeZone(view, value, tzinfo)
            return value

        # TODO: more comments
        kwargs = dict(
            (k, getattr(self, k, None)) for k in self.notSpecialNames)
        for key in self.specialNames:
            value = coerceIfDatetime(getattr(self, key))
            if value is not None:
                kwargs[key] = toDateUtil(value)
        if hasattr(self, 'until'):
            kwargs['until'] = coerceIfDatetime(self.calculatedUntil())
        rule = rrule(dtstart=coerceIfDatetime(dtstart), **kwargs)
        if ignoreIsCount or not self.isCount or not hasattr(self, 'until'):
            return rule
        else:
            # modifying in place may screw up cache, fix when we turn
            # on caching
            rule._count = rule.count()
            rule._until = None
            return rule

    def setRuleFromDateUtil(self, rrule):
        """Extract attributes from rrule, set them in self.

        @param rrule: The rule to marshall into Chandler
        @type  rrule: C{dateutil.rrule.rrule}

        """
        view = self.itsView
        self.untilIsDate = False
        until = None  # assume no limit
        if rrule._count is not None:
            self.isCount = True
            until = rrule[-1]
        self.wkst = fromDateUtilWeekday(rrule._wkst)
        self.freq = fromDateUtilFrequency(rrule._freq)

        # ignore byweekday if freq is WEEKLY and day correlates with dtstart
        # because it was automatically set by dateutil
        if rrule._freq != dateutil.rrule.WEEKLY or \
           len(rrule._byweekday or ()) != 1 or \
           rrule._dtstart.weekday() != rrule._byweekday[0]:
            listOfDayTuples = []
            if rrule._byweekday:
                # Day tuples are (dayOrdinal, n-th week of the month),
                # 0 means all weeks
                listOfDayTuples = [(day, 0) for day in rrule._byweekday]
            if rrule._bynweekday is not None:
                listOfDayTuples.extend(rrule._bynweekday)
            if len(listOfDayTuples) > 0:
                self.byweekday = []
                for day, n in listOfDayTuples:
                    day = fromDateUtilWeekday(day)
                    self.byweekday.append(WeekdayAndPositionStruct(day, n))
        if rrule._until is not None:
            until = rrule._until
        if rrule._interval != 1:
            self.interval = rrule._interval
        if until is None:
            if self.hasLocalAttributeValue('until'):
                del self.until
        else:
            if until.tzinfo is None:
                self.until = until.replace(tzinfo=view.tzinfo.floating)
            else:
                self.until = coerceTimeZone(view, until, view.tzinfo.default)

        for key in self.listNames:
            # TODO: cache getattr(rrule, '_' + key)
            value = getattr(rrule, '_' + key)
            if key == 'bymonthday':
                if value is not None:
                    value += (rrule._bynmonthday or ())
            if value is not None and \
               (key not in self.interpretedNames or \
               len(value) > 1):
                # cast tuples to list
                setattr(self, key, list(value))
        # bymonthday and bymonth may be set automatically by dateutil, if so,
        # unset them
        if rrule._freq in (dateutil.rrule.MONTHLY, dateutil.rrule.YEARLY):
            if len(rrule._bymonthday) == 1 and len(rrule._bynmonthday) == 0:
                if rrule._bymonthday[0] == rrule._dtstart.day:
                    del self.bymonthday
        if rrule._freq == dateutil.rrule.YEARLY:
            if len(rrule._bymonth or ()) == 1 and \
                   rrule._byweekday is None and \
                   len(rrule._bynweekday or ()) == 0:
                if rrule._bymonth[0] == rrule._dtstart.month:
                    del self.bymonth

    def getPreviousRecurrenceID(self, dtstart, recurrenceID):
        """Return the date of the previous recurrenceID, or None.

        @param dtstart: The start time for the recurrence rule
        @type  dtstart: C{datetime}

        @param recurrenceID: The current recurrenceID
        @type  recurrenceID: C{datetime}

        @rtype: C{datetime} or C{None}
        """
        return self.createDateUtilFromRule(dtstart).before(recurrenceID)

    def moveUntilBefore(self, dtstart, recurrenceID):
        """Find the previous recurrenceID, set UNTIL to match it.

        @param dtstart: The start time for the recurrence rule
        @type  dtstart: C{datetime}

        @param recurrenceID: The current recurrenceID
        @type  recurrenceID: C{datetime}

        """
        previous = self.getPreviousRecurrenceID(dtstart, recurrenceID)
        assert previous is not None

        self.until = previous
        self.untilIsDate = False
Пример #20
0
class Reminder(schema.Item):
    """
    The base class for reminders. Note that this only supports 'custom'
    (fixed-time) reminders; the more familiar relative reminders are
    defined in Calendar.py on as RelativeReminder. This resolves some
    unfortunate circular import dependency issues we had in the past.
    """
    # Make values we can use for distant (or invalid) reminder times
    farFuture = datetime.max
    if farFuture.tzinfo is None:
        farFuture = farFuture.replace(tzinfo=ICUtzinfo.getInstance('UTC'))
    distantPast = datetime.min
    if distantPast.tzinfo is None:
        distantPast = distantPast.replace(tzinfo=ICUtzinfo.getInstance('UTC'))
    
    absoluteTime = schema.One(
        schema.DateTimeTZ,
        defaultValue=None,
        doc="If set, overrides relativeTo as the base time for this reminder"
    )

    # These flags represent what kind of reminder this is:
    # userCreated promptUser
    #   True        True    a relative or absolute user reminder, usually
    #                       created in the detail view (or possibly, imported
    #                       as ICalendar)
    #   False       False   An internal reminder relative to the
    #                       the effectiveStartTime of an event, used
    #                       to update its triageStatus when it fires.
    #
    # @@@ [grant] For now, userCreated and promptUser are identical... It's
    # not so clear to me whether we need both.
    #
    
    userCreated = schema.One(
        schema.Boolean,
        defaultValue=True,
        doc="Is a user-created reminder?"
    )

    promptUser = schema.One(
        schema.Boolean,
        defaultValue=True,
        doc="Should we show this reminder to the user when it goes off?")
    
    reminderItem = schema.One(
        defaultValue=None,
    )

    pendingEntries = schema.Sequence(
        PendingReminderEntry,
        defaultValue=Empty,
        inverse=PendingReminderEntry.reminder,
        doc="What user-created reminders have fired, and not been " \
            "dismissed or snoozed?"
    )
    
    nextPoll = schema.One(
        schema.DateTimeTZ,
        doc="When next will something interesting happen with this reminder?" \
            "Set to reminder.farFuture if this reminder has expired.",
        defaultValue=None,
    )
    
    description = schema.One(
        schema.Text,
        doc="End-user text description of this reminder. Currently unused by "
            "Chandler.",
    )

    duration = schema.One(
        schema.TimeDelta,
        doc="Reminder DURATION (a la ICalendar VALARM); unused by Chandler.",
        defaultValue=timedelta(0),
    )
    repeat = schema.One(
        schema.Integer,
        doc="Reminder REPEAT (a la ICalendar VALARM); unused by Chandler.",
        defaultValue=0,
    )

    schema.addClouds(
        sharing = schema.Cloud(
            literal = [absoluteTime, userCreated, promptUser]
        ),
        copying = schema.Cloud(
            literal = [absoluteTime, userCreated, promptUser, nextPoll]
        )
    )

    def onItemDelete(self, view, deferring):
        if self.pendingEntries:
            pending = list(self.pendingEntries)
            self.pendingEntries.clear()
            for entry in pending:
                entry.delete(recursive=True)

    def updatePending(self, when=None):
        """
        The method makes sure that the Reminder's list of
        PendingReminderEntries is up-to-date. This involves adding new
        entries to the list, via the Remindable.reminderFired() method.
        Also, the C{nextPoll} attribute is updated to the next time
        something interesting will happen (i.e. another item should be
        reminded); this may be C{Reminder.farFuture} if the C{Reminder}
        has no more items to process.
        
        @param when: The time to update to. You can pass C{None}, in which
                     case C{datetime.now()} is used.
        @type when: C{datetime}.
        """
        if when is None:
            when = datetime.now(self.itsView.tzinfo.default)

        if self.nextPoll is None:
            # No value for nextPoll means we've just been initialized.
            # If we're in the past, we treat ourselves as expired.
                
            if self.absoluteTime >= when:
                self.nextPoll = self.absoluteTime
            else:
                self.nextPoll = self.farFuture
                
        if self.nextPoll != self.farFuture and when >= self.absoluteTime:
            self.reminderItem.reminderFired(self, self.absoluteTime)
            self.nextPoll = self.farFuture
            
        self._checkExpired()

    def _checkExpired(self):
        if self.isExpired() and not self.userCreated:
            self.delete(True)

    def dismissItem(self, item):
        pendingEntries = self.pendingEntries
        toDismiss = list(p for p in pendingEntries if p.item is item)
        assert len(toDismiss), "Attempt to dismiss item non-pending item %r" % (
                               item)

        for item in toDismiss:
            item.delete(recursive=True)
            
        if not self.pendingEntries:
            self.nextPoll = self.farFuture

        self._checkExpired()
                

    def snoozeItem(self, item, delta):
        nextPoll = self.nextPoll
        pendingEntries = self.pendingEntries
        toSnooze = list(p for p in pendingEntries if p.item is item)
        assert len(toSnooze), "Attempt to snooze item non-pending item %r" % (
                               item)

        when = datetime.now(self.itsView.tzinfo.default) + delta
        for item in toSnooze:
            item.when = when
            item.snoozed = True
            nextPoll = min(nextPoll, item.when)
            
        self.nextPoll = nextPoll
        
    def getItemBaseTime(self, item):
        """
        Return the time we would fire for this item (independent of
        if it has been snoozed?)
        """
        return self.absoluteTime or Reminder.farFuture
        
    getReminderTime = getItemBaseTime
        
    def isExpired(self):
        return (not self.pendingEntries and
                self.nextPoll is not None
                and self.nextPoll >= Reminder.farFuture)
        
        
    @schema.observer(reminderItem)
    def reminderItemChanged(self, op, attr):
        if op == 'remove':
            if self.pendingEntries:
                # @@@ [grant] Check this!
                toRemove = list(p for p in self.pendingEntries
                                if isDead(p.item))
            
                for pending in toRemove:
                    self.pendingEntries.remove(pending)
                    
            if not self.pendingEntries:
                self.delete(True)

    @classmethod
    def defaultTime(cls, view):
        """ 
        Something's creating a reminder and needs a default time.
        We'll return 5PM today if that's in the future; otherwise, 8AM tomorrow.
        """    
        # start with today at 5PM
        t = datetime.now(tz=view.tzinfo.default)\
            .replace(hour=17, minute=0, second=0, microsecond=0)
        now = datetime.now(tz=view.tzinfo.default)
        if t < now:
            # Make it tomorrow morning at 8AM (eg, 15 hours later)
            t += timedelta(hours=15)
        return t

    @classmethod
    def getPendingTuples(cls, view, when):
        """
        Return a list of all reminder tuples with fire times in the past, 
        sorted by reminder time.

        Each tuple contains (reminderTime, remindable, reminder).
        """

        allFutureReminders = schema.ns('osaf.pim', view).allFutureReminders
        
        remindersToPoll = []
        for reminder in allFutureReminders.iterindexvalues('reminderPoll'):
            if reminder.nextPoll is None or reminder.nextPoll <= when:
                remindersToPoll.append(reminder)
            else:
                break

        for reminder in remindersToPoll:
            reminder.updatePending(when)

        pendingKind = PendingReminderEntry.getKind(view)
        trash = schema.ns("osaf.pim", view).trashCollection
        resultTuples = []
        
        for entry in pendingKind.iterItems():
            thisTuple = tuple(getattr(entry, attr, None)
                               for attr in ('when', 'item', 'reminder'))
            # Show everything except reminders in the trash, and
            # reminders that have been snoozed into the future. Also,
            # don't return any "dead" items in the tuple, and check
            # for missing attributes (bug 11415).
            if (thisTuple[0] is not None and
                not isDead(thisTuple[1]) and
                not isDead(thisTuple[2]) and
                not thisTuple[1] in trash and
                not (entry.snoozed and thisTuple[0] > when)):

                resultTuples.append(thisTuple)
                                
        resultTuples.sort(key=lambda t: t[0])
        return resultTuples
Пример #21
0
class Remindable(schema.Item):
    reminders = schema.Sequence(
        Reminder,
        inverse=Reminder.reminderItem,
        defaultValue=Empty,
    )

    schema.addClouds(
        copying = schema.Cloud(reminders),
        sharing = schema.Cloud(
            byCloud = [reminders]
        )
    )

    def InitOutgoingAttributes(self):
        pass

    def onItemDelete(self, view, deferring):
        for rem in self.reminders:
            rem.delete(recursive=True)
    
    def getUserReminder(self, expiredToo=True):
        """
        Get the user reminder on this item. There's supposed to be only one; 
        it could be relative or absolute.
        
        We'll look in the 'reminders' reflist, and allow expired reminders
        only if expiredToo is set to False.
        """
        # @@@ Note: This code is reimplemented in the index for the dashboard
        # calendar column: be sure to change that if you change this!
        for reminder in self.reminders:
            if reminder.userCreated:
                if expiredToo or not reminder.isExpired():
                    return reminder

    # @@@ Note: 'Calculated' APIs are provided for only absolute user-set
    # reminders. Relative reminders, which currently can only apply to
    # EventStamp instances, can be set via similar APIs on EventStamp.
    # Currently only one reminder (which can be of either
    # flavor) can be set right now. The 'set' functions can replace an existing
    # reminder of either flavor, but the 'get' functions ignore (that is, return
    # 'None' for) reminders of the wrong flavor.
    
    def getUserReminderTime(self):
        userReminder = self.getUserReminder()
        if userReminder is None or userReminder.absoluteTime is None:
            return None
        return userReminder.absoluteTime

    def setUserReminderTime(self, absoluteTime):
        existing = self.getUserReminder()
        if absoluteTime is not None:
            retval = Reminder(itsView=self.itsView, absoluteTime=absoluteTime,
                              reminderItem=self)
        else:
            retval = None

        if existing is not None:
            existing.delete(recursive=True)
            
        return retval
    
    userReminderTime = schema.Calculated(
        schema.DateTimeTZ,
        basedOn=(reminders,),
        fget=getUserReminderTime,
        fset=setUserReminderTime,
        doc="User-set absolute reminder time."
    )

    @schema.observer(reminders)
    def onRemindersChanged(self, op, attr):
        logger.debug("Hey, onRemindersChanged called!")
        self.updateDisplayDate(op, attr)
    
    def addDisplayDates(self, dates, now):
        """
        Subclasses will override this to add relevant dates to this list;
        each should be a tuple, (priority, dateTimeValue, 'attributeName').
        """
        # Add our reminder, if we have one
        reminder = self.getUserReminder()
        if reminder is not None:
            reminderTime = reminder.getReminderTime(self)
            # displayDate should be base time for RelativeReminder, bug 12246
            # for absolute reminders, getItemBaseTime matches reminderTime
            displayDate = reminder.getItemBaseTime(self)
            if reminderTime not in (None, Reminder.farFuture):
                dates.append((30 if reminderTime < now else 10, displayDate,
                              'reminder'))
                              
    def reminderFired(self, reminder, when):
        """
        Called when a reminder's fire date (whether snoozed or not)
        rolls around. This is overridden by subclasses; e.g. ContentItem
        uses it to set triage status, whereas Occurrence makes sure that
        the triage status change is a THIS change.
        
        The Remindable implementation makes sure that a
        PendingReminderEntry is created, if necessary, if reminder is
        userCreated.
        """
        for pending in reminder.pendingEntries:
            if pending.item is self:
                break
        else:
            if reminder.userCreated:
                # No matching item, so add one
                pending = PendingReminderEntry(itsView=self.itsView, item=self,
                                               reminder=reminder, when=when)
            else:
                pending = None

        return pending
Пример #22
0
class PhotoMixin(pim.ContentItem):
    dateTaken = schema.One(schema.DateTimeTZ)
    file = schema.One(schema.Text)
    exif = schema.Mapping(schema.Text, initialValue={})
    photoBody = schema.One(schema.Lob)

    @schema.observer(photoBody)
    def onPhotoBodyChanged(self, op, attribute):
        self.processEXIF()

    schema.addClouds(sharing=schema.Cloud(literal=[dateTaken, photoBody]))

    def importFromFile(self, path):
        if isinstance(path, unicode):
            path = path.encode('utf8')

        data = file(path, "rb").read()
        (mimetype, encoding) = mimetypes.guess_type(path)
        self.photoBody = self.itsView.createLob(data,
                                                mimetype=mimetype,
                                                compression='bz2')

    def importFromURL(self, url):
        if isinstance(url, URL):
            url = str(url)
        data = urllib2.urlopen(url).read()
        (mimetype, encoding) = mimetypes.guess_type(url)
        self.photoBody = self.itsView.createLob(data,
                                                mimetype=mimetype,
                                                compression='bz2')

    def exportToFile(self, path):
        if isinstance(path, unicode):
            path = path.encode('utf8')

        input = self.photoBody.getInputStream()
        data = input.read()
        input.close()
        out = file(path, "wb")
        out.write(data)
        out.close()

    def processEXIF(self):
        if hasattr(self, 'photoBody'):
            input = self.photoBody.getInputStream()
        else:
            input = file(self.file, 'r')

        data = input.read()
        input.close()
        stream = cStringIO.StringIO(data)
        try:
            exif = EXIF.process_file(stream)

            # First try DateTimeOriginal, falling back to DateTime
            takenString = str(
                exif.get('EXIF DateTimeOriginal', exif['Image DateTime']))

            timestamp = time.mktime(
                time.strptime(takenString, "%Y:%m:%d %H:%M:%S"))
            self.dateTaken = datetime.fromtimestamp(timestamp)
            if self.dateTaken.tzinfo is None:
                self.dateTaken = self.dateTaken.replace(
                    tzinfo=self.itsView.tzinfo.default)

            self.exif = {}
            for (key, value) in exif.iteritems():
                if isinstance(value, EXIF.IFD_Tag):
                    self.exif[key] = unicode(value.printable)
                else:
                    self.exif[key] = unicode(value)

        except Exception, e:
            logger.debug("Couldn't process EXIF of Photo %s (%s)" % \
                (self.itsPath, e))
Пример #23
0
class ContentItem(Triageable):
    """
    Content Item

    Content Item is the abstract super-kind for things like Contacts, Calendar
    Events, Tasks, Mail Messages, and Notes.  Content Items are user-level
    items, which a user might file, categorize, share, and delete.

    Examples:
     - a Calendar Event -- 'Lunch with Tug'
     - a Contact -- 'Terry Smith'
     - a Task -- 'mail 1040 to IRS'
    """
    isProxy = False

    displayName = schema.One(LocalizableString, defaultValue=u"", indexed=True)
    body = schema.One(
        LocalizableString,
        indexed=True,
        defaultValue=u"",
        doc="All Content Items may have a body to contain notes.  It's "
        "not decided yet whether this body would instead contain the "
        "payload for resource items such as presentations or "
        "spreadsheets -- resource items haven't been nailed down "
        "yet -- but the payload may be different from the notes because "
        "payload needs to know MIME type, etc.")

    creator = schema.One(
        # Contact
        doc="Link to the contact who created the item.")

    modifiedFlags = schema.Many(
        Modification,
        defaultValue=Empty,
        description='Used to track the modification state of the item')

    lastModified = schema.One(
        schema.DateTimeTZ,
        doc="DateTime (including timezone) this item was last modified",
        defaultValue=None,
    )

    lastModifiedBy = schema.One(
        doc="Link to the EmailAddress who last modified the item.",
        defaultValue=None)

    lastModification = schema.One(
        Modification,
        doc="What the last modification was.",
        defaultValue=Modification.created,
    )

    BYLINE_FORMATS = {
        Modification.created: (
            _(u"Created by %(user)s on %(date)s %(tz)s"),
            _(u"Created on %(date)s %(tz)s"),
        ),
        Modification.edited: (
            _(u"Edited by %(user)s on %(date)s %(tz)s"),
            _(u"Edited on %(date)s %(tz)s"),
        ),
        Modification.updated: (
            _(u"Updated by %(user)s on %(date)s %(tz)s"),
            _(u"Updated on %(date)s %(tz)s"),
        ),
        Modification.sent: (
            _(u"Sent by %(user)s on %(date)s %(tz)s"),
            _(u"Sent on %(date)s %(tz)s"),
        ),
        Modification.queued: (
            _(u"Queued by %(user)s on %(date)s %(tz)s"),
            _(u"Queued on %(date)s %(tz)s"),
        ),
    }

    def getByline(self):
        lastModification = self.lastModification
        assert lastModification in self.BYLINE_FORMATS

        fmt, noUserFmt = self.BYLINE_FORMATS[lastModification]

        # fall back to createdOn
        view = self.itsView
        lastModified = (self.lastModified or getattr(self, 'createdOn', None)
                        or datetime.now(view.tzinfo.default))

        shortDateTimeFormat = schema.importString(
            "osaf.pim.shortDateTimeFormat")
        date = shortDateTimeFormat.format(view, lastModified)

        tzPrefs = schema.ns('osaf.pim', view).TimezonePrefs
        if tzPrefs.showUI:
            from calendar.TimeZone import shortTZ
            tzName = shortTZ(view, lastModified)
        else:
            tzName = u''

        user = self.lastModifiedBy
        if user:
            result = fmt % dict(user=user.getLabel(), date=date, tz=tzName)
        else:
            result = noUserFmt % dict(date=date, tz=tzName)
        return result.strip()

    error = schema.One(
        schema.Text,
        doc="A user-visible string containing the last error that occurred. "
        "Typically, this should be set by the sharing or email layers when "
        "a conflict or delivery problem occurs.",
        defaultValue=None)

    byline = schema.Calculated(schema.Text,
                               basedOn=(modifiedFlags, lastModified,
                                        lastModification, lastModifiedBy),
                               fget=getByline)

    importance = schema.One(
        ImportanceEnum,
        doc="Most items are of normal importance (no value need be shown), "
        "however some things may be flagged either highly important or "
        "merely 'fyi'. This attribute is also used in the mail schema, so "
        "we shouldn't make any changes here that would break e-mail "
        "interoperability features.",
        defaultValue="normal",
    )

    mine = schema.One(schema.Boolean, defaultValue=True)

    private = schema.One(schema.Boolean, defaultValue=False)

    read = schema.One(schema.Boolean,
                      defaultValue=False,
                      doc="A flag indicating whether the this item has "
                      "been 'viewed' by the user")

    needsReply = schema.One(
        schema.Boolean,
        defaultValue=False,
        doc="A flag indicating that the user wants to reply to this item")

    createdOn = schema.One(schema.DateTimeTZ,
                           doc="DateTime this item was created")

    # ContentItem instances can be put into ListCollections and AppCollections
    collections = schema.Sequence(
        notify=True)  # inverse=collections.inclusions

    # ContentItem instances can be excluded by AppCollections
    excludedBy = schema.Sequence()

    # ContentItem instances can be put into SmartCollections (which define
    # the other end of this biref)
    appearsIn = schema.Sequence()

    # The date used for sorting the Date column
    displayDate = schema.One(schema.DateTimeTZ, indexed=True)
    displayDateSource = schema.One(schema.Importable)

    # The value displayed (and sorted) for the Who column.
    displayWho = schema.One(schema.Text, indexed=True)
    displayWhoSource = schema.One(schema.Importable)

    schema.addClouds(sharing=schema.Cloud(
        literal=["displayName", body, createdOn, "description"],
        byValue=[lastModifiedBy]),
                     copying=schema.Cloud())

    schema.initialValues(
        createdOn=lambda self: datetime.now(self.itsView.tzinfo.default))

    def __str__(self):
        if self.isStale():
            return super(ContentItem, self).__str__()
            # Stale items can't access their attributes

        return self.__unicode__().encode('utf8')

    def __unicode__(self):
        if self.isStale():
            return super(ContentItem, self).__unicode__()

        return unicode(
            getattr(self, 'displayName', self.itsName) or self.itsUUID.str64())

    def InitOutgoingAttributes(self):
        """ Init any attributes on ourself that are appropriate for
        a new outgoing item.
        """

        super(ContentItem, self).InitOutgoingAttributes()

        # default the displayName
        self.displayName = messages.UNTITLED

        if not self.hasLocalAttributeValue('lastModifiedBy'):
            self.lastModifiedBy = self.getMyModifiedByAddress()

    def ExportItemData(self, clipboardHandler):
        # Create data for this kind of item in the clipboard handler
        # The data is used for Drag and Drop or Cut and Paste
        try:
            super(ContentItem, self).ExportItemData(clipboardHandler)
        except AttributeError:
            pass

        # Let the clipboard handler know we've got a ContentItem to export
        clipboardHandler.ExportItemFormat(self, 'ContentItem')

    def onItemDelete(self, view, deferring):
        # Hook for stamp deletion ...
        from stamping import Stamp
        for stampObject in Stamp(self).stamps:
            onItemDelete = getattr(stampObject, 'onItemDelete', None)
            if onItemDelete is not None:
                onItemDelete(view, deferring)
        super(ContentItem, self).onItemDelete(view, deferring)

    def addToCollection(self, collection):
        """Add self to the given collection.

        For most items, just call collection.add(self), but for recurring
        events, this method is intercepted by a proxy and buffered while the
        user selects from various possible meanings for adding a recurring event
        to a collection.

        """
        collection.add(self)

    def removeFromCollection(self, collection):
        """Remove self from the given collection.

        For most items, just call collection.remove(self), but for recurring
        events, this method is intercepted by a proxy and buffered while the
        user selects from various possible meanings for removing a recurring
        event from a collection.

        The special mine collection behavior that removed items should remain in
        the Dashboard is implemented here.

        """
        self._prepareToRemoveFromCollection(collection)
        collection.remove(self)

    def _prepareToRemoveFromCollection(self, collection):
        """
        If the collection is a mine collection and the item doesn't exist in any
        other 'mine' collections, manually add it to 'all' to keep the item
        'mine'.

        We don't want to do this blindly though, or all's inclusions will get
        unnecessarily full.

        We also don't want to remove collection from mine.sources. That will
        cause a notification storm as items temporarily leave and re-enter
        being 'mine'.
        
        """
        pim_ns = schema.ns('osaf.pim', self.itsView)
        mine = pim_ns.mine
        allCollection = pim_ns.allCollection

        if collection in mine.sources:
            for otherCollection in self.appearsIn:
                if otherCollection is collection:
                    continue

                if otherCollection in mine.sources:
                    # we found it in another 'mine'
                    break
            else:
                # we didn't find it in a 'mine' Collection
                self.collections.add(allCollection)

    def getMembershipItem(self):
        """ Get the item that should be used to test for membership
        tests i.e. if item in collection: should be if
        item.getMembershipItem() in collection

        For most items, this is just itself, but for recurring
        events, this method is intercepted by a proxy.
        """
        return self

    def changeEditState(self,
                        modType=Modification.edited,
                        who=None,
                        when=None):
        """
        @param modType: What kind of modification you are making. Used to set
                        the value of C{self.lastModification}.
        @type modType: C{Modification}
        
        @param who: May be C{None}, which is interpreted as an anonymous
                    user (e.g. a "drive-by" sharing user).
                    Used to set the value of {self.lastModifiedBy}.
        @type who: C{EmailAddress}
        
        @param when: The date&time of this change. Used to set the value
                     of C{self.lastModified}. The default, C{None}, sets
                     the 
        @type when: C{datetime}
        """

        logger.debug(
            "ContentItem.changeEditState() self=%s view=%s modType=%s who=%s when=%s",
            self, self.itsView, modType, who, when)

        currentModFlags = self.modifiedFlags

        if (modType == Modification.edited
                and not self.hasLocalAttributeValue('lastModification', None)
                and
                not getattr(self, 'inheritFrom',
                            self).hasLocalAttributeValue('lastModification')):
            # skip edits until the item is explicitly marked created

            return

        if modType == Modification.sent:
            if Modification.sent in currentModFlags:
                #raise ValueError, "You can't send an item twice"
                pass
            elif Modification.queued in currentModFlags:
                currentModFlags.remove(Modification.queued)
        elif modType == Modification.updated:
            #XXX Brian K: an update can occur with out a send.
            # Case user a sends a item to user b (state == sent)
            #      user a edits item to user b and adds user c (state == update).
            # For user c the state of sent was never seen.

            #if not Modification.sent in currentModFlags:
            #    raise ValueError, "You can't update an item till it's been sent"

            if Modification.queued in currentModFlags:
                currentModFlags.remove(Modification.queued)

        # Clear the edited flag and error on send/update/queue
        if (modType in (Modification.sent, Modification.updated,
                        Modification.queued)):
            if Modification.edited in currentModFlags:
                currentModFlags.remove(Modification.edited)
            del self.error

        if not currentModFlags:
            self.modifiedFlags = set([modType])
        else:
            currentModFlags.add(modType)
        self.lastModification = modType
        self.lastModified = when or datetime.now(self.itsView.tzinfo.default)
        self.lastModifiedBy = who  # None => me

    """
    ACCESSORS

    Accessors for Content Item attributes
    """

    def getEmailAddress(self, nameOrAddressString):
        """
          Lookup or create an EmailAddress based
        on the supplied string.
        This method is here for convenient access, so users
        don't need to import Mail.
        """
        from mail import EmailAddress
        return EmailAddress.getEmailAddress(self.itsView, nameOrAddressString)

    def getCurrentMeEmailAddress(self):
        """
        Lookup or create a current "me" EmailAddress.
        This method is here for convenient access, so users
        don't need to import Mail.
        """
        import mail
        return mail.getCurrentMeEmailAddress(self.itsView)

    def getMyModifiedByAddress(self):
        """
        Get an EmailAddress that represents the local user, for
        storing in lastModifiedBy after a local change.
        """
        me = self.getCurrentMeEmailAddress()
        if not me:  # Email not configured...
            # Get the user name associated with the default sharing account
            import osaf.sharing  # hmm, this import seems wrong
            sharingAccount = osaf.sharing.getDefaultAccount(self.itsView)
            if sharingAccount is not None:
                import mail
                me = mail.EmailAddress.getEmailAddress(self.itsView,
                                                       sharingAccount.username)
        return me

    def _updateCommonAttribute(self,
                               attributeName,
                               sourceAttributeName,
                               collectorMethodName,
                               args=()):
        """
        Mechanism for coordinating updates to a common-display field
        (like displayWho and displayDate, but not displayName for now).
        """
        if self.isDeleted():
            return
        logger.debug("Collecting relevant %ss for %r %s", attributeName, self,
                     self)

        collectorMethod = getattr(type(self), collectorMethodName)

        # Collect possible values. The collector method adds tuples to the
        # contenders list; each tuple starts with a value to sort by, and
        # ends with the attribute value to assign and the name of the attribute
        # it came from (which will be used later to pick a displayable string
        # to describe the source of the value).
        #
        # Examples: if we sort by the attribute value, only two values are
        # needed in the tuple: eg, (aDate, 'dateField). If the picking happens
        # based on an additional value (or values), the sort value(s) come
        # before the attribute value: eg (1, '*****@*****.**', 'to'),
        # (2, '*****@*****.**', 'from'). This works because sort sorts by the
        # values in order, and we access the attribute value and name using
        # negative indexes (so contender[-2] is the value, contender[-1] is
        # the name).
        contenders = []
        collectorMethod(self, contenders, *args)

        # Now that we have the contenders, pick one.
        contenderCount = len(contenders)
        if contenderCount == 0:
            # No contenders: delete the value
            if hasattr(self, attributeName):
                delattr(self, attributeName)
            if hasattr(self, sourceAttributeName):
                delattr(self, sourceAttributeName)
            logger.debug("No relevant %s for %r %s", attributeName, self, self)
            return

        if contenderCount > 1:
            # We have more than one possibility: sort, then we'll use the first one.
            contenders.sort()
        result = contenders[0]
        logger.debug("Relevant %s for %r %s is %s", attributeName, self, self,
                     result)
        assert result[-2] is not None
        setattr(self, attributeName, result[-2])
        setattr(self, sourceAttributeName, result[-1])

        if getattr(self, 'inheritFrom', None) is None:
            for item in getattr(self, 'inheritTo', []):
                item._updateCommonAttribute(attributeName, sourceAttributeName,
                                            collectorMethodName, args)

    def addDisplayWhos(self, whos):
        pass

    def updateDisplayWho(self, op, attr):
        self._updateCommonAttribute('displayWho', 'displayWhoSource',
                                    'addDisplayWhos')

    def addDisplayDates(self, dates, now):
        super(ContentItem, self).addDisplayDates(dates, now)
        # Add our creation and last-mod dates, if they exist.
        for importance, attr in (999, "lastModified"), (1000, "createdOn"):
            v = getattr(self, attr, None)
            if v is not None:
                dates.append((importance, v, attr))

    def updateDisplayDate(self, op, attr):
        now = datetime.now(tz=self.itsView.tzinfo.default)
        self._updateCommonAttribute('displayDate', 'displayDateSource',
                                    'addDisplayDates', [now])

    @schema.observer(modifiedFlags, lastModified, createdOn)
    def onCreatedOrLastModifiedChanged(self, op, attr):
        self.updateDisplayDate(op, attr)

    @schema.observer(modifiedFlags, lastModification, lastModifiedBy, read)
    def onModificationChange(self, op, name):
        # CommunicationStatus might have changed
        self.updateDisplayWho(op, name)

    @schema.observer(error)
    def onErrorChanged(self, op, attr):
        # Pop to now if error is anything non-False-ish (a non-empty
        # string, etc)
        if getattr(self, 'error', None):
            self.setTriageStatus(None, popToNow=True, force=True)

    def getBasedAttributes(self, attribute):
        """ Determine the schema attributes that affect this attribute
        (which might be a Calculated attribute) """
        # If it's Calculated, see what it's based on;
        # otherwise, just return a list containing its own name.
        descriptor = getattr(self.__class__, attribute, None)
        try:
            basedOn = descriptor.basedOn
        except AttributeError:
            return (attribute, )
        else:
            return tuple(desc.name for desc in basedOn)

    def isAttributeModifiable(self, attribute):
        """ Determine if an item's attribute is modifiable based on the
            shares it's in """
        from osaf.sharing import isReadOnly
        return not isReadOnly(self)
Пример #24
0
class BranchPointBlock(BoxContainer):
    """
    A block that can swap in different sets of child blocks (branch point
    "subtrees") based on its detailContents. It uses a BranchPointDelegate to
    do the heavy lifting.
    """
    colorStyle = schema.One(ColorStyle)

    delegate = schema.One(required=True)
    detailItem = schema.One(schema.Item,
                            defaultValue=None,
                            inverse=schema.Sequence())

    detailItemCollection = schema.One(schema.Item,
                                      defaultValue=None,
                                      inverse=schema.Sequence())

    selectedItem = schema.One(schema.Item,
                              defaultValue=None,
                              inverse=schema.Sequence())

    setFocus = schema.One(schema.Boolean, defaultValue=False)

    schema.addClouds(copying=schema.Cloud(
        byRef=[delegate, colorStyle, detailItem, selectedItem]))

    def instantiateWidget(self):
        return wxBranchPointBlock(self.parentBlock.widget)

    def onSelectItemsEvent(self, event):
        # for the moment, multiple selection means, "select nothing"
        # i.e. multiple selection in the summary view means selecting
        # nothing in the detail view

        # eventually we might want selectedItem to be an iterable
        # of some kind
        items = event.arguments['items']

        if len(items) == 1:
            self.selectedItem = items[0]
        else:
            self.selectedItem = None

        self.detailItemCollection = self.delegate.getContentsCollection(
            self.selectedItem, event.arguments.get('collection'))

        widget = getattr(self, 'widget', None)
        if widget is not None:
            # eventually results in installTreeOfBlocks()
            widget.wxSynchronizeWidget()

    def installTreeOfBlocks(self, hints):
        """
        If necessary, replace our children with a tree of blocks appropriate
        for our content.

        Four steps:
        1) map the selected item to a cache key
        2) Use the cache key to get the appropriate tree of blocks
        3) Set contents on that new tree of blocks
        4) Render the tree of blocks
        """

        # Get a cache key from self.selectedItem using the delegate
        keyItem = self.delegate._mapItemToCacheKeyItem(self.selectedItem,
                                                       hints)

        # Ask the delegate for the right tree of blocks

        # (actually there is only implmentation of this function,
        # should probably be rolled into BranchParentBlock eventually)
        newView = self.delegate.getBranchForKeyItem(keyItem)
        if keyItem is None:
            detailItem = None
        else:
            # Seems like we should always mark new views with an event boundary
            assert newView is None or newView.eventBoundary
            detailItem = self.delegate._getContentsForBranch(
                newView, self.selectedItem, keyItem)

        detailItemChanged = self.detailItem is not detailItem

        self.detailItem = detailItem

        # Set contents on the root of the tree of blocks

        # For bug 4269 in 0.6: If we've been given a contents collection,
        # it's so that we can put our detailItem in it, to get a notification
        # when that item is deleted. Update the collection if necessary.
        contents = getattr(self, 'contents', None)
        if (contents is not None and contents.first() is not detailItem):
            contents.clear()
            if detailItem is not None:
                contents.add(self.detailItem)

        oldView = self.childBlocks.first()
        treeChanged = newView is not oldView

        logger.debug(
            "installTreeOfBlocks %s: treeChanged=%s, detailItemChanged=%s, detailItem=%s",
            debugName(self), treeChanged, detailItemChanged,
            debugName(detailItem))

        # Render or rerender as necessary
        if treeChanged:
            # get rid of the old view
            if oldView is not None:
                # We need to get rid of our sizer that refers to widgets
                # that are going to get deleted
                assert hasattr(self, "widget")
                self.widget.SetSizer(None)
                oldView.unRender()

            # attach the new view
            self.childBlocks = [newView] if newView is not None else []

        if newView is not None:

            def Rerender():
                if (detailItemChanged or treeChanged
                        or hints.get("sendSetContents", False)):
                    newView.postEventByName(
                        "SetContents", {
                            'item': detailItem,
                            'collection': self.detailItemCollection
                        })

                if not hasattr(newView, "widget"):
                    newView.render()
                elif detailItemChanged or treeChanged:
                    resyncMethod = getattr(newView, 'synchronizeWidgetDeep',
                                           None)
                    if resyncMethod is not None:
                        resyncMethod()

                if self.setFocus:
                    newView.postEventByName("SetFocus", {})

            treeController = getattr(newView, "treeController", None)
            if treeController is not None:
                treeController.initializeTree(hints)
            IgnoreSynchronizeWidget(False, Rerender)
Пример #25
0
class Contact(items.ContentItem):
    """An entry in an address book.

    Typically represents either a person or a company.

    * issue: We might want to keep track of lots of sharing information like
      'Permissions I've given them', 'Items of mine they've subscribed to',
      'Items of theirs I've subscribed to', etc.
    """
    schema.kindInfo(displayName="Contact", displayAttribute="emailAddress")

    itemsCreated = schema.Sequence(
        displayName="Items Created",
        doc="List of content items created by this user.",
        inverse=items.ContentItem.creator,
    )

    contactName = schema.One(ContactName,
                             inverse=ContactName.contact,
                             initialValue=None)

    emailAddress = schema.One(schema.String,
                              displayName="Email Address",
                              initialValue="")

    itemsLastModified = schema.Sequence(
        items.ContentItem,
        displayName="Items Last Modified",
        doc="List of content items last modified by this user.",
        inverse=items.ContentItem.lastModifiedBy)

    requestedTasks = schema.Sequence(
        "osaf.pim.tasks.TaskMixin",
        displayName="Requested Tasks",
        doc="List of tasks requested by this user.",
        inverse="requestor")

    taskRequests = schema.Sequence(
        "osaf.pim.tasks.TaskMixin",
        displayName="Task Requests",
        doc="List of tasks requested for this user.",
        otherName="requestee"  # XXX other end points to ContentItem???
    )

    organizedEvents = schema.Sequence(
        "osaf.pim.calendar.Calendar.CalendarEventMixin",
        displayName="Organized Events",
        doc="List of events this user has organized.",
        inverse="organizer")

    participatingEvents = schema.Sequence(
        "osaf.pim.calendar.Calendar.CalendarEventMixin",
        displayName="Participating Events",
        doc="List of events this user is a participant.",
        inverse="participants")

    sharerOf = schema.Sequence(  # Share
        displayName="Sharer Of",
        doc="List of shares shared by this user.",
        otherName="sharer")

    shareeOf = schema.Sequence(  # Share
        displayName="Sharee Of",
        doc="List of shares for which this user is a sharee.",
        otherName="sharees")

    # <!-- redirections -->

    who = schema.Role(redirectTo="contactName")
    about = schema.Role(redirectTo="displayName")
    date = schema.Role(redirectTo="createdOn")

    schema.addClouds(sharing=schema.Cloud(emailAddress, byCloud=[contactName]))

    def __init__(self, name=None, parent=None, kind=None, view=None, **kw):
        super(Contact, self).__init__(name, parent, kind, view, **kw)

        # If I didn't get assigned a creator, then I must be the "me" contact
        # and I want to be my own creator:
        if self.creator is None:
            self.creator = self

    def InitOutgoingAttributes(self):
        """ Init any attributes on ourself that are appropriate for
        a new outgoing item.
        """
        try:
            super(Contact, self).InitOutgoingAttributes()
        except AttributeError:
            pass

        self.contactName = ContactName()
        self.contactName.firstName = ''
        self.contactName.lastName = ''

    # Cache "me" for fast lookup; used by getCurrentMeContact()
    meContactID = None

    @classmethod
    def getCurrentMeContact(cls, view):
        """ Lookup the current "me" Contact """

        # cls.meContactID caches the Contact representing the user.  One will
        # be created if it doesn't yet exist.

        if cls.meContactID is not None:
            me = view.findUUID(cls.meContactID)
            if me is not None:
                return me
            # Our cached UUID is invalid
            cls.meContactID is None

        parent = cls.getDefaultParent(view)
        me = parent.getItemChild("me")
        if me is None:
            me = Contact(name="me", parent=parent, displayName="Me")
            me.contactName = ContactName(parent=parent,
                                         firstName="Chandler",
                                         lastName="User")

        cls.meContactID = me.itsUUID
        return me

    def getContactForEmailAddress(cls, view, address):
        """ Given an email address string, find (or create) a matching contact.

        @param view: The repository view object
        @type view: L{repository.persistence.RepositoryView}
        @param address: An email address to use for looking up a contact
        @type address: string
        @return: A Contact
        """
        """ @@@MOR, convert this section to use Query; I tried briefly but
        wasn't successful, and it's just using KindQuery right now:

        query = Query.Query(view, parent=view.findPath("//userdata"), queryString="") # @@@MOR Move this to a singleton

        queryString = "for i in '//parcels/osaf/pim/contacts/Contact' where i.emailAddress == $0"
        query.args = { 0 : address }
        query.execute()
        for item in query:
        """

        for item in cls.iterItems(view):
            if item.emailAddress == address:
                return item  # Just return the first match

        # Need to create a new Contact
        contact = Contact(view=view)
        contact.emailAddress = address
        contact.contactName = None
        return contact

    getContactForEmailAddress = classmethod(getContactForEmailAddress)

    def __str__(self):
        """ User readable string version of this address. """

        if self.isStale():
            return super(Contact, self).__str__()
            # Stale items shouldn't go through the code below

        value = self.getItemDisplayName()

        return value
Пример #26
0
class RecurrenceRuleSet(items.ContentItem):
    """
    A collection of recurrence and exclusion rules, dates, and exclusion dates.
    """
    rrules = schema.Sequence(RecurrenceRule,
                             inverse=RecurrenceRule.rruleFor,
                             deletePolicy='cascade')
    exrules = schema.Sequence(RecurrenceRule,
                              inverse=RecurrenceRule.exruleFor,
                              deletePolicy='cascade')
    rdates = schema.Sequence(schema.DateTimeTZ, )
    exdates = schema.Sequence(schema.DateTimeTZ, )
    events = schema.Sequence()  # inverse of EventStamp.rruleset

    schema.addClouds(copying=schema.Cloud(rrules, exrules, rdates, exdates),
                     sharing=schema.Cloud(literal=[exdates, rdates],
                                          byCloud=[exrules, rrules]))

    @schema.observer(rrules, exrules, rdates, exdates)
    def onRuleSetChanged(self, op, name):
        """If the RuleSet changes, update the associated event."""
        if not getattr(self, '_ignoreValueChanges', False):
            if self.hasLocalAttributeValue('events'):
                pimNs = schema.ns("osaf.pim", self.itsView)
                for eventItem in self.events:
                    pimNs.EventStamp(eventItem).getFirstInRule().cleanRule()
                    # assume we have only one conceptual event per rrule
                    break

    def addRule(self, rule, rrulesorexrules='rrules'):
        """Add an rrule or exrule, defaults to rrule.

        @param rule: Rule to be added
        @type  rule: L{RecurrenceRule}

        @param rrulesorexrules: Whether the rule is an rrule or exrule
        @type  rrulesorexrules: 'rrules' or 'exrules'

        """
        try:
            getattr(self, rrulesorexrules).append(rule)
        except AttributeError:
            setattr(self, rrulesorexrules, [rule])

    def createDateUtilFromRule(self,
                               dtstart,
                               ignoreIsCount=True,
                               convertFloating=False,
                               ignoreShortFrequency=True):
        """Return an appropriate dateutil.rrule.rruleset.

        @param dtstart: The start time for the recurrence rule
        @type  dtstart: C{datetime}

        @param ignoreIsCount: Whether the isCount flag should be used to convert
                              until endtimes to a count. Converting to count
                              takes extra cycles and is only necessary when
                              the rule is going to be serialized
        @type  ignoreIsCount: C{bool}

        @param convertFloating: Whether or not to allow view.tzinfo.floating
                                in datetimes of the rruleset. If C{True},
                                naive datetimes are used instead. This is
                                needed for exporting floating events to
                                icalendar format.
        @type  convertFloating: C{bool}

        @param ignoreShortFrequency: If ignoreShortFrequency is True, replace
                                     hourly or more frequent rules with a single
                                     RDATE matching dtstart, so as to avoid 
                                     wasting millions of cycles on nonsensical
                                     recurrence rules.
        @type  ignoreShortFrequency: C{bool}

        @rtype: C{dateutil.rrule.rruleset}

        """
        view = self.itsView
        args = (ignoreIsCount, convertFloating)
        ruleset = rruleset()
        for rtype in 'rrule', 'exrule':
            for rule in getattr(self, rtype + 's', []):
                if ignoreShortFrequency and rule.freq in SHORT_FREQUENCIES:
                    # too-frequent rule, as Admiral Ackbar said, "IT'S A TRAP!"
                    ruleset.rdate(dtstart)
                    return ruleset
                rule_adder = getattr(ruleset, rtype)
                rule_adder(rule.createDateUtilFromRule(dtstart, *args))

        for datetype in 'rdate', 'exdate':
            for date in getattr(self, datetype + 's', []):
                if convertFloating and date.tzinfo == view.tzinfo.floating:
                    date = date.replace(tzinfo=None)
                else:
                    date = coerceTimeZone(view, date, dtstart.tzinfo)
                getattr(ruleset, datetype)(date)

        if (ignoreIsCount and not getattr(self, 'rrules', [])
                and getattr(self, 'rdates', [])):
            # no rrule, but there are RDATEs, create an RDATE for dtstart, or it
            # won't appear to be part of the rule
            ruleset.rdate(dtstart)

        return ruleset

    def setRuleFromDateUtil(self, ruleSetOrRule):
        """Extract rules and dates from ruleSetOrRule, set them in self.

        If a dateutil.rrule.rrule is passed in instead of an rruleset, treat
        it as the new rruleset.

        @param ruleSetOrRule: The rule to marshall into Chandler
        @type  ruleSetOrRule: C{dateutil.rrule.rrule} or C{dateutil.rrule.rruleset}

        """
        ignoreChanges = getattr(self, '_ignoreValueChanges', False)
        self._ignoreValueChanges = True
        try:
            if isinstance(ruleSetOrRule, rrule):
                set = rruleset()
                set.rrule(ruleSetOrRule)
                ruleSetOrRule = set
            elif not isinstance(ruleSetOrRule, rruleset):
                raise TypeError, "ruleSetOrRule must be an rrule or rruleset"
            for rtype in 'rrule', 'exrule':
                rules = getattr(ruleSetOrRule, '_' + rtype, [])
                if rules is None: rules = []
                itemlist = []
                for rule in rules:
                    ruleItem = RecurrenceRule(None, None, None, self.itsView)
                    ruleItem.setRuleFromDateUtil(rule)
                    itemlist.append(ruleItem)
                setattr(self, rtype + 's', itemlist)
            for typ in 'rdate', 'exdate':
                datetimes = [
                    forceToDateTime(self.itsView, d)
                    for d in getattr(ruleSetOrRule, '_' + typ, [])
                ]
                setattr(self, typ + 's', datetimes)
        finally:
            self._ignoreValueChanges = ignoreChanges
        # only one rule cleaning is useful when setting a new rule
        self.onRuleSetChanged(None, None)

    def isComplex(self):
        """Determine if the rule is too complex to display a meaningful
        description about it.

        @rtype: C{bool}

        """
        if hasattr(self, 'rrules'):
            if len(self.rrules) != 1:
                return True  # multiple rules
            for recurtype in 'exrules', 'rdates':
                if self.hasLocalAttributeValue(recurtype) and \
                       len(getattr(self, recurtype)) != 0:
                    return True  # more complicated rules
            rule = self.rrules.first()
            for attr in RecurrenceRule.listNames:
                if getattr(rule, attr) not in (None, []):
                    return True
            if rule.byweekday is not None:
                # treat nth weekday of the month as complex
                for daystruct in rule.byweekday:
                    if daystruct.selector != 0:
                        return True
            return False
        else:
            return True

    def isCustomRule(self):
        """Determine if this is a custom rule.

        For the moment, simple daily, weekly, or monthly repeating events,
        optionally with an UNTIL date, or the abscence of a rule, are the only
        rules which are not custom.

        @rtype: C{bool}

        """
        if self.isComplex():
            return True
        # isComplex has already tested for most custom things, but
        # not intervals greater than 1 and multiple weekdays
        rule = self.rrules.first()
        if not (rule.interval == 1 or
                (rule.interval == 2 and rule.freq == 'weekly')):
            return True
        elif rule.isWeekdayRule():
            return False
        elif rule.byweekday:
            return True
        else:
            return False

    def getCustomDescription(self):
        """Return a string describing custom rules.

        @rtype: C{str}

        """
        too_frequent = False
        if hasattr(self, 'rrules') and len(self.rrules) > 0:
            for rule in self.rrules:
                if rule.freq in SHORT_FREQUENCIES:
                    too_frequent = True
                    break

        if self.isComplex():
            if not too_frequent:
                return _(u"No description.")
            else:
                return _(u"Too frequent.")
        else:
            # Get the rule values we can interpret (so far...)
            rule = self.rrules.first()
            freq = rule.freq
            interval = rule.interval
            until = rule.calculatedUntil()
            days = rule.byweekday

            # Build the index and argument dictionary
            # The index must be built in the 'fxdus' order
            index = 'f'
            dct = {}

            if too_frequent:
                index += 'x'

            elif freq == 'weekly' and days is not None:
                index += 'd'
                daylist = [weekdayAbbrevMap[i.weekday] for i in days]
                dct['days'] = u" ".join(daylist)

            dct['frequencyplural'] = pluralFrequencyMap[freq]
            dct['frequencysingular'] = singularFrequencyMap[freq]
            dct['frequencyadverb'] = adverbFrequencyMap[freq]

            if not too_frequent and until is not None:
                index += 'u'
                formatter = DateFormat.createDateInstance(DateFormat.kShort)
                dct['date'] = unicode(formatter.format(until))

            if interval != 1:
                dct['interval'] = str(interval)
                index += 'p'
            else:
                index += 's'

            return descriptionFormat[index] % dct

    def transformDatesAfter(self, after, changeDate):
        """
        Transform dates (later than "after") in exdates, rdates and
        until, by applying the function C{changeDate}.

        @param after: Earliest date to move, or None for all occurrences
        @type  after: C{datetime} or None

        @param changeDate: Time difference
        @type  changeDate: C{callable}, taking a C{datetime} and
                           returning a C{datetime}


        """
        self._ignoreValueChanges = True
        for datetype in 'rdates', 'exdates':
            datelist = getattr(self, datetype, None)
            if datelist is not None:
                l = []
                for dt in datelist:
                    if after is None or dt >= after:
                        l.append(changeDate(dt))
                    else:
                        l.append(dt)
                setattr(self, datetype, l)

        for rule in self.rrules or []:
            if not rule.untilIsDate:
                try:
                    until = rule.until
                except AttributeError:
                    pass
                else:
                    if after is None or until >= after:
                        rule.until = changeDate(until)

        del self._ignoreValueChanges

    def removeDates(self, cmpFn, endpoint):
        """Remove dates in exdates and rdates before or after endpoint.

        @param cmpFn: Comparison function (will be called with two
                      C{datetime} objects as arguments).
        @type  cmpFn: callable

        @param endpoint: Start or end point for comparisons
        @type  endpoint: C{datetime}

        """
        for datetype in 'rdates', 'exdates':
            datelist = getattr(self, datetype, None)
            if datelist is not None:
                # need to start from the end, bug 11005
                for i in reversed(xrange(len(datelist))):
                    if cmpFn(datelist[i], endpoint):
                        self._ignoreValueChanges = True
                        del datelist[i]
                        self._ignoreValueChanges = False

    def moveRuleEndBefore(self, dtstart, end):
        """Make self's rules end before end.

        @param dtstart: Start time for the recurrence rule
        @type  dtstart: C{datetime}

        @param end: Date not to include in the rule's new end
        @type  end: C{datetime}

        """
        #change the rule, onRuleSetChanged will trigger cleanRule for master
        for rule in getattr(self, 'rrules', []):
            if (not rule.hasLocalAttributeValue('until')
                    or (rule.calculatedUntil() >= end)):
                rule.moveUntilBefore(dtstart, end)
        self.removeDates(datetime.__ge__, end)
Пример #27
0
class PhotoMixin(items.ContentItem):
    schema.kindInfo(displayName="Photo Mixin Kind",
                    displayAttribute="displayName")
    dateTaken = schema.One(schema.DateTime, displayName="taken")
    file = schema.One(schema.String)
    exif = schema.Mapping(schema.String, initialValue={})
    photoBody = schema.One(schema.Lob)

    about = schema.One(redirectTo='displayName')
    date = schema.One(redirectTo='dateTaken')
    who = schema.One(redirectTo='creator')

    schema.addClouds(sharing=schema.Cloud(dateTaken, photoBody))

    def importFromFile(self, path):
        data = file(path, "rb").read()
        (mimeType, encoding) = mimetypes.guess_type(path)
        self.photoBody = utils.dataToBinary(self,
                                            'photoBody',
                                            data,
                                            mimeType=mimeType)

    def importFromURL(self, url):
        if isinstance(url, URL):
            url = str(url)
        data = urllib.urlopen(url).read()
        (mimeType, encoding) = mimetypes.guess_type(url)
        self.photoBody = utils.dataToBinary(self,
                                            'photoBody',
                                            data,
                                            mimeType=mimeType)

    def exportToFile(self, path):
        data = utils.binaryToData(self.photoBody)
        out = file(path, "wb")
        out.write(data)
        out.close()

    def processEXIF(self):
        input = self.photoBody.getInputStream()
        data = input.read()
        input.close()
        stream = cStringIO.StringIO(data)
        try:
            exif = EXIF.process_file(stream)

            # Warning, serious nesting ahead
            self.dateTaken = datetime.datetime.fromtimestamp(
                time.mktime(
                    time.strptime(str(exif['Image DateTime']),
                                  "%Y:%m:%d %H:%M:%S")))

            self.exif = {}
            for (key, value) in exif.iteritems():
                if isinstance(value, EXIF.IFD_Tag):
                    self.exif[key] = value.printable
                else:
                    self.exif[key] = value

        except Exception, e:
            logger.debug("Couldn't process EXIF of Photo %s (%s)" % \
                (self.itsPath, e))
Пример #28
0
class RSSChannel(ItemCollection):

    schema.kindInfo(displayName="RSS Channel")

    link = schema.One(
        schema.URL,
        displayName="link"
    )

    category = schema.One(
        schema.String,
        displayName="Category"
    )

    author = schema.One(
        schema.String,
        displayName="Author"
    )

    date = schema.One(
        schema.DateTime,
        displayName="Date"
    )

    url = schema.One(
        schema.URL,
        displayName="URL"
    )

    etag = schema.One(
        schema.String,
        displayName="eTag"
    )

    lastModified = schema.One(
        schema.DateTime,
        displayName="Last Modified"
    )

    copyright = schema.One(
        schema.String,
        displayName="Copyright"
    )

    language = schema.One(
        schema.String,
        displayName="Language"
    )

    isUnread = schema.One(
        schema.Boolean,
        displayName="Is Unread"
    )

    schema.addClouds(
        sharing = schema.Cloud(author, copyright, link, url)
    )

    who = schema.Role(redirectTo="author")
    about = schema.Role(redirectTo="about")

    def Update(self, data=None):
        logger.info("Updating channel: %s" % getattr(self, 'displayName',
                    self.url))

        etag = self.getAttributeValue('etag', default=None)
        lastModified = self.getAttributeValue('lastModified', default=None)
        if lastModified:
            lastModified = lastModified.timetuple()

        if not data:
            # fetch the data
            data = feedparser.parse(str(self.url), etag, lastModified)

        # set etag
        SetAttribute(self, data, 'etag')

        # set lastModified
        modified = data.get('modified')
        if modified:
            self.lastModified = datetime.datetime.fromtimestamp(time.mktime(modified)).replace(tzinfo=None)

        # if the feed is bad, raise the sax exception
        try:
            if data.bozo and not isinstance(data.bozo_exception, feedparser.CharacterEncodingOverride):
                logger.error("For url '%s', feedparser exception: %s" % (self.url, data.bozo_exception))
                raise data.bozo_exception
        except KeyError:
            print "Error"
            return

        self._DoChannel(data['channel'])
        count = self._DoItems(data['items'])
        if count:
            logger.info("...added %d RSSItems" % count)

    def addRSSItem(self, rssItem):
        """
            Add a single item, and add it to any listening collections
        """
        rssItem.channel = self
        self.add(rssItem)


    def _DoChannel(self, data):
        # fill in the item
        attrs = {'title':'displayName'}
        SetAttributes(self, data, attrs)

        attrs = ['link', 'description', 'copyright', 'category', 'language']
        # @@@MOR attrs = ['link', 'description', 'copyright', 'creator', 'category', 'language']
        SetAttributes(self, data, attrs)

        date = data.get('date')
        if date:
            self.date = parse(str(date))

    def _DoItems(self, items):
        # make children

        # lets look for each existing item. This is ugly and is an O(n^2) problem
        # if the items are unsorted. Bleah.
        view = self.itsView

        count = 0

        for newItem in items:

            # Convert date to datetime object
            try:
                itemDate = newItem.date
            except:
                itemDate = None

            if itemDate:
                newItem.date = parse(str(itemDate))
            else:
                # Give the item a date so we can sort on it
                newItem.date = datetime.datetime.now()

            # Disregard timezone for now
            newItem.date = newItem.date.replace(tzinfo=None)

            found = False
            for oldItem in self.resultSet:
                # check to see if this doesn't already exist
                if oldItem.isSimilar(newItem):
                    found = True
                    oldItem.Update(newItem)
                    break

            if not found:
                # we have a new item - add it
                rssItem = RSSItem(view=view)
                rssItem.Update(newItem)
                try:
                    self.addRSSItem(rssItem)
                    count += 1
                except:
                    logger.error("Error adding an RSS item")

        return count

    def markAllItemsRead(self):
        for item in self:
            item.isRead = True
Пример #29
0
class RecurrenceRuleSet(items.ContentItem):
    rrules = schema.Sequence(RecurrenceRule,
                             displayName="Recurrence rules",
                             inverse=RecurrenceRule.rruleFor,
                             deletePolicy='cascade')
    exrules = schema.Sequence(RecurrenceRule,
                              displayName="Exclusion rules",
                              inverse=RecurrenceRule.exruleFor,
                              deletePolicy='cascade')
    rdates = schema.Sequence(schema.DateTime, displayName="Recurrence Dates")
    exdates = schema.Sequence(schema.DateTime, displayName="Exclusion Dates")
    events = schema.Sequence("osaf.pim.calendar.Calendar.CalendarEventMixin",
                             displayName="Events",
                             inverse="rruleset")

    schema.addClouds(copying=schema.Cloud(rrules, exrules, rdates, exdates))

    def addRule(self, rule, rruleorexrule='rrule'):
        """Add an rrule or exrule, defaults to rrule."""
        rulelist = getattr(self, rruleorexrule + 's', [])
        rulelist.append(rule)
        setattr(self, rruleorexrule + 's', rulelist)

    def createDateUtilFromRule(self, dtstart):
        """Return an appropriate dateutil.rrule.rruleset."""
        ruleset = rruleset()
        for rtype in 'rrule', 'exrule':
            for rule in getattr(self, rtype + 's', []):
                getattr(ruleset, rtype)(rule.createDateUtilFromRule(dtstart))
        for datetype in 'rdate', 'exdate':
            for date in getattr(self, datetype + 's', []):
                getattr(ruleset, datetype)(date)
        return ruleset

    def setRuleFromDateUtil(self, ruleSetOrRule):
        """Extract rules and dates from ruleSetOrRule, set them in self.
        
        If a dateutil.rrule.rrule is passed in instead of an rruleset, treat
        it as the new rruleset.
        
        """
        if isinstance(ruleSetOrRule, rrule):
            set = rruleset()
            set.rrule(ruleSetOrRule)
            ruleSetOrRule = set
        elif not isinstance(ruleSetOrRule, rruleset):
            raise TypeError, "ruleSetOrRule must be an rrule or rruleset"
        for rtype in 'rrule', 'exrule':
            rules = getattr(ruleSetOrRule, '_' + rtype, [])
            if rules is None: rules = []
            itemlist = []
            for rule in rules:
                ruleItem = RecurrenceRule(parent=self)
                ruleItem.setRuleFromDateUtil(rule)
                itemlist.append(ruleItem)
            setattr(self, rtype + 's', itemlist)
        for typ in 'rdate', 'exdate':
            # While most dates are naive, strip tzinfo off
            naive = [stripTZ(e) for e in getattr(ruleSetOrRule, '_' + typ, [])]
            setattr(self, typ + 's', naive)

    def isCustomRule(self):
        """Determine if this is a custom rule.
        
        For the moment, simple daily, weekly, or monthly repeating events, 
        optionally with an UNTIL date, or the abscence of a rule, are the only
        rules which are not custom.
        
        """
        if self.hasLocalAttributeValue('rrules'):
            if len(self.rrules) > 1:
                return True  # multiple rules
            for recurtype in 'exrules', 'rdates', 'exdates':
                if self.hasLocalAttributeValue(recurtype) and \
                       len(getattr(self, recurtype)) != 0:
                    return True  # more complicated rules
            rule = list(self.rrules)[0]
            if rule.interval != 1:
                return True
            for attr in RecurrenceRule.listNames + ("byweekday", ):
                if getattr(rule, attr):
                    return True
        return False

    def getCustomDescription(self):
        """Return a string describing custom rules."""
        return "not yet implemented"

    def onValueChanged(self, name):
        """If the RuleSet changes, update the associated event."""
        if name in ('rrules', 'exrules', 'rdates', 'exdates'):
            if self.hasLocalAttributeValue('events'):
                for event in self.events:
                    event.getFirstInRule().cleanRule()
                    # assume we have only one conceptual event per rrule
                    break
Пример #30
0
class TaskMixin(items.ContentItem):
    """This is the set of Task-specific attributes.

      Task Mixin is the bag of Task-specific attributes.
    We only instantiate these Items when we "unstamp" an
    Item, to save the attributes for later "restamping".
    """
    
    schema.kindInfo(
        displayName = "Task Mixin Kind",
        description = 
            "This Kind is 'mixed in' to others kinds to create Kinds that "
            "can be instantiated"
    )

    recurrence = schema.Sequence(
        displayName = 'Recurrence Patterns',
        doc = 'This is a placeholder and probably not used for 0.5',
    )
    reminderTime = schema.One(
        schema.DateTime,
        displayName = 'ReminderTime',
        doc = 'This may not be general enough',
    )
    requestor = schema.One(
        Contact,
        displayName = 'Requestor',
        issues = [
            'Type could be Contact, EmailAddress or String',
            'Think about using the icalendar terminology'
        ],
        inverse = Contact.requestedTasks,
    )
    requestee = schema.Sequence(
        items.ContentItem,
        displayName = 'Requestee',
        issues = [
            'Type could be Contact, EmailAddress or String',
            'Think about using the icalendar terminology'
        ],
        otherName = 'taskRequests',
    )

    taskStatus = schema.One(
        TaskStatusEnum,
        displayName = 'Task Status',
    )
    dueDate = schema.One(schema.DateTime, displayName = 'Due date')
    who = schema.One(redirectTo = 'requestee')
    whoFrom = schema.One(redirectTo = 'requestor')
    about = schema.One(redirectTo = 'displayName')

    # XXX these two links should probably point to TaskMixin instead of
    #     Task, because as-is they won't support stamping.  Note that if
    #     this is corrected, the opposite ends should be set using 'inverse'
    #     instead of 'otherName'.
    dependsOn = schema.Sequence(
        'Task', displayName = 'Depends On', otherName = 'preventsProgressOn',
    )
    preventsProgressOn = schema.Sequence(
        'Task', displayName = 'Blocks', otherName = 'dependsOn',
    )

    schema.addClouds(
        copying = schema.Cloud(
            requestor, requestee, dependsOn, preventsProgressOn
        )
    )
    
    def InitOutgoingAttributes (self):
        """ Init any attributes on ourself that are appropriate for
        a new outgoing item.
        """
        try:
            super(TaskMixin, self).InitOutgoingAttributes ()
        except AttributeError:
            pass

        TaskMixin._initMixin (self) # call our init, not the method of a subclass

    def _initMixin (self):
        """
          Init only the attributes specific to this mixin.
        Called when stamping adds these attributes, and from __init__ above.
        """
        # default status is To Do
        self.taskStatus = 'todo'

        # default due date is 1 hour hence
        self.dueDate = datetime.now() + timedelta(hours=1)

        # default the title to any super class "about" definition
        try:
            self.about = self.getAnyAbout ()
        except AttributeError:
            pass

        # default the requestor to any super class "whoFrom" definition
        try:
            whoFrom = self.getAnyWhoFrom ()

            # I only want a Contact
            if not isinstance(whoFrom, Contact):
                whoFrom = self.getCurrentMeContact(self.itsView)

            self.requestor = whoFrom
        except AttributeError:
            pass

        """ @@@ Commenting out this block
        requestee can only accept Contact items.  At some point
        this code will need inspect the results of getAnyWho() and
        create Contact items for any EmailAddresses in the list

        # default the requestee to any super class "who" definition
        try:
            shallow copy the list
            self.requestee = self.getAnyWho ()

        except AttributeError:
            pass

        @@@ End block comment """

    def getAnyDate (self):
        """
        Get any non-empty definition for the "date" attribute.
        """
        
        # @@@ Don't do this for now, per bug 2654; will be revisited in 0.6.
        """
        try:
            return self.dueDate
        except AttributeError:
            pass
        """
        return super (TaskMixin, self).getAnyDate ()

    def getAnyWho (self):
        """
        Get any non-empty definition for the "who" attribute.
        """
        try:
            return self.requestee
        except AttributeError:
            pass
        return super (TaskMixin, self).getAnyWho ()

    def getAnyWhoFrom (self):
        """
        Get any non-empty definition for the "whoFrom" attribute.
        """
        try:
            return self.requestor
        except AttributeError:
            pass
        return super (TaskMixin, self).getAnyWhoFrom ()