def _setup_other_components(self): log.debug("Setting up the rest of the components") if self.rate_limiter is None: self.rate_limiter = RateLimiter() if self.blob_manager is None: if self.blob_dir is None: self.blob_manager = TempBlobManager(self.hash_announcer) else: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.db_dir) if self.blob_tracker is None: self.blob_tracker = self.blob_tracker_class(self.blob_manager, self.peer_finder, self.dht_node) if self.payment_rate_manager is None: self.payment_rate_manager = self.payment_rate_manager_class( self.base_payment_rate_manager, self.blob_tracker, self.is_generous) self.rate_limiter.start() d1 = self.blob_manager.setup() d2 = self.wallet.start() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) dl.addCallback(lambda _: self.blob_tracker.start()) return dl
def _setup_other_components(self): log.debug("Setting up the rest of the components") if self.rate_limiter is None: self.rate_limiter = RateLimiter() if self.blob_manager is None: if self.blob_dir is None: raise Exception( "TempBlobManager is no longer supported, specify BlobManager or db_dir") else: self.blob_manager = DiskBlobManager( self.hash_announcer, self.blob_dir, self.storage ) if self.blob_tracker is None: self.blob_tracker = self.blob_tracker_class( self.blob_manager, self.peer_finder, self.dht_node ) if self.payment_rate_manager is None: self.payment_rate_manager = self.payment_rate_manager_class( self.base_payment_rate_manager, self.blob_tracker, self.is_generous ) self.rate_limiter.start() d = self.storage.setup() d.addCallback(lambda _: self.blob_manager.setup()) d.addCallback(lambda _: self.wallet.start()) d.addCallback(lambda _: self.blob_tracker.start()) return d
class BlobComponent(Component): component_name = BLOB_COMPONENT depends_on = [DATABASE_COMPONENT, DHT_COMPONENT] def __init__(self, component_manager): Component.__init__(self, component_manager) self.blob_manager = None @property def component(self): return self.blob_manager def start(self): storage = self.component_manager.get_component(DATABASE_COMPONENT) dht_node = self.component_manager.get_component(DHT_COMPONENT) self.blob_manager = DiskBlobManager(CS.get_blobfiles_dir(), storage, dht_node._dataStore) return self.blob_manager.setup() def stop(self): return self.blob_manager.stop() @defer.inlineCallbacks def get_status(self): count = 0 if self.blob_manager: count = yield self.blob_manager.storage.count_finished_blobs() defer.returnValue({'finished_blobs': count})
def setUp(self): conf.initialize_settings() self.blob_dir = tempfile.mkdtemp() self.db_dir = tempfile.mkdtemp() hash_announcer = DummyHashAnnouncer() self.bm = DiskBlobManager(hash_announcer, self.blob_dir, self.db_dir) self.peer = Peer('somehost', 22)
def _setup_other_components(self): log.debug("Setting up the rest of the components") if self.rate_limiter is None: self.rate_limiter = RateLimiter() if self.blob_manager is None: if self.blob_dir is None: self.blob_manager = TempBlobManager(self.hash_announcer) else: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.db_dir) if self.blob_tracker is None: self.blob_tracker = self.blob_tracker_class(self.blob_manager, self.peer_finder, self.dht_node) if self.payment_rate_manager is None: self.payment_rate_manager = self.payment_rate_manager_class( self.base_payment_rate_manager, self.blob_tracker, self.is_generous) self.rate_limiter.start() d1 = self.blob_manager.setup() d2 = self.wallet.start() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) dl.addCallback(lambda _: self.blob_tracker.start()) dl.addErrback(self._subfailure) return dl
def setUp(self): conf.initialize_settings(False) self.blob_dir = tempfile.mkdtemp() self.db_dir = tempfile.mkdtemp() self.bm = DiskBlobManager(self.blob_dir, SQLiteStorage(self.db_dir)) self.peer = Peer('somehost', 22) yield self.bm.storage.setup()
def encrypt_blob(filename, key, iv): dummy_announcer = DummyHashAnnouncer() manager = DiskBlobManager(dummy_announcer, '.', '.') yield manager.setup() creator = CryptStreamCreator(manager, filename, key, iv_generator(iv)) with open(filename, 'r') as infile: producer = FileBodyProducer(infile, readSize=2**22) yield producer.startProducing(creator) yield creator.stop()
def download_temp_blob_from_peer(self, peer, timeout, blob_hash): tmp_dir = yield threads.deferToThread(tempfile.mkdtemp) tmp_blob_manager = DiskBlobManager(tmp_dir, tmp_dir) try: result = yield self.download_blob_from_peer( peer, timeout, blob_hash, tmp_blob_manager) finally: yield tmp_blob_manager.stop() yield threads.deferToThread(shutil.rmtree, tmp_dir) defer.returnValue(result)
class TestTransfer(unittest.TestCase): @defer.inlineCallbacks def setUp(self): mocks.mock_conf_settings(self) self.db_dir, self.blob_dir = mk_db_and_blob_dir() self.wallet = FakeWallet() self.peer_manager = PeerManager() self.peer_finder = FakePeerFinder(5553, self.peer_manager, 1) self.rate_limiter = RateLimiter() self.prm = OnlyFreePaymentsManager() self.storage = SQLiteStorage(self.db_dir) self.blob_manager = DiskBlobManager(self.blob_dir, self.storage) self.sd_identifier = StreamDescriptorIdentifier() self.lbry_file_manager = EncryptedFileManager( self.peer_finder, self.rate_limiter, self.blob_manager, self.wallet, self.prm, self.storage, self.sd_identifier) self.uploader = LbryUploader(5209343) self.sd_hash = yield self.uploader.setup() yield self.storage.setup() yield self.blob_manager.setup() yield self.lbry_file_manager.setup() yield add_lbry_file_to_sd_identifier(self.sd_identifier) @defer.inlineCallbacks def tearDown(self): yield self.uploader.stop() lbry_files = self.lbry_file_manager.lbry_files for lbry_file in lbry_files: yield self.lbry_file_manager.delete_lbry_file(lbry_file) yield self.lbry_file_manager.stop() yield self.blob_manager.stop() yield self.storage.stop() rm_db_and_blob_dir(self.db_dir, self.blob_dir) if os.path.exists("test_file"): os.remove("test_file") @defer.inlineCallbacks def test_lbry_transfer(self): sd_blob = yield download_sd_blob(self.sd_hash, self.blob_manager, self.peer_finder, self.rate_limiter, self.prm, self.wallet) metadata = yield self.sd_identifier.get_metadata_for_sd_blob(sd_blob) downloader = yield metadata.factories[0].make_downloader( metadata, self.prm.min_blob_data_payment_rate, self.prm, self.db_dir, download_mirrors=None) yield downloader.start() with open(os.path.join(self.db_dir, 'test_file'), 'rb') as f: hashsum = md5() hashsum.update(f.read()) self.assertEqual(hashsum.hexdigest(), "4ca2aafb4101c1e42235aad24fbb83be")
def download_it(peer, timeout, blob_hash): tmp_dir = yield threads.deferToThread(tempfile.mkdtemp) storage = SQLiteStorage(tmp_dir, reactor) yield storage.setup() tmp_blob_manager = DiskBlobManager(tmp_dir, storage) config = {'auto_connect': True} if conf.settings['lbryum_wallet_dir']: config['lbryum_wallet_dir'] = conf.settings['lbryum_wallet_dir'] config['use_keyring'] = False config['blockchain_name'] = conf.settings['blockchain_name'] config['lbryum_servers'] = [] wallet = LbryWalletManager.from_lbrynet_config(config, storage) downloader = SinglePeerDownloader() downloader.setup(wallet) try: blob_downloaded = yield downloader.download_blob_from_peer( peer, timeout, blob_hash, tmp_blob_manager) if blob_downloaded: log.info("SUCCESS!") blob = yield tmp_blob_manager.get_blob(blob_hash) pprint(blob) if not blob.verified: log.error("except that its not verified....") else: reader = BlobStreamDescriptorReader(blob) info = None for x in range(0, 3): try: info = yield reader.get_info() except ValueError: pass if info: break # there's some kind of race condition where it sometimes doesnt write the blob to disk in time time.sleep(0.1) if info is not None: pprint(info) for content_blob in info['blobs']: if 'blob_hash' in content_blob: yield download_it(peer, timeout, content_blob['blob_hash']) else: log.error("Download failed") finally: yield tmp_blob_manager.stop() yield threads.deferToThread(shutil.rmtree, tmp_dir) defer.returnValue(True)
def start(self): if self.peer_manager is None: self.peer_manager = PeerManager() def match_port(h, p): return h, p def join_resolved_addresses(result): addresses = [] for success, value in result: if success is True: addresses.append(value) return addresses def start_dht(addresses): log.info("Starting the dht") log.info("lbry id: %s", base64.encodestring(self.lbryid).strip("\n")) self.dht_node.joinNetwork(addresses) self.peer_finder.run_manage_loop() self.hash_announcer.run_manage_loop() ds = [] for host, port in self.known_dht_nodes: d = reactor.resolve(host) d.addCallback(match_port, port) ds.append(d) if self.dht_node is None: self.dht_node = Node(udpPort=self.dht_node_port, lbryid=self.lbryid, externalIP=self.external_ip) if self.peer_finder is None: self.peer_finder = DHTPeerFinder(self.dht_node, self.peer_manager) if self.hash_announcer is None: self.hash_announcer = DHTHashAnnouncer(self.dht_node, self.peer_port) if self.blob_manager is None: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.blob_data_dir) d1 = defer.DeferredList(ds) d1.addCallback(join_resolved_addresses) d1.addCallback(start_dht) d2 = self.blob_manager.setup() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) return dl
def download_it(peer, timeout, blob_hash): tmp_dir = yield threads.deferToThread(tempfile.mkdtemp) announcer = DummyHashAnnouncer() tmp_blob_manager = DiskBlobManager(announcer, tmp_dir, tmp_dir) config = {'auto_connect': True} if conf.settings['lbryum_wallet_dir']: config['lbryum_path'] = conf.settings['lbryum_wallet_dir'] storage = Wallet.InMemoryStorage() wallet = Wallet.LBRYumWallet(storage, config) downloader = SinglePeerDownloader() downloader.setup(wallet) try: blob_downloaded = yield downloader.download_blob_from_peer( peer, timeout, blob_hash, tmp_blob_manager) if blob_downloaded: log.info("SUCCESS!") blob = yield tmp_blob_manager.get_blob(blob_hash) pprint(blob) if not blob.verified: log.error("except that its not verified....") else: reader = BlobStreamDescriptorReader(blob) info = None for x in range(0, 3): try: info = yield reader.get_info() except ValueError: pass if info: break time.sleep( 0.1 ) # there's some kind of race condition where it sometimes doesnt write the blob to disk in time if info is not None: pprint(info) for content_blob in info['blobs']: if 'blob_hash' in content_blob: yield download_it(peer, timeout, content_blob['blob_hash']) else: log.error("Download failed") finally: yield tmp_blob_manager.stop() yield threads.deferToThread(shutil.rmtree, tmp_dir) defer.returnValue(True)
def setUp(self): mocks.mock_conf_settings(self) self.tmp_db_dir, self.tmp_blob_dir = mk_db_and_blob_dir() self.wallet = FakeWallet() self.peer_manager = PeerManager() self.peer_finder = FakePeerFinder(5553, self.peer_manager, 2) self.rate_limiter = DummyRateLimiter() self.sd_identifier = StreamDescriptorIdentifier() self.storage = SQLiteStorage(self.tmp_db_dir) self.blob_manager = DiskBlobManager(self.tmp_blob_dir, self.storage) self.prm = OnlyFreePaymentsManager() self.lbry_file_manager = EncryptedFileManager( self.peer_finder, self.rate_limiter, self.blob_manager, self.wallet, self.prm, self.storage, self.sd_identifier) d = self.storage.setup() d.addCallback(lambda _: self.lbry_file_manager.setup()) return d
def setup(self): init_conf_windows() self.db_dir, self.blob_dir = mk_db_and_blob_dir() self.wallet = FakeWallet() self.peer_manager = PeerManager() self.rate_limiter = RateLimiter() if self.ul_rate_limit is not None: self.rate_limiter.set_ul_limit(self.ul_rate_limit) self.prm = OnlyFreePaymentsManager() self.storage = SQLiteStorage(self.db_dir) self.blob_manager = DiskBlobManager(self.blob_dir, self.storage) self.lbry_file_manager = EncryptedFileManager( FakePeerFinder(5553, self.peer_manager, 1), self.rate_limiter, self.blob_manager, self.wallet, self.prm, self.storage, StreamDescriptorIdentifier()) yield self.storage.setup() yield self.blob_manager.setup() yield self.lbry_file_manager.setup() query_handler_factories = { 1: BlobAvailabilityHandlerFactory(self.blob_manager), 2: BlobRequestHandlerFactory(self.blob_manager, self.wallet, self.prm, None), 3: self.wallet.get_wallet_info_query_handler_factory(), } server_factory = ServerProtocolFactory(self.rate_limiter, query_handler_factories, self.peer_manager) self.server_port = reactor.listenTCP(5553, server_factory, interface="localhost") test_file = GenFile(self.file_size, b''.join([chr(i) for i in xrange(0, 64, 6)])) lbry_file = yield create_lbry_file(self.blob_manager, self.storage, self.prm, self.lbry_file_manager, "test_file", test_file) defer.returnValue(lbry_file.sd_hash)
def _setup_other_components(self): log.debug("Setting up the rest of the components") if self.rate_limiter is None: self.rate_limiter = RateLimiter() if self.blob_manager is None: if self.blob_dir is None: self.blob_manager = TempBlobManager(self.hash_announcer) else: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.db_dir) self.rate_limiter.start() d1 = self.blob_manager.setup() d2 = self.wallet.start() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) dl.addErrback(lambda err: err.value.subFailure) return dl
def setUp(self): mocks.mock_conf_settings(self) self.db_dir, self.blob_dir = mk_db_and_blob_dir() self.wallet = FakeWallet() self.peer_manager = PeerManager() self.peer_finder = FakePeerFinder(5553, self.peer_manager, 1) self.rate_limiter = RateLimiter() self.prm = OnlyFreePaymentsManager() self.storage = SQLiteStorage(self.db_dir) self.blob_manager = DiskBlobManager(self.blob_dir, self.storage) self.sd_identifier = StreamDescriptorIdentifier() self.lbry_file_manager = EncryptedFileManager( self.peer_finder, self.rate_limiter, self.blob_manager, self.wallet, self.prm, self.storage, self.sd_identifier) self.uploader = LbryUploader(5209343) self.sd_hash = yield self.uploader.setup() yield self.storage.setup() yield self.blob_manager.setup() yield self.lbry_file_manager.setup() yield add_lbry_file_to_sd_identifier(self.sd_identifier)
def setUp(self): mocks.mock_conf_settings(self) self.session = None self.lbry_file_manager = None self.is_generous = True self.db_dir = tempfile.mkdtemp() self.blob_dir = os.path.join(self.db_dir, "blobfiles") os.mkdir(self.blob_dir) self.dht_node = FakeNode() self.wallet = FakeWallet() self.peer_manager = PeerManager() self.peer_finder = FakePeerFinder(5553, self.peer_manager, 2) self.rate_limiter = DummyRateLimiter() self.sd_identifier = StreamDescriptorIdentifier() self.storage = SQLiteStorage(self.db_dir) self.blob_manager = DiskBlobManager(self.blob_dir, self.storage, self.dht_node._dataStore) self.prm = OnlyFreePaymentsManager() self.lbry_file_manager = EncryptedFileManager( self.peer_finder, self.rate_limiter, self.blob_manager, self.wallet, self.prm, self.storage, self.sd_identifier) d = self.storage.setup() d.addCallback(lambda _: self.lbry_file_manager.setup()) return d
class LBRYSession(object): """This class manages all important services common to any application that uses the network: the hash announcer, which informs other peers that this peer is associated with some hash. Usually, this means this peer has a blob identified by the hash in question, but it can be used for other purposes. the peer finder, which finds peers that are associated with some hash. the blob manager, which keeps track of which blobs have been downloaded and provides access to them, the rate limiter, which attempts to ensure download and upload rates stay below a set maximum, and upnp, which opens holes in compatible firewalls so that remote peers can connect to this peer.""" def __init__(self, blob_data_payment_rate, db_dir=None, lbryid=None, peer_manager=None, dht_node_port=None, known_dht_nodes=None, peer_finder=None, hash_announcer=None, blob_dir=None, blob_manager=None, peer_port=None, use_upnp=True, rate_limiter=None, wallet=None): """ @param blob_data_payment_rate: The default payment rate for blob data @param db_dir: The directory in which levelDB files should be stored @param lbryid: The unique ID of this node @param peer_manager: An object which keeps track of all known peers. If None, a PeerManager will be created @param dht_node_port: The port on which the dht node should listen for incoming connections @param known_dht_nodes: A list of nodes which the dht node should use to bootstrap into the dht @param peer_finder: An object which is used to look up peers that are associated with some hash. If None, a DHTPeerFinder will be used, which looks for peers in the distributed hash table. @param hash_announcer: An object which announces to other peers that this peer is associated with some hash. If None, and peer_port is not None, a DHTHashAnnouncer will be used. If None and peer_port is None, a DummyHashAnnouncer will be used, which will not actually announce anything. @param blob_dir: The directory in which blobs will be stored. If None and blob_manager is None, blobs will be stored in memory only. @param blob_manager: An object which keeps track of downloaded blobs and provides access to them. If None, and blob_dir is not None, a DiskBlobManager will be used, with the given blob_dir. If None and blob_dir is None, a TempBlobManager will be used, which stores blobs in memory only. @param peer_port: The port on which other peers should connect to this peer @param use_upnp: Whether or not to try to open a hole in the firewall so that outside peers can connect to this peer's peer_port and dht_node_port @param rate_limiter: An object which keeps track of the amount of data transferred to and from this peer, and can limit that rate if desired @param wallet: An object which will be used to keep track of expected payments and which will pay peers. If None, a wallet which uses the Point Trader system will be used, which is meant for testing only @return: """ self.db_dir = db_dir self.lbryid = lbryid self.peer_manager = peer_manager self.dht_node_port = dht_node_port self.known_dht_nodes = known_dht_nodes if self.known_dht_nodes is None: self.known_dht_nodes = [] self.peer_finder = peer_finder self.hash_announcer = hash_announcer self.blob_dir = blob_dir self.blob_manager = blob_manager self.peer_port = peer_port self.use_upnp = use_upnp self.rate_limiter = rate_limiter self.external_ip = '127.0.0.1' self.upnp_redirects = [] self.wallet = wallet self.dht_node = None self.base_payment_rate_manager = BasePaymentRateManager(blob_data_payment_rate) def setup(self): """Create the blob directory and database if necessary, start all desired services""" log.debug("Setting up the lbry session") if self.lbryid is None: self.lbryid = generate_id() if self.wallet is None: self.wallet = PTCWallet(self.db_dir) if self.peer_manager is None: self.peer_manager = PeerManager() if self.use_upnp is True: d = self._try_upnp() else: d = defer.succeed(True) if self.peer_finder is None: d.addCallback(lambda _: self._setup_dht()) else: if self.hash_announcer is None and self.peer_port is not None: log.warning("The server has no way to advertise its available blobs.") self.hash_announcer = DummyHashAnnouncer() d.addCallback(lambda _: self._setup_other_components()) return d def shut_down(self): """Stop all services""" ds = [] if self.dht_node is not None: ds.append(defer.maybeDeferred(self.dht_node.stop)) if self.rate_limiter is not None: ds.append(defer.maybeDeferred(self.rate_limiter.stop)) if self.peer_finder is not None: ds.append(defer.maybeDeferred(self.peer_finder.stop)) if self.hash_announcer is not None: ds.append(defer.maybeDeferred(self.hash_announcer.stop)) if self.wallet is not None: ds.append(defer.maybeDeferred(self.wallet.stop)) if self.blob_manager is not None: ds.append(defer.maybeDeferred(self.blob_manager.stop)) if self.use_upnp is True: ds.append(defer.maybeDeferred(self._unset_upnp)) return defer.DeferredList(ds) def _try_upnp(self): log.debug("In _try_upnp") def threaded_try_upnp(): if self.use_upnp is False: log.debug("Not using upnp") return False u = miniupnpc.UPnP() num_devices_found = u.discover() if num_devices_found > 0: u.selectigd() external_ip = u.externalipaddress() if external_ip != '0.0.0.0': self.external_ip = external_ip if self.peer_port is not None: if u.getspecificportmapping(self.peer_port, 'TCP') is None: u.addportmapping(self.peer_port, 'TCP', u.lanaddr, self.peer_port, 'LBRY peer port', '') self.upnp_redirects.append((self.peer_port, 'TCP')) log.info("Set UPnP redirect for TCP port %d", self.peer_port) else: log.warning("UPnP redirect already set for TCP port %d", self.peer_port) if self.dht_node_port is not None: if u.getspecificportmapping(self.dht_node_port, 'UDP') is None: u.addportmapping(self.dht_node_port, 'UDP', u.lanaddr, self.dht_node_port, 'LBRY DHT port', '') self.upnp_redirects.append((self.dht_node_port, 'UDP')) log.info("Set UPnP redirect for UPD port %d", self.dht_node_port) else: log.warning("UPnP redirect already set for UDP port %d", self.dht_node_port) return True return False def upnp_failed(err): log.warning("UPnP failed. Reason: %s", err.getErrorMessage()) return False d = threads.deferToThread(threaded_try_upnp) d.addErrback(upnp_failed) return d def _setup_dht(self): from twisted.internet import reactor log.debug("Starting the dht") def match_port(h, p): return h, p def join_resolved_addresses(result): addresses = [] for success, value in result: if success is True: addresses.append(value) return addresses def start_dht(addresses): self.dht_node.joinNetwork(addresses) self.peer_finder.run_manage_loop() self.hash_announcer.run_manage_loop() return True ds = [] for host, port in self.known_dht_nodes: d = reactor.resolve(host) d.addCallback(match_port, port) ds.append(d) self.dht_node = node.Node(udpPort=self.dht_node_port, lbryid=self.lbryid, externalIP=self.external_ip) self.peer_finder = DHTPeerFinder(self.dht_node, self.peer_manager) if self.hash_announcer is None: self.hash_announcer = DHTHashAnnouncer(self.dht_node, self.peer_port) dl = defer.DeferredList(ds) dl.addCallback(join_resolved_addresses) dl.addCallback(start_dht) return dl def _setup_other_components(self): log.debug("Setting up the rest of the components") if self.rate_limiter is None: self.rate_limiter = RateLimiter() if self.blob_manager is None: if self.blob_dir is None: self.blob_manager = TempBlobManager(self.hash_announcer) else: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.db_dir) self.rate_limiter.start() d1 = self.blob_manager.setup() d2 = self.wallet.start() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) dl.addErrback(lambda err: err.value.subFailure) return dl def _unset_upnp(self): def threaded_unset_upnp(): u = miniupnpc.UPnP() num_devices_found = u.discover() if num_devices_found > 0: u.selectigd() for port, protocol in self.upnp_redirects: if u.getspecificportmapping(port, protocol) is None: log.warning("UPnP redirect for %s %d was removed by something else.", protocol, port) else: u.deleteportmapping(port, protocol) log.info("Removed UPnP redirect for %s %d.", protocol, port) self.upnp_redirects = [] d = threads.deferToThread(threaded_unset_upnp) d.addErrback(lambda err: str(err)) return d
class StreamAvailabilityManager(object): def __init__(self, storage): self.storage = storage self.lbryid = base64.decodestring(LBRYID) self.peer_manager = None self.peer_finder = None self.dht_node = None self.hash_announcer = None self.blob_manager = None self.dht_node_port = conf.settings['dht_node_port'] self.blob_data_dir = conf.settings['data_dir'] self.blob_dir = os.path.join(self.blob_data_dir, conf.settings['BLOBFILES_DIR']) self.peer_port = conf.settings['peer_port'] self.known_dht_nodes = conf.settings['known_dht_nodes'] self.external_ip = '127.0.0.1' def start(self): if self.peer_manager is None: self.peer_manager = PeerManager() def match_port(h, p): return h, p def join_resolved_addresses(result): addresses = [] for success, value in result: if success is True: addresses.append(value) return addresses def start_dht(addresses): log.info("Starting the dht") log.info("lbry id: %s", base64.encodestring(self.lbryid).strip("\n")) self.dht_node.joinNetwork(addresses) self.peer_finder.run_manage_loop() self.hash_announcer.run_manage_loop() ds = [] for host, port in self.known_dht_nodes: d = reactor.resolve(host) d.addCallback(match_port, port) ds.append(d) if self.dht_node is None: self.dht_node = Node(udpPort=self.dht_node_port, lbryid=self.lbryid, externalIP=self.external_ip) if self.peer_finder is None: self.peer_finder = DHTPeerFinder(self.dht_node, self.peer_manager) if self.hash_announcer is None: self.hash_announcer = DHTHashAnnouncer(self.dht_node, self.peer_port) if self.blob_manager is None: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.blob_data_dir) d1 = defer.DeferredList(ds) d1.addCallback(join_resolved_addresses) d1.addCallback(start_dht) d2 = self.blob_manager.setup() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) return dl def stop(self): log.info("Shutting down availability manager") ds = [] if self.blob_manager is not None: ds.append(defer.maybeDeferred(self.blob_manager.stop)) if self.dht_node is not None: ds.append(defer.maybeDeferred(self.dht_node.stop)) if self.peer_finder is not None: ds.append(defer.maybeDeferred(self.peer_finder.stop)) if self.hash_announcer is not None: ds.append(defer.maybeDeferred(self.hash_announcer.stop)) return defer.DeferredList(ds) def get_peers_for_hash(self, blob_hash): return self.peer_finder.find_peers_for_blob(blob_hash)
class BlobManagerTest(unittest.TestCase): @defer.inlineCallbacks def setUp(self): conf.initialize_settings(False) self.blob_dir = tempfile.mkdtemp() self.db_dir = tempfile.mkdtemp() self.bm = DiskBlobManager(self.blob_dir, SQLiteStorage(self.db_dir)) self.peer = Peer('somehost', 22) yield self.bm.storage.setup() @defer.inlineCallbacks def tearDown(self): yield self.bm.stop() yield self.bm.storage.stop() shutil.rmtree(self.blob_dir) shutil.rmtree(self.db_dir) @defer.inlineCallbacks def _create_and_add_blob(self, should_announce=False): # create and add blob to blob manager data_len = random.randint(1, 1000) data = b''.join( random.choice(string.ascii_lowercase).encode() for _ in range(data_len)) hashobj = get_lbry_hash_obj() hashobj.update(data) out = hashobj.hexdigest() blob_hash = out # create new blob yield self.bm.setup() blob = yield self.bm.get_blob(blob_hash, len(data)) writer, finished_d = yield blob.open_for_writing(self.peer) yield writer.write(data) yield self.bm.blob_completed(blob, should_announce) # check to see if blob is there self.assertTrue(os.path.isfile(os.path.join(self.blob_dir, blob_hash))) blobs = yield self.bm.get_all_verified_blobs() self.assertTrue(blob_hash in blobs) defer.returnValue(blob_hash) @defer.inlineCallbacks def test_create_blob(self): blob_hashes = [] # create a bunch of blobs for i in range(0, 10): blob_hash = yield self._create_and_add_blob() blob_hashes.append(blob_hash) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(10, len(blobs)) @defer.inlineCallbacks def test_delete_blob(self): # create blob blob_hash = yield self._create_and_add_blob() blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 1) # delete blob yield self.bm.delete_blobs([blob_hash]) self.assertFalse(os.path.isfile(os.path.join(self.blob_dir, blob_hash))) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 0) blobs = yield self.bm.storage.get_all_blob_hashes() self.assertEqual(len(blobs), 0) self.assertFalse(blob_hash in self.bm.blobs) # delete blob that was already deleted once yield self.bm.delete_blobs([blob_hash]) # delete blob that does not exist, nothing will # happen blob_hash = random_lbry_hash() yield self.bm.delete_blobs([blob_hash]) @defer.inlineCallbacks def test_delete_open_blob(self): # Test that a blob that is opened for writing will not be deleted # create blobs blob_hashes = [] for i in range(0, 10): blob_hash = yield self._create_and_add_blob() blob_hashes.append(blob_hash) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 10) # open the last blob blob = yield self.bm.get_blob(blob_hashes[-1]) w, finished_d = yield blob.open_for_writing(self.peer) # schedule a close, just to leave the reactor clean finished_d.addBoth(lambda x: None) self.addCleanup(w.close) # delete the last blob and check if it still exists yield self.bm.delete_blobs([blob_hash]) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 10) self.assertTrue(blob_hashes[-1] in blobs) self.assertTrue( os.path.isfile(os.path.join(self.blob_dir, blob_hashes[-1]))) @defer.inlineCallbacks def test_should_announce(self): # create blob with should announce blob_hash = yield self._create_and_add_blob(should_announce=True) out = yield self.bm.get_should_announce(blob_hash) self.assertTrue(out) count = yield self.bm.count_should_announce_blobs() self.assertEqual(1, count) # set should announce to False yield self.bm.set_should_announce(blob_hash, should_announce=False) out = yield self.bm.get_should_announce(blob_hash) self.assertFalse(out) count = yield self.bm.count_should_announce_blobs() self.assertEqual(0, count)
def start(self): storage = self.component_manager.get_component(DATABASE_COMPONENT) dht_node = self.component_manager.get_component(DHT_COMPONENT) self.blob_manager = DiskBlobManager(CS.get_blobfiles_dir(), storage, dht_node._dataStore) return self.blob_manager.setup()
class LbryUploader(object): def __init__(self, file_size, ul_rate_limit=None): self.file_size = file_size self.ul_rate_limit = ul_rate_limit self.kill_check = None # these attributes get defined in `start` self.db_dir = None self.blob_dir = None self.wallet = None self.peer_manager = None self.rate_limiter = None self.prm = None self.storage = None self.blob_manager = None self.lbry_file_manager = None self.server_port = None @defer.inlineCallbacks def setup(self): init_conf_windows() self.db_dir, self.blob_dir = mk_db_and_blob_dir() self.wallet = FakeWallet() self.peer_manager = PeerManager() self.rate_limiter = RateLimiter() if self.ul_rate_limit is not None: self.rate_limiter.set_ul_limit(self.ul_rate_limit) self.prm = OnlyFreePaymentsManager() self.storage = SQLiteStorage(self.db_dir) self.blob_manager = DiskBlobManager(self.blob_dir, self.storage) self.lbry_file_manager = EncryptedFileManager( FakePeerFinder(5553, self.peer_manager, 1), self.rate_limiter, self.blob_manager, self.wallet, self.prm, self.storage, StreamDescriptorIdentifier()) yield self.storage.setup() yield self.blob_manager.setup() yield self.lbry_file_manager.setup() query_handler_factories = { 1: BlobAvailabilityHandlerFactory(self.blob_manager), 2: BlobRequestHandlerFactory(self.blob_manager, self.wallet, self.prm, None), 3: self.wallet.get_wallet_info_query_handler_factory(), } server_factory = ServerProtocolFactory(self.rate_limiter, query_handler_factories, self.peer_manager) self.server_port = reactor.listenTCP(5553, server_factory, interface="localhost") test_file = GenFile(self.file_size, bytes(i for i in range(0, 64, 6))) lbry_file = yield create_lbry_file(self.blob_manager, self.storage, self.prm, self.lbry_file_manager, "test_file", test_file) defer.returnValue(lbry_file.sd_hash) @defer.inlineCallbacks def stop(self): lbry_files = self.lbry_file_manager.lbry_files for lbry_file in lbry_files: yield self.lbry_file_manager.delete_lbry_file(lbry_file) yield self.lbry_file_manager.stop() yield self.blob_manager.stop() yield self.storage.stop() self.server_port.stopListening() rm_db_and_blob_dir(self.db_dir, self.blob_dir) if os.path.exists("test_file"): os.remove("test_file")
class BlobManagerTest(unittest.TestCase): def setUp(self): conf.initialize_settings() self.blob_dir = tempfile.mkdtemp() self.db_dir = tempfile.mkdtemp() hash_announcer = DummyHashAnnouncer() self.bm = DiskBlobManager(hash_announcer, self.blob_dir, self.db_dir) self.peer = Peer('somehost', 22) def tearDown(self): self.bm.stop() # BlobFile will try to delete itself in _close_writer # thus when calling rmtree we may get a FileNotFoundError # for the blob file shutil.rmtree(self.blob_dir, ignore_errors=True) shutil.rmtree(self.db_dir) @defer.inlineCallbacks def _create_and_add_blob(self): # create and add blob to blob manager data_len = random.randint(1, 1000) data = ''.join( random.choice(string.lowercase) for data_len in range(data_len)) hashobj = get_lbry_hash_obj() hashobj.update(data) out = hashobj.hexdigest() blob_hash = out # create new blob yield self.bm.setup() blob = yield self.bm.get_blob(blob_hash, len(data)) writer, finished_d = yield blob.open_for_writing(self.peer) yield writer.write(data) yield self.bm.blob_completed(blob) yield self.bm.add_blob_to_upload_history(blob_hash, 'test', len(data)) # check to see if blob is there self.assertTrue(os.path.isfile(os.path.join(self.blob_dir, blob_hash))) blobs = yield self.bm.get_all_verified_blobs() self.assertTrue(blob_hash in blobs) defer.returnValue(blob_hash) @defer.inlineCallbacks def test_create_blob(self): blob_hashes = [] # create a bunch of blobs for i in range(0, 10): blob_hash = yield self._create_and_add_blob() blob_hashes.append(blob_hash) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(10, len(blobs)) @defer.inlineCallbacks def test_delete_blob(self): # create blob blob_hash = yield self._create_and_add_blob() blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 1) # delete blob yield self.bm.delete_blobs([blob_hash]) self.assertFalse(os.path.isfile(os.path.join(self.blob_dir, blob_hash))) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 0) blobs = yield self.bm._get_all_blob_hashes() self.assertEqual(len(blobs), 0) # delete blob that does not exist, nothing will # happen blob_hash = random_lbry_hash() out = yield self.bm.delete_blobs([blob_hash]) @defer.inlineCallbacks def test_delete_open_blob(self): # Test that a blob that is opened for writing will not be deleted # create blobs blob_hashes = [] for i in range(0, 10): blob_hash = yield self._create_and_add_blob() blob_hashes.append(blob_hash) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 10) # open the last blob blob = yield self.bm.get_blob(blob_hashes[-1]) writer, finished_d = yield blob.open_for_writing(self.peer) # delete the last blob and check if it still exists out = yield self.bm.delete_blobs([blob_hash]) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 10) self.assertTrue(blob_hashes[-1] in blobs) self.assertTrue( os.path.isfile(os.path.join(self.blob_dir, blob_hashes[-1])))
class LBRYSession(object): """This class manages all important services common to any application that uses the network: the hash announcer, which informs other peers that this peer is associated with some hash. Usually, this means this peer has a blob identified by the hash in question, but it can be used for other purposes. the peer finder, which finds peers that are associated with some hash. the blob manager, which keeps track of which blobs have been downloaded and provides access to them, the rate limiter, which attempts to ensure download and upload rates stay below a set maximum, and upnp, which opens holes in compatible firewalls so that remote peers can connect to this peer.""" def __init__(self, blob_data_payment_rate, db_dir=None, lbryid=None, peer_manager=None, dht_node_port=None, known_dht_nodes=None, peer_finder=None, hash_announcer=None, blob_dir=None, blob_manager=None, peer_port=None, use_upnp=True, rate_limiter=None, wallet=None, dht_node_class=node.Node): """ @param blob_data_payment_rate: The default payment rate for blob data @param db_dir: The directory in which levelDB files should be stored @param lbryid: The unique ID of this node @param peer_manager: An object which keeps track of all known peers. If None, a PeerManager will be created @param dht_node_port: The port on which the dht node should listen for incoming connections @param known_dht_nodes: A list of nodes which the dht node should use to bootstrap into the dht @param peer_finder: An object which is used to look up peers that are associated with some hash. If None, a DHTPeerFinder will be used, which looks for peers in the distributed hash table. @param hash_announcer: An object which announces to other peers that this peer is associated with some hash. If None, and peer_port is not None, a DHTHashAnnouncer will be used. If None and peer_port is None, a DummyHashAnnouncer will be used, which will not actually announce anything. @param blob_dir: The directory in which blobs will be stored. If None and blob_manager is None, blobs will be stored in memory only. @param blob_manager: An object which keeps track of downloaded blobs and provides access to them. If None, and blob_dir is not None, a DiskBlobManager will be used, with the given blob_dir. If None and blob_dir is None, a TempBlobManager will be used, which stores blobs in memory only. @param peer_port: The port on which other peers should connect to this peer @param use_upnp: Whether or not to try to open a hole in the firewall so that outside peers can connect to this peer's peer_port and dht_node_port @param rate_limiter: An object which keeps track of the amount of data transferred to and from this peer, and can limit that rate if desired @param wallet: An object which will be used to keep track of expected payments and which will pay peers. If None, a wallet which uses the Point Trader system will be used, which is meant for testing only @return: """ self.db_dir = db_dir self.lbryid = lbryid self.peer_manager = peer_manager self.dht_node_port = dht_node_port self.known_dht_nodes = known_dht_nodes if self.known_dht_nodes is None: self.known_dht_nodes = [] self.peer_finder = peer_finder self.hash_announcer = hash_announcer self.blob_dir = blob_dir self.blob_manager = blob_manager self.peer_port = peer_port self.use_upnp = use_upnp self.rate_limiter = rate_limiter self.external_ip = '127.0.0.1' self.upnp_redirects = [] self.wallet = wallet self.dht_node_class = dht_node_class self.dht_node = None self.base_payment_rate_manager = BasePaymentRateManager( blob_data_payment_rate) def setup(self): """Create the blob directory and database if necessary, start all desired services""" log.debug("Setting up the lbry session") if self.lbryid is None: self.lbryid = generate_id() if self.wallet is None: self.wallet = PTCWallet(self.db_dir) if self.peer_manager is None: self.peer_manager = PeerManager() if self.use_upnp is True: d = self._try_upnp() else: d = defer.succeed(True) if self.peer_finder is None: d.addCallback(lambda _: self._setup_dht()) else: if self.hash_announcer is None and self.peer_port is not None: log.warning( "The server has no way to advertise its available blobs.") self.hash_announcer = DummyHashAnnouncer() d.addCallback(lambda _: self._setup_other_components()) return d def shut_down(self): """Stop all services""" ds = [] if self.dht_node is not None: ds.append(defer.maybeDeferred(self.dht_node.stop)) if self.rate_limiter is not None: ds.append(defer.maybeDeferred(self.rate_limiter.stop)) if self.peer_finder is not None: ds.append(defer.maybeDeferred(self.peer_finder.stop)) if self.hash_announcer is not None: ds.append(defer.maybeDeferred(self.hash_announcer.stop)) if self.wallet is not None: ds.append(defer.maybeDeferred(self.wallet.stop)) if self.blob_manager is not None: ds.append(defer.maybeDeferred(self.blob_manager.stop)) if self.use_upnp is True: ds.append(defer.maybeDeferred(self._unset_upnp)) return defer.DeferredList(ds) def _try_upnp(self): log.debug("In _try_upnp") def threaded_try_upnp(): if self.use_upnp is False: log.debug("Not using upnp") return False u = miniupnpc.UPnP() num_devices_found = u.discover() if num_devices_found > 0: u.selectigd() external_ip = u.externalipaddress() if external_ip != '0.0.0.0': self.external_ip = external_ip if self.peer_port is not None: if u.getspecificportmapping(self.peer_port, 'TCP') is None: u.addportmapping(self.peer_port, 'TCP', u.lanaddr, self.peer_port, 'LBRY peer port', '') self.upnp_redirects.append((self.peer_port, 'TCP')) log.info("Set UPnP redirect for TCP port %d", self.peer_port) else: # see comment below log.warning( "UPnP redirect already set for TCP port %d", self.peer_port) self.upnp_redirects.append((self.peer_port, 'TCP')) if self.dht_node_port is not None: if u.getspecificportmapping(self.dht_node_port, 'UDP') is None: u.addportmapping(self.dht_node_port, 'UDP', u.lanaddr, self.dht_node_port, 'LBRY DHT port', '') self.upnp_redirects.append((self.dht_node_port, 'UDP')) log.info("Set UPnP redirect for UPD port %d", self.dht_node_port) else: # TODO: check that the existing redirect was put up by an old lbrynet session before grabbing it # if such a disconnected redirect exists, then upnp won't work unless the redirect is appended # or is torn down and set back up. a bad shutdown of lbrynet could leave such a redirect up # and cause problems on the next start. # this could be problematic if a previous lbrynet session didn't make the redirect, and it was # made by another application log.warning( "UPnP redirect already set for UDP port %d", self.dht_node_port) self.upnp_redirects.append((self.dht_node_port, 'UDP')) return True return False def upnp_failed(err): log.warning("UPnP failed. Reason: %s", err.getErrorMessage()) return False d = threads.deferToThread(threaded_try_upnp) d.addErrback(upnp_failed) return d def _setup_dht(self): from twisted.internet import reactor log.debug("Starting the dht") def match_port(h, p): return h, p def join_resolved_addresses(result): addresses = [] for success, value in result: if success is True: addresses.append(value) return addresses def start_dht(addresses): self.dht_node.joinNetwork(addresses) self.peer_finder.run_manage_loop() self.hash_announcer.run_manage_loop() return True ds = [] for host, port in self.known_dht_nodes: d = reactor.resolve(host) d.addCallback(match_port, port) ds.append(d) self.dht_node = self.dht_node_class(udpPort=self.dht_node_port, lbryid=self.lbryid, externalIP=self.external_ip) self.peer_finder = DHTPeerFinder(self.dht_node, self.peer_manager) if self.hash_announcer is None: self.hash_announcer = DHTHashAnnouncer(self.dht_node, self.peer_port) dl = defer.DeferredList(ds) dl.addCallback(join_resolved_addresses) dl.addCallback(start_dht) return dl def _setup_other_components(self): log.debug("Setting up the rest of the components") if self.rate_limiter is None: self.rate_limiter = RateLimiter() if self.blob_manager is None: if self.blob_dir is None: self.blob_manager = TempBlobManager(self.hash_announcer) else: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.db_dir) self.rate_limiter.start() d1 = self.blob_manager.setup() d2 = self.wallet.start() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) dl.addErrback(lambda err: err.value.subFailure) return dl def _unset_upnp(self): def threaded_unset_upnp(): u = miniupnpc.UPnP() num_devices_found = u.discover() if num_devices_found > 0: u.selectigd() for port, protocol in self.upnp_redirects: if u.getspecificportmapping(port, protocol) is None: log.warning( "UPnP redirect for %s %d was removed by something else.", protocol, port) else: u.deleteportmapping(port, protocol) log.info("Removed UPnP redirect for %s %d.", protocol, port) self.upnp_redirects = [] d = threads.deferToThread(threaded_unset_upnp) d.addErrback(lambda err: str(err)) return d
class CreateEncryptedFileTest(unittest.TestCase): timeout = 5 def setUp(self): mocks.mock_conf_settings(self) self.tmp_db_dir, self.tmp_blob_dir = mk_db_and_blob_dir() self.wallet = FakeWallet() self.peer_manager = PeerManager() self.peer_finder = FakePeerFinder(5553, self.peer_manager, 2) self.rate_limiter = DummyRateLimiter() self.sd_identifier = StreamDescriptorIdentifier() self.storage = SQLiteStorage(self.tmp_db_dir) self.blob_manager = DiskBlobManager(self.tmp_blob_dir, self.storage) self.prm = OnlyFreePaymentsManager() self.lbry_file_manager = EncryptedFileManager( self.peer_finder, self.rate_limiter, self.blob_manager, self.wallet, self.prm, self.storage, self.sd_identifier) d = self.storage.setup() d.addCallback(lambda _: self.lbry_file_manager.setup()) return d @defer.inlineCallbacks def tearDown(self): yield self.lbry_file_manager.stop() yield self.blob_manager.stop() yield self.storage.stop() rm_db_and_blob_dir(self.tmp_db_dir, self.tmp_blob_dir) @defer.inlineCallbacks def create_file(self, filename): handle = mocks.GenFile(3 * MB, b'1') key = b'2' * (AES.block_size // 8) out = yield EncryptedFileCreator.create_lbry_file( self.blob_manager, self.storage, self.prm, self.lbry_file_manager, filename, handle, key, iv_generator()) defer.returnValue(out) @defer.inlineCallbacks def test_can_create_file(self): expected_stream_hash = "41e6b247d923d191b154fb6f1b8529d6ddd6a73d65c35" \ "7b1acb742dd83151fb66393a7709e9f346260a4f4db6de10c25" expected_sd_hash = "40c485432daec586c1a2d247e6c08d137640a5af6e81f3f652" \ "3e62e81a2e8945b0db7c94f1852e70e371d917b994352c" filename = 'test.file' lbry_file = yield self.create_file(filename) sd_hash = yield self.storage.get_sd_blob_hash_for_stream( lbry_file.stream_hash) # read the sd blob file sd_blob = self.blob_manager.blobs[sd_hash] sd_reader = BlobStreamDescriptorReader(sd_blob) sd_file_info = yield sd_reader.get_info() # this comes from the database, the blobs returned are sorted sd_info = yield get_sd_info(self.storage, lbry_file.stream_hash, include_blobs=True) self.maxDiff = None unicode_sd_info = json.loads( json.dumps(sd_info, sort_keys=True, cls=JSONBytesEncoder)) self.assertDictEqual(unicode_sd_info, sd_file_info) self.assertEqual(sd_info['stream_hash'], expected_stream_hash) self.assertEqual(len(sd_info['blobs']), 3) self.assertNotEqual(sd_info['blobs'][0]['length'], 0) self.assertNotEqual(sd_info['blobs'][1]['length'], 0) self.assertEqual(sd_info['blobs'][2]['length'], 0) self.assertEqual(expected_stream_hash, lbry_file.stream_hash) self.assertEqual(sd_hash, lbry_file.sd_hash) self.assertEqual(sd_hash, expected_sd_hash) blobs = yield self.blob_manager.get_all_verified_blobs() self.assertEqual(3, len(blobs)) num_should_announce_blobs = yield self.blob_manager.count_should_announce_blobs( ) self.assertEqual(2, num_should_announce_blobs) @defer.inlineCallbacks def test_can_create_file_with_unicode_filename(self): expected_stream_hash = ( 'd1da4258f3ce12edb91d7e8e160d091d3ab1432c2e55a6352dce0' '2fd5adb86fe144e93e110075b5865fff8617776c6c0') filename = '☃.file' lbry_file = yield self.create_file(filename) self.assertEqual(expected_stream_hash, lbry_file.stream_hash)
class Session(object): """This class manages all important services common to any application that uses the network. the hash announcer, which informs other peers that this peer is associated with some hash. Usually, this means this peer has a blob identified by the hash in question, but it can be used for other purposes. the peer finder, which finds peers that are associated with some hash. the blob manager, which keeps track of which blobs have been downloaded and provides access to them, the rate limiter, which attempts to ensure download and upload rates stay below a set maximum upnp, which opens holes in compatible firewalls so that remote peers can connect to this peer. """ def __init__(self, blob_data_payment_rate, db_dir=None, node_id=None, peer_manager=None, dht_node_port=None, known_dht_nodes=None, peer_finder=None, hash_announcer=None, blob_dir=None, blob_manager=None, peer_port=None, use_upnp=True, rate_limiter=None, wallet=None, dht_node_class=node.Node, blob_tracker_class=None, payment_rate_manager_class=None, is_generous=True, external_ip=None): """@param blob_data_payment_rate: The default payment rate for blob data @param db_dir: The directory in which levelDB files should be stored @param node_id: The unique ID of this node @param peer_manager: An object which keeps track of all known peers. If None, a PeerManager will be created @param dht_node_port: The port on which the dht node should listen for incoming connections @param known_dht_nodes: A list of nodes which the dht node should use to bootstrap into the dht @param peer_finder: An object which is used to look up peers that are associated with some hash. If None, a DHTPeerFinder will be used, which looks for peers in the distributed hash table. @param hash_announcer: An object which announces to other peers that this peer is associated with some hash. If None, and peer_port is not None, a DHTHashAnnouncer will be used. If None and peer_port is None, a DummyHashAnnouncer will be used, which will not actually announce anything. @param blob_dir: The directory in which blobs will be stored. If None and blob_manager is None, blobs will be stored in memory only. @param blob_manager: An object which keeps track of downloaded blobs and provides access to them. If None, and blob_dir is not None, a DiskBlobManager will be used, with the given blob_dir. If None and blob_dir is None, a TempBlobManager will be used, which stores blobs in memory only. @param peer_port: The port on which other peers should connect to this peer @param use_upnp: Whether or not to try to open a hole in the firewall so that outside peers can connect to this peer's peer_port and dht_node_port @param rate_limiter: An object which keeps track of the amount of data transferred to and from this peer, and can limit that rate if desired @param wallet: An object which will be used to keep track of expected payments and which will pay peers. If None, a wallet which uses the Point Trader system will be used, which is meant for testing only """ self.db_dir = db_dir self.node_id = node_id self.peer_manager = peer_manager self.dht_node_port = dht_node_port self.known_dht_nodes = known_dht_nodes if self.known_dht_nodes is None: self.known_dht_nodes = [] self.peer_finder = peer_finder self.hash_announcer = hash_announcer self.blob_dir = blob_dir self.blob_manager = blob_manager self.blob_tracker = None self.blob_tracker_class = blob_tracker_class or BlobAvailabilityTracker self.peer_port = peer_port self.use_upnp = use_upnp self.rate_limiter = rate_limiter self.external_ip = external_ip self.upnp_redirects = [] self.wallet = wallet self.dht_node_class = dht_node_class self.dht_node = None self.base_payment_rate_manager = BasePaymentRateManager(blob_data_payment_rate) self.payment_rate_manager = None self.payment_rate_manager_class = payment_rate_manager_class or NegotiatedPaymentRateManager self.is_generous = is_generous def setup(self): """Create the blob directory and database if necessary, start all desired services""" log.debug("Starting session.") if self.node_id is None: self.node_id = generate_id() if self.wallet is None: from lbrynet.core.PTCWallet import PTCWallet self.wallet = PTCWallet(self.db_dir) if self.peer_manager is None: self.peer_manager = PeerManager() if self.use_upnp is True: d = self._try_upnp() else: d = defer.succeed(True) if self.peer_finder is None: d.addCallback(lambda _: self._setup_dht()) else: if self.hash_announcer is None and self.peer_port is not None: log.warning("The server has no way to advertise its available blobs.") self.hash_announcer = DummyHashAnnouncer() d.addCallback(lambda _: self._setup_other_components()) return d def shut_down(self): """Stop all services""" log.info('Stopping session.') ds = [] if self.blob_tracker is not None: ds.append(defer.maybeDeferred(self.blob_tracker.stop)) if self.dht_node is not None: ds.append(defer.maybeDeferred(self.dht_node.stop)) if self.rate_limiter is not None: ds.append(defer.maybeDeferred(self.rate_limiter.stop)) if self.peer_finder is not None: ds.append(defer.maybeDeferred(self.peer_finder.stop)) if self.hash_announcer is not None: ds.append(defer.maybeDeferred(self.hash_announcer.stop)) if self.wallet is not None: ds.append(defer.maybeDeferred(self.wallet.stop)) if self.blob_manager is not None: ds.append(defer.maybeDeferred(self.blob_manager.stop)) if self.use_upnp is True: ds.append(defer.maybeDeferred(self._unset_upnp)) return defer.DeferredList(ds) def _try_upnp(self): log.debug("In _try_upnp") def get_free_port(upnp, port, protocol): # returns an existing mapping if it exists mapping = upnp.getspecificportmapping(port, protocol) if not mapping: return port if upnp.lanaddr == mapping[0]: return mapping return get_free_port(upnp, port + 1, protocol) def get_port_mapping(upnp, internal_port, protocol, description): # try to map to the requested port, if there is already a mapping use the next external # port available if protocol not in ['UDP', 'TCP']: raise Exception("invalid protocol") external_port = get_free_port(upnp, internal_port, protocol) if isinstance(external_port, tuple): log.info("Found existing UPnP redirect %s:%i (%s) to %s:%i, using it", self.external_ip, external_port[1], protocol, upnp.lanaddr, internal_port) return external_port[1], protocol upnp.addportmapping(external_port, protocol, upnp.lanaddr, internal_port, description, '') log.info("Set UPnP redirect %s:%i (%s) to %s:%i", self.external_ip, external_port, protocol, upnp.lanaddr, internal_port) return external_port, protocol def threaded_try_upnp(): if self.use_upnp is False: log.debug("Not using upnp") return False u = miniupnpc.UPnP() num_devices_found = u.discover() if num_devices_found > 0: u.selectigd() external_ip = u.externalipaddress() if external_ip != '0.0.0.0' and not self.external_ip: # best not to rely on this external ip, the router can be behind layers of NATs self.external_ip = external_ip if self.peer_port: self.upnp_redirects.append(get_port_mapping(u, self.peer_port, 'TCP', 'LBRY peer port')) if self.dht_node_port: self.upnp_redirects.append(get_port_mapping(u, self.dht_node_port, 'UDP', 'LBRY DHT port')) return True return False def upnp_failed(err): log.warning("UPnP failed. Reason: %s", err.getErrorMessage()) return False d = threads.deferToThread(threaded_try_upnp) d.addErrback(upnp_failed) return d def _setup_dht(self): from twisted.internet import reactor log.info("Starting DHT") def join_resolved_addresses(result): addresses = [] for success, value in result: if success is True: addresses.append(value) return addresses def start_dht(join_network_result): self.peer_finder.run_manage_loop() self.hash_announcer.run_manage_loop() return True ds = [] for host, port in self.known_dht_nodes: d = reactor.resolve(host) d.addCallback(lambda h: (h, port)) # match host to port ds.append(d) self.dht_node = self.dht_node_class( udpPort=self.dht_node_port, node_id=self.node_id, externalIP=self.external_ip, peerPort=self.peer_port ) self.peer_finder = DHTPeerFinder(self.dht_node, self.peer_manager) if self.hash_announcer is None: self.hash_announcer = DHTHashAnnouncer(self.dht_node, self.peer_port) dl = defer.DeferredList(ds) dl.addCallback(join_resolved_addresses) dl.addCallback(self.dht_node.joinNetwork) dl.addCallback(start_dht) return dl def _setup_other_components(self): log.debug("Setting up the rest of the components") if self.rate_limiter is None: self.rate_limiter = RateLimiter() if self.blob_manager is None: if self.blob_dir is None: raise Exception( "TempBlobManager is no longer supported, specify BlobManager or db_dir") else: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.db_dir) if self.blob_tracker is None: self.blob_tracker = self.blob_tracker_class(self.blob_manager, self.peer_finder, self.dht_node) if self.payment_rate_manager is None: self.payment_rate_manager = self.payment_rate_manager_class( self.base_payment_rate_manager, self.blob_tracker, self.is_generous) self.rate_limiter.start() d1 = self.blob_manager.setup() d2 = self.wallet.start() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) dl.addCallback(lambda _: self.blob_tracker.start()) return dl def _unset_upnp(self): log.info("Unsetting upnp for session") def threaded_unset_upnp(): u = miniupnpc.UPnP() num_devices_found = u.discover() if num_devices_found > 0: u.selectigd() for port, protocol in self.upnp_redirects: if u.getspecificportmapping(port, protocol) is None: log.warning( "UPnP redirect for %s %d was removed by something else.", protocol, port) else: u.deleteportmapping(port, protocol) log.info("Removed UPnP redirect for %s %d.", protocol, port) self.upnp_redirects = [] d = threads.deferToThread(threaded_unset_upnp) d.addErrback(lambda err: str(err)) return d
class CreateEncryptedFileTest(unittest.TestCase): timeout = 5 def setUp(self): mocks.mock_conf_settings(self) self.tmp_db_dir, self.tmp_blob_dir = mk_db_and_blob_dir() self.wallet = FakeWallet() self.peer_manager = PeerManager() self.peer_finder = FakePeerFinder(5553, self.peer_manager, 2) self.rate_limiter = DummyRateLimiter() self.sd_identifier = StreamDescriptorIdentifier() self.storage = SQLiteStorage(self.tmp_db_dir) self.blob_manager = DiskBlobManager(self.tmp_blob_dir, self.storage) self.prm = OnlyFreePaymentsManager() self.lbry_file_manager = EncryptedFileManager( self.peer_finder, self.rate_limiter, self.blob_manager, self.wallet, self.prm, self.storage, self.sd_identifier) d = self.storage.setup() d.addCallback(lambda _: self.lbry_file_manager.setup()) return d @defer.inlineCallbacks def tearDown(self): yield self.lbry_file_manager.stop() yield self.blob_manager.stop() yield self.storage.stop() rm_db_and_blob_dir(self.tmp_db_dir, self.tmp_blob_dir) @defer.inlineCallbacks def create_file(self, filename): handle = mocks.GenFile(3 * MB, '1') key = '2' * (AES.block_size / 8) out = yield EncryptedFileCreator.create_lbry_file( self.blob_manager, self.storage, self.prm, self.lbry_file_manager, filename, handle, key, iv_generator()) defer.returnValue(out) @defer.inlineCallbacks def test_can_create_file(self): expected_stream_hash = "41e6b247d923d191b154fb6f1b8529d6ddd6a73d65c35" \ "7b1acb742dd83151fb66393a7709e9f346260a4f4db6de10c25" expected_sd_hash = "db043b44384c149126685990f6bb6563aa565ae331303d522" \ "c8728fe0534dd06fbcacae92b0891787ad9b68ffc8d20c1" filename = 'test.file' lbry_file = yield self.create_file(filename) sd_hash = yield self.storage.get_sd_blob_hash_for_stream( lbry_file.stream_hash) # read the sd blob file sd_blob = self.blob_manager.blobs[sd_hash] sd_reader = BlobStreamDescriptorReader(sd_blob) sd_file_info = yield sd_reader.get_info() # this comes from the database, the blobs returned are sorted sd_info = yield get_sd_info(self.storage, lbry_file.stream_hash, include_blobs=True) self.assertDictEqual(sd_info, sd_file_info) self.assertListEqual(sd_info['blobs'], sd_file_info['blobs']) self.assertEqual(sd_info['stream_hash'], expected_stream_hash) self.assertEqual(len(sd_info['blobs']), 3) self.assertNotEqual(sd_info['blobs'][0]['length'], 0) self.assertNotEqual(sd_info['blobs'][1]['length'], 0) self.assertEqual(sd_info['blobs'][2]['length'], 0) self.assertEqual(expected_stream_hash, lbry_file.stream_hash) self.assertEqual(sd_hash, lbry_file.sd_hash) self.assertEqual(sd_hash, expected_sd_hash) blobs = yield self.blob_manager.get_all_verified_blobs() self.assertEqual(3, len(blobs)) num_should_announce_blobs = yield self.blob_manager.count_should_announce_blobs( ) self.assertEqual(2, num_should_announce_blobs) @defer.inlineCallbacks def test_can_create_file_with_unicode_filename(self): expected_stream_hash = ( 'd1da4258f3ce12edb91d7e8e160d091d3ab1432c2e55a6352dce0' '2fd5adb86fe144e93e110075b5865fff8617776c6c0') filename = u'☃.file' lbry_file = yield self.create_file(filename) self.assertEqual(expected_stream_hash, lbry_file.stream_hash)
class Session(object): """This class manages all important services common to any application that uses the network. the hash announcer, which informs other peers that this peer is associated with some hash. Usually, this means this peer has a blob identified by the hash in question, but it can be used for other purposes. the peer finder, which finds peers that are associated with some hash. the blob manager, which keeps track of which blobs have been downloaded and provides access to them, the rate limiter, which attempts to ensure download and upload rates stay below a set maximum upnp, which opens holes in compatible firewalls so that remote peers can connect to this peer. """ def __init__(self, blob_data_payment_rate, db_dir=None, node_id=None, peer_manager=None, dht_node_port=None, known_dht_nodes=None, peer_finder=None, hash_announcer=None, blob_dir=None, blob_manager=None, peer_port=None, use_upnp=True, rate_limiter=None, wallet=None, dht_node_class=node.Node, blob_tracker_class=None, payment_rate_manager_class=None, is_generous=True, external_ip=None): """@param blob_data_payment_rate: The default payment rate for blob data @param db_dir: The directory in which levelDB files should be stored @param node_id: The unique ID of this node @param peer_manager: An object which keeps track of all known peers. If None, a PeerManager will be created @param dht_node_port: The port on which the dht node should listen for incoming connections @param known_dht_nodes: A list of nodes which the dht node should use to bootstrap into the dht @param peer_finder: An object which is used to look up peers that are associated with some hash. If None, a DHTPeerFinder will be used, which looks for peers in the distributed hash table. @param hash_announcer: An object which announces to other peers that this peer is associated with some hash. If None, and peer_port is not None, a DHTHashAnnouncer will be used. If None and peer_port is None, a DummyHashAnnouncer will be used, which will not actually announce anything. @param blob_dir: The directory in which blobs will be stored. If None and blob_manager is None, blobs will be stored in memory only. @param blob_manager: An object which keeps track of downloaded blobs and provides access to them. If None, and blob_dir is not None, a DiskBlobManager will be used, with the given blob_dir. If None and blob_dir is None, a TempBlobManager will be used, which stores blobs in memory only. @param peer_port: The port on which other peers should connect to this peer @param use_upnp: Whether or not to try to open a hole in the firewall so that outside peers can connect to this peer's peer_port and dht_node_port @param rate_limiter: An object which keeps track of the amount of data transferred to and from this peer, and can limit that rate if desired @param wallet: An object which will be used to keep track of expected payments and which will pay peers. If None, a wallet which uses the Point Trader system will be used, which is meant for testing only """ self.db_dir = db_dir self.node_id = node_id self.peer_manager = peer_manager self.dht_node_port = dht_node_port self.known_dht_nodes = known_dht_nodes if self.known_dht_nodes is None: self.known_dht_nodes = [] self.peer_finder = peer_finder self.hash_announcer = hash_announcer self.blob_dir = blob_dir self.blob_manager = blob_manager self.blob_tracker = None self.blob_tracker_class = blob_tracker_class or BlobAvailabilityTracker self.peer_port = peer_port self.use_upnp = use_upnp self.rate_limiter = rate_limiter self.external_ip = external_ip self.upnp_redirects = [] self.wallet = wallet self.dht_node_class = dht_node_class self.dht_node = None self.base_payment_rate_manager = BasePaymentRateManager( blob_data_payment_rate) self.payment_rate_manager = None self.payment_rate_manager_class = payment_rate_manager_class or NegotiatedPaymentRateManager self.is_generous = is_generous def setup(self): """Create the blob directory and database if necessary, start all desired services""" log.debug("Starting session.") if self.node_id is None: self.node_id = generate_id() if self.wallet is None: from lbrynet.core.PTCWallet import PTCWallet self.wallet = PTCWallet(self.db_dir) if self.peer_manager is None: self.peer_manager = PeerManager() if self.use_upnp is True: d = self._try_upnp() else: d = defer.succeed(True) if self.peer_finder is None: d.addCallback(lambda _: self._setup_dht()) else: if self.hash_announcer is None and self.peer_port is not None: log.warning( "The server has no way to advertise its available blobs.") self.hash_announcer = DummyHashAnnouncer() d.addCallback(lambda _: self._setup_other_components()) return d def shut_down(self): """Stop all services""" log.info('Stopping session.') ds = [] if self.blob_tracker is not None: ds.append(defer.maybeDeferred(self.blob_tracker.stop)) if self.dht_node is not None: ds.append(defer.maybeDeferred(self.dht_node.stop)) if self.rate_limiter is not None: ds.append(defer.maybeDeferred(self.rate_limiter.stop)) if self.peer_finder is not None: ds.append(defer.maybeDeferred(self.peer_finder.stop)) if self.hash_announcer is not None: ds.append(defer.maybeDeferred(self.hash_announcer.stop)) if self.wallet is not None: ds.append(defer.maybeDeferred(self.wallet.stop)) if self.blob_manager is not None: ds.append(defer.maybeDeferred(self.blob_manager.stop)) if self.use_upnp is True: ds.append(defer.maybeDeferred(self._unset_upnp)) return defer.DeferredList(ds) def _try_upnp(self): log.debug("In _try_upnp") def get_free_port(upnp, port, protocol): # returns an existing mapping if it exists mapping = upnp.getspecificportmapping(port, protocol) if not mapping: return port if upnp.lanaddr == mapping[0]: return mapping return get_free_port(upnp, port + 1, protocol) def get_port_mapping(upnp, internal_port, protocol, description): # try to map to the requested port, if there is already a mapping use the next external # port available if protocol not in ['UDP', 'TCP']: raise Exception("invalid protocol") external_port = get_free_port(upnp, internal_port, protocol) if isinstance(external_port, tuple): log.info( "Found existing UPnP redirect %s:%i (%s) to %s:%i, using it", self.external_ip, external_port[1], protocol, upnp.lanaddr, internal_port) return external_port[1], protocol upnp.addportmapping(external_port, protocol, upnp.lanaddr, internal_port, description, '') log.info("Set UPnP redirect %s:%i (%s) to %s:%i", self.external_ip, external_port, protocol, upnp.lanaddr, internal_port) return external_port, protocol def threaded_try_upnp(): if self.use_upnp is False: log.debug("Not using upnp") return False u = miniupnpc.UPnP() num_devices_found = u.discover() if num_devices_found > 0: u.selectigd() external_ip = u.externalipaddress() if external_ip != '0.0.0.0' and not self.external_ip: # best not to rely on this external ip, the router can be behind layers of NATs self.external_ip = external_ip if self.peer_port: self.upnp_redirects.append( get_port_mapping(u, self.peer_port, 'TCP', 'LBRY peer port')) if self.dht_node_port: self.upnp_redirects.append( get_port_mapping(u, self.dht_node_port, 'UDP', 'LBRY DHT port')) return True return False def upnp_failed(err): log.warning("UPnP failed. Reason: %s", err.getErrorMessage()) return False d = threads.deferToThread(threaded_try_upnp) d.addErrback(upnp_failed) return d def _setup_dht(self): from twisted.internet import reactor log.info("Starting DHT") def join_resolved_addresses(result): addresses = [] for success, value in result: if success is True: addresses.append(value) return addresses def start_dht(join_network_result): self.peer_finder.run_manage_loop() self.hash_announcer.run_manage_loop() return True ds = [] for host, port in self.known_dht_nodes: d = reactor.resolve(host) d.addCallback(lambda h: (h, port)) # match host to port ds.append(d) self.dht_node = self.dht_node_class(udpPort=self.dht_node_port, node_id=self.node_id, externalIP=self.external_ip, peerPort=self.peer_port) self.peer_finder = DHTPeerFinder(self.dht_node, self.peer_manager) if self.hash_announcer is None: self.hash_announcer = DHTHashAnnouncer(self.dht_node, self.peer_port) dl = defer.DeferredList(ds) dl.addCallback(join_resolved_addresses) dl.addCallback(self.dht_node.joinNetwork) dl.addCallback(start_dht) return dl def _setup_other_components(self): log.debug("Setting up the rest of the components") if self.rate_limiter is None: self.rate_limiter = RateLimiter() if self.blob_manager is None: if self.blob_dir is None: raise Exception( "TempBlobManager is no longer supported, specify BlobManager or db_dir" ) else: self.blob_manager = DiskBlobManager(self.hash_announcer, self.blob_dir, self.db_dir) if self.blob_tracker is None: self.blob_tracker = self.blob_tracker_class( self.blob_manager, self.peer_finder, self.dht_node) if self.payment_rate_manager is None: self.payment_rate_manager = self.payment_rate_manager_class( self.base_payment_rate_manager, self.blob_tracker, self.is_generous) self.rate_limiter.start() d1 = self.blob_manager.setup() d2 = self.wallet.start() dl = defer.DeferredList([d1, d2], fireOnOneErrback=True, consumeErrors=True) dl.addCallback(lambda _: self.blob_tracker.start()) return dl def _unset_upnp(self): log.info("Unsetting upnp for session") def threaded_unset_upnp(): u = miniupnpc.UPnP() num_devices_found = u.discover() if num_devices_found > 0: u.selectigd() for port, protocol in self.upnp_redirects: if u.getspecificportmapping(port, protocol) is None: log.warning( "UPnP redirect for %s %d was removed by something else.", protocol, port) else: u.deleteportmapping(port, protocol) log.info("Removed UPnP redirect for %s %d.", protocol, port) self.upnp_redirects = [] d = threads.deferToThread(threaded_unset_upnp) d.addErrback(lambda err: str(err)) return d
class BlobManagerTest(unittest.TestCase): def setUp(self): conf.initialize_settings() self.blob_dir = tempfile.mkdtemp() self.db_dir = tempfile.mkdtemp() hash_announcer = DummyHashAnnouncer() self.bm = DiskBlobManager(hash_announcer, self.blob_dir, self.db_dir) self.peer = Peer('somehost', 22) def tearDown(self): self.bm.stop() # BlobFile will try to delete itself in _close_writer # thus when calling rmtree we may get a FileNotFoundError # for the blob file shutil.rmtree(self.blob_dir, ignore_errors=True) shutil.rmtree(self.db_dir) @defer.inlineCallbacks def _create_and_add_blob(self, should_announce=False): # create and add blob to blob manager data_len = random.randint(1, 1000) data = ''.join(random.choice(string.lowercase) for data_len in range(data_len)) hashobj = get_lbry_hash_obj() hashobj.update(data) out = hashobj.hexdigest() blob_hash = out # create new blob yield self.bm.setup() blob = yield self.bm.get_blob(blob_hash, len(data)) writer, finished_d = yield blob.open_for_writing(self.peer) yield writer.write(data) yield self.bm.blob_completed(blob, should_announce) yield self.bm.add_blob_to_upload_history(blob_hash, 'test', len(data)) # check to see if blob is there self.assertTrue(os.path.isfile(os.path.join(self.blob_dir, blob_hash))) blobs = yield self.bm.get_all_verified_blobs() self.assertTrue(blob_hash in blobs) defer.returnValue(blob_hash) @defer.inlineCallbacks def test_create_blob(self): blob_hashes = [] # create a bunch of blobs for i in range(0, 10): blob_hash = yield self._create_and_add_blob() blob_hashes.append(blob_hash) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(10, len(blobs)) @defer.inlineCallbacks def test_delete_blob(self): # create blob blob_hash = yield self._create_and_add_blob() blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 1) # delete blob yield self.bm.delete_blobs([blob_hash]) self.assertFalse(os.path.isfile(os.path.join(self.blob_dir, blob_hash))) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 0) blobs = yield self.bm._get_all_blob_hashes() self.assertEqual(len(blobs), 0) self.assertFalse(blob_hash in self.bm.blobs) # delete blob that was already deleted once out = yield self.bm.delete_blobs([blob_hash]) # delete blob that does not exist, nothing will # happen blob_hash = random_lbry_hash() out = yield self.bm.delete_blobs([blob_hash]) @defer.inlineCallbacks def test_delete_open_blob(self): # Test that a blob that is opened for writing will not be deleted # create blobs blob_hashes = [] for i in range(0, 10): blob_hash = yield self._create_and_add_blob() blob_hashes.append(blob_hash) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 10) # open the last blob blob = yield self.bm.get_blob(blob_hashes[-1]) writer, finished_d = yield blob.open_for_writing(self.peer) # delete the last blob and check if it still exists out = yield self.bm.delete_blobs([blob_hash]) blobs = yield self.bm.get_all_verified_blobs() self.assertEqual(len(blobs), 10) self.assertTrue(blob_hashes[-1] in blobs) self.assertTrue(os.path.isfile(os.path.join(self.blob_dir, blob_hashes[-1]))) @defer.inlineCallbacks def test_should_announce(self): # create blob with should announce blob_hash = yield self._create_and_add_blob(should_announce=True) out = yield self.bm.get_should_announce(blob_hash) self.assertTrue(out) count = yield self.bm.count_should_announce_blobs() self.assertEqual(1, count) # set should annouce to False out = yield self.bm.set_should_announce(blob_hash, should_announce=False) out = yield self.bm.get_should_announce(blob_hash) self.assertFalse(out) count = yield self.bm.count_should_announce_blobs() self.assertEqual(0, count)