source: branches/numpy/anuga/utilities/system_tools.py @ 6822

Last change on this file since 6822 was 6822, checked in by rwilson, 16 years ago

Back-ported changes to getting of web files.

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