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 getuser |
| | 35 | 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, \ |
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): |
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 | import tempfile |
| 18 | 19 | |
| 19 | 20 | from trac.core import ExtensionPoint, TracError |
| | 21 | from trac.util import rename |
| 20 | 22 | from trac.util.compat import set, sorted |
| 21 | 23 | from trac.util.text import to_unicode, CRLF |
| 22 | 24 | from trac.util.translation import _ |
| … |
… |
|
| 208 | 210 | |
| 209 | 211 | # At this point, all the strings in `sections` are UTF-8 encoded `str` |
| 210 | 212 | 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") |
| 212 | 216 | try: |
| 213 | 217 | fileobj.write('# -*- coding: utf-8 -*-\n\n') |
| 214 | 218 | for section, options in sections: |
| … |
… |
|
| 223 | 227 | fileobj.write('\n') |
| 224 | 228 | finally: |
| 225 | 229 | 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) |
| 226 | 245 | self._old_sections = deepcopy(self.parser._sections) |
| 227 | 246 | except Exception: |
| 228 | 247 | # Revert all changes to avoid inconsistencies |
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_pkginfo |
| | 31 | 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(): |
diff --git a/trac/util/__init__.py b/trac/util/__init__.py
|
a
|
b
|
|
| 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 | def 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 | |
| | 104 | def 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 | |
| 80 | 114 | def create_unique_file(path): |
| 81 | 115 | """Create a new file. An index is added if the path exists""" |
| 82 | 116 | parts = os.path.splitext(path) |