diff --git a/src/calibre/devices/__init__.py b/src/calibre/devices/__init__.py index f2c89ea1c5..f300c666b9 100644 --- a/src/calibre/devices/__init__.py +++ b/src/calibre/devices/__init__.py @@ -1,5 +1,6 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' + ''' Device drivers. ''' @@ -8,8 +9,9 @@ def devices(): from calibre.devices.prs500.driver import PRS500 from calibre.devices.prs505.driver import PRS505 from calibre.devices.prs700.driver import PRS700 + from calibre.devices.cybookg3.driver import CYBOOKG3 #from calibre.devices.kindle.driver import KINDLE - return (PRS500, PRS505, PRS700) + return (PRS500, PRS505, PRS700, CYBOOKG3) import time diff --git a/src/calibre/devices/cybookg3/__init__.py b/src/calibre/devices/cybookg3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/devices/cybookg3/books.py b/src/calibre/devices/cybookg3/books.py new file mode 100644 index 0000000000..5c15919ea6 --- /dev/null +++ b/src/calibre/devices/cybookg3/books.py @@ -0,0 +1,61 @@ +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal ' + +''' +''' +import os, fnmatch, time + +from calibre.devices.interface import BookList as _BookList + +EBOOK_DIR = "eBooks" +EBOOK_TYPES = ['mobi', 'prc', 'pdf', 'txt'] + +class Book(object): + def __init__(self, path, title, authors): + self.title = title + self.authors = authors + self.size = os.path.getsize(path) + self.datetime = time.gmtime(os.path.getctime(path)) + self.path = path + self.thumbnail = None + self.tags = [] + + @apply + def thumbnail(): + return 0 + + 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 __init__(self, mountpath): + self._mountpath = mountpath + _BookList.__init__(self) + self.return_books(mountpath) + + def return_books(self, mountpath): + books = []; + for path, dirs, files in os.walk(os.path.join(mountpath, EBOOK_DIR)): + for book_type in EBOOK_TYPES: + for filename in fnmatch.filter(files, '*.%s' % (book_type)): + self.append(Book(os.path.join(path, filename), filename, "")) + + def add_book(self, path, title): + self.append(Book(path, title, "")) + + def remove_book(self, path): + for book in self: + if path.endswith(book.path): + self.remove(book) + break + + def supports_tags(self): + ''' Return True if the the device supports tags (collections) for this book list. ''' + return False + + def set_tags(self, book, tags): + pass + diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py new file mode 100644 index 0000000000..b397192cfe --- /dev/null +++ b/src/calibre/devices/cybookg3/driver.py @@ -0,0 +1,291 @@ +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal ' + +''' +Device driver for Bookeen's Cybook Gen 3 +''' +import os, fnmatch, shutil, time +from itertools import cycle + +from calibre.devices.interface import Device +from calibre.devices.errors import DeviceError, FreeSpaceError + +from calibre.devices.cybookg3.books import BookList, EBOOK_DIR +from calibre import iswindows, islinux, isosx, __appname__ + +class CYBOOKG3(Device): + # Ordered list of supported formats + FORMATS = ["mobi", "prc", "pdf", "txt"] + VENDOR_ID = 0x0bda + PRODUCT_ID = 0x0703 + BCD = 0x110 + #THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device + + MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory' + STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card' + + FDI_TEMPLATE = \ +''' + + + + + + + %(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 get_device_information(self, end_session=True): + """ + Ask device for device information. See L{DeviceInfoQuery}. + @return: (device name, device version, software version on device, mime type) + """ + return (self.__class__.__name__, '', '', '') + + 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) + + def books(self, oncard=False, end_session=True): + if oncard and self._card_prefix is None: + return [] + prefix = self._card_prefix if oncard else self._main_prefix + bl = BookList(prefix) + return bl + + def upload_books(self, files, names, on_card=False, end_session=True): + if on_card and not self._card_prefix: + raise ValueError(_('The reader has no storage card connected.')) + + if not on_card: + path = os.path.join(self._main_prefix, EBOOK_DIR) + else: + path = os.path.join(self._card_prefix, EBOOK_DIR) + + sizes = map(os.path.getsize, files) + size = sum(sizes) + + if on_card and size > 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 + booklists[on_card].add_book(path, os.path.basename(path)) + + def delete_books(self, paths, end_session=True): + for path in paths: + if os.path.exists(path): + # Delete the ebook + os.unlink(path) + + filepath, ext = os.path.splitext(path) + basepath, filename = os.path.split(filepath) + + # Delete the ebook auxiliary file + if os.path.exists(filepath + '.mbp'): + os.unlink(filepath + '.mbp') + + # Delete the thumbnails file auto generated for the ebook + for p, d, files in os.walk(basepath): + for filen in fnmatch.filter(files, filename + "*.t2b"): + os.unlink(os.path.join(p, filen)) + + @classmethod + def remove_books_from_metadata(cls, paths, booklists): + for path in paths: + for bl in booklists: + bl.remove_book(path) + + 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 + + def open_windows(self): + raise NotImplementedError() + 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/trac/donations/server.py b/src/calibre/trac/donations/server.py index 18771d6fdf..163851a4f1 100644 --- a/src/calibre/trac/donations/server.py +++ b/src/calibre/trac/donations/server.py @@ -21,7 +21,7 @@ from lxml import etree def range_for_month(year, month): ty, tm = date.today().year, date.today().month - min = date(year=year, month=month, day=1) + min = max = date(year=year, month=month, day=1) x = date.today().day if ty == year and tm == month else 31 while x > 1: try: