Created conversion dialog.

This commit is contained in:
Kovid Goyal 2007-08-17 02:58:51 +00:00
parent 653cd41d50
commit 22e3f9aaf9
22 changed files with 1779 additions and 116 deletions

View File

@ -18,7 +18,7 @@ __docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
__appname__ = 'libprs500'
import sys, os, logging
import sys, os, logging, mechanize
iswindows = 'win32' in sys.platform.lower()
isosx = 'darwin' in sys.platform.lower()
@ -75,3 +75,12 @@ def extract(path, dir):
if not extractor:
raise Exception('Unknown archive type')
extractor(path, dir)
def browser():
opener = mechanize.Browser()
opener.set_handle_refresh(True)
opener.set_handle_robots(False)
opener.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; i686 Linux; en_US; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4')]
return opener

View File

@ -72,14 +72,10 @@ def option_parser(usage):
parser = OptionParser(usage=usage, version=__appname__+' '+__version__,
epilog='Created by '+__author__)
metadata = parser.add_option_group('METADATA OPTIONS')
metadata.add_option('--header', action='store_true', default=False, dest='header',
help='Add a header to all the pages with title and author.')
metadata.add_option('--headerformat', default="%t by %a", dest='headerformat', type='string',
help='Set the format of the header. %a is replaced by the author and %t by the title. Default is %default')
metadata.add_option("-t", "--title", action="store", type="string", default=None,\
dest="title", help="Set the title. Default: filename.")
metadata.add_option("-a", "--author", action="store", type="string", \
dest="author", help="Set the author. Default: %default", default='Unknown')
dest="author", help="Set the author(s). Multiple authors should be set as a comma separated list. Default: %default", default='Unknown')
metadata.add_option("--comment", action="store", type="string", \
dest="freetext", help="Set the comment.", default=' ')
metadata.add_option("--category", action="store", type="string", \
@ -90,14 +86,14 @@ def option_parser(usage):
help='Sort key for the author')
metadata.add_option('--publisher', action='store', default='Unknown', dest='publisher',
help='Publisher')
metadata.add_option('--cover', action='store', dest='cover', default=None, \
help='Path to file containing image to be used as cover')
profiles=['prs500']
parser.add_option('-o', '--output', action='store', default=None, \
help='Output file name. Default is derived from input filename')
parser.add_option('--ignore-tables', action='store_true', default=False, dest='ignore_tables',
help='Render HTML tables as blocks of text instead of actual tables. This is neccessary if the HTML contains very large or complex tables.')
laf = parser.add_option_group('LOOK AND FEEL')
laf.add_option('--cover', action='store', dest='cover', default=None, \
help='Path to file containing image to be used as cover')
laf.add_option('--font-delta', action='store', type='float', default=0., \
help="""Increase the font size by 2 * FONT_DELTA pts and """
'''the line spacing by FONT_DELTA pts. FONT_DELTA can be a fraction.'''
@ -109,12 +105,19 @@ def option_parser(usage):
help='Set the space between words in pts. Default is %default')
laf.add_option('--blank-after-para', action='store_true', default=False,
dest='blank_after_para', help='Separate paragraphs by blank lines.')
laf.add_option('--header', action='store_true', default=False, dest='header',
help='Add a header to all the pages with title and author.')
laf.add_option('--headerformat', default="%t by %a", dest='headerformat', type='string',
help='Set the format of the header. %a is replaced by the author and %t by the title. Default is %default')
page = parser.add_option_group('PAGE OPTIONS')
page.add_option('-p', '--profile', default=PRS500_PROFILE, dest='profile', type='choice',
choices=profiles, action='callback', callback=profile_from_string,
help='''Profile of the target device for which this LRF is '''
'''being generated. Default: ''' + profiles[0] + \
''' Supported profiles: '''+', '.join(profiles))
'''being generated. The profile determines things like the '''
'''resolution and screen size of the target device. '''
'''Default: ''' + profiles[0] + ''' Supported profiles: '''+\
', '.join(profiles))
page.add_option('--left-margin', default=20, dest='left_margin', type='int',
help='''Left margin of page. Default is %default px.''')
page.add_option('--right-margin', default=20, dest='right_margin', type='int',
@ -134,14 +137,14 @@ def option_parser(usage):
'''matches will be ignored. Defaults to %default''')
chapter = parser.add_option_group('CHAPTER OPTIONS')
chapter.add_option('--disable-chapter-detection', action='store_false',
default=True, dest='chapter_detection',
default=False, dest='disable_chapter_detection',
help='''Prevent html2lrf from automatically inserting page breaks'''
''' before what it thinks are chapters.''')
chapter.add_option('--chapter-regex', dest='chapter_regex',
default='chapter|book|appendix',
help='''The regular expression used to detect chapter titles.'''
''' It is searched for in heading tags. Defaults to %default''')
chapter.add_option('--page-break-before', dest='page_break', default='h[12]',
chapter.add_option('--page-break-before-tag', dest='page_break', default='h[12]',
help='''If html2lrf does not find any page breaks in the '''
'''html file and cannot detect chapter headings, it will '''
'''automatically insert page-breaks before the tags whose '''
@ -151,8 +154,8 @@ def option_parser(usage):
'''there are no really long pages as this degrades the page '''
'''turn performance of the LRF. Thus this option is ignored '''
'''if the current page has only a few elements.''')
chapter.add_option('--force-page-break-before', dest='force_page_break',
default='$', help='Like --page-break-before, but page breaks are forced.')
chapter.add_option('--force-page-break-before-tag', dest='force_page_break',
default='$', help='Force a page break before tags whoose names match this regular expression.')
chapter.add_option('--force-page-break-before-attr', dest='force_page_break_attr',
default='$,,$', help='Force a page break before an element having the specified attribute. The format for this option is tagname regexp,attribute name,attribute value regexp. For example to match all heading tags that have the attribute class="chapter" you would use "h\d,class,chapter". Default is %default''')
prepro = parser.add_option_group('PREPROCESSING OPTIONS')

View File

@ -1,4 +1,6 @@
UI = main_ui.py dialogs/metadata_single_ui.py dialogs/metadata_bulk_ui.py dialogs/jobs_ui.py dialogs/conversion_error_ui.py
UI = main_ui.py dialogs/metadata_single_ui.py dialogs/metadata_bulk_ui.py dialogs/jobs_ui.py \
dialogs/conversion_error_ui.py dialogs/lrf_single_ui.py dialogs/choose_format_ui.py \
dialogs/password_ui.py
RC = images_rc.pyc
%_ui.py : %.ui

View File

@ -41,7 +41,7 @@ def error_dialog(parent, title, msg):
return d
def qstring_to_unicode(q):
return unicode(q.toUtf8(), 'utf8')
return unicode(q)
def human_readable(size):
""" Convert a size in bytes into a human readable form """

View File

@ -102,6 +102,7 @@ class DeviceManager(QObject):
'''Upload books to device'''
def upload_books(updater, files, names, on_card=False):
'''Upload books to device: '''
self.device.set_progress_reporter(updater)
return self.device.upload_books(files, names, on_card, end_session=False)
return upload_books

View File

@ -13,20 +13,3 @@
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
'''Various dialogs used in the GUI'''
from PyQt4.QtCore import QObject
from PyQt4.QtGui import QDialog
class Dialog(QObject):
def __init__(self, window):
QObject.__init__(self, window)
self.dialog = QDialog(window)
self.accept = self.dialog.accept
self.reject = self.dialog.reject
self._close_event = self.dialog.closeEvent
self.dialog.closeEvent = self.close_event
self.window = window
self.isVisible = self.dialog.isVisible
def close_event(self, e):
e.accept()

View File

@ -0,0 +1,38 @@
## Copyright (C) 2007 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.
from PyQt4.QtGui import QDialog, QListWidgetItem
from libprs500.gui2 import file_icon_provider
from libprs500.gui2.dialogs.choose_format_ui import Ui_ChooseFormatDialog
class ChooseFormatDialog(QDialog, Ui_ChooseFormatDialog):
def __init__(self, window, msg, formats):
QDialog.__init__(self, window)
Ui_ChooseFormatDialog.__init__(self)
self.setupUi(self)
self.msg.setText(msg)
for format in formats:
self.formats.addItem(QListWidgetItem(file_icon_provider().icon_from_ext(format.lower()),
format.upper()))
self._formats = formats
self.formats.setCurrentRow(0)
def format(self):
self._formats[self.formats.currentRow()]

View File

@ -0,0 +1,85 @@
<ui version="4.0" >
<class>ChooseFormatDialog</class>
<widget class="QDialog" name="ChooseFormatDialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>507</width>
<height>377</height>
</rect>
</property>
<property name="windowTitle" >
<string>Choose Format</string>
</property>
<property name="windowIcon" >
<iconset resource="../images.qrc" >:/images/mimetypes/unknown.svg</iconset>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QLabel" name="msg" >
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="formats" >
<property name="iconSize" >
<size>
<width>64</width>
<height>64</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../images.qrc" />
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ChooseFormatDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel" >
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ChooseFormatDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel" >
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -14,16 +14,21 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
''''''
from libprs500.gui2.dialogs import Dialog
from PyQt4.QtGui import QDialog
from libprs500.gui2.dialogs.conversion_error_ui import Ui_ConversionErrorDialog
class ConversionErrorDialog(Dialog, Ui_ConversionErrorDialog):
class ConversionErrorDialog(QDialog, Ui_ConversionErrorDialog):
def __init__(self, window, title, html):
def __init__(self, window, title, html, show=False):
QDialog.__init__(self, window)
Ui_ConversionErrorDialog.__init__(self)
Dialog.__init__(self, window)
self.setupUi(self.dialog)
html = '<html><body>' + html + '</body></html>'
self.dialog.setWindowTitle(title)
self.text.setHtml(html)
self.dialog.show()
self.setupUi(self)
self.setWindowTitle(title)
self.set_message(html)
if show:
self.show()
def set_message(self, html):
self.text.setHtml('<html><body>%s</body></html'%(html,))

View File

@ -15,30 +15,23 @@
'''Display active jobs'''
from PyQt4.QtCore import Qt, QObject, SIGNAL
from PyQt4.QtGui import QDialog
from libprs500.gui2.dialogs import Dialog
from libprs500.gui2.dialogs.jobs_ui import Ui_JobsDialog
from libprs500 import __appname__
class JobsDialog(Ui_JobsDialog, Dialog):
class JobsDialog(QDialog, Ui_JobsDialog):
def __init__(self, window, model):
QDialog.__init__(self, window)
Ui_JobsDialog.__init__(self)
Dialog.__init__(self, window)
self.setupUi(self.dialog)
self.setupUi(self)
self.jobs_view.setModel(model)
self.model = model
self.dialog.setWindowModality(Qt.NonModal)
self.dialog.setWindowTitle(__appname__ + ' - Active Jobs')
self.setWindowModality(Qt.NonModal)
self.setWindowTitle(__appname__ + ' - Active Jobs')
QObject.connect(self.jobs_view.model(), SIGNAL('modelReset()'),
self.jobs_view.resizeColumnsToContents)
def show(self):
self.dialog.show()
self.jobs_view.resizeColumnsToContents()
def hide(self):
self.dialog.hide()
def close_event(self, e):
def closeEvent(self, e):
self.jobs_view.write_settings()
e.accept()

View File

@ -0,0 +1,277 @@
## Copyright (C) 2007 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 os
from PyQt4.QtCore import QObject, SIGNAL, Qt
from PyQt4.QtGui import QAbstractSpinBox, QLineEdit, QCheckBox, QDialog, QPixmap
from libprs500.gui2.dialogs.lrf_single_ui import Ui_LRFSingleDialog
from libprs500.gui2.dialogs.choose_format import ChooseFormatDialog
from libprs500.gui2 import qstring_to_unicode, error_dialog, \
pixmap_to_data, choose_images
from libprs500.ebooks.lrf import option_parser
class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
PARSER = option_parser('')
PREPROCESS_OPTIONS = [ o for o in PARSER.option_groups if o.title == 'PREPROCESSING OPTIONS'][0].option_list
@classmethod
def options(cls):
options = cls.PARSER.option_list
for g in cls.PARSER.option_groups:
options.extend(g.option_list)
for opt in options:
yield opt
@classmethod
def option_to_name(cls, opt):
src = opt.get_opt_string()
return 'gui_' + src[2:].replace('-', '_')
def __init__(self, window, db, row):
QDialog.__init__(self, window)
Ui_LRFSingleDialog.__init__(self)
self.setupUi(self)
self.categoryList.setCurrentRow(0)
QObject.connect(self.categoryList, SIGNAL('itemEntered(QListWidgetItem *)'),
self.show_category_help)
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), self.select_cover)
self.categoryList.leaveEvent = self.reset_help
self.reset_help()
self.setup_tooltips()
self.initialize_options()
self.db = db
self.row = row.row()
self.id = self.db.id(self.row)
self.cover_changed = False
self.cpixmap = None
self.changed = False
self.read_saved_options()
self.initialize_metadata()
formats = self.db.formats(self.row)
if not formats:
d = error_dialog(self, 'No availabla formats', 'Cannot convert as this book has not available formats')
d.exec_()
formats = [i.upper() for i in formats.split(',')]
self.selected_format = None
try:
formats.remove('LRF')
except ValueError:
pass
if len(formats) > 1:
d = ChooseFormatDialog(self, 'Choose the format to convert into LRF', formats)
d.exec_()
if d.result() == QDialog.Accepted:
self.selected_format = d.format()
else:
self.selected_format = formats[0]
if self.selected_format:
self.setWindowTitle('Convert %s to LRF'%(self.selected_format,))
def read_saved_options(self):
cmdline = self.db.conversion_options(self.id, 'lrf')
print 1, cmdline
if cmdline:
for opt in self.options():
try:
i = cmdline.index(opt.get_opt_string())
except ValueError:
continue
guiname = self.option_to_name(opt)
try:
obj = getattr(self, guiname)
except AttributeError:
continue
if isinstance(obj, QCheckBox):
obj.setCheckState(Qt.Checked)
elif isinstance(obj, QAbstractSpinBox):
obj.setValue(cmdline[i+1])
elif isinstance(obj, QLineEdit):
obj.setText(cmdline[i+1])
profile = cmdline[cmdline.index('--profile')+1]
self.gui_profile.setCurrentIndex(self.gui_profile.findText(profile))
for prepro in self.PREPROCESS_OPTIONS:
ops = prepro.get_opt_string()
if ops in cmdline:
self.preprocess.setCurrentIndex(self.preprocess.findText(ops[2:]))
break
def select_cover(self, checked):
files = choose_images(self, 'change cover dialog',
u'Choose cover for ' + qstring_to_unicode(self.title.text()))
if not files:
return
_file = files[0]
if _file:
_file = os.path.abspath(_file)
if not os.access(_file, os.R_OK):
d = error_dialog(self.window, 'Cannot read',
'You do not have permission to read the file: ' + _file)
d.exec_()
return
cf, cover = None, None
try:
cf = open(_file, "rb")
cover = cf.read()
except IOError, e:
d = error_dialog(self.window, 'Error reading file',
"<p>There was an error reading from file: <br /><b>" + _file + "</b></p><br />"+str(e))
d.exec_()
if cover:
pix = QPixmap()
pix.loadFromData(cover)
if pix.isNull():
d = error_dialog(self.window, _file + " is not a valid picture")
d.exec_()
else:
self.cover_path.setText(_file)
self.cover.setPixmap(pix)
self.cover_changed = True
self.cpixmap = pix
def initialize_metadata(self):
db, row = self.db, self.row
self.id = self.db.id(row)
self.gui_title.setText(db.title(row))
au = self.db.authors(row)
self.gui_author.setText(au if au else '')
aus = self.db.author_sort(row)
self.gui_author_sort.setText(aus if aus else '')
pub = self.db.publisher(row)
self.gui_publisher.setText(pub if pub else '')
tags = self.db.tags(row)
self.tags.setText(tags if tags else '')
comments = self.db.comments(row)
self.gui_comment.setPlainText(comments if comments else '')
cover = self.db.cover(row)
if cover:
pm = QPixmap()
pm.loadFromData(cover)
if not pm.isNull():
self.cover.setPixmap(pm)
def initialize_options(self):
'''Initialize non metadata options from the defaults.'''
for name in self.option_map.keys():
default = self.option_map[name].default
obj = getattr(self, name)
if isinstance(obj, QAbstractSpinBox):
obj.setValue(default)
elif isinstance(obj, QLineEdit) and default:
obj.setText(default)
elif isinstance(obj, QCheckBox):
state = Qt.Checked if default else Qt.Unchecked
obj.setCheckState(state)
self.gui_headerformat.setDisabled(True)
self.preprocess.addItem('No preprocessing')
for opt in self.PREPROCESS_OPTIONS:
self.preprocess.addItem(opt.get_opt_string()[2:])
ph = 'Preprocess the file before converting to LRF. This is useful if you know that the file is from a specific source. Known sources:'
ph += '<ol><li><b>baen</b> - Books from BAEN Publishers</li>'
ph += '<li><b>pdftohtml</b> - HTML files that are the output of the program pdftohtml</li>'
self.preprocess.setToolTip(ph)
self.preprocess.setWhatsThis(ph)
for profile in self.PARSER.get_option('--profile').choices:
if self.gui_profile.findText(profile) < 0:
self.gui_profile.addItem(profile)
def setup_tooltips(self):
def show_item_help(obj, event):
self.set_help(obj.toolTip())
self.option_map = {}
for opt in self.options():
try:
help = opt.help.replace('%default', str(opt.default))
except (ValueError, TypeError):
help = opt.help
guiname = self.option_to_name(opt)
if hasattr(self, guiname):
obj = getattr(self, guiname)
obj.setToolTip(help)
obj.setWhatsThis(help)
self.option_map[guiname] = opt
obj.__class__.enterEvent = show_item_help
obj.leaveEvent = self.reset_help
self.preprocess.__class__.enterEvent = show_item_help
self.preprocess.leaveEvent = self.reset_help
def show_category_help(self, item):
text = qstring_to_unicode(item.text())
help = {
u'Metadata' : 'Specify metadata such as title and author for the book.<p>Metadata will be updated in the database as well as the generated LRF file.',
u'Look & Feel' : 'Adjust the look of the generated LRF file by specifying things like font sizes and the spacing between words.',
u'Page Setup' : 'Specify the page settings like margins and the screen size of the target device.',
u'Chapter Detection' : 'Fine tune the detection of chapter and section headings.',
}
self.set_help(help[text])
def set_help(self, msg):
self.help_view.setHtml('<html><body>%s</body></html>'%(msg,))
def reset_help(self, *args):
self.set_help('<font color="gray">No help available</font>')
if args:
args[0].accept()
def build_commandline(self):
cmd = []
for name in self.option_map.keys():
opt = self.option_map[name].get_opt_string()
obj = getattr(self, name)
if isinstance(obj, QAbstractSpinBox):
cmd.extend([opt, obj.value()])
elif isinstance(obj, QLineEdit):
val = qstring_to_unicode(obj.text())
if val:
cmd.extend([opt, val])
elif isinstance(obj, QCheckBox):
if obj.checkState() == Qt.Checked:
cmd.append(opt)
text = qstring_to_unicode(self.preprocess.currentText())
if text != 'No preprocessing':
cmd.append('--'+text)
cmd.extend(['--profile', qstring_to_unicode(self.gui_profile.currentText())])
return cmd
def write_metadata(self):
title = qstring_to_unicode(self.gui_title.text())
self.db.set_title(self.id, title)
au = qstring_to_unicode(self.gui_author.text()).split(',')
if au: self.db.set_authors(self.id, au)
aus = qstring_to_unicode(self.gui_author_sort.text())
if aus: self.db.set_author_sort(self.id, aus)
print self.db.author_sort(self.row)
self.db.set_publisher(self.id, qstring_to_unicode(self.gui_publisher.text()))
self.db.set_tags(self.id, qstring_to_unicode(self.tags.text()).split(','))
self.db.set_series(self.id, qstring_to_unicode(self.series.currentText()))
self.db.set_series_index(self.id, self.series_index.value())
if self.cover_changed:
self.db.set_cover(self.id, pixmap_to_data(self.cover.pixmap()))
def accept(self):
cmdline = self.build_commandline()
# TODO: put cover into tempfile
self.write_metadata()
self.db.set_conversion_options(self.id, 'lrf', cmdline)
QDialog.accept(self)

