2 from zope.interface import Interface
4 class INode(Interface):
5 """This is some sort of retrievable node. All objects which implement
6 other I*Node interfaces also implement this one."""
8 # the INode-implementing class must have an attribute named .prefix which
12 """Return a data structure which contains enough information to build
13 this node again in the future (by calling
14 INodeMaker.make_node_from_serialized(). For IDirectoryNodes, this
15 will be a list. For all other nodes this will be a string of the form
16 'prefix:body', where 'prefix' must be the same as the class attribute
18 def populate_node(body, node_maker):
19 """INodeMaker.make_node_from_serialized() will first use the prefix
20 from the .prefix attribute to decide what kind of Node to create.
21 They will then call this populate_node() method with the body to
22 populate the new Node. 'node_maker' provides INodeMaker, which
23 provides that same make_node_from_serialized function to create any
24 internal child nodes that might be necessary."""
26 def is_leaf_subtree():
27 """Return True if this node does not refer to a traversable
28 subtree. When searching for the node that describes a path, the
29 search will stop at the first leaf node found. IFileNodes should
32 # TODO: there is a slightly confusing mixture of IDirectoryNodes and all
33 # other kinds of nodes. It is convenient to mix them because that way list()
34 # can point at nodes of all sorts, but IDirectoryNodes are very different
35 # than the rest (because they to not represent distinct subtrees). There
36 # might be a better way to factor this.
38 # TODO: 'node' is a problematic term here. It refers to nodes in the graph of
39 # connected subtrees. It also refers to nodes in the graph of directories and
40 # links within a single subtree. And the interface named INode is
41 # unfortunately a homonym with "inode", the data structure we previously used
42 # to represent information about an uploaded file which was too large to keep
43 # locally (the list of blockids), which meant the inode itself was uploaded.
44 # We no longer use inodes, but using a word that sounds just like it may
47 class IFileNode(Interface):
48 """This is a file which can be retrieved."""
49 # TODO: not sure which of these to provide.. should URIs contain "CHK" or
50 # "SSK" in them? Or should that be a detail of IDownloader?
52 """Return the URI of the target file. This URI can be passed
53 to an IDownloader to retrieve the data."""
54 def download(downloader, target):
55 """Download the file to the given target (using the provided
56 downloader). Return a deferred that fires (with 'target') when the
57 download is complete."""
59 class IDirectoryNode(Interface):
60 """This is a directory which can be listed."""
61 # these calls do not modify the subtree
63 """Return a dictionary mapping each childname to a node. These nodes
64 implement various I*Node interfaces depending upon what they can do."""
66 """Return a child node. Raises NoSuchChildError if there is no
67 child of that name."""
69 """Return the ISubTree which contains this node."""
71 # the following calls modify the subtree. After calling them, you must
72 # tell the enclosing subtree to serialize and upload itself. They can
73 # only be called if this directory node is associated with a mutable
75 def delete(childname):
76 """Delete any child referenced by this name."""
77 def add_subdir(childname):
78 """Create a new directory node, and return it."""
79 def add(childname, node):
80 """Add a new node to this path. Returns self."""
82 class ISubTree(Interface):
83 """A subtree is a collection of Nodes: files, directories, other trees.
85 A subtree represents a set of connected directories and files that all
86 share the same access control: any given person can read or write
87 anything in this tree as a group, and it is not possible to give access
88 to some pieces of this tree and not to others. Read-only access to
89 individual files can be granted independently, of course, but through an
90 unnamed URI, not as a subdirectory.
92 Each internal directory is represented by a separate Node. This might be
93 a DirectoryNode, or it might be a FileNode.
96 # All ISubTree-providing instances must have a class-level attribute
97 # named .node_class which references the matching INode-providing class.
98 # This is used by the ISubTreeMaker to turn nodes into subtrees.
100 def populate_from_node(node, parent_is_mutable, node_maker, downloader):
101 """Subtrees are created by ISubTreeMaker.open() being called with an
102 INode which describes both the kind of subtree to be created and a
103 way to obtain its contents. open() uses the node to create a new
104 instance of the appropriate subtree type, then calls this
105 populate_from_node() method.
107 Each subtree's populate_from_node() method is expected to use the
108 downloader to obtain a file with the subtree's serialized contents
109 (probably by pulling data from some source, like the mesh, the vdrive
110 server, an HTTP server, or somewhere on the local filesystem), then
111 unserialize them and populate the subtree's state.
113 Return a Deferred that will fire (with self) when this subtree is
114 ready for use (specifically when it is ready for get() and add()
119 """This returns True if we have the ability to modify this subtree.
120 If this returns True, this reference may be adapted to
121 IMutableSubTree to actually exercise these mutation rights.
124 def mutation_modifies_parent():
125 """This returns True if any modification to this subtree will result
126 in it getting a new identity, and thus requiring its parent be
127 notified. This is True for CHKDirectorySubTree, but False for
128 SSKDirectorySubTree and all redirections.
131 def get_node_for_path(path):
132 """Ask this subtree to follow the path through its internal nodes.
134 Returns a tuple of (found_path, node, remaining_path). This method
135 operations synchronously, and does not return a Deferred.
137 (found_path=path, found_node, [])
138 If the path terminates within this subtree, found_path=path and
139 remaining_path=[], and the node will be an internal IDirectoryNode.
141 (found_path, last_node, remaining_path)
142 If the path does not terminate within this subtree but neither does
143 it exit this subtree, the last internal IDirectoryNode that *was* on
144 the path will be returned in 'node'. The path components that led to
145 this node will be in found_path, and the remaining components will be
146 in remaining_path. If you want to create the target node, loop over
147 remaining_path as follows::
149 while remaining_path:
150 node = node.add_subdir(remaining_path.pop(0))
152 (found_path, exit_node, remaining_path)
153 If the path leaves this subtree, 'node' will be a different kind of
154 INode (probably one that points at a child directory of some sort),
155 found_path will be the components that led to this point, and
156 remaining_path will be the remaining components. If you still wish to
157 locate the target, use 'node' to open a new subtree, then provide
158 'remaining_path' to the new subtree's get_node_for_path() method.
162 def put_node_at_path(path, node):
163 """Add the given node to this subtree, at 'path'.
165 This may create internal directory subnodes as necessary. This must
166 run synchronously, and returns None.
169 def delete_node_at_path(path):
170 """Delete the node at the the given path.
172 This must run synchronously, and returns None.
175 def serialize_subtree_to_file(f):
176 """Create a string which describes my structure and write it to the
177 given filehandle (using only .write()). This string should be
178 suitable for uploading to the mesh or storing in a local file."""
180 def update_now(uploader):
181 """Perform whatever work is necessary to record this subtree to
184 This returns an INode, or a Deferred that fires (with an INode) when
185 the subtree has been persisted.
187 For directory subtrees, this will cause the subtree to serialize
188 itself to a file, then upload this file to the mesh, then create an
189 INode-providing instance which describes where the file wound up. For
190 redirections, this will cause the subtree to modify the redirection's
191 persistent storage, then return the (unmodified) INode that describes
194 This form does not use the workqueue. If the node is shut down before
195 the Deferred fires, a redirection or SSK subtree might be left in its
196 previous state, or it might have been updated.
199 def update(workqueue):
200 """Perform and schedule whatever work is necessary to record this
201 subtree to persistent storage.
203 Returns a boxname or None, synchronously. This function does not
206 If the parent subtree needs to be modified with the new identity of
207 this subtree (i.e. for CHKDirectorySubTree instances), this will
208 return a boxname in which the serialized INode will be placed once
209 the added workqueue steps have completed. The caller should add
210 'addpath' steps to the workqueue using this boxname (which will
211 eventually cause recursion on other subtrees, until some subtree is
212 updated which does not require notifying the parent). update() will
213 add steps to delete the box at the end of the workqueue.
215 If the parent subtree does not need to be modified (i.e. for
216 SSKDirectorySubTree instances, or redirections), this will return
219 This is like update_now(), but uses the workqueue to insure
220 consistency in the face of node shutdowns. Once our intentions have
221 been recorded in the workqueue, if the node is shut down before the
222 upload steps have completed, the update will eventually complete the
223 next time the node is started.
226 def create_node_now():
227 # TODO: this is no longer just for testing.. vdrive.addpath needs it
228 """FOR TESTING ONLY. Immediately create and return an INode which
229 describes the current state of this subtree. This does not perform
230 any upload or persistence work, and thus depends upon any internal
231 state having been previously set correctly. In general this will
232 return the correct value for subtrees which have just been created
233 (and not yet mutated). It will also return the correct value for
234 subtrees which do not change their identity when they are mutated
235 (SSKDirectorySubTrees and redirections).
238 class INodeMaker(Interface):
239 def make_node_from_serialized(serialized):
240 """Turn a string into an INode, which contains information about the
241 file or directory (like a URI), but does not contain the actual
242 contents. An ISubTreeMaker can be used later to retrieve the contents
243 (which means downloading the file if this is an IFileNode, or perhaps
244 creating a new subtree from the contents)."""
246 class ISubTreeMaker(Interface):
247 def make_subtree_from_node(node, parent_is_mutable):
248 """Turn an INode into an ISubTree.
250 I accept an INode-providing specification of a subtree, and return a
251 Deferred that fires with an ISubTree-providing instance. I will
252 perform network IO and download the serialized data that the INode
253 references, if necessary, or ask the vdrive server (or other provider)
254 for a pointer, or read it from local disk.
258 class IVirtualDrive(Interface):
260 def __init__(workqueue, downloader, root_node):
263 # commands to manipulate files
266 """List the contents of the directory at the given path.
268 'path' is a list of strings (empty to refer to the root directory)
269 and must refer to a DIRECTORY node. This method returns a Deferred
270 that fires with a dictionary that maps strings to filetypes. The
271 strings are useful as path name components. The filetypes are
272 Interfaces: either IDirectoryNode if path+[childname] can be used in
273 a 'list' method, or IFileNode if path+[childname] can be used in a
276 The Deferred will errback (with NoSuchDirectoryError) if the path
277 does not point to an actual directory.
280 def download(path, target):
281 """Download the file at the given path to 'target'.
283 'path' must refer to a FILE. 'target' must implement IDownloadTarget.
284 This returns a Deferred that fires (with 'target') when the download
288 def upload_data(path, data):
289 """Upload a string to the given path. The path must not already exist.
291 path[:-1] must refer to a writable DIRECTORY node.
293 This uses the workqueue, and returns None.
296 def upload(path, filename):
297 """Upload a file from disk to the given path.
299 This uses the workqueue, and returns None.
303 """Delete the file or directory at the given path.
305 Returns a Deferred that fires (with self) when the delete is
309 def add_node(path, node):
310 """Add a node to the given path. Use the workqueue.
313 # commands to manipulate subtrees
315 # ... detach subtree, merge subtree, etc
320 class ICHKDirectoryNode(Interface):
323 class ISSKDirectoryNode(Interface):
324 def get_read_capability():
326 def get_write_capability():
331 class NoSuchChildError(Exception):
333 class NoSuchDirectoryError(Exception):
335 class PathAlreadyExistsError(Exception):
337 class PathDoesNotExistError(Exception):