source: trunk/anuga_core/source/anuga/utilities/system_tools.py @ 8272

Last change on this file since 8272 was 8145, checked in by wilsonr, 14 years ago

Removed '@brief' comments.

File size: 16.8 KB
Line 
1"""Implementation of tools to do with system administration made as platform independent as possible.
2
3
4"""
5
6import sys
7import os
8import string
9import urllib
10import urllib2
11import getpass
12import tarfile
13import warnings
14
15try:
16    import hashlib
17except ImportError:
18    import md5 as hashlib
19
20import anuga.utilities.log as log
21
22
23def log_to_file(filename, s, verbose=False, mode='a'):
24    """Log string to file name
25    """
26
27    fid = open(filename, mode)
28    if verbose: print s
29    fid.write(s + '\n')
30    fid.close()
31
32
33def get_user_name():
34    """Get user name provide by operating system
35    """
36
37    if sys.platform == 'win32':
38        #user = os.getenv('USERPROFILE')
39        user = os.getenv('USERNAME')
40    else:
41        user = os.getenv('LOGNAME')
42
43
44    return user   
45
46def get_host_name():
47    """Get host name provide by operating system
48    """
49
50    if sys.platform == 'win32':
51        host = os.getenv('COMPUTERNAME')
52    else:
53        host = os.uname()[1]
54
55
56    return host   
57
58   
59   
60   
61   
62def __get_revision_from_svn_entries__():
63    """Get a subversion revision number from the .svn/entries file."""
64
65   
66    msg = '''
67No version info stored and command 'svn' is not recognised on the system PATH.
68
69If ANUGA has been installed from a distribution e.g. as obtained from SourceForge,
70the version info should be available in the automatically generated file
71'stored_version_info.py' in the anuga root directory.
72
73If run from a Subversion sandpit, ANUGA will try to obtain the version info by
74using the command 'svn info'.  In this case, make sure the command line client
75'svn' is accessible on the system path.  Simply aliasing 'svn' to the binary will
76not work.
77
78If you are using Windows, you have to install the file svn.exe which can be
79obtained from http://www.collab.net/downloads/subversion.
80
81Good luck!
82'''
83
84    try:
85        fd = open(os.path.join('.svn', 'entries'))
86    except:
87        raise Exception, msg
88
89    line = fd.readlines()[3]
90    fd.close()
91    try:
92        revision_number = int(line)
93    except:
94        msg = ".svn/entries, line 4 was '%s'?" % line.strip()
95        raise Exception, msg
96
97    return revision_number
98
99def __get_revision_from_svn_client__():
100    """Get a subversion revision number from an svn client."""
101
102    if sys.platform[0:3] == 'win':
103        try:
104            fid = os.popen(r'C:\Program Files\TortoiseSVN\bin\SubWCRev.exe')
105        except:
106            return __get_revision_from_svn_entries__()
107        else:
108            version_info = fid.read()
109            if version_info == '':
110                return __get_revision_from_svn_entries__()
111
112        # split revision number from data
113        for line in version_info.split('\n'):
114            if line.startswith('Updated to revision '):
115                break
116
117        fields = line.split(' ')
118        msg = 'Keyword "Revision" was not found anywhere in text: %s' % version_info
119        assert fields[0].startswith('Updated'), msg
120
121        try:
122            revision_number = int(fields[3])
123        except:
124            msg = ('Revision number must be an integer. I got "%s" from '
125                   '"SubWCRev.exe".' % fields[3])
126            raise Exception, msg
127    else:                   # assume Linux
128        try:
129            fid = os.popen('svn info . 2>/dev/null')
130        except:
131            return __get_revision_from_svn_entries__()
132        else:
133            version_info = fid.read()
134            if version_info == '':
135                return __get_revision_from_svn_entries__()
136
137        # Split revision number from data
138        for line in version_info.split('\n'):
139            if line.startswith('Revision:'):
140                break
141       
142        fields = line.split(':')
143        msg = 'Keyword "Revision" was not found anywhere in text: %s' % version_info
144        assert fields[0].startswith('Revision'), msg
145       
146       
147        #if ':' in version_info:
148        #    revision_number, _ = version_info.split(':', 1)
149        #    msg = ('Some modules have not been checked in. '
150        #           'Using last version from repository: %s' % revision_number)
151        #    warnings.warn(msg)
152        #else:
153        #    revision_number = version_info
154
155        try:
156            revision_number = int(fields[1])
157        except:
158            msg = ("Revision number must be an integer. I got '%s' from "
159                   "'svn'." % fields[1])
160            raise Exception, msg
161
162    return revision_number
163
164   
165   
166   
167   
168def get_revision_number():
169    """Get the version number of this repository copy.
170
171    Try getting data from stored_version_info.py first, otherwise
172    try using SubWCRev.exe (Windows) or svnversion (linux), otherwise
173    try reading file .svn/entries for version information, otherwise
174    throw an exception.
175
176    NOTE: This requires that the command svn is on the system PATH
177    (simply aliasing svn to the binary will not work)
178    """
179
180    # try to get revision information from stored_version_info.py
181    try:
182        from anuga.stored_version_info import version_info
183    except:
184        return __get_revision_from_svn_client__()
185
186    # split revision number from data
187    for line in version_info.split('\n'):
188        if line.startswith('Revision:'):
189            break
190
191    fields = line.split(':')
192    msg = 'Keyword "Revision" was not found anywhere in text: %s' % version_info
193    assert fields[0].startswith('Revision'), msg
194
195    try:
196        revision_number = int(fields[1])
197    except:
198        msg = ("Revision number must be an integer. I got '%s'.\n"
199               'Check that the command svn is on the system path.'
200               % fields[1])
201        raise Exception, msg
202
203    return revision_number
204
205
206def store_version_info(destination_path='.', verbose=False):
207    """Obtain current version from Subversion and store it.
208   
209    Title: store_version_info()
210
211    Author: Ole Nielsen (Ole.Nielsen@ga.gov.au)
212
213    CreationDate: January 2006
214
215    Description:
216        This function obtains current version from Subversion and stores it
217        is a Python file named 'stored_version_info.py' for use with
218        get_version_info()
219
220        If svn is not available on the system PATH, an Exception is thrown
221    """
222
223    # Note (Ole): This function should not be unit tested as it will only
224    # work when running out of the sandpit. End users downloading the
225    # ANUGA distribution would see a failure.
226    #
227    # FIXME: This function should really only be used by developers (
228    # (e.g. for creating new ANUGA releases), so maybe it should move
229    # to somewhere else.
230   
231    import config
232
233    try:
234        fid = os.popen('svn info')
235    except:
236        msg = 'Command "svn" is not recognised on the system PATH'
237        raise Exception(msg)
238    else:   
239        txt = fid.read()
240        fid.close()
241
242
243        # Determine absolute filename
244        if destination_path[-1] != os.sep:
245            destination_path += os.sep
246           
247        filename = destination_path + config.version_filename
248
249        fid = open(filename, 'w')
250
251        docstring = 'Stored version info.\n\n'
252        docstring += 'This file provides the version for distributions '
253        docstring += 'that are not accessing Subversion directly.\n'
254        docstring += 'The file is automatically generated and should not '
255        docstring += 'be modified manually.\n'
256        fid.write('"""%s"""\n\n' %docstring)
257       
258        fid.write('version_info = """\n%s"""' %txt)
259        fid.close()
260
261
262        if verbose is True:
263            log.critical('Version info stored to %s' % filename)
264
265
266def safe_crc(string):
267    """64 bit safe crc computation.
268
269    See http://docs.python.org/library/zlib.html#zlib.crc32:
270
271        To generate the same numeric value across all Python versions
272        and platforms use crc32(data) & 0xffffffff.
273    """
274
275    from zlib import crc32
276
277    return crc32(string) & 0xffffffff
278
279
280def compute_checksum(filename, max_length=2**20):
281    """Compute the CRC32 checksum for specified file
282
283    Optional parameter max_length sets the maximum number
284    of bytes used to limit time used with large files.
285    Default = 2**20 (1MB)
286    """
287
288    fid = open(filename, 'rb') # Use binary for portability
289    crcval = safe_crc(fid.read(max_length))
290    fid.close()
291
292    return crcval
293
294def get_pathname_from_package(package):
295    """Get pathname of given package (provided as string)
296
297    This is useful for reading files residing in the same directory as
298    a particular module. Typically, this is required in unit tests depending
299    on external files.
300
301    The given module must start from a directory on the pythonpath
302    and be importable using the import statement.
303
304    Example
305    path = get_pathname_from_package('anuga.utilities')
306
307    """
308
309    exec('import %s as x' %package)
310
311    path = x.__path__[0]
312   
313    return path
314
315    # Alternative approach that has been used at times
316    #try:
317    #    # When unit test is run from current dir
318    #    p1 = read_polygon('mainland_only.csv')
319    #except:
320    #    # When unit test is run from ANUGA root dir
321    #    from os.path import join, split
322    #    dir, tail = split(__file__)
323    #    path = join(dir, 'mainland_only.csv')
324    #    p1 = read_polygon(path)
325       
326   
327def clean_line(str, delimiter):
328    """Split a string into 'clean' fields.
329
330    str        the string to process
331    delimiter  the delimiter string to split 'line' with
332
333    Returns a list of 'cleaned' field strings.
334
335    Any fields that were initially zero length will be removed.
336    If a field contains '\n' it isn't zero length.
337    """
338
339    return [x.strip() for x in str.strip().split(delimiter) if x != '']
340
341
342################################################################################
343# The following two functions are used to get around a problem with numpy and
344# NetCDF files.  Previously, using Numeric, we could take a list of strings and
345# convert to a Numeric array resulting in this:
346#     Numeric.array(['abc', 'xy']) -> [['a', 'b', 'c'],
347#                                      ['x', 'y', ' ']]
348#
349# However, under numpy we get:
350#     numpy.array(['abc', 'xy']) -> ['abc',
351#                                    'xy']
352#
353# And writing *strings* to a NetCDF file is problematic.
354#
355# The solution is to use these two routines to convert a 1-D list of strings
356# to the 2-D list of chars form and back.  The 2-D form can be written to a
357# NetCDF file as before.
358#
359# The other option, of inverting a list of tag strings into a dictionary with
360# keys being the unique tag strings and the key value a list of indices of where
361# the tag string was in the original list was rejected because:
362#    1. It's a lot of work
363#    2. We'd have to rewite the I/O code a bit (extra variables instead of one)
364#    3. The code below is fast enough in an I/O scenario
365################################################################################
366
367def string_to_char(l):
368    '''Convert 1-D list of strings to 2-D list of chars.'''
369
370    if not l:
371        return []
372
373    if l == ['']:
374        l = [' ']
375
376    maxlen = reduce(max, map(len, l))
377    ll = [x.ljust(maxlen) for x in l]
378    result = []
379    for s in ll:
380        result.append([x for x in s])
381    return result
382
383
384def char_to_string(ll):
385    '''Convert 2-D list of chars to 1-D list of strings.'''
386
387    return map(string.rstrip, [''.join(x) for x in ll])
388
389################################################################################
390
391def get_vars_in_expression(source):
392    '''Get list of variable names in a python expression.'''
393
394    import compiler
395    from compiler.ast import Node
396
397    def get_vars_body(node, var_list=[]):
398        if isinstance(node, Node):
399            if node.__class__.__name__ == 'Name':
400                for child in node.getChildren():
401                    if child not in var_list:
402                        var_list.append(child)
403            for child in node.getChildren():
404                if isinstance(child, Node):
405                    for child in node.getChildren():
406                        var_list = get_vars_body(child, var_list)
407                    break
408
409        return var_list
410
411    return get_vars_body(compiler.parse(source))
412
413
414def get_web_file(file_url, file_name, auth=None, blocksize=1024*1024):
415    '''Get a file from the web (HTTP).
416
417    file_url:  The URL of the file to get
418    file_name: Local path to save loaded file in
419    auth:      A tuple (httpproxy, proxyuser, proxypass)
420    blocksize: Block size of file reads
421   
422    Will try simple load through urllib first.  Drop down to urllib2
423    if there is a proxy and it requires authentication.
424
425    Environment variable HTTP_PROXY can be used to supply proxy information.
426    PROXY_USERNAME is used to supply the authentication username.
427    PROXY_PASSWORD supplies the password, if you dare!
428    '''
429
430    # Simple fetch, if fails, check for proxy error
431    try:
432        urllib.urlretrieve(file_url, file_name)
433        return (True, auth)     # no proxy, no auth required
434    except IOError, e:
435        if e[1] == 407:     # proxy error
436            pass
437        elif e[1][0] == 113:  # no route to host
438            print 'No route to host for %s' % file_url
439            return (False, auth)    # return False
440        else:
441            print 'Unknown connection error to %s' % file_url
442            return (False, auth)
443
444    # We get here if there was a proxy error, get file through the proxy
445    # unpack auth info
446    try:
447        (httpproxy, proxyuser, proxypass) = auth
448    except:
449        (httpproxy, proxyuser, proxypass) = (None, None, None)
450
451    # fill in any gaps from the environment
452    if httpproxy is None:
453        httpproxy = os.getenv('HTTP_PROXY')
454    if proxyuser is None:
455        proxyuser = os.getenv('PROXY_USERNAME')
456    if proxypass is None:
457        proxypass = os.getenv('PROXY_PASSWORD')
458
459    # Get auth info from user if still not supplied
460    if httpproxy is None or proxyuser is None or proxypass is None:
461        print '-'*72
462        print ('You need to supply proxy authentication information.')
463        if httpproxy is None:
464            httpproxy = raw_input('                    proxy server: ')
465        else:
466            print '         HTTP proxy was supplied: %s' % httpproxy
467        if proxyuser is None:
468            proxyuser = raw_input('                  proxy username: ') 
469        else:
470            print 'HTTP proxy username was supplied: %s' % proxyuser
471        if proxypass is None:
472            proxypass = getpass.getpass('                  proxy password: ')
473        else:
474            print 'HTTP proxy password was supplied: %s' % '*'*len(proxyuser)
475        print '-'*72
476
477    # the proxy URL cannot start with 'http://', we add that later
478    httpproxy = httpproxy.lower()
479    if httpproxy.startswith('http://'):
480        httpproxy = httpproxy.replace('http://', '', 1)
481
482    # open remote file
483    proxy = urllib2.ProxyHandler({'http': 'http://' + proxyuser
484                                              + ':' + proxypass
485                                              + '@' + httpproxy})
486    authinfo = urllib2.HTTPBasicAuthHandler()
487    opener = urllib2.build_opener(proxy, authinfo, urllib2.HTTPHandler)
488    urllib2.install_opener(opener)
489    try:
490        webget = urllib2.urlopen(file_url)
491    except urllib2.HTTPError, e:
492        print 'Error received from proxy:\n%s' % str(e)
493        print 'Possibly the user/password is wrong.'
494        return (False, (httpproxy, proxyuser, proxypass))
495
496    # transfer file to local filesystem
497    fd = open(file_name, 'wb')
498    while True:
499        data = webget.read(blocksize)
500        if len(data) == 0:
501            break
502        fd.write(data)
503    fd.close
504    webget.close()
505
506    # return successful auth info
507    return (True, (httpproxy, proxyuser, proxypass))
508
509
510def tar_file(files, tarname):
511    '''Compress a file or directory into a tar file.'''
512
513    if isinstance(files, basestring):
514        files = [files]
515
516    o = tarfile.open(tarname, 'w:gz')
517    for file in files:
518        o.add(file)
519    o.close()
520
521
522def untar_file(tarname, target_dir='.'):
523    '''Uncompress a tar file.'''
524
525    o = tarfile.open(tarname, 'r:gz')
526    members = o.getmembers()
527    for member in members:
528        o.extract(member, target_dir)
529    o.close()
530
531
532def get_file_hexdigest(filename, blocksize=1024*1024*10):
533    '''Get a hex digest of a file.'''
534
535    if hashlib.__name__ == 'hashlib':
536        m = hashlib.md5()       # new - 'hashlib' module
537    else:
538        m = hashlib.new()       # old - 'md5' module - remove once py2.4 gone
539    fd = open(filename, 'r')
540           
541    while True:
542        data = fd.read(blocksize)
543        if len(data) == 0:
544            break
545        m.update(data)
546                                                               
547    fd.close()
548    return m.hexdigest()
549
550
551def make_digest_file(data_file, digest_file):
552    '''Create a file containing the hex digest string of a data file.'''
553   
554    hexdigest = get_file_hexdigest(data_file)
555    fd = open(digest_file, 'w')
556    fd.write(hexdigest)
557    fd.close()
558
559
560def file_length(in_file):
561    '''Function to return the length of a file.'''
562
563    fid = open(in_file)
564    data = fid.readlines()
565    fid.close()
566    return len(data)
567
568
Note: See TracBrowser for help on using the repository browser.