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)
isosx = 'darwin' in sys.platform
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)
APPNAME = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
print 'Setup', APPNAME, 'version:', VERSION

View File

@ -1,123 +1,21 @@
''' E-book management software'''
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
__version__ = '0.4.83'
__docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid at kovidgoyal.net>"
__appname__ = 'calibre'
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys, os, logging, mechanize, locale, copy, cStringIO, re, subprocess, \
textwrap, atexit, cPickle, codecs, time
from gettext import GNUTranslations
import sys, os, re, logging, time, subprocess, mechanize, atexit
from htmlentitydefs import name2codepoint
from math import floor
from optparse import OptionParser as _OptionParser
from optparse import IndentedHelpFormatter
from logging import Formatter
from PyQt4.QtCore import QSettings, QVariant, QUrl, QByteArray, QString
from PyQt4.QtGui import QDesktopServices
from PyQt4.QtCore import QUrl
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):
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))
# Default translation is NOOP
import __builtin__
__builtin__.__dict__['_'] = lambda s: s
class CommandLineError(Exception):
pass
@ -180,122 +74,6 @@ def setup_cli_handlers(logger, level):
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):
if iswindows:
return cdll.LoadLibrary(name)
@ -392,40 +170,6 @@ def fit_image(width, height, pwidth, pheight):
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):
'''
@ -510,120 +254,6 @@ def relpath(target, base=os.curdir):
rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:]
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)
def english_sort(x, y):
'''
@ -671,10 +301,7 @@ def strftime(fmt, t=time.localtime()):
A version of strtime that returns unicode strings.
'''
result = time.strftime(fmt, t)
try:
return unicode(result, locale.getpreferredencoding(), 'replace')
except:
return unicode(result, 'utf-8', 'replace')
return unicode(result, preferred_encoding, 'replace')
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')):
from calibre.ebooks.lrf.fonts.liberation import __all__ as fonts
for font in fonts:
exec 'from calibre.ebooks.lrf.fonts.liberation.'+font+' import font_data'
open(os.path.join(fdir, font+'.ttf'), 'wb').write(font_data)
l = {}
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
from calibre import OptionParser, iswindows, isosx
from calibre.utils.config import OptionParser
from calibre.constants import iswindows, isosx
from calibre.libunzip import update
def option_parser():

View File

@ -808,7 +808,7 @@ class LitReader(object):
os.makedirs(dir)
def option_parser():
from calibre import OptionParser
from calibre.utils.config import OptionParser
parser = OptionParser(usage=_('%prog [options] LITFILE'))
parser.add_option(
'-o', '--output-dir', default='.',

View File

@ -14,7 +14,8 @@ from calibre.ebooks.lrf.pylrs.pylrs import TextBlock, Header, PutObj, \
Paragraph, TextStyle, BlockStyle
from calibre.ebooks.lrf.fonts import FONT_FILE_MAP
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"

View File

@ -2,7 +2,8 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
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.lrf.meta import get_metadata
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
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.objects import get_object, PageTree, StyleObject, \
Font, Text, TOCObject, BookAttr, ruby_tags

View File

@ -6,7 +6,8 @@ Compile a LRS file into a LRF file.
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, \
CData, Tag
from calibre.ebooks.lrf.pylrs.pylrs import Book, PageStyle, TextStyle, \

View File

@ -574,8 +574,8 @@ class LRFMetaFile(object):
def option_parser():
from optparse import OptionParser
from calibre import __appname__, __version__
from calibre.utils.config import OptionParser
from calibre.constants import __appname__, __version__
parser = OptionParser(usage = \
_('''%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
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
PDFTOXML = 'pdftoxml.exe'

View File

