refs #10028
[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):
33         pass
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         if obj is None:
99             # TODO: retrieve the current record for self.object_uuid
100             # from the server. For now, at least don't crash when
101             # someone tells us it's a good time to update but doesn't
102             # pass us a fresh obj. See #8345
103             return
104         self._mtime = convertTime(obj['modified_at']) if 'modified_at' in obj else 0
105         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
106
107     def persisted(self):
108         return True
109
110
111 class FuncToJSONFile(StringFile):
112     """File content is the return value of a given function, encoded as JSON.
113
114     The function is called at the time the file is read. The result is
115     cached until invalidate() is called.
116     """
117     def __init__(self, parent_inode, func):
118         super(FuncToJSONFile, self).__init__(parent_inode, "", 0)
119         self.func = func
120
121         # invalidate_inode() and invalidate_entry() are asynchronous
122         # with no callback to wait for. In order to guarantee
123         # userspace programs don't get stale data that was generated
124         # before the last invalidate(), we must disallow dirent
125         # caching entirely.
126         self.allow_dirent_cache = False
127
128     def size(self):
129         self._update()
130         return super(FuncToJSONFile, self).size()
131
132     def readfrom(self, *args, **kwargs):
133         self._update()
134         return super(FuncToJSONFile, self).readfrom(*args, **kwargs)
135
136     def _update(self):
137         if not self.stale():
138             return
139         self._mtime = time.time()
140         obj = self.func()
141         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
142         self.fresh()