2 import os, sha, stat, time, re
3 from foolscap import Referenceable, SturdyRef
4 from zope.interface import implements
5 from allmydata.interfaces import RIClient
6 from allmydata import node
8 from twisted.internet import reactor
9 from twisted.application.internet import TimerService
10 from twisted.python import log
13 from allmydata.storage import StorageServer
14 from allmydata.upload import Uploader
15 from allmydata.download import Downloader
16 from allmydata.checker import Checker
17 from allmydata.control import ControlServer
18 from allmydata.introducer import IntroducerClient
19 from allmydata.util import hashutil, idlib, testutil
20 from allmydata.filenode import FileNode
21 from allmydata.dirnode import NewDirectoryNode
22 from allmydata.mutable import MutableFileNode
23 from allmydata.interfaces import IURI, INewDirectoryURI, \
24 IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI
26 class Client(node.Node, Referenceable, testutil.PollMixin):
28 PORTNUMFILE = "client.port"
31 SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline"
33 # we're pretty narrow-minded right now
34 OLDEST_SUPPORTED_VERSION = allmydata.__version__
36 def __init__(self, basedir="."):
37 node.Node.__init__(self, basedir)
38 self.logSource="Client"
40 self.introducer_client = None
41 self.init_lease_secret()
44 self.add_service(Uploader())
45 self.add_service(Downloader())
46 self.add_service(Checker())
48 self.introducer_furl = self.get_config("introducer.furl", required=True)
50 hotline_file = os.path.join(self.basedir,
51 self.SUICIDE_PREVENTION_HOTLINE_FILE)
52 if os.path.exists(hotline_file):
53 age = time.time() - os.stat(hotline_file)[stat.ST_MTIME]
54 self.log("hotline file noticed (%ds old), starting timer" % age)
55 hotline = TimerService(1.0, self._check_hotline, hotline_file)
56 hotline.setServiceParent(self)
58 webport = self.get_config("webport")
60 self.init_web(webport) # strports string
62 def init_lease_secret(self):
64 return idlib.b2a(os.urandom(hashutil.CRYPTO_VAL_SIZE)) + "\n"
65 secret_s = self.get_or_create_private_config("secret", make_secret)
66 self._lease_secret = idlib.a2b(secret_s)
68 def init_storage(self):
69 storedir = os.path.join(self.basedir, self.STOREDIR)
72 data = self.get_config("sizelimit")
74 m = re.match(r"^(\d+)([kKmMgG]?[bB]?)$", data)
76 log.msg("SIZELIMIT_FILE contains unparseable value %s" % data)
78 number, suffix = m.groups()
79 suffix = suffix.upper()
80 if suffix.endswith("B"):
85 "G": 1000 * 1000 * 1000,
87 sizelimit = int(number) * multiplier
88 no_storage = self.get_config("debug_no_storage") is not None
89 self.add_service(StorageServer(storedir, sizelimit, no_storage))
91 def init_options(self):
92 self.push_to_ourselves = None
93 if self.get_config("push_to_ourselves") is not None:
94 self.push_to_ourselves = True
96 def init_web(self, webport):
97 self.log("init_web(webport=%s)", args=(webport,))
99 from allmydata.webish import WebishServer
100 ws = WebishServer(webport)
101 if self.get_config("webport_allow_localfile") is not None:
102 ws.allow_local_access(True)
105 def _check_hotline(self, hotline_file):
106 if os.path.exists(hotline_file):
107 mtime = os.stat(hotline_file)[stat.ST_MTIME]
108 if mtime > time.time() - 20.0:
111 self.log("hotline file too old, shutting down")
113 self.log("hotline file missing, shutting down")
117 self.log("tub_ready")
118 node.Node.tub_ready(self)
120 # we use separate get_config/write_config here because we want to
121 # update the connection hints each time.
123 my_old_furl = self.get_config("myself.furl")
124 if my_old_furl is not None:
125 sturdy = SturdyRef(my_old_furl)
126 my_old_name = sturdy.name
128 self.my_furl = self.tub.registerReference(self, my_old_name)
129 self.write_config("myself.furl", self.my_furl + "\n")
131 ic = IntroducerClient(self.tub, self.introducer_furl, self.my_furl)
132 self.introducer_client = ic
133 ic.setServiceParent(self)
135 self.register_control()
137 def register_control(self):
139 c.setServiceParent(self)
140 control_url = self.tub.registerReference(c)
141 self.write_private_config("control.furl", control_url + "\n")
143 def remote_get_versions(self):
144 return str(allmydata.__version__), str(self.OLDEST_SUPPORTED_VERSION)
146 def remote_get_service(self, name):
147 if name in ("storageserver",):
148 return self.getServiceNamed(name)
149 raise RuntimeError("I am unwilling to give you service %s" % name)
151 def remote_get_nodeid(self):
154 def get_all_peerids(self):
155 if not self.introducer_client:
157 return self.introducer_client.get_all_peerids()
159 def get_permuted_peers(self, key, include_myself=True):
161 @return: list of (permuted-peerid, peerid, connection,)
164 for peerid, connection in self.introducer_client.get_all_peers():
165 assert isinstance(peerid, str)
166 if not include_myself and peerid == self.nodeid:
167 self.log("get_permuted_peers: removing myself from the list")
169 permuted = sha.new(key + peerid).digest()
170 results.append((permuted, peerid, connection))
174 def get_push_to_ourselves(self):
175 return self.push_to_ourselves
177 def get_encoding_parameters(self):
178 if not self.introducer_client:
180 return self.introducer_client.encoding_parameters
182 def connected_to_introducer(self):
183 if self.introducer_client:
184 return self.introducer_client.connected_to_introducer()
187 def get_renewal_secret(self):
188 return hashutil.my_renewal_secret_hash(self._lease_secret)
190 def get_cancel_secret(self):
191 return hashutil.my_cancel_secret_hash(self._lease_secret)
193 def debug_wait_for_client_connections(self, num_clients):
194 """Return a Deferred that fires (with None) when we have connections
195 to the given number of peers. Useful for tests that set up a
196 temporary test network and need to know when it is safe to proceed
197 with an upload or download."""
199 current_clients = list(self.get_all_peerids())
200 return len(current_clients) >= num_clients
201 d = self.poll(_check, 0.5)
202 d.addCallback(lambda res: None)
206 # these four methods are the primitives for creating filenodes and
207 # dirnodes. The first takes a URI and produces a filenode or (new-style)
208 # dirnode. The other three create brand-new filenodes/dirnodes.
210 def create_node_from_uri(self, u):
211 # this returns synchronously.
213 if IReadonlyNewDirectoryURI.providedBy(u):
214 # new-style read-only dirnodes
215 return NewDirectoryNode(self).init_from_uri(u)
216 if INewDirectoryURI.providedBy(u):
218 return NewDirectoryNode(self).init_from_uri(u)
219 if IFileURI.providedBy(u):
221 return FileNode(u, self)
222 assert IMutableFileURI.providedBy(u), u
223 return MutableFileNode(self).init_from_uri(u)
225 def create_empty_dirnode(self, wait_for_numpeers=None):
226 n = NewDirectoryNode(self)
227 d = n.create(wait_for_numpeers=wait_for_numpeers)
228 d.addCallback(lambda res: n)
231 def create_mutable_file(self, contents="", wait_for_numpeers=None):
232 n = MutableFileNode(self)
233 d = n.create(contents, wait_for_numpeers=wait_for_numpeers)
234 d.addCallback(lambda res: n)
237 def upload(self, uploadable, wait_for_numpeers=None):
238 uploader = self.getServiceNamed("uploader")
239 return uploader.upload(uploadable, wait_for_numpeers=wait_for_numpeers)