4 python dangerSync.py --dev --user=connolly_sim --passwd=xyz --get=contact
6 python dangerSync.py --prod --user=XYZ --passwd=PDQ --get=contact
9 $Id: dangerSync.py,v 1.20 2007/04/17 22:24:02 connolly Exp $
15 import types, operator
16 from xml.sax import saxutils
17 from xmlrpclib import Server
18 from warnings import warn
20 # http://www.python.org/doc/current/lib/module-getopt.html
25 opts, args = getopt.getopt(sys.argv[1:],
26 "hdru:p:s:g:t:z:x:n:t:",
29 "user=", "passwd=", "session=", "pgp",
30 "get=", "post=", "zap=",
33 except getopt.GetoptError:
46 if o in ("-h", "--help"):
49 elif o in ("-t", "--test"):
52 elif o == '-u' or o == '--user':
53 userName = a # e.g. connolly_sim or danc
54 elif o == "-p" or o == '--passwd':
56 print peer.login(userName, passwd)
60 # TypeError: cannot marshal <type 'dbus.String'> objects
61 print peer.login(userName, str(credstore.decrypt()))
62 elif o == "-d" or o == '--dev':
63 pass # this is the default
66 elif o == "-r" or o == '--prod':
68 elif o == "-s" or o == '--session':
69 print "resuming...", a
71 elif o == '-g' or o == '--get':
73 elif o == '-t' or o == '--post':
75 elif o == '-z' or o == '--zap':
77 elif o == '-x' or o == '--xml':
79 elif o == '-n' or o == '--newer':
84 db = shelve.open('event', flag="r")
88 dump_kinds(db_dump.split(","))
91 for name in db_zap.split(","):
95 for name in db_get.split(","):
96 db = shelve.open(name, flag='c')
97 download(db, peer, name)
100 for name in db_post.split(","):
101 db = shelve.open(name, flag="r")
102 upload(db, peer, name)
110 print >>sys.stderr, "@@zap kind: ", kind
112 ids = peer.allItemIds(kind)
113 print >>sys.stderr, "@@deleting: ", ids
114 peer.delete(kind, ids)
117 def download(db, peer, kind):
118 print >>sys.stderr, "@@download kind: ", kind
120 anchor = db.get('anchor', None)
123 ids = peer.deletedItemIds(kind)
128 warn("what happened to %d?" % id)
129 ids = peer.changedItemIds(kind)
131 ids = peer.allItemIds(kind)
133 for record in peer.get(kind, ids):
135 print >>sys.stderr, "@@getting: ", k, record.get('title', '')
137 db['anchor'] = peer.resetAnchor()
140 def upload(db, peer, kind):
141 print >>sys.stderr, "@@upload kind: ", kind
145 if k == 'anchor': continue
147 print >>sys.stderr, "@@queing for upload: ", k
148 records.append(db[k])
151 while i < len(records):
152 recs = records[i:i+30]
155 ids_old.append(r.get('id', None))
156 print >>sys.stderr, "@@uploading %d records... " % len(recs)
157 ids_new = peer.post(kind, recs)
158 print zip(ids_old, ids_new)
163 def dump_kinds(kinds):
166 db = shelve.open(name, flag="r")
171 dump_anchor(name, db[k])
173 dumpItem(name, db[k], 0)
177 print "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://dev.w3.org/2001/palmagent/danger#'>"
179 def dump_anchor(kind, anchor):
180 print "<rdf:Description rdf:about='#%s' rdf:value='%s'/>" % (kind, anchor)
182 def dump_newer(db, newer):
188 dump_anchor('event', db[k])
191 if item.get('repeat_type', 0) == 0:
192 if item.get('end_date', '9999') < newer: continue
193 if item.get('start_date', '9999') < newer: continue
195 if (item.get('repeat_end_date', '9999') or '9999') < newer: continue
197 dumpItem('event', db[k], 0)
204 def dumpItem(name, item, indent):
205 spaces = ' ' * indent
208 #hmm... it seems that empty strings
209 #mean "value not recorded" rather than
210 # "value is an empty string".
212 #print "%s<%s />" % (spaces, name)
214 elif operator.isNumberType(item):
215 print "%s<%s rdf:datatype='http://www.w3.org/2001/XMLSchema#integer'>%s</%s>" % (spaces, name, item, name)
216 elif isinstance(item, types.StringTypes):
217 print "%s<%s>%s</%s>" % (spaces, name, saxutils.escape(item).encode('UTF-8'), name)
218 elif operator.isSequenceType(item):
220 print "%s<%s rdf:parseType='Collection'/>" % (spaces, name)
222 print "%s<%s rdf:parseType='Collection'>" % (spaces, name)
224 dumpItem("rdf:Description", j, indent + 2)
225 print "%s</%s>" % (spaces, name)
227 elif operator.isMappingType(item):
228 id = item.get('id', None)
230 print "%s<%s>" % (spaces, name)
232 print "%s<%s rdf:ID='_%s'>" % (spaces, name, id)
233 for name2, value2 in item.iteritems():
234 dumpItem(name2, value2, indent + 2)
235 print "%s</%s>" % (spaces, name)
237 print "%s {unknown item %s }" % (spaces, item)
241 """punctuate item as per ISO8601 and W3C XML Schema datatypes
243 >>> asDate("20041225")
245 >>> asDate("20041202T170000Z")
246 '2004-12-02T17:00:00Z'
249 return "%s-%s-%s:%s:%s" % (item[:4], item[4:6], item[6:11],
250 item[11:13] ,item[13:])
252 return "%s-%s-%s" % (item[:4], item[4:6], item[6:8])
254 raise ValueError, item
262 class PimSyncServer (xmlrpclib.ServerProxy):
263 def __init__ (self, url):
265 xmlrpclib.ServerProxy.__init__ (self, url, verbose=DEBUG)
269 """ A connection to Danger PIMAPI user data calls XMLRPC service
270 http://developer.danger.com/wiki/space/user+data+calls
272 maintains session state, but not anchor state
276 """ default to connect to the developer service
278 self._addr = "http://sync.developer.danger.com:5014/pimapi"
279 self._carrier = "danger"
282 def useProduction(self):
283 self._addr = "http://pimapi.prod1.dngr.net/pimapi"
284 self._carrier = 'voicestream'
286 def setClient(self, uastring, version='1.0'):
287 self._client = {'Client': uastring,
290 def login(self, username, password):
291 self._server = PimSyncServer(self._addr)
292 # pprint(self._server.get_carriers(), sys.stderr)
293 self._session = self._server.create_session (self._carrier, username,
298 def resume(self, session):
299 self._server = PimSyncServer(self._addr)
300 self._session = session
302 # keep sessions open just in case
304 # self._server.end_session(self._session)
306 def allItemIds(self, type):
307 return self._server.get_all_item_ids(self._session, type)
309 def post(self, type, vector):
310 return self._server.new_item (self._session, type, vector)
312 def put(self, string, vector):
313 return self._server.update_item (self._session, string, vector)
315 def get(self, string, vector):
316 return self._server.get_item (self._session, string, vector)
318 def delete(self, string, vector):
319 return self._server.delete_item (self._session, string, vector)
322 class Anchored(DangerService):
323 def resetAnchor(self):
324 self.anchor = self._server.get_sync_anchor (self._session)
327 def deletedItemIds(self, type):
328 return self._server.get_deleted_item_ids(self._session, type,
331 def changedItemIds(self, type):
332 return self._server.get_changed_item_ids (self._session,
338 if __name__ == '__main__':
342 # $Log: dangerSync.py,v $
343 # Revision 1.19 2007/01/01 01:06:21 connolly
344 # show title of record in download()
346 # Revision 1.18 2006/07/14 07:22:16 connolly
347 # include client info in login for write operations
349 # Revision 1.17 2006/06/30 01:25:37 connolly
350 # use rdf:Description rather than foo_item in lists
352 # Revision 1.16 2005/02/17 22:50:45 connolly
353 # when dumping rdf/xml, sort items by key
355 # Revision 1.15 2004/12/31 18:38:52 connolly
356 # emit flattened lists as well as collections
358 # Revision 1.14 2004/12/31 17:13:02 connolly
359 # date punctuation is now taken care of in dangerSync.py
360 # dangerSync.py now has some doctest stuff, invoked using --test
362 # Revision 1.13 2004/12/31 16:40:49 connolly
363 # open read-only when we can so that Makefiles work
365 # Revision 1.12 2004/11/13 16:46:18 connolly
366 # prepend _ to make numeric IDs into XML names
368 # Revision 1.11 2004/10/23 18:41:31 connolly
369 # oops; fixed previously-untested empty string handling
371 # Revision 1.10 2004/10/23 18:21:19 connolly
372 # skipping empty strings
374 # Revision 1.9 2004/04/19 16:27:48 connolly
375 # update namespace name
377 # Revision 1.8 2004/02/23 07:40:01 connolly
378 # - handle KeyError in delete
379 # - fixed --newer for repeating events
381 # Revision 1.7 2004/02/16 00:04:00 connolly
382 # added --newer to get events that affect the future
384 # Revision 1.6 2004/02/15 20:31:15 connolly
385 # on post, print correspondence between old ids and new
387 # Revision 1.5 2004/02/15 08:04:42 connolly
388 # - use item id as rdf:ID
390 # Revision 1.4 2004/02/15 07:52:51 connolly
391 # - moved zap before post
393 # - resume session (not successfully tested)
395 # Revision 1.3 2004/02/13 14:10:18 connolly
396 # zap, upload seem to be working...
397 # well enough to contact,task,note,event data
398 # from my one phone to the other
400 # Revision 1.2 2004/02/12 22:18:45 connolly
401 # downloading all 4 kinds from production server worked.
402 # upload code untested
404 # Revision 1.1 2004/02/12 22:02:38 connolly
405 # anchored downloading works