From cc366e9369ce76c5f1bd4936543fbb3b6db11207 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Wed, 5 Nov 2008 12:35:02 +0000 Subject: [PATCH] Re-write the Config class --- deluge/config.py | 368 ++++++++++++-------- deluge/core/core.py | 10 +- deluge/docs/source/modules/config.rst | 5 +- deluge/plugins/label/label/core.py | 15 +- deluge/plugins/label/label/gtkui/submenu.py | 6 - deluge/ui/gtkui/mainwindow.py | 15 +- 6 files changed, 253 insertions(+), 166 deletions(-) diff --git a/deluge/config.py b/deluge/config.py index e106ec3aa..c35ed1a2a 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -1,20 +1,20 @@ # # config.py # -# Copyright (C) 2007 Andrew Resch ('andar') -# +# Copyright (C) 2008 Andrew Resch +# # Deluge is free software. -# +# # You may redistribute it and/or modify it under the terms of the # GNU General Public License, as published by the Free Software # Foundation; either version 3 of the License, or (at your option) # any later version. -# +# # deluge is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with deluge. If not, write to: # The Free Software Foundation, Inc., @@ -31,171 +31,263 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -"""Configuration class used to access/create/modify configuration files.""" - -import cPickle -import os.path +""" +Deluge Config Module +""" +import cPickle as pickle +import shutil +import os import gobject import deluge.common from deluge.log import LOG as log -class Config: - """This class is used to access configuration files.""" - - def __init__(self, filename, defaults=None, config_dir=None): - log.debug("Config created with filename: %s", filename) - log.debug("Config defaults: %s", defaults) - self.config = {} - self.previous_config = {} - self.set_functions = {} - self._change_callback = None - - # If defaults is not None then we need to use "defaults". - if defaults != None: - self.config = defaults +def prop(func): + """Function decorator for defining property attributes - # Load the config from file in the config_dir - if config_dir == None: - self.config_file = deluge.common.get_default_config_dir(filename) - else: - self.config_file = os.path.join(config_dir, filename) - - self.load(self.config_file) - # Save - self.save() - + The decorated function is expected to return a dictionary + containing one or more of the following pairs: + fget - function for getting attribute value + fset - function for setting attribute value + fdel - function for deleting attribute + This can be conveniently constructed by the locals() builtin + function; see: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183 + """ + return property(doc=func.__doc__, **func()) + +class Config(object): + """ + This class is used to access/create/modify config files + + :param filename: the name of the config file + :param defaults: dictionary of default values + :param config_dir: the path to the config directory + + """ + def __init__(self, filename, defaults=None, config_dir=None): + self.__config = {} + self.__previous_config = {} + self.__set_functions = {} + self.__change_callback = None # This will get set with a gobject.timeout_add whenever a config option # is set. - self.save_timer = None - - def __del__(self): - self.save() - - def load(self, filename=None): - """Load a config file either by 'filename' or the filename set during - construction of this object.""" - # Use self.config_file if filename is None - if filename is None: - filename = self.config_file - try: - # Un-pickle the file and update the config dictionary - pkl_file = open(filename, "rb") - filedump = cPickle.load(pkl_file) - self.config.update(filedump) - pkl_file.close() - except IOError: - log.warning("IOError: Unable to load file '%s'", filename) - except EOFError: - pkl_file.close() - - def save(self, filename=None): - """Save configuration to either 'filename' or the filename set during - construction of this object.""" - # Saves the config dictionary - if filename is None: - filename = self.config_file - # Check to see if the current config differs from the one on disk - # We will only write a new config file if there is a difference - try: - pkl_file = open(filename, "rb") - filedump = cPickle.load(pkl_file) - pkl_file.close() - if filedump == self.config: - # The config has not changed so lets just return - self.save_timer = None - return - except (EOFError, IOError): - log.warning("IOError: Unable to open file: '%s'", filename) - - try: - log.debug("Saving config file %s..", filename) - pkl_file = open(filename, "wb") - cPickle.dump(self.config, pkl_file) - pkl_file.close() - except IOError: - log.warning("IOError: Unable to save file '%s'", filename) - - self.save_timer = None - - def set(self, key, value): - """Set the 'key' with 'value'.""" - # Sets the "key" with "value" in the config dict - if self.config[key] == value: + self.__save_timer = None + + if defaults: + self.__config = defaults + + # Load the config from file in the config_dir + if config_dir: + self.__config_file = os.path.join(config_dir, filename) + else: + self.__config_file = deluge.common.get_default_config_dir(filename) + + self.load() + + def __setitem__(self, key, value): + """ + See + :meth:`set_item` + """ + + return self.set_item(key, value) + + def set_item(self, key, value): + """ + Sets item 'key' to 'value' in the config dictionary, but does not allow + changing the item's type unless it is None + + :param key: string, item to change to change + :param value: the value to change item to, must be same type as what is currently in the config + + :raises ValueError: raised when the type of value is not the same as what is currently in the config + + **Usage** + + >>> config = Config("test.conf") + >>> config["test"] = 5 + >>> config["test"] + 5 + + """ + if self.__config[key] == value: return - - oldtype, newtype = type(self.config[key]), type(value) + + # Do not allow the type to change unless it is None + oldtype, newtype = type(self.__config[key]), type(value) if value is not None and oldtype != type(None) and oldtype != newtype: try: value = oldtype(value) except ValueError: log.warning("Type '%s' invalid for '%s'", newtype, key) - return - - log.debug("Setting '%s' to %s of %s", key, value, oldtype) - + raise + + log.debug("Setting '%s' to %s of %s", key, value, type(value)) + # Make a copy of the current config prior to changing it - self.previous_config = self.config.copy() - self.config[key] = value + self.__previous_config.update(self.__config) + self.__config[key] = value # Run the set_function for this key if any try: - gobject.idle_add(self.set_functions[key], key, value) + gobject.idle_add(self.__set_functions[key], key, value) except KeyError: pass try: - gobject.idle_add(self._change_callback, key, value) + gobject.idle_add(self.__change_callback, key, value) except: pass # We set the save_timer for 5 seconds if not already set - log.debug("save_timer: %s", self.save_timer) - if not self.save_timer: - self.save_timer = gobject.timeout_add(5000, self.save) - - def get(self, key): - """Get the value of 'key'. If it is an invalid key then get() will - return None.""" - # Attempts to get the "key" value and returns None if the key is - # invalid - try: - value = self.config[key] - log.debug("Getting '%s' as %s of %s", key, value, type(value)) - return value - except KeyError: - log.warning("Key does not exist, returning None") - return None + if not self.__save_timer: + self.__save_timer = gobject.timeout_add(5000, self.save) + + def __getitem__(self, key): + """ + See + :meth:`get_item` + """ + return self.get_item(key) + + def get_item(self, key): + """ + Gets the value of item 'key' + + :param key: the item for which you want it's value + :return: the value of item 'key' + + :raises KeyError: if 'key' is not in the config dictionary + + **Usage** + + >>> config = Config("test.conf", defaults={"test": 5}) + >>> config["test"] + 5 + + """ + return self.__config[key] - def get_config(self): - """Returns the entire configuration as a dictionary.""" - return self.config - - def get_previous_config(self): - """Returns the config prior to the last set()""" - return self.previous_config - def register_change_callback(self, callback): - """Registers a callback that will be called when a value is changed""" - self._change_callback = callback - + """ + Registers a callback function that will be called when a value is changed in the config dictionary + + :param callback: the function, callback(key, value) + + **Usage** + + >>> config = Config("test.conf", default={"test": 5}) + >>> def cb(key, value): + >>> print key, value + >>> config.register_change_callback(cb) + >>> config["test"] = 4 + test 4 + + """ + self.__change_callback = callback + def register_set_function(self, key, function, apply_now=True): - """Register a function to be run when a config value changes.""" + """ + Register a function to be called when a config value changes + + :param key: the item to monitor for change + :param function: the function to call when the value changes, f(key, value) + :keyword apply_now: if True, the function will be called after it's registered + + **Usage** + + >>> config = Config("test.conf", default={"test": 5}) + >>> def cb(key, value): + >>> print key, value + >>> config.register_set_function("test", cb) + >>> config["test"] = 4 + test 4 + + """ log.debug("Registering function for %s key..", key) - self.set_functions[key] = function + self.__set_functions[key] = function # Run the function now if apply_now is set if apply_now: - self.set_functions[key](key, self.config[key]) + self.__set_functions[key](key, self.__config[key]) return - + def apply_all(self): - """Runs all set functions""" - log.debug("Running all set functions..") - for key in self.set_functions.keys(): - self.set_functions[key](key, self.config[key]) - - def __getitem__(self, key): - return self.config[key] + """ + Calls all set functions - def __setitem__(self, key, value): - self.set(key, value) + **Usage** + >>> config = Config("test.conf", default={"test": 5}) + >>> def cb(key, value): + >>> print key, value + >>> config.register_set_function("test", cb) + >>> config.apply_all() + test 5 + + """ + log.debug("Calling all set functions..") + for key, value in self.__set_functions.iteritems(): + value(key, self.__config[key]) + + def load(self, filename=None): + """ + Load a config file + + :param filename: if None, uses filename set in object initialization + + + """ + if not filename: + filename = self.__config_file + try: + self.__config.update(pickle.load(open(filename, "rb"))) + except Exception, e: + log.warning("Unable to load config file: %s", filename) + + log.debug("Config %s loaded: %s", filename, self.__config) + + def save(self, filename=None): + """ + Save configuration to disk + + :param filename: if None, uses filename set in object initiliazation + + """ + if not filename: + filename = self.__config_file + # Check to see if the current config differs from the one on disk + # We will only write a new config file if there is a difference + try: + if self.__config == pickle.load(open(filename, "rb")): + # The config has not changed so lets just return + self.__save_timer = None + return + except Exception, e: + log.warning("Unable to open config file: %s", filename) + + self.__save_timer = None + + try: + log.debug("Saving new config file %s", filename + ".new") + pickle.dump(self.__config, open(filename + ".new", "wb")) + except Exception, e: + log.error("Error writing new config file: %s", e) + return + + # The new config file has been written successfully, so let's move it over + # the existing one. + try: + log.debug("Moving new config file %s to %s..", filename + ".new", filename) + shutil.move(filename + ".new", filename) + except Exception, e: + log.error("Error moving new config file: %s", e) + return + + @prop + def config(): + """The config dictionary""" + def fget(self): + return self.__config + def fdel(self): + return self.save() + return locals() diff --git a/deluge/core/core.py b/deluge/core/core.py index 238d6ef88..63a04046c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -191,7 +191,7 @@ class Core( # This keeps track of the timer to set the ip filter.. We do this a few # seconds aftering adding a rule so that 'batch' adding of rules isn't slow. self._set_ip_filter_timer = None - + # Load metadata extension self.session.add_extension(lt.create_metadata_plugin) self.session.add_extension(lt.create_ut_metadata_plugin) @@ -528,7 +528,7 @@ class Core( def export_get_config(self): """Get all the preferences as a dictionary""" - return self.config.get_config() + return self.config.config def export_get_config_value(self, key): """Get the config value for key""" @@ -649,12 +649,12 @@ class Core( def export_block_ip_range(self, range): """Block an ip range""" self.ip_filter.add_rule(range[0], range[1], 1) - + # Start a 2 second timer (and remove the previous one if it exists) if self._set_ip_filter_timer: gobject.source_remove(self._set_ip_filter_timer) self._set_ip_filter_timer = gobject.timeout_add(2000, self.session.set_ip_filter, self.ip_filter) - + def export_reset_ip_filter(self): """Clears the ip filter""" self.ip_filter = lt.ip_filter() @@ -722,7 +722,7 @@ class Core( """Renames files in 'torrent_id'. The 'filenames' parameter should be a list of (index, filename) pairs.""" self.torrents[torrent_id].rename_files(filenames) - + def export_rename_folder(self, torrent_id, folder, new_folder): """Renames the 'folder' to 'new_folder' in 'torrent_id'.""" self.torrents[torrent_id].rename_folder(folder, new_folder) diff --git a/deluge/docs/source/modules/config.rst b/deluge/docs/source/modules/config.rst index 335f1a7c1..8c97ef200 100644 --- a/deluge/docs/source/modules/config.rst +++ b/deluge/docs/source/modules/config.rst @@ -1,4 +1,4 @@ -:mod:'deluge.config' +:mod:`deluge.config` ==================== .. automodule:: deluge.config @@ -6,3 +6,6 @@ :show-inheritance: :members: :undoc-members: + + .. automethod:: __setitem__ + .. automethod:: __getitem__ diff --git a/deluge/plugins/label/label/core.py b/deluge/plugins/label/label/core.py index 59dabe544..929a2381b 100644 --- a/deluge/plugins/label/label/core.py +++ b/deluge/plugins/label/label/core.py @@ -101,14 +101,14 @@ class Core(CorePluginBase): #__init__ core = self.plugin.get_core() - self.config = ConfigManager("label.conf") + self.config = ConfigManager("label.conf", defaults=CONFIG_DEFAULTS) self.core_cfg = ConfigManager("core.conf") - self.set_config_defaults() + #self.set_config_defaults() #reduce typing, assigning some values to self... self.torrents = core.torrents.torrents - self.labels = self.config.get("labels") - self.torrent_labels = self.config.get("torrent_labels") + self.labels = self.config["labels"] + self.torrent_labels = self.config["torrent_labels"] self.clean_initial_config() #todo: register to torrent_added event. @@ -176,7 +176,7 @@ class Core(CorePluginBase): changed = False for key, value in CONFIG_DEFAULTS.iteritems(): if not key in self.config.config: - self.config.config[key] = value + self.config[key] = value changed = True if changed: self.config.save() @@ -298,13 +298,13 @@ class Core(CorePluginBase): def export_get_global_options(self): "see : label_set_global_options" - return dict ( (k,self.config.get(k) ) for k in CORE_OPTIONS) + return dict ( (k,self.config[k] ) for k in CORE_OPTIONS) def export_set_global_options(self, options): """global_options:""" for key in CORE_OPTIONS: if options.has_key(key): - self.config.set(key, options[key]) + self.config[key] = options[key] self.config.save() def _status_get_label(self, torrent_id): @@ -312,4 +312,3 @@ class Core(CorePluginBase): if __name__ == "__main__": import test - diff --git a/deluge/plugins/label/label/gtkui/submenu.py b/deluge/plugins/label/label/gtkui/submenu.py index cfe6f6afb..595a6c29c 100644 --- a/deluge/plugins/label/label/gtkui/submenu.py +++ b/deluge/plugins/label/label/gtkui/submenu.py @@ -42,7 +42,6 @@ from deluge.ui.client import aclient from deluge.configmanager import ConfigManager config = ConfigManager("label.conf") -GTK_ALFA = config.get("gtk_alfa") NO_LABEL = "No Label" class LabelMenu(gtk.MenuItem): @@ -85,8 +84,3 @@ class LabelMenu(gtk.MenuItem): for torrent_id in self.get_torrent_ids(): aclient.label_set_torrent(None, torrent_id, label_id) #aclient.force_call(block=True) - - - - - diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 58d59c4f1..41d7a9902 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -152,18 +152,18 @@ class MainWindow(component.Component): def on_window_configure_event(self, widget, event): if not self.config["window_maximized"] and self.visible: - self.config.set("window_x_pos", self.window.get_position()[0]) - self.config.set("window_y_pos", self.window.get_position()[1]) - self.config.set("window_width", event.width) - self.config.set("window_height", event.height) + self.config["window_x_pos"] = self.window.get_position()[0] + self.config["window_y_pos"] = self.window.get_position()[1] + self.config["window_width"] = event.width + self.config["window_height"] = event.height def on_window_state_event(self, widget, event): if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED: if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED: log.debug("pos: %s", self.window.get_position()) - self.config.set("window_maximized", True) + self.config["window_maximized"] = True else: - self.config.set("window_maximized", False) + self.config["window_maximized"] = False if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: log.debug("MainWindow is minimized..") @@ -189,8 +189,7 @@ class MainWindow(component.Component): return True def on_vpaned_position_event(self, obj, param): - self.config.set("window_pane_position", - self.config["window_height"] - self.vpaned.get_position()) + self.config["window_pane_position"] = self.config["window_height"] - self.vpaned.get_position() def on_drag_data_received_event(self, widget, drag_context, x, y, selection_data, info, timestamp): args = []