~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Patch Queue Manager
  • Date: 2012-01-05 10:39:49 UTC
  • mfrom: (6404.5.9 store-save-changes)
  • Revision ID: pqm@pqm.ubuntu.com-20120105103949-xxe5dkbfiu16q4ch
(vila) Provide Store.save_changes() to allow configuration store to be saved
 incrementally. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2012 Canonical Ltd
2
2
#   Authors: Robert Collins <robert.collins@canonical.com>
3
3
#            and others
4
4
#
2886
2886
 
2887
2887
_NewlyCreatedOption = object()
2888
2888
"""Was the option created during the MutableSection lifetime"""
 
2889
_DeletedOption = object()
 
2890
"""Was the option deleted during the MutableSection lifetime"""
2889
2891
 
2890
2892
 
2891
2893
class MutableSection(Section):
2893
2895
 
2894
2896
    def __init__(self, section_id, options):
2895
2897
        super(MutableSection, self).__init__(section_id, options)
2896
 
        self.orig = {}
 
2898
        self.reset_changes()
2897
2899
 
2898
2900
    def set(self, name, value):
2899
2901
        if name not in self.options:
2908
2910
            self.orig[name] = self.get(name, None)
2909
2911
        del self.options[name]
2910
2912
 
 
2913
    def reset_changes(self):
 
2914
        self.orig = {}
 
2915
 
 
2916
    def apply_changes(self, dirty, store):
 
2917
        """Apply option value changes.
 
2918
 
 
2919
        ``self`` has been reloaded from the persistent storage. ``dirty``
 
2920
        contains the changes made since the previous loading.
 
2921
 
 
2922
        :param dirty: the mutable section containing the changes.
 
2923
 
 
2924
        :param store: the store containing the section
 
2925
        """
 
2926
        for k, expected in dirty.orig.iteritems():
 
2927
            actual = dirty.get(k, _DeletedOption)
 
2928
            reloaded = self.get(k, _NewlyCreatedOption)
 
2929
            if actual is _DeletedOption:
 
2930
                if k in self.options:
 
2931
                    self.remove(k)
 
2932
            else:
 
2933
                self.set(k, actual)
 
2934
            # Report concurrent updates in an ad-hoc way. This should only
 
2935
            # occurs when different processes try to update the same option
 
2936
            # which is not supported (as in: the config framework is not meant
 
2937
            # to be used a sharing mechanism).
 
2938
            if expected != reloaded:
 
2939
                if actual is _DeletedOption:
 
2940
                    actual = '<DELETED>'
 
2941
                if reloaded is _NewlyCreatedOption:
 
2942
                    reloaded = '<CREATED>'
 
2943
                if expected is _NewlyCreatedOption:
 
2944
                    expected = '<CREATED>'
 
2945
                # Someone changed the value since we get it from the persistent
 
2946
                # storage.
 
2947
                trace.warning(gettext(
 
2948
                        "Option {0} in section {1} of {2} was changed"
 
2949
                        " from {3} to {4}. The {5} value will be saved.".format(
 
2950
                            k, self.id, store.external_url(), expected,
 
2951
                            reloaded, actual)))
 
2952
        # No need to keep track of these changes
 
2953
        self.reset_changes()
 
2954
 
2911
2955
 
2912
2956
class Store(object):
2913
2957
    """Abstract interface to persistent storage for configuration options."""
2915
2959
    readonly_section_class = Section
2916
2960
    mutable_section_class = MutableSection
2917
2961
 
 
2962
    def __init__(self):
 
2963
        # Which sections need to be saved
 
2964
        self.dirty_sections = []
 
2965
 
2918
2966
    def is_loaded(self):
2919
2967
        """Returns True if the Store has been loaded.
2920
2968
 
2961
3009
        """Saves the Store to persistent storage."""
2962
3010
        raise NotImplementedError(self.save)
2963
3011
 
 
3012
    def _need_saving(self):
 
3013
        for s in self.dirty_sections:
 
3014
            if s.orig:
 
3015
                # At least one dirty section contains a modification
 
3016
                return True
 
3017
        return False
 
3018
 
 
3019
    def apply_changes(self, dirty_sections):
 
3020
        """Apply changes from dirty sections while checking for coherency.
 
3021
 
 
3022
        The Store content is discarded and reloaded from persistent storage to
 
3023
        acquire up-to-date values.
 
3024
 
 
3025
        Dirty sections are MutableSection which kept track of the value they
 
3026
        are expected to update.
 
3027
        """
 
3028
        # We need an up-to-date version from the persistent storage, unload the
 
3029
        # store. The reload will occur when needed (triggered by the first
 
3030
        # get_mutable_section() call below.
 
3031
        self.unload()
 
3032
        # Apply the changes from the preserved dirty sections
 
3033
        for dirty in dirty_sections:
 
3034
            clean = self.get_mutable_section(dirty.id)
 
3035
            clean.apply_changes(dirty, self)
 
3036
        # Everything is clean now
 
3037
        self.dirty_sections = []
 
3038
 
 
3039
    def save_changes(self):
 
3040
        """Saves the Store to persistent storage if changes occurred.
 
3041
 
 
3042
        Apply the changes recorded in the mutable sections to a store content
 
3043
        refreshed from persistent storage.
 
3044
        """
 
3045
        raise NotImplementedError(self.save_changes)
 
3046
 
2964
3047
    def external_url(self):
2965
3048
        raise NotImplementedError(self.external_url)
2966
3049
 
3041
3124
 
3042
3125
    def unload(self):
3043
3126
        self._config_obj = None
 
3127
        self.dirty_sections = []
3044
3128
 
3045
3129
    def _load_content(self):
3046
3130
        """Load the config file bytes.
3087
3171
        except UnicodeDecodeError:
3088
3172
            raise errors.ConfigContentError(self.external_url())
3089
3173
 
 
3174
    def save_changes(self):
 
3175
        if not self.is_loaded():
 
3176
            # Nothing to save
 
3177
            return
 
3178
        if not self._need_saving():
 
3179
            return
 
3180
        # Preserve the current version
 
3181
        current = self._config_obj
 
3182
        dirty_sections = list(self.dirty_sections)
 
3183
        self.apply_changes(dirty_sections)
 
3184
        # Save to the persistent storage
 
3185
        self.save()
 
3186
 
3090
3187
    def save(self):
3091
3188
        if not self.is_loaded():
3092
3189
            # Nothing to save
3127
3224
            section = self._config_obj
3128
3225
        else:
3129
3226
            section = self._config_obj.setdefault(section_id, {})
3130
 
        return self.mutable_section_class(section_id, section)
 
3227
        mutable_section = self.mutable_section_class(section_id, section)
 
3228
        # All mutable sections can become dirty
 
3229
        self.dirty_sections.append(mutable_section)
 
3230
        return mutable_section
3131
3231
 
3132
3232
    def quote(self, value):
3133
3233
        try: