Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

* Added conduit module.

* Added bookmarks and notes stubs.
* Added README.
* Updated bootstrap.py and paver-minilib.zip.
  • Loading branch information...
commit 9ac81572caa8429aed2d0e8459052b0c002c0cf2 1 parent 33a0291
Bryan Forbes authored March 03, 2010
1  .gitignore
@@ -3,4 +3,5 @@
3 3
 bin
4 4
 lib
5 5
 include
  6
+dist
6 7
 imobilesync.egg-info
18  README
... ...
@@ -0,0 +1,18 @@
  1
+About
  2
+=====
  3
+A package to provide synchronization capabilities for iDevices.  Comes with an optional Conduit module.
  4
+
  5
+Requirements
  6
+============
  7
+python-imobiledevice
  8
+python-vobject
  9
+conduit (optional)
  10
+
  11
+Installation
  12
+============
  13
+
  14
+The easy way:
  15
+sudo python setup.py install
  16
+
  17
+For developers (virtualenv):
  18
+python bootstrap.py
1  bootstrap.py
@@ -976,6 +976,7 @@ def after_install(options, home_dir):
976 976
     else:
977 977
         bin_dir = join(home_dir, 'bin')
978 978
     subprocess.call([join(bin_dir, 'easy_install'), 'paver==1.0.1'])
  979
+    subprocess.call([join(bin_dir, 'easy_install'), 'ipython'])
979 980
     subprocess.call([join(bin_dir, 'paver'),'develop'])
980 981
 
981 982
 ##file site.py
207  conduit/iDeviceModule.py
... ...
@@ -0,0 +1,207 @@
  1
+# -*- coding: utf-8 -*-
  2
+import logging
  3
+log = logging.getLogger("modules.iDevice")
  4
+
  5
+import conduit
  6
+import conduit.dataproviders.HalFactory as HalFactory
  7
+import conduit.dataproviders.DataProvider as DataProvider
  8
+import conduit.dataproviders.DataProviderCategory as DataProviderCategory
  9
+import conduit.datatypes.DataType as DataType
  10
+import conduit.TypeConverter as TypeConverter
  11
+import conduit.utils as Utils
  12
+import conduit.Exceptions as Exceptions
  13
+
  14
+import conduit.datatypes.Contact as Contact
  15
+import conduit.datatypes.Event as Event
  16
+import conduit.datatypes.Event as Bookmark
  17
+
  18
+Utils.dataprovider_add_dir_to_path(__file__)
  19
+
  20
+from imobilesync.sync import SyncFactory, SyncError
  21
+from imobilesync.data import Calendars, Contacts
  22
+
  23
+MODULES = {
  24
+    "iDeviceFactory": { "type": "dataprovider-factory" },
  25
+    "iDeviceConverter": { "type": "converter" }
  26
+}
  27
+
  28
+class iDeviceFactory(HalFactory.HalFactory):
  29
+    """
  30
+    Detects when an iPhone is mounted and creates the appropriate dataproviders
  31
+    """
  32
+    __vendor_id = 0x05ac
  33
+    __supported_devices = [
  34
+        0x1290, # iPhone
  35
+        0x1291, # iPod Touch
  36
+        0x1292, # iPhone 3G
  37
+        0x1293, # iPod Touch 2G
  38
+        0x1294, # iPhone 3GS
  39
+        0x1295, # Unknown
  40
+        0x1296, # Unknown
  41
+        0x1297, # Unknown
  42
+        0x1298, # Unknown
  43
+        0x1299  # iPod Touch 3G 64GB
  44
+    ]
  45
+    # Defined by fd icon spec and gratefully supplied by dobey
  46
+    __model_icons = {
  47
+            "iPhone": "phone-apple-iphone",
  48
+            "iPhone 3G": "phone-apple-iphone",
  49
+            "iPod": "multimedia-player-apple-ipod-touch"
  50
+    }
  51
+    def is_interesting(self, udi, props):
  52
+        # Check for vendor and product id
  53
+        if props.get("usb_device.vendor_id") == self.__vendor_id:
  54
+            if props.get("usb_device.product_id") in self.__supported_devices:
  55
+                return True
  56
+        return False
  57
+
  58
+    def get_category(self, udi, **props):
  59
+        product = props.get("usb_device.product")
  60