@ -11,8 +11,9 @@ from urllib import unquote, quote
from urlparse import urlparse
from calibre import __version__ as VERSION, relpath
from calibre import OptionParser
from calibre.constants import __version__ as VERSION
from calibre import relpath
from calibre.utils.config import OptionParser
def get_parser(extension):
''' 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
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.BeautifulSoup import BeautifulStoneSoup

View File

@ -6,7 +6,8 @@ Fetch cover from LibraryThing.com based on ISBN number.
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
browser = None

View File

@ -3,6 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
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.fb2 import get_metadata as fb2_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':
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):
name = os.path.splitext(name)[0]
mi = MetaInformation(None, None)
if pat is None:
pat = _filename_pat
pat = re.compile(prefs.get('filename_pattern'))
match = pat.search(name)
if match:
try:

View File

@ -408,7 +408,7 @@ def get_metadata(stream):
return mi
def option_parser():
from calibre import OptionParser
from calibre.utils.config import OptionParser
parser = OptionParser(usage=_('%prog [options] myebook.mobi'))
parser.add_option('-o', '--output-dir', default='.',
help=_('Output directory. Defaults to current directory.'))

View File

@ -2,19 +2,49 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" The GUI """
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
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
QIcon, QTableView, QDialogButtonBox, QApplication
ORG_NAME = 'KovidsBrain'
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
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
if iswindows:
import warnings
@ -105,14 +135,12 @@ class TableView(QTableView):
QTableView.__init__(self, parent)
self.read_settings()
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):
settings = Settings()
settings.set(self.__class__.__name__ + ' column widths',
tuple([int(self.columnWidth(i)) for i in range(self.model().columnCount(None))]))
dynamic[self.__class__.__name__+'column widths'] = \
tuple([int(self.columnWidth(i)) for i in range(self.model().columnCount(None))])
def restore_column_widths(self):
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
is hidden, if True it is shown.
'''
key = self.__class__.__name__+'visible columns'
if cols:
Settings().set(self.__class__.__name__ + ' visible columns', cols)
dynamic[key] = cols
else:
cols = Settings().get(self.__class__.__name__ + ' visible columns')
cols = dynamic[key]
if not cols:
cols = [True for i in range(self.model().columnCount(self))]
@ -232,7 +261,7 @@ _sidebar_directories = []
def set_sidebar_directories(dirs):
global _sidebar_directories
if dirs is None:
dirs = Settings().get('frequently used directories', [])
dirs = config['frequently_used_directories']
_sidebar_directories = [QUrl.fromLocalFile(i) for i in dirs]
class FileDialog(QObject):
@ -255,10 +284,10 @@ class FileDialog(QObject):
if add_all_files_filter or not ftext:
ftext += 'All files (*)'
settings = Settings()
self.dialog_name = name if name else 'dialog_' + title
self.selected_files = None
self.fd = None
if islinux:
self.fd = QFileDialog(parent)
self.fd.setFileMode(mode)
@ -266,15 +295,15 @@ class FileDialog(QObject):
self.fd.setModal(modal)
self.fd.setFilter(ftext)
self.fd.setWindowTitle(title)
state = settings.get(self.dialog_name, QByteArray())
if not self.fd.restoreState(state):
state = dynamic[self.dialog_name]
if not state or not self.fd.restoreState(state):
self.fd.setDirectory(os.path.expanduser('~'))
osu = [i for i in self.fd.sidebarUrls()]
self.fd.setSidebarUrls(osu + _sidebar_directories)
QObject.connect(self.fd, SIGNAL('accepted()'), self.save_dir)
self.accepted = self.fd.exec_() == QFileDialog.Accepted
else:
dir = settings.get(self.dialog_name, os.path.expanduser('~'))
dir = dynamic.get(self.dialog_name, default=os.path.expanduser('~'))
self.selected_files = []
if mode == QFileDialog.AnyFile:
f = qstring_to_unicode(
@ -299,7 +328,7 @@ class FileDialog(QObject):
self.selected_files.append(f)
if 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)
@ -313,8 +342,7 @@ class FileDialog(QObject):
def save_dir(self):
if self.fd:
settings = Settings()
settings.set(self.dialog_name, self.fd.saveState())
dynamic[self.dialog_name] = self.fd.saveState()
def choose_dir(window, name, title):

View File

@ -12,7 +12,8 @@ import sys, os
from PyQt4.QtGui import QImage, QSizePolicy
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']
if pictureflow is not None:
@ -70,7 +71,7 @@ if pictureflow is not None:
def __init__(self, height=300, parent=None):
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.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+25))
self.setFocusPolicy(Qt.WheelFocus)

View File

