IGN:Complete migration to new config infrastructure

This commit is contained in:
Kovid Goyal 2008-08-15 19:38:35 -07:00
parent fab60df71d
commit 4d450f556f
35 changed files with 735 additions and 541 deletions

View File

@ -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

View File

@ -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
View 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')

View File

@ -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():

View File

@ -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='.',

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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, \

View File

@ -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

View File

@ -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'

View File

@ -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'''

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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.'))

View File

@ -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):

View File

@ -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)

View File

@ -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):

View File

@ -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:

View File

@ -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):

View File

@ -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:

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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:

View File

@ -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]

View File

@ -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() \

View File

@ -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)

View File

@ -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
View 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):]
################################################################################

View File

@ -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.

View File

@ -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
View 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

View File

@ -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