mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-03 19:17:02 -05:00 
			
		
		
		
	GUI now supports displaying lists of books from both the library and the device
This commit is contained in:
		
							parent
							
								
									1ec8632924
								
							
						
					
					
						commit
						c8d21c5930
					
				@ -45,12 +45,12 @@ Contains the logic for communication with the device (a SONY PRS-500).
 | 
				
			|||||||
The public interface of class L{PRS500Device} defines the methods for performing various tasks. 
 | 
					The public interface of class L{PRS500Device} defines the methods for performing various tasks. 
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
import usb, sys, os, time
 | 
					import usb, sys, os, time
 | 
				
			||||||
 | 
					from base64 import b64decode as decode
 | 
				
			||||||
 | 
					from tempfile import TemporaryFile
 | 
				
			||||||
from array import array
 | 
					from array import array
 | 
				
			||||||
from xml.sax.handler import ContentHandler
 | 
					from xml.sax.handler import ContentHandler
 | 
				
			||||||
from xml.sax import make_parser
 | 
					from xml.sax import make_parser
 | 
				
			||||||
from xml.sax.handler import feature_namespaces
 | 
					from xml.sax.handler import feature_namespaces
 | 
				
			||||||
from base64 import b64decode as decode
 | 
					 | 
				
			||||||
from tempfile import TemporaryFile
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from prstypes import *
 | 
					from prstypes import *
 | 
				
			||||||
from errors import *
 | 
					from errors import *
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										91
									
								
								libprs500/gui/database.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								libprs500/gui/database.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					##    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.
 | 
				
			||||||
 | 
					import sqlite3 as sqlite
 | 
				
			||||||
 | 
					import os, os.path, zlib
 | 
				
			||||||
 | 
					from stat import ST_SIZE
 | 
				
			||||||
 | 
					from libprs500.lrf.meta import LRFMetaFile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LibraryDatabase(object):
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  BOOKS_SQL = """
 | 
				
			||||||
 | 
					                           create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT, authors TEXT, publisher TEXT, size INTEGER,  tags TEXT,
 | 
				
			||||||
 | 
					                                                                                       cover BLOB, date DATE DEFAULT CURRENT_TIMESTAMP );
 | 
				
			||||||
 | 
					                           create table if not exists books_data(id INTEGER, extension TEXT, data BLOB);
 | 
				
			||||||
 | 
					                           """
 | 
				
			||||||
 | 
					                              
 | 
				
			||||||
 | 
					  def __init__(self, dbpath):    
 | 
				
			||||||
 | 
					    self.con = sqlite.connect(dbpath)
 | 
				
			||||||
 | 
					    self.con.row_factory = sqlite.Row # Allow case insensitive field access by name
 | 
				
			||||||
 | 
					    self.con.executescript(LibraryDatabase.BOOKS_SQL)
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					  def get_cover(self, id):
 | 
				
			||||||
 | 
					    raw = self.con.execute("select cover from books_meta where id=?", (id,)).next()["cover"]
 | 
				
			||||||
 | 
					    return zlib.decompress(str(raw)) if raw else None
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  def get_extensions(self, id):
 | 
				
			||||||
 | 
					    exts = []
 | 
				
			||||||
 | 
					    cur = self.con.execute("select extension from books_data where id=?", (id,))
 | 
				
			||||||
 | 
					    for row in cur:
 | 
				
			||||||
 | 
					      exts.append(row["extension"])
 | 
				
			||||||
 | 
					    return exts
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def add_book(self, path):
 | 
				
			||||||
 | 
					    file = os.path.abspath(path)
 | 
				
			||||||
 | 
					    title, author, publisher, size, cover = os.path.basename(file), None, None, os.stat(file)[ST_SIZE], None
 | 
				
			||||||
 | 
					    ext = title[title.rfind(".")+1:].lower() if title.find(".") > -1 else None
 | 
				
			||||||
 | 
					    if ext == "lrf":
 | 
				
			||||||
 | 
					      lrf = LRFMetaFile(open(file, "r+b"))
 | 
				
			||||||
 | 
					      title, author, cover, publisher = lrf.title, lrf.author.strip(), lrf.thumbnail, lrf.publisher.strip()
 | 
				
			||||||
 | 
					      if "unknown" in publisher.lower(): publisher = None
 | 
				
			||||||
 | 
					      if "unknown" in author.lower(): author = None
 | 
				
			||||||
 | 
					    file = zlib.compress(open(file).read())
 | 
				
			||||||
 | 
					    if cover: cover = sqlite.Binary(zlib.compress(cover))
 | 
				
			||||||
 | 
					    self.con.execute("insert into books_meta (title, authors, publisher, size, tags, cover) values (?,?,?,?,?,?)", (title, author, publisher, size, None, cover))    
 | 
				
			||||||
 | 
					    id =  self.con.execute("select max(id) from books_meta").next()[0]    
 | 
				
			||||||
 | 
					    self.con.execute("insert into books_data values (?,?,?)", (id, ext, sqlite.Binary(file)))
 | 
				
			||||||
 | 
					    self.con.commit()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  def get_table(self, columns):
 | 
				
			||||||
 | 
					    cols = ",".join([ c for c in columns])
 | 
				
			||||||
 | 
					    cur = self.con.execute("select " + cols + " from books_meta")
 | 
				
			||||||
 | 
					    rows = []
 | 
				
			||||||
 | 
					    for row in cur:
 | 
				
			||||||
 | 
					      r = {}
 | 
				
			||||||
 | 
					      for c in columns: r[c] = row[c]
 | 
				
			||||||
 | 
					      rows.append(r)
 | 
				
			||||||
 | 
					    return rows
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def get_meta_data(self, id):
 | 
				
			||||||
 | 
					    try: row = self.con.execute("select * from books_meta where id=?", (id,)).next()
 | 
				
			||||||
 | 
					    except StopIteration: return None
 | 
				
			||||||
 | 
					    data = {}
 | 
				
			||||||
 | 
					    for field in ("id", "title", "authors", "publisher", "size", "tags", "cover", "date"):
 | 
				
			||||||
 | 
					      data[field] = row[field]
 | 
				
			||||||
 | 
					    return data
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def search(self, query): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					  lbm = LibraryDatabase("/home/kovid/library.sqlite")
 | 
				
			||||||
 | 
					#  lbm.add_book("/home/kovid/documents/ebooks/hobbfar01.lrf")
 | 
				
			||||||
 | 
					#  lbm.add_book("/home/kovid/documents/ebooks/hobbfar02.lrf")
 | 
				
			||||||
 | 
					#  lbm.add_book("/home/kovid/documents/ebooks/hobbfar03.lrf")
 | 
				
			||||||
 | 
					#  lbm.add_book("/home/kovid/documents/ebooks/hobblive01.lrf")
 | 
				
			||||||
 | 
					#  lbm.add_book("/home/kovid/documents/ebooks/hobbtawny01.lrf")
 | 
				
			||||||
 | 
					#  lbm.add_book("/home/kovid/documents/ebooks/hobbtawny02.lrf")
 | 
				
			||||||
 | 
					#  lbm.add_book("/home/kovid/documents/ebooks/hobbtawny03.lrf")
 | 
				
			||||||
 | 
					  print lbm.get_table(["id","title"])
 | 
				
			||||||
							
								
								
									
										1581
									
								
								libprs500/gui/images.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1581
									
								
								libprs500/gui/images.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -14,15 +14,24 @@
 | 
				
			|||||||
##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
					##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
				
			||||||
from libprs500.communicate import PRS500Device as device
 | 
					from libprs500.communicate import PRS500Device as device
 | 
				
			||||||
from libprs500.errors import *
 | 
					from libprs500.errors import *
 | 
				
			||||||
 | 
					from libprs500.lrf.meta import LRFMetaFile, LRFException
 | 
				
			||||||
 | 
					from database import LibraryDatabase
 | 
				
			||||||
import images
 | 
					import images
 | 
				
			||||||
from PyQt4.QtCore import Qt, SIGNAL
 | 
					from PyQt4.QtCore import Qt, SIGNAL
 | 
				
			||||||
