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
033c3d9f30
106
Changelog.yaml
106
Changelog.yaml
@ -4,6 +4,111 @@
|
|||||||
# for important features/bug fixes.
|
# for important features/bug fixes.
|
||||||
# Also, each release can have new and improved recipes.
|
# Also, each release can have new and improved recipes.
|
||||||
|
|
||||||
|
- version: 0.7.24
|
||||||
|
date: 2010-10-17
|
||||||
|
|
||||||
|
new features:
|
||||||
|
- title: "Content server: New interface that allows browsing via categories, similar to the Tag Browser in the calibre interface."
|
||||||
|
description: >
|
||||||
|
"You can access the new interface by going to /browse. So if your calibre content server is available at http://192.168.1.2, use
|
||||||
|
http://192.168.1.2/browse. The new interface requires a fairly modern browser, so no Internet Explorer 6,7."
|
||||||
|
type: major
|
||||||
|
|
||||||
|
- title: "Support for the SNB e-book format, used by the Bambook e-book reader"
|
||||||
|
type: major
|
||||||
|
|
||||||
|
- title: "Driver for the Wifi Kobo"
|
||||||
|
|
||||||
|
- title: "Edit metadata dialog: If metadata is downloaded successfully, set focus to download cover button"
|
||||||
|
|
||||||
|
- title: "News download system: Allow recipes with optional subscriptions"
|
||||||
|
tickets: [7199]
|
||||||
|
|
||||||
|
- title: "Templates: Improve the smarten function"
|
||||||
|
|
||||||
|
- title: "Linux device mounting: Use udisks, if it is available, to mount devices, so that I no longer have to hear bug reports from users using distro packages that have crippled calibre-mount-helper. You can turn off udisks by setting the environment variable CALIBRE_DISABLE_UDISKS=1"
|
||||||
|
|
||||||
|
- title: "Implement Drag'n'drop to tags in user categories"
|
||||||
|
tickets: [7172]
|
||||||
|
|
||||||
|
- title: "Ebook viewer: Add command line option to start in full screen mode"
|
||||||
|
|
||||||
|
- title: "Set completion mode on search boxes to popup completion"
|
||||||
|
|
||||||
|
- title: "Update version of jQuery used in content server and viewer. Required a little hackery in the viewer, hopefully nothing broke"
|
||||||
|
|
||||||
|
bug fixes:
|
||||||
|
- title: "Linux device drivers: Ignore read only partition exported by the device"
|
||||||
|
|
||||||
|
- title: "E-book viewer: Fix scrolling down with mouse wheel not always reaching bottom in windows"
|
||||||
|
|
||||||
|
- title: "Smarten punctuation: Fix bug in handling of comments and <style> tags"
|
||||||
|
|
||||||
|
- title: "EPUB Input: Handle EPUB files with components encoded in an encoding other than UTF-8 correctly, though why anyone would do that is a mystery."
|
||||||
|
tickets: [7196]
|
||||||
|
|
||||||
|
- title: "OS X commandline tools: Decode non-ascii command line arguments correctly"
|
||||||
|
tickets: [6964]
|
||||||
|
|
||||||
|
- title: "MOBI Output: Fix bug that broke conversion of <svg> elements in the input document when the <svg> element was followed by non-whitespace text."
|
||||||
|
tickets: [7083]
|
||||||
|
|
||||||
|
- title: "CHM Input: Fix handling of relative file paths in <img> tags."
|
||||||
|
tickets: [7159]
|
||||||
|
|
||||||
|
- title: "EPUB Output: Fix incorrect format for xml:lang when specifying a sub language"
|
||||||
|
tickets: [7198]
|
||||||
|
|
||||||
|
- title: "EPUB Input: Make parsing of toc.ncx more robust."
|
||||||
|
tickets: [7170]
|
||||||
|
|
||||||
|
- title: "Content server: Fix searching with non-ascii characters on windows"
|
||||||
|
tickets: [5249]
|
||||||
|
|
||||||
|
- title: "Fix average rating calculation for rating datatype in Tag Browser incorrect"
|
||||||
|
|
||||||
|
- title: "Comic Input: Fix image borders becoming yellow on some windows installs"
|
||||||
|
|
||||||
|
- title: "Email sending: Fix sending of email with non ascii chars"
|
||||||
|
tickets: [7137]
|
||||||
|
|
||||||
|
- title: "SONY driver: Fix collections created from series not in order with manual metadata management, if all books in the series are not sent at once"
|
||||||
|
|
||||||
|
- title: "Content server: Apply the search restriction when generating category lists as well"
|
||||||
|
|
||||||
|
- title: "RTF Input: Fix regression in conversion of WMF images on linux at least, maybe on other platforms as wel"
|
||||||
|
|
||||||
|
- title: "Fix isbndb.com metadata downloading sometimes yield a title of Unknown"
|
||||||
|
tickets: [7114]
|
||||||
|
|
||||||
|
- title: "Fix edit metadata dialog causing the hour:minute:seconds of the date column being lost, even when date is not changed"
|
||||||
|
tickets: [7125]
|
||||||
|
|
||||||
|
new recipes:
|
||||||
|
- title: "Revista El Cultural"
|
||||||
|
author: "Jefferson Frantz"
|
||||||
|
|
||||||
|
- title: "Novaya Gazeta"
|
||||||
|
author: "muwa"
|
||||||
|
|
||||||
|
- title: "frazpc.pl"
|
||||||
|
author: "Tomasz Dlugosz"
|
||||||
|
|
||||||
|
- title: "Orsai and Financial Times UK"
|
||||||
|
author: "Darko Miletic"
|
||||||
|
|
||||||
|
- title: "Malayasian Mirror and Rolling Stones"
|
||||||
|
author: "Tony Stegall"
|
||||||
|
|
||||||
|
improved recipes:
|
||||||
|
- Globe and Mail
|
||||||
|
- Business Standard
|
||||||
|
- Miami Herald
|
||||||
|
- El Mercurio
|
||||||
|
- volkskrant.nl
|
||||||
|
- GoComics.com
|
||||||
|
- The New Yorker
|
||||||
|
|
||||||
- version: 0.7.23
|
- version: 0.7.23
|
||||||
date: 2010-10-08
|
date: 2010-10-08
|
||||||
|
|
||||||
@ -51,6 +156,7 @@
|
|||||||
- title: "CHM input: handle another class of broken CHM files"
|
- title: "CHM input: handle another class of broken CHM files"
|
||||||
tickets: [7058]
|
tickets: [7058]
|
||||||
|
|
||||||
|
- title: "Make calibre worker processes use the same temp directory as the calibre GUI"
|
||||||
|
|
||||||
new recipes:
|
new recipes:
|
||||||
- title: "Communications of the Association for Computing Machinery"
|
- title: "Communications of the Association for Computing Machinery"
|
||||||
|
@ -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() {
|
||||||
|
@ -50,7 +50,8 @@ div.navigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#listing td {
|
#listing td {
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing td.thumbnail {
|
#listing td.thumbnail {
|
||||||
@ -70,6 +71,7 @@ div.navigation {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
__version__ = '0.7.23'
|
__version__ = '0.7.24'
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -120,6 +120,11 @@ class InputFormatPlugin(Plugin):
|
|||||||
#: to make its output suitable for viewing
|
#: to make its output suitable for viewing
|
||||||
for_viewer = False
|
for_viewer = False
|
||||||
|
|
||||||
|
#: The encoding that this input plugin creates files in. A value of
|
||||||
|
#: None means that the encoding is undefined and must be
|
||||||
|
#: detected individually
|
||||||
|
output_encoding = 'utf-8'
|
||||||
|
|
||||||
#: Options shared by all Input format plugins. Do not override
|
#: Options shared by all Input format plugins. Do not override
|
||||||
#: in sub-classes. Use :attr:`options` instead. Every option must be an
|
#: in sub-classes. Use :attr:`options` instead. Every option must be an
|
||||||
#: instance of :class:`OptionRecommendation`.
|
#: instance of :class:`OptionRecommendation`.
|
||||||
|
@ -36,8 +36,8 @@ class KOBO(USBMS):
|
|||||||
PRODUCT_ID = [0x4161]
|
PRODUCT_ID = [0x4161]
|
||||||
BCD = [0x0110]
|
BCD = [0x0110]
|
||||||
|
|
||||||
VENDOR_NAME = 'KOBO_INC'
|
VENDOR_NAME = ['KOBO_INC', 'KOBO']
|
||||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '.KOBOEREADER'
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['.KOBOEREADER', 'EREADER']
|
||||||
|
|
||||||
EBOOK_DIR_MAIN = ''
|
EBOOK_DIR_MAIN = ''
|
||||||
SUPPORTS_SUB_DIRS = True
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
@ -838,7 +838,8 @@ OptionRecommendation(name='timestamp',
|
|||||||
self.opts_to_mi(self.user_metadata)
|
self.opts_to_mi(self.user_metadata)
|
||||||
if not hasattr(self.oeb, 'manifest'):
|
if not hasattr(self.oeb, 'manifest'):
|
||||||
self.oeb = create_oebbook(self.log, self.oeb, self.opts,
|
self.oeb = create_oebbook(self.log, self.oeb, self.opts,
|
||||||
self.input_plugin)
|
self.input_plugin,
|
||||||
|
encoding=self.input_plugin.output_encoding)
|
||||||
self.input_plugin.postprocess_book(self.oeb, self.opts, self.log)
|
self.input_plugin.postprocess_book(self.oeb, self.opts, self.log)
|
||||||
self.opts.is_image_collection = self.input_plugin.is_image_collection
|
self.opts.is_image_collection = self.input_plugin.is_image_collection
|
||||||
pr = CompositeProgressReporter(0.34, 0.67, self.ui_reporter)
|
pr = CompositeProgressReporter(0.34, 0.67, self.ui_reporter)
|
||||||
|
@ -543,6 +543,13 @@ class HTMLPreProcessor(object):
|
|||||||
def smarten_punctuation(self, html):
|
def smarten_punctuation(self, html):
|
||||||
from calibre.utils.smartypants import smartyPants
|
from calibre.utils.smartypants import smartyPants
|
||||||
from calibre.ebooks.chardet import substitute_entites
|
from calibre.ebooks.chardet import substitute_entites
|
||||||
|
from uuid import uuid4
|
||||||
|
start = 'calibre-smartypants-'+str(uuid4())
|
||||||
|
stop = 'calibre-smartypants-'+str(uuid4())
|
||||||
|
html = html.replace('<!--', start)
|
||||||
|
html = html.replace('-->', stop)
|
||||||
html = smartyPants(html)
|
html = smartyPants(html)
|
||||||
|
html = html.replace(start, '<!--')
|
||||||
|
html = html.replace(stop, '-->')
|
||||||
return substitute_entites(html)
|
return substitute_entites(html)
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
author = 'Kovid Goyal'
|
author = 'Kovid Goyal'
|
||||||
description = 'Convert EPUB files (.epub) to HTML'
|
description = 'Convert EPUB files (.epub) to HTML'
|
||||||
file_types = set(['epub'])
|
file_types = set(['epub'])
|
||||||
|
output_encoding = None
|
||||||
|
|
||||||
recommendations = set([('page_breaks_before', '/', OptionRecommendation.MED)])
|
recommendations = set([('page_breaks_before', '/', OptionRecommendation.MED)])
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -3,11 +3,16 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
|
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
|
||||||
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
|
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
|
||||||
QLineEdit
|
QLineEdit, Qt
|
||||||
|
|
||||||
|
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
|
||||||
|
from calibre import prints
|
||||||
|
|
||||||
class Item(QTreeWidgetItem):
|
class Item(QTreeWidgetItem):
|
||||||
pass
|
pass
|
||||||
@ -24,23 +29,28 @@ class CheckLibraryDialog(QDialog):
|
|||||||
self.setLayout(self._layout)
|
self.setLayout(self._layout)
|
||||||
|
|
||||||
self.log = QTreeWidget(self)
|
self.log = QTreeWidget(self)
|
||||||
|
self.log.itemChanged.connect(self.item_changed)
|
||||||
self._layout.addWidget(self.log)
|
self._layout.addWidget(self.log)
|
||||||
|
|
||||||
self.check = QPushButton(_('Run the check'))
|
self.check = QPushButton(_('&Run the check'))
|
||||||
self.check.setDefault(False)
|
self.check.setDefault(False)
|
||||||
self.check.clicked.connect(self.run_the_check)
|
self.check.clicked.connect(self.run_the_check)
|
||||||
self.copy = QPushButton(_('Copy to clipboard'))
|
self.copy = QPushButton(_('Copy &to clipboard'))
|
||||||
self.copy.setDefault(False)
|
self.copy.setDefault(False)
|
||||||
self.copy.clicked.connect(self.copy_to_clipboard)
|
self.copy.clicked.connect(self.copy_to_clipboard)
|
||||||
self.ok = QPushButton('&Done')
|
self.ok = QPushButton('&Done')
|
||||||
self.ok.setDefault(True)
|
self.ok.setDefault(True)
|
||||||
self.ok.clicked.connect(self.accept)
|
self.ok.clicked.connect(self.accept)
|
||||||
|
self.delete = QPushButton('Delete &marked')
|
||||||
|
self.delete.setDefault(False)
|
||||||
|
self.delete.clicked.connect(self.delete_marked)
|
||||||
self.cancel = QPushButton('&Cancel')
|
self.cancel = QPushButton('&Cancel')
|
||||||
self.cancel.setDefault(False)
|
self.cancel.setDefault(False)
|
||||||
self.cancel.clicked.connect(self.reject)
|
self.cancel.clicked.connect(self.reject)
|
||||||
self.bbox = QDialogButtonBox(self)
|
self.bbox = QDialogButtonBox(self)
|
||||||
self.bbox.addButton(self.copy, QDialogButtonBox.ActionRole)
|
self.bbox.addButton(self.copy, QDialogButtonBox.ActionRole)
|
||||||
self.bbox.addButton(self.check, QDialogButtonBox.ActionRole)
|
self.bbox.addButton(self.check, QDialogButtonBox.ActionRole)
|
||||||
|
self.bbox.addButton(self.delete, QDialogButtonBox.ActionRole)
|
||||||
self.bbox.addButton(self.cancel, QDialogButtonBox.RejectRole)
|
self.bbox.addButton(self.cancel, QDialogButtonBox.RejectRole)
|
||||||
self.bbox.addButton(self.ok, QDialogButtonBox.AcceptRole)
|
self.bbox.addButton(self.ok, QDialogButtonBox.AcceptRole)
|
||||||
|
|
||||||
@ -83,35 +93,66 @@ class CheckLibraryDialog(QDialog):
|
|||||||
plaintext = []
|
plaintext = []
|
||||||
|
|
||||||
def builder(tree, checker, check):
|
def builder(tree, checker, check):
|
||||||
attr = check[0]
|
attr, h, checkable = check
|
||||||
list = getattr(checker, attr, None)
|
list = getattr(checker, attr, None)
|
||||||
if list is None:
|
if list is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
h = check[1]
|
|
||||||
tl = Item([h])
|
tl = Item([h])
|
||||||
for problem in list:
|
for problem in list:
|
||||||
it = Item()
|
it = Item()
|
||||||
|
if checkable:
|
||||||
|
it.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
|
||||||
|
it.setCheckState(1, False)
|
||||||
|
else:
|
||||||
|
it.setFlags(Qt.ItemIsEnabled)
|
||||||
it.setText(0, problem[0])
|
it.setText(0, problem[0])
|
||||||
it.setText(1, problem[1])
|
it.setText(1, problem[1])
|
||||||
p = ', '.join(problem[2])
|
|
||||||
it.setText(2, p)
|
|
||||||
tl.addChild(it)
|
tl.addChild(it)
|
||||||
plaintext.append(','.join([h, problem[0], problem[1], p]))
|
self.all_items.append(it)
|
||||||
|
plaintext.append(','.join([h, problem[0], problem[1]]))
|
||||||
tree.addTopLevelItem(tl)
|
tree.addTopLevelItem(tl)
|
||||||
|
|
||||||
t = self.log
|
t = self.log
|
||||||
t.clear()
|
t.clear()
|
||||||
t.setColumnCount(3);
|
t.setColumnCount(2);
|
||||||
t.setHeaderLabels([_('Name'), _('Path from library'), _('Additional Information')])
|
t.setHeaderLabels([_('Name'), _('Path from library')])
|
||||||
|
self.all_items = []
|
||||||
for check in CHECKS:
|
for check in CHECKS:
|
||||||
builder(t, checker, check)
|
builder(t, checker, check)
|
||||||
|
|
||||||
t.setColumnWidth(0, 200)
|
t.setColumnWidth(0, 200)
|
||||||
t.setColumnWidth(1, 400)
|
t.setColumnWidth(1, 400)
|
||||||
|
self.delete.setEnabled(False)
|
||||||
self.text_results = '\n'.join(plaintext)
|
self.text_results = '\n'.join(plaintext)
|
||||||
|
|
||||||
|
def item_changed(self, item, column):
|
||||||
|
for it in self.all_items:
|
||||||
|
if it.checkState(1):
|
||||||
|
self.delete.setEnabled(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
def delete_marked(self):
|
||||||
|
if not confirm('<p>'+_('The marked files and folders will be '
|
||||||
|
'<b>permanently deleted</b>. Are you sure?')
|
||||||
|
+'</p>', 'check_library_editor_delete', self):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Sort the paths in reverse length order so that we can be sure that
|
||||||
|
# if an item is in another item, the sub-item will be deleted first.
|
||||||
|
items = sorted(self.all_items,
|
||||||
|
key=lambda x: len(x.text(1)),
|
||||||
|
reverse=True)
|
||||||
|
for it in items:
|
||||||
|
if it.checkState(1):
|
||||||
|
try:
|
||||||
|
delete_file(os.path.join(self.db.library_path, unicode(it.text(1))))
|
||||||
|
except:
|
||||||
|
prints('failed to delete',
|
||||||
|
os.path.join(self.db.library_path,
|
||||||
|
unicode(it.text(1))))
|
||||||
|
self.run_the_check()
|
||||||
|
|
||||||
def copy_to_clipboard(self):
|
def copy_to_clipboard(self):
|
||||||
QApplication.clipboard().setText(self.text_results)
|
QApplication.clipboard().setText(self.text_results)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -353,6 +353,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
self.pending_bookmark = bm
|
self.pending_bookmark = bm
|
||||||
if spine_index < 0 or spine_index >= len(self.iterator.spine):
|
if spine_index < 0 or spine_index >= len(self.iterator.spine):
|
||||||
spine_index = 0
|
spine_index = 0
|
||||||
|
self.pending_bookmark = None
|
||||||
self.load_path(self.iterator.spine[spine_index])
|
self.load_path(self.iterator.spine[spine_index])
|
||||||
|
|
||||||
def toc_clicked(self, index):
|
def toc_clicked(self, index):
|
||||||
|
@ -14,14 +14,14 @@ from calibre.ebooks import BOOK_EXTENSIONS
|
|||||||
EBOOK_EXTENSIONS = frozenset(BOOK_EXTENSIONS)
|
EBOOK_EXTENSIONS = frozenset(BOOK_EXTENSIONS)
|
||||||
NORMALS = frozenset(['metadata.opf', 'cover.jpg'])
|
NORMALS = frozenset(['metadata.opf', 'cover.jpg'])
|
||||||
|
|
||||||
CHECKS = [('invalid_titles', _('Invalid titles')),
|
CHECKS = [('invalid_titles', _('Invalid titles'), True),
|
||||||
('extra_titles', _('Extra titles')),
|
('extra_titles', _('Extra titles'), True),
|
||||||
('invalid_authors', _('Invalid authors')),
|
('invalid_authors', _('Invalid authors'), True),
|
||||||
('extra_authors', _('Extra authors')),
|
('extra_authors', _('Extra authors'), True),
|
||||||
('missing_formats', _('Missing book formats')),
|
('missing_formats', _('Missing book formats'), False),
|
||||||
('extra_formats', _('Extra book formats')),
|
('extra_formats', _('Extra book formats'), True),
|
||||||
('extra_files', _('Unknown files in books')),
|
('extra_files', _('Unknown files in books'), True),
|
||||||
('failed_folders', _('Folders raising exception'))
|
('failed_folders', _('Folders raising exception'), False)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +41,6 @@ class CheckLibrary(object):
|
|||||||
self.all_lc_dbpaths = frozenset([f.lower() for f in self.all_dbpaths])
|
self.all_lc_dbpaths = frozenset([f.lower() for f in self.all_dbpaths])
|
||||||
|
|
||||||
self.db_id_regexp = re.compile(r'^.* \((\d+)\)$')
|
self.db_id_regexp = re.compile(r'^.* \((\d+)\)$')
|
||||||
self.bad_ext_pat = re.compile(r'[^a-z0-9]+')
|
|
||||||
|
|
||||||
self.dirs = []
|
self.dirs = []
|
||||||
self.book_dirs = []
|
self.book_dirs = []
|
||||||
@ -78,7 +77,7 @@ class CheckLibrary(object):
|
|||||||
auth_path = os.path.join(lib, auth_dir)
|
auth_path = os.path.join(lib, auth_dir)
|
||||||
# First check: author must be a directory
|
# First check: author must be a directory
|
||||||
if not os.path.isdir(auth_path):
|
if not os.path.isdir(auth_path):
|
||||||
self.invalid_authors.append((auth_dir, auth_dir, []))
|
self.invalid_authors.append((auth_dir, auth_dir))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.potential_authors[auth_dir] = {}
|
self.potential_authors[auth_dir] = {}
|
||||||
@ -93,7 +92,7 @@ class CheckLibrary(object):
|
|||||||
m = self.db_id_regexp.search(title_dir)
|
m = self.db_id_regexp.search(title_dir)
|
||||||
# Second check: title must have an ID and must be a directory
|
# Second check: title must have an ID and must be a directory
|
||||||
if m is None or not os.path.isdir(title_path):
|
if m is None or not os.path.isdir(title_path):
|
||||||
self.invalid_titles.append((auth_dir, db_path, [title_dir]))
|
self.invalid_titles.append((auth_dir, db_path))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
id = m.group(1)
|
id = m.group(1)
|
||||||
@ -101,12 +100,12 @@ class CheckLibrary(object):
|
|||||||
if self.is_case_sensitive:
|
if self.is_case_sensitive:
|
||||||
if int(id) not in self.all_ids or \
|
if int(id) not in self.all_ids or \
|
||||||
db_path not in self.all_dbpaths:
|
db_path not in self.all_dbpaths:
|
||||||
self.extra_titles.append((title_dir, db_path, []))
|
self.extra_titles.append((title_dir, db_path))
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if int(id) not in self.all_ids or \
|
if int(id) not in self.all_ids or \
|
||||||
db_path.lower() not in self.all_lc_dbpaths:
|
db_path.lower() not in self.all_lc_dbpaths:
|
||||||
self.extra_titles.append((title_dir, db_path, []))
|
self.extra_titles.append((title_dir, db_path))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Record the book to check its formats
|
# Record the book to check its formats
|
||||||
@ -115,7 +114,7 @@ class CheckLibrary(object):
|
|||||||
|
|
||||||
# Fourth check: author directories that contain no titles
|
# Fourth check: author directories that contain no titles
|
||||||
if not found_titles:
|
if not found_titles:
|
||||||
self.extra_authors.append((auth_dir, auth_dir, []))
|
self.extra_authors.append((auth_dir, auth_dir))
|
||||||
|
|
||||||
for x in self.book_dirs:
|
for x in self.book_dirs:
|
||||||
try:
|
try:
|
||||||
@ -132,9 +131,7 @@ class CheckLibrary(object):
|
|||||||
ext = ext[1:].lower()
|
ext = ext[1:].lower()
|
||||||
if ext in EBOOK_EXTENSIONS:
|
if ext in EBOOK_EXTENSIONS:
|
||||||
return True
|
return True
|
||||||
if self.bad_ext_pat.search(ext) is not None:
|
return False
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def process_book(self, lib, book_info):
|
def process_book(self, lib, book_info):
|
||||||
(db_path, title_dir, book_id) = book_info
|
(db_path, title_dir, book_id) = book_info
|
||||||
@ -148,18 +145,18 @@ class CheckLibrary(object):
|
|||||||
if self.is_case_sensitive:
|
if self.is_case_sensitive:
|
||||||
unknowns = frozenset(filenames-formats-NORMALS)
|
unknowns = frozenset(filenames-formats-NORMALS)
|
||||||
# Check: any books that aren't formats or normally there?
|
# Check: any books that aren't formats or normally there?
|
||||||
if unknowns:
|
for u in unknowns:
|
||||||
self.extra_files.append((title_dir, db_path, unknowns))
|
self.extra_files.append((title_dir, os.path.join(db_path, u)))
|
||||||
|
|
||||||
# Check: any book formats that should be there?
|
# Check: any book formats that should be there?
|
||||||
missing = book_formats - formats
|
missing = book_formats - formats
|
||||||
if missing:
|
for m in missing:
|
||||||
self.missing_formats.append((title_dir, db_path, missing))
|
self.missing_formats.append((title_dir, os.path.join(db_path, m)))
|
||||||
|
|
||||||
# Check: any book formats that shouldn't be there?
|
# Check: any book formats that shouldn't be there?
|
||||||
extra = formats - book_formats - NORMALS
|
extra = formats - book_formats - NORMALS
|
||||||
if extra:
|
for e in extra:
|
||||||
self.extra_formats.append((title_dir, db_path, extra))
|
self.extra_formats.append((title_dir, os.path.join(db_path, e)))
|
||||||
else:
|
else:
|
||||||
def lc_map(fnames, fset):
|
def lc_map(fnames, fset):
|
||||||
m = {}
|
m = {}
|
||||||
@ -171,19 +168,16 @@ class CheckLibrary(object):
|
|||||||
formats_lc = frozenset([f.lower() for f in formats])
|
formats_lc = frozenset([f.lower() for f in formats])
|
||||||
unknowns = frozenset(filenames_lc-formats_lc-NORMALS)
|
unknowns = frozenset(filenames_lc-formats_lc-NORMALS)
|
||||||
# Check: any books that aren't formats or normally there?
|
# Check: any books that aren't formats or normally there?
|
||||||
if unknowns:
|
for f in lc_map(filenames, unknowns):
|
||||||
self.extra_files.append((title_dir, db_path,
|
self.extra_files.append((title_dir, os.path.join(db_path, f)))
|
||||||
lc_map(filenames, unknowns)))
|
|
||||||
|
|
||||||
book_formats_lc = frozenset([f.lower() for f in book_formats])
|
book_formats_lc = frozenset([f.lower() for f in book_formats])
|
||||||
# Check: any book formats that should be there?
|
# Check: any book formats that should be there?
|
||||||
missing = book_formats_lc - formats_lc
|
missing = book_formats_lc - formats_lc
|
||||||
if missing:
|
for m in lc_map(book_formats, missing):
|
||||||
self.missing_formats.append((title_dir, db_path,
|
self.missing_formats.append((title_dir, os.path.join(db_path, m)))
|
||||||
lc_map(book_formats, missing)))
|
|
||||||
|
|
||||||
# Check: any book formats that shouldn't be there?
|
# Check: any book formats that shouldn't be there?
|
||||||
extra = formats_lc - book_formats_lc - NORMALS
|
extra = formats_lc - book_formats_lc - NORMALS
|
||||||
if extra:
|
for e in lc_map(formats, extra):
|
||||||
self.extra_formats.append((title_dir, db_path,
|
self.extra_formats.append((title_dir, os.path.join(db_path, e)))
|
||||||
lc_map(formats, extra)))
|
|
||||||
|
@ -943,11 +943,11 @@ def command_check_library(args, dbpath):
|
|||||||
return
|
return
|
||||||
if opts.csv:
|
if opts.csv:
|
||||||
for i in list:
|
for i in list:
|
||||||
print check[1] + ',' + i[0] + ',' + i[1] + ',' + '|'.join(i[2])
|
print check[1] + ',' + i[0] + ',' + i[1]
|
||||||
else:
|
else:
|
||||||
print check[1]
|
print check[1]
|
||||||
for i in list:
|
for i in list:
|
||||||
print ' %-30.30s - %-30.30s - %s'%(i[0], i[1], ', '.join(i[2]))
|
print ' %-40.40s - %-40.40s'%(i[0], i[1])
|
||||||
|
|
||||||
db = LibraryDatabase2(dbpath)
|
db = LibraryDatabase2(dbpath)
|
||||||
checker = CheckLibrary(dbpath, db)
|
checker = CheckLibrary(dbpath, db)
|
||||||
|
@ -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:
|
||||||
|
@ -16,7 +16,7 @@ __builtin__.__dict__['_'] = lambda s: s
|
|||||||
# immediately translated to the environment language
|
# immediately translated to the environment language
|
||||||
__builtin__.__dict__['__'] = lambda s: s
|
__builtin__.__dict__['__'] = lambda s: s
|
||||||
|
|
||||||
from calibre.constants import iswindows, preferred_encoding, plugins
|
from calibre.constants import iswindows, preferred_encoding, plugins, isosx
|
||||||
|
|
||||||
_run_once = False
|
_run_once = False
|
||||||
winutil = winutilerror = None
|
winutil = winutilerror = None
|
||||||
@ -35,9 +35,17 @@ if not _run_once:
|
|||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Convert command line arguments to unicode
|
# Convert command line arguments to unicode
|
||||||
|
enc = preferred_encoding
|
||||||
|
if isosx:
|
||||||
|
# Newer versions of OS X seem to use UTF-8
|
||||||
|
try:
|
||||||
|
[x.decode('utf-8') for x in sys.argv[1:]]
|
||||||
|
enc = 'utf-8'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
for i in range(1, len(sys.argv)):
|
for i in range(1, len(sys.argv)):
|
||||||
if not isinstance(sys.argv[i], unicode):
|
if not isinstance(sys.argv[i], unicode):
|
||||||
sys.argv[i] = sys.argv[i].decode(preferred_encoding, 'replace')
|
sys.argv[i] = sys.argv[i].decode(enc, 'replace')
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Setup resources
|
# Setup resources
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
11559
src/calibre/translations/ur.po
Normal file
11559
src/calibre/translations/ur.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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