#!/usr/bin/env python import time import exceptions import sys import os import outlook import samsung_phone.calendar import gregorian_calendar import text_encoding.UTF8 data_source_prefix = "" # "[W]" is_verbose = False def deoutlook_string(body): body = body.replace(u"\u2026", "...").replace(u"\u2013", "-").replace(u"\u20ac", " EUR").replace(u"\u2019", "'") #body = body.decode("cp1252") is_weird = False for c in body: if ord(c) > 255: is_weird = True print "weird char:", hex(ord(c)) if is_weird: for c in body: if ord(c) >= 32 and ord(c) < 127: print c, else: print "\\x%02X" % ord(c), print #body = "".join([chr(ord(x)) for x in body if ord(x) < 255]).decode("iso8859-1").encode("utf-8") body = body.encode("utf-8") # \x2026 ellipsis # \x0D\x0Asorry\x2026. \x0D\x0Ag r\xFC\xDFe, h o r s t #print body """for c in body: if ord(c) >= 32 and ord(c) < 127: print c, else: print "\\x%02X" % ord(c), print""" #body = body.encode("utf-8") # ??? return body arena_count = 5 # keep 5 items available. def zap_items_for_arena(calendar_connection): """ This function is dangerous. What it does is keep an arena (free space) at the top so you can add your own appointments on the phone). Let's say you pushed your PC appointments to your cell phone and by that fill all your cell phone calendar slots. If you now wanted to note down some appointment while on the go, you wouldn't be able to. Therefore, this function will make room for some items by zapping some items that are most in the future (ideally). Note that it isn't intelligent enough not to destroy them on the next push yet. """ try: for i in range(calendar_connection.max_count, calendar_connection.max_count - arena_count, -1): calendar_connection.remove(i) except: pass # given an outlook item (!), a starting time and an ending time (both in outlook format), prepares a cellphone item. # arguments: item :: , start :: , end :: def prepare_push_item(item, start, end): cellphone_item = samsung_phone.calendar.Item() cellphone_item.ID = 1 # FIXME. cellphone_item.subject = deoutlook_string(item.Subject) if cellphone_item.subject.startswith("Aktualisiert: "): cellphone_item.subject = cellphone_item.subject[len("Aktualisiert: ") : ] if cellphone_item.subject.startswith("Updated: "): cellphone_item.subject = cellphone_item.subject[len("Updated: ") : ] if cellphone_item.subject.find("Grundlagen der ") > -1: # either at the beginning or after a colon. cellphone_item.subject = cellphone_item.subject.replace("Grundlagen der ", "Grundl. ") useful_subject = cellphone_item.subject cellphone_item.subject = text_encoding.UTF8.abbreviate(cellphone_item.subject, 15) body = deoutlook_string(item.Body) # Because the cell phone has a small, field value length limit, strip mostly useless stuff: if body.find("_____________________________________________") > -1: body = body.split("_____________________________________________", 1)[0] body = body.replace("*~*~*~*~*~*~*~*~*~*", "") detailed_timezone = "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna." if body.find(detailed_timezone) > -1: body = body.replace(detailed_timezone, "(GMT+01:00)") if body.startswith("Zeit: "): body = body.split("\n", 1)[1].lstrip() # skip first line ("Zeit: Mittwoch, 06. August 2008 10:00-11:30 (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna."). if body == "": body = useful_subject body = data_source_prefix + body cellphone_item.details = text_encoding.UTF8.abbreviate(body.strip().replace("\n", ".").replace("\x0D", ""), 100) # TODO newline properly. TODO cull. # FIXME use this: cellphone_item.details = text_encoding.UTF8.abbreviate(body.strip().replace("\n", "\x0A").replace("\x0D", ""), 100) # TODO newline properly. TODO cull. cellphone_item.location = text_encoding.UTF8.abbreviate(deoutlook_string(item.Location), 15) cellphone_item.start = start[ : 5] # only minute resolution. # Technically, Samsung seems to be handling appointments to be: start is inclusive, end is inclusive. # I don't like the "10:00-11:59" display, though, so I cheat here by just writing "10:00-12:00". # This works except when it's "2008-01-01T00:00..2008-01-02T00:00", in which case it looks like a multi-day event on the cell phone. # That's why I special-case these here ONLY (formally, you'd _always_ subtract 1thingie from "end"). cellphone_item.end = end if cellphone_item.end[0 : 3] > cellphone_item.start[0 : 3] and cellphone_item.end[3 : 5] == [0,0]: # looks like another day, but isn't. cellphone_item.end = gregorian_calendar.decrement_gregorian(end, 5, 1) cellphone_item.end = cellphone_item.end[ : 5] # only minute resolution. if item.ReminderSet: # ? ReminderSoundFile ReminderMinutesBeforeStart = item.ReminderMinutesBeforeStart cellphone_item.remind_mode = samsung_phone.calendar.RemindMode.minute cellphone_item.remind_value = ReminderMinutesBeforeStart #print "reminder for \"%s\" (\"%s\")" % (item.Subject, item.Start) else: cellphone_item.remind_mode = samsung_phone.calendar.RemindMode.no #print "no reminder for \"%s\"" % item.Subject cellphone_item.repeat_until = cellphone_item.end[ : 3] return cellphone_item def prepare_push_items(starting_time): pushed_items = [] low_cutoff_time = starting_time - 60 * 60 * 24 * 7 # 7 days in the past in order to catch items that have been moved from the past (imperfect). cutoff_time = low_cutoff_time + 60 * 60 * 24 * 80 # plan 80 days from there. # TODO maybe sort these by start time? Cellphone doesn't seem to do so. for item, next_occurrences in outlook.get_next_appointments(low_cutoff_time, cutoff_time): for start, end in next_occurrences: cellphone_item = prepare_push_item(item, start, end) pushed_items.append(cellphone_item) # TODO self.location = None #self.repeat_mode = samsung_phone.calendar.RepeatMode.no # TODO self.repeat_until = [None, None, None] # [year, month, day] #print item.ResponseStatus # TODO index? cellphone_calendar_connection_1.append(cellphone_item) return pushed_items def push_items(cellphone_calendar_connection_1, pushed_items): for item in pushed_items: #print "Pushing", item.start, item.end, item.subject, item.details pass #sys.exit(0) #print "deleting from cellphone..." # zap_items_for_arena(cellphone_calendar_connection_1) # since my cell phone does not sort itself, I sort them here. # this also helps in case the cell phone calendar store gets full: then it pushed all the earlier items in preferrence to the later items. pushed_items.sort(lambda a, b : cmp(a.start, b.start) or cmp(a.subject, b.subject)) #print "pushing new entries..." for item in pushed_items: #print "Pushing", item.subject try: cellphone_calendar_connection_1.append(item) except exceptions.Exception, e: if cellphone_calendar_connection_1.full_P(): break else: print >>sys.stderr, "error while pushing the item starting at %s: %s" % (item.start, e) import traceback traceback.print_exc() # FIXME print that in the balloon. pass # FIXME this is dangerous and _can_ lose data. Do not use your cell phone as master calendar storage. zap_items_for_arena(cellphone_calendar_connection_1) return pushed_items def push_items_with_gui(): trayicon_1 = trayicon.TrayIcon(os.path.join(os.path.dirname(sys.argv[0]), "icon", "samsung_x680.ico")) try: cellphone_calendar_connection_1 = samsung_phone.calendar.Connection() cellphone_calendar_connection_1.truncate() pushed_items = push_items(cellphone_calendar_connection_1, prepare_push_items()) trayicon_1.set_balloon_text("sent %d items to the cell phone" % len(pushed_items)) trayicon_1.close() except exceptions.Exception, e: import traceback trayicon_1.set_balloon_text("%s %s\n%s" % (e.__class__, str(e), traceback.format_exc()), "error") # trayicon_1.close() argh. if __name__ == "__main__": #push_items_with_gui() cellphone_calendar_connection_1 = samsung_phone.calendar.Connection() cellphone_calendar_connection_1.truncate() push_items(cellphone_calendar_connection_1, prepare_push_items(time.time()))