from PyQt4.Qt import QObject, QThread, QCoreApplication, QEventLoop, QString, QStandardItem, QStandardItemModel, QStatusBar, QVariant, QAbstractTableModel, \
 | 
					from PyQt4.Qt import QObject, QThread, QCoreApplication, QEventLoop, QString, QStandardItem, QStandardItemModel, QStatusBar, QVariant, QAbstractTableModel, \
 | 
				
			||||||
                                   QAbstractItemView, QImage, QPixmap, QIcon, QSize
 | 
					                                   QAbstractItemView, QImage, QPixmap, QIcon, QSize, QMessageBox, QSettings, QFileDialog, QErrorMessage
 | 
				
			||||||
from PyQt4 import uic
 | 
					from PyQt4 import uic
 | 
				
			||||||
import sys, pkg_resources, re, string
 | 
					import sys, pkg_resources, re, string, time, os, os.path, traceback, textwrap, zlib
 | 
				
			||||||
 | 
					from stat import ST_SIZE
 | 
				
			||||||
 | 
					from tempfile import TemporaryFile, NamedTemporaryFile
 | 
				
			||||||
 | 
					from exceptions import Exception as Exception
 | 
				
			||||||
 | 
					import xml.dom.minidom as dom
 | 
				
			||||||
 | 
					from xml.dom.ext import PrettyPrint as PrettyPrint
 | 
				
			||||||
from operator import itemgetter
 | 
					from operator import itemgetter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NONE = QVariant()
 | 
					NONE = QVariant()
 | 
				
			||||||
 | 
					TIME_WRITE_FMT  = "%d %b %Y"
 | 
				
			||||||
 | 
					COVER_HEIGHT = 80
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def human_readable(size):
 | 
					def human_readable(size):
 | 
				
			||||||
  """ Convert a size in bytes into a human readle form """
 | 
					  """ Convert a size in bytes into a human readle form """
 | 
				
			||||||
