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.
|
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
|
The public interface for device backends is defined in libprs500.device.
|
||||||
>>> from libprs500.communicate import PRS500Device
|
|
||||||
>>> dev = PRS500Device()
|
|
||||||
>>> dev.get_device_information()
|
|
||||||
('Sony Reader', 'PRS-500/U', '1.0.00.21081', 'application/x-bbeb-book')
|
|
||||||
|
|
||||||
There is also a script L{prs500} that provides a command-line interface to
|
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.
|
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
|
The packet structure used by the SONY Reader USB protocol is defined
|
||||||
|
@ -22,7 +22,7 @@ import StringIO, sys, time, os
|
|||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
from libprs500 import __version__ as VERSION
|
from libprs500 import __version__ as VERSION
|
||||||
from libprs500.communicate import PRS500Device
|
from libprs500.prs500 import PRS500
|
||||||
from terminfo import TerminalController
|
from terminfo import TerminalController
|
||||||
from libprs500.errors import ArgumentError, DeviceError, DeviceLocked
|
from libprs500.errors import ArgumentError, DeviceError, DeviceLocked
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ def main():
|
|||||||
|
|
||||||
command = args[0]
|
command = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
dev = PRS500Device(key=options.key, log_packets=options.log_packets)
|
dev = PRS500(key=options.key, log_packets=options.log_packets)
|
||||||
try:
|
try:
|
||||||
if command == "df":
|
if command == "df":
|
||||||
total = dev.total_space(end_session=False)
|
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
|
QMessageBox, QFileDialog, QIcon, QDialog, QInputDialog
|
||||||
from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
|
from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
|
||||||
|
|
||||||
from libprs500.communicate import PRS500Device as device
|
from libprs500.prs500 import PRS500 as device
|
||||||
from libprs500.books import fix_ids
|
|
||||||
from libprs500.errors import *
|
from libprs500.errors import *
|
||||||
from libprs500.gui import import_ui, installErrorHandler, Error, _Warning, \
|
from libprs500.gui import import_ui, installErrorHandler, Error, _Warning, \
|
||||||
extension, APP_TITLE
|
extension, APP_TITLE
|
||||||
@ -165,17 +164,10 @@ class Main(QObject, Ui_MainWindow):
|
|||||||
self.library_model.delete(self.library_view.selectionModel()\
|
self.library_model.delete(self.library_view.selectionModel()\
|
||||||
.selectedRows())
|
.selectedRows())
|
||||||
else:
|
else:
|
||||||
self.status("Deleting files from device")
|
self.status("Deleting books and updating metadata on device")
|
||||||
paths = self.device_view.model().delete(rows)
|
paths = self.device_view.model().delete(rows)
|
||||||
for path in paths:
|
self.dev.remove_book(paths, (self.reader_model.booklist, \
|
||||||
self.status("Deleting "+path[path.rfind("/")+1:])
|
self.card_model.booklist), end_session=False)
|
||||||
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.update_availabe_space()
|
self.update_availabe_space()
|
||||||
self.model_modified()
|
self.model_modified()
|
||||||
self.show_book(self.current_view.currentIndex(), QModelIndex())
|
self.show_book(self.current_view.currentIndex(), QModelIndex())
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
"""
|
"""
|
||||||
Contains the logic for communication with the device (a SONY PRS-500).
|
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.
|
methods for performing various tasks.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
@ -53,6 +53,7 @@ from tempfile import TemporaryFile
|
|||||||
from array import array
|
from array import array
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
from libprs500.device import Device
|
||||||
from libprs500.libusb import Error as USBError
|
from libprs500.libusb import Error as USBError
|
||||||
from libprs500.libusb import get_device_by_id
|
from libprs500.libusb import get_device_by_id
|
||||||
from libprs500.prstypes import *
|
from libprs500.prstypes import *
|
||||||
@ -60,38 +61,9 @@ from libprs500.errors import *
|
|||||||
from libprs500.books import BookList, fix_ids
|
from libprs500.books import BookList, fix_ids
|
||||||
from libprs500 import __author__ as AUTHOR
|
from libprs500 import __author__ as AUTHOR
|
||||||
|
|
||||||
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
|
||||||
|
|
||||||
# Protocol versions libprs500 has been tested with
|
# Protocol versions libprs500 has been tested with
|
||||||
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L]
|
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):
|
class File(object):
|
||||||
"""
|
"""
|
||||||
@ -117,10 +89,10 @@ class File(object):
|
|||||||
return self.name
|
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.
|
Each method decorated by C{safe} performs a task.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -138,6 +110,31 @@ class PRS500Device(Device):
|
|||||||
# Height for thumbnails of books/images on the device
|
# 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
|
@classmethod
|
||||||
def signature(cls):
|
def signature(cls):
|
||||||
""" Return a two element tuple (vendor id, product id) """
|
""" Return a two element tuple (vendor id, product id) """
|
||||||
@ -202,7 +199,6 @@ class PRS500Device(Device):
|
|||||||
If it is called with -1 that means that the
|
If it is called with -1 that means that the
|
||||||
task does not have any progress information
|
task does not have any progress information
|
||||||
"""
|
"""
|
||||||
Device.__init__(self)
|
|
||||||
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||||
# Handle that is used to communicate with device. Setup in L{open}
|
# Handle that is used to communicate with device. Setup in L{open}
|
||||||
self.handle = None
|
self.handle = None
|
||||||
@ -237,8 +233,6 @@ class PRS500Device(Device):
|
|||||||
Requires write privileges to the device file.
|
Requires write privileges to the device file.
|
||||||
Also initialize the device.
|
Also initialize the device.
|
||||||
See the source code for the sequenceof initialization commands.
|
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)
|
self.device = get_device_by_id(self.VENDOR_ID, self.PRODUCT_ID)
|
||||||
if not self.device:
|
if not self.device:
|
||||||
@ -790,6 +784,19 @@ class PRS500Device(Device):
|
|||||||
self.get_file(self.MEDIA_XML, tfile, end_session=False)
|
self.get_file(self.MEDIA_XML, tfile, end_session=False)
|
||||||
return BookList(prefix=prefix, root=root, sfile=tfile)
|
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
|
@safe
|
||||||
def add_book(self, infile, name, info, booklists, oncard=False, \
|
def add_book(self, infile, name, info, booklists, oncard=False, \
|
||||||
sync_booklists=False, end_session=True):
|
sync_booklists=False, end_session=True):
|
Loading…
x
Reference in New Issue
Block a user