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