@ -34,12 +43,122 @@ def human_readable(size):
 | 
				
			|||||||
  if size.find(".") > -1: size = size[:size.find(".")+2]
 | 
					  if size.find(".") > -1: size = size[:size.find(".")+2]
 | 
				
			||||||
  return size + " " + suffix
 | 
					  return size + " " + suffix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DeviceBooksModel(QAbstractTableModel):
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def wrap(s, width=20):
 | 
				
			||||||
 | 
					  return textwrap.fill(str(s), width) 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LibraryBooksModel(QAbstractTableModel):
 | 
				
			||||||
 | 
					  FIELDS = ["id", "title", "authors", "size", "date", "publisher", "tags"]
 | 
				
			||||||
 | 
					  TIME_READ_FMT = "%Y-%m-%d %H:%M:%S"
 | 
				
			||||||
 | 
					  def __init__(self, parent, db_path):
 | 
				
			||||||
 | 
					    QAbstractTableModel.__init__(self, parent)
 | 
				
			||||||
 | 
					    self.db    = LibraryDatabase(db_path)
 | 
				
			||||||
 | 
					    self._data = self.db.get_table(self.FIELDS)
 | 
				
			||||||
 | 
					    self._orig_data = self._data
 | 
				
			||||||
 | 
					    self.image_file = None
 | 
				
			||||||
 | 
					    self.sort(0, Qt.DescendingOrder)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  def rowCount(self, parent): return len(self._data)
 | 
				
			||||||
 | 
					  def columnCount(self, parent): return len(self.FIELDS)-2
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  def set_data(self, db):
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
				
			||||||
 | 
					    self.db    = db
 | 
				
			||||||
 | 
					    self._data = self.db.get_table(self.FIELDS)    
 | 
				
			||||||
 | 
					    self._orig_data = self._data
 | 
				
			||||||
 | 
					    self.sort(0, Qt.DescendingOrder)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  def headerData(self, section, orientation, role):    
 | 
				
			||||||
 | 
					    if role != Qt.DisplayRole:
 | 
				
			||||||
 | 
					      return NONE
 | 
				
			||||||
 | 
					    text = ""
 | 
				
			||||||
 | 
					    if orientation == Qt.Horizontal:      
 | 
				
			||||||
 | 
					      if   section == 0: text = "Title"
 | 
				
			||||||
 | 
					      elif section == 1: text = "Author(s)"
 | 
				
			||||||
 | 
					      elif section == 2: text = "Size"
 | 
				
			||||||
 | 
					      elif section == 3: text = "Date"
 | 
				
			||||||
 | 
					      elif section == 4: text = "Publisher"
 | 
				
			||||||
 | 
					      return QVariant(self.trUtf8(text))
 | 
				
			||||||
 | 
					    else: return QVariant(str(1+section))
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  def info(self, row):
 | 
				
			||||||
 | 
					    row = self._data[row]
 | 
				
			||||||
 | 
					    cover = self.db.get_cover(row["id"])
 | 
				
			||||||
 | 
					    exts = ",".join(self.db.get_extensions(row["id"]))    
 | 
				
			||||||
 | 
					    if not cover: 
 | 
				
			||||||
 | 
					      cover = QPixmap(":/images/book.png")
 | 
				
			||||||
 | 
					      self.image_file = None
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					      pix = QPixmap()
 | 
				
			||||||
 | 
					      self.image_file = NamedTemporaryFile()
 | 
				
			||||||
 | 
					      self.image_file.write(cover)
 | 
				
			||||||
 | 
					      self.image_file.flush()
 | 
				
			||||||
 | 
					      pix.loadFromData(cover, "", Qt.AutoColor)
 | 
				
			||||||
 | 
					      cover = pix.scaledToHeight(COVER_HEIGHT, Qt.SmoothTransformation)
 | 
				
			||||||
 | 
					    return row["title"], row["authors"], human_readable(int(row["size"])), exts, cover
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def data(self, index, role):
 | 
				
			||||||
 | 
					    if role == Qt.DisplayRole:      
 | 
				
			||||||
 | 
					      row, col = index.row(), index.column()
 | 
				
			||||||
 | 
					      text = None
 | 
				
			||||||
 | 
					      row = self._data[row]
 | 
				
			||||||
 | 
					      if   col == 0: text = wrap(row["title"], width=25)
 | 
				
			||||||
 | 
					      elif col == 1: 
 | 
				
			||||||
 | 
					        au = row["authors"]
 | 
				
			||||||
 | 
					        if au : text = wrap(re.sub("&", "\n", au), width=25)
 | 
				
			||||||
 | 
					      elif col == 2: text = human_readable(row["size"])
 | 
				
			||||||
 | 
					      elif col == 3: text = time.strftime(TIME_WRITE_FMT, time.strptime(row["date"], self.TIME_READ_FMT))
 | 
				
			||||||
 | 
					      elif col == 4: 
 | 
				
			||||||
 | 
					        pub = row["publisher"]
 | 
				
			||||||
 | 
					        if pub: text = wrap(pub, 20)
 | 
				
			||||||
 | 
					      if not text: text = "Unknown"
 | 
				
			||||||
 | 
					      return QVariant(text)
 | 
				
			||||||
 | 
					    elif role == Qt.TextAlignmentRole and index.column() in [2,3,4]:
 | 
				
			||||||
 | 
					      return QVariant(Qt.AlignRight)
 | 
				
			||||||
 | 
					    return NONE
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					  def sort(self, col, order):
 | 
				
			||||||
 | 
					    descending = order != Qt.AscendingOrder
 | 
				
			||||||
 | 
					    def getter(key, func):  return lambda x : func(itemgetter(key)(x))
 | 
				
			||||||
 | 
					    if col == 0: key, func = "title", string.lower
 | 
				
			||||||
 | 
					    if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower() if x else ""
 | 
				
			||||||
 | 
					    if col == 2: key, func = "size", int
 | 
				
			||||||
 | 
					    if col == 3: key, func = "date", lambda x: time.mktime(time.strptime(x, self.TIME_READ_FMT))
 | 
				
			||||||
 | 
					    if col == 4: key, func = "publisher", lambda x : x.lower() if x else ""
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
				
			||||||
 | 
					    self._data.sort(key=getter(key, func))
 | 
				
			||||||
 | 
					    if descending: self._data.reverse()
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("layoutChanged()"))
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("sorted()"))
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def search(self, query):
 | 
				
			||||||
 | 
					    def query_in(book, q):
 | 
				
			||||||
 | 
					      au = book["authors"]
 | 
				
			||||||
 | 
					      if not au : au = "unknown"
 | 
				
			||||||
 | 
					      pub = book["publisher"]
 | 
				
			||||||
 | 
					      if not pub : pub = "unknown"
 | 
				
			||||||
 | 
					      return q in book["title"].lower() or q in au.lower() or q in pub.lower()
 | 
				
			||||||
 | 
					    queries = unicode(query, 'utf-8').lower().split()
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
				
			||||||
 | 
					    self._data = []
 | 
				
			||||||
 | 
					    for book in self._orig_data:
 | 
				
			||||||
 | 
					      match = True
 | 
				
			||||||
 | 
					      for q in queries:
 | 
				
			||||||
 | 
					        if query_in(book, q) : continue
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					          match = False
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					      if match: self._data.append(book)
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("layoutChanged()"))
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("searched()"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DeviceBooksModel(QAbstractTableModel):
 | 
				
			||||||
 | 
					  TIME_READ_FMT  = "%a, %d %b %Y %H:%M:%S %Z"  
 | 
				
			||||||
  def __init__(self, parent, data):
 | 
					  def __init__(self, parent, data):
 | 
				
			||||||
    QAbstractTableModel.__init__(self, parent)
 | 
					    QAbstractTableModel.__init__(self, parent)
 | 
				
			||||||
    self._data = data
 | 
					    self._data = data
 | 
				
			||||||
    self._orig_data = data
 | 
					    self._orig_data = data
 | 
				
			||||||
 | 
					    self.image_file = None
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def set_data(self, data):
 | 
					  def set_data(self, data):
 | 
				
			||||||
    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
				
			||||||
@ -48,7 +167,7 @@ class DeviceBooksModel(QAbstractTableModel):
 | 
				
			|||||||
    self.sort(0, Qt.DescendingOrder)
 | 
					    self.sort(0, Qt.DescendingOrder)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
  def rowCount(self, parent): return len(self._data)
 | 
					  def rowCount(self, parent): return len(self._data)
 | 
				
			||||||
  def columnCount(self, parent): return 3
 | 
					  def columnCount(self, parent): return 4
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def headerData(self, section, orientation, role):
 | 
					  def headerData(self, section, orientation, role):
 | 
				
			||||||
    if role != Qt.DisplayRole:
 | 
					    if role != Qt.DisplayRole:
 | 
				
			||||||
@ -58,6 +177,7 @@ class DeviceBooksModel(QAbstractTableModel):
 | 
				
			|||||||
      if section == 0: text = "Title"
 | 
					      if section == 0: text = "Title"
 | 
				
			||||||
      elif section == 1: text = "Author(s)"
 | 
					      elif section == 1: text = "Author(s)"
 | 
				
			||||||
      elif section == 2: text = "Size"
 | 
					      elif section == 2: text = "Size"
 | 
				
			||||||
 | 
					      elif section == 3: text = "Date"
 | 
				
			||||||
      return QVariant(self.trUtf8(text))
 | 
					      return QVariant(self.trUtf8(text))
 | 
				
			||||||
    else: return QVariant(str(1+section))
 | 
					    else: return QVariant(str(1+section))
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -68,24 +188,33 @@ class DeviceBooksModel(QAbstractTableModel):
 | 
				
			|||||||
      if col == 0: text = book["title"]
 | 
					      if col == 0: text = book["title"]
 | 
				
			||||||
      elif col == 1: text = book["author"]
 | 
					      elif col == 1: text = book["author"]
 | 
				
			||||||
      elif col == 2: text = human_readable(int(book["size"]))
 | 
					      elif col == 2: text = human_readable(int(book["size"]))
 | 
				
			||||||
 | 
					      elif col == 3: text = time.strftime(TIME_WRITE_FMT, time.strptime(book["date"], self.TIME_READ_FMT))
 | 
				
			||||||
      return QVariant(text)
 | 
					      return QVariant(text)
 | 
				
			||||||
    elif role == Qt.TextAlignmentRole and index.column() == 2:
 | 
					    elif role == Qt.TextAlignmentRole and index.column() in [2,3]:
 | 
				
			||||||
      return QVariant(Qt.AlignRight)
 | 
					      return QVariant(Qt.AlignRight)
 | 
				
			||||||
    elif role == Qt.DecorationRole and index.column() == 0:
 | 
					 | 
				
			||||||
      book = self._data[index.row()]
 | 
					 | 
				
			||||||
      if book.has_key("thumbnail"):
 | 
					 | 
				
			||||||
        return QVariant(book["thumbnail"])
 | 
					 | 
				
			||||||
    return NONE
 | 
					    return NONE
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
  def info(self, row):
 | 
					  def info(self, row):
 | 
				
			||||||
    row = self._data[row]    
 | 
					    row = self._data[row]    
 | 
				
			||||||
    return row["title"], row["author"], human_readable(int(row["size"])), row["mime"], row["thumbnail"].pixmap(60, 80, QIcon.Normal, QIcon.On)
 | 
					    try:
 | 
				
			||||||
 | 
					      cover = row["thumbnail"]
 | 
				
			||||||
 | 
					      pix = QPixmap()
 | 
				
			||||||
 | 
					      self.image_file = NamedTemporaryFile()
 | 
				
			||||||
 | 
					      self.image_file.write(cover)
 | 
				
			||||||
 | 
					      self.image_file.flush()
 | 
				
			||||||
 | 
					      pix.loadFromData(cover, "", Qt.AutoColor)
 | 
				
			||||||
 | 
					      cover = pix.scaledToHeight(COVER_HEIGHT, Qt.SmoothTransformation)      
 | 
				
			||||||
 | 
					    except Exception, e: 
 | 
				
			||||||
 | 
					      self.image_file = None
 | 
				
			||||||
 | 
					      cover = QPixmap(":/images/book.png")
 | 
				
			||||||
 | 
					    return row["title"], row["author"], human_readable(int(row["size"])), row["mime"], cover
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def sort(self, col, order):
 | 
					  def sort(self, col, order):
 | 
				
			||||||
    def getter(key, func):  return lambda x : func(itemgetter(key)(x))
 | 
					    def getter(key, func):  return lambda x : func(itemgetter(key)(x))
 | 
				
			||||||
    if col == 0: key, func = "title", string.lower
 | 
					    if col == 0: key, func = "title", string.lower
 | 
				
			||||||
    if col == 1: key, func = "author", lambda x : x.split()[-1:][0].lower()
 | 
					    if col == 1: key, func = "author", lambda x : x.split()[-1:][0].lower()
 | 
				
			||||||
    if col == 2: key, func = "size", int
 | 
					    if col == 2: key, func = "size", int
 | 
				
			||||||
 | 
					    if col == 3: key, func = "date", lambda x: time.mktime(time.strptime(x, self.TIME_READ_FMT))
 | 
				
			||||||
    descending = order != Qt.AscendingOrder
 | 
					    descending = order != Qt.AscendingOrder
 | 
				
			||||||
    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
				
			||||||
    self._data.sort(key=getter(key, func))
 | 
					    self._data.sort(key=getter(key, func))
 | 
				
			||||||
@ -94,7 +223,7 @@ class DeviceBooksModel(QAbstractTableModel):
 | 
				
			|||||||
    self.emit(SIGNAL("sorted()"))
 | 
					    self.emit(SIGNAL("sorted()"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def search(self, query):
 | 
					  def search(self, query):
 | 
				
			||||||
    queries = unicode(query).lower().split()
 | 
					    queries = unicode(query, 'utf-8').lower().split()
 | 
				
			||||||
    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
					    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
				
			||||||
    self._data = []
 | 
					    self._data = []
 | 
				
			||||||
    for book in self._orig_data:
 | 
					    for book in self._orig_data:
 | 
				
			||||||
@ -108,56 +237,171 @@ class DeviceBooksModel(QAbstractTableModel):
 | 
				
			|||||||
    self.emit(SIGNAL("layoutChanged()"))
 | 
					    self.emit(SIGNAL("layoutChanged()"))
 | 
				
			||||||
    self.emit(SIGNAL("searched()"))
 | 
					    self.emit(SIGNAL("searched()"))
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					  def delete_by_path(self, path):
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("layoutAboutToBeChanged()"))
 | 
				
			||||||
 | 
					    index = -1
 | 
				
			||||||
 | 
					    for book in self._data:
 | 
				
			||||||
 | 
					      if path in book["path"]:
 | 
				
			||||||
 | 
					        self._data.remove(book)
 | 
				
			||||||
 | 
					        break
 | 
				
			||||||
 | 
					    for book in self._orig_data:
 | 
				
			||||||
 | 
					      if path in book["path"]:
 | 
				
			||||||
 | 
					        self._orig_data.remove(book)
 | 
				
			||||||
 | 
					        break
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("layoutChanged()"))
 | 
				
			||||||
 | 
					    self.emit(SIGNAL("deleted()"))
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  def path(self, index):  return self._data[index.row()]["path"]
 | 
				
			||||||
 | 
					  def title(self, index):  return self._data[index.row()]["title"]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Ui_MainWindow, bclass = uic.loadUiType(pkg_resources.resource_stream(__name__, "main.ui"))
 | 
					Ui_MainWindow, bclass = uic.loadUiType(pkg_resources.resource_stream(__name__, "main.ui"))
 | 
				
			||||||
class MainWindow(QObject, Ui_MainWindow): 
 | 
					class MainWindow(QObject, Ui_MainWindow): 
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def tree_clicked(self, index):
 | 
					  def tree_clicked(self, index):
 | 
				
			||||||
 | 
					    def show_device(yes):
 | 
				
			||||||
 | 
					      if yes: self.device_view.show(), self.library_view.hide()
 | 
				
			||||||
 | 
					      else: self.device_view.hide(), self.library_view.show()
 | 
				
			||||||
    item = self.tree.itemFromIndex(index)
 | 
					    item = self.tree.itemFromIndex(index)
 | 
				
			||||||
    text = str(item.text())
 | 
					    text = str(item.text())
 | 
				
			||||||
    if text == "Library":
 | 
					    if text == "Library":
 | 
				
			||||||
      print "Library Clicked"
 | 
					      show_device(False)
 | 
				
			||||||
    elif text == "SONY Reader":
 | 
					    elif text == "SONY Reader":
 | 
				
			||||||
      self.set_data(self.main_books + self.card_books)
 | 
					      show_device(True)
 | 
				
			||||||
 | 
					      self.set_device_data(self.main_books + self.card_books)
 | 
				
			||||||
    elif text == "Main Memory":
 | 
					    elif text == "Main Memory":
 | 
				
			||||||
      self.set_data(self.main_books)
 | 
					      show_device(True)
 | 
				
			||||||
 | 
					      self.set_device_data(self.main_books)
 | 
				
			||||||
    elif text == "Storage Card":
 | 
					    elif text == "Storage Card":
 | 
				
			||||||
      self.set_data(self.card_books)
 | 
					      show_device(True)
 | 
				
			||||||
 | 
					      self.set_device_data(self.card_books)
 | 
				
			||||||
    elif text == "Books":
 | 
					    elif text == "Books":
 | 
				
			||||||
      text = str(item.parent().text())
 | 
					      text = str(item.parent().text())
 | 
				
			||||||
      if text == "Library":
 | 
					      if text == "Library":
 | 
				
			||||||
        print "Library --> Books Clicked"
 | 
					        show_device(False)        
 | 
				
			||||||
      elif text == "Main Memory":
 | 
					      elif text == "Main Memory":
 | 
				
			||||||
        self.set_data(self.main_books)  
 | 
					        show_device(True)
 | 
				
			||||||
 | 
					        self.set_device_data(self.main_books)  
 | 
				
			||||||
      elif text == "Storage Card":
 | 
					      elif text == "Storage Card":
 | 
				
			||||||
 | 
					        show_device(True)
 | 
				
			||||||
        self.set_data(self.card_books)
 | 
					        self.set_data(self.card_books)
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def set_data(self, data):
 | 
					  def set_device_data(self, data): 
 | 
				
			||||||
    self.model.set_data(data)    
 | 
					    self.device_model.set_data(data) 
 | 
				
			||||||
    self.table_view.resizeColumnsToContents()
 | 
					    self.device_view.resizeColumnsToContents()
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  
 | 
					  def model_modified(self):
 | 
				
			||||||
  def data_sorted(self):
 | 
					    self.device_view.clearSelection()
 | 
				
			||||||
    self.table_view.resizeRowsToContents()
 | 
					    self.library_view.clearSelection()
 | 
				
			||||||
    self.table_view.clearSelection()
 | 
					 | 
				
			||||||
    self.book_cover.hide()
 | 
					    self.book_cover.hide()
 | 
				
			||||||
    self.book_info.hide()
 | 
					    self.book_info.hide()
 | 
				
			||||||
 | 
					    QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)    
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def show_book(self, current, previous):
 | 
					  def show_book(self, current, previous):
 | 
				
			||||||
    title, author, size, mime, thumbnail = current.model().info(current.row())
 | 
					    title, author, size, mime, thumbnail = current.model().info(current.row())
 | 
				
			||||||
    self.book_info.setText(self.BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime))
 | 
					    self.book_info.setText(self.BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime))
 | 
				
			||||||
    self.book_cover.setPixmap(thumbnail)
 | 
					    self.book_cover.setPixmap(thumbnail)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					      name = os.path.abspath(current.model().image_file.name)
 | 
				
			||||||
 | 
					      self.book_cover.setToolTip('<img src="'+name+'">')
 | 
				
			||||||
 | 
					    except Exception, e: self.book_cover.setToolTip('<img src=":/images/book.png">')
 | 
				
			||||||
    self.book_cover.show()
 | 
					    self.book_cover.show()
 | 
				
			||||||
    self.book_info.show()
 | 
					    self.book_info.show()
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def searched(self):
 | 
					 | 
				
			||||||
    self.table_view.clearSelection()
 | 
					 | 
				
			||||||
    self.book_cover.hide()
 | 
					 | 
				
			||||||
    self.book_info.hide()
 | 
					 | 
				
			||||||
    self.table_view.resizeRowsToContents()
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  def clear(self, checked): self.search.setText("")
 | 
					  def clear(self, checked): self.search.setText("")
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  def list_context_event(self, event):
 | 
				
			||||||
 | 
					    print "TODO:"
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def do_delete(self, rows):
 | 
				
			||||||
 | 
					    if self.device_model.__class__.__name__ == "DeviceBooksdevice_model":
 | 
				
			||||||
 | 
					      paths, mc, cc = [], False, False
 | 
				
			||||||
 | 
					      for book in rows:
 | 
				
			||||||
 | 
					        path = book.model().path(book)
 | 
				
			||||||
 | 
					        if path[0] == "/": file, prefix, mc = self.main_xml, "xs1:", True
 | 
				
			||||||
 | 
					        else:                    file, prefix, cc = self.cache_xml, "",       True
 | 
				
			||||||
 | 
					        file.seek(0)
 | 
				
			||||||
 | 
					        document = dom.parse(file)
 | 
				
			||||||
 | 
					        books = document.getElementsByTagName(prefix + "text")
 | 
				
			||||||
 | 
					        for candidate in books:
 | 
				
			||||||
 | 
					          if candidate.attributes["path"].value in path:
 | 
				
			||||||
 | 
					            paths.append(path)
 | 
				
			||||||
 | 
					            candidate.parentNode.removeChild(candidate)
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        file.close()
 | 
				
			||||||
 | 
					        file = TemporaryFile()
 | 
				
			||||||
 | 
					        PrettyPrint(document, file)        
 | 
				
			||||||
 | 
					        if len(prefix) > 0: self.main_xml = file
 | 
				
			||||||
 | 
					        else: self.cache_xml = file
 | 
				
			||||||
 | 
					      for path in paths:
 | 
				
			||||||
 | 
					        self.dev.del_file(path)
 | 
				
			||||||
 | 
					        self.device_model.delete_by_path(path)        
 | 
				
			||||||
 | 
					      self.cache_xml.seek(0)
 | 
				
			||||||
 | 
					      self.main_xml.seek(0)
 | 
				
			||||||
 | 
					      self.status("Files deleted. Updating media list on device")
 | 
				
			||||||
 | 
					      if mc: 
 | 
				
			||||||
 | 
					        self.dev.del_file(self.dev.MEDIA_XML)
 | 
				
			||||||
 | 
					        self.dev.put_file(self.main_xml, self.dev.MEDIA_XML)
 | 
				
			||||||
 | 
					      if cc: 
 | 
				
			||||||
 | 
					        self.dev.del_file(self.card+self.dev.CACHE_XML)
 | 
				
			||||||
 | 
					        self.dev.put_file(self.cache_xml, self.card+self.dev.CACHE_XML)
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def delete(self, action):
 | 
				
			||||||
 | 
					    self.window.setCursor(Qt.WaitCursor)
 | 
				
			||||||
 | 
					    rows   = self.device_view.selectionModel().selectedRows()
 | 
				
			||||||
 | 
					    items = [ row.model().title(row) + ": " +  row.model().path(row)[row.model().path(row).rfind("/")+1:] for row in rows ]
 | 
				
			||||||
 | 
					    ret = QMessageBox.question(self.window, self.trUtf8("SONY Reader - confirm"),  self.trUtf8("Are you sure you want to delete these items from the device?\n\n") + "\n".join(items), 
 | 
				
			||||||
 | 
					             QMessageBox.YesToAll | QMessageBox.No, QMessageBox.YesToAll)
 | 
				
			||||||
 | 
					    if ret == QMessageBox.YesToAll:
 | 
				
			||||||
 | 
					      self.do_delete(rows)
 | 
				
			||||||
 | 
					    self.window.setCursor(Qt.ArrowCursor)
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def read_settings(self):
 | 
				
			||||||
 | 
					    settings = QSettings()
 | 
				
			||||||
 | 
					    settings.beginGroup("MainWindow")
 | 
				
			||||||
 | 
					    self.window.resize(settings.value("size", QVariant(QSize(1000, 700))).toSize())
 | 
				
			||||||
 | 
					    settings.endGroup()
 | 
				
			||||||
 | 
					    self.database_path = settings.value("database path", QVariant(os.path.expanduser("~/library.sqlite"))).toString()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  def write_settings(self):
 | 
				
			||||||
 | 
					    settings = QSettings()
 | 
				
			||||||
 | 
					    settings.beginGroup("MainWindow")
 | 
				
			||||||
 | 
					    settings.setValue("size", QVariant(self.window.size()))
 | 
				
			||||||
 | 
					    settings.endGroup()
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def close_event(self, e):
 | 
				
			||||||
 | 
					    self.write_settings()
 | 
				
			||||||
 | 
					    e.accept()
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def add(self, action):
 | 
				
			||||||
 | 
					    settings = QSettings()
 | 
				
			||||||
 | 
					    dir = settings.value("add books dialog dir", QVariant(os.path.expanduser("~"))).toString()
 | 
				
			||||||
 | 
					    files = QFileDialog.getOpenFileNames(self.window, "Choose books to add to library", dir, "Books (*.lrf *.lrx *.rtf *.pdf *.txt);;All files (*)")
 | 
				
			||||||
 | 
					    if not files.isEmpty():
 | 
				
			||||||
 | 
					      x = str(files[0])
 | 
				
			||||||
 | 
					      settings.setValue("add books dialog dir", QVariant(os.path.dirname(x)))
 | 
				
			||||||
 | 
					      files = str(files.join("|||")).split("|||")      
 | 
				
			||||||
 | 
					      for file in files:
 | 
				
			||||||
 | 
					        file = os.path.abspath(file)
 | 
				
			||||||
 | 
					        title, author, cover, publisher = None, None, None, None
 | 
				
			||||||
 | 
					        if ext == "lrf":
 | 
				
			||||||
 | 
					          try: 
 | 
				
			||||||
 | 
					            lrf = LRFMetaFile(open(file, "r+b"))
 | 
				
			||||||
 | 
					            title = lrf.title
 | 
				
			||||||
 | 
					            author = lrf.author
 | 
				
			||||||
 | 
					            publisher = lrf.publisher
 | 
				
			||||||
 | 
					            cover = lrf.thumbnail
 | 
				
			||||||
 | 
					            if "unknown" in author.lower(): author = None
 | 
				
			||||||
 | 
					          except IOError, e:
 | 
				
			||||||
 | 
					            self.show_error(e, "Unable to access <b>"+file+"</b>")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          except LRFException: pass
 | 
				
			||||||
 | 
					          self.library_model.add(file, title, author, publisher, cover)
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					  def show_error(self, e, msg): 
 | 
				
			||||||
 | 
					    QErrorMessage(self.window).showMessage(msg+"<br><b>Error: </b>"+str(e)+"<br><br>Traceback:<br>"+traceback.format_exc(e))
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  def __init__(self, window):
 | 
					  def __init__(self, window):
 | 
				
			||||||
    QObject.__init__(self)
 | 
					    QObject.__init__(self)
 | 
				
			||||||
    Ui_MainWindow.__init__(self)    
 | 
					    Ui_MainWindow.__init__(self)    
 | 
				
			||||||
