2 from zope.interface import implements
3 from twisted.internet import defer
4 from cStringIO import StringIO
5 from allmydata.filetree.interfaces import (
6 INode, INodeMaker, IDirectoryNode, ISubTree,
7 ICHKDirectoryNode, ISSKDirectoryNode,
10 from allmydata.filetree.basenode import BaseDataNode
11 from allmydata import download
12 from allmydata.util import bencode
14 # interesting feature ideas:
15 # pubsub for MutableDirectoryNode: get rapid notification of changes
16 # caused by someone else
18 # bind a local physical directory to the MutableDirectoryNode contents:
19 # each time the vdrive changes, update the local drive to match, and
22 from itertools import islice, izip
23 def in_pairs(iterable):
24 "s -> (s0,s1), (s2,s3), (s4,s5), ..."
25 a = islice(iterable, 0, None, 2)
26 b = islice(iterable, 1, None, 2)
31 implements(INode, IDirectoryNode)
33 def __init__(self, tree):
34 self.enclosing_tree = tree
36 # # subdirectory_node_children maps child name to another SubTreeNode
37 # # instance. This is only for internal directory nodes. All other
38 # # nodes are listed in child_specifications instead.
39 # self.subdirectory_node_children = {}
40 # # child_specifications maps child name to a specification tuple which
41 # # describes how to obtain the actual child. For example, if "foo.jpg"
42 # # in this node represents a CHK-encoded FILE with a uri of "fooURI",
43 # # then self.child_specifications["foo.jpg"] = ("CHKFILE","fooURI")
44 # self.child_specifications = {}
49 def get(self, childname):
50 if childname in self.children:
51 return self.children[childname]
53 raise NoSuchChildError("no child named '%s'" % (childname,))
55 def get_subtree(self):
56 return self.enclosing_tree
58 def delete(self, childname):
59 assert self.enclosing_tree.is_mutable()
60 if childname in self.children:
61 del self.children[childname]
63 raise NoSuchChildError("no child named '%s'" % (childname,))
65 def add_subdir(self, childname):
66 assert childname not in self.children
67 newnode = SubTreeNode(self.enclosing_tree)
68 self.children[childname] = newnode
71 def add(self, childname, node):
72 assert childname not in self.children
74 self.children[childname] = node
77 def serialize_node(self):
78 # note: this is a one-pass recursive serialization that will result
79 # in the whole file table being held in memory. This is only
80 # appropriate for directories with fewer than, say, 10k nodes. If we
81 # support larger directories, we should turn this into some kind of
82 # generator instead, and write the serialized data directly to a
85 # [name1, child1, name2, child2..]
87 # child1 is either a list for subdirs, or a string for non-subdirs
90 for name in sorted(self.children.keys()):
92 data.append(self.children[name].serialize_node())
95 def populate_dirnode(self, data, node_maker):
96 assert INodeMaker(node_maker)
97 assert len(data) % 2 == 0
98 for (name, child_data) in in_pairs(data):
99 if isinstance(child_data, (list, tuple)):
100 child = SubTreeNode(self.enclosing_tree)
101 child.populate_dirnode(child_data, node_maker)
103 assert isinstance(child_data, str)
104 child = node_maker.make_node_from_serialized(child_data)
105 self.children[name] = child
107 def is_leaf_subtree(self):
111 class _DirectorySubTree(object):
112 """I represent a set of connected directories that all share the same
113 access control: any given person can read or write anything in this tree
114 as a group, and it is not possible to give access to some pieces of this
115 tree and not to others. Read-only access to individual files can be
116 granted independently, of course, but through an unnamed URI, not as a
119 Each internal directory is represented by a separate Node.
121 This is an abstract base class. Individual subclasses will implement
122 various forms of serialization, persistence, and mutability.
129 # create a new, empty directory
130 self.root = SubTreeNode(self)
131 self.mutable = True # sure, why not
134 def populate_from_node(self, node, parent_is_mutable, node_maker, downloader):
135 # self.populate_from_node must be defined by the subclass (CHK or
136 # SSK), since it controls how the spec is interpreted. It will
137 # probably use the contents of the node to figure out what to
138 # download from the mesh, then pass this downloaded serialized data
139 # to populate_from_data()
140 raise NotImplementedError
142 def _populate_from_data(self, data, node_maker):
143 self.root = SubTreeNode(self)
144 self.root.populate_dirnode(bencode.bdecode(data), node_maker)
147 def serialize_subtree_to_file(self, f):
148 sexprs = self.root.serialize_node()
149 bencode.bwrite(sexprs, f)
151 def is_mutable(self):
154 def get_node_for_path(self, path):
155 # this is restricted to traversing our own subtree. Returns
156 # (found_path, node, remaining_path)
158 remaining_path = path[:]
160 while remaining_path:
161 name = remaining_path[0]
163 childnode = node.get(name)
164 except NoSuchChildError:
165 # The node *would* be in this subtree if it existed, but it
166 # doesn't. Leave found_path and remaining_path alone, and
167 # node points at the last parent node that was on the path.
169 if IDirectoryNode.providedBy(childnode):
172 found_path.append(name)
173 remaining_path.pop(0)
176 # the path takes us out of this subtree and into another
177 node = childnode # next subtree node
178 found_path.append(name)
179 remaining_path.pop(0)
181 return (found_path, node, remaining_path)
183 def put_node_at_path(self, path, new_node):
185 child_name = path[-1]
187 # first step: get (or create) the parent directory
189 for subdir_name in path[:-1]:
190 # TODO: peeking at private attributes is naughty, but using
191 # node.get() and catching NoSuchChildError would be slightly
192 # ugly. Reconsider the IDirectoryNode.get() API.
193 childnode = node.children.get(subdir_name)
195 assert IDirectoryNode.providedBy(childnode)
197 # we have to create new directories until the parent exists
198 childnode = node.add_subdir(subdir_name)
201 # 'node' is now pointing at the parent directory
202 if child_name in node.children:
203 # oops, there's already something there. We can replace it.
204 # TODO: How do we free the subtree that was just orphaned?
205 node.delete(child_name)
207 # now we can finally add the new node
208 node.add(child_name, new_node)
210 def delete_node_at_path(self, path):
212 child_name = path[-1]
214 # first step: get the parent directory
216 for subdir_name in path[:-1]:
217 subdir_node = node.get(subdir_name) # may raise NoSuchChildError
220 # 'node' is now pointing at the parent directory. Let's make sure the
221 # path they want to delete actually exists. We don't really care what
222 # the child *is*, just that it exists.
223 node.get(child_name) # may raise NoSuchChildError
226 # TODO: How do we free the subtree that was just orphaned?
227 node.delete(child_name)
230 class LocalFileSubTreeNode(BaseDataNode):
231 prefix = "LocalFileDirectory"
233 def new(self, filename):
234 self.filename = filename
237 def get_base_data(self):
239 def set_base_data(self, data):
242 def is_leaf_subtree(self):
245 class LocalFileSubTree(_DirectorySubTree):
246 node_class = LocalFileSubTreeNode
248 def new(self, filename):
249 self.filename = filename
250 return _DirectorySubTree.new(self)
252 def populate_from_node(self, node, parent_is_mutable, node_maker, downloader):
253 self.mutable = True # probably
254 self.filename = node.filename
255 f = open(self.filename, "rb")
258 d = defer.succeed(data)
259 d.addCallback(self._populate_from_data, node_maker)
262 def mutation_modifies_parent(self):
265 def create_node_now(self):
266 return LocalFileSubTreeNode().new(self.filename)
269 f = open(self.filename, "wb")
270 self.serialize_subtree_to_file(f)
273 def update_now(self, uploader):
275 return self.create_node_now()
277 def update(self, work_queue):
278 # TODO: this may suffer from the same execute-too-early problem as
279 # redirect.LocalFileRedirection
284 class CHKDirectorySubTreeNode(BaseDataNode):
285 implements(ICHKDirectoryNode)
286 prefix = "CHKDirectory"
288 def get_base_data(self):
290 def set_base_data(self, data):
296 def is_leaf_subtree(self):
300 class CHKDirectorySubTree(_DirectorySubTree):
301 # maybe mutable, maybe not
302 node_class = CHKDirectorySubTreeNode
306 return _DirectorySubTree.new(self)
308 def set_uri(self, uri):
311 def populate_from_node(self, node, parent_is_mutable, node_maker, downloader):
312 assert ICHKDirectoryNode(node)
313 self.mutable = parent_is_mutable
314 d = downloader.download(node.get_uri(), download.Data())
315 d.addCallback(self._populate_from_data, node_maker)
317 self.uri = node.get_uri()
319 d.addCallback(_populated)
322 def mutation_modifies_parent(self):
325 def create_node_now(self):
326 return CHKDirectorySubTreeNode().new(self.uri)
328 def update_now(self, uploader):
330 self.serialize_subtree_to_file(f)
332 d = uploader.upload_data(data)
335 return self.create_node_now()
336 d.addCallback(_uploaded)
339 def update(self, workqueue):
340 # this is the CHK form
342 f, filename = workqueue.create_tempfile(".chkdir")
343 self.serialize_subtree_to_file(f)
345 boxname = workqueue.create_boxname()
346 workqueue.add_upload_chk(filename, boxname)
347 workqueue.add_delete_tempfile(filename)
348 workqueue.add_retain_uri_from_box(boxname)
349 workqueue.add_delete_box(boxname)
351 workqueue.add_unlink_uri(old_uri)
352 # TODO: think about how self.old_uri will get updated. I *think* that
353 # this whole instance will get replaced, so it ought to be ok. But
354 # this needs investigation.
356 # mutation affects our parent, so we return a boxname for them
360 class SSKDirectorySubTreeNode(object):
361 implements(INode, ISSKDirectoryNode)
362 prefix = "SSKDirectory"
364 def serialize_node(self):
365 data = (self.read_cap, self.write_cap)
366 return "%s:%s" % (self.prefix, bencode.bencode(data))
367 def populate_node(self, body, node_maker):
368 self.read_cap, self.write_cap = bencode.bdecode(body)
370 def get_read_capability(self):
372 def get_write_capability(self):
373 return self.write_cap
374 def set_read_capability(self, read_cap):
375 self.read_cap = read_cap
376 def set_write_capability(self, write_cap):
377 self.write_cap = write_cap
379 def is_leaf_subtree(self):
383 class SSKDirectorySubTree(_DirectorySubTree):
384 node_class = SSKDirectorySubTreeNode
387 _DirectorySubTree.new(self)
392 def populate_from_node(self, node, parent_is_mutable, node_maker, downloader):
393 node = ISSKDirectoryNode(node)
394 self.read_capability = node.get_read_capability()
395 self.write_capability = node.get_write_capability()
396 self.mutable = bool(self.write_capability)
397 d = downloader.download_ssk(self.read_capability, download.Data())
398 d.addCallback(self._populate_from_data, node_maker)
401 def set_version(self, version):
402 self.version = version
404 def mutation_modifies_parent(self):
407 def create_node_now(self):
408 node = SSKDirectorySubTreeNode()
409 node.set_read_capability(self.read_capability)
410 node.set_write_capability(self.write_capability)
413 def update_now(self, uploader):
414 if not self.write_capability:
415 raise RuntimeError("This SSKDirectorySubTree is not mutable")
418 self.serialize_subtree_to_file(f)
422 d = uploader.upload_ssk_data(self.write_capability, self.version, data)
423 d.addCallback(lambda ignored: self.create_node_now())
426 def update(self, workqueue):
427 # this is the SSK form
428 f, filename = workqueue.create_tempfile(".sskdir")
429 self.serialize_subtree_to_file(f)
432 oldversion = self.version
433 self.version = self.version + 1
435 workqueue.add_upload_ssk(self.write_capability, oldversion, filename)
436 workqueue.add_delete_tempfile(filename)
437 workqueue.add_retain_ssk(self.read_capability)
438 # mutation does not affect our parent