From a1c8fcff510cb4d57665c95f63d571e695ef94a7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 11 Aug 2007 00:19:04 +0000 Subject: [PATCH] Save to disk implemented. --- src/libprs500/devices/interface.py | 9 +++++ src/libprs500/ebooks/lrf/html/convert_from.py | 2 +- src/libprs500/gui2/__init__.py | 7 ++++ src/libprs500/gui2/device.py | 18 ++++++++-- src/libprs500/gui2/images.qrc | 1 + src/libprs500/gui2/library.py | 4 +++ src/libprs500/gui2/main.py | 31 ++++++++++++++--- src/libprs500/gui2/main.ui | 9 +++++ src/libprs500/library/database.py | 33 +++++++++++++++++-- 9 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/libprs500/devices/interface.py b/src/libprs500/devices/interface.py index e8b94b069d..bcaed5e495 100644 --- a/src/libprs500/devices/interface.py +++ b/src/libprs500/devices/interface.py @@ -162,6 +162,15 @@ class Device(object): ''' raise NotImplementedError() + def get_file(self, path, outfile, end_session=True): + ''' + Read the file at C{path} on the device and write it to outfile. + @param outfile: file object like C{sys.stdout} or the result of an C{open} call + ''' + raise NotImplementedError() + + + class BookList(list): ''' A list of books. Each Book object must have the fields: diff --git a/src/libprs500/ebooks/lrf/html/convert_from.py b/src/libprs500/ebooks/lrf/html/convert_from.py index 1399426374..17096d78b9 100644 --- a/src/libprs500/ebooks/lrf/html/convert_from.py +++ b/src/libprs500/ebooks/lrf/html/convert_from.py @@ -1231,7 +1231,7 @@ class HTMLConverter(object): except Exception, err: print 'WARNING: An error occurred while processing a table:', err print 'Ignoring table markup for table:' - print str(tag)[:100] + print str(tag)[:300] self.in_table = False self.process_children(tag, tag_css) else: diff --git a/src/libprs500/gui2/__init__.py b/src/libprs500/gui2/__init__.py index 26561a9f17..12cf3301b4 100644 --- a/src/libprs500/gui2/__init__.py +++ b/src/libprs500/gui2/__init__.py @@ -186,6 +186,13 @@ class FileDialog(QObject): settings.setValue(self.dialog_name, QVariant(self.fd.saveState())) +def choose_dir(window, name, title): + settings = QSettings() + dir = settings.value(name, QVariant(os.path.expanduser('~'))).toString() + dir = qstring_to_unicode(QFileDialog.getExistingDirectory(window, title, dir)) + if os.path.exists(dir): + return dir + def choose_files(window, name, title, filters=[], all_files=True, select_only_single_file=False): ''' diff --git a/src/libprs500/gui2/device.py b/src/libprs500/gui2/device.py index 3a24b92797..381342d2a4 100644 --- a/src/libprs500/gui2/device.py +++ b/src/libprs500/gui2/device.py @@ -12,7 +12,7 @@ ## 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.Warning -import sys +import sys, os from PyQt4.QtCore import QThread, SIGNAL, QObject @@ -111,9 +111,21 @@ class DeviceManager(QObject): def delete_books_func(self): '''Remove books from device''' def delete_books(updater, paths): - '''Delete books from device''' + '''Remove books from device''' self.device.delete_books(paths, end_session=True) return delete_books def remove_books_from_metadata(self, paths, booklists): - self.device_class.remove_books_from_metadata(paths, booklists) \ No newline at end of file + self.device_class.remove_books_from_metadata(paths, booklists) + + def save_books_func(self): + '''Copy books from device to disk''' + def save_books(updater, paths, target): + '''Copy books from device to disk''' + self.device.set_progress_reporter(updater) + for path in paths: + name = path.rpartition('/')[2] + f = open(os.path.join(target, name), 'wb') + self.device.get_file(path, f) + f.close() + return save_books \ No newline at end of file diff --git a/src/libprs500/gui2/images.qrc b/src/libprs500/gui2/images.qrc index 2205db8bd1..e968fb9e64 100644 --- a/src/libprs500/gui2/images.qrc +++ b/src/libprs500/gui2/images.qrc @@ -29,6 +29,7 @@ images/mimetypes/zip.svg images/plus.svg images/reader.svg + images/save.svg images/sd.svg images/sync.svg images/trash.svg diff --git a/src/libprs500/gui2/library.py b/src/libprs500/gui2/library.py index 9818ece7e1..d5fde3b17c 100644 --- a/src/libprs500/gui2/library.py +++ b/src/libprs500/gui2/library.py @@ -103,6 +103,10 @@ class BooksModel(QAbstractTableModel): ''' Return list indices of all cells in index.row()''' return [ self.index(index.row(), c) for c in range(self.columnCount(None))] + def save_to_disk(self, rows, path): + rows = [row.row() for row in rows] + self.db.export_to_dir(path, rows) + def delete_books(self, indices): ids = [ self.id(i) for i in indices ] for id in ids: diff --git a/src/libprs500/gui2/main.py b/src/libprs500/gui2/main.py index 67c96ec396..6a42d99626 100644 --- a/src/libprs500/gui2/main.py +++ b/src/libprs500/gui2/main.py @@ -19,13 +19,13 @@ from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \ from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox from PyQt4.QtSvg import QSvgRenderer -from libprs500 import __version__, __appname__, iswindows, isosx +from libprs500 import __version__, __appname__ from libprs500.ebooks.metadata.meta import get_metadata from libprs500.devices.errors import FreeSpaceError from libprs500.devices.interface import Device from libprs500.gui2 import APP_TITLE, warning_dialog, choose_files, error_dialog, \ initialize_file_icon_provider, BOOK_EXTENSIONS, \ - pixmap_to_data + pixmap_to_data, choose_dir from libprs500.gui2.main_ui import Ui_MainWindow from libprs500.gui2.device import DeviceDetector, DeviceManager from libprs500.gui2.status import StatusBar @@ -95,13 +95,13 @@ class Main(QObject, Ui_MainWindow): QObject.connect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_main_memory) QObject.connect(sm.actions()[0], SIGNAL('triggered(bool)'), self.sync_to_main_memory) QObject.connect(sm.actions()[1], SIGNAL('triggered(bool)'), self.sync_to_card) - + QObject.connect(self.action_save, SIGNAL("triggered(bool)"), self.save_to_disk) self.action_sync.setMenu(sm) self.action_edit.setMenu(md) self.tool_bar.addAction(self.action_sync) self.tool_bar.addAction(self.action_edit) self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu) - + ####################### Library view ######################## self.library_view.set_database(self.database_path) for func, target in [ @@ -423,6 +423,29 @@ class Main(QObject, Ui_MainWindow): ############################################################################ + ############################## Save to disk ################################ + def save_to_disk(self, checked): + rows = self.current_view().selectionModel().selectedRows() + if not rows or len(rows) == 0: + d = error_dialog(self.window, 'Cannot save to disk', 'No books selected') + d.exec_() + return + dir = choose_dir(self.window, 'save to disk dialog', 'Choose destination directory') + if not dir: + return + if self.current_view() == self.library_view: + self.current_view().model().save_to_disk(rows, dir) + else: + paths = self.current_view().model().paths(rows) + self.job_manager.run_device_job(self.books_saved, + self.device_manager.save_books_func(), paths, dir) + + def books_saved(self, id, description, result, exception, formatted_traceback): + if exception: + self.device_job_exception(id, description, exception, formatted_traceback) + return + + ############################################################################ def location_selected(self, location): ''' Called when a location icon is clicked (e.g. Library) diff --git a/src/libprs500/gui2/main.ui b/src/libprs500/gui2/main.ui index 6aa762b485..afc08087f2 100644 --- a/src/libprs500/gui2/main.ui +++ b/src/libprs500/gui2/main.ui @@ -319,6 +319,7 @@ + @@ -378,6 +379,14 @@ Send to device + + + :/images/save.svg + + + Save to disk + + diff --git a/src/libprs500/library/database.py b/src/libprs500/library/database.py index 529938d1e1..6cbcd73358 100644 --- a/src/libprs500/library/database.py +++ b/src/libprs500/library/database.py @@ -12,11 +12,12 @@ ## 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. +from libprs500 import __appname__ """ Backend that implements storage of ebooks in an sqlite database. """ import sqlite3 as sqlite -import datetime, re +import datetime, re, os from zlib import compress, decompress class Concatenate(object): @@ -897,4 +898,32 @@ class LibraryDatabase(object): except TypeError: #If data and cache are the same object pass self.conn.execute('DELETE FROM books WHERE id=?', (id,)) - self.conn.commit() \ No newline at end of file + self.conn.commit() + + def export_to_dir(self, dir, indices): + by_author = {} + for index in indices: + id = self.id(index) + au = self.conn.execute('SELECT concat(sort) FROM authors WHERE authors.id IN (SELECT author from books_authors_link WHERE book=?)', (id,)).fetchone()[0] + if not by_author.has_key(au): + by_author[au] = [] + by_author[au].append(index) + for au in by_author.keys(): + apath = os.path.join(dir, au) + if not os.path.exists(apath): + os.mkdir(apath) + for idx in by_author[au]: + title = self.title(idx) + tpath = os.path.join(apath, title) + id = str(self.id(idx)) + if not os.path.exists(tpath): + os.mkdir(tpath) + for fmt in self.formats(idx).split(','): + data = self.format(idx, fmt) + f = open(os.path.join(tpath, __appname__+'_'+id+'.'+fmt.lower()), 'wb') + f.write(data) + +if __name__ == '__main__': + db = LibraryDatabase('/home/kovid/library1.db') + db.refresh('title', True) + db.export_to_dir('/tmp/test', range(1, 10)) \ No newline at end of file