Ticket #8623: 8623-atomic-config-updates-r8631.2.patch
| File 8623-atomic-config-updates-r8631.2.patch, 8.9 KB (added by rblank, 3 years ago) |
|---|
-
trac/admin/console.py
diff --git a/trac/admin/console.py b/trac/admin/console.py
a b 32 32 from trac.env import Environment 33 33 from trac.perm import PermissionSystem 34 34 from trac.ticket.model import * 35 from trac.util import getuser35 from trac.util import WindowsError, getuser 36 36 from trac.util.datefmt import parse_date, format_date, format_datetime, utc 37 37 from trac.util.html import html 38 38 from 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 54 54 return expected 55 55 56 56 57 class InMemoryConfiguration(Configuration): 58 """A subclass of Configuration that doesn't save to disk.""" 59 def save(self): 60 pass 61 62 57 63 class InMemoryEnvironment(Environment): 58 64 """ 59 65 A subclass of Environment that keeps its' DB in memory. … … 79 85 cls.__module__.find('.tests.') == -1 80 86 81 87 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) 86 89 87 90 88 91 class TracadminTestCase(unittest.TestCase): -
trac/config.py
diff --git a/trac/config.py b/trac/config.py
a b 14 14 15 15 from ConfigParser import ConfigParser 16 16 from copy import deepcopy 17 import os 17 import os.path 18 18 19 19 from trac.core import ExtensionPoint, TracError 20 from trac.util import AtomicFile 20 21 from trac.util.compat import set, sorted 21 22 from trac.util.text import to_unicode, CRLF 22 23 from trac.util.translation import _ … … 208 209 209 210 # At this point, all the strings in `sections` are UTF-8 encoded `str` 210 211 try: 211 fileobj = open(self.filename, 'w')212 fileobj = AtomicFile(self.filename, 'w') 212 213 try: 213 214 fileobj.write('# -*- coding: utf-8 -*-\n\n') 214 215 for section, options in sections: -
trac/env.py
diff --git a/trac/env.py b/trac/env.py
a b 28 28 from trac.core import Component, ComponentManager, implements, Interface, \ 29 29 ExtensionPoint, TracError 30 30 from trac.db import DatabaseManager 31 from trac.util import get_pkginfo31 from trac.util import create_file, get_pkginfo 32 32 from trac.util.text import exception_to_unicode 33 33 from trac.util.translation import _ 34 34 from trac.versioncontrol import RepositoryManager … … 299 299 If options contains ('inherit', 'file'), default values will not be 300 300 loaded; they are expected to be provided by that file or other options. 301 301 """ 302 def _create_file(fname, data=None):303 fd = open(fname, 'w')304 if data:305 fd.write(data)306 fd.close()307 308 302 # Create the directory structure 309 303 if not os.path.exists(self.path): 310 304 os.mkdir(self.path) … … 313 307 os.mkdir(os.path.join(self.path, 'plugins')) 314 308 315 309 # 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') 321 315 322 316 # Setup the default configuration 323 317 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')) 325 320 skip_defaults = options and ('inherit', 'file') in [(section, option) \ 326 321 for (section, option, value) in options] 327 322 self.setup_config(load_defaults=not skip_defaults) … … 526 521 527 522 def _update_sample_config(self): 528 523 filename = os.path.join(self.env.path, 'conf', 'trac.ini.sample') 524 if not os.path.isfile(filename): 525 return 529 526 config = Configuration(filename) 530 527 for section, default_options in config.defaults().iteritems(): 531 528 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 19 19 20 20 import errno 21 21 import locale 22 import os 22 import os.path 23 23 import re 24 24 import sys 25 25 import time … … 77 77 78 78 # -- os utilities 79 79 80 try: 81 WindowsError = WindowsError 82 except NameError: 83 class WindowsError(OSError): 84 """Dummy exception replacing WindowsError on non-Windows platforms""" 85 86 87 if os.name == 'nt': 88 try: 89 import ctypes 90 MOVEFILE_REPLACE_EXISTING = 0x1 91 MOVEFILE_WRITE_THROUGH = 0x8 92 93 try: 94 MoveFileTransacted = ctypes.kernel32.MoveFileTransactedA 95 CreateTransaction = ctypes.windll.ktmw32.CreateTransaction 96 CommitTransaction = ctypes.windll.ktmw32.CommitTransaction 97 CloseHandle = ctypes.windll.kernel32.CloseHandle 98 99 def rename(src, dst): 100 ta = CreateTransaction(None, 0, 0, 0, 0, 1000, 101 'Rename "%s" to "%s"' % (src, dst)) 102 try: 103 if not MoveFileTransacted(src, dst, None, None, 104 MOVEFILE_REPLACE_EXISTING 105 | MOVEFILE_WRITE_THROUGH, ta): 106 raise ctypes.WinError() 107 if not CommitTransaction(ta): 108 raise ctypes.WinError() 109 finally: 110 CloseHandle(ta) 111 except AttributeError: 112 MoveFileEx = ctypes.windll.kernel32.MoveFileExA 113 114 def rename(src, dst): 115 if not MoveFileEx(src, dst, MOVEFILE_REPLACE_EXISTING 116 | MOVEFILE_WRITE_THROUGH): 117 raise ctypes.WinError() 118 except Exception: 119 import random 120 121 def rename(src, dst): 122 try: 123 os.rename(src, dst) 124 except WindowsError, e: 125 if e.errno != errno.EEXIST: 126 raise 127 old = "%s-%08x" % (dst, random.randint(0, 0xffffffff)) 128 os.rename(dst, old) 129 os.rename(src, dst) 130 try: 131 os.unlink(old) 132 except Exception: 133 pass 134 else: 135 rename = os.rename 136 137 138 class AtomicFile(object): 139 """A file that appears atomically with its full content. 140 141 This file-like object writes to a temporary file in the same directory 142 as the final file. If the file is committed, the temporary file is renamed 143 atomically (on Unix, at least) to its final name. If it is rolled back, 144 the temporary file is removed. 145 """ 146 def __init__(self, path, mode='w', bufsize=-1): 147 self._path = path 148 (dir, name) = os.path.split(path) 149 (fd, self._temp) = tempfile.mkstemp(prefix=name + '-', dir=dir) 150 self._file = os.fdopen(fd, mode, bufsize) 151 152 # Try to preserve permissions and group ownership, but failure 153 # should not be fatal 154 try: 155 st = os.stat(path) 156 if hasattr(os, 'chmod'): 157 os.chmod(self._temp, st.st_mode) 158 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'): 159 os.chflags(self._temp, st.st_flags) 160 if hasattr(os, 'chown'): 161 os.chown(self._temp, -1, st.st_gid) 162 except OSError: 163 pass 164 165 def __getattr__(self, name): 166 return getattr(self._file, name) 167 168 def commit(self): 169 if self._file is None: 170 return 171 try: 172 f, self._file = self._file, None 173 f.close() 174 rename(self._temp, self._path) 175 except Exception: 176 os.unlink(self._temp) 177 raise 178 179 def rollback(self): 180 if self._file is None: 181 return 182 try: 183 f, self._file = self._file, None 184 f.close() 185 finally: 186 try: 187 os.unlink(self._temp) 188 except: 189 pass 190 191 close = commit 192 __del__ = rollback 193 194 195 def create_file(path, data='', mode='w'): 196 """Create a new file with the given data.""" 197 f = open(path, mode) 198 try: 199 if data: 200 f.write(data) 201 finally: 202 f.close() 203 204 80 205 def create_unique_file(path): 81 206 """Create a new file. An index is added if the path exists""" 82 207 parts = os.path.splitext(path)
