Merge branch '7832-pysdk-use-slots' refs #7832
[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         # invalidate_inode() and invalidate_entry() are asynchronous
116         # with no callback to wait for. In order to guarantee
117         # userspace programs don't get stale data that was generated
118         # before the last invalidate(), we must disallow dirent
119         # caching entirely.
120         self.allow_dirent_cache = False
121
122     def size(self):
123         self._update()
124         return super(FuncToJSONFile, self).size()
125
126     def readfrom(self, *args, **kwargs):
127         self._update()
128         return super(FuncToJSONFile, self).readfrom(*args, **kwargs)
129
130     def _update(self):
131         if not self.stale():
132             return
133         self._mtime = time.time()
134         obj = self.func()
135         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
136         self.fresh()