@ -5,9 +5,10 @@ import os
from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon
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 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.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.db = db
self.current_cols = columns
settings = Settings()
path = settings.get('database path')
path = prefs['database_path']
self.location.setText(os.path.dirname(path))
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
dirs = settings.get('frequently used directories', [])
rn = settings.get('use roman numerals for series number', True)
self.timeout.setValue(settings.get('network timeout', 5))
dirs = config['frequently_used_directories']
rn = config['use_roman_numerals_for_series_number']
self.timeout.setValue(prefs['network_timeout'])
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.connect(self.add_button, SIGNAL('clicked(bool)'), self.add_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.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.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:
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.cover_browse.setValue(settings.get('cover flow queue length', 6))
self.confirm_delete.setChecked(settings.get('confirm delete', False))
self.cover_browse.setValue(config['cover_flow_queue_length'])
self.confirm_delete.setChecked(config['confirm_delete'])
def compact(self, toggled):
d = Vacuum(self, self.db)
@ -89,19 +89,18 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.directory_list.takeItem(idx)
def accept(self):
settings = Settings()
settings.set('use roman numerals for series number', bool(self.roman_numerals.isChecked()))
settings.set('new version notification', bool(self.new_version_notification.isChecked()))
settings.set('network timeout', int(self.timeout.value()))
config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked())
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
prefs['network_timeout'] = int(self.timeout.value())
path = qstring_to_unicode(self.location.text())
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()])
settings.set('show text in toolbar', bool(self.show_toolbar_text.isChecked()))
settings.set('confirm delete', bool(self.confirm_delete.isChecked()))
config['toolbar_icon_size'] = self.ICON_SIZES[self.toolbar_button_size.currentIndex()]
config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked())
config['confirm_delete'] = bool(self.confirm_delete.isChecked())
pattern = self.filename_pattern.commit()
settings.set('filename pattern', pattern)
settings.set('save to disk single format', BOOK_EXTENSIONS[self.single_format.currentIndex()])
settings.set('cover flow queue length', self.cover_browse.value())
config['filename_pattern'] = pattern
config['save_to_disk_single_format'] = BOOK_EXTENSIONS[self.single_format.currentIndex()]
config['cover_flow_queue_length'] = self.cover_browse.value()
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.'))
@ -112,7 +111,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
else:
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())]
settings.set('frequently used directories', self.directories)
config['frequently_used_directories'] = self.directories
QDialog.accept(self)
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 import error_dialog, NONE, info_dialog
from calibre.ebooks.metadata.isbndb import create_books, option_parser, ISBNDBError
from calibre import Settings
from calibre.utils.config import prefs
class Matches(QAbstractTableModel):
@ -76,7 +76,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
self.timeout = timeout
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.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'))
return
else:
Settings().set('isbndb.com key', key)
prefs['isbndb_com_key'] = key
args = ['isbndb']
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.choose_format import ChooseFormatDialog
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.ebooks.lrf import option_parser
from calibre.ptempfile import PersistentTemporaryFile
from calibre import __appname__, Settings
from calibre.constants import __appname__
font_family_model = None
@ -109,7 +109,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
def load_saved_global_defaults(self):
cmdline = Settings().get('LRF conversion defaults')
cmdline = config['LRF_conversion_defaults']
if cmdline:
self.set_options_from_cmdline(cmdline)
@ -163,7 +163,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
def select_cover(self, checked):
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:
return
_file = files[0]
@ -385,7 +385,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
cmdline.extend([u'--cover', self.cover_file.name])
self.cmdline = [unicode(i) for i in cmdline]
else:
Settings().set('LRF conversion defaults', cmdline)
config.set('LRF_conversion_defaults', cmdline)
QDialog.accept(self)
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.ebooks import BOOK_EXTENSIONS
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):
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()'),
self.remove_unused_series)
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))
isbn = db.isbn(self.id, index_is_id=True)
if not isbn:

View File

@ -1,12 +1,11 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import re
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 import qstring_to_unicode
from calibre import Settings
from calibre.gui2 import qstring_to_unicode, dynamic
class PasswordDialog(QDialog, Ui_Dialog):
@ -14,10 +13,12 @@ class PasswordDialog(QDialog, Ui_Dialog):
QDialog.__init__(self, window)
Ui_Dialog.__init__(self)
self.setupUi(self)
self.cfg_key = re.sub(r'[^0-9a-zA-Z]', '_', name)
settings = Settings()
un = settings.get(name+': un', u'')
pw = settings.get(name+': pw', u'')
un = dynamic[self.cfg_key+'__un']
pw = dynamic[self.cfg_key+'__un']
if not un: un = ''
if not pw: pw = ''
self.gui_username.setText(un)
self.gui_password.setText(pw)
self.sname = name
@ -37,7 +38,6 @@ class PasswordDialog(QDialog, Ui_Dialog):
return qstring_to_unicode(self.gui_password.text())
def accept(self):
settings = Settings()
settings.set(self.sname+': un', unicode(self.gui_username.text()))
settings.set(self.sname+': pw', unicode(self.gui_password.text()))
dynamic.set(self.cfg_key+'__un', unicode(self.gui_username.text()))
dynamic.set(self.cfg_key+'__pw', unicode(self.gui_password.text()))
QDialog.accept(self)

