def create_node(self, *args, **kwargs): metadata_store = MetadataStore( Path(self.temporary_directory()) / f"{self.count}.db", Path(self.temporary_directory()), default_eccrypto.generate_key("curve25519"), disable_sync=True, ) self.metadata_store_set.add(metadata_store) kwargs['metadata_store'] = metadata_store kwargs['rqc_settings'] = RemoteQueryCommunitySettings() node = super().create_node(*args, **kwargs) self.count += 1 return node
def get_dest_dir(self): """ Gets the directory where to save this Download. """ dest_dir = self.config['download_defaults']['saveas'] if not dest_dir: dest_dir = get_default_dest_dir() self.set_dest_dir(dest_dir) # This is required to support relative paths if not Path(dest_dir).is_absolute(): dest_dir = self.state_dir / dest_dir return Path(dest_dir)
def create_node(self, *args, **kwargs): mds = MetadataStore( Path(self.temporary_directory()) / ("%d.db" % self.count), Path(self.temporary_directory()), default_eccrypto.generate_key(u"curve25519")) torrent_checker = MockObject() torrent_checker.torrents_checked = set() return MockIPv8(u"curve25519", PopularityCommunity, metadata_store=mds, torrent_checker=torrent_checker)
def create_node(self, *args, **kwargs): metadata_store = MetadataStore( Path(self.temporary_directory()) / f"{self.count}.db", Path(self.temporary_directory()), default_eccrypto.generate_key("curve25519"), disable_sync=True, ) kwargs['metadata_store'] = metadata_store with mock.patch( 'tribler_core.modules.metadata_store.community.gigachannel_community.DiscoveryBooster' ): node = super().create_node(*args, **kwargs) self.count += 1 return node
def create_node(self, *args, **kwargs): self.metadata_store = MetadataStore( Path(self.temporary_directory()) / "mds.db", Path(self.temporary_directory()), default_eccrypto.generate_key("curve25519"), disable_sync=True, ) self.tags_db = TagDatabase( str(Path(self.temporary_directory()) / "tags.db")) kwargs['metadata_store'] = self.metadata_store kwargs['tags_db'] = self.tags_db kwargs['rqc_settings'] = RemoteQueryCommunitySettings() return super().create_node(*args, **kwargs)
def create_node(self, *args, **kwargs): metadata_store = MetadataStore( Path(self.temporary_directory()) / f"{self.count}.db", Path(self.temporary_directory()), default_eccrypto.generate_key("curve25519"), disable_sync=True, ) self.metadata_store_set.add(metadata_store) kwargs['metadata_store'] = metadata_store kwargs['settings'] = ChantSettings() kwargs['rqc_settings'] = RemoteQueryCommunitySettings() with mock.patch('tribler_core.components.gigachannel.community.gigachannel_community.DiscoveryBooster'): node = super().create_node(*args, **kwargs) self.count += 1 return node
def create_node(self): config = TunnelCommunitySettings() mock_ipv8 = MockIPv8("curve25519", TriblerTunnelCommunity, settings={'remove_tunnel_delay': 0}, config=config, exitnode_cache=Path(self.temporary_directory()) / "exitnode_cache.dat") mock_ipv8.overlay.settings.max_circuits = 1 db = BandwidthDatabase( db_path=MEMORY_DB, my_pub_key=mock_ipv8.my_peer.public_key.key_to_bin()) # Load the bandwidth accounting community mock_ipv8.overlay.bandwidth_community = BandwidthAccountingCommunity( mock_ipv8.my_peer, mock_ipv8.endpoint, mock_ipv8.network, settings=BandwidthAccountingSettings(), database=db) mock_ipv8.overlay.dht_provider = MockDHTProvider( Peer(mock_ipv8.overlay.my_peer.key, mock_ipv8.overlay.my_estimated_wan)) return mock_ipv8
def _existing_files(path_list): for path in path_list: path = Path(path) if not path.exists(): raise OSError(f'Path does not exist: {path}') elif path.is_file(): yield path
def get_files_info_json(self, download): """ Return file information as JSON from a specified download. """ files_json = [] files_completion = dict( (name, progress) for name, progress in download.get_state().get_files_completion()) selected_files = download.config.get_selected_files() file_index = 0 for fn, size in download.get_def().get_files_with_length(): files_json.append({ "index": file_index, "name": str(Path(fn)), "size": size, "included": (file_index in selected_files or not selected_files), "progress": files_completion.get(fn, 0.0) }) file_index += 1 return files_json
def test_multiple_squashed_commit_and_read(metadata_store): """ Test committing entries into several squashed blobs and reading them back """ metadata_store.ChannelMetadata._CHUNK_SIZE_LIMIT = 500 num_entries = 10 channel = metadata_store.ChannelMetadata.create_channel('testchan') md_list = [ metadata_store.TorrentMetadata(origin_id=channel.id_, title='test' + str(x), status=NEW, infohash=random_infohash()) for x in range(0, num_entries) ] channel.commit_channel_torrent() channel.local_version = 0 for md in md_list: md.delete() channel_dir = Path( metadata_store.ChannelMetadata._channels_dir) / channel.dirname assert len(os.listdir(channel_dir) ) > 1 # make sure it was broken into more than one .mdblob file metadata_store.process_channel_dir(channel_dir, channel.public_key, channel.id_, skip_personal_metadata_payload=False) assert num_entries == len(channel.contents)
def test_skip_processing_of_received_personal_channel_torrents(metadata_store): """ Test that personal torrent is ignored by default when processing the torrent metadata payload """ channel = metadata_store.ChannelMetadata.create_channel('testchan') torrent_md = metadata_store.TorrentMetadata(origin_id=channel.id_, title='test', status=NEW, infohash=random_infohash()) channel.commit_channel_torrent() torrent_md.delete() channel_dir = Path( metadata_store.ChannelMetadata._channels_dir) / channel.dirname assert os.listdir(Path.fix_win_long_file(channel_dir)) # By default, personal channel torrent metadata processing is skipped so there should be no torrents # added to the channel channel.local_version = 0 metadata_store.process_channel_dir(channel_dir, channel.public_key, channel.id_) assert not channel.contents # Enable processing of personal channel torrent metadata channel.local_version = 0 metadata_store.process_channel_dir(channel_dir, channel.public_key, channel.id_, skip_personal_metadata_payload=False) assert len(channel.contents) == 1
def test_relative_paths(self): # Default should be taken from config.spec self.assertEqual( self.tribler_config.get_trustchain_keypair_filename(), path_util.abspath(self.state_dir / "ec_multichain.pem")) local_name = Path("somedir") / "ec_multichain.pem" global_name = self.state_dir / local_name self.tribler_config.set_trustchain_keypair_filename(global_name) # It should always return global path self.assertEqual(self.tribler_config.get_trustchain_keypair_filename(), global_name) # But internally it should be stored as a local path string self.assertEqual( self.tribler_config.config['trustchain']['ec_keypair_filename'], str(local_name)) # If it points out of the state dir, it should be saved as a global path string out_of_dir_name_global = path_util.abspath(self.state_dir / ".." / "filename").resolve() self.tribler_config.set_trustchain_keypair_filename( out_of_dir_name_global) self.assertEqual( self.tribler_config.config['trustchain']['ec_keypair_filename'], str(out_of_dir_name_global))
async def test_regenerate_channel_torrent(self): with db_session: chan = self.generate_personal_channel() chan.commit_channel_torrent() chan_pk, chan_id = chan.public_key, chan.id_ channel_dir = Path(self.mock_session.mds.ChannelMetadata._channels_dir) / Path(chan.dirname) for f in channel_dir.iterdir(): f.unlink() # Test trying to regenerate a non-existing channel self.assertIsNone(await self.chanman.regenerate_channel_torrent(chan_pk, chan_id + 1)) # Mock existing downloads removal-related functions self.mock_session.dlmgr.get_downloads_by_name = lambda *_: [Mock()] downloads_to_remove = [] async def mock_remove_download(download_obj, **_): downloads_to_remove.append(download_obj) self.mock_session.dlmgr.remove_download = mock_remove_download # Test regenerating an empty channel self.mock_session.mds.ChannelMetadata.consolidate_channel_torrent = lambda *_: None self.assertIsNone(await self.chanman.regenerate_channel_torrent(chan_pk, chan_id)) self.assertEqual(1, len(downloads_to_remove)) # Test regenerating a non-empty channel self.chanman.updated_my_channel = Mock() self.mock_session.mds.ChannelMetadata.consolidate_channel_torrent = lambda *_: Mock() with patch("tribler_core.modules.libtorrent.torrentdef.TorrentDef.load_from_dict"): await self.chanman.regenerate_channel_torrent(chan_pk, chan_id) self.chanman.updated_my_channel.assert_called_once()
def set_dest_dir(self, path): """ Sets the directory where to save this Download. @param path A path of a directory. """ # If something is saved inside the Tribler state dir, it should use relative path path = Path(path).normalize_to(self.state_dir) self.config['download_defaults']['saveas'] = str(path)
async def test_remove_with_files(self): """ Testing whether the API returns 200 if a download is being removed """ # Create a copy of the file, so we can remove it later source_file = TESTS_DATA_DIR / 'video.avi' tmpdir = self.temporary_directory() copied_file = tmpdir / Path(source_file).name shutil.copyfile(source_file, copied_file) video_tdef, _ = self.create_local_torrent(copied_file) dcfg = DownloadConfig() dcfg.set_dest_dir(tmpdir) download = self.session.dlmgr.start_download(tdef=video_tdef, config=dcfg) infohash = get_hex_infohash(video_tdef) while not download.handle: await sleep(0.1) await sleep(2) await self.do_request('downloads/%s' % infohash, post_data={"remove_data": True}, expected_code=200, request_type='DELETE', expected_json={ u"removed": True, u"infohash": u"c9a19e7fe5d9a6c106d6ea3c01746ac88ca3c7a5" }) while copied_file.exists(): await sleep(0.1) self.assertEqual(len(self.session.dlmgr.get_downloads()), 0) self.assertFalse(copied_file.exists())
def on_save_resume_data_alert(self, alert): """ Callback for the alert that contains the resume data of a specific download. This resume data will be written to a file on disk. """ self._logger.debug(f'On save resume data alert: {alert}') if self.checkpoint_disabled: return resume_data = alert.resume_data # Make save_path relative if the torrent is saved in the Tribler state directory if self.state_dir and b'save_path' in resume_data: save_path = Path(resume_data[b'save_path'].decode('utf8')) if save_path.exists(): resume_data[b'save_path'] = str( save_path.normalize_to(self.state_dir)) metainfo = { 'infohash': self.tdef.get_infohash(), 'name': self.tdef.get_name_as_unicode(), 'url': self.tdef.get_url() } if isinstance(self.tdef, TorrentDefNoMetainfo) else self.tdef.get_metainfo() self.config.set_metainfo(metainfo) self.config.set_engineresumedata(resume_data) # Save it to file basename = hexlify(resume_data[b'info-hash']) + '.conf' filename = self.dlmgr.get_checkpoint_dir() / basename self.config.config['download_defaults'][ 'name'] = self.tdef.get_name_as_unicode( ) # store name (for debugging) self.config.write(str(filename)) self._logger.debug('Saving download config to file %s', filename)
def test_get_files_with_length(self): name_bytes = b'\xe8\xaf\xad\xe8\xa8\x80\xe5\xa4\x84\xe7\x90\x86' name_unicode = name_bytes.decode() t = TorrentDef() t.metainfo = { b'info': { b'files': [{ b'path.utf-8': [name_bytes], b'length': 123 }, { b'path.utf-8': [b'file.txt'], b'length': 456 }] } } self.assertEqual(t.get_files_with_length(), [(Path(name_unicode), 123), (Path('file.txt'), 456)]) t.metainfo = { b'info': { b'files': [{ b'path': [name_bytes], b'length': 123 }, { b'path': [b'file.txt'], b'length': 456 }] } } self.assertEqual(t.get_files_with_length(), [(Path(name_unicode), 123), (Path('file.txt'), 456)]) t.metainfo = { b'info': { b'files': [{ b'path': [b'test\xff' + name_bytes], b'length': 123 }, { b'path': [b'file.txt'], b'length': 456 }] } } self.assertEqual(t.get_files_with_length(), [(Path('test?????????????'), 123), (Path('file.txt'), 456)]) t.metainfo = { b'info': { b'files': [{ b'path.utf-8': [b'test\xff' + name_bytes], b'length': 123 }, { b'path': [b'file.txt'], b'length': 456 }] } } self.assertEqual(t.get_files_with_length(), [(Path('file.txt'), 456)])
async def start(self, options): # Determine ipv8 port ipv8_port = options.ipv8_port if ipv8_port == -1 and "HELPER_INDEX" in os.environ and "HELPER_BASE" in os.environ: base_port = int(os.environ["HELPER_BASE"]) ipv8_port = base_port + int(os.environ["HELPER_INDEX"]) * 5 statedir = Path( os.path.join(get_root_state_directory(), "tunnel-%d") % ipv8_port) config = TriblerConfig(statedir, config_file=statedir / 'triblerd.conf') config.set_tunnel_community_socks5_listen_ports([]) config.set_tunnel_community_random_slots(options.random_slots) config.set_tunnel_community_competing_slots(options.competing_slots) config.set_torrent_checking_enabled(False) config.set_ipv8_enabled(True) config.set_libtorrent_enabled(False) config.set_ipv8_port(ipv8_port) config.set_ipv8_address(options.ipv8_address) config.set_trustchain_enabled(True) config.set_market_community_enabled(False) config.set_dht_enabled(True) config.set_tunnel_community_exitnode_enabled(bool(options.exit)) config.set_popularity_community_enabled(False) config.set_testnet(bool(options.testnet)) config.set_chant_enabled(False) config.set_bootstrap_enabled(False) if not options.no_rest_api: config.set_http_api_enabled(True) api_port = options.restapi if "HELPER_INDEX" in os.environ and "HELPER_BASE" in os.environ: api_port = int(os.environ["HELPER_BASE"]) + 10000 + int( os.environ["HELPER_INDEX"]) config.set_http_api_port(api_port) if options.ipv8_bootstrap_override is not None: config.set_ipv8_bootstrap_override(options.ipv8_bootstrap_override) self.session = Session(config) self.log_circuits = options.log_circuits self.session.notifier.add_observer(NTFY.TUNNEL_REMOVE, self.circuit_removed) await self.session.start() if options.log_rejects: # We set this after Tribler has started since the tunnel_community won't be available otherwise self.session.tunnel_community.reject_callback = self.on_circuit_reject # Tunnel helpers store more TrustChain blocks self.session.trustchain_community.settings.max_db_blocks = 5000000 self.tribler_started()
def test_get_files_with_length(tdef): name_bytes = b'\xe8\xaf\xad\xe8\xa8\x80\xe5\xa4\x84\xe7\x90\x86' name_unicode = name_bytes.decode() tdef.metainfo = { b'info': { b'files': [{ b'path.utf-8': [name_bytes], b'length': 123 }, { b'path.utf-8': [b'file.txt'], b'length': 456 }] } } assert tdef.get_files_with_length() == [(Path(name_unicode), 123), (Path('file.txt'), 456)] tdef.metainfo = { b'info': { b'files': [{ b'path': [name_bytes], b'length': 123 }, { b'path': [b'file.txt'], b'length': 456 }] } } assert tdef.get_files_with_length() == [(Path(name_unicode), 123), (Path('file.txt'), 456)] tdef.metainfo = { b'info': { b'files': [{ b'path': [b'test\xff' + name_bytes], b'length': 123 }, { b'path': [b'file.txt'], b'length': 456 }] } } assert tdef.get_files_with_length() == [(Path('test?????????????'), 123), (Path('file.txt'), 456)] tdef.metainfo = { b'info': { b'files': [{ b'path.utf-8': [b'test\xff' + name_bytes], b'length': 123 }, { b'path': [b'file.txt'], b'length': 456 }] } } assert tdef.get_files_with_length() == [(Path('file.txt'), 456)]
def make_config(options) -> TriblerConfig: # Determine ipv8 port ipv8_port = options.ipv8_port if ipv8_port == -1: if "HELPER_INDEX" in os.environ and "HELPER_BASE" in os.environ: base_port = int(os.environ["HELPER_BASE"]) ipv8_port = base_port + int(os.environ["HELPER_INDEX"]) * 5 else: raise ValueError( 'ipv8_port option is not set, and HELPER_BASE/HELPER_INDEX env vars are not defined' ) statedir = Path( os.path.join(get_root_state_directory(), "tunnel-%d") % ipv8_port) config = TriblerConfig.load(file=statedir / 'triblerd.conf', state_dir=statedir) config.tunnel_community.random_slots = options.random_slots config.tunnel_community.competing_slots = options.competing_slots config.torrent_checking.enabled = False config.ipv8.enabled = True config.libtorrent.enabled = False config.ipv8.port = ipv8_port config.ipv8.address = options.ipv8_address config.dht.enabled = True config.tunnel_community.exitnode_enabled = bool(options.exit) config.popularity_community.enabled = False config.tunnel_community.testnet = bool(options.testnet) config.chant.enabled = False config.bootstrap.enabled = False if not options.no_rest_api: https = bool(options.cert_file) config.api.https_enabled = https config.api.http_enabled = not https config.api.key = options.api_key api_port = options.restapi if "HELPER_INDEX" in os.environ and "HELPER_BASE" in os.environ: api_port = int(os.environ["HELPER_BASE"]) + 10000 + int( os.environ["HELPER_INDEX"]) if https: config.api.https_port = api_port config.api.put_path_as_relative('https_certfile', options.cert_file, config.state_dir) else: config.api.http_port = api_port else: config.api.https_enabled = False config.api.http_enabled = False if options.ipv8_bootstrap_override is not None: config.ipv8.bootstrap_override = options.ipv8_bootstrap_override return config
def test_compute_channel_update_progress(metadata_store, tmpdir): """ Test estimating progress of channel processing """ payload = ChannelMetadataPayload.from_file(CHANNEL_METADATA_UPDATED) channel = metadata_store.process_payload(payload)[0].md_obj with patch.object(metadata_store, 'get_channel_dir_path', lambda _: Path(CHANNEL_DIR)): assert metadata_store.compute_channel_update_progress(channel) == 0.0 metadata_store.process_channel_dir(CHANNEL_DIR, channel.public_key, channel.id_) assert metadata_store.compute_channel_update_progress(channel) == 1.0
def get_default_dest_dir(): """ Returns the default dir to save content to. """ tribler_downloads = Path("TriblerDownloads") if tribler_downloads.is_dir(): return tribler_downloads.resolve() home = get_home_dir() downloads = home / "Downloads" if downloads.is_dir(): return (downloads / tribler_downloads).resolve() return (home / tribler_downloads).resolve()
def verify_settings(self, settings_dict): """ Verify that the expected sections are present. """ check_section = [ 'libtorrent', 'general', 'torrent_checking', 'tunnel_community', 'http_api', 'trustchain', 'watch_folder' ] self.assertTrue(settings_dict['settings']) self.assertTrue(settings_dict['ports']) self.assertTrue( Path(settings_dict['settings']['download_defaults']['saveas'])) for section in check_section: self.assertTrue(settings_dict['settings'][section])
async def test_load_checkpoints(fake_dlmgr, tmpdir): """ Test whether we are resuming downloads after loading checkpoints """ def mocked_load_checkpoint(filename): assert str(filename).endswith('abcd.conf') mocked_load_checkpoint.called = True mocked_load_checkpoint.called = False fake_dlmgr.get_checkpoint_dir = lambda: Path(tmpdir) with open(fake_dlmgr.get_checkpoint_dir() / 'abcd.conf', 'wb') as state_file: state_file.write(b"hi") fake_dlmgr.load_checkpoint = mocked_load_checkpoint await fake_dlmgr.load_checkpoints() assert mocked_load_checkpoint.called
def consolidate_channel_torrent(self): """ Delete the channel dir contents and create it anew. Use it to consolidate fragmented channel torrent directories. :param key: The public/private key, used to sign the data """ # Remark: there should be a way to optimize this stuff with SQL and better tree traversal algorithms # Cleanup entries marked for deletion db.CollectionNode.collapse_deleted_subtrees() # Note: It should be possible to stop alling get_contents_to_commit here commit_queue = self.get_contents_to_commit() for entry in commit_queue: if entry.status == TODELETE: entry.delete() folder = Path(self._channels_dir) / self.dirname # We check if we need to re-create the channel dir in case it was deleted for some reason if not folder.is_dir(): os.makedirs(folder) for filename in os.listdir(folder): file_path = folder / filename # We only remove mdblobs and leave the rest as it is if filename.endswith(BLOB_EXTENSION) or filename.endswith( BLOB_EXTENSION + '.lz4'): os.unlink(Path.fix_win_long_file(file_path)) # Channel should get a new starting timestamp and its contents should get higher timestamps start_timestamp = clock.tick() def update_timestamps_recursive(node): if issubclass(type(node), db.CollectionNode): for child in node.contents: update_timestamps_recursive(child) if node.status in [COMMITTED, UPDATED, NEW]: node.status = UPDATED node.timestamp = clock.tick() node.sign() update_timestamps_recursive(self) return self.commit_channel_torrent( new_start_timestamp=start_timestamp)
def __init__(self, state_dir, config_file=None): """ Create a new TriblerConfig instance. :param config_file: path to existing config file :raises an InvalidConfigException if ConfigObj is invalid """ self._logger = logging.getLogger(self.__class__.__name__) self._state_dir = Path(state_dir) self.config = ConfigObj(infile=(str(config_file) if config_file else None), configspec=str(CONFIG_SPEC_PATH), default_encoding='utf-8') self.validate() self.selected_ports = {} # Set the default destination dir. The value should be in the config dict # because of the REST endpoint sending the whole dict to the GUI. # TODO: do not write the value into the config file if the user did not change it if self.config['download_defaults']['saveas'] is None: self.config['download_defaults']['saveas'] = str(self.abspath(get_default_dest_dir()))
async def add_torrent(self, piece_length=1024): [srchandle, sourcefn] = mkstemp(dir=TESTS_DIR) data = b''.join([i.to_bytes(2, byteorder='big') for i in range(1000)]) os.write(srchandle, data) os.close(srchandle) tdef = TorrentDef() tdef.add_content(sourcefn) tdef.set_piece_length(piece_length) torrentfn = self.session.config.get_state_dir() / "gen.torrent" tdef.save(torrentfn) dscfg = DownloadConfig() destdir = Path(sourcefn).parent dscfg.set_dest_dir(destdir) download = self.session.dlmgr.start_download(tdef=tdef, config=dscfg) await download.wait_for_status(DLSTATUS_SEEDING) return tdef.get_infohash(), data
async def add_torrent(self): [srchandle, sourcefn] = mkstemp(dir=TESTS_DIR) self.data = b'\xFF' * self.size # pylint: disable=attribute-defined-outside-init os.write(srchandle, self.data) os.close(srchandle) tdef = TorrentDef() tdef.add_content(sourcefn) tdef.set_piece_length(self.piece_len) torrentfn = self.session.config.get_state_dir() / "gen.torrent" tdef.save(torrentfn) dscfg = DownloadConfig() destdir = Path(sourcefn).parent dscfg.set_dest_dir(destdir) self.download = self.session.dlmgr.start_download(tdef=tdef, config=dscfg) # pylint: disable=attribute-defined-outside-init await self.download.wait_for_status(DLSTATUS_SEEDING) self.infohash = tdef.get_infohash() # pylint: disable=attribute-defined-outside-init
def __init__(self, state_dir, config_file=None, reset_config_on_error=False): """ Create a new TriblerConfig instance. :param config_file: path to existing config file :param reset_config_on_error: Flag indicating whether to recover from corrupt config using default config. :raises an InvalidConfigException if ConfigObj is invalid """ self._logger = logging.getLogger(self.__class__.__name__) self._state_dir = Path(state_dir) self.selected_ports = {} self.config = None self.config_error = None self.load_config_file(config_file, reset_config_on_error) # Set the default destination dir. The value should be in the config dict # because of the REST endpoint sending the whole dict to the GUI. # TODO: do not write the value into the config file if the user did not change it if self.config['download_defaults']['saveas'] is None: self.config['download_defaults']['saveas'] = str(self.abspath(get_default_dest_dir()))
def get_atp(self): save_path = self.config.get_dest_dir() atp = { "save_path": str(save_path), "storage_mode": lt.storage_mode_t.storage_mode_sparse, "flags": lt.add_torrent_params_flags_t.flag_paused | lt.add_torrent_params_flags_t.flag_duplicate_is_error | lt.add_torrent_params_flags_t.flag_update_subscribe } if self.config.get_share_mode(): atp["flags"] = atp[ "flags"] | lt.add_torrent_params_flags_t.flag_share_mode if self.config.get_upload_mode(): atp["flags"] = atp[ "flags"] | lt.add_torrent_params_flags_t.flag_upload_mode resume_data = self.config.get_engineresumedata() if not isinstance(self.tdef, TorrentDefNoMetainfo): metainfo = self.tdef.get_metainfo() torrentinfo = lt.torrent_info(metainfo) atp["ti"] = torrentinfo if resume_data and isinstance(resume_data, dict): # Rewrite save_path as a global path, if it is given as a relative path save_path = (ensure_unicode(resume_data[b"save_path"], 'utf8') if b"save_path" in resume_data else None) if save_path and not Path(save_path).is_absolute(): resume_data[b"save_path"] = str(self.state_dir / save_path) atp["resume_data"] = lt.bencode(resume_data) else: atp["url"] = self.tdef.get_url( ) or "magnet:?xt=urn:btih:" + hexlify(self.tdef.get_infohash()) atp["name"] = self.tdef.get_name_as_unicode() return atp