@ -165,7 +409,26 @@ class MainWindow(QObject, Ui_MainWindow):
 | 
				
			|||||||
    self.is_connected = False    
 | 
					    self.is_connected = False    
 | 
				
			||||||
    self.setupUi(window)
 | 
					    self.setupUi(window)
 | 
				
			||||||
    self.card = None
 | 
					    self.card = None
 | 
				
			||||||
    # Create Tree
 | 
					    self.window = window
 | 
				
			||||||
 | 
					    window.closeEvent = self.close_event
 | 
				
			||||||
 | 
					    self.read_settings()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Setup Library Book list
 | 
				
			||||||
 | 
					    self.library_model = LibraryBooksModel(window, str(self.database_path))
 | 
				
			||||||
 | 
					    self.library_view.setModel(self.library_model)
 | 
				
			||||||
 | 
					    self.library_view.setSelectionBehavior(QAbstractItemView.SelectRows)
 | 
				
			||||||
 | 
					    self.library_view.setSortingEnabled(True)
 | 
				
			||||||
 | 
					    self.library_view.contextMenuEvent = self.list_context_event
 | 
				
			||||||
 | 
					    QObject.connect(self.library_model, SIGNAL("layoutChanged()"), self.library_view.resizeRowsToContents)
 | 
				
			||||||
 | 
					    QObject.connect(self.library_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
 | 
				
			||||||
 | 
					    QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.library_model.search)
 | 
				
			||||||
 | 
					    QObject.connect(self.library_model, SIGNAL("sorted()"), self.model_modified)
 | 
				
			||||||
 | 
					    QObject.connect(self.library_model, SIGNAL("searched()"), self.model_modified)
 | 
				
			||||||
 | 
					    QObject.connect(self.library_model, SIGNAL("deleted()"), self.model_modified)    
 | 
				
			||||||
 | 
					    self.library_view.resizeColumnsToContents()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Create Device list
 | 
				
			||||||
    self.tree = QStandardItemModel()
 | 
					    self.tree = QStandardItemModel()
 | 
				
			||||||
    library = QStandardItem(QString("Library"))
 | 
					    library = QStandardItem(QString("Library"))
 | 
				
			||||||
    library.setIcon(QIcon(":/images/mycomputer.png"))
 | 
					    library.setIcon(QIcon(":/images/mycomputer.png"))
 | 
				
			||||||