+        icon = self.__model_icons[product]
  61
+        return DataProviderCategory.DataProviderCategory(
  62
+                    product,
  63
+                    icon,
  64
+                    udi)
  65
+
  66
+    def get_dataproviders(self, udi, **kwargs):
  67
+        # FIXME: Query lockdownd for supported data classes
  68
+        return [iDeviceContactsTwoWay, iDeviceCalendarsTwoWay]
  69
+
  70
+    def get_args(self, udi, **props):
  71
+        product = props.get("usb_device.product")
  72
+        props["uuid"] = props.get("usb_device.serial")
  73
+        props["model"] = product
  74
+        return (props["uuid"], props["model"], udi)
  75
+
  76
+class iDeviceDataProviderTwoWay(DataProvider.TwoWay):
  77
+    _category_ = conduit.dataproviders.CATEGORY_MISC
  78
+    _module_type_ = 'twoway'
  79
+
  80
+    def __init__(self, *args):
  81
+        self.uuid = str(args[0])
  82
+        self.model = str(args[1])
  83
+        self.hal_udi = str(args[2])
  84
+
  85
+        try:
  86
+            self.sync = SyncFactory.get(self.uuid)
  87
+        except SyncError, e:
  88
+            log.info("Failed to connect to iPhone/iPod Touch")
  89
+        else:
  90
+            log.info("Connected to %s with uuid %s" % (self.model, self.uuid))
  91
+
  92
+        DataProvider.TwoWay.__init__(self)
  93
+
  94
+    def uninitialize(self):
  95
+        pass
  96
+        #self.sync.disconnect()
  97
+
  98
+    def refresh(self):
  99
+        DataProvider.TwoWay.refresh(self)
  100
+        self._refresh()
  101
+
  102
+    def _refresh(self):
  103
+        self.data = self.sync.get_all_records_hashed(self.data_class)
  104
+        self.sync.finish(self.data_class, True)
  105
+
  106
+    def get(self, LUID):
  107
+        DataProvider.TwoWay.get(self, LUID)
  108
+        data = self._get_data(LUID)
  109
+        data.set_UID(LUID)
  110
+        return data
  111
+
  112
+    def get_all(self):
  113
+        DataProvider.TwoWay.get_all(self)
  114
+        return [record.uuid for record in self.data.values()]
  115
+
  116
+class iDeviceContactsTwoWay(iDeviceDataProviderTwoWay):
  117
+    """
  118
+    Contact syncing for iPhone and iPod Touch
  119
+    """
  120
+
  121
+    _name_ = "iPhone Contacts"
  122
+    _description_ = "iPhone and iPod Touch Contact Dataprovider"
  123
+    _in_type_ = "idevice/contact"
  124
+    _out_type_ = "idevice/contact"
  125
+    _icon_ = "contact-new"
  126
+
  127
+    data_class = Contacts
  128
+
  129
+    def get_UID(self):
  130
+        return "iDeviceContactsTwoWay-%s" % self.uuid
  131
+
  132
+    def _get_data(self, LUID):
  133
+        return iDeviceContact(uri=LUID, record=self.data[LUID])
  134
+
  135
+class iDeviceCalendarsTwoWay(iDeviceDataProviderTwoWay):
  136
+    """
  137
+    Contact syncing for iPhone and iPod Touch
  138
+    """
  139
+
  140
+    _name_ = "iPhone Calendar"
  141
+    _description_ = "iPhone and iPod Touch Calendar Dataprovider"
  142
+    _in_type_ = "idevice/event"
  143
+    _out_type_ = "idevice/event"
  144
+    _icon_ = "appointment-new"
  145
+    _configurable_ = True
  146
+
  147
+    data_class = Calendars
  148
+
  149
+    def __init__(self, *args):
  150
+        iDeviceDataProviderTwoWay.__init__(self, *args)
  151
+
  152
+        self.update_configuration(
  153
+            _calendarId=""
  154
+        )
  155
+    def get_UID(self):
  156
+        return "iDeviceCalendarsTwoWay-%s" % self.uuid
  157
+
  158
+    def _get_data(self, LUID):
  159
+        return iDeviceCalendar(uri=LUID, record=self.data[LUID])
  160
+
  161
+    def config_setup(self, config):
  162
