mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
e7240626a2
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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?'),
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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): # {{{
|
||||
|
@ -116,7 +116,7 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="title">
|
||||
<property name="toolTip">
|
||||
<string>Regular expression (?P&lt;title&gt;)</string>
|
||||
<string>Regular expression (?P<title>)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No match</string>
|
||||
|
@ -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()):
|
||||
|
@ -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
|
||||
|
@ -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 &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>&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 &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>&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>&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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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:
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user