@ -187,8 +450,6 @@ class MainWindow(QObject, Ui_MainWindow):
 | 
				
			|||||||
    self.reader.setIcon(QIcon(":/images/reader.png"))
 | 
					    self.reader.setIcon(QIcon(":/images/reader.png"))
 | 
				
			||||||
    self.tree.appendRow(self.reader)
 | 
					    self.tree.appendRow(self.reader)
 | 
				
			||||||
    self.reader.setFont(font)
 | 
					    self.reader.setFont(font)
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    self.treeView.setModel(self.tree)    
 | 
					    self.treeView.setModel(self.tree)    
 | 
				
			||||||
    self.treeView.header().hide()
 | 
					    self.treeView.header().hide()
 | 
				
			||||||
    self.treeView.setExpanded(library.index(), True)
 | 
					    self.treeView.setExpanded(library.index(), True)
 | 
				
			||||||
@ -199,17 +460,21 @@ class MainWindow(QObject, Ui_MainWindow):
 | 
				
			|||||||
    QObject.connect(self.treeView, SIGNAL("activated(QModelIndex)"),  self.tree_clicked)
 | 
					    QObject.connect(self.treeView, SIGNAL("activated(QModelIndex)"),  self.tree_clicked)
 | 
				
			||||||
    QObject.connect(self.treeView, SIGNAL("clicked(QModelIndex)"),  self.tree_clicked)
 | 
					    QObject.connect(self.treeView, SIGNAL("clicked(QModelIndex)"),  self.tree_clicked)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # Create Table
 | 
					    # Create Device Book list
 | 
				
			||||||
    self.model = DeviceBooksModel(window, [])
 | 
					    self.device_model = DeviceBooksModel(window, [])    
 | 
				
			||||||
    QObject.connect(self.model, SIGNAL("sorted()"), self.data_sorted)
 | 
					    self.device_view.setModel(self.device_model)
 | 
				
			||||||
    self.table_view.setModel(self.model)
 | 
					    self.device_view.setSelectionBehavior(QAbstractItemView.SelectRows)
 | 
				
			||||||
    self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
 | 
					    self.device_view.setSortingEnabled(True)
 | 
				
			||||||
    self.table_view.setSortingEnabled(True)
 | 
					    self.device_view.contextMenuEvent = self.list_context_event
 | 
				
			||||||
    QObject.connect(self.table_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
 | 
					    QObject.connect(self.device_model, SIGNAL("layoutChanged()"), self.device_view.resizeRowsToContents)
 | 
				
			||||||
    QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.model.search)
 | 
					    QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
 | 
				
			||||||
    QObject.connect(self.model, SIGNAL("searched()"), self.searched)
 | 
					    QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.device_model.search)
 | 
				
			||||||
 | 
					    QObject.connect(self.device_model, SIGNAL("sorted()"), self.model_modified)
 | 
				
			||||||
 | 
					    QObject.connect(self.device_model, SIGNAL("searched()"), self.model_modified)
 | 
				
			||||||
 | 
					    QObject.connect(self.device_model, SIGNAL("deleted()"), self.model_modified)
 | 
				
			||||||
    self.clearButton.setIcon(QIcon(":/images/clear.png"))
 | 
					    self.clearButton.setIcon(QIcon(":/images/clear.png"))
 | 
				
			||||||
    QObject.connect(self.clearButton, SIGNAL("clicked(bool)"), self.clear)
 | 
					    QObject.connect(self.clearButton, SIGNAL("clicked(bool)"), self.clear)
 | 
				
			||||||
 | 
					    self.device_view.hide()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # Setup book display
 | 
					    # Setup book display
 | 
				
			||||||
    self.BOOK_TEMPLATE = self.book_info.text()    
 | 
					    self.BOOK_TEMPLATE = self.book_info.text()    
 | 
				
			||||||