+        config.add_section("Calendar Name")
  163
+        config.add_item("Calendar", "combo", config_name = "_calendarId", choices = self._calendar_names())
  164
+
  165
+class iDeviceConverter(TypeConverter.Converter):
  166
+    def __init__(self):
  167
+        self.conversions = {
  168
+            "idevice/contact,contact": self.idevice_contact_to_contact,
  169
+            "idevice/event,event": self.idevice_event_to_event
  170
+        }
  171
+
  172
+    def idevice_contact_to_contact(self, data, **kwargs):
  173
+        c = Contact.Contact(vcard=data.get_vcard())
  174
+        return c
  175
+
  176
+    def idevice_event_to_event(self, data, **kwargs):
  177
+        e = Event.Event()
  178
+        e.iCal = data.get_ical()
  179
+        return e
  180
+
  181
+class iDeviceDataType(DataType.DataType):
  182
+    def __init__(self, uri, **kwargs):
  183
+        DataType.DataType.__init__(self)
  184
+        self.record = kwargs.get("record", None)
  185
+    def __str__(self):
  186
+        return self.get_string()
  187
+
  188
+    def get_hash(self):
  189
+        return hash(self.record)
  190
+
  191
+class iDeviceContact(iDeviceDataType):
  192
+    _name_ = 'idevice/contact'
  193
+
  194
+    def get_vcard(self):
  195
+        return self.record.__vcard__()
  196
+
  197
+    def get_string(self):
  198
+        return 'iDeviceContact: %s' % str(self.record)
  199
+
  200
+class iDeviceEvent(iDeviceDataType):
  201
+    _name_ = 'idevice/event'
  202
+
  203
+    def get_ical(self):
  204
+        return self.record.__ical__()
  205
+
  206
+    def get_string(self):
  207
+        return 'iDeviceCalendar: %s' % str(self.record)
40  imobilesync/__init__.py 100755 → 100644
... ...
@@ -1,37 +1,3 @@
1  
-from optparse import OptionParser
2  
-import sys, pdb
3  
-
4  
-import imobiledevice, datetime
5  
-from imobilesync.sync import Sync
6  
-from imobilesync.data import Contacts, Calendars
7  
-from imobilesync.config import state
8  
-
9  
-def main():
10  
-    parser = OptionParser("Usage: %prog --[sync_type] args")
11  
-    parser.add_option('--contacts', action='append_const', dest='sync_type', const=Contacts)
12  
-    parser.add_option('--calendars', action='append_const', dest='sync_type', const=Calendars)
13  
-    parser.add_option('-i', '--ignore-sync-time', action='store_true', dest='ignore_sync_time', default=False)
14  
-
15  
-    (options, args) = parser.parse_args()
16  
-
17  
-    if options.sync_type is None:
18  
-        print "ERROR: You must specify at least one sync type."
19  
-        parser.print_usage()
20  
-        sys.exit(1)
21  
-
22  
-    s = Sync()
23  
-    s.connect()
24  
-
25  
-    for sync_type in options.sync_type:
26  
-        if not sync_type.state.last_sync_time or options.ignore_sync_time:
27  
-            print s.serialize_all(sync_type)
28  
-        else:
29  
-            print s.serialize_changed(sync_type)
30  
-
31  
-        s.finish(sync_type, not options.ignore_sync_time)
32  
-    s.disconnect()
33  
-
34  
-    state.write()
35  
-
36  
-if __name__ == '__main__':
37  
-    main()
  1
+from imobilesync import data
  2
+from imobilesync import config
  3
+from imobilesync import sync
36  imobilesync/commands.py
... ...
@@ -0,0 +1,36 @@
  1
+import sys
  2
+
  3
+import imobiledevice, datetime
  4
+from imobilesync.options import parser
  5
+from imobilesync.sync import Sync, SyncError
  6
+from imobilesync.data import *
  7
+from imobilesync.config import state
  8
+
  9
+def main():
  10
+    (options, args) = parser.parse_args()
  11
+
  12
+    if options.sync_type is None:
  13
+        print "ERROR: You must specify at least one sync type."
  14
+        parser.print_usage()
  15
+        sys.exit(1)
  16
+
  17
+    try:
  18
+        s = Sync()
  19
+        s.connect()
  20
+
  21
+        try:
  22