View File

@ -14,10 +14,10 @@ from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex, \
QTimer
from calibre import Settings, preferred_encoding
from calibre import preferred_encoding
from calibre.ptempfile import PersistentTemporaryFile
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):
COLOR = QColor("blue")
@ -117,7 +117,7 @@ class BooksModel(QAbstractTableModel):
self.load_queue = deque()
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):

View File

@ -6,10 +6,11 @@ import sys, logging, os, traceback, time
from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon
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.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.lrf_renderer.main_ui import Ui_MainWindow
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
@ -102,13 +103,15 @@ class Main(MainWindow, Ui_MainWindow):
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.exec_()
if d.result() == QDialog.Accepted:
opts.white_background = bool(d.white_background.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):
self.progress_bar.setMinimum(0)
@ -281,7 +284,9 @@ Read the LRF ebook book.lrf
return parser
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:
if not opt.dest:
continue

View File

@ -3,23 +3,24 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, sys, textwrap, collections, traceback, shutil, time
from xml.parsers.expat import ExpatError
from functools import partial
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QVariant, QUrl, QSize
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
QToolButton, QDialog, QDesktopServices
from PyQt4.QtSvg import QSvgRenderer
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.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.interface import Device
from calibre.utils.config import prefs, dynamic
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
initialize_file_icon_provider, question_dialog,\
pixmap_to_data, choose_dir, ORG_NAME, \
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.library.database import LibraryDatabase
from calibre.gui2.update import CheckForUpdates
@ -120,12 +121,12 @@ class Main(MainWindow, Ui_MainWindow):
sm.addAction(_('Send to storage card by default'))
sm.actions()[-1].setCheckable(True)
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_card)
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)
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())
self.sync_menu = sm # Needed
md = QMenu()
@ -152,7 +153,7 @@ class Main(MainWindow, Ui_MainWindow):
self.save_menu = QMenu()
self.save_menu.addAction(_('Save to disk'))
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.addAction(_('View'))
@ -529,7 +530,7 @@ class Main(MainWindow, Ui_MainWindow):
rows = view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
return
if Settings().get('confirm delete', False):
if config['confirm_delete']:
d = question_dialog(self, _('Confirm delete'),
_('Are you sure you want to delete these %d books?')%len(rows))
if d.exec_() != QMessageBox.Yes:
@ -680,7 +681,7 @@ class Main(MainWindow, Ui_MainWindow):
############################## Save to disk ################################
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):
self.save_to_disk(checked, True)
@ -1067,9 +1068,8 @@ class Main(MainWindow, Ui_MainWindow):
d.exec_()
if d.result() == d.Accepted:
self.library_view.set_visible_columns(d.final_columns)
settings = Settings()
self.tool_bar.setIconSize(settings.value('toolbar icon size', QVariant(QSize(48, 48))).toSize())
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if settings.get('show text in toolbar', True) else Qt.ToolButtonIconOnly)
self.tool_bar.setIconSize(config['toolbar_icon_size'])
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly)
if os.path.dirname(self.database_path) != d.database_location:
try:
@ -1100,8 +1100,7 @@ class Main(MainWindow, Ui_MainWindow):
d.exec_()
newloc = self.database_path
self.database_path = newloc
settings = Settings()
settings.set('database path', self.database_path)
prefs().set('database_path', self.database_path)
except Exception, err:
traceback.print_exc()
d = error_dialog(self, _('Could not move database'), unicode(err))
@ -1200,12 +1199,10 @@ class Main(MainWindow, Ui_MainWindow):
def read_settings(self):
settings = Settings()
settings.beginGroup('Main Window')
geometry = settings.value('main window geometry', QVariant()).toByteArray()
self.restoreGeometry(geometry)
settings.endGroup()
self.database_path = settings.get('database path')
geometry = config['main_window_geometry']
if geometry is not None:
self.restoreGeometry(geometry)
self.database_path = prefs['database_path']
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_()
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):
os.makedirs(self.database_path)
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_filename_pat(settings.get('filename pattern', get_filename_pat()))
self.tool_bar.setIconSize(settings.get('toolbar icon size', QSize(48, 48)))
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if settings.get('show text in toolbar', True) else Qt.ToolButtonIconOnly)
self.tool_bar.setIconSize(config['toolbar_icon_size'])
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly)
def write_settings(self):
settings = Settings()
settings.beginGroup("Main Window")
settings.setValue("main window geometry", QVariant(self.saveGeometry()))
settings.endGroup()
settings.beginGroup('Book Views')
config.set('main_window_geometry', self.saveGeometry())
self.library_view.write_settings()
if self.device_connected:
self.memory_view.write_settings()
settings.endGroup()
def closeEvent(self, e):
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,
device=self.device_info)))
self.vanity.update()
s = Settings()
if s.get('new version notification', True) and s.get('update to version %s'%version, True):
if config.get('new_version_notification') and dynamic.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))
if d.exec_() == QMessageBox.Yes:
url = 'http://calibre.kovidgoyal.net/download_'+('windows' if iswindows else 'osx' if isosx else 'linux')
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):
from calibre import singleinstance
from calibre.utils.lock import singleinstance
pid = os.fork() if False and islinux else -1
if pid <= 0:

