1
2
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
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
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
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
109 else:
110 self.w, self.r = 1, 0
111 self.reload_files()
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 ] )
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()
125 self.writer.write( x )
126 - def read( self, len = 8192 ):
127 return self.reader.read( len )
131
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
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
181 result = cache_func()
182 with file(cache_path, 'w') as f: dump(result, f)
183 return result
184 else:
185
186 with file(cache_path) as f: return load(f)
187
189 f = file(path)
190 try: return f.read()
191 finally: f.close()
192
194 f = file(path,'w')
195 try: f.write(contents)
196 finally: f.close()
197
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
206