+            for sync_type in options.sync_type:
  23
+                if not sync_type.state.last_sync_time or options.ignore_sync_time:
  24
+                    print s.serialize_all(sync_type)
  25
+                else:
  26
+                    print s.serialize_changed(sync_type)
  27
+
  28
+                s.finish(sync_type, not options.ignore_sync_time)
  29
+        except SyncError, e:
  30
+            print 'An error was encountered: %s' % e
  31
+        finally:
  32
+            s.disconnect()
  33
+    except SyncError, e:
  34
+        print 'An error was encountered: %s' % e
  35
+
  36
+    state.write()
18  imobilesync/config.py
@@ -15,6 +15,14 @@
15 15
 class Bunch(dict):
16 16
     """A dictionary that provides attribute-style access."""
17 17
 
  18
+    def add(self, section, defaults=None):
  19
+        if not hasattr(self, section):
  20
+            if defaults is not None:
  21
+                setattr(self, section, Bunch(**defaults))
  22
+            else:
  23
+                setattr(self, section, Bunch())
  24
+        return getattr(self, section)
  25
+
18 26
     def __repr__(self):
19 27
         keys = self.keys()
20 28
         keys.sort()
@@ -86,16 +94,6 @@ def load(cls):
86 94
 class State(Bunch):
87 95
     state_file = config_directory / 'state.pickle'
88 96
 
89  
-    def __init__(self, *args, **kwargs):
90  
-        super(State, self).__init__(*args, **kwargs)
91  
-
92  
-        self.contacts = Bunch(
93  
-            last_sync_time=None
94  
-        )
95  
-        self.calendars = Bunch(
96  
-            last_sync_time=None
97  
-        )
98  
-
99 97
     def write(self):
100 98
         if not config_directory.exists():
101 99
             config_directory.makedirs()
2  imobilesync/data/__init__.py
... ...
@@ -1,3 +1,5 @@
1 1
 from imobilesync.data.base import *
2 2
 from imobilesync.data.contacts import *
3 3
 from imobilesync.data.calendars import *
  4
+from imobilesync.data.bookmarks import *
  5
+from imobilesync.data.notes import *
36  imobilesync/data/base.py
... ...
@@ -1,5 +1,6 @@
1 1
 from imobilesync.plist_util import create_array, EMPTY_PARAM
2 2
 from imobilesync.sync import SyncErrorCancel
  3
+from imobilesync.config import state
3 4
 
4 5
 from plist import PLIST_STRING, PLIST_DICT, PLIST_ARRAY, PLIST_NONE, PLIST_DATA, PLIST_DATE, PLIST_UINT
5 6
 from datetime import timedelta
@@ -17,7 +18,8 @@ class Base(object):
17 18
 
18 19
     related_classes = ()
19 20
 
20  
-    def __init__(self, record_id, record_dict):
  21
+    def __init__(self, uuid, record_id, record_dict):
  22
+        self._uuid = uuid
21 23
         self.__record_id = record_id
22 24
         self.__record_dict = record_dict
23 25
 
@@ -52,6 +54,10 @@ def __get_record_id(self):
52 54
         return self.__record_id
53 55
     id = property(__get_record_id)
54 56
 
  57
+    def __get_unique_id(self):
  58
+        return '%s@iphone-%s' % (self.__record_id, self._uuid)
  59
+    uuid = property(__get_unique_id)
  60
+
55 61
     def __get_record_dict(self):
56 62
         return self.__record_dict
57 63
     record_dict = property(__get_record_dict)
@@ -76,6 +82,18 @@ class BaseList(object):
76 82
 
77 83
     related_schema_classes = ()
78 84
 
  85
+    config = None
  86
+    state = None
  87
+
  88
+    def __init__(self, uuid, mobile_sync):
  89
+        self._uuid = uuid
  90
+        self._mobile_sync = mobile_sync
  91
+        self._parent_records = {}
  92
+
  93
+        self.__related_class_map = {}
  94
+        for cls in self.parent_schema_class.related_classes:
  95
+            self.__related_class_map[cls.entity_name] = cls
  96
+
79 97
     @classmethod
80 98
     def sync_message(cls, last_sync_time, version):
81 99
         return create_array(
@@ -95,18 +113,10 @@ def finish_message(cls):
95 113
         )
96 114
 
