Edgewall Software

Ticket #8623: 8623-atomic-config-updates-r8625.patch

File 8623-atomic-config-updates-r8625.patch, 6.6 KB (added by rblank, 3 years ago)

Patch adding atomic config updates.

  • trac/admin/console.py

    diff --git a/trac/admin/console.py b/trac/admin/console.py
    a b  
    3232from trac.env import Environment 
    3333from trac.perm import PermissionSystem 
    3434from trac.ticket.model import * 
    35 from trac.util import getuser 
     35from trac.util import WindowsError, getuser 
    3636from trac.util.datefmt import parse_date, format_date, format_datetime, utc 
    3737from trac.util.html import html 
    3838from trac.util.text import to_unicode, wrap, unicode_quote, unicode_unquote, \ 
  • trac/admin/tests/console.py

    diff --git a/trac/admin/tests/console.py b/trac/admin/tests/console.py
    a b  
    5454    return expected 
    5555 
    5656 
     57class InMemoryConfiguration(Configuration): 
     58    """A subclass of Configuration that doesn't save to disk.""" 
     59    def save(self): 
     60        pass 
     61 
     62 
    5763class InMemoryEnvironment(Environment): 
    5864    """ 
    5965    A subclass of Environment that keeps its' DB in memory. 
     
    7985               cls.__module__.find('.tests.') == -1 
    8086 
    8187    def setup_config(self, load_defaults=None): 
    82         self.config = Configuration(None) 
    83  
    84     def save_config(self): 
    85         pass 
     88        self.config = InMemoryConfiguration(None) 
    8689 
    8790 
    8891class TracadminTestCase(unittest.TestCase): 
  • trac/config.py

    diff --git a/trac/config.py b/trac/config.py
    a b  
    1414 
    1515from ConfigParser import ConfigParser 
    1616from copy import deepcopy 
    17 import os 
     17import os.path 
     18import tempfile 
    1819 
    1920from trac.core import ExtensionPoint, TracError 
     21from trac.util import rename 
    2022from trac.util.compat import set, sorted 
    2123from trac.util.text import to_unicode, CRLF 
    2224from trac.util.translation import _ 
     
    208210 
    209211        # At this point, all the strings in `sections` are UTF-8 encoded `str` 
    210212        try: 
    211             fileobj = open(self.filename, 'w') 
     213            (fd, path) = tempfile.mkstemp(prefix='trac.ini.', 
     214                                          dir=os.path.dirname(self.filename)) 
     215            fileobj = os.fdopen(fd, "w") 
    212216            try: 
    213217                fileobj.write('# -*- coding: utf-8 -*-\n\n') 
    214218                for section, options in sections: 
     
    223227                    fileobj.write('\n') 
    224228            finally: 
    225229                fileobj.close() 
     230             
     231            # Try to preserve group ownership and permissions, but failure 
     232            # should not be fatal 
     233            try: 
     234                st = os.stat(self.filename) 
     235                if hasattr(os, 'chmod'): 
     236                    os.chmod(path, st.st_mode) 
     237                if hasattr(os, 'chflags') and hasattr(st, 'st_flags'): 
     238                    os.chflags(dst, st.st_flags) 
     239                if hasattr(os, 'chown'): 
     240                    os.chown(path, -1, st.st_gid) 
     241            except OSError: 
     242                pass 
     243             
     244            rename(path, self.filename, duration=2.0) 
    226245            self._old_sections = deepcopy(self.parser._sections) 
    227246        except Exception: 
    228247            # Revert all changes to avoid inconsistencies 
  • trac/env.py

    diff --git a/trac/env.py b/trac/env.py
    a b  
    2828from trac.core import Component, ComponentManager, implements, Interface, \ 
    2929                      ExtensionPoint, TracError 
    3030from trac.db import DatabaseManager 
    31 from trac.util import get_pkginfo 
     31from trac.util import create_file, get_pkginfo 
    3232from trac.util.text import exception_to_unicode 
    3333from trac.util.translation import _ 
    3434from trac.versioncontrol import RepositoryManager 
     
    299299        If options contains ('inherit', 'file'), default values will not be 
    300300        loaded; they are expected to be provided by that file or other options. 
    301301        """ 
    302         def _create_file(fname, data=None): 
    303             fd = open(fname, 'w') 
    304             if data: 
    305                 fd.write(data) 
    306             fd.close() 
    307  
    308302        # Create the directory structure 
    309303        if not os.path.exists(self.path): 
    310304            os.mkdir(self.path) 
     
    313307        os.mkdir(os.path.join(self.path, 'plugins')) 
    314308 
    315309        # Create a few files 
    316         _create_file(os.path.join(self.path, 'VERSION'), 
    317                      'Trac Environment Version 1\n') 
    318         _create_file(os.path.join(self.path, 'README'), 
    319                      'This directory contains a Trac environment.\n' 
    320                      'Visit http://trac.edgewall.org/ for more information.\n') 
     310        create_file(os.path.join(self.path, 'VERSION'), 
     311                    'Trac Environment Version 1\n') 
     312        create_file(os.path.join(self.path, 'README'), 
     313                    'This directory contains a Trac environment.\n' 
     314                    'Visit http://trac.edgewall.org/ for more information.\n') 
    321315 
    322316        # Setup the default configuration 
    323317        os.mkdir(os.path.join(self.path, 'conf')) 
    324         _create_file(os.path.join(self.path, 'conf', 'trac.ini')) 
     318        create_file(os.path.join(self.path, 'conf', 'trac.ini')) 
     319        create_file(os.path.join(self.path, 'conf', 'trac.ini.sample')) 
    325320        skip_defaults = options and ('inherit', 'file') in [(section, option) \ 
    326321                for (section, option, value) in options] 
    327322        self.setup_config(load_defaults=not skip_defaults) 
     
    526521 
    527522    def _update_sample_config(self): 
    528523        filename = os.path.join(self.env.path, 'conf', 'trac.ini.sample') 
     524        if not os.path.isfile(filename): 
     525            return 
    529526        config = Configuration(filename) 
    530527        for section, default_options in config.defaults().iteritems(): 
    531528            for name, value in default_options.iteritems(): 
  • trac/util/__init__.py

    diff --git a/trac/util/__init__.py b/trac/util/__init__.py
    a b  
    7777 
    7878# -- os utilities 
    7979 
     80try: 
     81    WindowsError = WindowsError 
     82except NameError: 
     83    class WindowsError(OSError): 
     84        """Dummy exception replacing WindowsError on non-Windows platforms""" 
     85 
     86 
     87def rename(src, dst, duration=1.0, retry_delay=0.1): 
     88    """Rename `src` to `dst`. 
     89     
     90    Windows raises a WindowsError if the destination of a rename is open (even 
     91    if only for reading). Retry for the given duration if this is the case. 
     92    """ 
     93    end = time.time() + duration 
     94    while True: 
     95        try: 
     96            os.rename(src, dst) 
     97            break 
     98        except WindowsError: 
     99            if time.time() >= end: 
     100                raise 
     101            time.sleep(retry_delay) 
     102 
     103 
     104def create_file(path, data=""): 
     105    """Create a new file with the given data.""" 
     106    f = open(path, 'w') 
     107    try: 
     108        if data: 
     109            f.write(data) 
     110    finally: 
     111        f.close() 
     112 
     113 
    80114def create_unique_file(path): 
    81115    """Create a new file. An index is added if the path exists""" 
    82116    parts = os.path.splitext(path)