Merge from trunk

This commit is contained in:
Charles Haley 2012-02-09 13:54:52 +01:00
commit e7240626a2
15 changed files with 1294 additions and 1182 deletions

View File

@ -156,9 +156,6 @@ class Develop(Command):
self.warn('Failed to compile mount helper. Auto mounting of',
' devices will not work')
if not isbsd and os.geteuid() != 0:
return self.warn('Must be run as root to compile mount helper. Auto '
'mounting of devices will not work.')
src = os.path.join(self.SRC, 'calibre', 'devices', 'linux_mount_helper.c')
dest = os.path.join(self.staging_bindir, 'calibre-mount-helper')
self.info('Installing mount helper to '+ dest)

View File

@ -449,7 +449,7 @@ class CatalogPlugin(Plugin): # {{{
['author_sort','authors','comments','cover','formats',
'id','isbn','ondevice','pubdate','publisher','rating',
'series_index','series','size','tags','timestamp',
'title_sort','title','uuid','languages'])
'title_sort','title','uuid','languages','identifiers'])
all_custom_fields = set(db.custom_field_keys())
for field in list(all_custom_fields):
fm = db.field_metadata[field]

View File

@ -382,8 +382,8 @@ class Adder(QObject): # {{{
if not duplicates:
return self.duplicates_processed()
self.pd.hide()
files = [_('%s by %s')%(x[0].title, x[0].format_field('authors')[1])
for x in duplicates]
files = [_('%(title)s by %(author)s')%dict(title=x[0].title,
author=x[0].format_field('authors')[1]) for x in duplicates]
if question_dialog(self._parent, _('Duplicates found!'),
_('Books with the same title as the following already '
'exist in the database. Add them anyway?'),

View File

@ -209,8 +209,8 @@ class AutoAdder(QObject):
paths.extend(p)
formats.extend(f)
metadata.extend(mis)
files = [_('%s by %s')%(mi.title, mi.format_field('authors')[1])
for mi in metadata]
files = [_('%(title)s by %(author)s')%dict(title=mi.title,
author=mi.format_field('authors')[1]) for mi in metadata]
if question_dialog(self.parent(), _('Duplicates found!'),
_('Books with the same title as the following already '
'exist in the database. Add them anyway?'),
@ -228,8 +228,8 @@ class AutoAdder(QObject):
if count > 0:
m.books_added(count)
gui.status_bar.show_message(_(
'Added %d book(s) automatically from %s') %
(count, self.worker.path), 2000)
'Added %(num)d book(s) automatically from %(src)s') %
dict(num=count, src=self.worker.path), 2000)
if hasattr(gui, 'db_images'):
gui.db_images.reset()

View File

@ -180,7 +180,7 @@ class ProceedNotification(MessageBox): # {{{
self.payload = payload
self.html_log = html_log
self.log_viewer_title = log_viewer_title
self.finished.connect(self.do_proceed, type=Qt.QueuedConnection)
self.finished.connect(self.do_proceed)
self.vlb = self.bb.addButton(_('View log'), self.bb.ActionRole)
self.vlb.setIcon(QIcon(I('debug.png')))
@ -195,18 +195,17 @@ class ProceedNotification(MessageBox): # {{{
parent=self)
def do_proceed(self, result):
try:
if result == self.Accepted:
self.callback(self.payload)
elif self.cancel_callback is not None:
self.cancel_callback(self.payload)
finally:
# Ensure this notification is garbage collected
self.callback = self.cancel_callback = None
self.setParent(None)
self.finished.disconnect()
self.vlb.clicked.disconnect()
_proceed_memory.remove(self)
from calibre.gui2.ui import get_gui
func = (self.callback if result == self.Accepted else
self.cancel_callback)
gui = get_gui()
gui.proceed_requested.emit(func, self.payload)
# Ensure this notification is garbage collected
self.callback = self.cancel_callback = self.payload = None
self.setParent(None)
self.finished.disconnect()
self.vlb.clicked.disconnect()
_proceed_memory.remove(self)
# }}}
class ErrorNotification(MessageBox): # {{{

