import weakref
-from allmydata.immutable.filenode import FileNode, LiteralFileNode
+from zope.interface import implements
+from allmydata.util.assertutil import precondition
+from allmydata.interfaces import INodeMaker, SDMF_VERSION
+from allmydata.immutable.literal import LiteralFileNode
+from allmydata.immutable.filenode import ImmutableFileNode, CiphertextFileNode
+from allmydata.immutable.upload import Data
from allmydata.mutable.filenode import MutableFileNode
-from allmydata.dirnode import DirectoryNode
+from allmydata.mutable.publish import MutableData
+from allmydata.dirnode import DirectoryNode, pack_children
from allmydata.unknown import UnknownNode
-from allmydata.uri import DirectoryURI, ReadonlyDirectoryURI
+from allmydata.blacklist import ProhibitedNode
+from allmydata import uri
-# the "node maker" is a two-argument callable (really a 'create' method on a
-# NodeMaker instance) which accepts a URI string (and an optional readcap
-# string, for use by dirnode.copy) and returns an object which (at the very
-# least) provides IFilesystemNode. That interface has other methods that can
-# be used to determine if the node represents a file or directory, in which
-# case other methods are available (like download() or modify()). Each Tahoe
-# process will typically have a single NodeMaker, but unit tests may create
-# simplified/mocked forms for test purposes.
-
-# any authorities which fsnodes will need (like a reference to the
-# StorageFarmBroker, to access storage servers for publish/retrieve/download)
-# will be retained as attributes inside the NodeMaker and passed to fsnodes
-# as necessary.
class NodeMaker:
+ implements(INodeMaker)
+
def __init__(self, storage_broker, secret_holder, history,
- uploader, downloader, download_cache_dirman,
- default_encoding_parameters, key_generator):
+ uploader, terminator,
+ default_encoding_parameters, key_generator,
+ blacklist=None):
self.storage_broker = storage_broker
self.secret_holder = secret_holder
self.history = history
self.uploader = uploader
- self.downloader = downloader
- self.download_cache_dirman = download_cache_dirman
+ self.terminator = terminator
self.default_encoding_parameters = default_encoding_parameters
self.key_generator = key_generator
+ self.blacklist = blacklist
self._node_cache = weakref.WeakValueDictionary() # uri -> node
def _create_lit(self, cap):
return LiteralFileNode(cap)
def _create_immutable(self, cap):
- return FileNode(cap, self.storage_broker, self.secret_holder,
- self.downloader, self.history,
- self.download_cache_dirman)
+ return ImmutableFileNode(cap, self.storage_broker, self.secret_holder,
+ self.terminator, self.history)
+ def _create_immutable_verifier(self, cap):
+ return CiphertextFileNode(cap, self.storage_broker, self.secret_holder,
+ self.terminator, self.history)
def _create_mutable(self, cap):
n = MutableFileNode(self.storage_broker, self.secret_holder,
self.default_encoding_parameters,
self.history)
- return n.init_from_uri(cap)
+ return n.init_from_cap(cap)
def _create_dirnode(self, filenode):
return DirectoryNode(filenode, self, self.uploader)
- def create_from_cap(self, writecap, readcap=None):
- # this returns synchronously.
+ def create_from_cap(self, writecap, readcap=None, deep_immutable=False, name=u"<unknown name>"):
+ # this returns synchronously. It starts with a "cap string".
assert isinstance(writecap, (str, type(None))), type(writecap)
assert isinstance(readcap, (str, type(None))), type(readcap)
- cap = writecap or readcap
- if not cap:
+
+ bigcap = writecap or readcap
+ if not bigcap:
# maybe the writecap was hidden because we're in a readonly
# directory, and the future cap format doesn't have a readcap, or
# something.
- return UnknownNode(writecap, readcap)
- if cap in self._node_cache:
- return self._node_cache[cap]
- elif cap.startswith("URI:LIT:"):
- node = self._create_lit(cap)
- elif cap.startswith("URI:CHK:"):
- node = self._create_immutable(cap)
- elif cap.startswith("URI:SSK-RO:") or cap.startswith("URI:SSK:"):
- node = self._create_mutable(cap)
- elif cap.startswith("URI:DIR2-RO:") or cap.startswith("URI:DIR2:"):
- if cap.startswith("URI:DIR2-RO:"):
- dircap = ReadonlyDirectoryURI.init_from_string(cap)
- elif cap.startswith("URI:DIR2:"):
- dircap = DirectoryURI.init_from_string(cap)
- filecap = dircap.get_filenode_uri().to_string()
- filenode = self.create_from_cap(filecap)
- node = self._create_dirnode(filenode)
+ return UnknownNode(None, None) # deep_immutable and name not needed
+
+ # The name doesn't matter for caching since it's only used in the error
+ # attribute of an UnknownNode, and we don't cache those.
+ if deep_immutable:
+ memokey = "I" + bigcap
+ else:
+ memokey = "M" + bigcap
+ if memokey in self._node_cache:
+ node = self._node_cache[memokey]
else:
- return UnknownNode(writecap, readcap) # don't cache UnknownNode
- self._node_cache[cap] = node # note: WeakValueDictionary
+ cap = uri.from_string(bigcap, deep_immutable=deep_immutable,
+ name=name)
+ node = self._create_from_single_cap(cap)
+ if node:
+ self._node_cache[memokey] = node # note: WeakValueDictionary
+ else:
+ # don't cache UnknownNode
+ node = UnknownNode(writecap, readcap,
+ deep_immutable=deep_immutable, name=name)
+
+ if self.blacklist:
+ si = node.get_storage_index()
+ # if this node is blacklisted, return the reason, otherwise return None
+ reason = self.blacklist.check_storageindex(si)
+ if reason is not None:
+ # The original node object is cached above, not the ProhibitedNode wrapper.
+ # This ensures that removing the blacklist entry will make the node
+ # accessible if create_from_cap is called again.
+ node = ProhibitedNode(node, reason)
return node
+ def _create_from_single_cap(self, cap):
+ if isinstance(cap, uri.LiteralFileURI):
+ return self._create_lit(cap)
+ if isinstance(cap, uri.CHKFileURI):
+ return self._create_immutable(cap)
+ if isinstance(cap, uri.CHKFileVerifierURI):
+ return self._create_immutable_verifier(cap)
+ if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI,
+ uri.WriteableMDMFFileURI, uri.ReadonlyMDMFFileURI)):
+ return self._create_mutable(cap)
+ if isinstance(cap, (uri.DirectoryURI,
+ uri.ReadonlyDirectoryURI,
+ uri.ImmutableDirectoryURI,
+ uri.LiteralDirectoryURI,
+ uri.MDMFDirectoryURI,
+ uri.ReadonlyMDMFDirectoryURI)):
+ filenode = self._create_from_single_cap(cap.get_filenode_cap())
+ return self._create_dirnode(filenode)
+ return None
- def create_mutable_file(self, contents="", keysize=None):
+ def create_mutable_file(self, contents=None, keysize=None,
+ version=SDMF_VERSION):
n = MutableFileNode(self.storage_broker, self.secret_holder,
self.default_encoding_parameters, self.history)
d = self.key_generator.generate(keysize)
- d.addCallback(n.create_with_keys, contents)
+ d.addCallback(n.create_with_keys, contents, version=version)
d.addCallback(lambda res: n)
return d
- def create_new_mutable_directory(self, initial_children={}):
- if initial_children:
- raise NotImplementedError("initial_children= not implemented yet")
- d = self.create_mutable_file()
+ def create_new_mutable_directory(self, initial_children={},
+ version=SDMF_VERSION):
+ # initial_children must have metadata (i.e. {} instead of None)
+ for (name, (node, metadata)) in initial_children.iteritems():
+ precondition(isinstance(metadata, dict),
+ "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
+ node.raise_error()
+ d = self.create_mutable_file(lambda n:
+ MutableData(pack_children(initial_children,
+ n.get_writekey())),
+ version=version)
+ d.addCallback(self._create_dirnode)
+ return d
+
+ def create_immutable_directory(self, children, convergence=None):
+ if convergence is None:
+ convergence = self.secret_holder.get_convergence_secret()
+ packed = pack_children(children, None, deep_immutable=True)
+ uploadable = Data(packed, convergence)
+ d = self.uploader.upload(uploadable, history=self.history)
+ d.addCallback(lambda results: self.create_from_cap(None, results.uri))
d.addCallback(self._create_dirnode)
return d