View File

@ -0,0 +1,948 @@
<ui version="4.0" >
<class>LRFSingleDialog</class>
<widget class="QDialog" name="LRFSingleDialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>850</width>
<height>671</height>
</rect>
</property>
<property name="windowTitle" >
<string>Convert to LRF</string>
</property>
<property name="windowIcon" >
<iconset resource="../images.qrc" >:/images/convert.svg</iconset>
</property>
<layout class="QGridLayout" >
<item rowspan="2" row="0" column="0" >
<widget class="QGroupBox" name="category" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Minimum" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title" >
<string>Category</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QListWidget" name="categoryList" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Minimum" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font" >
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="mouseTracking" >
<bool>true</bool>
</property>
<property name="showDropIndicator" stdset="0" >
<bool>false</bool>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize" >
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="textElideMode" >
<enum>Qt::ElideLeft</enum>
</property>
<property name="movement" >
<enum>QListView::Snap</enum>
</property>
<property name="flow" >
<enum>QListView::TopToBottom</enum>
</property>
<property name="isWrapping" stdset="0" >
<bool>false</bool>
</property>
<property name="spacing" >
<number>40</number>
</property>
<property name="viewMode" >
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes" >
<bool>true</bool>
</property>
<item>
<property name="text" >
<string>Metadata</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >:/images/metadata.svg</iconset>
</property>
</item>
<item>
<property name="text" >
<string>Look &amp; Feel</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >:/images/lookfeel.svg</iconset>
</property>
</item>
<item>
<property name="text" >
<string>Page Setup</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >:/images/page.svg</iconset>
</property>
</item>
<item>
<property name="text" >
<string>Chapter Detection</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >:/images/chapters.svg</iconset>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" >
<widget class="QGroupBox" name="groupBox_3" >
<property name="title" >
<string>Options</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QStackedWidget" name="stack" >
<property name="currentIndex" >
<number>0</number>
</property>
<widget class="QWidget" name="metadata_page" >
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label" >
<property name="text" >
<string>&amp;Title: </string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>gui_title</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="4" >
<widget class="QLineEdit" name="gui_title" >
<property name="toolTip" >
<string>Change the title of this book</string>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>&amp;Author(s): </string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>gui_author</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2" >
<widget class="QLineEdit" name="gui_author" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Change the author(s) of this book. Multiple authors should be separated by a comma</string>
</property>
</widget>
</item>
<item row="1" column="3" >
<widget class="QLabel" name="label_6" >
<property name="text" >
<string>So&amp;rt:</string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>gui_author_sort</cstring>
</property>
</widget>
</item>
<item row="1" column="4" >
<widget class="QLineEdit" name="gui_author_sort" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Change the author(s) of this book. Multiple authors should be separated by a comma</string>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_3" >
<property name="text" >
<string>&amp;Publisher: </string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>gui_publisher</cstring>
</property>
</widget>
</item>
<item row="2" column="1" colspan="4" >
<widget class="QLineEdit" name="gui_publisher" >
<property name="toolTip" >
<string>Change the publisher of this book</string>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>Ta&amp;gs: </string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>tags</cstring>
</property>
</widget>
</item>
<item row="3" column="1" colspan="4" >
<widget class="QLineEdit" name="tags" >
<property name="toolTip" >
<string>Tags categorize the book. This is particularly useful while searching. &lt;br>&lt;br>They can be any words or phrases, separated by commas.</string>
</property>
</widget>
</item>
<item row="4" column="0" >
<widget class="QLabel" name="label_7" >
<property name="text" >
<string>&amp;Series:</string>
</property>
<property name="textFormat" >
<enum>Qt::PlainText</enum>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>series</cstring>
</property>
</widget>
</item>
<item row="4" column="1" >
<widget class="QComboBox" name="series" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>List of known series. You can add new series.</string>
</property>
<property name="whatsThis" >
<string>List of known series. You can add new series.</string>
</property>
<property name="editable" >
<bool>true</bool>
</property>
<property name="insertPolicy" >
<enum>QComboBox::InsertAlphabetically</enum>
</property>
<property name="sizeAdjustPolicy" >
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item row="4" column="2" colspan="3" >
<widget class="QSpinBox" name="series_index" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="toolTip" >
<string>Series index.</string>
</property>
<property name="whatsThis" >
<string>Series index.</string>
</property>
<property name="prefix" >
<string>Book </string>
</property>
<property name="minimum" >
<number>1</number>
</property>
<property name="maximum" >
<number>10000</number>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2" >
<widget class="QGroupBox" name="groupBox_2" >
<property name="title" >
<string>Comments</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QTextEdit" name="gui_comment" />
</item>
</layout>
</widget>
</item>
<item row="5" column="2" colspan="3" >
<widget class="QGroupBox" name="groupBox_4" >
<property name="title" >
<string>Book Cover</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<layout class="QHBoxLayout" >
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>81</width>
<height>181</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="cover" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize" >
<size>
<width>250</width>
<height>180</height>
</size>
</property>
<property name="text" >
<string/>
</property>
<property name="pixmap" >
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
</property>
<property name="scaledContents" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>71</width>
<height>181</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0" >
<layout class="QVBoxLayout" >
<property name="spacing" >
<number>6</number>
</property>
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5" >
<property name="text" >
<string>Change &amp;cover image:</string>
</property>
<property name="buddy" >
<cstring>cover_path</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>6</number>
</property>
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="cover_path" >
<property name="readOnly" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="cover_button" >
<property name="toolTip" >
<string>Browse for an image to use as the cover of this book.</string>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >:/images/document_open.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="lookandfeel_page" >
<layout class="QGridLayout" >
<item row="0" column="0" colspan="3" >
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>&amp;Font delta:</string>
</property>
<property name="buddy" >
<cstring>gui_font_delta</cstring>
</property>
</widget>
</item>
<item row="0" column="3" >
<widget class="QDoubleSpinBox" name="gui_font_delta" >
<property name="buttonSymbols" >
<enum>QAbstractSpinBox::PlusMinus</enum>
</property>
<property name="suffix" >
<string> pts</string>
</property>
<property name="decimals" >
<number>1</number>
</property>
<property name="minimum" >
<double>-5.000000000000000</double>
</property>
<property name="maximum" >
<double>5.000000000000000</double>
</property>
<property name="singleStep" >
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3" >
<widget class="QLabel" name="label_9" >
<property name="text" >
<string>&amp;Word spacing:</string>
</property>
<property name="textFormat" >
<enum>Qt::PlainText</enum>
</property>
<property name="buddy" >
<cstring>gui_wordspace</cstring>
</property>
</widget>
</item>
<item row="1" column="3" >
<widget class="QDoubleSpinBox" name="gui_wordspace" >
<property name="buttonSymbols" >
<enum>QAbstractSpinBox::PlusMinus</enum>
</property>
<property name="suffix" >
<string> pts</string>
</property>
<property name="decimals" >
<number>1</number>
</property>
<property name="minimum" >
<double>0.000000000000000</double>
</property>
<property name="maximum" >
<double>10.000000000000000</double>
</property>
<property name="singleStep" >
<double>0.100000000000000</double>
</property>
<property name="value" >
<double>2.500000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_16" >
<property name="text" >
<string>&amp;Preprocess:</string>
</property>
<property name="buddy" >
<cstring>preprocess</cstring>
</property>
</widget>
</item>
<item row="2" column="2" colspan="2" >
<widget class="QComboBox" name="preprocess" />
</item>
<item row="3" column="0" colspan="4" >
<widget class="QGroupBox" name="groupBox_5" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Minimum" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title" >
<string>Header</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QCheckBox" name="gui_header" >
<property name="text" >
<string>&amp;Show header</string>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_10" >
<property name="text" >
<string>&amp;Header format:</string>
</property>
<property name="buddy" >
<cstring>gui_headerformat</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="gui_headerformat" />
</item>
</layout>
</widget>
</item>
<item row="4" column="0" colspan="3" >
<widget class="QCheckBox" name="gui_disable_autorotation" >
<property name="text" >
<string>Disable auto &amp;rotation of images</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="3" >
<widget class="QCheckBox" name="gui_blank_after_para" >
<property name="text" >
<string>Insert &amp;blank lines between paragraphs</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="3" >
<widget class="QCheckBox" name="gui_ignore_tables" >
<property name="text" >
<string>Ignore &amp;tables</string>
</property>
</widget>
</item>
<item row="7" column="1" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<size>
<width>20</width>
<height>41</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="pagesetup_page" >
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_11" >
<property name="text" >
<string>&amp;Profile:</string>
</property>
<property name="buddy" >
<cstring>gui_profile</cstring>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QComboBox" name="gui_profile" >
<property name="currentIndex" >
<number>-1</number>
</property>
<property name="minimumContentsLength" >
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_12" >
<property name="text" >
<string>&amp;Left Margin:</string>
</property>
<property name="buddy" >
<cstring>gui_left_margin</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QSpinBox" name="gui_left_margin" >
<property name="suffix" >
<string> px</string>
</property>
<property name="maximum" >
<number>100</number>
</property>
<property name="value" >
<number>20</number>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_13" >
<property name="text" >
<string>&amp;Right Margin:</string>
</property>
<property name="buddy" >
<cstring>gui_right_margin</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QSpinBox" name="gui_right_margin" >
<property name="suffix" >
<string> px</string>
</property>
<property name="maximum" >
<number>100</number>
</property>
<property name="value" >
<number>20</number>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QLabel" name="label_14" >
<property name="text" >
<string>&amp;Top Margin:</string>
</property>
<property name="buddy" >
<cstring>gui_top_margin</cstring>
</property>
</widget>
</item>
<item row="3" column="1" >
<widget class="QSpinBox" name="gui_top_margin" >
<property name="suffix" >
<string> px</string>
</property>
<property name="maximum" >
<number>100</number>
</property>
<property name="value" >
<number>10</number>
</property>
</widget>
</item>
<item row="4" column="0" >
<widget class="QLabel" name="label_15" >
<property name="text" >
<string>&amp;Bottom Margin:</string>
</property>
<property name="buddy" >
<cstring>gui_bottom_margin</cstring>
</property>
</widget>
</item>
<item row="4" column="1" >
<widget class="QSpinBox" name="gui_bottom_margin" >
<property name="suffix" >
<string> px</string>
</property>
<property name="maximum" >
<number>100</number>
</property>
<property name="value" >
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="chapterdetection_page" >
<layout class="QVBoxLayout" >
<item>
<widget class="QGroupBox" name="groupBox_6" >
<property name="title" >
<string>Title based detection</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QCheckBox" name="gui_disable_chapter_detection" >
<property name="text" >
<string>&amp;Disable chapter detection</string>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_17" >
<property name="text" >
<string>&amp;Regular expression:</string>
</property>
<property name="buddy" >
<cstring>gui_chapter_regex</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="gui_chapter_regex" />
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_7" >
<property name="title" >
<string>Tag based detection</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_18" >
<property name="text" >
<string>&amp;Page break before tag:</string>
</property>
<property name="buddy" >
<cstring>gui_page_break_before_tag</cstring>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QLineEdit" name="gui_page_break_before_tag" />
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_19" >
<property name="text" >
<string>&amp;Force page break before tag:</string>
</property>
<property name="buddy" >
<cstring>gui_force_page_break_before_tag</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="gui_force_page_break_before_tag" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_20" >
<property name="text" >
<string>Force page break before &amp;attribute:</string>
</property>
<property name="buddy" >
<cstring>gui_force_page_break_before_attr</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QLineEdit" name="gui_force_page_break_before_attr" />
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_5" />
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1" >
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<string>Help on item</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QTextBrowser" name="help_view" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<size>
<width>0</width>
<height>150</height>
</size>
</property>
<property name="maximumSize" >
<size>
<width>16777215</width>
<height>170</height>
</size>
</property>
<property name="html" >
<string>&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
p, li { white-space: pre-wrap; }
&lt;/style>&lt;/head>&lt;body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
&lt;p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&lt;/p>&lt;/body>&lt;/html></string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="1" >
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons" >
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../images.qrc" />
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LRFSingleDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>516</x>
<y>655</y>
</hint>
<hint type="destinationlabel" >
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LRFSingleDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>584</x>
<y>661</y>
</hint>
<hint type="destinationlabel" >
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>categoryList</sender>
<signal>currentRowChanged(int)</signal>
<receiver>stack</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>245</x>
<y>199</y>
</hint>
<hint type="destinationlabel" >
<x>368</x>
<y>185</y>
</hint>
</hints>
</connection>
<connection>
<sender>gui_disable_chapter_detection</sender>
<signal>toggled(bool)</signal>
<receiver>gui_chapter_regex</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>439</x>
<y>97</y>
</hint>
<hint type="destinationlabel" >
<x>539</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>gui_header</sender>
<signal>toggled(bool)</signal>
<receiver>gui_headerformat</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>398</x>
<y>177</y>
</hint>
<hint type="destinationlabel" >
<x>476</x>
<y>211</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -16,15 +16,15 @@
'''Dialog to edit metadata in bulk'''
from PyQt4.QtCore import SIGNAL, QObject
from PyQt4.QtGui import QDialog
from libprs500.gui2 import qstring_to_unicode
from libprs500.gui2.dialogs import Dialog
from libprs500.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
class MetadataBulkDialog(Ui_MetadataBulkDialog, Dialog):
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
def __init__(self, window, rows, db):
QDialog.__init__(self, window)
Ui_MetadataBulkDialog.__init__(self)
Dialog.__init__(self, window)
self.setupUi(self.dialog)
self.db = db
self.ids = [ db.id(r) for r in rows]

