mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
IGN:Complete migration to new config infrastructure
This commit is contained in:
parent
fab60df71d
commit
4d450f556f
2
setup.py
2
setup.py
@ -7,7 +7,7 @@ sys.path.append('src')
|
|||||||
iswindows = re.search('win(32|64)', sys.platform)
|
iswindows = re.search('win(32|64)', sys.platform)
|
||||||
isosx = 'darwin' in sys.platform
|
isosx = 'darwin' in sys.platform
|
||||||
islinux = not isosx and not iswindows
|
islinux = not isosx and not iswindows
|
||||||
src = open('src/calibre/__init__.py', 'rb').read()
|
src = open('src/calibre/constants.py', 'rb').read()
|
||||||
VERSION = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
VERSION = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
||||||
APPNAME = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
APPNAME = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
||||||
print 'Setup', APPNAME, 'version:', VERSION
|
print 'Setup', APPNAME, 'version:', VERSION
|
||||||
|
@ -1,123 +1,21 @@
|
|||||||
''' E-book management software'''
|
''' E-book management software'''
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__version__ = '0.4.83'
|
__docformat__ = 'restructuredtext en'
|
||||||
__docformat__ = "epytext"
|
|
||||||
__author__ = "Kovid Goyal <kovid at kovidgoyal.net>"
|
|
||||||
__appname__ = 'calibre'
|
|
||||||
|
|
||||||
import sys, os, logging, mechanize, locale, copy, cStringIO, re, subprocess, \
|
import sys, os, re, logging, time, subprocess, mechanize, atexit
|
||||||
textwrap, atexit, cPickle, codecs, time
|
|
||||||
from gettext import GNUTranslations
|
|
||||||
from htmlentitydefs import name2codepoint
|
from htmlentitydefs import name2codepoint
|
||||||
from math import floor
|
from math import floor
|
||||||
from optparse import OptionParser as _OptionParser
|
|
||||||
from optparse import IndentedHelpFormatter
|
|
||||||
from logging import Formatter
|
from logging import Formatter
|
||||||
|
|
||||||
from PyQt4.QtCore import QSettings, QVariant, QUrl, QByteArray, QString
|
from PyQt4.QtCore import QUrl
|
||||||
from PyQt4.QtGui import QDesktopServices
|
from PyQt4.QtGui import QDesktopServices
|
||||||
|
from calibre.startup import plugins, winutil, winutilerror
|
||||||
|
from calibre.constants import iswindows, isosx, islinux, isfrozen, \
|
||||||
|
terminal_controller, preferred_encoding, \
|
||||||
|
__appname__, __version__, __author__, \
|
||||||
|
win32event, win32api, winerror, fcntl
|
||||||
|
|
||||||
from calibre.translations.msgfmt import make
|
|
||||||
from calibre.ebooks.chardet import detect
|
|
||||||
from calibre.utils.terminfo import TerminalController
|
|
||||||
|
|
||||||
terminal_controller = TerminalController(sys.stdout)
|
|
||||||
iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower()
|
|
||||||
isosx = 'darwin' in sys.platform.lower()
|
|
||||||
islinux = not(iswindows or isosx)
|
|
||||||
isfrozen = hasattr(sys, 'frozen')
|
|
||||||
|
|
||||||
try:
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
except:
|
|
||||||
dl = locale.getdefaultlocale()
|
|
||||||
try:
|
|
||||||
if dl:
|
|
||||||
locale.setlocale(dl[0])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
preferred_encoding = locale.getpreferredencoding()
|
|
||||||
codecs.lookup(preferred_encoding)
|
|
||||||
except:
|
|
||||||
preferred_encoding = 'utf-8'
|
|
||||||
|
|
||||||
if getattr(sys, 'frozen', False):
|
|
||||||
if iswindows:
|
|
||||||
plugin_path = os.path.join(os.path.dirname(sys.executable), 'plugins')
|
|
||||||
elif isosx:
|
|
||||||
plugin_path = os.path.join(getattr(sys, 'frameworks_dir'), 'plugins')
|
|
||||||
elif islinux:
|
|
||||||
plugin_path = os.path.join(getattr(sys, 'frozen_path'), 'plugins')
|
|
||||||
sys.path.insert(0, plugin_path)
|
|
||||||
else:
|
|
||||||
import pkg_resources
|
|
||||||
plugins = getattr(pkg_resources, 'resource_filename')(__appname__, 'plugins')
|
|
||||||
sys.path.insert(0, plugins)
|
|
||||||
|
|
||||||
if iswindows and getattr(sys, 'frozen', False):
|
|
||||||
sys.path.insert(1, os.path.dirname(sys.executable))
|
|
||||||
|
|
||||||
|
|
||||||
plugins = {}
|
|
||||||
for plugin in ['pictureflow', 'lzx', 'msdes'] + \
|
|
||||||
(['winutil'] if iswindows else []) + \
|
|
||||||
(['usbobserver'] if isosx else []):
|
|
||||||
try:
|
|
||||||
p, err = __import__(plugin), ''
|
|
||||||
except Exception, err:
|
|
||||||
p = None
|
|
||||||
err = str(err)
|
|
||||||
plugins[plugin] = (p, err)
|
|
||||||
|
|
||||||
if iswindows:
|
|
||||||
winutil, winutilerror = plugins['winutil']
|
|
||||||
if not winutil:
|
|
||||||
raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror)
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
sys.argv[1:] = winutil.argv()[1-len(sys.argv):]
|
|
||||||
win32event = __import__('win32event')
|
|
||||||
winerror = __import__('winerror')
|
|
||||||
win32api = __import__('win32api')
|
|
||||||
else:
|
|
||||||
import fcntl
|
|
||||||
|
|
||||||
_abspath = os.path.abspath
|
|
||||||
def my_abspath(path, encoding=sys.getfilesystemencoding()):
|
|
||||||
'''
|
|
||||||
Work around for buggy os.path.abspath. This function accepts either byte strings,
|
|
||||||
in which it calls os.path.abspath, or unicode string, in which case it first converts
|
|
||||||
to byte strings using `encoding`, calls abspath and then decodes back to unicode.
|
|
||||||
'''
|
|
||||||
to_unicode = False
|
|
||||||
if isinstance(path, unicode):
|
|
||||||
path = path.encode(encoding)
|
|
||||||
to_unicode = True
|
|
||||||
res = _abspath(path)
|
|
||||||
if to_unicode:
|
|
||||||
res = res.decode(encoding)
|
|
||||||
return res
|
|
||||||
|
|
||||||
os.path.abspath = my_abspath
|
|
||||||
_join = os.path.join
|
|
||||||
def my_join(a, *p):
|
|
||||||
encoding=sys.getfilesystemencoding()
|
|
||||||
p = [a] + list(p)
|
|
||||||
_unicode = False
|
|
||||||
for i in p:
|
|
||||||
if isinstance(i, unicode):
|
|
||||||
_unicode = True
|
|
||||||
break
|
|
||||||
p = [i.encode(encoding) if isinstance(i, unicode) else i for i in p]
|
|
||||||
|
|
||||||
res = _join(*p)
|
|
||||||
if _unicode:
|
|
||||||
res = res.decode(encoding)
|
|
||||||
return res
|
|
||||||
|
|
||||||
os.path.join = my_join
|
|
||||||
|
|
||||||
def unicode_path(path, abs=False):
|
def unicode_path(path, abs=False):
|
||||||
if not isinstance(path, unicode):
|
if not isinstance(path, unicode):
|
||||||
@ -135,10 +33,6 @@ def osx_version():
|
|||||||
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||||
|
|
||||||
|
|
||||||
# Default translation is NOOP
|
|
||||||
import __builtin__
|
|
||||||
__builtin__.__dict__['_'] = lambda s: s
|
|
||||||
|
|
||||||
class CommandLineError(Exception):
|
class CommandLineError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -180,122 +74,6 @@ def setup_cli_handlers(logger, level):
|
|||||||
|
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
class CustomHelpFormatter(IndentedHelpFormatter):
|
|
||||||
|
|
||||||
def format_usage(self, usage):
|
|
||||||
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
|
|
||||||
|
|
||||||
def format_heading(self, heading):
|
|
||||||
return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
|
|
||||||
"", heading, terminal_controller.NORMAL)
|
|
||||||
|
|
||||||
def format_option(self, option):
|
|
||||||
result = []
|
|
||||||
opts = self.option_strings[option]
|
|
||||||
opt_width = self.help_position - self.current_indent - 2
|
|
||||||
if len(opts) > opt_width:
|
|
||||||
opts = "%*s%s\n" % (self.current_indent, "",
|
|
||||||
terminal_controller.GREEN+opts+terminal_controller.NORMAL)
|
|
||||||
indent_first = self.help_position
|
|
||||||
else: # start help on same line as opts
|
|
||||||
opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
|
|
||||||
terminal_controller.GREEN + opts + terminal_controller.NORMAL)
|
|
||||||
indent_first = 0
|
|
||||||
result.append(opts)
|
|
||||||
if option.help:
|
|
||||||
help_text = self.expand_default(option).split('\n')
|
|
||||||
help_lines = []
|
|
||||||
|
|
||||||
for line in help_text:
|
|
||||||
help_lines.extend(textwrap.wrap(line, self.help_width))
|
|
||||||
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
|
|
||||||
result.extend(["%*s%s\n" % (self.help_position, "", line)
|
|
||||||
for line in help_lines[1:]])
|
|
||||||
elif opts[-1] != "\n":
|
|
||||||
result.append("\n")
|
|
||||||
return "".join(result)+'\n'
|
|
||||||
|
|
||||||
class OptionParser(_OptionParser):
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
usage='%prog [options] filename',
|
|
||||||
version='%%prog (%s %s)'%(__appname__, __version__),
|
|
||||||
epilog=_('Created by ')+terminal_controller.RED+__author__+terminal_controller.NORMAL,
|
|
||||||
gui_mode=False,
|
|
||||||
conflict_handler='resolve',
|
|
||||||
**kwds):
|
|
||||||
usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
|
|
||||||
'''enclose the arguments in quotation marks.'''
|
|
||||||
_OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
|
|
||||||
formatter=CustomHelpFormatter(),
|
|
||||||
conflict_handler=conflict_handler, **kwds)
|
|
||||||
self.gui_mode = gui_mode
|
|
||||||
|
|
||||||
def error(self, msg):
|
|
||||||
if self.gui_mode:
|
|
||||||
raise Exception(msg)
|
|
||||||
_OptionParser.error(self, msg)
|
|
||||||
|
|
||||||
def merge(self, parser):
|
|
||||||
'''
|
|
||||||
Add options from parser to self. In case of conflicts, confilicting options from
|
|
||||||
parser are skipped.
|
|
||||||
'''
|
|
||||||
opts = list(parser.option_list)
|
|
||||||
groups = list(parser.option_groups)
|
|
||||||
|
|
||||||
def merge_options(options, container):
|
|
||||||
for opt in copy.deepcopy(options):
|
|
||||||
if not self.has_option(opt.get_opt_string()):
|
|
||||||
container.add_option(opt)
|
|
||||||
|
|
||||||
merge_options(opts, self)
|
|
||||||
|
|
||||||
for group in groups:
|
|
||||||
g = self.add_option_group(group.title)
|
|
||||||
merge_options(group.option_list, g)
|
|
||||||
|
|
||||||
def subsume(self, group_name, msg=''):
|
|
||||||
'''
|
|
||||||
Move all existing options into a subgroup named
|
|
||||||
C{group_name} with description C{msg}.
|
|
||||||
'''
|
|
||||||
opts = [opt for opt in self.options_iter() if opt.get_opt_string() not in ('--version', '--help')]
|
|
||||||
self.option_groups = []
|
|
||||||
subgroup = self.add_option_group(group_name, msg)
|
|
||||||
for opt in opts:
|
|
||||||
self.remove_option(opt.get_opt_string())
|
|
||||||
subgroup.add_option(opt)
|
|
||||||
|
|
||||||
def options_iter(self):
|
|
||||||
for opt in self.option_list:
|
|
||||||
if str(opt).strip():
|
|
||||||
yield opt
|
|
||||||
for gr in self.option_groups:
|
|
||||||
for opt in gr.option_list:
|
|
||||||
if str(opt).strip():
|
|
||||||
yield opt
|
|
||||||
|
|
||||||
def option_by_dest(self, dest):
|
|
||||||
for opt in self.options_iter():
|
|
||||||
if opt.dest == dest:
|
|
||||||
return opt
|
|
||||||
|
|
||||||
def merge_options(self, lower, upper):
|
|
||||||
'''
|
|
||||||
Merge options in lower and upper option lists into upper.
|
|
||||||
Default values in upper are overriden by
|
|
||||||
non default values in lower.
|
|
||||||
'''
|
|
||||||
for dest in lower.__dict__.keys():
|
|
||||||
if not upper.__dict__.has_key(dest):
|
|
||||||
continue
|
|
||||||
opt = self.option_by_dest(dest)
|
|
||||||
if lower.__dict__[dest] != opt.default and \
|
|
||||||
upper.__dict__[dest] == opt.default:
|
|
||||||
upper.__dict__[dest] = lower.__dict__[dest]
|
|
||||||
|
|
||||||
|
|
||||||
def load_library(name, cdll):
|
def load_library(name, cdll):
|
||||||
if iswindows:
|
if iswindows:
|
||||||
return cdll.LoadLibrary(name)
|
return cdll.LoadLibrary(name)
|
||||||
@ -392,40 +170,6 @@ def fit_image(width, height, pwidth, pheight):
|
|||||||
|
|
||||||
return scaled, int(width), int(height)
|
return scaled, int(width), int(height)
|
||||||
|
|
||||||
def get_lang():
|
|
||||||
lang = locale.getdefaultlocale()[0]
|
|
||||||
if lang is None and os.environ.has_key('LANG'): # Needed for OS X
|
|
||||||
try:
|
|
||||||
lang = os.environ['LANG']
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if lang:
|
|
||||||
match = re.match('[a-z]{2,3}', lang)
|
|
||||||
if match:
|
|
||||||
lang = match.group()
|
|
||||||
return lang
|
|
||||||
|
|
||||||
def set_translator():
|
|
||||||
# To test different translations invoke as
|
|
||||||
# LC_ALL=de_DE.utf8 program
|
|
||||||
try:
|
|
||||||
from calibre.translations.compiled import translations
|
|
||||||
except:
|
|
||||||
return
|
|
||||||
lang = get_lang()
|
|
||||||
if lang:
|
|
||||||
buf = None
|
|
||||||
if os.access(lang+'.po', os.R_OK):
|
|
||||||
buf = cStringIO.StringIO()
|
|
||||||
make(lang+'.po', buf)
|
|
||||||
buf = cStringIO.StringIO(buf.getvalue())
|
|
||||||
elif translations.has_key(lang):
|
|
||||||
buf = cStringIO.StringIO(translations[lang])
|
|
||||||
if buf is not None:
|
|
||||||
t = GNUTranslations(buf)
|
|
||||||
t.install(unicode=True)
|
|
||||||
|
|
||||||
set_translator()
|
|
||||||
|
|
||||||
def sanitize_file_name(name):
|
def sanitize_file_name(name):
|
||||||
'''
|
'''
|
||||||
@ -510,120 +254,6 @@ def relpath(target, base=os.curdir):
|
|||||||
rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:]
|
rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:]
|
||||||
return os.path.join(*rel_list)
|
return os.path.join(*rel_list)
|
||||||
|
|
||||||
def _clean_lock_file(file):
|
|
||||||
try:
|
|
||||||
file.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
os.remove(file.name)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class LockError(Exception):
|
|
||||||
pass
|
|
||||||
class ExclusiveFile(object):
|
|
||||||
|
|
||||||
def __init__(self, path, timeout=10):
|
|
||||||
self.path = path
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.file = open(self.path, 'a+b')
|
|
||||||
self.file.seek(0)
|
|
||||||
timeout = self.timeout
|
|
||||||
if iswindows:
|
|
||||||
name = ('Local\\'+(__appname__+self.file.name).replace('\\', '_'))[:201]
|
|
||||||
while self.timeout < 0 or timeout >= 0:
|
|
||||||
self.mutex = win32event.CreateMutex(None, False, name)
|
|
||||||
if win32api.GetLastError() != winerror.ERROR_ALREADY_EXISTS: break
|
|
||||||
time.sleep(1)
|
|
||||||
timeout -= 1
|
|
||||||
else:
|
|
||||||
while self.timeout < 0 or timeout >= 0:
|
|
||||||
try:
|
|
||||||
fcntl.lockf(self.file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
|
||||||
break
|
|
||||||
except IOError:
|
|
||||||
time.sleep(1)
|
|
||||||
timeout -= 1
|
|
||||||
if timeout < 0 and self.timeout >= 0:
|
|
||||||
self.file.close()
|
|
||||||
raise LockError
|
|
||||||
return self.file
|
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
|
||||||
self.file.close()
|
|
||||||
if iswindows:
|
|
||||||
win32api.CloseHandle(self.mutex)
|
|
||||||
|
|
||||||
def singleinstance(name):
|
|
||||||
'''
|
|
||||||
Return True if no other instance of the application identified by name is running,
|
|
||||||
False otherwise.
|
|
||||||
@param name: The name to lock.
|
|
||||||
@type name: string
|
|
||||||
'''
|
|
||||||
if iswindows:
|
|
||||||
mutexname = 'mutexforsingleinstanceof'+__appname__+name
|
|
||||||
mutex = win32event.CreateMutex(None, False, mutexname)
|
|
||||||
if mutex:
|
|
||||||
atexit.register(win32api.CloseHandle, mutex)
|
|
||||||
return not win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS
|
|
||||||
else:
|
|
||||||
global _lock_file
|
|
||||||
path = os.path.expanduser('~/.'+__appname__+'_'+name+'.lock')
|
|
||||||
try:
|
|
||||||
f = open(path, 'w')
|
|
||||||
fcntl.lockf(f.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
|
||||||
atexit.register(_clean_lock_file, f)
|
|
||||||
return True
|
|
||||||
except IOError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
class Settings(QSettings):
|
|
||||||
|
|
||||||
def __init__(self, name='calibre2'):
|
|
||||||
QSettings.__init__(self, QSettings.IniFormat, QSettings.UserScope,
|
|
||||||
'kovidgoyal.net', name)
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
try:
|
|
||||||
key = str(key)
|
|
||||||
if not self.contains(key):
|
|
||||||
return default
|
|
||||||
val = str(self.value(key, QVariant()).toByteArray())
|
|
||||||
if not val:
|
|
||||||
return None
|
|
||||||
return cPickle.loads(val)
|
|
||||||
except:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def set(self, key, val):
|
|
||||||
val = cPickle.dumps(val, -1)
|
|
||||||
self.setValue(str(key), QVariant(QByteArray(val)))
|
|
||||||
|
|
||||||
_settings = Settings()
|
|
||||||
|
|
||||||
if not _settings.get('rationalized'):
|
|
||||||
__settings = Settings(name='calibre')
|
|
||||||
dbpath = os.path.join(os.path.expanduser('~'), 'library1.db').decode(sys.getfilesystemencoding())
|
|
||||||
dbpath = unicode(__settings.value('database path',
|
|
||||||
QVariant(QString.fromUtf8(dbpath.encode('utf-8')))).toString())
|
|
||||||
cmdline = __settings.value('LRF conversion defaults', QVariant(QByteArray(''))).toByteArray().data()
|
|
||||||
|
|
||||||
if cmdline:
|
|
||||||
cmdline = cPickle.loads(cmdline)
|
|
||||||
_settings.set('LRF conversion defaults', cmdline)
|
|
||||||
_settings.set('rationalized', True)
|
|
||||||
try:
|
|
||||||
os.unlink(unicode(__settings.fileName()))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
_settings.set('database path', dbpath)
|
|
||||||
|
|
||||||
_spat = re.compile(r'^the\s+|^a\s+|^an\s+', re.IGNORECASE)
|
_spat = re.compile(r'^the\s+|^a\s+|^an\s+', re.IGNORECASE)
|
||||||
def english_sort(x, y):
|
def english_sort(x, y):
|
||||||
'''
|
'''
|
||||||
@ -671,10 +301,7 @@ def strftime(fmt, t=time.localtime()):
|
|||||||
A version of strtime that returns unicode strings.
|
A version of strtime that returns unicode strings.
|
||||||
'''
|
'''
|
||||||
result = time.strftime(fmt, t)
|
result = time.strftime(fmt, t)
|
||||||
try:
|
return unicode(result, preferred_encoding, 'replace')
|
||||||
return unicode(result, locale.getpreferredencoding(), 'replace')
|
|
||||||
except:
|
|
||||||
return unicode(result, 'utf-8', 'replace')
|
|
||||||
|
|
||||||
def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
|
def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
|
||||||
'''
|
'''
|
||||||
@ -716,6 +343,10 @@ if isosx:
|
|||||||
if not os.path.exists(os.path.join(fdir, 'LiberationSans_Regular.ttf')):
|
if not os.path.exists(os.path.join(fdir, 'LiberationSans_Regular.ttf')):
|
||||||
from calibre.ebooks.lrf.fonts.liberation import __all__ as fonts
|
from calibre.ebooks.lrf.fonts.liberation import __all__ as fonts
|
||||||
for font in fonts:
|
for font in fonts:
|
||||||
exec 'from calibre.ebooks.lrf.fonts.liberation.'+font+' import font_data'
|
l = {}
|
||||||
open(os.path.join(fdir, font+'.ttf'), 'wb').write(font_data)
|
exec 'from calibre.ebooks.lrf.fonts.liberation.'+font+' import font_data' in l
|
||||||
|
open(os.path.join(fdir, font+'.ttf'), 'wb').write(l['font_data'])
|
||||||
|
|
||||||
|
# Migrate from QSettings based config system
|
||||||
|
from calibre.utils.config import migrate
|
||||||
|
migrate()
|
30
src/calibre/constants.py
Normal file
30
src/calibre/constants.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
__appname__ = 'calibre'
|
||||||
|
__version__ = '0.4.83'
|
||||||
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
'''
|
||||||
|
Various run time constants.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys, locale, codecs
|
||||||
|
from calibre.utils.terminfo import TerminalController
|
||||||
|
|
||||||
|
terminal_controller = TerminalController(sys.stdout)
|
||||||
|
|
||||||
|
iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower()
|
||||||
|
isosx = 'darwin' in sys.platform.lower()
|
||||||
|
islinux = not(iswindows or isosx)
|
||||||
|
isfrozen = hasattr(sys, 'frozen')
|
||||||
|
|
||||||
|
try:
|
||||||
|
preferred_encoding = locale.getpreferredencoding()
|
||||||
|
codecs.lookup(preferred_encoding)
|
||||||
|
except:
|
||||||
|
preferred_encoding = 'utf-8'
|
||||||
|
|
||||||
|
win32event = __import__('win32event') if iswindows else None
|
||||||
|
winerror = __import__('winerror') if iswindows else None
|
||||||
|
win32api = __import__('win32api') if iswindows else None
|
||||||
|
fcntl = None if iswindows else __import__('fcntl')
|
@ -7,7 +7,8 @@ Embedded console for debugging.
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import sys, os, re
|
import sys, os, re
|
||||||
from calibre import OptionParser, iswindows, isosx
|
from calibre.utils.config import OptionParser
|
||||||
|
from calibre.constants import iswindows, isosx
|
||||||
from calibre.libunzip import update
|
from calibre.libunzip import update
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
|
@ -808,7 +808,7 @@ class LitReader(object):
|
|||||||
os.makedirs(dir)
|
os.makedirs(dir)
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
from calibre import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
parser = OptionParser(usage=_('%prog [options] LITFILE'))
|
parser = OptionParser(usage=_('%prog [options] LITFILE'))
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'-o', '--output-dir', default='.',
|
'-o', '--output-dir', default='.',
|
||||||
|
@ -14,7 +14,8 @@ from calibre.ebooks.lrf.pylrs.pylrs import TextBlock, Header, PutObj, \
|
|||||||
Paragraph, TextStyle, BlockStyle
|
Paragraph, TextStyle, BlockStyle
|
||||||
from calibre.ebooks.lrf.fonts import FONT_FILE_MAP
|
from calibre.ebooks.lrf.fonts import FONT_FILE_MAP
|
||||||
from calibre.ebooks import ConversionError
|
from calibre.ebooks import ConversionError
|
||||||
from calibre import __appname__, __version__, __author__, iswindows, OptionParser
|
from calibre import __appname__, __version__, __author__, iswindows
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
|
|
||||||
__docformat__ = "epytext"
|
__docformat__ = "epytext"
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
import sys, logging, os
|
import sys, logging, os
|
||||||
|
|
||||||
from calibre import setup_cli_handlers, OptionParser
|
from calibre import setup_cli_handlers
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
from calibre.ebooks import ConversionError
|
from calibre.ebooks import ConversionError
|
||||||
from calibre.ebooks.lrf.meta import get_metadata
|
from calibre.ebooks.lrf.meta import get_metadata
|
||||||
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
||||||
|
@ -4,7 +4,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import sys, array, os, re, codecs, logging
|
import sys, array, os, re, codecs, logging
|
||||||
|
|
||||||
from calibre import OptionParser, setup_cli_handlers
|
from calibre import setup_cli_handlers
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
from calibre.ebooks.lrf.meta import LRFMetaFile
|
from calibre.ebooks.lrf.meta import LRFMetaFile
|
||||||
from calibre.ebooks.lrf.objects import get_object, PageTree, StyleObject, \
|
from calibre.ebooks.lrf.objects import get_object, PageTree, StyleObject, \
|
||||||
Font, Text, TOCObject, BookAttr, ruby_tags
|
Font, Text, TOCObject, BookAttr, ruby_tags
|
||||||
|
@ -6,7 +6,8 @@ Compile a LRS file into a LRF file.
|
|||||||
|
|
||||||
import sys, os, logging
|
import sys, os, logging
|
||||||
|
|
||||||
from calibre import OptionParser, setup_cli_handlers
|
from calibre import setup_cli_handlers
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, NavigableString, \
|
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, NavigableString, \
|
||||||
CData, Tag
|
CData, Tag
|
||||||
from calibre.ebooks.lrf.pylrs.pylrs import Book, PageStyle, TextStyle, \
|
from calibre.ebooks.lrf.pylrs.pylrs import Book, PageStyle, TextStyle, \
|
||||||
|
@ -574,8 +574,8 @@ class LRFMetaFile(object):
|
|||||||
|
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
from optparse import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
from calibre import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
parser = OptionParser(usage = \
|
parser = OptionParser(usage = \
|
||||||
_('''%prog [options] mybook.lrf
|
_('''%prog [options] mybook.lrf
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ Convert PDF to a reflowable format using pdftoxml.exe as the PDF parsing backend
|
|||||||
import sys, os, re, tempfile, subprocess, atexit, shutil, logging, xml.parsers.expat
|
import sys, os, re, tempfile, subprocess, atexit, shutil, logging, xml.parsers.expat
|
||||||
from xml.etree.ElementTree import parse
|
from xml.etree.ElementTree import parse
|
||||||
|
|
||||||
from calibre import isosx, OptionParser, setup_cli_handlers, __appname__
|
from calibre import isosx, setup_cli_handlers, __appname__
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
from calibre.ebooks import ConversionError
|
from calibre.ebooks import ConversionError
|
||||||
|
|
||||||
PDFTOXML = 'pdftoxml.exe'
|
PDFTOXML = 'pdftoxml.exe'
|
||||||
|
@ -11,8 +11,9 @@ from urllib import unquote, quote
|
|||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
|
|
||||||
from calibre import __version__ as VERSION, relpath
|
from calibre.constants import __version__ as VERSION
|
||||||
from calibre import OptionParser
|
from calibre import relpath
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
|
|
||||||
def get_parser(extension):
|
def get_parser(extension):
|
||||||
''' Return an option parser with the basic metadata options already setup'''
|
''' Return an option parser with the basic metadata options already setup'''
|
||||||
|
@ -7,7 +7,8 @@ Interface to isbndb.com. My key HLLXQX2A.
|
|||||||
import sys, logging, re, socket
|
import sys, logging, re, socket
|
||||||
from urllib import urlopen, quote
|
from urllib import urlopen, quote
|
||||||
|
|
||||||
from calibre import setup_cli_handlers, OptionParser
|
from calibre import setup_cli_handlers
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
|
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ Fetch cover from LibraryThing.com based on ISBN number.
|
|||||||
|
|
||||||
import sys, socket, os, re, mechanize
|
import sys, socket, os, re, mechanize
|
||||||
|
|
||||||
from calibre import browser as _browser, OptionParser
|
from calibre import browser as _browser
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
browser = None
|
browser = None
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import os, re, collections
|
import os, re, collections
|
||||||
|
|
||||||
|
from calibre.utils.config import prefs
|
||||||
from calibre.ebooks.metadata.rtf import get_metadata as rtf_metadata
|
from calibre.ebooks.metadata.rtf import get_metadata as rtf_metadata
|
||||||
from calibre.ebooks.metadata.fb2 import get_metadata as fb2_metadata
|
from calibre.ebooks.metadata.fb2 import get_metadata as fb2_metadata
|
||||||
from calibre.ebooks.lrf.meta import get_metadata as lrf_metadata
|
from calibre.ebooks.lrf.meta import get_metadata as lrf_metadata
|
||||||
@ -94,20 +95,11 @@ def set_metadata(stream, mi, stream_type='lrf'):
|
|||||||
elif stream_type == 'rtf':
|
elif stream_type == 'rtf':
|
||||||
set_rtf_metadata(stream, mi)
|
set_rtf_metadata(stream, mi)
|
||||||
|
|
||||||
_filename_pat = re.compile(ur'(?P<title>.+) - (?P<author>[^_]+)')
|
|
||||||
|
|
||||||
def get_filename_pat():
|
|
||||||
return _filename_pat.pattern
|
|
||||||
|
|
||||||
def set_filename_pat(pat):
|
|
||||||
global _filename_pat
|
|
||||||
_filename_pat = re.compile(pat)
|
|
||||||
|
|
||||||
def metadata_from_filename(name, pat=None):
|
def metadata_from_filename(name, pat=None):
|
||||||
name = os.path.splitext(name)[0]
|
name = os.path.splitext(name)[0]
|
||||||
mi = MetaInformation(None, None)
|
mi = MetaInformation(None, None)
|
||||||
if pat is None:
|
if pat is None:
|
||||||
pat = _filename_pat
|
pat = re.compile(prefs.get('filename_pattern'))
|
||||||
match = pat.search(name)
|
match = pat.search(name)
|
||||||
if match:
|
if match:
|
||||||
try:
|
try:
|
||||||
|
@ -408,7 +408,7 @@ def get_metadata(stream):
|
|||||||
return mi
|
return mi
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
from calibre import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
parser = OptionParser(usage=_('%prog [options] myebook.mobi'))
|
parser = OptionParser(usage=_('%prog [options] myebook.mobi'))
|
||||||
parser.add_option('-o', '--output-dir', default='.',
|
parser.add_option('-o', '--output-dir', default='.',
|
||||||
help=_('Output directory. Defaults to current directory.'))
|
help=_('Output directory. Defaults to current directory.'))
|
||||||
|
@ -2,19 +2,49 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
""" The GUI """
|
""" The GUI """
|
||||||
import sys, os, re, StringIO, traceback
|
import sys, os, re, StringIO, traceback
|
||||||
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
|
||||||
QByteArray, QLocale, QUrl, QTranslator, QCoreApplication
|
QByteArray, QLocale, QUrl, QTranslator, QCoreApplication
|
||||||
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||||
QIcon, QTableView, QDialogButtonBox, QApplication
|
QIcon, QTableView, QDialogButtonBox, QApplication
|
||||||
|
|
||||||
ORG_NAME = 'KovidsBrain'
|
ORG_NAME = 'KovidsBrain'
|
||||||
APP_UID = 'libprs500'
|
APP_UID = 'libprs500'
|
||||||
from calibre import __author__, islinux, iswindows, Settings, isosx, get_lang
|
from calibre import __author__, islinux, iswindows, isosx
|
||||||
|
from calibre.startup import get_lang
|
||||||
|
from calibre.utils.config import Config, ConfigProxy, dynamic
|
||||||
import calibre.resources as resources
|
import calibre.resources as resources
|
||||||
|
|
||||||
NONE = QVariant() #: Null value to return from the data function of item models
|
NONE = QVariant() #: Null value to return from the data function of item models
|
||||||
|
|
||||||
|
def _config():
|
||||||
|
c = Config('gui', 'preferences for the calibre GUI')
|
||||||
|
c.add_opt('frequently_used_directories', default=[],
|
||||||
|
help=_('Frequently used directories'))
|
||||||
|
c.add_opt('send_to_device_by_default', default=True,
|
||||||
|
help=_('Send downloaded periodical content to device automatically'))
|
||||||
|
c.add_opt('save_to_disk_single_format', default='lrf',
|
||||||
|
help=_('The format to use when saving single files to disk'))
|
||||||
|
c.add_opt('confirm_delete', default=False,
|
||||||
|
help=_('Confirm before deleting'))
|
||||||
|
c.add_opt('toolbar_icon_size', default=QSize(48, 48),
|
||||||
|
help=_('Toolbar icon size')) # value QVariant.toSize
|
||||||
|
c.add_opt('show_text_in_toolbar', default=True,
|
||||||
|
help=_('Show button labels in the toolbar'))
|
||||||
|
c.add_opt('main_window_geometry', default=None,
|
||||||
|
help=_('Main window geometry')) # value QVariant.toByteArray
|
||||||
|
c.add_opt('new_version_notification', default=True,
|
||||||
|
help=_('Notify when a new version is available'))
|
||||||
|
c.add_opt('use_roman_numerals_for_series_number', default=True,
|
||||||
|
help=_('Use Roman numerals for series number'))
|
||||||
|
c.add_opt('cover_flow_queue_length', default=6,
|
||||||
|
help=_('Number of covers to show in the cover browsing mode'))
|
||||||
|
c.add_opt('LRF_conversion_defaults', default=[],
|
||||||
|
help=_('Defaults for conversion to LRF'))
|
||||||
|
c.add_opt('LRF_ebook_viewer_options', default=None,
|
||||||
|
help=_('Options for the LRF ebook viewer'))
|
||||||
|
return ConfigProxy(c)
|
||||||
|
|
||||||
|
config = _config()
|
||||||
# Turn off DeprecationWarnings in windows GUI
|
# Turn off DeprecationWarnings in windows GUI
|
||||||
if iswindows:
|
if iswindows:
|
||||||
import warnings
|
import warnings
|
||||||
@ -105,14 +135,12 @@ class TableView(QTableView):
|
|||||||
QTableView.__init__(self, parent)
|
QTableView.__init__(self, parent)
|
||||||
self.read_settings()
|
self.read_settings()
|
||||||
|
|
||||||
|
|
||||||
def read_settings(self):
|
def read_settings(self):
|
||||||
self.cw = Settings().get(self.__class__.__name__ + ' column widths')
|
self.cw = dynamic[self.__class__.__name__+'column widths']
|
||||||
|
|
||||||
def write_settings(self):
|
def write_settings(self):
|
||||||
settings = Settings()
|
dynamic[self.__class__.__name__+'column widths'] = \
|
||||||
settings.set(self.__class__.__name__ + ' column widths',
|
tuple([int(self.columnWidth(i)) for i in range(self.model().columnCount(None))])
|
||||||
tuple([int(self.columnWidth(i)) for i in range(self.model().columnCount(None))]))
|
|
||||||
|
|
||||||
def restore_column_widths(self):
|
def restore_column_widths(self):
|
||||||
if self.cw and len(self.cw):
|
if self.cw and len(self.cw):
|
||||||
@ -125,10 +153,11 @@ class TableView(QTableView):
|
|||||||
@param cols: A list of booleans or None. If an entry is False the corresponding column
|
@param cols: A list of booleans or None. If an entry is False the corresponding column
|
||||||
is hidden, if True it is shown.
|
is hidden, if True it is shown.
|
||||||
'''
|
'''
|
||||||
|
key = self.__class__.__name__+'visible columns'
|
||||||
if cols:
|
if cols:
|
||||||
Settings().set(self.__class__.__name__ + ' visible columns', cols)
|
dynamic[key] = cols
|
||||||
else:
|
else:
|
||||||
cols = Settings().get(self.__class__.__name__ + ' visible columns')
|
cols = dynamic[key]
|
||||||
if not cols:
|
if not cols:
|
||||||
cols = [True for i in range(self.model().columnCount(self))]
|
cols = [True for i in range(self.model().columnCount(self))]
|
||||||
|
|
||||||
@ -232,7 +261,7 @@ _sidebar_directories = []
|
|||||||
def set_sidebar_directories(dirs):
|
def set_sidebar_directories(dirs):
|
||||||
global _sidebar_directories
|
global _sidebar_directories
|
||||||
if dirs is None:
|
if dirs is None:
|
||||||
dirs = Settings().get('frequently used directories', [])
|
dirs = config['frequently_used_directories']
|
||||||
_sidebar_directories = [QUrl.fromLocalFile(i) for i in dirs]
|
_sidebar_directories = [QUrl.fromLocalFile(i) for i in dirs]
|
||||||
|
|
||||||
class FileDialog(QObject):
|
class FileDialog(QObject):
|
||||||
@ -255,10 +284,10 @@ class FileDialog(QObject):
|
|||||||
if add_all_files_filter or not ftext:
|
if add_all_files_filter or not ftext:
|
||||||
ftext += 'All files (*)'
|
ftext += 'All files (*)'
|
||||||
|
|
||||||
settings = Settings()
|
|
||||||
self.dialog_name = name if name else 'dialog_' + title
|
self.dialog_name = name if name else 'dialog_' + title
|
||||||
self.selected_files = None
|
self.selected_files = None
|
||||||
self.fd = None
|
self.fd = None
|
||||||
|
|
||||||
if islinux:
|
if islinux:
|
||||||
self.fd = QFileDialog(parent)
|
self.fd = QFileDialog(parent)
|
||||||
self.fd.setFileMode(mode)
|
self.fd.setFileMode(mode)
|
||||||
@ -266,15 +295,15 @@ class FileDialog(QObject):
|
|||||||
self.fd.setModal(modal)
|
self.fd.setModal(modal)
|
||||||
self.fd.setFilter(ftext)
|
self.fd.setFilter(ftext)
|
||||||
self.fd.setWindowTitle(title)
|
self.fd.setWindowTitle(title)
|
||||||
state = settings.get(self.dialog_name, QByteArray())
|
state = dynamic[self.dialog_name]
|
||||||
if not self.fd.restoreState(state):
|
if not state or not self.fd.restoreState(state):
|
||||||
self.fd.setDirectory(os.path.expanduser('~'))
|
self.fd.setDirectory(os.path.expanduser('~'))
|
||||||
osu = [i for i in self.fd.sidebarUrls()]
|
osu = [i for i in self.fd.sidebarUrls()]
|
||||||
self.fd.setSidebarUrls(osu + _sidebar_directories)
|
self.fd.setSidebarUrls(osu + _sidebar_directories)
|
||||||
QObject.connect(self.fd, SIGNAL('accepted()'), self.save_dir)
|
QObject.connect(self.fd, SIGNAL('accepted()'), self.save_dir)
|
||||||
self.accepted = self.fd.exec_() == QFileDialog.Accepted
|
self.accepted = self.fd.exec_() == QFileDialog.Accepted
|
||||||
else:
|
else:
|
||||||
dir = settings.get(self.dialog_name, os.path.expanduser('~'))
|
dir = dynamic.get(self.dialog_name, default=os.path.expanduser('~'))
|
||||||
self.selected_files = []
|
self.selected_files = []
|
||||||
if mode == QFileDialog.AnyFile:
|
if mode == QFileDialog.AnyFile:
|
||||||
f = qstring_to_unicode(
|
f = qstring_to_unicode(
|
||||||
@ -299,7 +328,7 @@ class FileDialog(QObject):
|
|||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
if self.selected_files:
|
if self.selected_files:
|
||||||
self.selected_files = [qstring_to_unicode(q) for q in self.selected_files]
|
self.selected_files = [qstring_to_unicode(q) for q in self.selected_files]
|
||||||
settings.set(self.dialog_name, os.path.dirname(self.selected_files[0]))
|
dynamic[self.dialog_name] = os.path.dirname(self.selected_files[0])
|
||||||
self.accepted = bool(self.selected_files)
|
self.accepted = bool(self.selected_files)
|
||||||
|
|
||||||
|
|
||||||
@ -313,8 +342,7 @@ class FileDialog(QObject):
|
|||||||
|
|
||||||
def save_dir(self):
|
def save_dir(self):
|
||||||
if self.fd:
|
if self.fd:
|
||||||
settings = Settings()
|
dynamic[self.dialog_name] = self.fd.saveState()
|
||||||
settings.set(self.dialog_name, self.fd.saveState())
|
|
||||||
|
|
||||||
|
|
||||||
def choose_dir(window, name, title):
|
def choose_dir(window, name, title):
|
||||||
|
@ -12,7 +12,8 @@ import sys, os
|
|||||||
from PyQt4.QtGui import QImage, QSizePolicy
|
from PyQt4.QtGui import QImage, QSizePolicy
|
||||||
from PyQt4.QtCore import Qt, QSize, SIGNAL, QObject
|
from PyQt4.QtCore import Qt, QSize, SIGNAL, QObject
|
||||||
|
|
||||||
from calibre import Settings, plugins
|
from calibre import plugins
|
||||||
|
from calibre.gui2 import config
|
||||||
pictureflow, pictureflowerror = plugins['pictureflow']
|
pictureflow, pictureflowerror = plugins['pictureflow']
|
||||||
|
|
||||||
if pictureflow is not None:
|
if pictureflow is not None:
|
||||||
@ -70,7 +71,7 @@ if pictureflow is not None:
|
|||||||
|
|
||||||
def __init__(self, height=300, parent=None):
|
def __init__(self, height=300, parent=None):
|
||||||
pictureflow.PictureFlow.__init__(self, parent,
|
pictureflow.PictureFlow.__init__(self, parent,
|
||||||
Settings().get('cover flow queue length', 6)+1)
|
config['cover_flow_queue_length']+1)
|
||||||
self.setSlideSize(QSize(int(2/3. * height), height))
|
self.setSlideSize(QSize(int(2/3. * height), height))
|
||||||
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+25))
|
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+25))
|
||||||
self.setFocusPolicy(Qt.WheelFocus)
|
self.setFocusPolicy(Qt.WheelFocus)
|
||||||
|
@ -5,9 +5,10 @@ import os
|
|||||||
from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon
|
from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon
|
||||||
from PyQt4.QtCore import SIGNAL, QTimer, Qt, QSize, QVariant
|
from PyQt4.QtCore import SIGNAL, QTimer, Qt, QSize, QVariant
|
||||||
|
|
||||||
from calibre import islinux, Settings
|
from calibre import islinux
|
||||||
from calibre.gui2.dialogs.config_ui import Ui_Dialog
|
from calibre.gui2.dialogs.config_ui import Ui_Dialog
|
||||||
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog
|
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config
|
||||||
|
from calibre.utils.config import prefs
|
||||||
from calibre.gui2.widgets import FilenamePattern
|
from calibre.gui2.widgets import FilenamePattern
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
|
|
||||||
@ -24,18 +25,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.item2 = QListWidgetItem(QIcon(':/images/view.svg'), _('Advanced'), self.category_list)
|
self.item2 = QListWidgetItem(QIcon(':/images/view.svg'), _('Advanced'), self.category_list)
|
||||||
self.db = db
|
self.db = db
|
||||||
self.current_cols = columns
|
self.current_cols = columns
|
||||||
settings = Settings()
|
path = prefs['database_path']
|
||||||
path = settings.get('database path')
|
|
||||||
|
|
||||||
self.location.setText(os.path.dirname(path))
|
self.location.setText(os.path.dirname(path))
|
||||||
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
|
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
|
||||||
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
||||||
|
|
||||||
dirs = settings.get('frequently used directories', [])
|
dirs = config['frequently_used_directories']
|
||||||
rn = settings.get('use roman numerals for series number', True)
|
rn = config['use_roman_numerals_for_series_number']
|
||||||
self.timeout.setValue(settings.get('network timeout', 5))
|
self.timeout.setValue(prefs['network_timeout'])
|
||||||
self.roman_numerals.setChecked(rn)
|
self.roman_numerals.setChecked(rn)
|
||||||
self.new_version_notification.setChecked(settings.get('new version notification', True))
|
self.new_version_notification.setChecked(config['new_version_notification'])
|
||||||
self.directory_list.addItems(dirs)
|
self.directory_list.addItems(dirs)
|
||||||
self.connect(self.add_button, SIGNAL('clicked(bool)'), self.add_dir)
|
self.connect(self.add_button, SIGNAL('clicked(bool)'), self.add_dir)
|
||||||
self.connect(self.remove_button, SIGNAL('clicked(bool)'), self.remove_dir)
|
self.connect(self.remove_button, SIGNAL('clicked(bool)'), self.remove_dir)
|
||||||
@ -57,17 +57,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.filename_pattern = FilenamePattern(self)
|
self.filename_pattern = FilenamePattern(self)
|
||||||
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
|
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
|
||||||
|
|
||||||
icons = settings.get('toolbar icon size', self.ICON_SIZES[0])
|
icons = config['toolbar_icon_size']
|
||||||
self.toolbar_button_size.setCurrentIndex(0 if icons == self.ICON_SIZES[0] else 1 if icons == self.ICON_SIZES[1] else 2)
|
self.toolbar_button_size.setCurrentIndex(0 if icons == self.ICON_SIZES[0] else 1 if icons == self.ICON_SIZES[1] else 2)
|
||||||
self.show_toolbar_text.setChecked(settings.get('show text in toolbar', True))
|
self.show_toolbar_text.setChecked(config['show_text_in_toolbar'])
|
||||||
|
|
||||||
for ext in BOOK_EXTENSIONS:
|
for ext in BOOK_EXTENSIONS:
|
||||||
self.single_format.addItem(ext.upper(), QVariant(ext))
|
self.single_format.addItem(ext.upper(), QVariant(ext))
|
||||||
|
|
||||||
single_format = settings.get('save to disk single format', 'lrf')
|
single_format = config['save_to_disk_single_format']
|
||||||
self.single_format.setCurrentIndex(BOOK_EXTENSIONS.index(single_format))
|
self.single_format.setCurrentIndex(BOOK_EXTENSIONS.index(single_format))
|
||||||
self.cover_browse.setValue(settings.get('cover flow queue length', 6))
|
self.cover_browse.setValue(config['cover_flow_queue_length'])
|
||||||
self.confirm_delete.setChecked(settings.get('confirm delete', False))
|
self.confirm_delete.setChecked(config['confirm_delete'])
|
||||||
|
|
||||||
def compact(self, toggled):
|
def compact(self, toggled):
|
||||||
d = Vacuum(self, self.db)
|
d = Vacuum(self, self.db)
|
||||||
@ -89,19 +89,18 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.directory_list.takeItem(idx)
|
self.directory_list.takeItem(idx)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
settings = Settings()
|
config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked())
|
||||||
settings.set('use roman numerals for series number', bool(self.roman_numerals.isChecked()))
|
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
||||||
settings.set('new version notification', bool(self.new_version_notification.isChecked()))
|
prefs['network_timeout'] = int(self.timeout.value())
|
||||||
settings.set('network timeout', int(self.timeout.value()))
|
|
||||||
path = qstring_to_unicode(self.location.text())
|
path = qstring_to_unicode(self.location.text())
|
||||||
self.final_columns = [self.columns.item(i).checkState() == Qt.Checked for i in range(self.columns.count())]
|
self.final_columns = [self.columns.item(i).checkState() == Qt.Checked for i in range(self.columns.count())]
|
||||||
settings.set('toolbar icon size', self.ICON_SIZES[self.toolbar_button_size.currentIndex()])
|
config['toolbar_icon_size'] = self.ICON_SIZES[self.toolbar_button_size.currentIndex()]
|
||||||
settings.set('show text in toolbar', bool(self.show_toolbar_text.isChecked()))
|
config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked())
|
||||||
settings.set('confirm delete', bool(self.confirm_delete.isChecked()))
|
config['confirm_delete'] = bool(self.confirm_delete.isChecked())
|
||||||
pattern = self.filename_pattern.commit()
|
pattern = self.filename_pattern.commit()
|
||||||
settings.set('filename pattern', pattern)
|
config['filename_pattern'] = pattern
|
||||||
settings.set('save to disk single format', BOOK_EXTENSIONS[self.single_format.currentIndex()])
|
config['save_to_disk_single_format'] = BOOK_EXTENSIONS[self.single_format.currentIndex()]
|
||||||
settings.set('cover flow queue length', self.cover_browse.value())
|
config['cover_flow_queue_length'] = self.cover_browse.value()
|
||||||
|
|
||||||
if not path or not os.path.exists(path) or not os.path.isdir(path):
|
if not path or not os.path.exists(path) or not os.path.isdir(path):
|
||||||
d = error_dialog(self, _('Invalid database location'), _('Invalid database location ')+path+_('<br>Must be a directory.'))
|
d = error_dialog(self, _('Invalid database location'), _('Invalid database location ')+path+_('<br>Must be a directory.'))
|
||||||
@ -112,7 +111,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
else:
|
else:
|
||||||
self.database_location = os.path.abspath(path)
|
self.database_location = os.path.abspath(path)
|
||||||
self.directories = [qstring_to_unicode(self.directory_list.item(i).text()) for i in range(self.directory_list.count())]
|
self.directories = [qstring_to_unicode(self.directory_list.item(i).text()) for i in range(self.directory_list.count())]
|
||||||
settings.set('frequently used directories', self.directories)
|
config['frequently_used_directories'] = self.directories
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
class Vacuum(QMessageBox):
|
class Vacuum(QMessageBox):
|
||||||
|
@ -13,7 +13,7 @@ from PyQt4.QtGui import QDialog, QItemSelectionModel
|
|||||||
from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata
|
from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata
|
||||||
from calibre.gui2 import error_dialog, NONE, info_dialog
|
from calibre.gui2 import error_dialog, NONE, info_dialog
|
||||||
from calibre.ebooks.metadata.isbndb import create_books, option_parser, ISBNDBError
|
from calibre.ebooks.metadata.isbndb import create_books, option_parser, ISBNDBError
|
||||||
from calibre import Settings
|
from calibre.utils.config import prefs
|
||||||
|
|
||||||
class Matches(QAbstractTableModel):
|
class Matches(QAbstractTableModel):
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata)
|
QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata)
|
||||||
|
|
||||||
self.key.setText(Settings().get('isbndb.com key', ''))
|
self.key.setText(prefs['isbndb_com_key'])
|
||||||
|
|
||||||
self.setWindowTitle(title if title else 'Unknown')
|
self.setWindowTitle(title if title else 'Unknown')
|
||||||
self.tlabel.setText(self.tlabel.text().arg(title if title else 'Unknown'))
|
self.tlabel.setText(self.tlabel.text().arg(title if title else 'Unknown'))
|
||||||
@ -105,7 +105,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
|||||||
_('You must specify a valid access key for isbndb.com'))
|
_('You must specify a valid access key for isbndb.com'))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
Settings().set('isbndb.com key', key)
|
prefs['isbndb_com_key'] = key
|
||||||
|
|
||||||
args = ['isbndb']
|
args = ['isbndb']
|
||||||
if self.isbn:
|
if self.isbn:
|
||||||
|
@ -9,11 +9,11 @@ from PyQt4.QtGui import QAbstractSpinBox, QLineEdit, QCheckBox, QDialog, \
|
|||||||
from calibre.gui2.dialogs.lrf_single_ui import Ui_LRFSingleDialog
|
from calibre.gui2.dialogs.lrf_single_ui import Ui_LRFSingleDialog
|
||||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||||
from calibre.gui2 import qstring_to_unicode, error_dialog, \
|
from calibre.gui2 import qstring_to_unicode, error_dialog, \
|
||||||
pixmap_to_data, choose_images
|
pixmap_to_data, choose_images, config
|
||||||
from calibre.gui2.widgets import FontFamilyModel
|
from calibre.gui2.widgets import FontFamilyModel
|
||||||
from calibre.ebooks.lrf import option_parser
|
from calibre.ebooks.lrf import option_parser
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre import __appname__, Settings
|
from calibre.constants import __appname__
|
||||||
|
|
||||||
font_family_model = None
|
font_family_model = None
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
|
|
||||||
|
|
||||||
def load_saved_global_defaults(self):
|
def load_saved_global_defaults(self):
|
||||||
cmdline = Settings().get('LRF conversion defaults')
|
cmdline = config['LRF_conversion_defaults']
|
||||||
if cmdline:
|
if cmdline:
|
||||||
self.set_options_from_cmdline(cmdline)
|
self.set_options_from_cmdline(cmdline)
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
|
|
||||||
def select_cover(self, checked):
|
def select_cover(self, checked):
|
||||||
files = choose_images(self, 'change cover dialog',
|
files = choose_images(self, 'change cover dialog',
|
||||||
u'Choose cover for ' + qstring_to_unicode(self.gui_title.text()))
|
_('Choose cover for ') + qstring_to_unicode(self.gui_title.text()))
|
||||||
if not files:
|
if not files:
|
||||||
return
|
return
|
||||||
_file = files[0]
|
_file = files[0]
|
||||||
@ -385,7 +385,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
cmdline.extend([u'--cover', self.cover_file.name])
|
cmdline.extend([u'--cover', self.cover_file.name])
|
||||||
self.cmdline = [unicode(i) for i in cmdline]
|
self.cmdline = [unicode(i) for i in cmdline]
|
||||||
else:
|
else:
|
||||||
Settings().set('LRF conversion defaults', cmdline)
|
config.set('LRF_conversion_defaults', cmdline)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
class LRFBulkDialog(LRFSingleDialog):
|
class LRFBulkDialog(LRFSingleDialog):
|
||||||
|
@ -18,7 +18,8 @@ from calibre.gui2.dialogs.tag_editor import TagEditor
|
|||||||
from calibre.gui2.dialogs.password import PasswordDialog
|
from calibre.gui2.dialogs.password import PasswordDialog
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.ebooks.metadata.library_thing import login, cover_from_isbn, LibraryThingError
|
from calibre.ebooks.metadata.library_thing import login, cover_from_isbn, LibraryThingError
|
||||||
from calibre import Settings, islinux
|
from calibre import islinux
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
|
||||||
class Format(QListWidgetItem):
|
class Format(QListWidgetItem):
|
||||||
def __init__(self, parent, ext, size, path=None):
|
def __init__(self, parent, ext, size, path=None):
|
||||||
@ -145,7 +146,7 @@ class MetadataSingleDialog(QDialog, Ui_MetadataSingleDialog):
|
|||||||
QObject.connect(self.remove_series_button, SIGNAL('clicked()'),
|
QObject.connect(self.remove_series_button, SIGNAL('clicked()'),
|
||||||
self.remove_unused_series)
|
self.remove_unused_series)
|
||||||
self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author)
|
self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author)
|
||||||
self.timeout = float(Settings().get('network timeout', 5))
|
self.timeout = float(prefs['network_timeout'])
|
||||||
self.title.setText(db.title(row))
|
self.title.setText(db.title(row))
|
||||||
isbn = db.isbn(self.id, index_is_id=True)
|
isbn = db.isbn(self.id, index_is_id=True)
|
||||||
if not isbn:
|
if not isbn:
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
import re
|
||||||
from PyQt4.QtGui import QDialog, QLineEdit
|
from PyQt4.QtGui import QDialog, QLineEdit
|
||||||
from PyQt4.QtCore import QVariant, SIGNAL, Qt
|
from PyQt4.QtCore import SIGNAL, Qt
|
||||||
|
|
||||||
from calibre.gui2.dialogs.password_ui import Ui_Dialog
|
from calibre.gui2.dialogs.password_ui import Ui_Dialog
|
||||||
from calibre.gui2 import qstring_to_unicode
|
from calibre.gui2 import qstring_to_unicode, dynamic
|
||||||
from calibre import Settings
|
|
||||||
|
|
||||||
class PasswordDialog(QDialog, Ui_Dialog):
|
class PasswordDialog(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
@ -14,10 +13,12 @@ class PasswordDialog(QDialog, Ui_Dialog):
|
|||||||
QDialog.__init__(self, window)
|
QDialog.__init__(self, window)
|
||||||
Ui_Dialog.__init__(self)
|
Ui_Dialog.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self.cfg_key = re.sub(r'[^0-9a-zA-Z]', '_', name)
|
||||||
|
|
||||||
settings = Settings()
|
un = dynamic[self.cfg_key+'__un']
|
||||||
un = settings.get(name+': un', u'')
|
pw = dynamic[self.cfg_key+'__un']
|
||||||
pw = settings.get(name+': pw', u'')
|
if not un: un = ''
|
||||||
|
if not pw: pw = ''
|
||||||
self.gui_username.setText(un)
|
self.gui_username.setText(un)
|
||||||
self.gui_password.setText(pw)
|
self.gui_password.setText(pw)
|
||||||
self.sname = name
|
self.sname = name
|
||||||
@ -37,7 +38,6 @@ class PasswordDialog(QDialog, Ui_Dialog):
|
|||||||
return qstring_to_unicode(self.gui_password.text())
|
return qstring_to_unicode(self.gui_password.text())
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
settings = Settings()
|
dynamic.set(self.cfg_key+'__un', unicode(self.gui_username.text()))
|
||||||
settings.set(self.sname+': un', unicode(self.gui_username.text()))
|
dynamic.set(self.cfg_key+'__pw', unicode(self.gui_password.text()))
|
||||||
settings.set(self.sname+': pw', unicode(self.gui_password.text()))
|
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
@ -14,10 +14,10 @@ from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
|||||||
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex, \
|
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex, \
|
||||||
QTimer
|
QTimer
|
||||||
|
|
||||||
from calibre import Settings, preferred_encoding
|
from calibre import preferred_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.library.database import LibraryDatabase, text_to_tokens
|
from calibre.library.database import LibraryDatabase, text_to_tokens
|
||||||
from calibre.gui2 import NONE, TableView, qstring_to_unicode
|
from calibre.gui2 import NONE, TableView, qstring_to_unicode, config
|
||||||
|
|
||||||
class LibraryDelegate(QItemDelegate):
|
class LibraryDelegate(QItemDelegate):
|
||||||
COLOR = QColor("blue")
|
COLOR = QColor("blue")
|
||||||
@ -117,7 +117,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
self.load_queue = deque()
|
self.load_queue = deque()
|
||||||
|
|
||||||
def read_config(self):
|
def read_config(self):
|
||||||
self.use_roman_numbers = Settings().get('use roman numerals for series number', True)
|
self.use_roman_numbers = config['use_roman_numerals_for_series_number']
|
||||||
|
|
||||||
|
|
||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
|
@ -6,10 +6,11 @@ import sys, logging, os, traceback, time
|
|||||||
from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon
|
from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon
|
||||||
from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread
|
from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread
|
||||||
|
|
||||||
from calibre import __appname__, setup_cli_handlers, islinux, Settings
|
from calibre import __appname__, setup_cli_handlers, islinux
|
||||||
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
||||||
|
|
||||||
from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, choose_files, Application
|
from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, \
|
||||||
|
config, choose_files, Application
|
||||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||||
from calibre.gui2.lrf_renderer.main_ui import Ui_MainWindow
|
from calibre.gui2.lrf_renderer.main_ui import Ui_MainWindow
|
||||||
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
|
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
|
||||||
@ -102,13 +103,15 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
|
|
||||||
def configure(self, triggered):
|
def configure(self, triggered):
|
||||||
opts = Settings().get('LRF ebook viewer options', self.opts)
|
opts = config['LRF_ebook_viewer_options']
|
||||||
|
if not opts:
|
||||||
|
opts = self.opts
|
||||||
d = Config(self, opts)
|
d = Config(self, opts)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
opts.white_background = bool(d.white_background.isChecked())
|
opts.white_background = bool(d.white_background.isChecked())
|
||||||
opts.hyphenate = bool(d.hyphenate.isChecked())
|
opts.hyphenate = bool(d.hyphenate.isChecked())
|
||||||
Settings().set('LRF ebook viewer options', opts)
|
config['LRF_ebook_viewer_options'] = opts
|
||||||
|
|
||||||
def set_ebook(self, stream):
|
def set_ebook(self, stream):
|
||||||
self.progress_bar.setMinimum(0)
|
self.progress_bar.setMinimum(0)
|
||||||
@ -281,7 +284,9 @@ Read the LRF ebook book.lrf
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
def normalize_settings(parser, opts):
|
def normalize_settings(parser, opts):
|
||||||
saved_opts = Settings().get('LRF ebook viewer options', opts)
|
saved_opts = config['LRF_ebook_viewer_options']
|
||||||
|
if not saved_opts:
|
||||||
|
saved_opts = opts
|
||||||
for opt in parser.option_list:
|
for opt in parser.option_list:
|
||||||
if not opt.dest:
|
if not opt.dest:
|
||||||
continue
|
continue
|
||||||
|
@ -3,23 +3,24 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import os, sys, textwrap, collections, traceback, shutil, time
|
import os, sys, textwrap, collections, traceback, shutil, time
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
|
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl
|
||||||
QVariant, QUrl, QSize
|
|
||||||
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
|
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
|
||||||
QToolButton, QDialog, QDesktopServices
|
QToolButton, QDialog, QDesktopServices
|
||||||
from PyQt4.QtSvg import QSvgRenderer
|
from PyQt4.QtSvg import QSvgRenderer
|
||||||
|
|
||||||
from calibre import __version__, __appname__, islinux, sanitize_file_name, \
|
from calibre import __version__, __appname__, islinux, sanitize_file_name, \
|
||||||
Settings, iswindows, isosx, preferred_encoding
|
iswindows, isosx, preferred_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.ebooks.metadata.meta import get_metadata, get_filename_pat, set_filename_pat
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.devices.errors import FreeSpaceError
|
from calibre.devices.errors import FreeSpaceError
|
||||||
from calibre.devices.interface import Device
|
from calibre.devices.interface import Device
|
||||||
|
from calibre.utils.config import prefs, dynamic
|
||||||
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
||||||
initialize_file_icon_provider, question_dialog,\
|
initialize_file_icon_provider, question_dialog,\
|
||||||
pixmap_to_data, choose_dir, ORG_NAME, \
|
pixmap_to_data, choose_dir, ORG_NAME, \
|
||||||
set_sidebar_directories, Dispatcher, \
|
set_sidebar_directories, Dispatcher, \
|
||||||
SingleApplication, Application, available_height, max_available_height
|
SingleApplication, Application, available_height, \
|
||||||
|
max_available_height, config
|
||||||
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
from calibre.gui2.update import CheckForUpdates
|
from calibre.gui2.update import CheckForUpdates
|
||||||
@ -120,12 +121,12 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
sm.addAction(_('Send to storage card by default'))
|
sm.addAction(_('Send to storage card by default'))
|
||||||
sm.actions()[-1].setCheckable(True)
|
sm.actions()[-1].setCheckable(True)
|
||||||
def default_sync(checked):
|
def default_sync(checked):
|
||||||
Settings().set('send to device by default', bool(checked))
|
config.set('send_to_device_by_default', bool(checked))
|
||||||
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_main_memory)
|
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_main_memory)
|
||||||
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card)
|
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card)
|
||||||
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card if checked else self.sync_to_main_memory)
|
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card if checked else self.sync_to_main_memory)
|
||||||
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'), default_sync)
|
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'), default_sync)
|
||||||
sm.actions()[-1].setChecked(Settings().get('send to device by default', False))
|
sm.actions()[-1].setChecked(config.get('send_to_device_by_default'))
|
||||||
default_sync(sm.actions()[-1].isChecked())
|
default_sync(sm.actions()[-1].isChecked())
|
||||||
self.sync_menu = sm # Needed
|
self.sync_menu = sm # Needed
|
||||||
md = QMenu()
|
md = QMenu()
|
||||||
@ -152,7 +153,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.save_menu = QMenu()
|
self.save_menu = QMenu()
|
||||||
self.save_menu.addAction(_('Save to disk'))
|
self.save_menu.addAction(_('Save to disk'))
|
||||||
self.save_menu.addAction(_('Save to disk in a single directory'))
|
self.save_menu.addAction(_('Save to disk in a single directory'))
|
||||||
self.save_menu.addAction(_('Save only %s format to disk')%Settings().get('save to disk single format', 'lrf').upper())
|
self.save_menu.addAction(_('Save only %s format to disk')%config.get('save_to_disk_single_format').upper())
|
||||||
|
|
||||||
self.view_menu = QMenu()
|
self.view_menu = QMenu()
|
||||||
self.view_menu.addAction(_('View'))
|
self.view_menu.addAction(_('View'))
|
||||||
@ -529,7 +530,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
rows = view.selectionModel().selectedRows()
|
rows = view.selectionModel().selectedRows()
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
return
|
return
|
||||||
if Settings().get('confirm delete', False):
|
if config['confirm_delete']:
|
||||||
d = question_dialog(self, _('Confirm delete'),
|
d = question_dialog(self, _('Confirm delete'),
|
||||||
_('Are you sure you want to delete these %d books?')%len(rows))
|
_('Are you sure you want to delete these %d books?')%len(rows))
|
||||||
if d.exec_() != QMessageBox.Yes:
|
if d.exec_() != QMessageBox.Yes:
|
||||||
@ -680,7 +681,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
############################## Save to disk ################################
|
############################## Save to disk ################################
|
||||||
def save_single_format_to_disk(self, checked):
|
def save_single_format_to_disk(self, checked):
|
||||||
self.save_to_disk(checked, True, Settings().get('save to disk single format', 'lrf'))
|
self.save_to_disk(checked, True, config['save_to_disk_single_format'])
|
||||||
|
|
||||||
def save_to_single_dir(self, checked):
|
def save_to_single_dir(self, checked):
|
||||||
self.save_to_disk(checked, True)
|
self.save_to_disk(checked, True)
|
||||||
@ -1067,9 +1068,8 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == d.Accepted:
|
if d.result() == d.Accepted:
|
||||||
self.library_view.set_visible_columns(d.final_columns)
|
self.library_view.set_visible_columns(d.final_columns)
|
||||||
settings = Settings()
|
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||||
self.tool_bar.setIconSize(settings.value('toolbar icon size', QVariant(QSize(48, 48))).toSize())
|
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly)
|
||||||
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if settings.get('show text in toolbar', True) else Qt.ToolButtonIconOnly)
|
|
||||||
|
|
||||||
if os.path.dirname(self.database_path) != d.database_location:
|
if os.path.dirname(self.database_path) != d.database_location:
|
||||||
try:
|
try:
|
||||||
@ -1100,8 +1100,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
newloc = self.database_path
|
newloc = self.database_path
|
||||||
self.database_path = newloc
|
self.database_path = newloc
|
||||||
settings = Settings()
|
prefs().set('database_path', self.database_path)
|
||||||
settings.set('database path', self.database_path)
|
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
d = error_dialog(self, _('Could not move database'), unicode(err))
|
d = error_dialog(self, _('Could not move database'), unicode(err))
|
||||||
@ -1200,12 +1199,10 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
|
|
||||||
def read_settings(self):
|
def read_settings(self):
|
||||||
settings = Settings()
|
geometry = config['main_window_geometry']
|
||||||
settings.beginGroup('Main Window')
|
if geometry is not None:
|
||||||
geometry = settings.value('main window geometry', QVariant()).toByteArray()
|
self.restoreGeometry(geometry)
|
||||||
self.restoreGeometry(geometry)
|
self.database_path = prefs['database_path']
|
||||||
settings.endGroup()
|
|
||||||
self.database_path = settings.get('database path')
|
|
||||||
if not os.access(os.path.dirname(self.database_path), os.W_OK):
|
if not os.access(os.path.dirname(self.database_path), os.W_OK):
|
||||||
error_dialog(self, _('Database does not exist'), _('The directory in which the database should be: %s no longer exists. Please choose a new database location.')%self.database_path).exec_()
|
error_dialog(self, _('Database does not exist'), _('The directory in which the database should be: %s no longer exists. Please choose a new database location.')%self.database_path).exec_()
|
||||||
self.database_path = choose_dir(self, 'database path dialog', 'Choose new location for database')
|
self.database_path = choose_dir(self, 'database path dialog', 'Choose new location for database')
|
||||||
@ -1214,23 +1211,17 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
if not os.path.exists(self.database_path):
|
if not os.path.exists(self.database_path):
|
||||||
os.makedirs(self.database_path)
|
os.makedirs(self.database_path)
|
||||||
self.database_path = os.path.join(self.database_path, 'library1.db')
|
self.database_path = os.path.join(self.database_path, 'library1.db')
|
||||||
settings.set('database path', self.database_path)
|
prefs.set('database_path', self.database_path)
|
||||||
set_sidebar_directories(None)
|
set_sidebar_directories(None)
|
||||||
set_filename_pat(settings.get('filename pattern', get_filename_pat()))
|
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||||
self.tool_bar.setIconSize(settings.get('toolbar icon size', QSize(48, 48)))
|
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly)
|
||||||
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if settings.get('show text in toolbar', True) else Qt.ToolButtonIconOnly)
|
|
||||||
|
|
||||||
|
|
||||||
def write_settings(self):
|
def write_settings(self):
|
||||||
settings = Settings()
|
config.set('main_window_geometry', self.saveGeometry())
|
||||||
settings.beginGroup("Main Window")
|
|
||||||
settings.setValue("main window geometry", QVariant(self.saveGeometry()))
|
|
||||||
settings.endGroup()
|
|
||||||
settings.beginGroup('Book Views')
|
|
||||||
self.library_view.write_settings()
|
self.library_view.write_settings()
|
||||||
if self.device_connected:
|
if self.device_connected:
|
||||||
self.memory_view.write_settings()
|
self.memory_view.write_settings()
|
||||||
settings.endGroup()
|
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
msg = 'There are active jobs. Are you sure you want to quit?'
|
msg = 'There are active jobs. Are you sure you want to quit?'
|
||||||
@ -1261,17 +1252,16 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.vanity.setText(self.vanity_template%(dict(version=self.latest_version,
|
self.vanity.setText(self.vanity_template%(dict(version=self.latest_version,
|
||||||
device=self.device_info)))
|
device=self.device_info)))
|
||||||
self.vanity.update()
|
self.vanity.update()
|
||||||
s = Settings()
|
if config.get('new_version_notification') and dynamic.get('update to version %s'%version, True):
|
||||||
if s.get('new version notification', True) and s.get('update to version %s'%version, True):
|
|
||||||
d = question_dialog(self, _('Update available'), _('%s has been updated to version %s. See the <a href="http://calibre.kovidgoyal.net/wiki/Changelog">new features</a>. Visit the download page?')%(__appname__, version))
|
d = question_dialog(self, _('Update available'), _('%s has been updated to version %s. See the <a href="http://calibre.kovidgoyal.net/wiki/Changelog">new features</a>. Visit the download page?')%(__appname__, version))
|
||||||
if d.exec_() == QMessageBox.Yes:
|
if d.exec_() == QMessageBox.Yes:
|
||||||
url = 'http://calibre.kovidgoyal.net/download_'+('windows' if iswindows else 'osx' if isosx else 'linux')
|
url = 'http://calibre.kovidgoyal.net/download_'+('windows' if iswindows else 'osx' if isosx else 'linux')
|
||||||
QDesktopServices.openUrl(QUrl(url))
|
QDesktopServices.openUrl(QUrl(url))
|
||||||
s.set('update to version %s'%version, False)
|
dynamic.set('update to version %s'%version, False)
|
||||||
|
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args=sys.argv):
|
||||||
from calibre import singleinstance
|
from calibre.utils.lock import singleinstance
|
||||||
|
|
||||||
pid = os.fork() if False and islinux else -1
|
pid = os.fork() if False and islinux else -1
|
||||||
if pid <= 0:
|
if pid <= 0:
|
||||||
|
@ -5,7 +5,7 @@ import StringIO, traceback, sys
|
|||||||
|
|
||||||
from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL
|
from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL
|
||||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||||
from calibre import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
|
|
||||||
def option_parser(usage='''\
|
def option_parser(usage='''\
|
||||||
Usage: %prog [options]
|
Usage: %prog [options]
|
||||||
|
@ -9,15 +9,16 @@ from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \
|
|||||||
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
||||||
QAbstractItemDelegate, QPixmap, QStyle, QFontMetrics
|
QAbstractItemDelegate, QPixmap, QStyle, QFontMetrics
|
||||||
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, SIGNAL, \
|
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, SIGNAL, \
|
||||||
QObject, QRegExp, QString
|
QObject, QRegExp, QString, QSettings
|
||||||
|
|
||||||
from calibre.gui2.jobs2 import DetailView
|
from calibre.gui2.jobs2 import DetailView
|
||||||
from calibre.gui2 import human_readable, NONE, TableView, qstring_to_unicode, error_dialog
|
from calibre.gui2 import human_readable, NONE, TableView, \
|
||||||
|
qstring_to_unicode, error_dialog
|
||||||
from calibre.gui2.filename_pattern_ui import Ui_Form
|
from calibre.gui2.filename_pattern_ui import Ui_Form
|
||||||
from calibre import fit_image, Settings
|
from calibre import fit_image
|
||||||
from calibre.utils.fontconfig import find_font_families
|
from calibre.utils.fontconfig import find_font_families
|
||||||
from calibre.ebooks.metadata.meta import get_filename_pat, metadata_from_filename, \
|
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||||
set_filename_pat
|
from calibre.utils.config import prefs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ class FilenamePattern(QWidget, Ui_Form):
|
|||||||
|
|
||||||
self.connect(self.test_button, SIGNAL('clicked()'), self.do_test)
|
self.connect(self.test_button, SIGNAL('clicked()'), self.do_test)
|
||||||
self.connect(self.re, SIGNAL('returnPressed()'), self.do_test)
|
self.connect(self.re, SIGNAL('returnPressed()'), self.do_test)
|
||||||
self.re.setText(get_filename_pat())
|
self.re.setText(prefs['filename_pattern'])
|
||||||
|
|
||||||
def do_test(self):
|
def do_test(self):
|
||||||
try:
|
try:
|
||||||
@ -66,9 +67,9 @@ class FilenamePattern(QWidget, Ui_Form):
|
|||||||
return re.compile(pat)
|
return re.compile(pat)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
pat = self.pattern()
|
pat = self.pattern().pattern
|
||||||
set_filename_pat(pat)
|
prefs['filename_pattern'] = pat
|
||||||
return pat.pattern
|
return pat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -367,13 +368,13 @@ class PythonHighlighter(QSyntaxHighlighter):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def loadConfig(cls):
|
def loadConfig(cls):
|
||||||
Config = cls.Config
|
Config = cls.Config
|
||||||
|
settings = QSettings()
|
||||||
def setDefaultString(name, default):
|
def setDefaultString(name, default):
|
||||||
value = settings.value(name).toString()
|
value = settings.value(name).toString()
|
||||||
if value.isEmpty():
|
if value.isEmpty():
|
||||||
value = default
|
value = default
|
||||||
Config[name] = value
|
Config[name] = value
|
||||||
|
|
||||||
settings = Settings()
|
|
||||||
for name in ("window", "shell"):
|
for name in ("window", "shell"):
|
||||||
Config["%swidth" % name] = settings.value("%swidth" % name,
|
Config["%swidth" % name] = settings.value("%swidth" % name,
|
||||||
QVariant(QApplication.desktop() \
|
QVariant(QApplication.desktop() \
|
||||||
|
@ -10,7 +10,8 @@ Command line interface to the calibre database.
|
|||||||
import sys, os
|
import sys, os
|
||||||
from textwrap import TextWrapper
|
from textwrap import TextWrapper
|
||||||
|
|
||||||
from calibre import OptionParser, Settings, terminal_controller, preferred_encoding
|
from calibre import terminal_controller, preferred_encoding
|
||||||
|
from calibre.utils.config import OptionParser, prefs
|
||||||
try:
|
try:
|
||||||
from calibre.utils.single_qt_application import send_message
|
from calibre.utils.single_qt_application import send_message
|
||||||
except:
|
except:
|
||||||
@ -24,7 +25,8 @@ FIELDS = set(['title', 'authors', 'publisher', 'rating', 'timestamp', 'size', 't
|
|||||||
def get_parser(usage):
|
def get_parser(usage):
|
||||||
parser = OptionParser(usage)
|
parser = OptionParser(usage)
|
||||||
go = parser.add_option_group('GLOBAL OPTIONS')
|
go = parser.add_option_group('GLOBAL OPTIONS')
|
||||||
go.add_option('--database', default=None, help=_('Path to the calibre database. Default is to use the path stored in the settings.'))
|
go.add_option('--database', default=None,
|
||||||
|
help=_('Path to the calibre database. Default is to use the path stored in the settings.'))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def get_db(dbpath, options):
|
def get_db(dbpath, options):
|
||||||
@ -422,7 +424,7 @@ For help on an individual command: %%prog command --help
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
command = eval('command_'+args[1])
|
command = eval('command_'+args[1])
|
||||||
dbpath = Settings().get('database path')
|
dbpath = prefs.get('database_path')
|
||||||
|
|
||||||
return command(args[2:], dbpath)
|
return command(args[2:], dbpath)
|
||||||
|
|
||||||
|
@ -244,6 +244,7 @@ def do_postinstall(destdir):
|
|||||||
os.chdir(destdir)
|
os.chdir(destdir)
|
||||||
os.environ['LD_LIBRARY_PATH'] = destdir+':'+os.environ.get('LD_LIBRARY_PATH', '')
|
os.environ['LD_LIBRARY_PATH'] = destdir+':'+os.environ.get('LD_LIBRARY_PATH', '')
|
||||||
os.environ['PYTHONPATH'] = destdir
|
os.environ['PYTHONPATH'] = destdir
|
||||||
|
os.environ['PYTHONSTARTUP'] = ''
|
||||||
subprocess.call((os.path.join(destdir, 'calibre_postinstall'), '--save-manifest-to', t.name))
|
subprocess.call((os.path.join(destdir, 'calibre_postinstall'), '--save-manifest-to', t.name))
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
149
src/calibre/startup.py
Normal file
149
src/calibre/startup.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Perform various initialization tasks.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import locale, sys, os, re, cStringIO
|
||||||
|
from gettext import GNUTranslations
|
||||||
|
|
||||||
|
# Default translation is NOOP
|
||||||
|
import __builtin__
|
||||||
|
__builtin__.__dict__['_'] = lambda s: s
|
||||||
|
|
||||||
|
from calibre.constants import iswindows, isosx, islinux, isfrozen
|
||||||
|
from calibre.translations.msgfmt import make
|
||||||
|
|
||||||
|
_run_once = False
|
||||||
|
if not _run_once:
|
||||||
|
_run_once = True
|
||||||
|
################################################################################
|
||||||
|
# Setup translations
|
||||||
|
|
||||||
|
def get_lang():
|
||||||
|
lang = locale.getdefaultlocale()[0]
|
||||||
|
if lang is None and os.environ.has_key('LANG'): # Needed for OS X
|
||||||
|
try:
|
||||||
|
lang = os.environ['LANG']
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if lang:
|
||||||
|
match = re.match('[a-z]{2,3}', lang)
|
||||||
|
if match:
|
||||||
|
lang = match.group()
|
||||||
|
return lang
|
||||||
|
|
||||||
|
def set_translator():
|
||||||
|
# To test different translations invoke as
|
||||||
|
# LC_ALL=de_DE.utf8 program
|
||||||
|
try:
|
||||||
|
from calibre.translations.compiled import translations
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
lang = get_lang()
|
||||||
|
if lang:
|
||||||
|
buf = None
|
||||||
|
if os.access(lang+'.po', os.R_OK):
|
||||||
|
buf = cStringIO.StringIO()
|
||||||
|
make(lang+'.po', buf)
|
||||||
|
buf = cStringIO.StringIO(buf.getvalue())
|
||||||
|
elif translations.has_key(lang):
|
||||||
|
buf = cStringIO.StringIO(translations[lang])
|
||||||
|
if buf is not None:
|
||||||
|
t = GNUTranslations(buf)
|
||||||
|
t.install(unicode=True)
|
||||||
|
|
||||||
|
set_translator()
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Initialize locale
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
|
except:
|
||||||
|
dl = locale.getdefaultlocale()
|
||||||
|
try:
|
||||||
|
if dl:
|
||||||
|
locale.setlocale(dl[0])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Load plugins
|
||||||
|
if isfrozen:
|
||||||
|
if iswindows:
|
||||||
|
plugin_path = os.path.join(os.path.dirname(sys.executable), 'plugins')
|
||||||
|
sys.path.insert(1, os.path.dirname(sys.executable))
|
||||||
|
elif isosx:
|
||||||
|
plugin_path = os.path.join(getattr(sys, 'frameworks_dir'), 'plugins')
|
||||||
|
elif islinux:
|
||||||
|
plugin_path = os.path.join(getattr(sys, 'frozen_path'), 'plugins')
|
||||||
|
sys.path.insert(0, plugin_path)
|
||||||
|
else:
|
||||||
|
import pkg_resources
|
||||||
|
plugins = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins')
|
||||||
|
sys.path.insert(0, plugins)
|
||||||
|
|
||||||
|
plugins = {}
|
||||||
|
for plugin in ['pictureflow', 'lzx', 'msdes'] + \
|
||||||
|
(['winutil'] if iswindows else []) + \
|
||||||
|
(['usbobserver'] if isosx else []):
|
||||||
|
try:
|
||||||
|
p, err = __import__(plugin), ''
|
||||||
|
except Exception, err:
|
||||||
|
p = None
|
||||||
|
err = str(err)
|
||||||
|
plugins[plugin] = (p, err)
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Improve builtin path functions to handle unicode sensibly
|
||||||
|
|
||||||
|
_abspath = os.path.abspath
|
||||||
|
def my_abspath(path, encoding=sys.getfilesystemencoding()):
|
||||||
|
'''
|
||||||
|
Work around for buggy os.path.abspath. This function accepts either byte strings,
|
||||||
|
in which it calls os.path.abspath, or unicode string, in which case it first converts
|
||||||
|
to byte strings using `encoding`, calls abspath and then decodes back to unicode.
|
||||||
|
'''
|
||||||
|
to_unicode = False
|
||||||
|
if isinstance(path, unicode):
|
||||||
|
path = path.encode(encoding)
|
||||||
|
to_unicode = True
|
||||||
|
res = _abspath(path)
|
||||||
|
if to_unicode:
|
||||||
|
res = res.decode(encoding)
|
||||||
|
return res
|
||||||
|
|
||||||
|
os.path.abspath = my_abspath
|
||||||
|
_join = os.path.join
|
||||||
|
def my_join(a, *p):
|
||||||
|
encoding=sys.getfilesystemencoding()
|
||||||
|
p = [a] + list(p)
|
||||||
|
_unicode = False
|
||||||
|
for i in p:
|
||||||
|
if isinstance(i, unicode):
|
||||||
|
_unicode = True
|
||||||
|
break
|
||||||
|
p = [i.encode(encoding) if isinstance(i, unicode) else i for i in p]
|
||||||
|
|
||||||
|
res = _join(*p)
|
||||||
|
if _unicode:
|
||||||
|
res = res.decode(encoding)
|
||||||
|
return res
|
||||||
|
|
||||||
|
os.path.join = my_join
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Platform specific modules
|
||||||
|
winutil = winutilerror = None
|
||||||
|
if iswindows:
|
||||||
|
winutil, winutilerror = plugins['winutil']
|
||||||
|
if not winutil:
|
||||||
|
raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror)
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
sys.argv[1:] = winutil.argv()[1-len(sys.argv):]
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
@ -164,8 +164,8 @@ DEFAULTKEYWORDS = ', '.join(default_keywords)
|
|||||||
|
|
||||||
EMPTYSTRING = ''
|
EMPTYSTRING = ''
|
||||||
|
|
||||||
from calibre import __appname__
|
from calibre.constants import __appname__
|
||||||
from calibre import __version__ as version
|
from calibre.constants import __version__ as version
|
||||||
|
|
||||||
# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
|
# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
|
||||||
# there.
|
# there.
|
||||||
|
@ -6,10 +6,14 @@ __docformat__ = 'restructuredtext en'
|
|||||||
'''
|
'''
|
||||||
Manage application-wide preferences.
|
Manage application-wide preferences.
|
||||||
'''
|
'''
|
||||||
import os, re, cPickle
|
import os, re, cPickle, textwrap
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from optparse import OptionParser as _OptionParser
|
||||||
|
from optparse import IndentedHelpFormatter
|
||||||
from PyQt4.QtCore import QString
|
from PyQt4.QtCore import QString
|
||||||
from calibre import iswindows, isosx, OptionParser, ExclusiveFile, LockError
|
from calibre.constants import terminal_controller, iswindows, isosx, \
|
||||||
|
__appname__, __version__, __author__
|
||||||
|
from calibre.utils.lock import LockError, ExclusiveFile
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
@ -27,6 +31,124 @@ else:
|
|||||||
if not os.path.exists(config_dir):
|
if not os.path.exists(config_dir):
|
||||||
os.makedirs(config_dir, mode=448) # 0700 == 448
|
os.makedirs(config_dir, mode=448) # 0700 == 448
|
||||||
|
|
||||||
|
class CustomHelpFormatter(IndentedHelpFormatter):
|
||||||
|
|
||||||
|
def format_usage(self, usage):
|
||||||
|
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
|
||||||
|
|
||||||
|
def format_heading(self, heading):
|
||||||
|
return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
|
||||||
|
"", heading, terminal_controller.NORMAL)
|
||||||
|
|
||||||
|
def format_option(self, option):
|
||||||
|
result = []
|
||||||
|
opts = self.option_strings[option]
|
||||||
|
opt_width = self.help_position - self.current_indent - 2
|
||||||
|
if len(opts) > opt_width:
|
||||||
|
opts = "%*s%s\n" % (self.current_indent, "",
|
||||||
|
terminal_controller.GREEN+opts+terminal_controller.NORMAL)
|
||||||
|
indent_first = self.help_position
|
||||||
|
else: # start help on same line as opts
|
||||||
|
opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
|
||||||
|
terminal_controller.GREEN + opts + terminal_controller.NORMAL)
|
||||||
|
indent_first = 0
|
||||||
|
result.append(opts)
|
||||||
|
if option.help:
|
||||||
|
help_text = self.expand_default(option).split('\n')
|
||||||
|
help_lines = []
|
||||||
|
|
||||||
|
for line in help_text:
|
||||||
|
help_lines.extend(textwrap.wrap(line, self.help_width))
|
||||||
|
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
|
||||||
|
result.extend(["%*s%s\n" % (self.help_position, "", line)
|
||||||
|
for line in help_lines[1:]])
|
||||||
|
elif opts[-1] != "\n":
|
||||||
|
result.append("\n")
|
||||||
|
return "".join(result)+'\n'
|
||||||
|
|
||||||
|
|
||||||
|
class OptionParser(_OptionParser):
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
usage='%prog [options] filename',
|
||||||
|
version='%%prog (%s %s)'%(__appname__, __version__),
|
||||||
|
epilog=_('Created by ')+terminal_controller.RED+__author__+terminal_controller.NORMAL,
|
||||||
|
gui_mode=False,
|
||||||
|
conflict_handler='resolve',
|
||||||
|
**kwds):
|
||||||
|
usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
|
||||||
|
'''enclose the arguments in quotation marks.'''
|
||||||
|
_OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
|
||||||
|
formatter=CustomHelpFormatter(),
|
||||||
|
conflict_handler=conflict_handler, **kwds)
|
||||||
|
self.gui_mode = gui_mode
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
if self.gui_mode:
|
||||||
|
raise Exception(msg)
|
||||||
|
_OptionParser.error(self, msg)
|
||||||
|
|
||||||
|
def merge(self, parser):
|
||||||
|
'''
|
||||||
|
Add options from parser to self. In case of conflicts, confilicting options from
|
||||||
|
parser are skipped.
|
||||||
|
'''
|
||||||
|
opts = list(parser.option_list)
|
||||||
|
groups = list(parser.option_groups)
|
||||||
|
|
||||||
|
def merge_options(options, container):
|
||||||
|
for opt in deepcopy(options):
|
||||||
|
if not self.has_option(opt.get_opt_string()):
|
||||||
|
container.add_option(opt)
|
||||||
|
|
||||||
|
merge_options(opts, self)
|
||||||
|
|
||||||
|
for group in groups:
|
||||||
|
g = self.add_option_group(group.title)
|
||||||
|
merge_options(group.option_list, g)
|
||||||
|
|
||||||
|
def subsume(self, group_name, msg=''):
|
||||||
|
'''
|
||||||
|
Move all existing options into a subgroup named
|
||||||
|
C{group_name} with description C{msg}.
|
||||||
|
'''
|
||||||
|
opts = [opt for opt in self.options_iter() if opt.get_opt_string() not in ('--version', '--help')]
|
||||||
|
self.option_groups = []
|
||||||
|
subgroup = self.add_option_group(group_name, msg)
|
||||||
|
for opt in opts:
|
||||||
|
self.remove_option(opt.get_opt_string())
|
||||||
|
subgroup.add_option(opt)
|
||||||
|
|
||||||
|
def options_iter(self):
|
||||||
|
for opt in self.option_list:
|
||||||
|
if str(opt).strip():
|
||||||
|
yield opt
|
||||||
|
for gr in self.option_groups:
|
||||||
|
for opt in gr.option_list:
|
||||||
|
if str(opt).strip():
|
||||||
|
yield opt
|
||||||
|
|
||||||
|
def option_by_dest(self, dest):
|
||||||
|
for opt in self.options_iter():
|
||||||
|
if opt.dest == dest:
|
||||||
|
return opt
|
||||||
|
|
||||||
|
def merge_options(self, lower, upper):
|
||||||
|
'''
|
||||||
|
Merge options in lower and upper option lists into upper.
|
||||||
|
Default values in upper are overridden by
|
||||||
|
non default values in lower.
|
||||||
|
'''
|
||||||
|
for dest in lower.__dict__.keys():
|
||||||
|
if not upper.__dict__.has_key(dest):
|
||||||
|
continue
|
||||||
|
opt = self.option_by_dest(dest)
|
||||||
|
if lower.__dict__[dest] != opt.default and \
|
||||||
|
upper.__dict__[dest] == opt.default:
|
||||||
|
upper.__dict__[dest] = lower.__dict__[dest]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Option(object):
|
class Option(object):
|
||||||
|
|
||||||
def __init__(self, name, switches=[], help='', type=None, choices=None,
|
def __init__(self, name, switches=[], help='', type=None, choices=None,
|
||||||
@ -251,7 +373,153 @@ class StringConfig(object):
|
|||||||
footer = self.option_set.get_override_section(self.src)
|
footer = self.option_set.get_override_section(self.src)
|
||||||
self.src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
|
self.src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
|
||||||
|
|
||||||
|
class ConfigProxy(object):
|
||||||
|
'''
|
||||||
|
A Proxy to minimize file reads for widely used config settings
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self.__config = config
|
||||||
|
self.__opts = None
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self.__opts = self.__config.parse()
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.get(key)
|
||||||
|
|
||||||
|
def __setitem__(self, key, val):
|
||||||
|
return self.set(key, val)
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
if self.__opts is None:
|
||||||
|
self.refresh()
|
||||||
|
return getattr(self.__opts, key)
|
||||||
|
|
||||||
|
def set(self, key, val):
|
||||||
|
if self.__opts is None:
|
||||||
|
self.refresh()
|
||||||
|
setattr(self.__opts, key, val)
|
||||||
|
return self.__config.set(key, val)
|
||||||
|
|
||||||
|
class DynamicConfig(dict):
|
||||||
|
'''
|
||||||
|
A replacement for QSettings that supports dynamic config keys.
|
||||||
|
Returns `None` if a config key is not found. Note that the config
|
||||||
|
data is stored in a non human readable pickle file, so only use this
|
||||||
|
class for preferences that you don't intend to have the users edit directly.
|
||||||
|
'''
|
||||||
|
def __init__(self, name='dynamic'):
|
||||||
|
self.name = name
|
||||||
|
self.file_path = os.path.join(config_dir, name+'.pickle')
|
||||||
|
with ExclusiveFile(self.file_path) as f:
|
||||||
|
raw = f.read()
|
||||||
|
d = cPickle.loads(raw) if raw.strip() else {}
|
||||||
|
dict.__init__(self, d)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
return dict.__getitem__(self, key)
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __setitem__(self, key, val):
|
||||||
|
dict.__setitem__(self, key, val)
|
||||||
|
self.commit()
|
||||||
|
|
||||||
|
def set(self, key, val):
|
||||||
|
self.__setitem__(key, val)
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
if hasattr(self, 'file_path') and self.file_path:
|
||||||
|
with ExclusiveFile(self.file_path) as f:
|
||||||
|
raw = cPickle.dumps(self, -1)
|
||||||
|
f.seek(0)
|
||||||
|
f.truncate()
|
||||||
|
f.write(raw)
|
||||||
|
|
||||||
|
dynamic = DynamicConfig()
|
||||||
|
|
||||||
|
def _prefs():
|
||||||
|
c = Config('global', 'calibre wide preferences')
|
||||||
|
c.add_opt('database_path',
|
||||||
|
default=os.path.expanduser('~/library1.db'),
|
||||||
|
help=_('Path to the database in which books are stored'))
|
||||||
|
c.add_opt('filename_pattern', default=ur'(?P<title>.+) - (?P<author>[^_]+)',
|
||||||
|
help=_('Pattern to guess metadata from filenames'))
|
||||||
|
c.add_opt('isbndb_com_key', default='',
|
||||||
|
help=_('Access key for isbndb.com'))
|
||||||
|
c.add_opt('network_timeout', default=5,
|
||||||
|
help=_('Default timeout for network operations (seconds)'))
|
||||||
|
|
||||||
|
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
||||||
|
return c
|
||||||
|
|
||||||
|
prefs = ConfigProxy(_prefs())
|
||||||
|
|
||||||
|
def migrate():
|
||||||
|
p = prefs
|
||||||
|
if p.get('migrated'):
|
||||||
|
return
|
||||||
|
|
||||||
|
from PyQt4.QtCore import QSettings, QVariant
|
||||||
|
class Settings(QSettings):
|
||||||
|
|
||||||
|
def __init__(self, name='calibre2'):
|
||||||
|
QSettings.__init__(self, QSettings.IniFormat, QSettings.UserScope,
|
||||||
|
'kovidgoyal.net', name)
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
try:
|
||||||
|
key = str(key)
|
||||||
|
if not self.contains(key):
|
||||||
|
return default
|
||||||
|
val = str(self.value(key, QVariant()).toByteArray())
|
||||||
|
if not val:
|
||||||
|
return None
|
||||||
|
return cPickle.loads(val)
|
||||||
|
except:
|
||||||
|
return default
|
||||||
|
|
||||||
|
s, migrated = Settings(), set([])
|
||||||
|
all_keys = set(map(unicode, s.allKeys()))
|
||||||
|
from calibre.gui2 import config, dynamic
|
||||||
|
def _migrate(key, safe=None, from_qvariant=None, p=config):
|
||||||
|
try:
|
||||||
|
if key not in all_keys:
|
||||||
|
return
|
||||||
|
if safe is None:
|
||||||
|
safe = re.sub(r'[^0-9a-zA-Z]', '_', key)
|
||||||
|
val = s.get(key)
|
||||||
|
if from_qvariant is not None:
|
||||||
|
val = getattr(s.value(key), from_qvariant)()
|
||||||
|
p.set(safe, val)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
migrated.add(key)
|
||||||
|
|
||||||
|
|
||||||
|
_migrate('database path', p=prefs)
|
||||||
|
_migrate('filename pattern', p=prefs)
|
||||||
|
_migrate('network timeout', p=prefs)
|
||||||
|
_migrate('isbndb.com key', p=prefs)
|
||||||
|
|
||||||
|
_migrate('frequently used directories')
|
||||||
|
_migrate('send to device by default')
|
||||||
|
_migrate('save to disk single format')
|
||||||
|
_migrate('confirm delete')
|
||||||
|
_migrate('show text in toolbar')
|
||||||
|
_migrate('new version notification')
|
||||||
|
_migrate('use roman numerals for series number')
|
||||||
|
_migrate('cover flow queue length')
|
||||||
|
_migrate('LRF conversion defaults')
|
||||||
|
_migrate('LRF ebook viewer options')
|
||||||
|
|
||||||
|
for key in all_keys - migrated:
|
||||||
|
if key.endswith(': un') or key.endswith(': pw'):
|
||||||
|
_migrate(key, p=dynamic)
|
||||||
|
p.set('migrated', True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
86
src/calibre/utils/lock.py
Normal file
86
src/calibre/utils/lock.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Secure access to locked files from multiple processes.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.constants import iswindows, __appname__, \
|
||||||
|
win32api, win32event, winerror, fcntl
|
||||||
|
import time, atexit, os
|
||||||
|
|
||||||
|
class LockError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ExclusiveFile(object):
|
||||||
|
|
||||||
|
def __init__(self, path, timeout=10):
|
||||||
|
self.path = path
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.file = open(self.path, 'a+b')
|
||||||
|
self.file.seek(0)
|
||||||
|
timeout = self.timeout
|
||||||
|
if iswindows:
|
||||||
|
name = ('Local\\'+(__appname__+self.file.name).replace('\\', '_'))[:201]
|
||||||
|
while self.timeout < 0 or timeout >= 0:
|
||||||
|
self.mutex = win32event.CreateMutex(None, False, name)
|
||||||
|
if win32api.GetLastError() != winerror.ERROR_ALREADY_EXISTS: break
|
||||||
|
time.sleep(1)
|
||||||
|
timeout -= 1
|
||||||
|
else:
|
||||||
|
while self.timeout < 0 or timeout >= 0:
|
||||||
|
try:
|
||||||
|
fcntl.lockf(self.file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||||
|
break
|
||||||
|
except IOError:
|
||||||
|
time.sleep(1)
|
||||||
|
timeout -= 1
|
||||||
|
if timeout < 0 and self.timeout >= 0:
|
||||||
|
self.file.close()
|
||||||
|
raise LockError
|
||||||
|
return self.file
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
self.file.close()
|
||||||
|
if iswindows:
|
||||||
|
win32api.CloseHandle(self.mutex)
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_lock_file(file):
|
||||||
|
try:
|
||||||
|
file.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
os.remove(file.name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def singleinstance(name):
|
||||||
|
'''
|
||||||
|
Return True if no other instance of the application identified by name is running,
|
||||||
|
False otherwise.
|
||||||
|
@param name: The name to lock.
|
||||||
|
@type name: string
|
||||||
|
'''
|
||||||
|
if iswindows:
|
||||||
|
mutexname = 'mutexforsingleinstanceof'+__appname__+name
|
||||||
|
mutex = win32event.CreateMutex(None, False, mutexname)
|
||||||
|
if mutex:
|
||||||
|
atexit.register(win32api.CloseHandle, mutex)
|
||||||
|
return not win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS
|
||||||
|
else:
|
||||||
|
path = os.path.expanduser('~/.'+__appname__+'_'+name+'.lock')
|
||||||
|
try:
|
||||||
|
f = open(path, 'w')
|
||||||
|
fcntl.lockf(f.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||||
|
atexit.register(_clean_lock_file, f)
|
||||||
|
return True
|
||||||
|
except IOError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
@ -12,9 +12,10 @@ from urllib import url2pathname
|
|||||||
from httplib import responses
|
from httplib import responses
|
||||||
|
|
||||||
from calibre import setup_cli_handlers, browser, sanitize_file_name, \
|
from calibre import setup_cli_handlers, browser, sanitize_file_name, \
|
||||||
OptionParser, relpath, LoggingInterface
|
relpath, LoggingInterface
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
|
||||||
from calibre.ebooks.chardet import xml_to_unicode
|
from calibre.ebooks.chardet import xml_to_unicode
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
|
|
||||||
class FetchError(Exception):
|
class FetchError(Exception):
|
||||||
pass
|
pass
|
||||||
|
Loading…
x
Reference in New Issue
Block a user