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
be78420241
@ -109,7 +109,7 @@ function toplevel_layout() {
|
|||||||
var last = $(".toplevel li").last();
|
var last = $(".toplevel li").last();
|
||||||
var title = $('.toplevel h3').first();
|
var title = $('.toplevel h3').first();
|
||||||
var bottom = last.position().top + last.height() - title.position().top;
|
var bottom = last.position().top + last.height() - title.position().top;
|
||||||
$("#main").height(Math.max(200, bottom));
|
$("#main").height(Math.max(200, bottom+75));
|
||||||
}
|
}
|
||||||
|
|
||||||
function toplevel() {
|
function toplevel() {
|
||||||
|
@ -53,6 +53,7 @@ div.navigation {
|
|||||||
}
|
}
|
||||||
#listing td {
|
#listing td {
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing td.thumbnail {
|
#listing td.thumbnail {
|
||||||
@ -73,6 +74,7 @@ div.navigation {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
|
@ -514,7 +514,7 @@ class FileDialog(QObject):
|
|||||||
if f and os.path.exists(f):
|
if f and os.path.exists(f):
|
||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
else:
|
else:
|
||||||
opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.DirectoryOnly else QFileDialog.Option()
|
opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.Directory else QFileDialog.Option()
|
||||||
f = unicode(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts))
|
f = unicode(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts))
|
||||||
if os.path.exists(f):
|
if os.path.exists(f):
|
||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
@ -534,7 +534,7 @@ class FileDialog(QObject):
|
|||||||
|
|
||||||
def choose_dir(window, name, title, default_dir='~'):
|
def choose_dir(window, name, title, default_dir='~'):
|
||||||
fd = FileDialog(title=title, filters=[], add_all_files_filter=False,
|
fd = FileDialog(title=title, filters=[], add_all_files_filter=False,
|
||||||
parent=window, name=name, mode=QFileDialog.DirectoryOnly,
|
parent=window, name=name, mode=QFileDialog.Directory,
|
||||||
default_dir=default_dir)
|
default_dir=default_dir)
|
||||||
dir = fd.get_files()
|
dir = fd.get_files()
|
||||||
if dir:
|
if dir:
|
||||||
|
@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
|
|||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
from calibre.library.check_library import CheckLibrary, CHECKS
|
from calibre.library.check_library import CheckLibrary, CHECKS
|
||||||
from calibre.library.database2 import delete_file, delete_tree
|
from calibre.library.database2 import delete_file, delete_tree
|
||||||
|
from calibre import prints
|
||||||
|
|
||||||
class Item(QTreeWidgetItem):
|
class Item(QTreeWidgetItem):
|
||||||
pass
|
pass
|
||||||
@ -126,14 +127,12 @@ class CheckLibraryDialog(QDialog):
|
|||||||
self.text_results = '\n'.join(plaintext)
|
self.text_results = '\n'.join(plaintext)
|
||||||
|
|
||||||
def item_changed(self, item, column):
|
def item_changed(self, item, column):
|
||||||
print 'item_changed'
|
|
||||||
for it in self.all_items:
|
for it in self.all_items:
|
||||||
if it.checkState(1):
|
if it.checkState(1):
|
||||||
self.delete.setEnabled(True)
|
self.delete.setEnabled(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
def delete_marked(self):
|
def delete_marked(self):
|
||||||
print 'delete marked'
|
|
||||||
if not confirm('<p>'+_('The marked files and folders will be '
|
if not confirm('<p>'+_('The marked files and folders will be '
|
||||||
'<b>permanently deleted</b>. Are you sure?')
|
'<b>permanently deleted</b>. Are you sure?')
|
||||||
+'</p>', 'check_library_editor_delete', self):
|
+'</p>', 'check_library_editor_delete', self):
|
||||||
@ -153,7 +152,9 @@ class CheckLibraryDialog(QDialog):
|
|||||||
else:
|
else:
|
||||||
delete_file(p)
|
delete_file(p)
|
||||||
except:
|
except:
|
||||||
print 'failed to delete', os.path.join(self.db.library_path ,unicode(it.text(1)))
|
prints('failed to delete',
|
||||||
|
os.path.join(self.db.library_path,
|
||||||
|
unicode(it.text(1))))
|
||||||
self.run_the_check()
|
self.run_the_check()
|
||||||
|
|
||||||
def copy_to_clipboard(self):
|
def copy_to_clipboard(self):
|
||||||
|
@ -365,6 +365,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
except:
|
except:
|
||||||
olddb = None
|
olddb = None
|
||||||
db = LibraryDatabase2(newloc)
|
db = LibraryDatabase2(newloc)
|
||||||
|
if self.content_server is not None:
|
||||||
|
self.content_server.set_database(db)
|
||||||
self.library_path = newloc
|
self.library_path = newloc
|
||||||
self.book_on_device(None, reset=True)
|
self.book_on_device(None, reset=True)
|
||||||
db.set_book_on_device_func(self.book_on_device)
|
db.set_book_on_device_func(self.book_on_device)
|
||||||
|
@ -10,6 +10,7 @@ import logging
|
|||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
from cherrypy.process.plugins import SimplePlugin
|
||||||
|
|
||||||
from calibre.constants import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
from calibre.utils.date import fromtimestamp
|
from calibre.utils.date import fromtimestamp
|
||||||
@ -54,16 +55,43 @@ class DispatchController(object): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class BonJour(SimplePlugin):
|
||||||
|
|
||||||
|
def __init__(self, engine, port=8080):
|
||||||
|
SimplePlugin.__init__(self, engine)
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
try:
|
||||||
|
publish_zeroconf('Books in calibre', '_stanza._tcp',
|
||||||
|
self.port, {'path':'/stanza'})
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
cherrypy.log.error('Failed to start BonJour:')
|
||||||
|
cherrypy.log.error(traceback.format_exc())
|
||||||
|
|
||||||
|
start.priority = 90
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
try:
|
||||||
|
stop_zeroconf()
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
cherrypy.log.error('Failed to stop BonJour:')
|
||||||
|
cherrypy.log.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
|
stop.priority = 10
|
||||||
|
|
||||||
|
cherrypy.engine.bonjour = BonJour(cherrypy.engine)
|
||||||
|
|
||||||
|
|
||||||
class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
||||||
BrowseServer):
|
BrowseServer):
|
||||||
|
|
||||||
server_name = __appname__ + '/' + __version__
|
server_name = __appname__ + '/' + __version__
|
||||||
|
|
||||||
def __init__(self, db, opts, embedded=False, show_tracebacks=True):
|
def __init__(self, db, opts, embedded=False, show_tracebacks=True):
|
||||||
self.db = db
|
|
||||||
for item in self.db:
|
|
||||||
item
|
|
||||||
break
|
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.embedded = embedded
|
self.embedded = embedded
|
||||||
self.state_callback = None
|
self.state_callback = None
|
||||||
@ -71,7 +99,14 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
map(int, self.opts.max_cover.split('x'))
|
map(int, self.opts.max_cover.split('x'))
|
||||||
path = P('content_server')
|
path = P('content_server')
|
||||||
self.build_time = fromtimestamp(os.stat(path).st_mtime)
|
self.build_time = fromtimestamp(os.stat(path).st_mtime)
|
||||||
self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read()
|
self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read()
|
||||||
|
|
||||||
|
cherrypy.engine.bonjour.port = opts.port
|
||||||
|
|
||||||
|
Cache.__init__(self)
|
||||||
|
|
||||||
|
self.set_database(db)
|
||||||
|
|
||||||
cherrypy.config.update({
|
cherrypy.config.update({
|
||||||
'log.screen' : opts.develop,
|
'log.screen' : opts.develop,
|
||||||
'engine.autoreload_on' : opts.develop,
|
'engine.autoreload_on' : opts.develop,
|
||||||
@ -97,18 +132,27 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
|
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
|
||||||
}
|
}
|
||||||
|
|
||||||
sr = getattr(opts, 'restriction', None)
|
|
||||||
sr = db.prefs.get('cs_restriction', '') if sr is None else sr
|
|
||||||
self.set_search_restriction(sr)
|
|
||||||
|
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.exception = None
|
self.exception = None
|
||||||
|
self.setup_loggers()
|
||||||
|
cherrypy.engine.bonjour.subscribe()
|
||||||
|
|
||||||
|
def set_database(self, db):
|
||||||
|
self.db = db
|
||||||
|
sr = getattr(self.opts, 'restriction', None)
|
||||||
|
sr = db.prefs.get('cs_restriction', '') if sr is None else sr
|
||||||
|
self.set_search_restriction(sr)
|
||||||
|
|
||||||
|
def graceful(self):
|
||||||
|
cherrypy.engine.graceful()
|
||||||
|
|
||||||
def set_search_restriction(self, restriction):
|
def set_search_restriction(self, restriction):
|
||||||
if restriction:
|
if restriction:
|
||||||
self.search_restriction = 'search:"%s"'%restriction
|
self.search_restriction = 'search:"%s"'%restriction
|
||||||
else:
|
else:
|
||||||
self.search_restriction = ''
|
self.search_restriction = ''
|
||||||
|
self.reset_caches()
|
||||||
|
|
||||||
def setup_loggers(self):
|
def setup_loggers(self):
|
||||||
access_file = log_access_file
|
access_file = log_access_file
|
||||||
@ -140,7 +184,6 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
root_conf['request.dispatch'] = d.dispatcher
|
root_conf['request.dispatch'] = d.dispatcher
|
||||||
self.config['/'] = root_conf
|
self.config['/'] = root_conf
|
||||||
|
|
||||||
self.setup_loggers()
|
|
||||||
cherrypy.tree.mount(root=None, config=self.config)
|
cherrypy.tree.mount(root=None, config=self.config)
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@ -154,24 +197,14 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
cherrypy.engine.start()
|
cherrypy.engine.start()
|
||||||
|
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
try:
|
#if hasattr(cherrypy.engine, 'signal_handler'):
|
||||||
publish_zeroconf('Books in calibre', '_stanza._tcp',
|
# cherrypy.engine.signal_handler.subscribe()
|
||||||
self.opts.port, {'path':'/stanza'})
|
|
||||||
except:
|
|
||||||
import traceback
|
|
||||||
cherrypy.log.error('Failed to start BonJour:')
|
|
||||||
cherrypy.log.error(traceback.format_exc())
|
|
||||||
cherrypy.engine.block()
|
cherrypy.engine.block()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.exception = e
|
self.exception = e
|
||||||
finally:
|
finally:
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
try:
|
|
||||||
stop_zeroconf()
|
|
||||||
except:
|
|
||||||
import traceback
|
|
||||||
cherrypy.log.error('Failed to stop BonJour:')
|
|
||||||
cherrypy.log.error(traceback.format_exc())
|
|
||||||
try:
|
try:
|
||||||
if callable(self.state_callback):
|
if callable(self.state_callback):
|
||||||
self.state_callback(self.is_running)
|
self.state_callback(self.is_running)
|
||||||
|
@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import operator, os, json
|
import operator, os, json
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
|
from urllib import quote
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ def get_category_items(category, items, db, datatype): # {{{
|
|||||||
q = i.category
|
q = i.category
|
||||||
if not q:
|
if not q:
|
||||||
q = category
|
q = category
|
||||||
href = '/browse/matches/%s/%s'%(q, id_)
|
href = '/browse/matches/%s/%s'%(quote(q), quote(id_))
|
||||||
return templ.format(xml(name), rating,
|
return templ.format(xml(name), rating,
|
||||||
xml(desc), xml(href), rstring)
|
xml(desc), xml(href), rstring)
|
||||||
|
|
||||||
@ -329,7 +330,7 @@ class BrowseServer(object):
|
|||||||
cats = [('<li title="{2} {0}"><img src="{src}" alt="{0}" />'
|
cats = [('<li title="{2} {0}"><img src="{src}" alt="{0}" />'
|
||||||
'<span class="label">{0}</span>'
|
'<span class="label">{0}</span>'
|
||||||
'<span class="url">/browse/category/{1}</span></li>')
|
'<span class="url">/browse/category/{1}</span></li>')
|
||||||
.format(xml(x, True), xml(y), xml(_('Browse books by')),
|
.format(xml(x, True), xml(quote(y)), xml(_('Browse books by')),
|
||||||
src='/browse/icon/'+z)
|
src='/browse/icon/'+z)
|
||||||
for x, y, z in cats]
|
for x, y, z in cats]
|
||||||
|
|
||||||
|
@ -10,7 +10,10 @@ from calibre.utils.ordered_dict import OrderedDict
|
|||||||
|
|
||||||
class Cache(object):
|
class Cache(object):
|
||||||
|
|
||||||
def add_routes(self, c):
|
def __init__(self):
|
||||||
|
self.reset_caches()
|
||||||
|
|
||||||
|
def reset_caches(self):
|
||||||
self._category_cache = OrderedDict()
|
self._category_cache = OrderedDict()
|
||||||
self._search_cache = OrderedDict()
|
self._search_cache = OrderedDict()
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, sys
|
import sys
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from calibre.library.server import server_config as config
|
from calibre.library.server import server_config as config
|
||||||
@ -38,50 +38,18 @@ def option_parser():
|
|||||||
' in the GUI'))
|
' in the GUI'))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
|
|
||||||
try:
|
|
||||||
pid = os.fork()
|
|
||||||
if pid > 0:
|
|
||||||
# exit first parent
|
|
||||||
sys.exit(0)
|
|
||||||
except OSError, e:
|
|
||||||
print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# decouple from parent environment
|
|
||||||
os.chdir("/")
|
|
||||||
os.setsid()
|
|
||||||
os.umask(0)
|
|
||||||
|
|
||||||
# do second fork
|
|
||||||
try:
|
|
||||||
pid = os.fork()
|
|
||||||
if pid > 0:
|
|
||||||
# exit from second parent
|
|
||||||
sys.exit(0)
|
|
||||||
except OSError, e:
|
|
||||||
print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Redirect standard file descriptors.
|
|
||||||
si = file(stdin, 'r')
|
|
||||||
so = file(stdout, 'a+')
|
|
||||||
se = file(stderr, 'a+', 0)
|
|
||||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
|
||||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
|
||||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args=sys.argv):
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
parser = option_parser()
|
parser = option_parser()
|
||||||
opts, args = parser.parse_args(args)
|
opts, args = parser.parse_args(args)
|
||||||
if opts.daemonize and not iswindows:
|
if opts.daemonize and not iswindows:
|
||||||
daemonize()
|
from cherrypy.process.plugins import Daemonizer
|
||||||
|
d = Daemonizer(cherrypy.engine)
|
||||||
|
d.subscribe()
|
||||||
if opts.pidfile is not None:
|
if opts.pidfile is not None:
|
||||||
with open(opts.pidfile, 'wb') as f:
|
from cherrypy.process.plugins import PIDFile
|
||||||
f.write(str(os.getpid()))
|
PIDFile(cherrypy.engine, opts.pidfile).subscribe()
|
||||||
cherrypy.log.screen = True
|
cherrypy.log.screen = True
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
if opts.with_library is None:
|
if opts.with_library is None:
|
||||||
|
@ -58,11 +58,12 @@ def publish(desc, type, port, properties=None, add_hostname=True):
|
|||||||
'''
|
'''
|
||||||
port = int(port)
|
port = int(port)
|
||||||
server = start_server()
|
server = start_server()
|
||||||
|
try:
|
||||||
|
hostname = socket.gethostname().partition('.')[0]
|
||||||
|
except:
|
||||||
|
hostname = 'Unknown'
|
||||||
|
|
||||||
if add_hostname:
|
if add_hostname:
|
||||||
try:
|
|
||||||
hostname = socket.gethostname().partition('.')[0]
|
|
||||||
except:
|
|
||||||
hostname = 'Unknown'
|
|
||||||
desc += ' (on %s)'%hostname
|
desc += ' (on %s)'%hostname
|
||||||
local_ip = get_external_ip()
|
local_ip = get_external_ip()
|
||||||
type = type+'.local.'
|
type = type+'.local.'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user