View File

@ -116,7 +116,7 @@
<item row="0" column="1">
<widget class="QLineEdit" name="title">
<property name="toolTip">
<string>Regular expression (?P&amp;lt;title&amp;gt;)</string>
<string>Regular expression (?P&lt;title&gt;)</string>
</property>
<property name="text">
<string>No match</string>

View File

@ -1073,19 +1073,40 @@ class DeviceBooksModel(BooksModel): # {{{
self.book_in_library = None
def mark_for_deletion(self, job, rows, rows_are_ids=False):
db_indices = rows if rows_are_ids else self.indices(rows)
db_items = [self.db[i] for i in db_indices if -1 < i < len(self.db)]
self.marked_for_deletion[job] = db_items
if rows_are_ids:
self.marked_for_deletion[job] = rows
self.reset()
else:
self.marked_for_deletion[job] = self.indices(rows)
for row in rows:
indices = self.row_indices(row)
self.dataChanged.emit(indices[0], indices[-1])
def find_item_in_db(self, item):
idx = None
try:
idx = self.db.index(item)
except:
path = getattr(item, 'path', None)
if path:
for i, x in enumerate(self.db):
if getattr(x, 'path', None) == path:
idx = i
break
return idx
def deletion_done(self, job, succeeded=True):
if not self.marked_for_deletion.has_key(job):
return
rows = self.marked_for_deletion.pop(job)
db_items = self.marked_for_deletion.pop(job, [])
rows = []
for item in db_items:
idx = self.find_item_in_db(item)
if idx is not None:
try:
rows.append(self.map.index(idx))
except ValueError:
pass
for row in rows:
if not succeeded:
indices = self.row_indices(self.index(row, 0))
@ -1096,11 +1117,18 @@ class DeviceBooksModel(BooksModel): # {{{
self.resort(False)
self.research(True)
def indices_to_be_deleted(self):
ans = []
for v in self.marked_for_deletion.values():
ans.extend(v)
return ans
def is_row_marked_for_deletion(self, row):
try:
item = self.db[self.map[row]]
except IndexError:
return False
path = getattr(item, 'path', None)
for items in self.marked_for_deletion.itervalues():
for x in items:
if x is item or (path and path == getattr(x, 'path', None)):
return True
return False
def clear_ondevice(self, db_ids, to_what=None):
for data in self.db:
@ -1112,8 +1140,8 @@ class DeviceBooksModel(BooksModel): # {{{
self.reset()
def flags(self, index):
if self.map[index.row()] in self.indices_to_be_deleted():
return Qt.ItemIsUserCheckable # Can't figure out how to get the disabled flag in python
if self.is_row_marked_for_deletion(index.row()):
return Qt.NoItemFlags
flags = QAbstractTableModel.flags(self, index)
if index.isValid():
cname = self.column_map[index.column()]
@ -1347,7 +1375,7 @@ class DeviceBooksModel(BooksModel): # {{{
elif DEBUG and cname == 'inlibrary':
return QVariant(self.db[self.map[row]].in_library)
elif role == Qt.ToolTipRole and index.isValid():
if self.map[row] in self.indices_to_be_deleted():
if self.is_row_marked_for_deletion(row):
return QVariant(_('Marked for deletion'))
if cname in ['title', 'authors'] or (cname == 'collections' and \
self.db.supports_collections()):

View File