97 115
     @staticmethod
98  
-    def serialize(uuid, records):
99  
-        output = [record.serialize(uuid) for record in records]
  116
+    def serialize(records):
  117
+        output = [record.serialize() for record in records]
100 118
         return ''.join(output)
101 119
 
102  
-    def __init__(self, mobile_sync):
103  
-        self._mobile_sync = mobile_sync
104  
-        self._parent_records = {}
105  
-
106  
-        self.__related_class_map = {}
107  
-        for cls in self.parent_schema_class.related_classes:
108  
-            self.__related_class_map[cls.entity_name] = cls
109  
-
110 120
     def all(self):
111 121
         msg = create_array(
112 122
             'SDMessageGetAllRecordsFromDevice',
@@ -158,10 +168,10 @@ def __receive_records(self, record_type):
158 168
 
159 169
     def _process_record(self, entity_name, id, record):
160 170
         if entity_name == self.parent_schema_class.entity_name:
161  
-            self._parent_records[id] = self.parent_schema_class(id, record)
  171
+            self._parent_records[id] = self.parent_schema_class(self._uuid, id, record)
162 172
         elif entity_name in self.__related_class_map:
163 173
             cls = self.__related_class_map[entity_name]
164  
-            obj = cls(id, record)
  174
+            obj = cls(self._uuid, id, record)
165 175
 
166 176
             self._process_related_record(obj)
167 177
 
26  imobilesync/data/bookmarks.py
... ...
@@ -0,0 +1,26 @@
  1
+from imobilesync.data.base import BaseList, Base, RelatedBase
  2
+from imobilesync.config import config, state
  3
+from imobilesync.options import parser
  4
+
  5
+__all__ = ['Bookmark', 'Bookmarks']
  6
+
  7
+TYPE_FIREFOX = 0
  8
+TYPE_CHROME = 1
  9
+TYPE_CHROMIUM = 2
  10
+
  11
+class Bookmark(Base):
  12
+    entity_name = 'com.apple.bookmarks.Bookmark'
  13
+
  14
+class Bookmarks(BaseList):
  15
+    parent_schema_name = "com.apple.Bookmarks"
  16
+    parent_schema_class = Bookmark
  17
+
  18
+    config = config.add('bookmarks', {})
  19
+    state = state.add('bookmarks', {
  20
+        'last_sync_time': None
  21
+    })
  22
+
  23
+    def __init__(self, uuid, mobile_sync):
  24
+        super(Bookmarks, self).__init__(uuid, mobile_sync)
  25
+
  26
+parser.add_option('--bookmarks', action='append_const', dest='sync_type', const=Bookmarks)
30  imobilesync/data/calendars.py
... ...
@@ -1,5 +1,7 @@
1 1
 from imobilesync.data.base import BaseList, Base, RelatedBase
2  
-from imobilesync.config import state
  2
+from imobilesync.config import state, config
  3
+from imobilesync.options import parser
  4
+
3 5
 import vobject, datetime
4 6
 
5 7
 __all__ = ['Calendar', 'Calendars']
@@ -82,10 +84,9 @@ class Event(CalendarRelated):
82 84
         Recurrence
83 85
     )
84 86
 
85  
-    def serialize(self, uuid, cal):
  87
+    def serialize(self, cal):
86 88
         event = cal.add('vevent')
87  
-        event.add('uid')
88  
-        event.uid.value = '%s@iphone-%s' % (self.id, uuid)
  89
+        event.add('uid').value = self.uuid
89 90
 
90 91
         if hasattr(self, 'all_day'):
91 92
             event.add('dtstart').value = self.start_date.date()
@@ -126,24 +127,29 @@ def __str__(self):
126 127
         return 'Calendar: %s' % self.title
127 128
     __repr__ = __str__
128 129
 
129  
-    def serialize(self, uuid):
  130
+    def __ical__(self):
130 131
         cal = vobject.iCalendar()
131 132
         title = cal.add('X-WR-CALNAME')
132 133
         title.value = self.title
133 134
 
134 135
         for id, event in self.events.items():
135  
-            event.serialize(uuid, cal)
  136
+            event.serialize(cal)
  137
+        return cal
136 138
 
137  
-        return cal.serialize()
  139
+    def serialize(self):
  140
