mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Defined the minimum interface that a device backend must satisfy to be used in the GUI. See device.py.
This commit is contained in:
parent
ecaa9e813b
commit
011fc40780
@ -15,14 +15,10 @@
|
||||
"""
|
||||
This package provides an interface to the SONY Reader PRS-500 over USB.
|
||||
|
||||
The public interface of libprs500 is in L{libprs500.communicate}. To use it
|
||||
>>> from libprs500.communicate import PRS500Device
|
||||
>>> dev = PRS500Device()
|
||||
>>> dev.get_device_information()
|
||||
('Sony Reader', 'PRS-500/U', '1.0.00.21081', 'application/x-bbeb-book')
|
||||
The public interface for device backends is defined in libprs500.device.
|
||||
|
||||
There is also a script L{prs500} that provides a command-line interface to
|
||||
libprs500. See the script
|
||||
the SONY Reader. See the script
|
||||
for more usage examples. A GUI is available via the command prs500-gui.
|
||||
|
||||
The packet structure used by the SONY Reader USB protocol is defined
|
||||
|
@ -47,16 +47,16 @@ class book_metadata_field(object):
|
||||
|
||||
class Book(object):
|
||||
""" Provides a view onto the XML element that represents a book """
|
||||
title = book_metadata_field("title")
|
||||
author = book_metadata_field("author", \
|
||||
title = book_metadata_field("title")
|
||||
author = book_metadata_field("author", \
|
||||
formatter=lambda x: x if x.strip() else "Unknown")
|
||||
mime = book_metadata_field("mime")
|
||||
rpath = book_metadata_field("path")
|
||||
id = book_metadata_field("id", formatter=int)
|
||||
sourceid = book_metadata_field("sourceid", formatter=int)
|
||||
size = book_metadata_field("size", formatter=int)
|
||||
mime = book_metadata_field("mime")
|
||||
rpath = book_metadata_field("path")
|
||||
id = book_metadata_field("id", formatter=int)
|
||||
sourceid = book_metadata_field("sourceid", formatter=int)
|
||||
size = book_metadata_field("size", formatter=int)
|
||||
# When setting this attribute you must use an epoch
|
||||
datetime = book_metadata_field("date", \
|
||||
datetime = book_metadata_field("date", \
|
||||
formatter=lambda x: time.strptime(x, "%a, %d %b %Y %H:%M:%S %Z"),
|
||||
setter=lambda x: time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(x)))
|
||||
|
||||
|
@ -22,7 +22,7 @@ import StringIO, sys, time, os
|
||||
from optparse import OptionParser
|
||||
|
||||
from libprs500 import __version__ as VERSION
|
||||
from libprs500.communicate import PRS500Device
|
||||
from libprs500.prs500 import PRS500
|
||||
from terminfo import TerminalController
|
||||
from libprs500.errors import ArgumentError, DeviceError, DeviceLocked
|
||||
|
||||
@ -197,7 +197,7 @@ def main():
|
||||
|
||||
command = args[0]
|
||||
args = args[1:]
|
||||
dev = PRS500Device(key=options.key, log_packets=options.log_packets)
|
||||
dev = PRS500(key=options.key, log_packets=options.log_packets)
|
||||
try:
|
||||
if command == "df":
|
||||
total = dev.total_space(end_session=False)
|
||||
|
114
src/libprs500/device.py
Normal file
114
src/libprs500/device.py
Normal file
@ -0,0 +1,114 @@
|
||||
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""
|
||||
Define the minimum interface that a device backend must satisfy to be used in
|
||||
the GUI. A device backend must subclass the L{Device} class. See prs500.py for
|
||||
a backend that implement the Device interface for the SONY PRS500 Reader.
|
||||
"""
|
||||
|
||||
class Device(object):
|
||||
"""
|
||||
Defines the interface that should be implemented by backends that
|
||||
communicate with an ebook reader.
|
||||
|
||||
The C{end_session} variables are used for USB session management. Sometimes
|
||||
the front-end needs to call several methods one after another, in which case
|
||||
the USB session should not be closed after each method call.
|
||||
"""
|
||||
# Ordered list of supported formats
|
||||
FORMATS = ["lrf", "rtf", "pdf", "txt"]
|
||||
VENDOR_ID = 0x0000
|
||||
PRODICT_ID = 0x0000
|
||||
|
||||
def __init__(self, key='-1', log_packets=False, report_progress=None) :
|
||||
"""
|
||||
@param key: The key to unlock the device
|
||||
@param log_packets: If true the packet stream to/from the device is logged
|
||||
@param report_progress: Function that is called with a % progress
|
||||
(number between 0 and 100) for various tasks
|
||||
If it is called with -1 that means that the
|
||||
task does not have any progress information
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
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)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def total_space(self, end_session=True):
|
||||
"""
|
||||
Get total space available on the mountpoints:
|
||||
1. Main memory
|
||||
2. Memory Stick
|
||||
3. SD Card
|
||||
|
||||
@return: A 3 element list with total space in bytes of (1, 2, 3). If a
|
||||
particular device doesn't have any of these locations it should return 0.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def free_space(self, end_session=True):
|
||||
"""
|
||||
Get free space available on the mountpoints:
|
||||
1. Main memory
|
||||
2. Memory Stick
|
||||
3. SD Card
|
||||
|
||||
@return: A 3 element list with free space in bytes of (1, 2, 3). If a
|
||||
particular device doesn't have any of these locations it should return 0.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def books(self, oncard=False, end_session=True):
|
||||
"""
|
||||
Return a list of ebooks on the device.
|
||||
@param oncard: If True return a list of ebooks on the storage card,
|
||||
otherwise return list of ebooks in main memory of device
|
||||
|
||||
@return: A list of Books. Each Book object must have the fields:
|
||||
title, author, size, datetime (a time tuple), path, thumbnail (can be None).
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_book(self, infile, name, info, booklists, oncard=False, \
|
||||
sync_booklists=False, end_session=True):
|
||||
"""
|
||||
Add a book to the device. If oncard is True then the book is copied
|
||||
to the card rather than main memory.
|
||||
|
||||
@param infile: The source file, should be opened in "rb" mode
|
||||
@param name: The name of the book file when uploaded to the
|
||||
device. The extension of name must be one of
|
||||
the supported formats for this device.
|
||||
@param info: A dictionary that must have the keys "title", "authors", "cover".
|
||||
C{info["cover"]} should be a three element tuple (width, height, data)
|
||||
where data is the image data in JPEG format as a string
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_book(self, paths, booklists, end_session=True):
|
||||
"""
|
||||
Remove the books specified by C{paths} from the device. The metadata
|
||||
cache on the device must also be updated.
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
"""
|
||||
raise NotImplementedError()
|
@ -26,8 +26,7 @@ from PyQt4.QtGui import QPixmap, QErrorMessage, QLineEdit, \
|
||||
QMessageBox, QFileDialog, QIcon, QDialog, QInputDialog
|
||||
from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
|
||||
|
||||
from libprs500.communicate import PRS500Device as device
|
||||
from libprs500.books import fix_ids
|
||||
from libprs500.prs500 import PRS500 as device
|
||||
from libprs500.errors import *
|
||||
from libprs500.gui import import_ui, installErrorHandler, Error, _Warning, \
|
||||
extension, APP_TITLE
|
||||
@ -165,17 +164,10 @@ class Main(QObject, Ui_MainWindow):
|
||||
self.library_model.delete(self.library_view.selectionModel()\
|
||||
.selectedRows())
|
||||
else:
|
||||
self.status("Deleting files from device")
|
||||
self.status("Deleting books and updating metadata on device")
|
||||
paths = self.device_view.model().delete(rows)
|
||||
for path in paths:
|
||||
self.status("Deleting "+path[path.rfind("/")+1:])
|
||||
self.dev.del_file(path, end_session=False)
|
||||
fix_ids(self.reader_model.booklist, self.card_model.booklist)
|
||||
self.status("Syncing media list to reader")
|
||||
self.dev.upload_book_list(self.reader_model.booklist)
|
||||
if len(self.card_model.booklist):
|
||||
self.status("Syncing media list to card")
|
||||
self.dev.upload_book_list(self.card_model.booklist)
|
||||
self.dev.remove_book(paths, (self.reader_model.booklist, \
|
||||
self.card_model.booklist), end_session=False)
|
||||
self.update_availabe_space()
|
||||
self.model_modified()
|
||||
self.show_book(self.current_view.currentIndex(), QModelIndex())
|
||||
|
@ -43,7 +43,7 @@
|
||||
"""
|
||||
Contains the logic for communication with the device (a SONY PRS-500).
|
||||
|
||||
The public interface of class L{PRS500Device} defines the
|
||||
The public interface of class L{PRS500} defines the
|
||||
methods for performing various tasks.
|
||||
"""
|
||||
import sys
|
||||
@ -53,6 +53,7 @@ from tempfile import TemporaryFile
|
||||
from array import array
|
||||
from functools import wraps
|
||||
|
||||
from libprs500.device import Device
|
||||
from libprs500.libusb import Error as USBError
|
||||
from libprs500.libusb import get_device_by_id
|
||||
from libprs500.prstypes import *
|
||||
@ -60,38 +61,9 @@ from libprs500.errors import *
|
||||
from libprs500.books import BookList, fix_ids
|
||||
from libprs500 import __author__ as AUTHOR
|
||||
|
||||
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
||||
|
||||
# Protocol versions libprs500 has been tested with
|
||||
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L]
|
||||
|
||||
class Device(object):
|
||||
""" Contains device independent methods """
|
||||
_packet_number = 0 #: Keep track of the packet number for packet tracing
|
||||
|
||||
def log_packet(self, packet, header, stream=sys.stderr):
|
||||
"""
|
||||
Log C{packet} to stream C{stream}.
|
||||
Header should be a small word describing the type of packet.
|
||||
"""
|
||||
self._packet_number += 1
|
||||
print >> stream, str(self._packet_number), header, "Type:", \
|
||||
packet.__class__.__name__
|
||||
print >> stream, packet
|
||||
print >> stream, "--"
|
||||
|
||||
@classmethod
|
||||
def validate_response(cls, res, _type=0x00, number=0x00):
|
||||
"""
|
||||
Raise a ProtocolError if the type and number of C{res}
|
||||
is not the same as C{type} and C{number}.
|
||||
"""
|
||||
if _type != res.type or number != res.rnumber:
|
||||
raise ProtocolError("Inavlid response.\ntype: expected=" + \
|
||||
hex(_type)+" actual=" + hex(res.type) + \
|
||||
"\nrnumber: expected=" + hex(number) + \
|
||||
" actual="+hex(res.rnumber))
|
||||
|
||||
|
||||
class File(object):
|
||||
"""
|
||||
@ -117,14 +89,14 @@ class File(object):
|
||||
return self.name
|
||||
|
||||
|
||||
class PRS500Device(Device):
|
||||
class PRS500(Device):
|
||||
|
||||
"""
|
||||
Contains the logic for performing various tasks on the reader.
|
||||
Implements the backend for communication with the SONY Reader.
|
||||
Each method decorated by C{safe} performs a task.
|
||||
"""
|
||||
|
||||
VENDOR_ID = 0x054c #: SONY Vendor Id
|
||||
VENDOR_ID = 0x054c #: SONY Vendor Id
|
||||
PRODUCT_ID = 0x029b #: Product Id for the PRS-500
|
||||
INTERFACE_ID = 0 #: The interface we use to talk to the device
|
||||
BULK_IN_EP = 0x81 #: Endpoint for Bulk reads
|
||||
@ -136,7 +108,32 @@ class PRS500Device(Device):
|
||||
# Ordered list of supported formats
|
||||
FORMATS = ["lrf", "rtf", "pdf", "txt"]
|
||||
# Height for thumbnails of books/images on the device
|
||||
THUMBNAIL_HEIGHT = 68
|
||||
THUMBNAIL_HEIGHT = 68
|
||||
|
||||
_packet_number = 0 #: Keep track of the packet number for packet tracing
|
||||
|
||||
def log_packet(self, packet, header, stream=sys.stderr):
|
||||
"""
|
||||
Log C{packet} to stream C{stream}.
|
||||
Header should be a small word describing the type of packet.
|
||||
"""
|
||||
self._packet_number += 1
|
||||
print >> stream, str(self._packet_number), header, "Type:", \
|
||||
packet.__class__.__name__
|
||||
print >> stream, packet
|
||||
print >> stream, "--"
|
||||
|
||||
@classmethod
|
||||
def validate_response(cls, res, _type=0x00, number=0x00):
|
||||
"""
|
||||
Raise a ProtocolError if the type and number of C{res}
|
||||
is not the same as C{type} and C{number}.
|
||||
"""
|
||||
if _type != res.type or number != res.rnumber:
|
||||
raise ProtocolError("Inavlid response.\ntype: expected=" + \
|
||||
hex(_type)+" actual=" + hex(res.type) + \
|
||||
"\nrnumber: expected=" + hex(number) + \
|
||||
" actual="+hex(res.rnumber))
|
||||
|
||||
@classmethod
|
||||
def signature(cls):
|
||||
@ -202,7 +199,6 @@ class PRS500Device(Device):
|
||||
If it is called with -1 that means that the
|
||||
task does not have any progress information
|
||||
"""
|
||||
Device.__init__(self)
|
||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
# Handle that is used to communicate with device. Setup in L{open}
|
||||
self.handle = None
|
||||
@ -237,8 +233,6 @@ class PRS500Device(Device):
|
||||
Requires write privileges to the device file.
|
||||
Also initialize the device.
|
||||
See the source code for the sequenceof initialization commands.
|
||||
|
||||
@todo: Implement unlocking of the device
|
||||
"""
|
||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||
if not self.device:
|
||||
@ -790,6 +784,19 @@ class PRS500Device(Device):
|
||||
self.get_file(self.MEDIA_XML, tfile, end_session=False)
|
||||
return BookList(prefix=prefix, root=root, sfile=tfile)
|
||||
|
||||
@safe
|
||||
def remove_book(self, paths, booklists, end_session=True):
|
||||
"""
|
||||
Remove the books specified by paths from the device. The metadata
|
||||
cache on the device should also be updated.
|
||||
"""
|
||||
for path in paths:
|
||||
self.del_file(path, end_session=False)
|
||||
fix_ids(booklists[0], booklists[1])
|
||||
self.upload_book_list(booklists[0], end_session=False)
|
||||
if len(booklists[1]):
|
||||
self.upload_book_list(booklists[1], end_session=False)
|
||||
|
||||
@safe
|
||||
def add_book(self, infile, name, info, booklists, oncard=False, \
|
||||
sync_booklists=False, end_session=True):
|
Loading…
x
Reference in New Issue
Block a user