View File

@ -18,13 +18,12 @@ add/remove formats
'''
import os
from PyQt4.QtCore import SIGNAL
from PyQt4.Qt import QObject, QPixmap, QListWidgetItem, QErrorMessage
from PyQt4.QtCore import SIGNAL, QObject
from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog
from libprs500.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \
choose_files, pixmap_to_data, BOOK_EXTENSIONS, choose_images
from libprs500.gui2.dialogs import Dialog
from libprs500.gui2.dialogs.metadata_single_ui import Ui_MetadataSingleDialog
class Format(QListWidgetItem):
@ -34,10 +33,10 @@ class Format(QListWidgetItem):
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
ext.upper(), parent, QListWidgetItem.UserType)
class MetadataSingleDialog(Ui_MetadataSingleDialog, Dialog):
class MetadataSingleDialog(QDialog, Ui_MetadataSingleDialog):
def select_cover(self, checked):
files = choose_images(self.window, 'change cover dialog',
files = choose_images(self, 'change cover dialog',
u'Choose cover for ' + qstring_to_unicode(self.title.text()))
if not files:
return
@ -71,7 +70,7 @@ class MetadataSingleDialog(Ui_MetadataSingleDialog, Dialog):
def add_format(self, x):
files = choose_files(self.window, 'add formats dialog',
files = choose_files(self, 'add formats dialog',
"Choose formats for " + str(self.title.text()),
[('Books', BOOK_EXTENSIONS)])
if not files:
@ -120,9 +119,9 @@ class MetadataSingleDialog(Ui_MetadataSingleDialog, Dialog):
self.db.remove_format(self.row, ext)
def __init__(self, window, row, db):
QDialog.__init__(self, window)
Ui_MetadataSingleDialog.__init__(self)
Dialog.__init__(self, window)
self.setupUi(self.dialog)
self.setupUi(self)
self.splitter.setStretchFactor(100, 1)
self.db = db
self.id = db.id(row)
@ -138,8 +137,6 @@ class MetadataSingleDialog(Ui_MetadataSingleDialog, Dialog):
self.add_format)
QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), \
self.remove_format)
QObject.connect(self.button_box, SIGNAL("accepted()"), \
self.sync)
self.title.setText(db.title(row))
au = self.db.authors(row)
@ -190,18 +187,18 @@ class MetadataSingleDialog(Ui_MetadataSingleDialog, Dialog):
QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index)
self.dialog.exec_()
self.exec_()
def enable_series_index(self, *args):
self.series_index.setEnabled(True)
def sync(self):
def accept(self):
if self.formats_changed:
self.sync_formats()
title = qstring_to_unicode(self.title.text())
self.db.set_title(self.id, title)
au = qstring_to_unicode(self.authors.text()).split(',')
self.db.set_authors(self.id, au)
if au: self.db.set_authors(self.id, au)
self.db.set_rating(self.id, 2*self.rating.value())
self.db.set_publisher(self.id, qstring_to_unicode(self.publisher.text()))
self.db.set_tags(self.id, qstring_to_unicode(self.tags.text()).split(','))
@ -211,6 +208,5 @@ class MetadataSingleDialog(Ui_MetadataSingleDialog, Dialog):
if self.cover_changed:
self.db.set_cover(self.id, pixmap_to_data(self.cover.pixmap()))
self.changed = True
QDialog.accept(self)
def reject(self):
self.rejected = True

View File

@ -0,0 +1,47 @@
## Copyright (C) 2007 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.
from PyQt4.QtGui import QDialog
from PyQt4.QtCore import QSettings, QVariant
from libprs500.gui2.dialogs.password_ui import Ui_Dialog
from libprs500.gui2 import qstring_to_unicode
class PasswordDialog(QDialog, Ui_Dialog):
def __init__(self, window, name, msg):
QDialog.__init__(self, window)
Ui_Dialog.__init__(self)
self.setupUi(self)
settings = QSettings()
un = settings.value(name+': un', QVariant('')).toString()
pw = settings.value(name+': pw', QVariant('')).toString()
self.gui_username.setText(un)
self.gui_username.setText(pw)
self.sname = name
self.msg.setText(msg)
def username(self):
return qstring_to_unicode(self.gui_username.text())
def password(self):
return qstring_to_unicode(self.gui_password.text())
def accept(self):
settings = QSettings()
settings.setValue(self.sname+': un', QVariant(self.gui_username.text()))
settings.setValue(self.sname+': pw', QVariant(self.gui_password.text()))
QDialog.accept(self)

View File

@ -0,0 +1,108 @@
<ui version="4.0" >
<class>Dialog</class>
<widget class="QDialog" name="Dialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>209</height>
</rect>
</property>
<property name="windowTitle" >
<string>Password needed</string>
</property>
<property name="windowIcon" >
<iconset resource="../images.qrc" >:/images/mimetypes/unknown.svg</iconset>
</property>
<layout class="QGridLayout" >
<item row="0" column="1" >
<widget class="QLabel" name="msg" >
<property name="text" >
<string>TextLabel</string>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label" >
<property name="text" >
<string>&amp;Username:</string>
</property>
<property name="buddy" >
<cstring>gui_username</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="gui_username" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>&amp;Password:</string>
</property>
<property name="buddy" >
<cstring>gui_password</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QLineEdit" name="gui_password" >
<property name="echoMode" >
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="3" column="1" >
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../images.qrc" />
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel" >
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel" >
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,4 +1,21 @@
from libprs500.gui2 import TableView
## Copyright (C) 2007 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.
from PyQt4.QtGui import QStackedWidget
from libprs500.gui2 import TableView, qstring_to_unicode
class JobsView(TableView):
pass

View File

@ -1,8 +1,10 @@
<RCC>
<qresource prefix="/" >
<file>images/book.svg</file>
<file>images/news.svg</file>
<file>images/chapters.svg</file>
<file>images/clear_left.svg</file>
<file>images/config.svg</file>
<file>images/convert.svg</file>
<file>images/dialog_error.svg</file>
<file>images/dialog_warning.svg</file>
<file>images/document_open.svg</file>
@ -11,6 +13,8 @@
<file>images/jobs.svg</file>
<file alias="library" >images/library.png</file>
<file>images/list_remove.svg</file>
<file>images/lookfeel.svg</file>
<file>images/metadata.svg</file>
<file>images/mimetypes/bmp.svg</file>
<file>images/mimetypes/dir.svg</file>
<file>images/mimetypes/gif.svg</file>
@ -28,14 +32,16 @@
<file>images/mimetypes/unknown.svg</file>
<file>images/mimetypes/zero.svg</file>
<file>images/mimetypes/zip.svg</file>
<file>images/news.svg</file>
<file>images/news/bbc.png</file>
<file>images/news/newsweek.png</file>
<file>images/news/nytimes.png</file>
<file>images/page.svg</file>
<file>images/plus.svg</file>
<file>images/reader.svg</file>
<file>images/save.svg</file>
<file>images/sd.svg</file>
<file>images/sync.svg</file>
<file>images/trash.svg</file>
<file>images/news/bbc.png</file>
<file>images/news/newsweek.png</file>
<file>images/news/nytimes.png</file>
</qresource>
</RCC>

View File

@ -362,7 +362,7 @@ class BooksView(TableView):
def migrate_database(self):
if self._model.database_needs_migration():
if self.model().database_needs_migration():
print 'Migrating database from pre 0.4.0 version'
path = os.path.abspath(os.path.expanduser('~/library.db'))
progress = QProgressDialog('Upgrading database from pre 0.4.0 version.<br>'+\
@ -554,7 +554,7 @@ class DeviceBooksModel(BooksModel):
text = self.db[self.map[row]].title
if not text:
text = self.unknown
return QVariant(BooksView.wrap(text, width=35))
return QVariant(text)
elif col == 1:
au = self.db[self.map[row]].authors
if not au:
@ -565,7 +565,7 @@ class DeviceBooksModel(BooksModel):
authors = []
for i in au:
authors += i.strip().split('&')
jau = [ BooksView.wrap(a.strip(), width=30).strip() for a in authors ]
jau = [ a.strip() for a in authors ]
return QVariant("\n".join(jau))
elif col == 2:
size = self.db[self.map[row]].size

View File

@ -12,12 +12,13 @@
## 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
from libprs500.gui2.dialogs.password import PasswordDialog
import os, sys, traceback, StringIO, textwrap
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QSettings, QVariant, QSize, QThread
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
QToolButton
QToolButton, QDialog
from PyQt4.QtSvg import QSvgRenderer
from libprs500 import __version__, __appname__
@ -37,6 +38,7 @@ from libprs500.gui2.dialogs.metadata_single import MetadataSingleDialog
from libprs500.gui2.dialogs.metadata_bulk import MetadataBulkDialog
from libprs500.gui2.dialogs.jobs import JobsDialog
from libprs500.gui2.dialogs.conversion_error import ConversionErrorDialog
from libprs500.gui2.dialogs.lrf_single import LRFSingleDialog
class Main(QObject, Ui_MainWindow):
@ -64,7 +66,7 @@ class Main(QObject, Ui_MainWindow):
self.conversion_jobs = {}
self.persistent_files = []
self.default_thumbnail = None
self.device_error_dialog = error_dialog(self.window, 'Error communicating with device', ' ')
self.device_error_dialog = ConversionErrorDialog(self.window, 'Error communicating with device', ' ')
self.device_error_dialog.setModal(Qt.NonModal)
self.tb_wrapper = textwrap.TextWrapper(width=40)
self.device_connected = False
@ -113,9 +115,18 @@ class Main(QObject, Ui_MainWindow):
QObject.connect(nm.actions()[2], SIGNAL('triggered(bool)'), self.fetch_news_nytimes)
self.news_menu = nm
self.action_news.setMenu(nm)
cm = QMenu()
cm.addAction('Convert individually')
cm.addAction('Bulk convert')
self.action_convert.setMenu(cm)
QObject.connect(cm.actions()[0], SIGNAL('triggered(bool)'), self.convert_single)
QObject.connect(cm.actions()[1], SIGNAL('triggered(bool)'), self.convert_bulk)
QObject.connect(self.action_convert, SIGNAL('triggered(bool)'), self.convert_single)
self.convert_menu = cm
self.tool_bar.widgetForAction(self.action_news).setPopupMode(QToolButton.InstantPopup)
self.tool_bar.widgetForAction(self.action_edit).setPopupMode(QToolButton.MenuButtonPopup)
self.tool_bar.widgetForAction(self.action_sync).setPopupMode(QToolButton.MenuButtonPopup)
self.tool_bar.widgetForAction(self.action_convert).setPopupMode(QToolButton.MenuButtonPopup)
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
####################### Library view ########################
self.library_view.set_database(self.database_path)
@ -470,10 +481,14 @@ class Main(QObject, Ui_MainWindow):
############################### Fetch news #################################
def fetch_news(self, profile, pretty):
def fetch_news(self, profile, pretty, username=None, password=None):
pt = PersistentTemporaryFile(suffix='.lrf')
pt.close()
args = ['web2lrf', '-o', pt.name, profile]
if username:
args.extend(['--username', username])
if password:
args.extend(['--password', password])
id = self.job_manager.run_conversion_job(self.news_fetched, web2lrf, args=args,
job_description='Fetch news from '+pretty)
self.conversion_jobs[id] = (pt, 'lrf')
@ -497,7 +512,29 @@ class Main(QObject, Ui_MainWindow):
self.fetch_news('newsweek', 'Newsweek')
def fetch_news_nytimes(self, checked):
self.fetch_news('nytimes', 'New York Times')
d = PasswordDialog(self.window, 'nytimes info dialog',
'<p>Please enter your username and password for nytimes.com<br>If you do not have, you can <a href="http://www.nytimes.com/gst/regi.html">register</a> for free.<br>Without a registration, some articles will not be downloaded correctly. Click OK to proceed.')
d.exec_()
if d.result() == QDialog.Accepted:
un, pw = d.username(), d.password()
self.fetch_news('nytimes', 'New York Times', username=un, password=pw)
############################################################################
############################### Convert ####################################
def convert_bulk(self, checked):
pass
def convert_single(self, checked):
rows = self.library_view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
d = error_dialog(self.window, 'Cannot convert', 'No books selected')
d.exec_()
for row in rows:
d = LRFSingleDialog(self.window, self.library_view.model().db, row)
if d.selected_format:
d.exec_()
############################################################################
def location_selected(self, location):
@ -519,15 +556,11 @@ class Main(QObject, Ui_MainWindow):
if self.device_connected:
self.action_sync.setEnabled(True)
self.action_edit.setEnabled(True)
self.action_convert.setEnabled(True)
else:
self.action_sync.setEnabled(False)
self.action_edit.setEnabled(False)
def wrap_traceback(self, tb):
tb = unicode(tb, 'utf8', 'replace')
tb = '\n'.join(self.tb_wrapper.wrap(tb))
return tb
self.action_convert.setEnabled(False)
def device_job_exception(self, id, description, exception, formatted_traceback):
'''
@ -541,8 +574,8 @@ class Main(QObject, Ui_MainWindow):
msg += u'<p>Failed to perform <b>job</b>: '+description
msg += u'<p>Further device related error messages will not be shown while this message is visible.'
msg += u'<p>Detailed <b>traceback</b>:<pre>'
msg += self.wrap_traceback(formatted_traceback)
self.device_error_dialog.setText(msg)
msg += formatted_traceback
self.device_error_dialog.set_message(msg)
self.device_error_dialog.show()
def conversion_job_exception(self, id, description, exception, formatted_traceback, log):
@ -556,7 +589,7 @@ class Main(QObject, Ui_MainWindow):
msg += formatted_traceback + '</pre>'
msg += '<p><b>Log:</b></p><pre>'
msg += log
ConversionErrorDialog(self.window, 'Conversion Error', msg)
ConversionErrorDialog(self.window, 'Conversion Error', msg, show=True)
def read_settings(self):
@ -604,8 +637,8 @@ class Main(QObject, Ui_MainWindow):
self.window.close()
self.window.thread().exit(0)
msg = '<p><b>' + unicode(str(value), 'utf8', 'replace') + '</b></p>'
msg += '<p>Detailed <b>traceback</b>:<pre>'+self.wrap_traceback(fe)+'</pre>'
d = error_dialog(self.window, 'ERROR: Unhandled exception', msg)
msg += '<p>Detailed <b>traceback</b>:<pre>'+fe+'</pre>'
d = ConversionErrorDialog(self.window, 'ERROR: Unhandled exception', msg)
d.exec_()
def main():

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>777</width>
<width>787</width>
<height>822</height>
</rect>
</property>
@ -44,7 +44,7 @@
<item>
<widget class="LocationView" name="location_view" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<sizepolicy vsizetype="Minimum" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -61,11 +61,20 @@
<property name="horizontalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="dragDropMode" >
<enum>QAbstractItemView::DragDrop</enum>
<property name="iconSize" >
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="movement" >
<enum>QListView::Static</enum>
</property>
<property name="flow" >
<enum>QListView::TopToBottom</enum>
<enum>QListView::LeftToRight</enum>
</property>
<property name="isWrapping" stdset="0" >
<bool>false</bool>
</property>
<property name="spacing" >
<number>20</number>
@ -73,6 +82,9 @@
<property name="viewMode" >
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -325,6 +337,7 @@
<addaction name="action_save" />
<addaction name="separator" />
<addaction name="action_news" />
<addaction name="action_convert" />
</widget>
<widget class="QStatusBar" name="statusBar" >
<property name="mouseTracking" >
@ -400,6 +413,14 @@
<string>Fetch news</string>
</property>
</action>
<action name="action_convert" >
<property name="icon" >
<iconset resource="images.qrc" >:/images/convert.svg</iconset>
</property>
<property name="text" >
<string>Convert E-books</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -13,11 +13,11 @@
## 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, os
import datetime, re, os, cPickle
from zlib import compress, decompress
class Concatenate(object):
@ -88,12 +88,12 @@ class LibraryDatabase(object):
def import_old_database(path, conn, progress=None):
count = 0
for book, cover, formats in LibraryDatabase.books_in_old_database(path):
obj = conn.execute('INSERT INTO books(title, timestamp) VALUES (?,?)',
(book['title'], book['timestamp']))
id = obj.lastrowid
authors = book['authors']
if not authors:
authors = 'Unknown'
obj = conn.execute('INSERT INTO books(title, timestamp, author_sort) VALUES (?,?,?)',
(book['title'], book['timestamp'], authors))
id = obj.lastrowid
authors = authors.split('&')
for a in authors:
author = conn.execute('SELECT id from authors WHERE name=?', (a,)).fetchone()
@ -560,15 +560,82 @@ class LibraryDatabase(object):
conn.execute('pragma user_version=1')
conn.commit()
@staticmethod
def upgrade_version1(conn):
conn.executescript(
'''
/***** authors_sort table *****/
ALTER TABLE books ADD COLUMN author_sort TEXT COLLATE NOCASE;
UPDATE books SET author_sort=(SELECT name FROM authors WHERE id=(SELECT author FROM books_authors_link WHERE book=books.id)) WHERE id IN (SELECT id FROM books ORDER BY id);
DROP INDEX authors_idx;
DROP TRIGGER authors_insert_trg;
DROP TRIGGER authors_update_trg;
CREATE INDEX authors_idx ON books (author_sort COLLATE NOCASE);
CREATE TABLE conversion_options ( id INTEGER PRIMARY KEY,
format TEXT NOT NULL COLLATE NOCASE,
book INTEGER,
data BLOB NOT NULL,
UNIQUE(format,book)
);
CREATE INDEX conversion_options_idx_a ON conversion_options (format COLLATE NOCASE);
CREATE INDEX conversion_options_idx_b ON conversion_options (book);
DROP TRIGGER books_delete_trg;
CREATE TRIGGER books_delete_trg
AFTER DELETE ON books
BEGIN
DELETE FROM books_authors_link WHERE book=OLD.id;
DELETE FROM books_publishers_link WHERE book=OLD.id;
DELETE FROM books_ratings_link WHERE book=OLD.id;
DELETE FROM books_series_link WHERE book=OLD.id;
DELETE FROM books_tags_link WHERE book=OLD.id;
DELETE FROM data WHERE book=OLD.id;
DELETE FROM covers WHERE book=OLD.id;
DELETE FROM comments WHERE book=OLD.id;
DELETE FROM conversion_options WHERE book=OLD.id;
END;
DROP VIEW meta;
CREATE VIEW meta AS
SELECT id, title,
(SELECT concat(name) FROM authors WHERE authors.id IN (SELECT author from books_authors_link WHERE book=books.id)) authors,
(SELECT name FROM publishers WHERE publishers.id IN (SELECT publisher from books_publishers_link WHERE book=books.id)) publisher,
(SELECT rating FROM ratings WHERE ratings.id IN (SELECT rating from books_ratings_link WHERE book=books.id)) rating,
timestamp,
(SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size,
(SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=books.id)) tags,
(SELECT text FROM comments WHERE book=books.id) comments,
(SELECT name FROM series WHERE series.id IN (SELECT series FROM books_series_link WHERE book=books.id)) series,
sort,
author_sort
FROM books;
DROP INDEX publishers_idx;
CREATE INDEX publishers_idx ON publishers (name COLLATE NOCASE);
DROP TRIGGER publishers_insert_trg;
DROP TRIGGER publishers_update_trg;
'''
)
conn.execute('pragma user_version=2')
conn.commit()
def __init__(self, dbpath):
self.dbpath = dbpath
self.conn = _connect(dbpath)
self.user_version = self.conn.execute('pragma user_version;').next()[0]
self.cache = []
self.data = []
if self.user_version == 0:
if self.user_version == 0: # No tables have been created
LibraryDatabase.create_version1(self.conn)
if self.user_version == 1: # Upgrade to 2
LibraryDatabase.upgrade_version1(self.conn)
@apply
def user_version():
doc = 'The user version of this database'
def fget(self):
return self.conn.execute('pragma user_version;').next()[0]
return property(doc=doc, fget=fget)
def is_empty(self):
return not self.conn.execute('SELECT id FROM books LIMIT 1').fetchone()
@ -578,7 +645,7 @@ class LibraryDatabase(object):
Rebuild self.data and self.cache. Filter results are lost.
'''
FIELDS = {'title' : 'sort',
'authors': 'authors_sort',
'authors': 'author_sort',
'publisher': 'publisher_sort',
'size': 'size',
'date': 'timestamp',
@ -631,6 +698,10 @@ class LibraryDatabase(object):
''' Authors as a comman separated list or None'''
return self.data[index][2]
def author_sort(self, index):
id = self.id(index)
return self.conn.execute('SELECT author_sort FROM books WHERE id=?', (id,)).fetchone()[0]
def publisher(self, index):
return self.data[index][3]
@ -699,6 +770,13 @@ class LibraryDatabase(object):
return [ (i[0], i[1]) for i in \
self.conn.execute('SELECT id, name FROM series').fetchall()]
def conversion_options(self, id, format):
data = self.conn.execute('SELECT data FROM conversion_options WHERE book=? AND format=?', (id, format.upper())).fetchone()
if data:
return cPickle.loads(str(data[0]))
return None
def add_format(self, index, ext, stream):
'''
Add the format specified by ext. If it already exists it is replaced.
@ -749,6 +827,16 @@ class LibraryDatabase(object):
elif column == 'rating':
self.set_rating(id, val)
def set_conversion_options(self, id, format, options):
data = sqlite.Binary(cPickle.dumps(options))
oid = self.conn.execute('SELECT id FROM conversion_options WHERE book=? AND format=?', (id, format.upper())).fetchone()
if oid:
self.conn.execute('UPDATE conversion_options SET data=? WHERE id=?', (data, oid[0]))
else:
self.conn.execute('INSERT INTO conversion_options(book,format,data) VALUES (?,?,?)', (id,format.upper(),data))
self.conn.commit()
def set_authors(self, id, authors):
'''
@param authors: A list of authors.
@ -766,6 +854,10 @@ class LibraryDatabase(object):
self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)', (id, aid))
self.conn.commit()
def set_author_sort(self, id, sort):
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (sort, id))
self.conn.commit()
def set_title(self, id, title):
if not title:
return
@ -925,5 +1017,4 @@ class LibraryDatabase(object):
if __name__ == '__main__':
db = LibraryDatabase('/home/kovid/library1.db')
db.refresh('title', True)
db.export_to_dir('/tmp/test', range(1, 10))