Changeset 2059

Show
Ignore:
Timestamp:
05/27/07 14:20:27 (19 months ago)
Author:
abaumann
Message:

update rrule processing code to handle new XML format (totally untested!)
fix bug caused by previous rearrangement of XML-generating code
general cleanup: change getXMLField to return None instead of if the node doesn't exist

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • plugins/moto-sync/motosync.py

    r2056 r2059  
    1515import xml.dom.minidom 
    1616from datetime import date, datetime, timedelta, time as datetime_time 
    17 import dateutil.parser, dateutil.rrule, dateutil.tz 
     17import dateutil.parser, dateutil.rrule as rrule, dateutil.tz 
    1818import opensync 
    1919 
     
    8080VCAL_DAYS = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'] 
    8181 
     82# days of week as constants in the dateutil.rrule module 
     83RRULE_DAYS = [rrule.MO, rrule.TU, rrule.WE, rrule.TH, rrule.FR, rrule.SA, rrule.SU] 
     84 
    8285# repeat types in the calendar 
    8386MOTO_REPEAT_NONE = 0 
     
    180183 
    181184def getXMLField(doc, tagname, subtag=None): 
    182     """Returns text in a given XML tag, or '' if not set. 
     185    """Returns text in a given XML tag, or None if not set. 
    183186 
    184187    The XML structure that it looks for is: 
     
    189192    elts = doc.getElementsByTagName(tagname) 
    190193    if elts == []: 
    191         return '' 
     194        return None 
    192195    elt = elts[0] 
    193196    if subtag: 
    194197        children = elt.getElementsByTagName(subtag) 
    195198        if children == []: 
    196             return '' 
     199            return None 
    197200        elt = children[0] 
    198201    return getXMLText(elt) 
     
    275278    """ 
    276279 
    277     # XXX FIXME: whole function needs reworkign for new XML format 
    278     return (MOTO_REPEAT_NONE, []) 
    279  
    280280    # can't support multiple rules 
    281281    if len(rulenodes) != 1: 
     
    283283    rulenode = rulenodes[0] 
    284284 
    285     # build hash of rule parts 
    286     rules = {} 
    287     for node in rulenode.childNodes: 
    288         key = node.nodeName.lower() 
    289         val = getXMLText(node).lower() 
    290         if key[:1] == 'by': 
    291             val = set(val.split(',')) 
    292         rules[key] = val 
    293  
    294     # extract the parts 
    295     assert(rules.has_key('content')) # required 
    296     freq = rules['content'].lower() 
    297     bymonth = rules.get('bymonth') 
    298     byweekno = rules.get('byweekno') 
    299     byyearday = rules.get('byyearday') 
    300     bymonthday = rules.get('bymonthday') 
    301     byday = rules.get('byday') 
     285    # extract rule parts 
     286    freqstr = getXMLField(rulenode, 'Frequency').lower() 
     287    if freqstr == 'daily': 
     288        freq = rrule.DAILY 
     289    elif freqstr == 'weekly': 
     290        freq = rrule.WEEKLY 
     291    elif freqstr == 'monthly': 
     292        freq = rrule.MONTHLY 
     293    elif freqstr == 'yearly': 
     294        freq = rrule.YEARLY 
     295    else: 
     296        assert(False, "invalid frequency %s" % freqstr) 
     297 
     298    until = getXMLField(rulenode, 'Until') 
     299    if until: 
     300        until = parse_ical_time(until) 
     301    count = getXMLField(rulenode, 'Count') 
     302    if count: 
     303        count = int(count) 
     304    interval = getXMLField(rulenode, 'Interval') 
     305    if interval: 
     306        interval = int(interval) 
     307 
     308    def getSet(name, convfunc=int): 
     309        """Internal convenience function, get a set of values from XML.""" 
     310        val = getXMLField(rulenode, name) 
     311        if not val: 
     312            return None 
     313        return set(map(convfunc, val.split(','))) 
     314 
     315    bymonth = getSet('ByMonth') 
     316    byweekno = getSet('ByWeekNo') 
     317    byyearday = getSet('ByYearDay') 
     318    bymonthday = getSet('ByMonthDay') 
     319    byday = getSet('ByDay', lambda s: s.upper()) 
    302320 
    303321    # fail the conversion if any of these are set 
    304     if (rules.has_key('byhour') or rules.has_key('byminute') 
    305         or rules.has_key('bysecond') or rules.has_key('bysetpos') 
    306         or rules.get('interval', '1') != '1'): 
     322    if (getXMLField(rulenode, 'ByHour') or getXMLField(rulenode, 'ByMinute') 
     323        or getXMLField(rulenode, 'BySecond') or getXMLField(rulenode, 'BySetPos') 
     324        or (interval is not None and interval != 1)): 
    307325        return (MOTO_REPEAT_NONE, []) 
    308326 
     
    320338 
    321339    # now test if the rule matches what we can represent 
    322     if (freq == 'daily' and (not byday or byday == byalldays) 
     340    if (freq == rrule.DAILY and (not byday or byday == byalldays) 
    323341        and not bymonthday and not byyearday and not byweekno 
    324342        and not bymonth): 
    325343        moto_type = MOTO_REPEAT_DAILY 
    326     elif (freq == 'weekly' and (not byday or byday == set([eventday])) 
     344    elif (freq == rrule.WEEKLY and (not byday or byday == set([eventday])) 
    327345            and not bymonthday and not byyearday and not byweekno and 
    328346            not bymonth): 
    329347        moto_type = MOTO_REPEAT_WEEKLY 
    330     elif (freq == 'monthly' and not byday 
     348    elif (freq == rrule.MONTHLY and not byday 
    331349            and (not bymonthday or bymonthday == set([eventdt.day])) 
    332350            and not byyearday and not byweekno 
    333351            and (not bymonth or bymonth == byallmonths)): 
    334352        moto_type = MOTO_REPEAT_MONTHLY_DATE 
    335     elif (freq == 'monthly' and byday == byeventweekday and not bymonthday 
     353    elif (freq == rrule.MONTHLY and byday == byeventweekday and not bymonthday 
    336354            and not byyearday and not byweekno 
    337355            and (not bymonth or bymonth == byallmonths)): 
    338356        moto_type = MOTO_REPEAT_MONTHLY_DAY 
    339     elif (freq == 'yearly' and not byday 
     357    elif (freq == rrule.YEARLY and not byday 
    340358            and (not bymonthday or bymonthday == set([eventdt.day])) 
    341359            and not byyearday and not byweekno 
     
    348366    # now we need to work out the exceptions and see if this event still occurs 
    349367 
    350     # recombine the rule parts into a single string, and parse into an rruleset 
    351     # XXX FIXME: totally different rrule format 
    352     # rulestr = ';'.join(map(getXMLText, rulenode.getElementsByTagName('Rule'))) 
    353     # ruleset = dateutil.rrule.rrulestr(rulestr, dtstart=eventdt, forceset=True) 
     368    # convert byday strings to constants needed by rrule 
     369    byweekday = [] 
     370    for bydaystr in byday: 
     371        day = RRULE_DAYS[VCAL_DAYS.index(bydaystr[-2:])] 
     372        nth = bydaystr[:-2] 
     373        if nth: 
     374            byweekday.append(day(int(nth))) 
     375        else: 
     376            byweekday.append(day) 
     377 
     378    # create an rrule object for this rule, and an rrule set 
     379    ruleobj = rrule.rrule(freq, dtstart=eventdt, count=count, until=until, 
     380                          bymonth=list(bymonth), bymonthday=list(bymonthday), 
     381                          byweekday=byweekday, byyearday=list(byyearday), 
     382                          byweekno=list(byweekno)) 
     383    ruleset = rrule.rruleset() 
     384    ruleset.rrule(ruleobj) 
    354385 
    355386    # are there any future occurrences of this event? 
     
    368399    # XXX FIXME: totally different rrule format 
    369400    #for node in exrules: 
    370     #    ruleset.exrule(dateutil.rrule.rrulestr(getXMLField(node, 'Content'))) 
     401    #    ruleset.exrule(rrule.rrulestr(getXMLField(node, 'Content'))) 
    371402 
    372403    # are there any future occurrences of this event if we consider exceptions? 
     
    919950        top = doc.documentElement 
    920951 
     952        # compute the week number that the event falls in 
     953        if self.eventdt.day % 7 == 0: 
     954            weeknum = self.eventdt.day / 7 
     955        else: 
     956            weeknum = self.eventdt.day / 7 + 1 
     957 
    921958        if self.alarmdt: 
    922959            alarm = doc.createElement('Alarm') 
     
    953990            # create an rrule object for this recurrence 
    954991            if self.repeat_type == MOTO_REPEAT_MONTHLY_DATE: 
    955                 rrule = dateutil.rrule.rrule(dateutil.rrule.MONTHLY, 
    956                                              bymonthday=self.eventdt.day, 
    957                                              dtstart=self.eventdt) 
     992                rule = rrule.rrule(rrule.MONTHLY, bymonthday=self.eventdt.day, 
     993                                   dtstart=self.eventdt) 
    958994            elif self.repeat_type == MOTO_REPEAT_MONTHLY_DAY: 
    959                 weekday = dateutil.rrule.weekdays[self.eventdt.weekday()] 
    960                 # weeknum is calculated above in the XML generation 
    961                 rrule = dateutil.rrule.rrule(dateutil.rrule.MONTHLY, 
    962                                              byweekday=weekday(+weeknum), 
    963                                              dtstart=self.eventdt) 
     995                weekday = rrule.weekdays[self.eventdt.weekday()] 
     996                rule = rrule.rrule(rrule.MONTHLY, byweekday=weekday(+weeknum), 
     997                                   dtstart=self.eventdt) 
    964998            else: 
    965999                if self.repeat_type == MOTO_REPEAT_DAILY: 
    966                     freq = dateutil.rrule.DAILY 
     1000                    freq = rrule.DAILY 
    9671001                elif self.repeat_type == MOTO_REPEAT_WEEKLY: 
    968                     freq = dateutil.rrule.WEEKLY 
     1002                    freq = rrule.WEEKLY 
    9691003                elif self.repeat_type == MOTO_REPEAT_YEARLY: 
    970                     freq = dateutil.rrule.YEARLY 
    971                 rrule = dateutil.rrule.rrule(freq, dtstart=self.eventdt) 
     1004                    freq = rrule.YEARLY 
     1005                rule = rrule.rrule(freq, dtstart=self.eventdt) 
    9721006 
    9731007            # work out which dates the exceptions correspond to and 
     
    9761010            e.setAttribute('Value', 'DATE') 
    9771011            for exnum in self.exceptions: 
    978                 appendXMLChild(doc, e, 'Content', format_time(rrule[exnum], VCAL_DATE)) 
     1012                appendXMLChild(doc, e, 'Content', format_time(rule[exnum], VCAL_DATE)) 
    9791013            if e.hasChildNodes(): 
    9801014                top.appendChild(e) 
     
    9911025            appendXMLChild(doc, e, 'Frequency', 'MONTHLY') 
    9921026            day = VCAL_DAYS[self.eventdt.weekday()] 
    993             # compute the week number that the event falls in 
    994             if self.eventdt.day % 7 == 0: 
    995                 weeknum = self.eventdt.day / 7 
    996             else: 
    997                 weeknum = self.eventdt.day / 7 + 1 
    9981027            appendXMLChild(doc, e, 'ByDay', '%d%s' % (weeknum, day)) 
    9991028        elif self.repeat_type == MOTO_REPEAT_YEARLY: 
     
    10691098            duration = event.getElementsByTagName('Duration')[0] 
    10701099            def toint(numstr): 
    1071                 """Convert a string to an integer, unless it's empty, in which case return 0.""" 
    1072                 if numstr == '': 
     1100                """Convert a string to an integer, unless it's None, in which case return 0.""" 
     1101                if numstr is None: 
    10731102                    return 0 
    10741103                else: 
     
    10821111        else: 
    10831112            endstr = getField('DateEnd') 
    1084             if endstr != '': 
     1113            if endstr is not None: 
    10851114                self.duration = parse_ical_time(endstr) - self.eventdt 
    10861115            else: 
     
    10991128 
    11001129        triggerstr = getField('Alarm', 'AlarmTrigger') 
    1101         if triggerstr == '': 
     1130        if triggerstr is None: 
    11021131            self.alarmdt = None 
    11031132        else: 
     
    13201349        # handle the name and formatted name fields 
    13211350        self.name = getField('FormattedName') 
    1322         if self.name != '': 
     1351        if self.name: 
    13231352            # FIXME: cheesy attempt at taking apart the FormattedName 
    13241353            last = getField('Name', 'LastName') 
    1325             if last != '': 
     1354            if last: 
    13261355                lastidx = self.name.find(last) 
    13271356                if lastidx == 0: 
    13281357                    first = getField('Name', 'FirstName') 
    1329                     if first != '': 
     1358                    if first: 
    13301359                        firstidx = self.name.find(first) 
    13311360                        if firstidx != -1: 
     
    13381367            # compute FormattedName from Name 
    13391368            nameparts = [getField('Name', p) for p in XML_NAME_PARTS] 
    1340             nameparts = [p != '' for p in nameparts] 
     1369            nameparts = [p for p in nameparts if p] 
    13411370            self.name = ' '.join(nameparts) 
    13421371            self.firstlast_enabled = 0 
    13431372            self.firstlast_index = self.name.index(getField('Name', 'LastName')) 
    13441373 
    1345         catname = getField('Categories', 'Category').lower() 
     1374        catname = getField('Categories', 'Category') 
     1375        if catname: 
     1376            catname = catname.lower() 
    13461377        self.categorynum = revcategories.get(catname, MOTO_CATEGORY_DEFAULT) 
    13471378        self.nickname = getField('Nickname') 
    13481379        bdaystr = getField('Birthday') 
    1349         if bdaystr != '': 
     1380        if bdaystr: 
    13501381            self.birthday = parse_ical_time(bdaystr) 
    13511382        else: 
     
    14631494        else: 
    14641495            birthdaystr = '' 
     1496        if self.parent.nickname: 
     1497            nickname = self.parent.nickname 
     1498        else: 
     1499            nickname = '' 
    14651500        if self.address: 
    1466             (street1, street2, city, state, postcode, country) = self.address 
     1501            # remove None parts 
     1502            address = [] 
     1503            for part in self.address: 
     1504                if part: 
     1505                    address.append(part) 
     1506                else: 
     1507                    address.append('') 
     1508            (street1, street2, city, state, postcode, country) = address 
    14671509        else: 
    14681510            street1 = street2 = city = state = postcode = country = '' 
     
    14731515                self.parent.firstlast_index, self.picture_path, 
    14741516                0, 0, street2, street1, city, state, postcode, country, 
    1475                 self.parent.nickname, birthdaystr) 
     1517                nickname, birthdaystr) 
    14761518 
    14771519    def child_xml(self, doc): 
     
    18231865 
    18241866    ret = getXMLField(doc, 'device').strip() 
    1825     if ret == '': 
     1867    if not ret: 
    18261868        raise opensync.Error('device not specified in config file', opensync.ERROR_MISCONFIGURATION) 
    18271869    return ret