Package gofer :: Module pmon
[hide private]
[frames] | no frames]

Source Code for Module gofer.pmon

  1  # 
  2  # Copyright (c) 2011 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU Lesser General Public 
  5  # License as published by the Free Software Foundation; either version 
  6  # 2 of the License (LGPLv2) or (at your option) any later version. 
  7  # There is NO WARRANTY for this software, express or implied, 
  8  # including the implied warranties of MERCHANTABILITY, 
  9  # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should 
 10  # have received a copy of LGPLv2 along with this software; if not, see 
 11  # http://www.gnu.org/licenses/old-licenses/lgpl-2.0.txt. 
 12  # 
 13  # Jeff Ortel <jortel@redhat.com> 
 14  # 
 15  """ 
 16  Provides path and process monitoring classes. 
 17  """ 
 18   
 19  import os 
 20  import errno 
 21  from hashlib import sha256 
 22  from time import sleep 
 23  from threading import Thread, RLock 
 24  from logging import getLogger 
 25   
 26  log = getLogger(__name__) 
 27   
 28   
29 -class PathMonitor:
30 """ 31 Path monitor. 32 @ivar __paths: A list of paths to monitor. 33 @type __paths: dict path:(mtime, digest, cb) 34 @ivar __mutex: The mutex. 35 @type __mutex: RLock 36 @ivar __thread: The optional thread. see: start(). 37 @type __thread: Thread 38 """ 39
40 - def __init__(self):
41 self.__paths = {} 42 self.__mutex = RLock() 43 self.__thread = None
44
45 - def add(self, path, cb):
46 """ 47 Add a path to be monitored. 48 @param path: An absolute path to monitor. 49 @type path: str 50 @param cb: A listener. 51 @type cb: callable 52 """ 53 self.__lock() 54 try: 55 self.__paths[path] = [0, None, cb] 56 finally: 57 self.__unlock()
58
59 - def delete(self, path):
60 """ 61 Delete a path to be monitored. 62 @param path: An absolute path to monitor. 63 @type path: str 64 """ 65 self.__lock() 66 try: 67 try: 68 del self.__paths[path] 69 except KeyError: 70 pass 71 finally: 72 self.__unlock()
73
74 - def start(self, precision=1):
75 """ 76 Start the monitor thread. 77 @param precision: The precision (how often to check). 78 @type precision: float 79 @return: self 80 @rtype: L{PathMonitor} 81 """ 82 self.__lock() 83 try: 84 if self.__thread: 85 raise Exception, 'already started' 86 thread = MonitorThread(self, precision) 87 thread.start() 88 self.__thread = thread 89 return self 90 finally: 91 self.__unlock()
92
93 - def join(self):
94 """ 95 Join the monitoring thread. 96 """ 97 if not self.__thread: 98 raise Exception, 'not started' 99 self.__thread.join()
100
101 - def check(self):
102 """ 103 Check paths and notify. 104 """ 105 self.__lock() 106 try: 107 paths = self.__paths.items() 108 finally: 109 self.__unlock() 110 for k,v in paths: 111 self.__sniff(k, v)
112
113 - def __sniff(self, path, stat):
114 """ 115 Sniff and compare the stats of the file at the 116 specified I{path}. 117 First, check the modification time, if different, then 118 check the I{hash} of the file content to see if it really 119 changed. If changed, notify the registered listener. 120 @param path: The path of the file to sniff. 121 @type path: str 122 @param stat: The cached stat (mtime, digest, cb) 123 @type stat: tuple 124 """ 125 try: 126 f = File(path) 127 mtime = f.mtime() 128 if mtime == stat[0]: 129 return 130 digest = f.digest() 131 if (digest and stat[1]) and (digest == stat[1]): 132 return 133 self.__notify(path, stat[2]) 134 stat[0] = mtime 135 stat[1] = digest 136 except: 137 log.exception(path)
138
139 - def __notify(self, path, cb):
140 """ 141 Safely invoke registered callback. 142 @param path: The path of the changed file. 143 @type path: str 144 @param cb: A registered callback. 145 @type cb: callable 146 """ 147 try: 148 cb(path) 149 except: 150 log.exception(path)
151
152 - def __lock(self):
153 self.__mutex.acquire()
154
155 - def __unlock(self):
156 self.__mutex.release()
157 158
159 -class File:
160 """ 161 Safe file operations. 162 @ivar path: The path. 163 @type path: str 164 @ivar fp: The python file object. 165 @type fp: fileobj 166 """ 167
168 - def __init__(self, path):
169 """ 170 @param path: The file path. 171 @type path: str 172 """ 173 self.path = path 174 self.fp = None
175
176 - def open(self):
177 """ 178 Open (if not already open) 179 """ 180 if not self.fp: 181 self.fp = open(self.path)
182
183 - def close(self):
184 """ 185 Close (if not already closed) 186 """ 187 if self.fp: 188 self.fp.close() 189 self.fp = None
190
191 - def read(self, n):
192 """ 193 Read (n) bytes. 194 @param n: The bytes to read. 195 @type n: int 196 @return: the bytes read. 197 @rtype: buffer 198 """ 199 return self.fp.read(n)
200
201 - def mtime(self):
202 """ 203 Get modification time. 204 @return: mtime 205 @rtype: int 206 """ 207 try: 208 return os.path.getmtime(self.path) 209 except OSError: 210 pass 211 except: 212 log.exception(self.path) 213 return 0
214
215 - def digest(self):
216 """ 217 Get the SHA256 hex digest for content. 218 @return: the hexdigest. 219 @rtype: str 220 """ 221 try: 222 self.open() 223 h = sha256() 224 while True: 225 s = self.read(10240) 226 if s: 227 h.update(s) 228 else: 229 break 230 self.close() 231 return h.hexdigest() 232 except IOError: 233 pass 234 except: 235 log.exception(self.path) 236 return None
237
238 - def __del__(self):
239 self.close()
240 241
242 -class MonitorThread(Thread):
243 """ 244 Monitor thread. 245 @ivar monitor: A monitor object. 246 @type monitor: Monitor 247 @ivar precision: The level of percision (seconds). 248 @type precision: float 249 """ 250
251 - def __init__(self, monitor, precision):
252 """ 253 @param monitor: A monitor object. 254 @type monitor: Monitor 255 @param precision: The level of percision (seconds). 256 @type precision: float 257 """ 258 Thread.__init__(self, name='PathMonitor%s' % precision) 259 self.monitor = monitor 260 self.precision = precision 261 self.setDaemon(True)
262
263 - def run(self):
264 """ 265 Thread main run(). 266 """ 267 monitor = self.monitor 268 while True: 269 monitor.check() 270 sleep(self.precision)
271 272 273 274 275 if __name__ == '__main__':
276 - def changed(path):
277 print 'changed: %s' % path
278 from logging import basicConfig 279 basicConfig() 280 pmon = PathMonitor() 281 pmon.add('/tmp/jeff.repo', changed) 282 pmon.start() 283 pmon.join() 284