1__license__   = 'GPL v3'
2__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
3import os
4from collections import namedtuple
5
6from calibre import prints
7from calibre.constants import iswindows
8from calibre.customize import Plugin
9
10
11class DevicePlugin(Plugin):
12    """
13    Defines the interface that should be implemented by backends that
14    communicate with an e-book reader.
15    """
16    type = _('Device interface')
17
18    #: Ordered list of supported formats
19    FORMATS     = ["lrf", "rtf", "pdf", "txt"]
20    # If True, the config dialog will not show the formats box
21    HIDE_FORMATS_CONFIG_BOX = False
22
23    #: VENDOR_ID can be either an integer, a list of integers or a dictionary
24    #: If it is a dictionary, it must be a dictionary of dictionaries,
25    #: of the form::
26    #:
27    #:   {
28    #:    integer_vendor_id : { product_id : [list of BCDs], ... },
29    #:    ...
30    #:   }
31    #:
32    VENDOR_ID   = 0x0000
33
34    #: An integer or a list of integers
35    PRODUCT_ID  = 0x0000
36    #: BCD can be either None to not distinguish between devices based on BCD, or
37    #: it can be a list of the BCD numbers of all devices supported by this driver.
38    BCD         = None
39
40    #: Height for thumbnails on the device
41    THUMBNAIL_HEIGHT = 68
42
43    #: Compression quality for thumbnails. Set this closer to 100 to have better
44    #: quality thumbnails with fewer compression artifacts. Of course, the
45    #: thumbnails get larger as well.
46    THUMBNAIL_COMPRESSION_QUALITY = 75
47
48    #: Set this to True if the device supports updating cover thumbnails during
49    #: sync_booklists. Setting it to true will ask device.py to refresh the
50    #: cover thumbnails during book matching
51    WANTS_UPDATED_THUMBNAILS = False
52
53    #: Whether the metadata on books can be set via the GUI.
54    CAN_SET_METADATA = ['title', 'authors', 'collections']
55
56    #: Whether the device can handle device_db metadata plugboards
57    CAN_DO_DEVICE_DB_PLUGBOARD = False
58
59    # Set this to None if the books on the device are files that the GUI can
60    # access in order to add the books from the device to the library
61    BACKLOADING_ERROR_MESSAGE = _('Cannot get files from this device')
62
63    #: Path separator for paths to books on device
64    path_sep = os.sep
65
66    #: Icon for this device
67    icon = I('reader.png')
68
69    # Encapsulates an annotation fetched from the device
70    UserAnnotation = namedtuple('Annotation','type, value')
71
72    #: GUI displays this as a message if not None. Useful if opening can take a
73    #: long time
74    OPEN_FEEDBACK_MESSAGE = None
75
76    #: Set of extensions that are "virtual books" on the device
77    #: and therefore cannot be viewed/saved/added to library.
78    #: For example: ``frozenset(['kobo'])``
79    VIRTUAL_BOOK_EXTENSIONS = frozenset()
80
81    #: Message to display to user for virtual book extensions.
82    VIRTUAL_BOOK_EXTENSION_MESSAGE = None
83
84    #: Whether to nuke comments in the copy of the book sent to the device. If
85    #: not None this should be short string that the comments will be replaced
86    #: by.
87    NUKE_COMMENTS = None
88
89    #: If True indicates that  this driver completely manages device detection,
90    #: ejecting and so forth. If you set this to True, you *must* implement the
91    #: detect_managed_devices and debug_managed_device_detection methods.
92    #: A driver with this set to true is responsible for detection of devices,
93    #: managing a blacklist of devices, a list of ejected devices and so forth.
94    #: calibre will periodically call the detect_managed_devices() method and
95    #: if it returns a detected device, calibre will call open(). open() will
96    #: be called every time a device is returned even if previous calls to open()
97    #: failed, therefore the driver must maintain its own blacklist of failed
98    #: devices. Similarly, when ejecting, calibre will call eject() and then
99    #: assuming the next call to detect_managed_devices() returns None, it will
100    #: call post_yank_cleanup().
101    MANAGES_DEVICE_PRESENCE = False
102
103    #: If set the True, calibre will call the :meth:`get_driveinfo()` method
104    #: after the books lists have been loaded to get the driveinfo.
105    SLOW_DRIVEINFO = False
106
107    #: If set to True, calibre will ask the user if they want to manage the
108    #: device with calibre, the first time it is detected. If you set this to
109    #: True you must implement :meth:`get_device_uid()` and
110    #: :meth:`ignore_connected_device()` and
111    #: :meth:`get_user_blacklisted_devices` and
112    #: :meth:`set_user_blacklisted_devices`
113    ASK_TO_ALLOW_CONNECT = False
114
115    #: Set this to a dictionary of the form {'title':title, 'msg':msg, 'det_msg':detailed_msg} to have calibre popup
116    #: a message to the user after some callbacks are run (currently only upload_books).
117    #: Be careful to not spam the user with too many messages. This variable is checked after *every* callback,
118    #: so only set it when you really need to.
119    user_feedback_after_callback = None
120
121    @classmethod
122    def get_gui_name(cls):
123        if hasattr(cls, 'gui_name'):
124            return cls.gui_name
125        if hasattr(cls, '__name__'):
126            return cls.__name__
127        return cls.name
128
129    # Device detection {{{
130    def test_bcd(self, bcdDevice, bcd):
131        if bcd is None or len(bcd) == 0:
132            return True
133        for c in bcd:
134            if c == bcdDevice:
135                return True
136        return False
137
138    def is_usb_connected(self, devices_on_system, debug=False, only_presence=False):
139        '''
140        Return True, device_info if a device handled by this plugin is currently connected.
141
142        :param devices_on_system: List of devices currently connected
143
144        '''
145        vendors_on_system = {x[0] for x in devices_on_system}
146        vendors = set(self.VENDOR_ID) if hasattr(self.VENDOR_ID, '__len__') else {self.VENDOR_ID}
147        if hasattr(self.VENDOR_ID, 'keys'):
148            products = []
149            for ven in self.VENDOR_ID:
150                products.extend(self.VENDOR_ID[ven].keys())
151        else:
152            products = self.PRODUCT_ID if hasattr(self.PRODUCT_ID, '__len__') else [self.PRODUCT_ID]
153
154        ch = self.can_handle_windows if iswindows else self.can_handle
155        for vid in vendors_on_system.intersection(vendors):
156            for dev in devices_on_system:
157                cvid, pid, bcd = dev[:3]
158                if cvid == vid:
159                    if pid in products:
160                        if hasattr(self.VENDOR_ID, 'keys'):
161                            try:
162                                cbcd = self.VENDOR_ID[vid][pid]
163                            except KeyError:
164                                # Vendor vid does not have product pid, pid
165                                # exists for some other vendor in this
166                                # device
167                                continue
168                        else:
169                            cbcd = self.BCD
170                        if self.test_bcd(bcd, cbcd):
171                            if debug:
172                                prints(dev)
173                            if ch(dev, debug=debug):
174                                return True, dev
175        return False, None
176
177    def detect_managed_devices(self, devices_on_system, force_refresh=False):
178        '''
179        Called only if MANAGES_DEVICE_PRESENCE is True.
180
181        Scan for devices that this driver can handle. Should return a device
182        object if a device is found. This object will be passed to the open()
183        method as the connected_device. If no device is found, return None. The
184        returned object can be anything, calibre does not use it, it is only
185        passed to open().
186
187        This method is called periodically by the GUI, so make sure it is not
188        too resource intensive. Use a cache to avoid repeatedly scanning the
189        system.
190
191        :param devices_on_system: Set of USB devices found on the system.
192
193        :param force_refresh: If True and the driver uses a cache to prevent
194                              repeated scanning, the cache must be flushed.
195
196        '''
197        raise NotImplementedError()
198
199    def debug_managed_device_detection(self, devices_on_system, output):
200        '''
201        Called only if MANAGES_DEVICE_PRESENCE is True.
202
203        Should write information about the devices detected on the system to
204        output, which is a file like object.
205
206        Should return True if a device was detected and successfully opened,
207        otherwise False.
208        '''
209        raise NotImplementedError()
210
211    # }}}
212
213    def reset(self, key='-1', log_packets=False, report_progress=None,
214            detected_device=None):
215        """
216        :param key: The key to unlock the device
217        :param log_packets: If true the packet stream to/from the device is logged
218        :param report_progress: Function that is called with a % progress
219                                (number between 0 and 100) for various tasks.
220                                If it is called with -1 that means that the
221                                task does not have any progress information
222        :param detected_device: Device information from the device scanner
223
224        """
225        raise NotImplementedError()
226
227    def can_handle_windows(self, usbdevice, debug=False):
228        '''
229        Optional method to perform further checks on a device to see if this driver
230        is capable of handling it. If it is not it should return False. This method
231        is only called after the vendor, product ids and the bcd have matched, so
232        it can do some relatively time intensive checks. The default implementation
233        returns True. This method is called only on Windows. See also
234        :meth:`can_handle`.
235
236        Note that for devices based on USBMS this method by default delegates
237        to :meth:`can_handle`.  So you only need to override :meth:`can_handle`
238        in your subclass of USBMS.
239
240        :param usbdevice: A usbdevice as returned by :func:`calibre.devices.winusb.scan_usb_devices`
241        '''
242        return True
243
244    def can_handle(self, device_info, debug=False):
245        '''
246        Unix version of :meth:`can_handle_windows`.
247
248        :param device_info: Is a tuple of (vid, pid, bcd, manufacturer, product,
249                            serial number)
250
251        '''
252
253        return True
254    can_handle.is_base_class_implementation = True
255
256    def open(self, connected_device, library_uuid):
257        '''
258        Perform any device specific initialization. Called after the device is
259        detected but before any other functions that communicate with the device.
260        For example: For devices that present themselves as USB Mass storage
261        devices, this method would be responsible for mounting the device or
262        if the device has been automounted, for finding out where it has been
263        mounted. The method :meth:`calibre.devices.usbms.device.Device.open` has
264        an implementation of
265        this function that should serve as a good example for USB Mass storage
266        devices.
267
268        This method can raise an OpenFeedback exception to display a message to
269        the user.
270
271        :param connected_device: The device that we are trying to open. It is
272            a tuple of (vendor id, product id, bcd, manufacturer name, product
273            name, device serial number). However, some devices have no serial
274            number and on Windows only the first three fields are present, the
275            rest are None.
276
277        :param library_uuid: The UUID of the current calibre library. Can be
278            None if there is no library (for example when used from the command
279            line).
280
281        '''
282        raise NotImplementedError()
283
284    def eject(self):
285        '''
286        Un-mount / eject the device from the OS. This does not check if there
287        are pending GUI jobs that need to communicate with the device.
288
289        NOTE: That this method may not be called on the same thread as the rest
290        of the device methods.
291        '''
292        raise NotImplementedError()
293
294    def post_yank_cleanup(self):
295        '''
296        Called if the user yanks the device without ejecting it first.
297        '''
298        raise NotImplementedError()
299
300    def set_progress_reporter(self, report_progress):
301        '''
302        Set a function to report progress information.
303
304        :param report_progress: Function that is called with a % progress
305                                (number between 0 and 100) for various tasks.
306                                If it is called with -1 that means that the
307                                task does not have any progress information
308
309        '''
310        raise NotImplementedError()
311
312    def get_device_information(self, end_session=True):
313        """
314        Ask device for device information. See L{DeviceInfoQuery}.
315
316        :return: (device name, device version, software version on device, MIME type)
317                 The tuple can optionally have a fifth element, which is a
318                 drive information dictionary. See usbms.driver for an example.
319
320        """
321        raise NotImplementedError()
322
323    def get_driveinfo(self):
324        '''
325        Return the driveinfo dictionary. Usually called from
326        get_device_information(), but if loading the driveinfo is slow for this
327        driver, then it should set SLOW_DRIVEINFO. In this case, this method
328        will be called by calibre after the book lists have been loaded. Note
329        that it is not called on the device thread, so the driver should cache
330        the drive info in the books() method and this function should return
331        the cached data.
332        '''
333        return {}
334
335    def card_prefix(self, end_session=True):
336        '''
337        Return a 2 element list of the prefix to paths on the cards.
338        If no card is present None is set for the card's prefix.
339        E.G.
340        ('/place', '/place2')
341        (None, 'place2')
342        ('place', None)
343        (None, None)
344        '''
345        raise NotImplementedError()
346
347    def total_space(self, end_session=True):
348        """
349        Get total space available on the mountpoints:
350            1. Main memory
351            2. Memory Card A
352            3. Memory Card B
353
354        :return: A 3 element list with total space in bytes of (1, 2, 3). If a
355                 particular device doesn't have any of these locations it should return 0.
356
357        """
358        raise NotImplementedError()
359
360    def free_space(self, end_session=True):
361        """
362        Get free space available on the mountpoints:
363          1. Main memory
364          2. Card A
365          3. Card B
366
367        :return: A 3 element list with free space in bytes of (1, 2, 3). If a
368                 particular device doesn't have any of these locations it should return -1.
369
370        """
371        raise NotImplementedError()
372
373    def books(self, oncard=None, end_session=True):
374        """
375        Return a list of e-books on the device.
376
377        :param oncard:  If 'carda' or 'cardb' return a list of e-books on the
378                        specific storage card, otherwise return list of e-books
379                        in main memory of device. If a card is specified and no
380                        books are on the card return empty list.
381
382        :return: A BookList.
383
384        """
385        raise NotImplementedError()
386
387    def upload_books(self, files, names, on_card=None, end_session=True,
388                     metadata=None):
389        '''
390        Upload a list of books to the device. If a file already
391        exists on the device, it should be replaced.
392        This method should raise a :class:`FreeSpaceError` if there is not enough
393        free space on the device. The text of the FreeSpaceError must contain the
394        word "card" if ``on_card`` is not None otherwise it must contain the word "memory".
395
396        :param files: A list of paths
397        :param names: A list of file names that the books should have
398                      once uploaded to the device. len(names) == len(files)
399        :param metadata: If not None, it is a list of :class:`Metadata` objects.
400                         The idea is to use the metadata to determine where on the device to
401                         put the book. len(metadata) == len(files). Apart from the regular
402                         cover (path to cover), there may also be a thumbnail attribute, which should
403                         be used in preference. The thumbnail attribute is of the form
404                         (width, height, cover_data as jpeg).
405
406        :return: A list of 3-element tuples. The list is meant to be passed
407                 to :meth:`add_books_to_metadata`.
408        '''
409        raise NotImplementedError()
410
411    @classmethod
412    def add_books_to_metadata(cls, locations, metadata, booklists):
413        '''
414        Add locations to the booklists. This function must not communicate with
415        the device.
416
417        :param locations: Result of a call to L{upload_books}
418        :param metadata: List of :class:`Metadata` objects, same as for
419                         :meth:`upload_books`.
420        :param booklists: A tuple containing the result of calls to
421                          (:meth:`books(oncard=None)`,
422                          :meth:`books(oncard='carda')`,
423                          :meth`books(oncard='cardb')`).
424
425        '''
426        raise NotImplementedError()
427
428    def delete_books(self, paths, end_session=True):
429        '''
430        Delete books at paths on device.
431        '''
432        raise NotImplementedError()
433
434    @classmethod
435    def remove_books_from_metadata(cls, paths, booklists):
436        '''
437        Remove books from the metadata list. This function must not communicate
438        with the device.
439
440        :param paths: paths to books on the device.
441        :param booklists: A tuple containing the result of calls to
442                          (:meth:`books(oncard=None)`,
443                          :meth:`books(oncard='carda')`,
444                          :meth`books(oncard='cardb')`).
445
446        '''
447        raise NotImplementedError()
448
449    def sync_booklists(self, booklists, end_session=True):
450        '''
451        Update metadata on device.
452
453        :param booklists: A tuple containing the result of calls to
454                          (:meth:`books(oncard=None)`,
455                          :meth:`books(oncard='carda')`,
456                          :meth`books(oncard='cardb')`).
457
458        '''
459        raise NotImplementedError()
460
461    def get_file(self, path, outfile, end_session=True):
462        '''
463        Read the file at ``path`` on the device and write it to outfile.
464
465        :param outfile: file object like ``sys.stdout`` or the result of an
466                       :func:`open` call.
467
468        '''
469        raise NotImplementedError()
470
471    @classmethod
472    def config_widget(cls):
473        '''
474        Should return a QWidget. The QWidget contains the settings for the
475        device interface
476        '''
477        raise NotImplementedError()
478
479    @classmethod
480    def save_settings(cls, settings_widget):
481        '''
482        Should save settings to disk. Takes the widget created in
483        :meth:`config_widget` and saves all settings to disk.
484        '''
485        raise NotImplementedError()
486
487    @classmethod
488    def settings(cls):
489        '''
490        Should return an opts object. The opts object should have at least one
491        attribute `format_map` which is an ordered list of formats for the
492        device.
493        '''
494        raise NotImplementedError()
495
496    def set_plugboards(self, plugboards, pb_func):
497        '''
498        provide the driver the current set of plugboards and a function to
499        select a specific plugboard. This method is called immediately before
500        add_books and sync_booklists.
501
502        pb_func is a callable with the following signature::
503            def pb_func(device_name, format, plugboards)
504
505        You give it the current device name (either the class name or
506        DEVICE_PLUGBOARD_NAME), the format you are interested in (a 'real'
507        format or 'device_db'), and the plugboards (you were given those by
508        set_plugboards, the same place you got this method).
509
510        :return: None or a single plugboard instance.
511
512        '''
513        pass
514
515    def set_driveinfo_name(self, location_code, name):
516        '''
517        Set the device name in the driveinfo file to 'name'. This setting will
518        persist until the file is re-created or the name is changed again.
519
520        Non-disk devices should implement this method based on the location
521        codes returned by the get_device_information() method.
522        '''
523        pass
524
525    def prepare_addable_books(self, paths):
526        '''
527        Given a list of paths, returns another list of paths. These paths
528        point to addable versions of the books.
529
530        If there is an error preparing a book, then instead of a path, the
531        position in the returned list for that book should be a three tuple:
532        (original_path, the exception instance, traceback)
533        '''
534        return paths
535
536    def startup(self):
537        '''
538        Called when calibre is starting the device. Do any initialization
539        required. Note that multiple instances of the class can be instantiated,
540        and thus __init__ can be called multiple times, but only one instance
541        will have this method called. This method is called on the device
542        thread, not the GUI thread.
543        '''
544        pass
545
546    def shutdown(self):
547        '''
548        Called when calibre is shutting down, either for good or in preparation
549        to restart. Do any cleanup required. This method is called on the
550        device thread, not the GUI thread.
551        '''
552        pass
553
554    def get_device_uid(self):
555        '''
556        Must return a unique id for the currently connected device (this is
557        called immediately after a successful call to open()). You must
558        implement this method if you set ASK_TO_ALLOW_CONNECT = True
559        '''
560        raise NotImplementedError()
561
562    def ignore_connected_device(self, uid):
563        '''
564        Should ignore the device identified by uid (the result of a call to
565        get_device_uid()) in the future. You must implement this method if you
566        set ASK_TO_ALLOW_CONNECT = True. Note that this function is called
567        immediately after open(), so if open() caches some state, the driver
568        should reset that state.
569        '''
570        raise NotImplementedError()
571
572    def get_user_blacklisted_devices(self):
573        '''
574        Return map of device uid to friendly name for all devices that the user
575        has asked to be ignored.
576        '''
577        return {}
578
579    def set_user_blacklisted_devices(self, devices):
580        '''
581        Set the list of device uids that should be ignored by this driver.
582        '''
583        pass
584
585    def specialize_global_preferences(self, device_prefs):
586        '''
587        Implement this method if your device wants to override a particular
588        preference. You must ensure that all call sites that want a preference
589        that can be overridden use device_prefs['something'] instead
590        of prefs['something']. Your
591        method should call device_prefs.set_overrides(pref=val, pref=val, ...).
592        Currently used for:
593        metadata management (prefs['manage_device_metadata'])
594        '''
595        device_prefs.set_overrides()
596
597    def set_library_info(self, library_name, library_uuid, field_metadata):
598        '''
599        Implement this method if you want information about the current calibre
600        library. This method is called at startup and when the calibre library
601        changes while connected.
602        '''
603        pass
604
605    # Dynamic control interface.
606    # The following methods are probably called on the GUI thread. Any driver
607    # that implements these methods must take pains to be thread safe, because
608    # the device_manager might be using the driver at the same time that one of
609    # these methods is called.
610
611    def is_dynamically_controllable(self):
612        '''
613        Called by the device manager when starting plugins. If this method returns
614        a string, then a) it supports the device manager's dynamic control
615        interface, and b) that name is to be used when talking to the plugin.
616
617        This method can be called on the GUI thread. A driver that implements
618        this method must be thread safe.
619        '''
620        return None
621
622    def start_plugin(self):
623        '''
624        This method is called to start the plugin. The plugin should begin
625        to accept device connections however it does that. If the plugin is
626        already accepting connections, then do nothing.
627
628        This method can be called on the GUI thread. A driver that implements
629        this method must be thread safe.
630        '''
631        pass
632
633    def stop_plugin(self):
634        '''
635        This method is called to stop the plugin. The plugin should no longer
636        accept connections, and should cleanup behind itself. It is likely that
637        this method should call shutdown. If the plugin is already not accepting
638        connections, then do nothing.
639
640        This method can be called on the GUI thread. A driver that implements
641        this method must be thread safe.
642        '''
643        pass
644
645    def get_option(self, opt_string, default=None):
646        '''
647        Return the value of the option indicated by opt_string. This method can
648        be called when the plugin is not started. Return None if the option does
649        not exist.
650
651        This method can be called on the GUI thread. A driver that implements
652        this method must be thread safe.
653        '''
654        return default
655
656    def set_option(self, opt_string, opt_value):
657        '''
658        Set the value of the option indicated by opt_string. This method can
659        be called when the plugin is not started.
660
661        This method can be called on the GUI thread. A driver that implements
662        this method must be thread safe.
663        '''
664        pass
665
666    def is_running(self):
667        '''
668        Return True if the plugin is started, otherwise false
669
670        This method can be called on the GUI thread. A driver that implements
671        this method must be thread safe.
672        '''
673        return False
674
675    def synchronize_with_db(self, db, book_id, book_metadata, first_call):
676        '''
677        Called during book matching when a book on the device is matched with
678        a book in calibre's db. The method is responsible for synchronizing
679        data from the device to calibre's db (if needed).
680
681        The method must return a two-value tuple. The first value is a set of
682        calibre book ids changed if calibre's database was changed or None if the
683        database was not changed. If the first value is an empty set then the
684        metadata for the book on the device is updated with calibre's metadata
685        and given back to the device, but no GUI refresh of that book is done.
686        This is useful when the calibre data is correct but must be sent to the
687        device.
688
689        The second value is itself a 2-value tuple. The first value in the tuple
690        specifies whether a book format should be sent to the device. The intent
691        is to permit verifying that the book on the device is the same as the
692        book in calibre. This value must be None if no book is to be sent,
693        otherwise return the base file name on the device (a string like
694        foobar.epub). Be sure to include the extension in the name. The device
695        subsystem will construct a send_books job for all books with not- None
696        returned values. Note: other than to later retrieve the extension, the
697        name is ignored in cases where the device uses a template to generate
698        the file name, which most do. The second value in the returned tuple
699        indicated whether the format is future-dated. Return True if it is,
700        otherwise return False. calibre will display a dialog to the user
701        listing all future dated books.
702
703        Extremely important: this method is called on the GUI thread. It must
704        be threadsafe with respect to the device manager's thread.
705
706        book_id: the calibre id for the book in the database.
707        book_metadata: the Metadata object for the book coming from the device.
708        first_call: True if this is the first call during a sync, False otherwise
709        '''
710        return (None, (None, False))
711
712
713class BookList(list):
714    '''
715    A list of books. Each Book object must have the fields
716
717      #. title
718      #. authors
719      #. size (file size of the book)
720      #. datetime (a UTC time tuple)
721      #. path (path on the device to the book)
722      #. thumbnail (can be None) thumbnail is either a str/bytes object with the
723         image data or it should have an attribute image_path that stores an
724         absolute (platform native) path to the image
725      #. tags (a list of strings, can be empty).
726
727    '''
728
729    __getslice__ = None
730    __setslice__ = None
731
732    def __init__(self, oncard, prefix, settings):
733        pass
734
735    def supports_collections(self):
736        ''' Return True if the device supports collections for this book list. '''
737        raise NotImplementedError()
738
739    def add_book(self, book, replace_metadata):
740        '''
741        Add the book to the booklist. Intent is to maintain any device-internal
742        metadata. Return True if booklists must be sync'ed
743        '''
744        raise NotImplementedError()
745
746    def remove_book(self, book):
747        '''
748        Remove a book from the booklist. Correct any device metadata at the
749        same time
750        '''
751        raise NotImplementedError()
752
753    def get_collections(self, collection_attributes):
754        '''
755        Return a dictionary of collections created from collection_attributes.
756        Each entry in the dictionary is of the form collection name:[list of
757        books]
758
759        The list of books is sorted by book title, except for collections
760        created from series, in which case series_index is used.
761
762        :param collection_attributes: A list of attributes of the Book object
763
764        '''
765        raise NotImplementedError()
766
767
768class CurrentlyConnectedDevice:
769
770    def __init__(self):
771        self._device = None
772
773    @property
774    def device(self):
775        return self._device
776
777
778# A device driver can check if a device is currently connected to calibre using
779# the following code::
780#   from calibre.device.interface import currently_connected_device
781#   if currently_connected_device.device is None:
782#      # no device connected
783# The device attribute will be either None or the device driver object
784# (DevicePlugin instance) for the currently connected device.
785currently_connected_device = CurrentlyConnectedDevice()
786