source: anuga_core/source/anuga/utilities/system_tools.py @ 7099

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

Merge from numpy branch.

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