+        return self.__ical__().serialize()
138 141
 
139 142
 class Calendars(BaseList):
140 143
     parent_schema_name = "com.apple.Calendars"
141 144
     parent_schema_class = Calendar
142 145
 
143  
-    state = state.calendars
  146
+    config = config.add('calendars', {})
  147
+    state = state.add('calendars', {
  148
+        'last_sync_time': None
  149
+    })
144 150
 
145  
-    def __init__(self, mobile_sync):
146  
-        super(Calendars, self).__init__(mobile_sync)
  151
+    def __init__(self, uuid, mobile_sync):
  152
+        super(Calendars, self).__init__(uuid, mobile_sync)
147 153
 
148 154
         self._event_records = {}
149 155
         self.__event_class_map = {}
@@ -154,7 +160,7 @@ def _process_record(self, entity_name, id, record):
154 160
         super(Calendars, self)._process_record(entity_name, id, record)
155 161
         if entity_name in self.__event_class_map:
156 162
             cls = self.__event_class_map[entity_name]
157  
-            obj = cls(id, record)
  163
+            obj = cls(self._uuid, id, record)
158 164
 
159 165
             self._process_related_record(obj)
160 166
 
@@ -167,3 +173,5 @@ def _process_related_record(self, related_record):
167 173
         else:
168 174
             parent_objs = getattr(self._event_records[related_record.parent_id], related_record.parent_prop)
169 175
             parent_objs[related_record.id] = related_record
  176
+
  177
+parser.add_option('--calendars', action='append_const', dest='sync_type', const=Calendars)
48  imobilesync/data/contacts.py
... ...
@@ -1,6 +1,8 @@
1 1
 from imobilesync.data.base import BaseList, Base, RelatedBase
2  
-from imobilesync.config import state
3  
-import vobject, time, base64
  2
+from imobilesync.config import state, config
  3
+from imobilesync.options import parser
  4
+
  5
+import vobject, time, base64, pdb
4 6
 
5 7
 __all__ = ['Contact', 'Contacts']
6 8
 
@@ -40,7 +42,7 @@ class EmailAddress(ContactRelated):
40 42
 
41 43
     def serialize(self, card):
42 44
         email = card.add('email')
43  
-        email.params['TYPE'] = [self.get_type_or_label().upper()]
  45
+        email.type_param = self.get_type_or_label().upper()
44 46
         email.value = self.value
45 47
 
46 48
 class Group(ContactRelated):
@@ -58,7 +60,7 @@ class IM(ContactRelated):
58 60
     def serialize(self, card):
59 61
         im = card.add('x-%s' % self.service)
60 62
         im.value = self.user
61  
-        im.params['TYPE'] = [self.get_type_or_label().upper()]
  63
+        im.type_param = self.get_type_or_label().upper()
62 64
 
63 65
 class PhoneNumber(ContactRelated):
64 66
     entity_name = 'com.apple.contacts.Phone Number'
@@ -71,7 +73,7 @@ def serialize(self, card):
71 73
         if teltype == "MOBILE":
72 74
             teltype = "CELL"
73 75
         tel.value = self.value
74  
-        tel.params['TYPE'] = [teltype]
  76
+        tel.type_param = teltype
75 77
 
76 78
 class RelatedName(ContactRelated):
77 79
     entity_name = 'com.apple.contacts.Related Name'
@@ -89,10 +91,13 @@ def get_value(name):
89 91
             address['street'] = get_value('street')
90 92
         if self.record_dict.has_key('street'):
91 93
             address['city'] = get_value('city')
92  
-        if self.record_dict.has_key('country code'):
93  
-            address['region'] = get_value('country code')
  94
+        if self.record_dict.has_key('state'):
  95
+            address['region'] = get_value('state')
94 96
         if self.record_dict.has_key('postal code'):
95 97
             address['code'] = get_value('postal code')
  98
+        if self.record_dict.has_key('country code') and \
  99
+            not self.record_dict.has_key('country'):
  100
+            address['country'] = get_value('country code').upper()
96 101
         if self.record_dict.has_key('country'):
97 102
             address['country'] = get_value('country')
98 103
 
@@ -102,7 +107,7 @@ def get_value(name):
102 107
     def serialize(self, card):
103 108
         adr = card.add('adr')