@ -217,11 +482,19 @@ class MainWindow(QObject, Ui_MainWindow):
 | 
				
			|||||||
    self.book_cover.hide()
 | 
					    self.book_cover.hide()
 | 
				
			||||||
    self.book_info.hide()
 | 
					    self.book_info.hide()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    # Populate toolbar
 | 
				
			||||||
 | 
					    self.add_action = self.tool_bar.addAction(QIcon(":/images/fileopen.png"), "Add files to Library")
 | 
				
			||||||
 | 
					    self.add_action.setShortcut(Qt.Key_A)
 | 
				
			||||||
 | 
					    QObject.connect(self.add_action, SIGNAL("triggered(bool)"), self.add)
 | 
				
			||||||
 | 
					    self.del_action = self.tool_bar.addAction(QIcon(":/images/delete.png"), "Delete selected items") 
 | 
				
			||||||
 | 
					    self.del_action.setShortcut(Qt.Key_Delete)
 | 
				
			||||||
 | 
					    QObject.connect(self.del_action, SIGNAL("triggered(bool)"), self.delete)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    self.device_detector = self.startTimer(1000)
 | 
					    self.device_detector = self.startTimer(1000)
 | 
				
			||||||
    self.splitter.setStretchFactor(0,0)
 | 
					    self.splitter.setStretchFactor(0,0)
 | 
				
			||||||
    self.splitter.setStretchFactor(1,100)
 | 
					    self.splitter.setStretchFactor(1,100)
 | 
				
			||||||
    self.search.setFocus(Qt.OtherFocusReason)
 | 
					    self.search.setFocus(Qt.OtherFocusReason)
 | 
				
			||||||
    self.window = window
 | 
					 | 
				
			||||||
    window.show()
 | 
					    window.show()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
  def timerEvent(self, e):
 | 
					  def timerEvent(self, e):
 | 
				
			||||||
@ -238,9 +511,10 @@ class MainWindow(QObject, Ui_MainWindow):
 | 
				
			|||||||
    self.df.setText("Main memory: <br><br>Storage card:")
 | 
					    self.df.setText("Main memory: <br><br>Storage card:")
 | 
				
			||||||
    self.card = None
 | 
					    self.card = None
 | 
				
			||||||
    self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), True)
 | 
					    self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), True)
 | 
				
			||||||
    self.model.set_data([])
 | 
					    self.device_model.set_data([])
 | 
				
			||||||
    self.book_cover.hide()
 | 
					    self.book_cover.hide()
 | 
				
			||||||
    self.book_info.hide()
 | 
					    self.book_info.hide()
 | 
				
			||||||
 | 
					    self.device_view.hide()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def timeout_error(self):
 | 
					  def timeout_error(self):
 | 
				
			||||||
@ -260,6 +534,7 @@ class MainWindow(QObject, Ui_MainWindow):
 | 
				
			|||||||
    QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
 | 
					    QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  def establish_connection(self):
 | 
					  def establish_connection(self):
 | 
				
			||||||
 | 
					    self.window.setCursor(Qt.WaitCursor)
 | 
				
			||||||
    self.status("Connecting to device")    
 | 
					    self.status("Connecting to device")    
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
      space = self.dev.available_space()
 | 
					      space = self.dev.available_space()
 | 
				
			||||||