View File

@ -5,7 +5,7 @@ import StringIO, traceback, sys
from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
from calibre import OptionParser
from calibre.utils.config import OptionParser
def option_parser(usage='''\
Usage: %prog [options]

View File

@ -9,15 +9,16 @@ from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \
QSyntaxHighlighter, QCursor, QColor, QWidget, \
QAbstractItemDelegate, QPixmap, QStyle, QFontMetrics
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, SIGNAL, \
QObject, QRegExp, QString
QObject, QRegExp, QString, QSettings
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 import fit_image, Settings
from calibre import fit_image
from calibre.utils.fontconfig import find_font_families
from calibre.ebooks.metadata.meta import get_filename_pat, metadata_from_filename, \
set_filename_pat
from calibre.ebooks.metadata.meta import metadata_from_filename
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.re, SIGNAL('returnPressed()'), self.do_test)
self.re.setText(get_filename_pat())
self.re.setText(prefs['filename_pattern'])
def do_test(self):
try:
@ -66,9 +67,9 @@ class FilenamePattern(QWidget, Ui_Form):
return re.compile(pat)
def commit(self):
pat = self.pattern()
set_filename_pat(pat)
return pat.pattern
pat = self.pattern().pattern
prefs['filename_pattern'] = pat
return pat
@ -367,13 +368,13 @@ class PythonHighlighter(QSyntaxHighlighter):
@classmethod
def loadConfig(cls):
Config = cls.Config
settings = QSettings()
def setDefaultString(name, default):
value = settings.value(name).toString()
if value.isEmpty():
value = default
Config[name] = value
settings = Settings()
for name in ("window", "shell"):
Config["%swidth" % name] = settings.value("%swidth" % name,
QVariant(QApplication.desktop() \

View File

@ -10,7 +10,8 @@ Command line interface to the calibre database.
import sys, os
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:
from calibre.utils.single_qt_application import send_message
except:
@ -24,7 +25,8 @@ FIELDS = set(['title', 'authors', 'publisher', 'rating', 'timestamp', 'size', 't
def get_parser(usage):
parser = OptionParser(usage)
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
def get_db(dbpath, options):
@ -422,7 +424,7 @@ For help on an individual command: %%prog command --help
return 1
command = eval('command_'+args[1])
dbpath = Settings().get('database path')
dbpath = prefs.get('database_path')
return command(args[2:], dbpath)

View File

@ -244,6 +244,7 @@ def do_postinstall(destdir):
os.chdir(destdir)
os.environ['LD_LIBRARY_PATH'] = destdir+':'+os.environ.get('LD_LIBRARY_PATH', '')
os.environ['PYTHONPATH'] = destdir
os.environ['PYTHONSTARTUP'] = ''
subprocess.call((os.path.join(destdir, 'calibre_postinstall'), '--save-manifest-to', t.name))
finally:
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 = ''
from calibre import __appname__
from calibre import __version__ as version
from calibre.constants import __appname__
from calibre.constants import __version__ as version
# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
# there.

View File

@ -6,10 +6,14 @@ __docformat__ = 'restructuredtext en'
'''
Manage application-wide preferences.
'''
import os, re, cPickle
import os, re, cPickle, textwrap
from copy import deepcopy
from optparse import OptionParser as _OptionParser
from optparse import IndentedHelpFormatter
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
if iswindows:
@ -27,6 +31,124 @@ else:
if not os.path.exists(config_dir):
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):
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)
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__':

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 calibre import setup_cli_handlers, browser, sanitize_file_name, \
OptionParser, relpath, LoggingInterface
relpath, LoggingInterface
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
from calibre.ebooks.chardet import xml_to_unicode
from calibre.utils.config import OptionParser
class FetchError(Exception):
pass