diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 061d0712ce..f84941d371 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -2,6 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' __docformat__ = 'restructuredtext en' + import sys, os, re, logging, time, mimetypes, \ __builtin__, warnings, multiprocessing from urllib import getproxies @@ -13,12 +14,13 @@ from functools import partial warnings.simplefilter('ignore', DeprecationWarning) -from calibre.startup import plugins, winutil, winutilerror from calibre.constants import iswindows, isosx, islinux, isfreebsd, isfrozen, \ terminal_controller, preferred_encoding, \ __appname__, __version__, __author__, \ win32event, win32api, winerror, fcntl, \ - filesystem_encoding + filesystem_encoding, plugins, config_dir +from calibre.startup import winutil, winutilerror + import mechanize if False: @@ -486,7 +488,6 @@ def ipython(user_ns=None): sys.argv = ['ipython'] if user_ns is None: user_ns = locals() - from calibre.utils.config import config_dir ipydir = os.path.join(config_dir, ('_' if iswindows else '.')+'ipython') os.environ['IPYTHONDIR'] = ipydir if not os.path.exists(ipydir): diff --git a/src/calibre/constants.py b/src/calibre/constants.py index ddcc21d4c5..ab23101b5d 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -14,7 +14,7 @@ numeric_version = tuple(_ver) Various run time constants. ''' -import sys, locale, codecs +import sys, locale, codecs, os from calibre.utils.terminfo import TerminalController terminal_controller = TerminalController(sys.stdout) @@ -47,7 +47,7 @@ def debug(): global DEBUG DEBUG = True -################################################################################ +# plugins {{{ plugins = None if plugins is None: # Load plugins @@ -80,3 +80,22 @@ if plugins is None: return plugins plugins = load_plugins() +# }}} + +# config_dir {{{ +if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'): + config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY']) +elif iswindows: + if plugins['winutil'][0] is None: + raise Exception(plugins['winutil'][1]) + config_dir = plugins['winutil'][0].special_folder_path(plugins['winutil'][0].CSIDL_APPDATA) + if not os.access(config_dir, os.W_OK|os.X_OK): + config_dir = os.path.expanduser('~') + config_dir = os.path.join(config_dir, 'calibre') +elif isosx: + config_dir = os.path.expanduser('~/Library/Preferences/calibre') +else: + bdir = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config'))) + config_dir = os.path.join(bdir, 'calibre') +# }}} + diff --git a/src/calibre/manual/customize.rst b/src/calibre/manual/customize.rst index f875b0e648..0852550c83 100644 --- a/src/calibre/manual/customize.rst +++ b/src/calibre/manual/customize.rst @@ -9,7 +9,8 @@ Customizing |app| |app| has a highly modular design. Various parts of it can be customized. You can learn how to create *recipes* to add new sources of online content to |app| in the Section :ref:`news`. Here, you will learn, -first, how to use environment variables and *tweaks* to customize |app|'s behavior and then how to +first, how to use environment variables and *tweaks* to customize |app|'s behavior, and then how to +specify your own static resources like icons and templates to override the defaults and finally how to use *plugins* to add funtionality to |app|. .. contents:: @@ -35,6 +36,20 @@ The default tweaks.py file is reproduced below .. literalinclude:: ../../../resources/default_tweaks.py +Overriding icons, templates, etcetera +---------------------------------------- + +|app| allows you to override the static resources, like icons, templates, javascript, etc. with customized versions that you like. +All static resources are stored in the resources sub-folder of the calibre install location. On Windows, this is usually +:file:`C:\Program Files\Calibre2\resources`. On OS X, :file:`/Applications/calibre.app/Contents/Resources/resources/`. On linux, if you are using the binary installer +from the calibre website it will be :file:`/opt/calibre/resources`. These paths can change depending on where you choose to install |app|. + +You should not change the files in this resources folder, as your changes will get overwritten the next time you update |app|. Instead, go to +:guilabel:`Preferences->Advanced` and click :guilabel:`Open calibre configuration directory`. In this configuration directory, create a sub-folder called resources and place the files you want to override in it. |app| will automatically use your custom file in preference to the builtin one the next time it is started. + +For example, if you wanted to change the icon for the :guilabel:`Remove books` action, you would first look in the builtin resources folder and see that the relevant file is +:file:`resources/images/trash.svg`. Assuming you have an alternate icon in svg format called :file:`mytrash.svg` you would save it in the configuration directory as :file:`resources/images/trash.svg`. All the icons used by the calibre user interface are in :file:`resources/images` and its sub-folders. + A Hello World plugin ------------------------ diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py index 91b1dc70cc..720f3ca977 100644 --- a/src/calibre/utils/config.py +++ b/src/calibre/utils/config.py @@ -13,25 +13,10 @@ from optparse import OptionParser as _OptionParser from optparse import IndentedHelpFormatter from collections import defaultdict -from calibre.constants import terminal_controller, iswindows, isosx, \ - __appname__, __version__, __author__, plugins +from calibre.constants import terminal_controller, config_dir, \ + __appname__, __version__, __author__ from calibre.utils.lock import LockError, ExclusiveFile -if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'): - config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY']) -elif iswindows: - if plugins['winutil'][0] is None: - raise Exception(plugins['winutil'][1]) - config_dir = plugins['winutil'][0].special_folder_path(plugins['winutil'][0].CSIDL_APPDATA) - if not os.access(config_dir, os.W_OK|os.X_OK): - config_dir = os.path.expanduser('~') - config_dir = os.path.join(config_dir, 'calibre') -elif isosx: - config_dir = os.path.expanduser('~/Library/Preferences/calibre') -else: - bdir = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config'))) - config_dir = os.path.join(bdir, 'calibre') - plugin_dir = os.path.join(config_dir, 'plugins') CONFIG_DIR_MODE = 0700 diff --git a/src/calibre/utils/resources.py b/src/calibre/utils/resources.py index d114654165..7f87cb4fc3 100644 --- a/src/calibre/utils/resources.py +++ b/src/calibre/utils/resources.py @@ -9,26 +9,54 @@ __docformat__ = 'restructuredtext en' import __builtin__, sys, os -_dev_path = os.environ.get('CALIBRE_DEVELOP_FROM', None) -if _dev_path is not None: - _dev_path = os.path.join(os.path.abspath(os.path.dirname(_dev_path)), 'resources') - if not os.path.exists(_dev_path): - _dev_path = None +from calibre import config_dir -_path_cache = {} +class PathResolver(object): + + def __init__(self): + self.locations = [sys.resources_location] + self.cache = {} + + def suitable(path): + try: + return os.path.exists(path) and os.path.isdir(path) and \ + os.listdir(path) + except: + pass + return False + + dev_path = os.environ.get('CALIBRE_DEVELOP_FROM', None) + if dev_path is not None: + dev_path = os.path.join(os.path.abspath( + os.path.dirname(dev_path)), 'resources') + if suitable(dev_path): + self.locations.insert(0, dev_path) + + user_path = os.path.join(config_dir, 'resources') + if suitable(user_path): + self.locations.insert(0, user_path) + + def __call__(self, path): + path = path.replace(os.sep, '/') + ans = self.cache.get(path, None) + if ans is None: + for base in self.locations: + fpath = os.path.join(base, *path.split('/')) + if os.path.exists(fpath): + ans = fpath + break + + if ans is None: + ans = os.path.join(self.location[0], *path.split('/')) + + self.cache[path] = ans + + return ans + +_resolver = PathResolver() def get_path(path, data=False): - global _dev_path - path = path.replace(os.sep, '/') - base = sys.resources_location - if _dev_path is not None: - if path in _path_cache: - return _path_cache[path] - if os.path.exists(os.path.join(_dev_path, *path.split('/'))): - base = _dev_path - fpath = os.path.join(base, *path.split('/')) - if _dev_path is not None: - _path_cache[path] = fpath + fpath = _resolver(path) if data: return open(fpath, 'rb').read() return fpath