@ -275,20 +550,17 @@ class MainWindow(QObject, Ui_MainWindow):
 | 
				
			|||||||
    sc = space[1][1] if space[1][1] else space[2][1]
 | 
					    sc = space[1][1] if space[1][1] else space[2][1]
 | 
				
			||||||
    self.df.setText("Main memory:  " + human_readable(space[0][1]) + "<br><br>Storage card: " + human_readable(sc))
 | 
					    self.df.setText("Main memory:  " + human_readable(space[0][1]) + "<br><br>Storage card: " + human_readable(sc))
 | 
				
			||||||
    self.is_connected = True
 | 
					    self.is_connected = True
 | 
				
			||||||
    if space[1][2] > 0: self.card = "a:/"
 | 
					    if space[1][2] > 0: self.card = "a:"
 | 
				
			||||||
    elif space[2][2] > 0: self.card = "b:/"
 | 
					    elif space[2][2] > 0: self.card = "b:"
 | 
				
			||||||
    else: self.card = None
 | 
					    else: self.card = None
 | 
				
			||||||
    if self.card: self.treeView.setRowHidden(1, self.reader.index(), False)
 | 
					    if self.card: self.treeView.setRowHidden(1, self.reader.index(), False)
 | 
				
			||||||
    else: self.treeView.setRowHidden(1, self.reader.index(), True)
 | 
					    else: self.treeView.setRowHidden(1, self.reader.index(), True)
 | 
				
			||||||
    self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), False)
 | 
					    self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), False)
 | 
				
			||||||
    self.status("Loading media list")
 | 
					    self.status("Loading media list from device")
 | 
				
			||||||
    mb, cb, mx, cx = self.dev.books()    
 | 
					    mb, cb, mx, cx = self.dev.books()    
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    for x in (mb, cb):
 | 
					    for x in (mb, cb):
 | 
				
			||||||
      for book in x:
 | 
					      for book in x:
 | 
				
			||||||
        if book.has_key("thumbnail"):
 | 
					 | 
				
			||||||
          book["thumbnail"] = QIcon(QPixmap.fromImage(QImage.fromData(book["thumbnail"])))
 | 
					 | 
				
			||||||
        else: book["thumbnail"] = QIcon(self.BOOK_IMAGE)
 | 
					 | 
				
			||||||
        if "&" in book["author"]:
 | 
					        if "&" in book["author"]:
 | 
				
			||||||
          book["author"] = re.sub(r"&\s*", r"\n", book["author"])
 | 
					          book["author"] = re.sub(r"&\s*", r"\n", book["author"])
 | 
				
			||||||
          
 | 
					          
 | 
				
			||||||
@ -296,11 +568,15 @@ class MainWindow(QObject, Ui_MainWindow):
 | 
				
			|||||||
    self.card_books = cb
 | 
					    self.card_books = cb
 | 
				
			||||||
    self.main_xml = mx
 | 
					    self.main_xml = mx
 | 
				
			||||||
    self.cache_xml = cx
 | 
					    self.cache_xml = cx
 | 
				
			||||||
 | 
					    self.window.setCursor(Qt.ArrowCursor)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
    from PyQt4.Qt import QApplication, QMainWindow
 | 
					    from PyQt4.Qt import QApplication, QMainWindow
 | 
				
			||||||
    app = QApplication(sys.argv)
 | 
					    app = QApplication(sys.argv)
 | 
				
			||||||
    window = QMainWindow()
 | 
					    window = QMainWindow()
 | 
				
			||||||
 | 
					    QCoreApplication.setOrganizationName("KovidsBrain")
 | 
				
			||||||
 | 
					    QCoreApplication.setApplicationName("prs500-gui")
 | 
				
			||||||
    gui = MainWindow(window)    
 | 
					    gui = MainWindow(window)    
 | 
				
			||||||
    return app.exec_()
 | 
					    ret = app.exec_()    
 | 
				
			||||||
 | 
					    return ret
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
				
			|||||||
@ -6,8 +6,8 @@
 | 
				
			|||||||
   <rect>
 | 
					   <rect>
 | 
				
			||||||
    <x>0</x>
 | 
					    <x>0</x>
 | 
				
			||||||
    <y>0</y>
 | 
					    <y>0</y>
 | 
				
			||||||
    <width>871</width>
 | 
					    <width>878</width>
 | 
				
			||||||
    <height>631</height>
 | 
					    <height>759</height>
 | 
				
			||||||
   </rect>
 | 
					   </rect>
 | 
				
			||||||
  </property>
 | 
					  </property>
 | 
				
			||||||
  <property name="sizePolicy" >
 | 
					  <property name="sizePolicy" >
 | 
				
			||||||
@ -22,14 +22,24 @@
 | 
				
			|||||||
   <string>SONY Reader</string>
 | 
					   <string>SONY Reader</string>
 | 
				
			||||||
  </property>
 | 
					  </property>
 | 
				
			||||||
  <widget class="QWidget" name="centralwidget" >
 | 
					  <widget class="QWidget" name="centralwidget" >
 | 
				
			||||||
   <layout class="QVBoxLayout" >
 | 
					   <layout class="QGridLayout" >
 | 
				
			||||||
    <property name="margin" >
 | 
					    <property name="margin" >
 | 
				
			||||||
     <number>9</number>
 | 
					     <number>9</number>
 | 
				
			||||||
    </property>
 | 
					    </property>
 | 
				
			||||||
    <property name="spacing" >
 | 
					    <property name="spacing" >
 | 
				
			||||||
     <number>6</number>
 | 
					     <number>6</number>
 | 
				
			||||||
    </property>
 | 
					    </property>
 | 
				
			||||||
    <item>
 | 
					    <item row="1" column="0" >
 | 
				
			||||||
 | 
					     <widget class="QProgressBar" name="progress_bar" >
 | 
				
			||||||
 | 
					      <property name="value" >
 | 
				
			||||||
 | 
					       <number>100</number>
 | 
				
			||||||
 | 
					      </property>
 | 
				
			||||||
 | 
					      <property name="orientation" >
 | 
				
			||||||
 | 
					       <enum>Qt::Horizontal</enum>
 | 
				
			||||||
 | 
					      </property>
 | 
				
			||||||
 | 
					     </widget>
 | 
				
			||||||
 | 
					    </item>
 | 
				
			||||||
 | 
					    <item row="0" column="0" >
 | 
				
			||||||
     <widget class="QSplitter" name="splitter" >
 | 
					     <widget class="QSplitter" name="splitter" >
 | 
				
			||||||
      <property name="orientation" >
 | 
					      <property name="orientation" >
 | 
				
			||||||
       <enum>Qt::Horizontal</enum>
 | 
					       <enum>Qt::Horizontal</enum>
 | 
				
			||||||
@ -162,7 +172,25 @@
 | 
				
			|||||||
           </layout>
 | 
					           </layout>
 | 
				
			||||||
          </item>
 | 
					          </item>
 | 
				
			||||||
          <item>
 | 
					          <item>
 | 
				
			||||||
           <widget class="QTableView" name="table_view" >
 | 
					           <widget class="QTableView" name="device_view" >
 | 
				
			||||||
 | 
					            <property name="sizePolicy" >
 | 
				
			||||||
 | 
					             <sizepolicy>
 | 
				
			||||||
 | 
					              <hsizetype>7</hsizetype>
 | 
				
			||||||
 | 
					              <vsizetype>7</vsizetype>
 | 
				
			||||||
 | 
					              <horstretch>0</horstretch>
 | 
				
			||||||
 | 
					              <verstretch>0</verstretch>
 | 
				
			||||||
 | 
					             </sizepolicy>
 | 
				
			||||||
 | 
					            </property>
 | 
				
			||||||
 | 
					            <property name="alternatingRowColors" >
 | 
				
			||||||
 | 
					             <bool>true</bool>
 | 
				
			||||||
 | 
					            </property>
 | 
				
			||||||
 | 
					            <property name="showGrid" >
 | 
				
			||||||
 | 
					             <bool>false</bool>
 | 
				
			||||||
 | 
					            </property>
 | 
				
			||||||
 | 
					           </widget>
 | 
				
			||||||
 | 
					          </item>
 | 
				
			||||||
 | 
					          <item>
 | 
				
			||||||
 | 
					           <widget class="QTableView" name="library_view" >
 | 
				
			||||||
            <property name="sizePolicy" >
 | 
					            <property name="sizePolicy" >
 | 
				
			||||||
             <sizepolicy>
 | 
					             <sizepolicy>
 | 
				
			||||||
              <hsizetype>7</hsizetype>
 | 
					              <hsizetype>7</hsizetype>
 | 
				
			||||||
