Edgewall Software

Ticket #2304: 2304-proposed-changes-r8038.patch

File 2304-proposed-changes-r8038.patch, 7.5 KB (added by rblank, 3 years ago)

Proposed changes against [8038] as per comment:12.

  • trac/db/mysql_backend.py

    diff --git a/trac/db/mysql_backend.py b/trac/db/mysql_backend.py
    a b  
    1515# history and logs, available at http://trac.edgewall.org/log/. 
    1616 
    1717import re, sys, os, time 
     18from subprocess import call 
    1819 
    1920from trac.core import * 
    2021from trac.config import Option 
    2122from trac.db.api import IDatabaseConnector, _parse_db_str 
    2223from trac.db.util import ConnectionWrapper 
    23 from trac.util import get_pkginfo 
    24 from trac.util.translation import _ 
    25 from subprocess import Popen, PIPE 
     24from trac.util import Null, get_pkginfo 
    2625from trac.util.compat import close_fds 
    2726 
    2827_like_escape_re = re.compile(r'([/_%])') 
     
    5958 
    6059    implements(IDatabaseConnector) 
    6160 
    62     dump_bin = Option('trac', 'mysqldump_bin', 'mysqldump', 
     61    mysqldump_path = Option('trac', 'mysqldump_path', 'mysqldump', 
    6362        """Location of mysqldump for MySQL database backups""") 
    6463 
    6564    def __init__(self): 
     
    148147                  '_'.join(index.columns), table.name, 
    149148                  self._collist(table, index.columns)) 
    150149 
    151  
    152150    def backup(self, dest_file): 
    153         # msyqldump -n schemaname dbname | gzip > filename.gz 
    154151        db_url = self.env.config.get('trac', 'database') 
    155152        scheme, db_prop = _parse_db_str(db_url) 
    156153        db_name = os.path.basename(db_prop['path']) 
    157         args = [self.dump_bin,  
     154        args = [self.mysqldump_path,  
    158155                '-u%s' % db_prop['user'], 
    159156                '-h%s' % db_prop['host']] 
    160157        if db_prop['port']: 
    161158            args.append('-P%s' % str(db_prop['port'])) 
    162159        args.append(db_name) 
    163160         
    164         args.extend(['>', dest_file]) 
    165         if sys.platform == 'win': 
    166             # XXX TODO verify on windows 
    167             args = ['cmd', '/c', ' '.join(args)] 
    168         else: 
    169             args = ['bash', '-c', ' '.join(args)] 
    170          
    171161        environ = os.environ.copy() 
    172162        environ['MYSQL_PWD'] = db_prop['password'] 
    173         #print >> sys.stderr, "backup command %r" % (args,) 
    174         #print >> sys.stderr, "backup props %r" % (db_prop,) 
    175         #print >> sys.stderr, "backup to %s" % dest_file 
    176         p = Popen(args, env=environ, shell=False, bufsize=0, stdin=None, 
    177                   stdout=PIPE, stderr=PIPE, close_fds=close_fds) 
    178         err = p.wait() 
    179         if err: 
    180             raise TracError("Backup attempt exited with error code %s." % err) 
    181         p.stdout.close() 
    182         p.stderr.close() 
    183         if not os.path.exists(dest_file): 
    184             raise TracError("Backup attempt failed") 
     163        out = open(dest_file, "wb") 
     164        try: 
     165            ret = call(args, env=environ, shell=False, bufsize=0, stdin=None, 
     166                       stdout=out, stderr=Null(), close_fds=close_fds) 
     167        finally: 
     168            out.close() 
     169        if ret != 0: 
     170            raise TracError("Backup attempt failed " 
     171                            "(mysqldump return code: %d)" % ret) 
    185172        return dest_file 
    186173 
    187174class MySQLConnection(ConnectionWrapper): 
  • trac/db/postgres_backend.py

    diff --git a/trac/db/postgres_backend.py b/trac/db/postgres_backend.py
    a b  
    1515# Author: Christopher Lenz <cmlenz@gmx.de> 
    1616 
    1717import re, sys, os, time 
     18from subprocess import call 
    1819 
    1920from trac.core import * 
    2021from trac.config import Option 
    2122from trac.db.api import IDatabaseConnector, _parse_db_str 
    2223from trac.db.util import ConnectionWrapper 
    23 from trac.util import get_pkginfo 
    24 from subprocess import Popen, PIPE 
     24from trac.util import Null, get_pkginfo 
    2525from trac.util.compat import close_fds 
    2626 
    2727psycopg = None 
     
    3636 
    3737    implements(IDatabaseConnector) 
    3838 
    39     pg_dump_bin = Option('trac', 'pg_dump_bin', 'pg_dump', 
     39    pg_dump_path = Option('trac', 'pg_dump_path', 'pg_dump', 
    4040        """Location of pg_dump for Postgres database backups""") 
    41     compression = Option('trac', 'backup_compression', '8', 
    42         """Gzip backup compression level (if supported by backend)""") 
    4341 
    4442    def __init__(self): 
    4543        self._version = None 
     
    102100                   '_'.join(index.columns), table.name, '","'.join(index.columns)) 
    103101 
    104102    def backup(self, dest_file): 
    105         # pg_dump -n schemaname dbname | gzip > filename.gz 
    106103        db_url = self.env.config.get('trac', 'database') 
    107104        scheme, db_prop = _parse_db_str(db_url) 
    108105        db_name = os.path.basename(db_prop['path']) 
    109         args = [self.pg_dump_bin, '-C', '-d', '-x', '-Z', self.compression, 
     106        args = [self.pg_dump_path, '-C', '-d', '-x', '-Z', '8', 
    110107                '-U', db_prop['user'],] 
    111108        port = db_prop.get('port', '5432') 
    112109        if 'host' in db_prop['params']: 
     
    124121        else: 
    125122            args.extend([db_name]) 
    126123 
    127         dest_file = "%s.gz" % (dest_file,) 
    128         args.extend(['>', dest_file]) 
    129         if sys.platform == 'win': 
    130             # XXX TODO verify on windows 
    131             args = ['cmd', '/c', ' '.join(args)] 
    132         else: 
    133             args = ['bash', '-c', ' '.join(args)] 
    134          
     124        dest_file += ".gz" 
    135125        environ = os.environ.copy() 
    136126        if 'password' in db_prop: 
    137127            environ['PGPASSWORD'] = db_prop['password'] 
    138         p = Popen(args, env=environ, shell=False, bufsize=0, stdin=None, 
    139                   stdout=PIPE, stderr=PIPE, close_fds=close_fds) 
    140         err = p.wait() 
    141         if err: 
    142             raise TracError("Backup attempt exited with error code %s." % err) 
    143         p.stdout.close() 
    144         p.stderr.close() 
    145         if not os.path.exists(dest_file): 
    146             raise TracError("Backup attempt failed") 
     128        out = open(dest_file, "wb") 
     129        try: 
     130            ret = call(args, env=environ, shell=False, bufsize=0, stdin=None, 
     131                       stdout=out, stderr=Null(), close_fds=close_fds) 
     132        finally: 
     133            out.close() 
     134        if ret != 0: 
     135            raise TracError("Backup attempt failed " 
     136                            "(pg_dump return code: %d)" % ret) 
    147137        return dest_file 
    148138 
    149139 
  • trac/util/__init__.py

    diff --git a/trac/util/__init__.py b/trac/util/__init__.py
    a b  
    542542        else: 
    543543            return 0 
    544544 
     545 
     546class Null(object): 
     547    """A class that does nothing, but does it well""" 
     548    def __new__(cls, *args, **kwargs): 
     549        try: 
     550            return cls._inst 
     551        except AttributeError: 
     552            cls._inst = object.__new__(cls, *args, **kwargs) 
     553            return cls._inst 
     554     
     555    def __init__(self, *args, **kwargs): 
     556        pass 
     557     
     558    def __call__(self, *args, **kwargs): 
     559        return self 
     560     
     561    def __repr__(self): 
     562        return 'Null()' 
     563     
     564    def __str__(self): 
     565        return '' 
     566     
     567    def __eq__(self, other): 
     568        return self is other 
     569     
     570    def __hash__(self): 
     571        return hash(None) 
     572     
     573    def __len__(self): 
     574        return 0 
     575     
     576    def __iter__(self): 
     577        return iter(()) 
     578     
     579    def __contains__(self, item): 
     580        return False 
     581     
     582    __getattr__ = __setattr__ = __delattr__ \ 
     583        = __getitem__ = __setitem__ = __delitem__ \ 
     584        = __add__ = __sub__ = __mul__ = __div__ = __truediv__ \ 
     585        = __floordiv__ = __mod__ = __divmod__ = __pow__ \ 
     586        = __lshift__ = __rshift__ = __and__ = __xor__ = __or__ \ 
     587        = __radd__ = __rsub__ = __rmul__ = __rdiv__ = __rtruediv__ \ 
     588        = __rfloordiv__ = __rmod__ = __rdivmod__ = __rpow__ \ 
     589        = __rlshift__ = __rrshift__ = __rand__ = __rxor__ = __ror__ \ 
     590        = __call__ 
     591 
     592 
    545593def content_disposition(type, filename=None): 
    546594    """Generate a properly escaped Content-Disposition header""" 
    547595    if isinstance(filename, unicode):