7751: Refactor TmpCollectionDirectory: generate .arvados#collection less often.
[arvados.git] / services / fuse / arvados_fuse / fusefile.py
1 import json
2 import llfuse
3 import logging
4 import re
5 import time
6
7 from fresh import FreshBase, convertTime
8
9 _logger = logging.getLogger('arvados.arvados_fuse')
10
11 class File(FreshBase):
12     """Base for file objects."""
13
14     def __init__(self, parent_inode, _mtime=0):
15         super(File, self).__init__()
16         self.inode = None
17         self.parent_inode = parent_inode
18         self._mtime = _mtime
19
20     def size(self):
21         return 0
22
23     def readfrom(self, off, size, num_retries=0):
24         return ''
25
26     def writeto(self, off, size, num_retries=0):
27         raise Exception("Not writable")
28
29     def mtime(self):
30         return self._mtime
31
32     def clear(self, force=False):
33         return True
34
35     def writable(self):
36         return False
37
38     def flush(self):
39         pass
40
41
42 class FuseArvadosFile(File):
43     """Wraps a ArvadosFile."""
44
45     def __init__(self, parent_inode, arvfile, _mtime):
46         super(FuseArvadosFile, self).__init__(parent_inode, _mtime)
47         self.arvfile = arvfile
48
49     def size(self):
50         with llfuse.lock_released:
51             return self.arvfile.size()
52
53     def readfrom(self, off, size, num_retries=0):
54         with llfuse.lock_released:
55             return self.arvfile.readfrom(off, size, num_retries, exact=True)
56
57     def writeto(self, off, buf, num_retries=0):
58         with llfuse.lock_released:
59             return self.arvfile.writeto(off, buf, num_retries)
60
61     def stale(self):
62         return False
63
64     def writable(self):
65         return self.arvfile.writable()
66
67     def flush(self):
68         with llfuse.lock_released:
69             if self.writable():
70                 self.arvfile.parent.root_collection().save()
71
72
73 class StringFile(File):
74     """Wrap a simple string as a file"""
75     def __init__(self, parent_inode, contents, _mtime):
76         super(StringFile, self).__init__(parent_inode, _mtime)
77         self.contents = contents
78
79     def size(self):
80         return len(self.contents)
81
82     def readfrom(self, off, size, num_retries=0):
83         return self.contents[off:(off+size)]
84
85
86 class ObjectFile(StringFile):
87     """Wrap a dict as a serialized json object."""
88
89     def __init__(self, parent_inode, obj):
90         super(ObjectFile, self).__init__(parent_inode, "", 0)
91         self.object_uuid = obj['uuid']
92         self.update(obj)
93
94     def uuid(self):
95         return self.object_uuid
96
97     def update(self, obj=None):
98         self._mtime = convertTime(obj['modified_at']) if 'modified_at' in obj else 0
99         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
100
101     def persisted(self):
102         return True
103
104
105 class FuncToJSONFile(StringFile):
106     """File content is the return value of a given function, encoded as JSON.
107
108     The function is called at the time the file is read. The result is
109     cached until invalidate() is called.
110     """
111     def __init__(self, parent_inode, func):
112         super(FuncToJSONFile, self).__init__(parent_inode, "", 0)
113         self.func = func
114
115     def size(self):
116         self._update()
117         return super(FuncToJSONFile, self).size()
118
119     def readfrom(self, *args, **kwargs):
120         self._update()
121         return super(FuncToJSONFile, self).readfrom(*args, **kwargs)
122
123     def _update(self):
124         if not self.stale():
125             return
126         self._mtime = time.time()
127         obj = self.func()
128         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
129         self.fresh()