Package commons :: Module files
[hide private]
[frames] | no frames]

Source Code for Module commons.files

  1  # -*- mode: python; tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4; -*- 
  2  # vim:ft=python:et:sw=4:ts=4 
  3   
  4  """ 
  5  File and directory manipulation. 
  6   
  7  @var invalid_filename_chars: The characters which are usually 
  8  prohibited on most modern file systems. 
  9   
 10  @var invalid_filename_chars_regex: A regex character class constructed 
 11  from L{invalid_filename_chars}. 
 12  """ 
 13   
 14  from __future__ import with_statement 
 15   
 16  __all__ = ''' 
 17  soft_makedirs 
 18  temp_dir 
 19  cleanse_filename 
 20  invalid_filename_chars 
 21  invalid_filename_chars_regex 
 22  disk_double_buffer 
 23  versioned_guard 
 24  versioned_cache 
 25  read_file 
 26  write_file 
 27  write_or_rm 
 28  is_nonempty_file 
 29  '''.split() 
 30   
 31  import os, re, tempfile 
 32  from cPickle import * 
 33  from commons.path import path 
 34   
35 -def soft_makedirs( path ):
36 """ 37 Emulate C{mkdir -p} (doesn't complain if it already exists). 38 39 @param path: The path of the directory to create. 40 @type path: str 41 42 @raise OSError: If it cannot create the directory. It only 43 swallows OS error 17. 44 """ 45 try: 46 os.makedirs( path ) 47 except OSError, ex: 48 if ex.errno == 17: 49 pass 50 else: 51 raise
52
53 -def temp_dir( base_dir_name, do_create_subdir = True ):
54 """ 55 Get a temporary directory without polluting top-level /tmp. This follows 56 Ubuntu's conventions, choosing a temporary directory name based on 57 the given name plus the user name to avoid user conflicts. 58 59 @param base_dir_name: The "name" of the temporary directory. This 60 is usually identifies the purpose of the directory, or the 61 application to which the temporary directory belongs. E.g., if joe 62 calls passes in C{"ssh-agent"} on a standard Linux/Unix system, 63 then the full path of the temporary directory will be 64 C{"/tmp/ssh-agent-joe"}. 65 @type base_dir_name: str 66 67 @param do_create_subdir: If C{True}, then creates a 68 sub-sub-directory within the temporary sub-directory (and returns 69 the path to that). The sub-sub-directory's name is randomized 70 (uses C{tempfile.mkdtemp}). 71 @type do_create_subdir: bool 72 73 @return: The path to the temporary (sub-)sub-directory. 74 @rtype: str 75 """ 76 base_dir_name += '-' + os.environ[ 'USER' ] 77 base_dir = path( tempfile.gettempdir() ) / base_dir_name 78 soft_makedirs( base_dir ) 79 if do_create_subdir: 80 return tempfile.mkdtemp( dir = base_dir ) 81 else: 82 return base_dir
83 84 invalid_filename_chars = r'*|\/:<>?' 85 invalid_filename_chars_regex = r'[*|\\\/:<>?]' 86
87 -def cleanse_filename( filename ):
88 """ 89 Replaces all problematic characters in a filename with C{"_"}, as 90 specified by L{invalid_filename_chars}. 91 92 @param filename: The filename to cleanse. 93 @type filename: str 94 """ 95 pattern = invalid_filename_chars_regex 96 return re.sub( pattern, '_', filename )
97
98 -class disk_double_buffer( object ):
99 """ 100 A simple disk double-buffer. One file is for reading, the other is for 101 writing, and a facility for swapping the two roles is provided. 102 """
103 - def __init__( self, path_base, do_persist = True ):
104 self.paths = map( path, [ path_base + '.0', path_base + '.1' ] ) 105 self.do_persist = do_persist 106 self.switch_status = path( path_base + '.switched' ) 107 if not do_persist or not self.switch_status.exists(): 108 self.w, self.r = 0, 1 # default 109 else: 110 self.w, self.r = 1, 0 111 self.reload_files()
112 - def reload_files( self ):
113 self.writer = file( self.paths[ self.w ], 'w' ) 114 if not self.paths[ self.r ].exists(): 115 self.paths[ self.r ].touch() 116 self.reader = file( self.paths[ self.r ] )
117 - def switch( self ):
118 self.close() 119 if self.do_persist: 120 if self.w == 0: self.switch_status.touch() 121 else: self.switch_status.remove() 122 self.r, self.w = self.w, self.r 123 self.reload_files()
124 - def write( self, x ):
125 self.writer.write( x )
126 - def read( self, len = 8192 ):
127 return self.reader.read( len )
128 - def close( self ):
129 self.reader.close() 130 self.writer.close()
131
132 -def versioned_guard(path, fresh_version):
133 """ 134 Maintain a version object. This is useful for working with versioned 135 caches. 136 137 @param path: The path to the file containing the cached version object. 138 @type path: str 139 140 @param fresh_version: The actual latest version that the cached version 141 should be compared against. 142 @type fresh_version: object (any type that can be compared) 143 144 @return: True iff the cached version is obsolete (less than the fresh 145 version or doesn't exist). 146 @rtype: bool 147 """ 148 cache_version = None 149 try: 150 with file( path ) as f: cache_version = load(f) 151 except IOError, (errno, errstr): 152 if errno != 2: raise 153 if cache_version is None or fresh_version > cache_version: 154 with file( path, 'w' ) as f: dump(fresh_version, f) 155 return True 156 else: 157 return False
158
159 -def versioned_cache(version_path, fresh_version, cache_path, cache_func):
160 """ 161 If fresh_version is newer than the version in version_path, then invoke 162 cache_func and cache the result in cache_path (using pickle). 163 164 Note the design flaw with L{versioned_guard}: the updated version value is 165 stored immediately, rather than after updating the cache. 166 167 @param version_path: The path to the file version. 168 @type version_path: str 169 170 @param fresh_version: The actual, up-to-date version value. 171 @type fresh_version: object (any type that can be compared) 172 173 @param cache_path: The path to the cached data. 174 @type cache_path: str 175 176 @param cache_func: The function that produces the fresh data to be cached. 177 @type cache_func: function (no arguments) 178 """ 179 if versioned_guard( version_path, fresh_version ): 180 # cache obsolete, force-fetch new data 181 result = cache_func() 182 with file(cache_path, 'w') as f: dump(result, f) 183 return result 184 else: 185 # cache up-to-date (should be available since dlcs-timestamp exists!) 186 with file(cache_path) as f: return load(f)
187
188 -def read_file(path):
189 f = file(path) 190 try: return f.read() 191 finally: f.close()
192
193 -def write_file(path, contents):
194 f = file(path,'w') 195 try: f.write(contents) 196 finally: f.close()
197
198 -def write_or_rm(p, contents):
199 'Write the file or remove it if contents is empty.' 200 p = path(p) 201 if contents.strip(): write_file(p, contents) 202 elif p.isfile(): p.remove()
203
204 -def is_nonempty_file(path):
205 return path.isfile() and read_file(path).strip() != ''
206