From 1d45ad9e7dc5d4c13278271104778ba64518f09c Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sat, 18 May 2013 21:23:21 -0700 Subject: [PATCH] Adding support for ubiquity (file synchronization/storage) API. Borrowed many implementation details from @matin's lovely icloud implementation; thanks @matin! --- README.md | 37 ++++++++++++ pyicloud/base.py | 13 ++++- pyicloud/services/__init__.py | 1 + pyicloud/services/ubiquity.py | 107 ++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 pyicloud/services/ubiquity.py diff --git a/README.md b/README.md index 4244446..1850d74 100644 --- a/README.md +++ b/README.md @@ -108,3 +108,40 @@ from_dt = datetime(2012, 1, 1) to_dt = datetime(2012, 1, 31) api.calendar.events(from_dt, to_dt) ``` + +### File Storage (Ubiquity) + +You can access documents stored in your iCloud account by using the `files` property's `dir` method: + +```python +>>> api.files.dir() +[u'.do-not-delete', + u'.localized', + u'com~apple~Notes', + u'com~apple~Preview', + u'com~apple~mail', + u'com~apple~shoebox', + u'com~apple~system~spotlight' +] +``` + +And, you can access children and their children's children using the filename as an index: + +```python +>>> api.files['com~apple~Notes'] + +>>> api.files['com~apple~Notes'].type +u'folder' +>>> api.files['com~apple~Notes'].dir() +[u'Documents'] +>>> api.files['com~apple~Notes']['Documents'].dir() +[u'Some Document'] +>>> api.files['com~apple~Notes']['Documents']['Some Document'].name +u'Some Document' +>>> api.files['com~apple~Notes']['Documents']['Some Document'].modified +datetime.datetime(2012, 9, 13, 2, 26, 17) +>>> api.files['com~apple~Notes']['Documents']['Some Document'].size +1308134 +>>> api.files['com~apple~Notes']['Documents']['Some Document'].type +u'file' +``` diff --git a/pyicloud/base.py b/pyicloud/base.py index 3328523..c7becd5 100644 --- a/pyicloud/base.py +++ b/pyicloud/base.py @@ -5,7 +5,11 @@ import json import requests from exceptions import PyiCloudFailedLoginException -from services import FindMyiPhoneServiceManager, CalendarService +from services import ( + FindMyiPhoneServiceManager, + CalendarService, + UbiquityService +) class PyiCloudService(object): @@ -101,6 +105,13 @@ class PyiCloudService(object): def iphone(self): return self.devices[0] + @property + def files(self): + if not hasattr(self, '_files'): + service_root = self.webservices['ubiquity']['url'] + self._files = UbiquityService(service_root, self.session, self.params) + return self._files + @property def calendar(self): service_root = self.webservices['calendar']['url'] diff --git a/pyicloud/services/__init__.py b/pyicloud/services/__init__.py index 004018c..fdafa36 100644 --- a/pyicloud/services/__init__.py +++ b/pyicloud/services/__init__.py @@ -1,2 +1,3 @@ from calendar import CalendarService from findmyiphone import FindMyiPhoneServiceManager +from ubiquity import UbiquityService diff --git a/pyicloud/services/ubiquity.py b/pyicloud/services/ubiquity.py new file mode 100644 index 0000000..5fce273 --- /dev/null +++ b/pyicloud/services/ubiquity.py @@ -0,0 +1,107 @@ +from datetime import datetime + + +class UbiquityService(object): + """ The 'Ubiquity' iCloud service.""" + + def __init__(self, service_root, session, params): + self.session = session + self.params = params + self._root = None + + self._service_root = service_root + self._node_url = '/ws/%s/%s/%s' + + host = self._service_root.split('//')[1].split(':')[0] + self.session.headers.update({'host': host}) + + def get_node_url(self, id, variant='item'): + return self._service_root + self._node_url % ( + self.params['dsid'], + variant, + id + ) + + def get_node(self, id): + request = self.session.get(self.get_node_url(id)) + return UbiquityNode(self, request.json()) + + def get_children(self, id): + request = self.session.get( + self.get_node_url(id, 'parent') + ) + items = request.json()['item_list'] + return [UbiquityNode(self, item) for item in items] + + @property + def root(self): + if not self._root: + self._root = self.get_node(0) + return self._root + + def __getattr__(self, attr): + return getattr(self.root, attr) + + def __getitem__(self, key): + return self.root[key] + + +class UbiquityNode(object): + def __init__(self, conn, data): + self.data = data + self.connection = conn + + @property + def item_id(self): + return self.data.get('item_id') + + @property + def name(self): + return self.data.get('name') + + @property + def type(self): + return self.data.get('type') + + def get_children(self): + if not hasattr(self, '_children'): + self._children = self.connection.get_children(self.item_id) + return self._children + + @property + def size(self): + try: + return int(self.data.get('size')) + except ValueError: + return None + + @property + def modified(self): + return datetime.strptime( + self.data.get('modified'), + '%Y-%m-%dT%H:%M:%SZ' + ) + + def dir(self): + return [child.name for child in self.get_children()] + + def get(self, name): + return [child for child in self.get_children() if child.name == name][0] + + def __getitem__(self, key): + try: + return self.get(key) + except IndexError: + raise KeyError('No child named %s exists' % key) + + def __unicode__(self): + return self.name + + def __str__(self): + return self.name.encode('unicode-escape') + + def __repr__(self): + return "<%s: u'%s'>" % ( + self.type.capitalize(), + str(self) + )