14965: Updates fuse code with futurize stage1
[arvados.git] / services / fuse / arvados_fuse / fusefile.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 from __future__ import absolute_import
6 import json
7 import llfuse
8 import logging
9 import re
10 import time
11
12 from .fresh import FreshBase, convertTime
13
14 _logger = logging.getLogger('arvados.arvados_fuse')
15
16 class File(FreshBase):
17     """Base for file objects."""
18
19     __slots__ = ("inode", "parent_inode", "_mtime")
20
21     def __init__(self, parent_inode, _mtime=0):
22         super(File, self).__init__()
23         self.inode = None
24         self.parent_inode = parent_inode
25         self._mtime = _mtime
26
27     def size(self):
28         return 0
29
30     def readfrom(self, off, size, num_retries=0):
31         return ''
32
33     def writeto(self, off, size, num_retries=0):
34         raise Exception("Not writable")
35
36     def mtime(self):
37         return self._mtime
38
39     def clear(self):
40         pass
41
42     def writable(self):
43         return False
44
45     def flush(self):
46         pass
47
48
49 class FuseArvadosFile(File):
50     """Wraps a ArvadosFile."""
51
52     __slots__ = ('arvfile',)
53
54     def __init__(self, parent_inode, arvfile, _mtime):
55         super(FuseArvadosFile, self).__init__(parent_inode, _mtime)
56         self.arvfile = arvfile
57
58     def size(self):
59         with llfuse.lock_released:
60             return self.arvfile.size()
61
62     def readfrom(self, off, size, num_retries=0):
63         with llfuse.lock_released:
64             return self.arvfile.readfrom(off, size, num_retries, exact=True)
65
66     def writeto(self, off, buf, num_retries=0):
67         with llfuse.lock_released:
68             return self.arvfile.writeto(off, buf, num_retries)
69
70     def stale(self):
71         return False
72
73     def writable(self):
74         return self.arvfile.writable()
75
76     def flush(self):
77         with llfuse.lock_released:
78             if self.writable():
79                 self.arvfile.parent.root_collection().save()
80
81
82 class StringFile(File):
83     """Wrap a simple string as a file"""
84     def __init__(self, parent_inode, contents, _mtime):
85         super(StringFile, self).__init__(parent_inode, _mtime)
86         self.contents = contents
87
88     def size(self):
89         return len(self.contents)
90
91     def readfrom(self, off, size, num_retries=0):
92         return self.contents[off:(off+size)]
93
94
95 class ObjectFile(StringFile):
96     """Wrap a dict as a serialized json object."""
97
98     def __init__(self, parent_inode, obj):
99         super(ObjectFile, self).__init__(parent_inode, "", 0)
100         self.object_uuid = obj['uuid']
101         self.update(obj)
102
103     def uuid(self):
104         return self.object_uuid
105
106     def update(self, obj=None):
107         if obj is None:
108             # TODO: retrieve the current record for self.object_uuid
109             # from the server. For now, at least don't crash when
110             # someone tells us it's a good time to update but doesn't
111             # pass us a fresh obj. See #8345
112             return
113         self._mtime = convertTime(obj['modified_at']) if 'modified_at' in obj else 0
114         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
115
116     def persisted(self):
117         return True
118
119
120 class FuncToJSONFile(StringFile):
121     """File content is the return value of a given function, encoded as JSON.
122
123     The function is called at the time the file is read. The result is
124     cached until invalidate() is called.
125     """
126     def __init__(self, parent_inode, func):
127         super(FuncToJSONFile, self).__init__(parent_inode, "", 0)
128         self.func = func
129
130         # invalidate_inode() is asynchronous with no callback to wait for. In
131         # order to guarantee userspace programs don't get stale data that was
132         # generated before the last invalidate(), we must disallow inode
133         # caching entirely.
134         self.allow_attr_cache = False
135
136     def size(self):
137         self._update()
138         return super(FuncToJSONFile, self).size()
139
140     def readfrom(self, *args, **kwargs):
141         self._update()
142         return super(FuncToJSONFile, self).readfrom(*args, **kwargs)
143
144     def _update(self):
145         if not self.stale():
146             return
147         self._mtime = time.time()
148         obj = self.func()
149         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
150         self.fresh()