@ -36,6 +36,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('max_cover', self.proxy)
r('max_opds_items', self.proxy)
r('max_opds_ungrouped_items', self.proxy)
r('url_prefix', self.proxy)
self.show_server_password.stateChanged[int].connect(
lambda s: self.opt_password.setEchoMode(
@ -100,7 +101,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.stopping_msg.accept()
def test_server(self):
open_url(QUrl('http://127.0.0.1:'+str(self.opt_port.value())))
prefix = unicode(self.opt_url_prefix.text()).strip()
open_url(QUrl('http://127.0.0.1:'+str(self.opt_port.value())+prefix))
def view_server_logs(self):
from calibre.library.server import log_access_file, log_error_file

View File

@ -16,36 +16,6 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Server &amp;port:</string>
</property>
<property name="buddy">
<cstring>opt_port</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QSpinBox" name="opt_port">
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>8080</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&amp;Username:</string>
</property>
<property name="buddy">
<cstring>opt_username</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="opt_username"/>
</item>
@ -91,6 +61,36 @@ Leave this blank if you intend to use the server with an
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Server &amp;port:</string>
</property>
<property name="buddy">
<cstring>opt_port</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QSpinBox" name="opt_port">
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>8080</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&amp;Username:</string>
</property>
<property name="buddy">
<cstring>opt_username</cstring>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QCheckBox" name="show_server_password">
<property name="text">
@ -181,6 +181,23 @@ Leave this blank if you intend to use the server with an
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&amp;URL Prefix:</string>
</property>
<property name="buddy">
<cstring>opt_url_prefix</cstring>
</property>
</widget>
</item>
<item row="8" column="1" colspan="2">
<widget class="QLineEdit" name="opt_url_prefix">
<property name="toolTip">
<string>A prefix that is applied to all URLs in the content server. Useful only if you plan to put the server behind another server like Apache, with a reverse proxy.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>

View File

@ -102,10 +102,13 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
):
'The main GUI'
proceed_requested = pyqtSignal(object, object)
def __init__(self, opts, parent=None, gui_debug=None):
global _gui
MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True)
self.proceed_requested.connect(self.do_proceed,
type=Qt.QueuedConnection)
self.keyboard = Manager(self)
_gui = self
self.opts = opts
@ -402,6 +405,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
except:
pass
def do_proceed(self, func, payload):
if callable(func):
func(payload)
def no_op(self, *args):
pass

View File

@ -701,7 +701,10 @@ class LibraryPage(QWizardPage, LibraryUI):
pass
def is_library_dir_suitable(self, x):
return LibraryDatabase2.exists_at(x) or not os.listdir(x)
try:
return LibraryDatabase2.exists_at(x) or not os.listdir(x)
except:
return False
def validatePage(self):
newloc = unicode(self.location.text())
@ -720,6 +723,13 @@ class LibraryPage(QWizardPage, LibraryUI):
_('Path to library too long. Must be less than'
' %d characters.')%LibraryDatabase2.WINDOWS_LIBRARY_PATH_LIMIT,
show=True)
if not os.path.exists(x):
try:
os.makedirs(x)
except:
return error_dialog(self, _('Bad location'),
_('Failed to create a folder at %s')%x,
det_msg=traceback.format_exc(), show=True)
if self.is_library_dir_suitable(x):
self.location.setText(x)

View File

@ -11,7 +11,7 @@ __docformat__ = 'restructuredtext en'
FIELDS = ['all', 'title', 'title_sort', 'author_sort', 'authors', 'comments',
'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher',
'rating', 'series_index', 'series', 'size', 'tags', 'timestamp',
'uuid', 'languages']
'uuid', 'languages', 'identifiers']
#Allowed fields for template
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', 'title_sort',

View File

@ -162,7 +162,7 @@ class CSV_XML(CatalogPlugin):
record.append(item)
for field in ('id', 'uuid', 'publisher', 'rating', 'size',
'isbn','ondevice'):
'isbn','ondevice', 'identifiers'):
if field in fields:
val = r[field]
if not val:

View File

@ -79,6 +79,8 @@ class BonJour(SimplePlugin): # {{{
try:
publish_zeroconf('Books in calibre', '_stanza._tcp',
self.port, {'path':self.prefix+'/stanza'})
publish_zeroconf('Books in calibre', '_calibre._tcp',
self.port, {'path':self.prefix+'/opds'})
except:
import traceback
cherrypy.log.error('Failed to start BonJour:')
@ -122,6 +124,8 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
path = P('content_server')
self.build_time = fromtimestamp(os.stat(path).st_mtime)
self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read()
if not opts.url_prefix:
opts.url_prefix = ''
cherrypy.engine.bonjour.port = opts.port
cherrypy.engine.bonjour.prefix = opts.url_prefix

File diff suppressed because it is too large Load Diff