~soren/filecache/trunk-fail

« back to all changes in this revision

Viewing changes to filecache/lib/sharedfile.py

  • Committer: Rick Harris
  • Date: 2010-04-23 00:48:59 UTC
  • Revision ID: git-v1:017b7d851a640a83a3222ea53d15a7634e546d3a
Added SharedFileDict Closes #22

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import os
2
2
import errno
3
3
import tempfile
 
4
import pickle
4
5
from filelock import FileLock
5
6
from filesem import FileSemaphore
6
7
from utils import logger
35
36
    ROLLBACK_NORMAL = 1
36
37
    ROLLBACK_WITH_DELETE = 2
37
38
 
38
 
    def __init__(self, path, overwrite=True, wait=True, timeout=10, delay=0.5):
 
39
    def __init__(self, path, wait=True, timeout=10, delay=0.5):
39
40
        self.path = path
40
41
        self.mode = None
41
 
        self.overwrite = overwrite
 
42
        self.overwrite = None
42
43
        self.wait = wait
43
44
        self.timeout = timeout
44
45
        self.delay = delay
48
49
        self.lock = self._make_lock()
49
50
        self.readers_sem = self._make_readers_semaphore()
50
51
 
51
 
    def open(self, mode="r", wait=True):
 
52
    def open(self, mode="r", overwrite=True, wait=True):
52
53
        self.mode = mode
 
54
        self.overwrite = overwrite
53
55
        if mode == "rw":
54
56
            self.lock.acquire(wait=wait)
55
57
            try:
73
75
            try:
74
76
                self._unsafe_close_read()
75
77
                self._close_write()
76
 
            except:
 
78
            finally:
77
79
                self.lock.release()
78
 
                raise
79
80
        elif mode == "r":
80
81
            self._close_read()
81
82
        elif mode == "w":
85
86
 
86
87
        self.closed = True
87
88
        self.mode = None
 
89
        self.overwrite = None
88
90
 
89
91
    def write(self, data):
90
92
        if self.closed:
127
129
            
128
130
            # wait for any readers to finish
129
131
            self.readers_sem.wait(wait=wait)
130
 
            
131
 
            os.unlink(self.path)
 
132
            self._unsafe_delete()
132
133
        finally:
133
134
            self.lock.release()
134
135
 
229
230
            raise SharedFileException("must be in read mode, not '%s'" %
230
231
                                      self.mode)
231
232
 
 
233
    def _unsafe_delete(self):
 
234
        try:
 
235
            os.unlink(self.path)
 
236
        except OSError, e:
 
237
            if e.errno != errno.ENOENT:
 
238
                raise
 
239
            raise SharedFileNotFound("'%s' not found" % self.path)
 
240
 
232
241
    def _commit(self):
233
242
        # the parent directory may have been deleted out from under us; if
234
243
        # that has happened we probably can't avoid a race condition
236
245
        os.rename(self.tmp_path, self.path)
237
246
 
238
247
    def __enter__(self):
 
248
        #FIXME: make open semantics match up with context manager
239
249
        self.open()
240
250
        return self
241
251
 
267
277
        """
268
278
        return FileSemaphore(self._make_hidden_file_with_suffix('readers_sem'))
269
279
 
 
280
class SharedFileDict(object):
 
281
    """ Provide a key-value store backed by a SharedFile """
 
282
    def __init__(self, path, serializer=None):
 
283
        self.file = SharedFile(path)
 
284
        self.serializer = serializer or pickle
 
285
 
 
286
    def __getitem__(self, key):
 
287
        try:
 
288
            self.file.open(mode='r')
 
289
        except SharedFileNotFound:
 
290
            raise KeyError("key '%s' not found" % key)
 
291
        try:
 
292
            return self.serializer.load(self.file)[key]
 
293
        finally:
 
294
            self.file.close()
 
295
 
 
296
    #TODO: get should support default value
 
297
    get = __getitem__
 
298
 
 
299
    def __setitem__(self, key, value):
 
300
        self.file.open(mode='rw')
 
301
        try:
 
302
            d = self.serializer.load(self.file)
 
303
            d[key] = value
 
304
            self.serializer.dump(d, self.file)
 
305
        except:
 
306
            self.file.request_rollback()
 
307
            raise
 
308
        finally:
 
309
            self.file.close()
 
310
 
 
311
    def __delitem__(self, key):
 
312
        self.file.open(mode='rw')
 
313
        try:
 
314
            d = self.serializer.load(self.file)
 
315
            del d[key]
 
316
            if not d:
 
317
                # Delete the file if the last key was deleted
 
318
                raise SharedFileRollbackWithDelete
 
319
            self.serializer.dump(d, self.file)
 
320
        except SharedFileRollbackWithDelete:
 
321
            self.file.request_rollback(delete=True)
 
322
        except:
 
323
            self.file.request_rollback()
 
324
            raise
 
325
        finally:
 
326
            self.file.close()
 
327
 
 
328
    def update(self, data):
 
329
        self.file.open(mode='rw')
 
330
        try:
 
331
            d = self.serializer.load(self.file)
 
332
            d.update(data)
 
333
            self.serializer.dump(d, self.file)
 
334
        except:
 
335
            self.file.request_rollback()
 
336
            raise
 
337
        finally:
 
338
            self.file.close()
 
339
 
 
340
    def set(self, data):
 
341
        # N.B. this is not part of the dictionary protocol, is there a more
 
342
        # pythonic way?
 
343
        self.file.open(mode='w', overwrite=True)
 
344
        try:
 
345
            self.serializer.dump(data, self.file)
 
346
        except:
 
347
            self.file.request_rollback()
 
348
            raise
 
349
        finally:
 
350
            self.file.close()
 
351
 
 
352
    def items(self):
 
353
        return self.to_dict().items()
 
354
 
 
355
    def keys(self):
 
356
        return self.to_dict().keys()
 
357
 
 
358
    def __len__(self):
 
359
        return len(self.keys())
 
360
 
 
361
    def __bool__(self):
 
362
        return bool(self.keys())
 
363
 
 
364
    def values(self):
 
365
        return self.to_dict().values()
 
366
 
 
367
    def has_key(self, key):
 
368
        return key in self.to_dict().keys()
 
369
 
 
370
    __contains__ = has_key
 
371
 
 
372
    def clear(self, wait=True):
 
373
        try:
 
374
            self.file.delete(wait=wait)
 
375
        except SharedFileNotFound:
 
376
            pass
 
377
 
 
378
    def to_dict(self):
 
379
        try:
 
380
            self.file.open(mode='r')
 
381
        except SharedFileNotFound:
 
382
            return {}
 
383
        try:
 
384
            return self.serializer.load(self.file)
 
385
        finally:
 
386
            self.file.close()
 
387
 
 
388
    def _unsafe_delete(self):
 
389
        try:
 
390
            self.file._unsafe_delete()
 
391
        except SharedFileNotFound:
 
392
            pass
 
393
 
 
394