@ -220,22 +248,21 @@
 | 
				
			|||||||
      </widget>
 | 
					      </widget>
 | 
				
			||||||
     </widget>
 | 
					     </widget>
 | 
				
			||||||
    </item>
 | 
					    </item>
 | 
				
			||||||
    <item>
 | 
					 | 
				
			||||||
     <widget class="QProgressBar" name="progress_bar" >
 | 
					 | 
				
			||||||
      <property name="value" >
 | 
					 | 
				
			||||||
       <number>100</number>
 | 
					 | 
				
			||||||
      </property>
 | 
					 | 
				
			||||||
      <property name="orientation" >
 | 
					 | 
				
			||||||
       <enum>Qt::Horizontal</enum>
 | 
					 | 
				
			||||||
      </property>
 | 
					 | 
				
			||||||
     </widget>
 | 
					 | 
				
			||||||
    </item>
 | 
					 | 
				
			||||||
   </layout>
 | 
					   </layout>
 | 
				
			||||||
  </widget>
 | 
					  </widget>
 | 
				
			||||||
  <widget class="QToolBar" name="toolBar" >
 | 
					  <widget class="QToolBar" name="tool_bar" >
 | 
				
			||||||
 | 
					   <property name="movable" >
 | 
				
			||||||
 | 
					    <bool>false</bool>
 | 
				
			||||||
 | 
					   </property>
 | 
				
			||||||
   <property name="orientation" >
 | 
					   <property name="orientation" >
 | 
				
			||||||
    <enum>Qt::Horizontal</enum>
 | 
					    <enum>Qt::Horizontal</enum>
 | 
				
			||||||
   </property>
 | 
					   </property>
 | 
				
			||||||
 | 
					   <property name="iconSize" >
 | 
				
			||||||
 | 
					    <size>
 | 
				
			||||||
 | 
					     <width>22</width>
 | 
				
			||||||
 | 
					     <height>22</height>
 | 
				
			||||||
 | 
					    </size>
 | 
				
			||||||
 | 
					   </property>
 | 
				
			||||||
   <attribute name="toolBarArea" >
 | 
					   <attribute name="toolBarArea" >
 | 
				
			||||||
    <number>4</number>
 | 
					    <number>4</number>
 | 
				
			||||||
   </attribute>
 | 
					   </attribute>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,5 +4,7 @@
 | 
				
			|||||||
     <file>images/mycomputer.png</file>
 | 
					     <file>images/mycomputer.png</file>
 | 
				
			||||||
     <file>images/reader.png</file>
 | 
					     <file>images/reader.png</file>
 | 
				
			||||||
     <file>images/clear.png</file>
 | 
					     <file>images/clear.png</file>
 | 
				
			||||||
 | 
					     <file>images/delete.png</file>
 | 
				
			||||||
 | 
					     <file>images/fileopen.png</file>
 | 
				
			||||||
 </qresource>
 | 
					 </qresource>
 | 
				
			||||||
 </RCC>
 | 
					 </RCC>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								libprs500/gui/resources/images/delete.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								libprs500/gui/resources/images/delete.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								libprs500/gui/resources/images/fileopen.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								libprs500/gui/resources/images/fileopen.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.3 KiB  | 
@ -305,12 +305,13 @@ def main():
 | 
				
			|||||||
  import sys, os.path
 | 
					  import sys, os.path
 | 
				
			||||||
  from optparse import OptionParser
 | 
					  from optparse import OptionParser
 | 
				
			||||||
  from libprs500 import __version__ as VERSION
 | 
					  from libprs500 import __version__ as VERSION
 | 
				
			||||||
  parser = OptionParser(usage="usage: %prog [options] mybook.lrf", version=VERSION)
 | 
					  parser = OptionParser(usage="usage: %prog [options] mybook.lrf\n\nWARNING: Based on reverse engineering the LRF format. Making changes may render your LRF file unreadable. ", version=VERSION)
 | 
				
			||||||
  parser.add_option("-t", "--title", action="store", type="string", dest="title", help="Set the book title")
 | 
					  parser.add_option("-t", "--title", action="store", type="string", dest="title", help="Set the book title")
 | 
				
			||||||
  parser.add_option("-a", "--author", action="store", type="string", dest="author", help="Set the author")
 | 
					  parser.add_option("-a", "--author", action="store", type="string", dest="author", help="Set the author")
 | 
				
			||||||
  parser.add_option("-c", "--category", action="store", type="string", dest="category", help="The category this book belongs to. E.g.: History")
 | 
					  parser.add_option("-c", "--category", action="store", type="string", dest="category", help="The category this book belongs to. E.g.: History")
 | 
				
			||||||
  parser.add_option("--thumbnail", action="store", type="string", dest="thumbnail", help="Path to a graphic that will be set as this files' thumbnail")
 | 
					  parser.add_option("--thumbnail", action="store", type="string", dest="thumbnail", help="Path to a graphic that will be set as this files' thumbnail")
 | 
				
			||||||
  parser.add_option("-p", "--page", action="store", type="string", dest="page", help="Set the current page number (I think)")
 | 
					  parser.add_option("--get-thumbnail", action="store_true", dest="get_thumbnail", default=False, help="Extract thumbnail from LRF file")
 | 
				
			||||||
 | 
					  parser.add_option("-p", "--page", action="store", type="string", dest="page", help="Don't know what this is for")
 | 
				
			||||||
  options, args = parser.parse_args()
 | 
					  options, args = parser.parse_args()
 | 
				
			||||||
  if len(args) != 1:
 | 
					  if len(args) != 1:
 | 
				
			||||||
    parser.print_help()
 | 
					    parser.print_help()
 | 
				
			||||||
@ -325,20 +326,17 @@ def main():
 | 
				
			|||||||
    lrf.thumbnail = f.read()
 | 
					    lrf.thumbnail = f.read()
 | 
				
			||||||
    f.close()
 | 
					    f.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  t = lrf.thumbnail
 | 
					  if options.get_thumbnail:
 | 
				
			||||||
  td = "None"
 | 
					    t = lrf.thumbnail
 | 
				
			||||||
  if t and len(t) > 0:
 | 
					    td = "None"
 | 
				
			||||||
    td = os.path.basename(args[0])+"_thumbnail_."+lrf.thumbail_extension()
 | 
					    if t and len(t) > 0:
 | 
				
			||||||
    f = open(td, "w")
 | 
					      td = os.path.basename(args[0])+"_thumbnail_."+lrf.thumbail_extension()
 | 
				
			||||||
    f.write(t)
 | 
					      f = open(td, "w")
 | 
				
			||||||
    f.close()
 | 
					      f.write(t)
 | 
				
			||||||
 | 
					      f.close()
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
  fields = LRFMetaFile.__dict__.items()
 | 
					  fields = LRFMetaFile.__dict__.items()
 | 
				
			||||||
  for f in fields:
 | 
					  for f in fields:
 | 
				
			||||||
    if "XML" in str(f): 
 | 
					    if "XML" in str(f): 
 | 
				
			||||||
      print str(f[1]) + ":", lrf.__getattribute__(f[0])
 | 
					      print str(f[1]) + ":", lrf.__getattribute__(f[0])
 | 
				
			||||||
  print "Thumbnail:", td
 | 
					  if options.get_thumbnail: print "Thumbnail:", td
 | 
				
			||||||
  print "object index offset:", hex(lrf.object_index_offset)
 | 
					 | 
				
			||||||
  print "toc object offset", hex(lrf.toc_object_offset)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -167,6 +167,7 @@ class field(object):
 | 
				
			|||||||
    obj.pack(val, start=self._start, fmt=self._fmt)
 | 
					    obj.pack(val, start=self._start, fmt=self._fmt)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
  def __repr__(self):
 | 
					  def __repr__(self):
 | 
				
			||||||
 | 
					    typ = ""
 | 
				
			||||||
    if self._fmt == DWORD: typ  = "unsigned int"
 | 
					    if self._fmt == DWORD: typ  = "unsigned int"
 | 
				
			||||||
    if self._fmt == DDWORD: typ = "unsigned long long"
 | 
					    if self._fmt == DDWORD: typ = "unsigned long long"
 | 
				
			||||||
    return "An " + typ + " stored in " + str(struct.calcsize(self._fmt)) + " bytes starting at byte " + str(self._start)
 | 
					    return "An " + typ + " stored in " + str(struct.calcsize(self._fmt)) + " bytes starting at byte " + str(self._start)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user