Re: PalmDB requests and bug report
"Jeff Mikels" <[email protected]> Wed, 27 Sep 2006 21:50:30 -0400
| Newsgroups | gmane.comp.handhelds.palm.progect |
|---|---|
| Message-ID | <[email protected]> |
Thanks... A while ago, I submitted a python module to sourceforge to read and write both calendar and addressbook. I have used it to create a palm2ics convertor. I actually didn't write it, but I modified it some. I don't know if it will be interesting to you or not, but I've attached it here. On the documentation front, I'd be happy to help you set up and run a documentation wiki. How much storage/database does sourceforge give us? Ideally, I'd like to have a set of python modules that would allow us to sync palm data with XML or a database like MySQL, plugin abilities so that many people could write their own sync code, and a conduit to make it all happen when I push my hotsync button! I'll help you with the code however I can, but I'm sure I can make some headway on the documentation as we go along. Is anyone else interested in this? It seems like opensource palm development is nearly dead. Oh, on the syntax front, I use scite as my text editor and it shows all python indentation inconsistencies with a little curly blue line. I myself always use tabs, and I've set the editor to honor that. On 9/27/06, Rick Price <[email protected]> wrote: > > > > On Tue, 26 Sep 2006, "Jeff Mikels" wrote: > > > I should have said "unpack" or "crack" instead of decrypt. That was > silly of > > me. > > No problem, when I reread your email after writing most of my reply, I > realized what you had meant. > > Basically it was a bad day, and I wasn't really paying attention as I read > it. > > > > > Anyway, about the plugin, the xml produced from the tododb by the basic > > plugin is perfect except for the payload. > > > > Yea, that makes sense, that is what is supposed to happen. > > > The todo db has 3 bytes, then a string for the description, then a > string > > for the note. I'm pretty sure those three bytes are for the category > int, > > the date, the deleted flag, and the completed flag. > > Some of this is already unpacked for you AFAIK. I think the category int, > and the deleted and so on flags are part of the header we unpack but I > would have to check. > > > > > It won't be difficult at all to implement, but I'm having trouble > figuring > > out the API (yes, it's the latest SVN) and python's unpack doesn't have > the > > option for variable length null terminated strings. What do you use? > > I believe I just cracked the string on the 0 at the end of the string. I > believe the function is split(), and you pass zero in for the char > somehow. > > I think the appropriate thing would be something like: > > Text,Note=incomingString.split('\0') > > You have to be careful when there isn't a note on the end, but I think > there *should* always be a double zero in that case, in which case the > above code should work. > > > > > > > > > Help me out though, do I need anything more than this? > > > > class ToDoRecordPlugin(PalmDB.Plugins.BasePlugin.DataRecord): > > def getPDBCreatorID(self): > > return 'todo' > > def getPalmApplicationName(self): > > return 'ToDo' > > This part seems fine. > > > > > def _crackPayload(self,dstr): > > [code here] > > > > This part is where you should have overridden the call to create the > object that is for each Palm record, that object implements _crackPayload. > I'm hoping to have the time to do it quickly to show you how. It should > only take 15 minutes to half an hour for me to write the skeleton for the > plugin for you. > > Then you can implement _crackPayload and be done with it. > > > Once the dust settles, would you have time to write a quick howto? > > Since you didn't design things, you should be in a better position to > start the document, I can expand on things for you as required. > > > > > > > Oh, one more thing, your whitespace is quite inconsistent in your code. > You > > have spaces and tabs all over the place. > > Yes, I would dearly love to find a good Python reformatter program. > > Rick > > > > > > On 9/26/06, Rick Price <[email protected]> wrote: > >> > >> > >> > >> On Tue, 26 Sep 2006, "Jeff Mikels" wrote: > >> > >>> I'm having a problem with the crackPalmDate function > >>> > >>> (I'm using svn code) > >>> > >>> PILOT_TIME_DELTA = 2082844800L > >>> def crackPalmDate(variable): > >>> if variable == 0: > >>> return None > >>> else: > >>> return datetime.datetime.fromtimestamp > >> (variable-PILOT_TIME_DELTA) > >>> > >>> I often get a ValueError because variable-PILOT_TIME_DELTA is > sometimes > >> a > >>> negative number. I've changed the code to be... > >>> > >>> PILOT_TIME_DELTA = 2082844800L > >>> def crackPalmDate(variable): > >>> if variable == 0 or variable < PILOT_TIME_DELTA: > >>> return None > >>> else: > >>> return datetime.datetime.fromtimestamp > >>> (variable-PILOT_TIME_DELTA) > >>> > >>> Is this a problem? > >> > >> No, I don't think so, except that if the variable is before the > >> PILOT_TIME_DELTA surprises could possibly happen in the Palm... > >> > >> The reasoning behind my code (and it could be flawed), is that we want > to > >> know if there really wasn't a value there, like in SQL. So since I > didn't > >> think that the value would ever be zero (or below zero), using zero for > >> NULL seemed like a perfect solution. > >> > >> If that is not true, then we may need to rethink how things work. > >> > >> Can you send me a patch with your changes so I can incorporate it? > >> > >> Do make sure you have the latest code from SVN, I haven't changed it > for a > >> few weeks, but it's best to make sure you have the latest. > >> > >>> > >>> > >>> Additionally, I'd like to create a "plugin" for the todo database, but > >> I'm > >>> not sure how to go about doing it. The Base Plugin class is working > >>> perfectly but the payload needs to be decrypted properly. How can I > >> create a > >>> plugin that only overloads the payload functions and have it loaded > when > >>> necessary? > >>> > >> > >> Well, I like plugins that are loaded automagically and that can be > >> distributed separately, but in this case, I decided to keep it simple > and > >> have you declare the plugin to the code. > >> > >> I also figured that we would just roll the plugins (not created by me) > >> into the release, since just about anything someone makes will be > useful > >> to others. > >> > >> [ If you use the library for something proprietary, you would just call > >> the plugin notification code, and keep your code separate. ] > >> > >> In the file PluginManager.py, you will find the functions > >> registerPDBPlugin and deRegisterPDBPlugin. These are the functions you > use > >> to register your plugin with the framework. > >> > >> At the bottom of the file, I use the standard functions to register my > >> Progect plugin. > >> > >> > >> Now, I'm not really sure what you mean by decrypted. > >> > >> If you mean you need to translate the XML that *would* have been > generated > >> into something more useful, the code knows how to apply XSLT > stylesheets > >> for you; both coming and going. It can also do some simple > transformations > >> like gzip. > >> > >> If you need to output to say a binary or text format, I think it can be > >> done, but I would have to look at the code again to explain how. > >> > >> > >> I really think I misdesigned the plugin somehow as well, but I can't > >> really describe what I mean except to say this (because I haven't > thought > >> it through yet): > >> * Some classes do more than one thing - very bad > >> * The symptom is that the plugin is difficult to use. > >> * I expect that writing/reading formats other than XML will > >> be more difficult than it should be (as in binary or text). > >> > >> > >> If you can describe what you are doing to me, then maybe I can give you > >> some suggestions. > >> > >> I'm going to have to redesign the code _again_ because of not getting > the > >> plugin code right, but I will try and remain compatible, or at least > make > >> it so much easier to use that it's not hard for you to update your > code. > >> > >> > >> If you mean how do I crack the Palm task format, I can certainly help > you > >> with that too. Basically the plugin you create returns an object that > >> knows what to do with the raw data as passed in by the framework. > >> > >> You will need to find documentation on the Palm task format, I am > pretty > >> sure it can be found easily with Google. > >> > >> In this case, you would probably just subclass the default plugin so > that > >> it returns a special Task object that knows how to crack the payload > for > >> tasks, and puts the values into a dictionary. > >> > >> I believe the class ProgectRecord in plugins/ProgectPlugin.py is the > >> closest thing to what you want. And the important method to create is > >> _crackPayload. > >> > >> The problem is, that the Progect format is quite complex, and we are > >> forcing a tree into rows. So the Progect class is probably not a good > >> example of how to do something simple (or not a good example at > all...). > >> > >> For something like the task database, you should have almost _no_ work > to > >> do, and so don't let the extreme complexity of the Progect plugin scare > >> you off. > >> > >> I can't predict how much work cracking a task would be, but I would > hope > >> it would be under a hundred lines of Python code all told, and simple > code > >> at that. > >> > >> Generating and reading the XML would happen automatically if I remember > >> correctly, so the big problem is being able to crack and pack the task > >> format. (that is, the payload, because we crack the record header for > >> you). > >> > >> I would be more than happy to give you a hand with this task, while I > am > >> overloaded at work for the next day or so, perhaps we can talk via IM > or > >> on the phone. Even email would be fine, if you can help me understand > your > >> problem a little better. > >> > >> I can then explain how I think it can be accomplished easily with the > >> framework. > >> > >> I will certainly try to do this for anyone who wants to use the > framework, > >> but having the task format converted to and from XML is an important > >> feature for the framework and is therefore a priority for me. > >> > >> > >> Rick > >> > >>> > >>> > >>> > >>> On 9/7/06, Rick Price <[email protected]> wrote: > >>>> > >>>> Okay, I just managed to get the Python PalmDB library to go from a > >> Progect > >>>> file to XML and back again without dropping anything really obvious. > >>>> > >>>> Anyone who wants to check out the Subversion code is welcome to do > so. > >>>> > >>>> I think I need to do a major rewrite at some point in the next little > >> bit, > >>>> but first I will setup some conversions to popular programs that > >>>> read/write XML. > >>>> > >>>> First on the list is going to be treeline since I use it right now > and > >> the > >>>> > >>>> format seems to be basically sane. > >>>> > >>>> I will then probably try to do something with Omni Outliner for the > >> Mac. > >>>> > >>>> That will probably require fixing a bug in the library that seems to > >> show > >>>> up on the Mac. > >>>> > >>>> After that, I will see what I can do based on popularity. > >>>> > >>>> > >>>> Once I have treeline support working, and it's not too ugly to run, I > >> will > >>>> make a release of Python PalmDB so people can start to use it. > >>>> > >>>> I will release again I would guess after I get Omni Outliner support > >>>> working. > >>>> > >>>> Any questions, just email me. > >>>> > >>>> Rick > >>>> > >>>> > >>>> > >>>> > >>>> Remember to visit Polls, Files and Bookmarks sections at > >>>> http://groups.yahoo.com/group/progect. You are welcome to vote, > upload > >>>> your files and submit bookmarks. > >>>> Yahoo! Groups Links > >>>> > >>>> > >>>> > >>>> > >>>> > >>>> > >>>> > >>>> > >>>> > >>>> > >>>> > >>> > >>> > >>> -- > >>> Jeff Mikels > >>> leading people one step closer to Jesus > >>> http://jeff.mikels.cc > >>> http://thesouthsidechurch.org > >>> > >>> > >>> -- > >>> Jeff Mikels > >>> leading people one step closer to Jesus > >>> http://jeff.mikels.cc > >>> http://thesouthsidechurch.org > >>> > >>> > >>> [Non-text portions of this message have been removed] > >>> > >>> > >> > >> > >> > >> Remember to visit Polls, Files and Bookmarks sections at > >> http://groups.yahoo.com/group/progect. You are welcome to vote, upload > >> your files and submit bookmarks. > >> Yahoo! Groups Links > >> > >> > >> > >> > >> > >> > >> > >> > >> > >> > >> > > > > > > -- > > Jeff Mikels > > leading people one step closer to Jesus > > http://jeff.mikels.cc > > http://thesouthsidechurch.org > > > > > > [Non-text portions of this message have been removed] > > > > > > > > Remember to visit Polls, Files and Bookmarks sections at > http://groups.yahoo.com/group/progect. You are welcome to vote, upload > your files and submit bookmarks. > Yahoo! Groups Links > > > > > > > > > > > -- Jeff Mikels leading people one step closer to Jesus http://jeff.mikels.cc http://thesouthsidechurch.org ---------- """ Python module that parses palm files The files are parsed into list/dictionary data structure that mirrors the structure of the palm file. Thanks to Scott Leighton who provided the Palm File format at http://www.geocities.com/Heartland/Acres/3216/palmrecs.htm Usage import PalmFile.py fileStruct = PalmFile.readPalmFile(<fileName>) the structure you get back mirrors the file structure. I use import pprint pprint.pprint(fileStruct) to make sense of the data I get back see printAWeekWorthOfCalendar and printAllNames for examples to write this information back to a file, use PalmFile.writePalmFile(<fileName>,<fileType>,fileStruct) """ """ Author: [email protected] (since Version 0.4) Original Author: atotic-/[email protected] Version 0.5 Date: 04/08/2004 Added: Major additions to enable the writePalmFile() function. writePalmFile(fileName, fileData) writes a palm desktop .dat format file containing fileData. fileType is determined from fileData[0]['versionTag'] fileData must be an appropriately formed list / dictionary of the same format returned by readPalmFile Version 0.4 Date: 03/28/2004 Added: getNextRepeatedEvent(event) computes the next occurrence of a repeated event and returns it as an event dictionary printAWeekWorthOfCalendar(calendar,traceRepeats=0) prints to stdout a week's worth of upcoming events. if traceRepeats is 1, then this function will also look for upcoming occurrences of repeated events getEvents(calendar) returns a dictionary of events when given a palm datebook fileStruct as calendar getUpcomingEvents(calendar, daysAhead, traceRepeats=0) returns a list of events from calendar (palm datebook fileStruct) from the present moment up to daysAhead number of days into the future. If traceRepeats is 1, then function will also look for upcoming occurrences of repeated events. Version 0.3 Date: 12/12/03 Patch for readRepeatEvent, from John Lim (Rainlendar guy) Date: Apr 24, 2003 Version: 0.2: fixed dateExceptionCount parsing Date: 11/21/2002 Version: 0.1, my first Python code It works on my machine, Python 2.2, I have not tried it on any others Terminology used in coding: record - a list of items in a known predefined format, not composed of fields each record read has a HEADERDEF defined in this file that describes it frecord - a special record, where each item is a *field* field - fields have type & data. The labels are implicit in their position and are defined in this file as a list items - basic units we know how to read: byte/short/long/, etc """ # Globals readDebug = False writeDebug = False # Address book defines """ HEADERDEF lists format HEADERDEF are data that represent the file format. I've tried to make all parsing data driven, and HEADERDEF structs describe the grammar. When data to be read go beyound what HEADERDEF defines, 3rd column provides the name Python to be executed. headerDef tuple columns format col 1 property name col 2 type short, long, palm cstring, record, frecord 3 additional argument: if type in col 2 is record, the name of the entry that defines the struct count if type in col 2 is frecord, the python code to execute """ """See HEADERDEF lists format above""" addressHeaderDef = ( ("versionTag", "long"), ("fileName", "cstring"), ("tableString", "cstring"), ("nextFree", "long"), ("categoryCount","long"), ("categoryList", "record", "addressCategoryEntryDef", "categoryCount"), ("resourceID", "long"), ("fieldsPerRow", "long"), ("recIDPos", "long"), ("recStatus", "long"), ("placementPos", "long"), ("fieldCount", "short"), ("fieldEntryList", "record", "addressSchemaFieldDef", "fieldCount"), ("numEntries", "long"), ("addresses", "frecord","addressEntryFields") ) """See HEADERDEF lists format above""" addressCategoryEntryDef = ( ("index", "long"), ("id", "long"), ("dirtyFlag", "long"), ("longName", "cstring"), ("shortName", "cstring") ) """See HEADERDEF lists format above""" addressSchemaFieldDef = ( ("fieldEntryType", "short"), ) """See HEADERDEF lists format above""" addressEntryFields = ( "recordID", "status", "position", "lastName", "firstName", "title", "companyName", "phone1LabelID", "phone1Text", "phone2LabelID", "phone2Text", "phone3LabelID", "phone3Text", "phone4LabelID", "phone4Text", "phone5LabelID", "phone5Text", "address", "city", "state", "zip", "country", "note", "private", "category", "custom1Text", "custom2Text", "custom3Text", "custom4Text", "displayPhone" ) # Calendar defines """See HEADERDEF lists format above""" calendarHeaderDef = ( ("versionTag", "long"), ("fileName", "cstring"), ("tableString", "cstring"), ("nextFree", "long"), ("categoryCount","long"), ("categoryList", "record", "addressCategoryEntryDef", "categoryCount"), ("resourceID", "long"), ("fieldsPerRow", "long"), ("recIDPos", "long"), ("recStatus", "long"), ("placementPos", "long"), ("fieldCount", "short"), ("fieldEntry", "record", "addressSchemaFieldDef", "fieldCount"), ("numEntries", "long"), ("datebookList", "frecord","calendarEntryFields") ) calendarEntryFields = ( "recordID", "status", "position", "startTime", "endTime", "text", "duration", "note", "untimed", "private", "category", "alarmSet", "alarmAdvUnits", "alarmAdvType", "repeatEvent" ) ### # Generic reading and writing routines # (not to be accessed by user) ### import struct def readCString(f): """Read in a Palm-format string.""" """ String docs off the net: Strings less than 255 bytes are stored with the length specified in the first byte followed by the actual string. Zero length strings are stored with a 0x00 byte. Strings 255 bytes or longer are stored with a flag byte set to 0xFF followed by a short (2*Byte) that specifies the length of the string, followed by the actual string. """ retVal = None (firstByte, ) = struct.unpack("B", f.read(1)) if firstByte == 0 : retVal = ""; elif firstByte == 0xFF: (length, ) = struct.unpack("H", f.read(2)) retVal = f.read(length) else: # length was in first byte retVal = f.read(firstByte) return retVal def writeCString(f,s): """Writes string to Palm .dat file""" if writeDebug: print '---------------------------------------' print 'WRITE CSTRING' print s print '---------------------------------------' length = len(s) if length >= 255: format = "BH" + str(length) + "s" f.write(struct.pack(format,0xFF,int(length),s)) else: format = "B" + str(length) + "s" f.write(struct.pack("B" + str(length) + "s",int(length),s)) def readShort(f): """Read unsigned 2 byte value from a file f.""" (retVal,) = struct.unpack("H", f.read(2)) return retVal def writeShort(f,n): if writeDebug: print '---------------------------------------' print 'WRITE SHORT' print n print '---------------------------------------' f.write(struct.pack("H",n)) def readLong(f): """Read unsigned 4 byte value from a file f.""" (retVal,) = struct.unpack("L", f.read(4)) return retVal def writeLong(f,n): if writeDebug: print '---------------------------------------' print 'WRITE LONG' print n print '---------------------------------------' f.write(struct.pack("L",n)) def readFloat(f): """Read float (4 byte?) from a file f.""" (retVal,) = struct.unpack("f", f.read(4)) return retVal def writeFloat(f,n): if writeDebug: print '---------------------------------------' print 'WRITE FLOAT' print n print '---------------------------------------' f.write(struct.pack("f",n)) def readRepeatEvent(f): """Read RepeatEvent, a hacky palm data structure must be read programatically due to randomness of the data structure """ event = {}; event['dateExceptionCount'] = readShort(f) dateExceptions = []; for i in range(event['dateExceptionCount']): dateExceptions.append(readLong(f)) if len(dateExceptions) > 0: event['dateExceptions'] = dateExceptions event['repeatEventFlag']= readShort(f) if event['repeatEventFlag'] == 0: return event if event['repeatEventFlag'] == 0xFFFF: classRecord = {} classRecord['constant'] = readShort(f) classRecord['nameLength'] = readShort(f) classRecord['name'] = f.read(classRecord['nameLength']) event['classRecord'] = classRecord event['brand'] = readLong(f) event['interval'] = readLong(f) event['endDate'] = readLong(f) event['firstDayOfWeek'] = readLong(f) if event['brand'] in (1L,2L,3L): event['brandDayIndex'] = readLong(f) if event['brand'] == 2L: event['brandDaysMask'] = f.read(1) if event['brand'] == 3L: event['brandWeekIndex'] = readLong(f) if event['brand'] in (4L, 5L): event['brandDayNumber'] = readLong(f) if event['brand'] == 5L: event['brandMonthIndex'] = readLong(f) return event def writeRepeatEvent(f,repeatEventDetails): """Write RepeatEvent. """ if writeDebug: print '---------------------------------------' print 'WRITING REPEAT EVENT' import pprint pprint.pprint(repeatEventDetails) print '---------------------------------------' writeShort(f,repeatEventDetails['dateExceptionCount']) if repeatEventDetails['dateExceptionCount'] != 0: for dateException in repeatEventDetails['dateExceptions']: writeLong(f,dateException) writeShort(f,repeatEventDetails['repeatEventFlag']) if repeatEventDetails['repeatEventFlag'] == 0: return if repeatEventDetails['repeatEventFlag'] == 0xFFFF: classRecord = repeatEventDetails['classRecord'] writeShort(f,classRecord['constant']) writeShort(f,classRecord['nameLength']) f.write(classRecord['name']) writeLong(f,repeatEventDetails['brand']) writeLong(f,repeatEventDetails['interval']) writeLong(f,repeatEventDetails['endDate']) writeLong(f,repeatEventDetails['firstDayOfWeek']) if repeatEventDetails['brand'] in (1L,2L,3L): writeLong(f,repeatEventDetails['brandDayIndex']) if repeatEventDetails['brand'] == 2L: f.write(repeatEventDetails['brandDaysMask']) if repeatEventDetails['brand'] == 3L: writeLong(f,repeatEventDetails['brandWeekIndex']) if repeatEventDetails['brand'] in (4L, 5L): writeLong(f,repeatEventDetails['brandDayNumber']) if repeatEventDetails['brand'] == 5L: writeLong(f,repeatEventDetails['brandMonthIndex']) return def readField(f, fieldType): """Read palm record from a file f. fieldType -- integer specifying the palm field type """ if readDebug: print '-----------------------------------' print 'READING FIELD' print 'file:',f print 'fieldType:',fieldType retVal = None if fieldType ==0: # none retVal = None elif fieldType == 1: # integer retVal = readLong(f) elif fieldType == 2: # float retVal = readFloat(f) elif fieldType == 3: # date retVal = readLong(f) elif fieldType == 4: # alpha raise NotImplementedError elif fieldType == 5: # cstring readLong(f) # padding retVal = readCString(f) elif fieldType == 6: # boolean retVal = readLong(f) != 0 elif fieldType == 7: # bit flag retVal = readLong(f) elif fieldType == 8: # repeat event, bad hack, bad retVal = readRepeatEvent(f) else: raise ValueError if readDebug: import pprint pprint.pprint(retVal) return retVal def writeField(f, fieldType, s): """Write palm field to a file. fieldType -- integer specifying the palm field type """ if writeDebug: print '---------------------------------------' print 'WRITING FIELD' print 'fieldType:', fieldType print 'value:',s print '---------------------------------------' if fieldType ==0: # none pass elif fieldType == 1: # integer writeLong(f,s) elif fieldType == 2: # float writeFloat(f,s) elif fieldType == 3: # date writeLong(f,s) elif fieldType == 4: # alpha raise NotImplementedError elif fieldType == 5: # cstring writeLong(f,0) # padding writeCString(f,s) elif fieldType == 6: # boolean if s: s = 1 else: s = 0 writeLong(f,s) elif fieldType == 7: # bit flag writeLong(f,s) elif fieldType == 8: # repeat event, bad hack, bad writeRepeatEvent(f,s) else: raise ValueError def readFRecords(f, fileSoFar, labels): """reads a list of frecords from file f returns -- a list of records fileSoFar -- dictionary of data read so far, used to get the number of records to read labels -- a list of labels for the fields """ if readDebug: print '---------------------------------' print 'READING FRECORDS' print 'fileSoFar' import pprint pprint.pprint(fileSoFar) fieldsPerRecord = fileSoFar['fieldCount'] # make sure that declared if fieldsPerRecord != len(labels): raise ValueError numberOfRecords = fileSoFar['numEntries'] / fieldsPerRecord; # print "reading", str(numberOfRecords), "records" entries = [] for i in range(numberOfRecords): # print "reading record", str(i) newEntry = {} for j in labels: fieldType = readLong(f) newEntry[j] = readField(f, fieldType) entries.append(newEntry) # print "done with", str(numberOfRecords), "records" return entries def writeFRecords(f, fieldEntryList, labels, list): """writes a list of frecords to file f fieldEntryList -- a list describing the order of field types in each record list -- a list of records to write labels -- a list of labels for the fields """ if writeDebug: print '---------------------------------------' print 'WRITING FRECORDS' import pprint print '\nfieldEntryList' pprint.pprint(fieldEntryList) print '\nlabels' pprint.pprint(labels) if len(fieldEntryList) != len(labels): raise ValueError for item in list: if writeDebug: print '\nitem to write' import pprint pprint.pprint(item) for i in range(len(labels)): fieldType = fieldEntryList[i]['fieldEntryType'] if writeDebug: print 'in frecords, attempting to write field:' print 'f:',f print 'fieldType:',fieldType print 'label:',labels[i] import pprint pprint.pprint(item[labels[i]]) writeLong(f,fieldType) writeField(f,fieldType,item[labels[i]]) def readRecords(f, fileFormat, howMany=1): """reads a list of objects from a file f fileFormat -- HEADERDEF of what format looks like howMany -- how many records to read returns a list of howMany dictionaries: [ {d1}, .... {dN}] """ retVal = [] for i in range(howMany): if readDebug: print '------------------------------' print "entering", str(fileFormat[0]); print 'READING RECORDS' print 'retVal:' import pprint pprint.pprint(retVal) entry = {} for fieldDef in fileFormat: if fieldDef[1] == "long": entry[fieldDef[0]] = readLong(f) elif fieldDef[1] == "short": entry[fieldDef[0]] = readShort(f) elif fieldDef[1] == "cstring": entry[fieldDef[0]] = readCString(f) elif fieldDef[1] == "record": entry[fieldDef[0]] = readRecords(f, eval(fieldDef[2]), entry[fieldDef[3]]) elif fieldDef[1] == "frecord": entry[fieldDef[0]] = readFRecords(f, entry, eval(fieldDef[2])) else: raise AssertionError retVal.append(entry); # print "returning", str(fileFormat[0]) return retVal def writeRecords(f, fileFormat, list): """reads a list of objects from a file f fileFormat -- HEADERDEF of what format looks like list -- a list of dictionaries to write as palm records """ if writeDebug: print '\n----------------------------\nWRITING RECORDS' import pprint print 'fileFormat:' pprint.pprint(fileFormat) print '\nlist:' pprint.pprint(list) #for i in range(len(list)): for item in list: for fieldDef in fileFormat: if writeDebug: print '\nfieldDef:' print fieldDef if fieldDef[1] == "long": writeLong(f, item[fieldDef[0]]) elif fieldDef[1] == "short": writeShort(f, item[fieldDef[0]]) elif fieldDef[1] == "cstring": writeCString(f, item[fieldDef[0]]) elif fieldDef[1] == "record": if writeDebug: print 'WILL WRITE RECORDS' writeRecords(f=f, fileFormat=eval(fieldDef[2]),list=item[fieldDef[0]]) if writeDebug: print 'BACK FROM WRITING RECORDS' elif fieldDef[1] == "frecord": if writeDebug: print 'WILL WRITE FRECORDS' writeFRecords(f=f, fieldEntryList=item['fieldEntry'],labels=eval(fieldDef[2]), list=item[fieldDef[0]]) if writeDebug: print 'BACK FROM WRITING FRECORDS' else: raise AssertionError ###################### # MAIN FUNCTIONS ###################### def readPalmFile(fileName): """ Read in a Palm fileName with a specified format The type of the file is determined automatically by reading the first four bytes fileFormat -- different files have different formats (address book, calendar...) [abHeaderDef | calHeaderDef] """ retVal = None try: PalmFile = open(fileName, "rb") except IOError: print "Palm file", fileName, "cannot be opene\n\n" raise IOError try: sig = PalmFile.read(4) PalmFile.seek(0) if sig == "\x00\x01BA": # address book fileFormat = addressHeaderDef elif sig == "\x00\x01BD": # datebook (calendar) fileFormat = calendarHeaderDef else: print "Unknown file format ", sig raise ValueError retVal = readRecords(PalmFile, fileFormat, 1) except IOError: print "Unexpected error while reading Palm file" raise IOError if PalmFile : PalmFile.close() return retVal def writePalmFile(fileName, fileData): '''Writes a palm desktop file ''' if writeDebug: print '---------------------------------------' print 'ATTEMPTING TO WRITE PALM FILE\nFILE:',fileName print 'fileType:',fileType print 'fileData:\n' import pprint pprint.pprint(fileData) """ Write a Palm fileName with a specified format The type of the file is determined automatically by reading the first four bytes fileFormat -- different files have different formats (address book, calendar...) [abHeaderDef | calHeaderDef] """ fileType = fileData[0]['versionTag'] sig = struct.pack("L",fileType) if not (sig == '\x00\x01BA' or sig == '\x00\x01BD'): print "Unknown file format ", sig raise ValueError try: PalmFile = open(fileName, "wb") except IOError: print "Palm file", fileName, "cannot be opened\n\n" raise IOError try: #PalmFile.write(sig) if sig == "\x00\x01BA": # address book fileFormat = addressHeaderDef elif sig == "\x00\x01BD": # datebook (calendar) fileFormat = calendarHeaderDef else: print "Unknown file format ", sig raise ValueError writeRecords(PalmFile, fileFormat, fileData) except IOError: print "Unexpected error while writing Palm file" raise IOError if PalmFile : PalmFile.close() def printAllNames(adBook): """print all names in the address book demo of walking the address book structure """ addressDict = adBook[0] addresses = addressDict["addresses"] for address in addresses: print address["firstName"],address["lastName"] def printAWeekWorthOfCalendar(calendar,traceRepeats=0): """demo of walking the calendar data structure prints all events a week from now """ import time print "Your schedule next week:" events = getUpcomingEvents(calendar,7,traceRepeats) for event in events: if event['untimed']: print time.strftime("%x ***", time.localtime(event['startTime'])), event['text'] else : print time.strftime("%c-", time.localtime(event['startTime'])), time.strftime("%X", time.localtime(event['endTime'])),event['text'] if event['note']: print event['note'] def getEvents(calendar): return calendar[0]['datebookList'] def getUpcomingEvents(calendar, daysAhead, traceRepeats=0): #returns a list of event dictionaries from now until daysAhead days from now import time calendarDict = calendar[0] dateList = calendarDict['datebookList'] #we don't want to change the original calendar so we create a copy startTime = time.time(); endTime = startTime + (daysAhead * 60 * 60 * 24) # converts daysAhead to seconds retVal = [] #print 'Checking for events between',time.localtime(startTime),'and',time.localtime(endTime) for event in dateList: newEvent = event.copy() if newEvent['startTime'] > startTime and newEvent['startTime'] < endTime: #print 'Adding [', newEvent['text'], '] [', time.localtime(newEvent['startTime']), ']' #pprint.pprint(event) #print '\n\n' retVal.append(newEvent.copy()) if traceRepeats and newEvent['repeatEvent']['repeatEventFlag'] and newEvent['repeatEvent']['endDate'] > startTime: newEvent = getNextRepeatedEvent(newEvent) #print 'Looking [',newEvent['text'],'] [',time.localtime(newEvent['startTime']),']' while newEvent['startTime'] < startTime: newEvent = getNextRepeatedEvent(newEvent) #print 'Looking [',newEvent['text'],'] [',time.localtime(newEvent['startTime']),']' while newEvent['startTime'] <= endTime and newEvent['startTime'] <= newEvent['repeatEvent']['endDate']: #print 'Adding [', newEvent['text'], '] [', time.localtime(newEvent['startTime']), ']' retVal.append(newEvent.copy()) newEvent = getNextRepeatedEvent(newEvent) return retVal def getNextRepeatedEvent(event): import time '''A sample from datebookList: 'repeatEvent': {'brand': 5L, Daily, Weekly, Monthly Date, Monthly Day, YEARLY 'brandDayNumber': 4L, 4th 'brandMonthIndex': 7L, August 'dateExceptionCount': 0, 'endDate': 1028437200L, 'firstDayOfWeek': 0L, 'interval': 1L, 'repeatEventFlag': 65535}, ''' repeatDetails = event['repeatEvent'] repeatStartDST = time.localtime(event['startTime'])[8] if repeatDetails['brand'] == 1: #daily repeat event['startTime'] = event['startTime'] + 60*60*24*repeatDetails['interval'] elif repeatDetails['brand'] == 2: # repeat weekly on specific days targetDaysMask = ord(repeatDetails['brandDaysMask']) #convert character read from file to ascii code #print 'targetDaysMask:',targetDaysMask repeatStartLocal=time.localtime(event['startTime']) if repeatStartLocal[6] == 5: # event's current start time is a Saturday '''add one day to get to Sunday, then add whole weeks according to the specified interval minus one, because we just went from Saturday to Sunday with the first addition. ''' event['startTime'] = event['startTime'] + (60*60*24) + (60*60*24*7) * (repeatDetails['interval'] - 1) repeatStartLocal=time.localtime(event['startTime']) else: event['startTime'] = event['startTime'] + (60*60*24) repeatStartLocal=time.localtime(event['startTime']) '''Python stores Wdays in element 6 of a time_struct, but stores them with Monday as 0 and Sun as 6. This line converts from the python time_struct to a WDay value with Sunday as 0 ''' rsWDay = (repeatStartLocal[6] + 1) % 7 #print time.localtime(event['startTime']),'\n\trsWDay:',rsWDay,'\n\t',2**rsWDay,'\n\t',not ((2**rsWDay) & targetDaysMask) while not ((2**rsWDay) & targetDaysMask): # targetDaysMask is sum of the following: 1 = Sunday, 2 = Monday, 4 = Tues, . . . , 64 = Saturday event['startTime'] = event['startTime'] + (60*60*24) repeatStartLocal=time.localtime(event['startTime']) rsWDay = (repeatStartLocal[6] + 1) % 7 elif repeatDetails['brand'] == 3: #repeat monthly based on day from calendar import monthrange targetDay = repeatDetails['brandDayIndex'] #returns wday according to Python standards with Monday = 0 targetWeek = repeatDetails['brandWeekIndex'] #returns week with first week = 0, fourth week = 3, and last week = 4 for interval in range(1,repeatDetails['interval'] + 1): if targetWeek == 4: #event repeats on last ?day of the month. #event['startTime'] = event['startTime'] + (60*60*24*7) #add one week to current event newTime = event['startTime'] + (60*60*24*7*4) #add four weeks to current event newLocalTime = time.localtime(newTime) monthBegins, monthLength = monthrange(newLocalTime[0],newLocalTime[1]) if (monthLength - newLocalTime[3]) >= 7: newTime = newTime + (60*60*24*7) event['startTime'] = newTime else: #repeats on 1st - 4th ?day of month newTime = event['startTime'] newLocalTime = time.localtime(newTime) oldStartTimeMonth = newLocalTime[1] #advance event until it's the first occurrence of that weekday in the next month while newLocalTime[1] == oldStartTimeMonth: newTime = newTime + (60*60*24*7) #add one week to current event newLocalTime = time.localtime(newTime) event['startTime'] = newTime + (60*60*24*7*(targetWeek)) elif repeatDetails['brand'] == 4: #repeat monthly based on date repeatStartLocal = time.localtime(event['startTime']) event['startTime'] = time.mktime((repeatStartLocal[0],repeatStartLocal[1]+repeatDetails['interval'],repeatStartLocal[2],repeatStartLocal[3],repeatStartLocal[4],repeatStartLocal[5],0,0,0)) elif repeatDetails['brand'] == 5: #repeat yearly based on date repeatStartLocal = time.localtime(event['startTime']) event['startTime'] = time.mktime((repeatStartLocal[0]+repeatDetails['interval'],repeatStartLocal[1],repeatStartLocal[2],repeatStartLocal[3],repeatStartLocal[4],repeatStartLocal[5],0,0,0)) newEventDST = time.localtime(event['startTime'])[8] correctDST = repeatStartDST - newEventDST event['startTime'] = event['startTime'] + (60*60*correctDST) return event def getAppointment(event): return event def createAppointment(event): return event def changeAppointment(event): return if __name__ == "__main__": import sys import getopt try: options, args = getopt.getopt(sys.argv[1:], 'v') fileToRead = args[0] verbose = len(options) > 0 and options[0][0] == "-v" except: print "Usage: python PalmFile.py [-v] <fileName>" print "-v for verbose, print all the data in the file" print "Prints out some sample data from a palm file" sys.exit(-1) palmData = readPalmFile(fileToRead) if verbose: import pprint pprint.pprint(palmData) if palmData[0]["versionTag"] == 1094844672L: # addresses printAllNames(palmData) elif palmData[0]["versionTag"] == 1145176320L: # datebook printAWeekWorthOfCalendar(palmData) ---------- #IMPORTS import palmFile #import mozCal import time # FUNCTION DECLARATIONS def iCalTimeFmt (timeval): _tmpVar = time.localtime(timeval) return str(_tmpVar[0])+str(_tmpVar[1]).zfill(2)+str(_tmpVar[2]).zfill(2)+'T'+str(_tmpVar[3]).zfill(2)+str(_tmpVar[4]).zfill(2)+str(_tmpVar[5]).zfill(2) def getWeekDayString (targetDaysMask): weekdayList = ['SU','MO','TU','WE','TH','FR','SA'] weekday = '' for i in range(7): if (2**i & targetDaysMask): weekday = weekday+weekdayList[i]+',' return weekday #BEGIN MAIN SCRIPT import sys for arg in sys.argv[1:]: userDir = 'c:\\program files\\palm\\'+arg palmDataFile = 'c:\\program files\\palm\\'+arg+'\\datebook\\datebook.dat' icsOutputPath = 'fromPalm.ics' fileStruct = palmFile.readPalmFile(palmDataFile) #fout = file('C:\\Documents and Settings\\Jeff\\Application Data\\Mozilla\\Profiles\\default\\mak3vke6.slt\\Calendar\\CalendarDataFile.ics','w') fout = file(icsOutputPath,'w') fout.write('BEGIN:VCALENDAR\nVERSION\n :2.0\nPRODID\n :-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN\n') eventsList = palmFile.getEvents(fileStruct) for event in eventsList: #set up the vevent information to write to file veventString = 'BEGIN:VEVENT\nSUMMARY\n :'+event['text'].replace('\n','\n ').replace('\r','') +'\nDESCRIPTION\n :'+event['note'].replace('\n','\n ').replace('\r','')+'\n' if event['private']: icalclass = 'private' else: icalclass = 'public' veventString = veventString + 'CLASS\n :'+icalclass+'\n' veventString = veventString + 'DTSTART\n :'+iCalTimeFmt(event['startTime'])+'\n' veventString = veventString + 'DTEND\n :'+iCalTimeFmt(event['endTime'])+'\n' recurString = '' if event['repeatEvent']['repeatEventFlag']: recurString = 'RRULE\n :' rDetails = event['repeatEvent'] recurString = recurString + 'INTERVAL=' + str(int(rDetails['interval'])) + ';' if rDetails['endDate']: recurString = recurString + 'UNTIL=' + iCalTimeFmt(rDetails['endDate']) + ';' recurString = recurString + 'WKST=SU;' #specifies Sunday as the first day in a Palm Desktop work week if rDetails['brand'] == 1: #repeat daily recurString = recurString + 'FREQ=DAILY;' elif rDetails['brand'] == 2: #repeat on specific days each week wdayString = getWeekDayString(ord(rDetails['brandDaysMask'])) recurString = recurString + 'FREQ=WEEKLY;BYDAY='+wdayString+';' elif rDetails['brand'] == 3: #repeat monthly based on day targetDay = rDetails['brandDayIndex'] #returns wday according to Python standards with Monday = 0 targetDay = (targetDay + 1) % 7 #convert to Sunday being indexed as 0 weekdayList = ['SU','MO','TU','WE','TH','FR','SA'] targetDay = weekdayList[targetDay] #print 'repeat monthly--targetday: ',targetDay targetWeek = rDetails['brandWeekIndex'] #returns week with first week = 0, fourth week = 3, and last week = 4 targetWeek = targetWeek + 1 if targetWeek == 5: targetWeek = -1 recurString = recurString + 'FREQ=MONTHLY;BYDAY=' + str(targetWeek) + targetDay + ';' elif rDetails['brand'] == 4: #repeat monthly based on date recurString = recurString + 'FREQ=MONTHLY;' elif rDetails['brand'] == 5: #repeat yearly recurString = recurString + 'FREQ=YEARLY;' recurString = recurString[:-1] #remove trailing semicolon veventString = veventString + recurString + '\nEND:VEVENT\n' #print veventString fout.write(veventString) fout.write('END:VCALENDAR\n') fout.close() [Non-text portions of this message have been removed] Remember to visit Polls, Files and Bookmarks sections at http://groups.yahoo.com/group/progect. You are welcome to vote, upload your files and submit bookmarks. Yahoo! Groups Links <*> To visit your group on the web, go to: http://groups.yahoo.com/group/progect/ <*> Your email settings: Individual Email | Traditional <*> To change settings online go to: http://groups.yahoo.com/group/progect/join (Yahoo! ID required) <*> To change settings via email: mailto:[email protected] mailto:[email protected] <*> To unsubscribe from this group, send an email to: [email protected] <*> Your use of Yahoo! Groups is subject to: http://docs.yahoo.com/info/terms/