diff --git a/src/calibre/devices/usbms/__init__.py b/src/calibre/devices/usbms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py new file mode 100644 index 0000000000..3943ef94f4 --- /dev/null +++ b/src/calibre/devices/usbms/books.py @@ -0,0 +1,44 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' +''' +''' + +import os, fnmatch, re, time + +from calibre.devices.interface import BookList as _BookList + +class Book(object): + def __init__(self, path, title, authors, mime): + self.title = title + self.authors = authors + self.mime = mime + self.size = os.path.getsize(path) + self.datetime = time.gmtime(os.path.getctime(path)) + self.path = path + self.thumbnail = None + self.tags = [] + + @apply + def title_sorter(): + doc = '''String to sort the title. If absent, title is returned''' + def fget(self): + return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip() + return property(doc=doc, fget=fget) + + @apply + def thumbnail(): + return None + + def __str__(self): + """ Return a utf-8 encoded string with title author and path information """ + return self.title.encode('utf-8') + " by " + \ + self.authors.encode('utf-8') + " at " + self.path.encode('utf-8') + +class BookList(_BookList): + def supports_tags(self): + return False + + def set_tags(self, book, tags): + pass + + diff --git a/src/calibre/devices/usbms/cli.py b/src/calibre/devices/usbms/cli.py new file mode 100644 index 0000000000..35a3a84824 --- /dev/null +++ b/src/calibre/devices/usbms/cli.py @@ -0,0 +1,16 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember + + + + + + %(main_memory)s + %(deviceclass)s + + + + + + + + + + + + + %(storage_card)s + %(deviceclass)s + + + + + + +''' + + def __init__(self, key='-1', log_packets=False, report_progress=None) : + self._main_prefix = self._card_prefix = None + + @classmethod + def get_fdi(cls): + return cls.FDI_TEMPLATE%dict( + app=__appname__, + deviceclass=cls.__name__, + vendor_id=hex(cls.VENDOR_ID), + product_id=hex(cls.PRODUCT_ID), + bcd=hex(cls.BCD), + main_memory=cls.MAIN_MEMORY_VOLUME_LABEL, + storage_card=cls.STORAGE_CARD_VOLUME_LABEL, + ) + + def set_progress_reporter(self, report_progress): + self.report_progress = report_progress + + def card_prefix(self, end_session=True): + return self._card_prefix + + @classmethod + def _windows_space(cls, prefix): + if prefix is None: + return 0, 0 + win32file = __import__('win32file', globals(), locals(), [], -1) + try: + sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ + win32file.GetDiskFreeSpace(prefix[:-1]) + except Exception, err: + if getattr(err, 'args', [None])[0] == 21: # Disk not ready + time.sleep(3) + sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ + win32file.GetDiskFreeSpace(prefix[:-1]) + else: raise + mult = sectors_per_cluster * bytes_per_sector + return total_clusters * mult, free_clusters * mult + + def total_space(self, end_session=True): + msz = csz = 0 + print self._main_prefix + if not iswindows: + if self._main_prefix is not None: + stats = os.statvfs(self._main_prefix) + msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) + if self._card_prefix is not None: + stats = os.statvfs(self._card_prefix) + csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) + else: + msz = self._windows_space(self._main_prefix)[0] + csz = self._windows_space(self._card_prefix)[0] + + return (msz, 0, csz) + + def free_space(self, end_session=True): + msz = csz = 0 + if not iswindows: + if self._main_prefix is not None: + stats = os.statvfs(self._main_prefix) + msz = stats.f_frsize * stats.f_bavail + if self._card_prefix is not None: + stats = os.statvfs(self._card_prefix) + csz = stats.f_frsize * stats.f_bavail + else: + msz = self._windows_space(self._main_prefix)[1] + csz = self._windows_space(self._card_prefix)[1] + + return (msz, 0, csz) + + @classmethod + def windows_match_device(cls, device_id): + device_id = device_id.upper() + if 'VEN_'+cls.VENDOR_NAME in device_id and \ + 'PROD_'+cls.PRODUCT_NAME in device_id: + return True + vid, pid = hex(cls.VENDOR_ID)[2:], hex(cls.PRODUCT_ID)[2:] + while len(vid) < 4: vid = '0' + vid + while len(pid) < 4: pid = '0' + pid + if 'VID_'+vid in device_id and 'PID_'+pid in device_id: + return True + return False + + # This only supports Windows >= 2000 + def open_windows(self): + drives = [] + wmi = __import__('wmi', globals(), locals(), [], -1) + c = wmi.WMI() + for drive in c.Win32_DiskDrive(): + if self.__class__.windows_match_device(str(drive.PNPDeviceID)): + if drive.Partitions == 0: + continue + try: + partition = drive.associators("Win32_DiskDriveToDiskPartition")[0] + logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0] + prefix = logical_disk.DeviceID+os.sep + drives.append((drive.Index, prefix)) + except IndexError: + continue + + if not drives: + raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) + + drives.sort(cmp=lambda a, b: cmp(a[0], b[0])) + self._main_prefix = drives[0][1] + if len(drives) > 1: + self._card_prefix = drives[1][1] + + def open_osx(self): + raise NotImplementedError() + + def open_linux(self): + import dbus + bus = dbus.SystemBus() + hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager") + + def conditional_mount(dev): + mmo = bus.get_object("org.freedesktop.Hal", dev) + label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device') + is_mounted = mmo.GetPropertyString('volume.is_mounted', dbus_interface='org.freedesktop.Hal.Device') + mount_point = mmo.GetPropertyString('volume.mount_point', dbus_interface='org.freedesktop.Hal.Device') + fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device') + if is_mounted: + return str(mount_point) + mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'], + dbus_interface='org.freedesktop.Hal.Device.Volume') + return os.path.normpath('/media/'+label)+'/' + + mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__) + if not mm: + raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,)) + self._main_prefix = None + for dev in mm: + try: + self._main_prefix = conditional_mount(dev)+os.sep + break + except dbus.exceptions.DBusException: + continue + + if not self._main_prefix: + raise DeviceError('Could not open device for reading. Try a reboot.') + + self._card_prefix = None + cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__) + + for dev in cards: + try: + self._card_prefix = conditional_mount(dev)+os.sep + break + except: + import traceback + print traceback + continue + + def open(self): + time.sleep(5) + self._main_prefix = self._card_prefix = None + if islinux: + try: + self.open_linux() + except DeviceError: + time.sleep(3) + self.open_linux() + if iswindows: + try: + self.open_windows() + except DeviceError: + time.sleep(3) + self.open_windows() + if isosx: + try: + self.open_osx() + except DeviceError: + time.sleep(3) + self.open_osx() + diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py new file mode 100644 index 0000000000..88e1cce6a2 --- /dev/null +++ b/src/calibre/devices/usbms/driver.py @@ -0,0 +1,144 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember self.free_space()[2] - 1024*1024: + raise FreeSpaceError("There is insufficient free space "+\ + "on the storage card") + if not on_card and size > self.free_space()[0] - 2*1024*1024: + raise FreeSpaceError("There is insufficient free space " +\ + "in main memory") + + paths = [] + names = iter(names) + + for infile in files: + filepath = os.path.join(path, names.next()) + paths.append(filepath) + + shutil.copy2(infile, filepath) + + return zip(paths, cycle([on_card])) + + @classmethod + def add_books_to_metadata(cls, locations, metadata, booklists): + for location in locations: + path = location[0] + on_card = 1 if location[1] else 0 + + title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path)) + booklists[on_card].append(Book(path, title, author, mime)) + + def delete_books(self, paths, end_session=True): + for path in paths: + if os.path.exists(path): + # Delete the ebook + os.unlink(path) + + @classmethod + def remove_books_from_metadata(cls, paths, booklists): + for path in paths: + for bl in booklists: + for book in bl: + if path.endswith(book.path): + bl.remove(book) + break + + def sync_booklists(self, booklists, end_session=True): + # There is no meta data on the device to update. The device is treated + # as a mass storage device and does not use a meta data xml file like + # the Sony Readers. + pass + + def get_file(self, path, outfile, end_session=True): + path = self.munge_path(path) + src = open(path, 'rb') + shutil.copyfileobj(src, outfile, 10*1024*1024) + + def munge_path(self, path): + if path.startswith('/') and not (path.startswith(self._main_prefix) or \ + (self._card_prefix and path.startswith(self._card_prefix))): + path = self._main_prefix + path[1:] + elif path.startswith('card:'): + path = path.replace('card:', self._card_prefix[:-1]) + return path + + @classmethod + def extract_book_metadata_by_filename(cls, filename): + book_title = '' + book_author = '' + book_mime = '' + # Calibre uses a specific format for file names. They take the form + # title_-_author_number.extention We want to see if the file name is + # in this format. + if fnmatch.fnmatchcase(filename, '*_-_*.*'): + # Get the title and author from the file name + title, sep, author = filename.rpartition('_-_') + author, sep, ext = author.rpartition('_') + book_title = title.replace('_', ' ') + book_author = author.replace('_', ' ') + # if the filename did not match just set the title to + # the filename without the extension + else: + book_title = os.path.splitext(filename)[0].replace('_', ' ') + + fileext = os.path.splitext(filename)[1] + if fileext in cls.MIME_MAP.keys(): + book_mime = cls.MIME_MAP[fileext] + + return book_title, book_author, book_mime +