104 109
         adr.value = vobject.vcard.Address(**self.address_dict)
105  
-        adr.params['TYPE'] = [self.get_type_or_label().upper()]
  110
+        adr.type_param = self.get_type_or_label().upper()
106 111
 
107 112
 class URL(ContactRelated):
108 113
     entity_name = 'com.apple.contacts.URL'
@@ -111,7 +116,7 @@ class URL(ContactRelated):
111 116
     def serialize(self, card):
112 117
         url = card.add('url')
113 118
         url.value = self.value
114  
-        url.params['TYPE'] = [self.get_type_or_label().upper()]
  119
+        url.type_param = self.get_type_or_label().upper().replace(' ', '')
115 120
 
116 121
 class Contact(Base):
117 122
     entity_name = 'com.apple.contacts.Contact'
@@ -156,10 +161,9 @@ def __str__(self):
156 161
 
157 162
     __repr__ = __str__
158 163
 
159  
-    def serialize(self, uuid):
  164
+    def __vcard__(self):
160 165
         card = vobject.vCard()
161  
-        card.add('uid')
162  
-        card.uid.value = '%s@iphone-%s' % (self.id, uuid)
  166
+        card.add('uid').value = self.uuid
163 167
 
164 168
         #for id, group in self.groups.items():
165 169
             #card.add('categories')
@@ -211,22 +215,28 @@ def serialize(self, uuid):
211 215
             card.bday.value = self.birthday.strftime("%Y-%m-%d")
212 216
 
213 217
         if hasattr(self, 'image'):
214  
-            card.add('photo')
215  
-            card.photo.value = base64.b64encode(self.image)
216  
-            card.photo.params['ENCODING'] = ['BASE64']
217  
-            card.photo.params['TYPE'] = ['JPEG']
  218
+            photo = card.add('photo')
  219
+            photo.encoding_param = 'B'
  220
+            photo.type_param = 'JPEG'
  221
+            photo.value = self.image
218 222
 
219 223
         if hasattr(self, "notes"):
220 224
             card.add('note')
221 225
             card.note.value = self.notes
222 226
 
223  
-        return card.serialize()
  227
+        return card
  228
+
  229
+    def serialize(self):
  230
+        return self.__vcard__().serialize()
224 231
 
225 232
 class Contacts(BaseList):
226 233
     parent_schema_name = "com.apple.Contacts"
227 234
     parent_schema_class = Contact
228 235
 
229  
-    state = state.contacts
  236
+    config = config.add('contacts', {})
  237
+    state = state.add('contacts', {
  238
+        'last_sync_time': None
  239
+    })
230 240
 
231 241
     def __process_related_record(self, related_record):
232 242
         if isinstance(related_record, Group):
@@ -234,3 +244,5 @@ def __process_related_record(self, related_record):
234 244
                 self._parent_records[id].groups[related_record.id] = related_record
235 245
         else:
236 246
             super(Contacts, self).__process_related_record(related_record)
  247
+
  248
+parser.add_option('--contacts', action='append_const', dest='sync_type', const=Contacts)
22  imobilesync/data/notes.py
... ...
@@ -0,0 +1,22 @@
  1
+from imobilesync.data.base import BaseList, Base, RelatedBase
  2
+from imobilesync.config import state, config
  3
+from imobilesync.options import parser
  4
+
  5
+__all__ = ['Note', 'Notes']
  6
+
  7
+class Note(Base):
  8
+    entity_name = 'com.apple.notes.Note'
  9
+
  10
+class Notes(BaseList):
  11
+    parent_schema_name = "com.apple.Notes"
  12
+    parent_schema_class = Note
  13
+
  14
+    config = config.add('contacts', {})
  15
+    state = state.add('contacts', {
  16
+        'last_sync_time': None
  17
+    })
  18
+
  19
+    def __init__(self, uuid, mobile_sync):
  20
+        super(Notes, self).__init__(uuid, mobile_sync)
  21
+
  22
+parser.add_option('--notes', action='append_const', dest='sync_type', const=Notes)
6  imobilesync/options.py
... ...
@@ -0,0 +1,6 @@
  1
+from optparse import OptionParser
  2
+
  3
+parser = OptionParser("Usage: %prog --[sync_type] args")
  4
+parser.add_option('-i', '--ignore-sync-time', action='store_true', dest='ignore_sync_time', default=False)
  5
+
  6
+from imobilesync.data import *
30  imobilesync/sync.py
@@ -13,7 +13,9 @@ def get_array(self):
13 13
         pass
14 14
 
15 15
     def __str__(self):
16  
-        return repr(self.value)
  16
+        return self.value
  17
+
  18
+    __repr__ = __str__
17 19
 
18 20
 class SyncErrorCancel(SyncError):
19 21
     def __init__(self, schema_type, reason):
@@ -47,7 +49,7 @@ def __init__(self, uuid=None):
47 49
         device = self.__device = idevice()
48 50
 
49 51
         if uuid is not None:
50  
-            if not device.init_device_by_uuid(uuid):
  52
+            if not device.init_device_by_uuid(str(uuid)):
51 53
                 raise SyncError('No iDevice with uuid %s.' % uuid)
52 54
         else:
53 55
             if not device.init_device():
@@ -72,13 +74,21 @@ def get_changed_records(self, cls):
72 74
         obj = self.__get_records(cls)
73 75
         return obj.changes()
74 76
 
  77
+    def get_all_records_hashed(self, cls):
  78
+        objs = self.get_all_records(cls)
  79
+        return dict((record.uuid, record) for record in objs)
  80
+
  81
+    def get_changed_records_hashed(self, cls):
  82
+        objs = self.get_changed_records(cls)
  83
+        return dict((record.uuid, record) for record in objs)
  84
+
75 85
     def serialize_all(self, cls):
76 86
         objs = self.get_all_records(cls)
77  
-        return cls.serialize(self.uuid, objs)
  87
+        return cls.serialize(objs)
78 88
 
79 89
     def serialize_changed(self, cls):
80 90
         objs = self.get_changed_records(cls)
81  
-        return cls.serialize(self.uuid, objs)
  91
+        return cls.serialize(objs)
82 92
 
83 93
     def ping(self):
84 94
         msg = create_array(
@@ -120,4 +130,14 @@ def __get_records(self, cls):
120 130
         )
121 131
         ret = self.__mobile_sync.receive()
122 132
 
123  
-        return cls(self.__mobile_sync)
  133
+        return cls(self.uuid, self.__mobile_sync)
  134
+
  135
+class SyncFactory(object):
  136
+    __sync = None
  137
+
  138
+    @classmethod
  139
+    def get(cls, uuid=None):
  140
+        if cls.__sync is None:
  141
+            cls.__sync = Sync(uuid)
  142
+            cls.__sync.connect()
  143
+        return cls.__sync
9  pavement.py
@@ -13,14 +13,14 @@
13 13
     author_email='bryan@reigndropsfall.net',
14 14
     url='',
15 15
     install_requires=['vobject'],
16  
-    packages=[],
  16
+    packages=['imobilesync', 'imobilesync.data'],
17 17
     package_data=find_package_data('imobilesync', package='imobilesync', only_in_packages=False),
18  
-    zip_safe=False,
  18
+    zip_safe=True,
19 19
     #test_suite='nose.collector',
20 20
     #setup_requires=['nose>=0.11'],
21 21
     entry_points="""
22 22
         [console_scripts]
23  
-        imobilesync = imobilesync:main
  23
+        imobilesync = imobilesync.commands:main
24 24
     """
25 25
 )
26 26
 options(
@@ -28,6 +28,7 @@
28 28
         extra_files=['virtual']
29 29
     ),
30 30
     virtualenv=Bunch(
  31
+        packages_to_install=["ipython"],
31 32
         paver_command_line='develop',
32 33
         unzip_setuptools=True
33 34
     )
@@ -43,7 +44,7 @@ def sdist():
43 44
 def clean():
44 45
     """Cleans up the virtualenv"""
45 46
     for p in ('bin', 'build', 'dist', 'docs', 'include', 'lib', 'man',
46  
-            'share', 'imobilesync.egg-info', 'paver-minilib.zip', 'setup.py'):
  47
+            'share', 'imobilesync.egg-info'):
47 48
         pth = path(p)
48 49
         if pth.isdir():
49 50
             pth.rmtree()
BIN  paver-minilib.zip
Binary file not shown

0 notes on commit 9ac8157

Please sign in to comment.
Something went wrong with that request. Please try again.