Example #1
0
async def test_start_download_while_getting_metainfo(fake_dlmgr):
    """
    Testing adding a torrent while a metainfo request is running.
    """
    infohash = b"a" * 20

    metainfo_session = MagicMock()
    metainfo_session.get_torrents = lambda: []

    metainfo_dl = MagicMock()
    metainfo_dl.get_def = lambda: MagicMock(get_infohash=lambda: infohash)

    fake_dlmgr.initialize()
    fake_dlmgr.get_session = lambda *_: metainfo_session
    fake_dlmgr.downloads[infohash] = metainfo_dl
    fake_dlmgr.metainfo_requests[infohash] = [metainfo_dl, 1]
    fake_dlmgr.remove_download = MagicMock(return_value=succeed(None))

    tdef = TorrentDefNoMetainfo(infohash, 'name',
                                f'magnet:?xt=urn:btih:{hexlify(infohash)}&')
    download = fake_dlmgr.start_download(tdef=tdef, checkpoint_disabled=True)
    assert metainfo_dl != download
    await sleep(.1)
    assert fake_dlmgr.downloads[infohash] == download
    fake_dlmgr.remove_download.assert_called_once_with(metainfo_dl,
                                                       remove_content=True,
                                                       remove_checkpoint=False)
Example #2
0
    def update_trackers(self, infohash, trackers):
        """ Update the trackers for a download.
        :param infohash: infohash of the torrent that needs to be updated
        :param trackers: A list of tracker urls.
        """
        download = self.get_download(infohash)
        if download:
            old_def = download.get_def()
            old_trackers = old_def.get_trackers_as_single_tuple()
            new_trackers = list(set(trackers) - set(old_trackers))
            all_trackers = list(old_trackers) + new_trackers

            if new_trackers:
                # Add new trackers to the download
                download.add_trackers(new_trackers)

                # Create a new TorrentDef
                if isinstance(old_def, TorrentDefNoMetainfo):
                    new_def = TorrentDefNoMetainfo(old_def.get_infohash(),
                                                   old_def.get_name(),
                                                   download.get_magnet_link())
                else:
                    metainfo = old_def.get_metainfo()
                    if len(all_trackers) > 1:
                        metainfo["announce-list"] = [all_trackers]
                    else:
                        metainfo["announce"] = all_trackers[0]
                    new_def = TorrentDef.load_from_dict(metainfo)

                # Set TorrentDef + checkpoint
                download.set_def(new_def)
                download.checkpoint()
Example #3
0
    async def get_metainfo(self, infohash, timeout=30, hops=None, url=None):
        """
        Lookup metainfo for a given infohash. The mechanism works by joining the swarm for the infohash connecting
        to a few peers, and downloading the metadata for the torrent.
        :param infohash: The (binary) infohash to lookup metainfo for.
        :param timeout: A timeout in seconds.
        :param hops: the number of tunnel hops to use for this lookup. If None, use config default.
        :param url: Optional URL. Can contain trackers info, etc.
        :return: The metainfo
        """
        infohash_hex = hexlify(infohash)
        if infohash in self.metainfo_cache:
            self._logger.info('Returning metainfo from cache for %s',
                              infohash_hex)
            return self.metainfo_cache[infohash]['meta_info']

        self._logger.info('Trying to fetch metainfo for %s', infohash_hex)
        if infohash in self.metainfo_requests:
            download = self.metainfo_requests[infohash][0]
            self.metainfo_requests[infohash][1] += 1
        elif infohash in self.downloads:
            download = self.downloads[infohash]
        else:
            tdef = TorrentDefNoMetainfo(infohash, 'metainfo request', url=url)
            dcfg = DownloadConfig()
            dcfg.set_hops(hops or self.download_defaults.number_hops)
            dcfg.set_upload_mode(
                True
            )  # Upload mode should prevent libtorrent from creating files
            dcfg.set_dest_dir(self.metadata_tmpdir)
            try:
                download = self.start_download(tdef=tdef,
                                               config=dcfg,
                                               hidden=True,
                                               checkpoint_disabled=True)
            except TypeError:
                return
            self.metainfo_requests[infohash] = [download, 1]

        try:
            metainfo = download.tdef.get_metainfo() or await wait_for(
                shield(download.future_metainfo), timeout)
            self._logger.info('Successfully retrieved metainfo for %s',
                              infohash_hex)
            self.metainfo_cache[infohash] = {
                'time': timemod.time(),
                'meta_info': metainfo
            }
        except (CancelledError, asyncio.TimeoutError):
            metainfo = None
            self._logger.info('Failed to retrieve metainfo for %s',
                              infohash_hex)

        if infohash in self.metainfo_requests:
            self.metainfo_requests[infohash][1] -= 1
            if self.metainfo_requests[infohash][1] <= 0:
                await self.remove_download(download, remove_content=True)
                self.metainfo_requests.pop(infohash, None)

        return metainfo
Example #4
0
async def test_start_download(fake_dlmgr):
    """
    Testing the addition of a torrent to the libtorrent manager
    """
    infohash = b'a' * 20

    mock_handle = MagicMock()
    mock_handle.info_hash = lambda: hexlify(infohash)
    mock_handle.is_valid = lambda: True

    mock_error = MagicMock()
    mock_error.value = lambda: None

    mock_alert = type(
        'add_torrent_alert', (object, ),
        dict(handle=mock_handle, error=mock_error, category=lambda _: None))()

    mock_ltsession = MagicMock()
    mock_ltsession.get_torrents = lambda: []
    mock_ltsession.async_add_torrent = lambda _: fake_dlmgr.register_task(
        'post_alert', fake_dlmgr.process_alert, mock_alert, delay=0.1)

    fake_dlmgr.get_session = lambda *_: mock_ltsession

    download = fake_dlmgr.start_download(tdef=TorrentDefNoMetainfo(
        infohash, ''),
                                         checkpoint_disabled=True)
    handle = await download.get_handle()
    assert handle == mock_handle
    fake_dlmgr.downloads.clear()
    await download.shutdown()

    # Test waiting on DHT getting enough nodes and adding the torrent after timing out
    fake_dlmgr.dht_readiness_timeout = 0.5
    flag = []
    check_was_run = MagicMock()

    async def mock_check():
        while not flag:
            check_was_run()
            await sleep(0.1)

    fake_dlmgr._check_dht_ready = mock_check
    fake_dlmgr.initialize()

    mock_download = MagicMock()
    mock_download.get_def().get_infohash = lambda: b"1" * 20
    mock_download.future_added = succeed(True)
    mock_ltsession.async_add_torrent = MagicMock()
    await fake_dlmgr.start_handle(mock_download, {})
    check_was_run.assert_called()
    fake_dlmgr.downloads.clear()

    # Test waiting on DHT getting enough nodes
    fake_dlmgr.dht_readiness_timeout = 100
    flag.append(True)
    mock_download.future_added = succeed(True)
    await fake_dlmgr.start_handle(mock_download, {})
    fake_dlmgr.downloads.clear()
Example #5
0
    def load_checkpoint(self, filename):
        try:
            config = DownloadConfig.load(filename)
        except Exception:
            self._logger.exception("Could not open checkpoint file %s",
                                   filename)
            return

        metainfo = config.get_metainfo()
        if not metainfo:
            self._logger.error(
                "Could not resume checkpoint %s; metainfo not found", filename)
            return
        if not isinstance(metainfo, dict):
            self._logger.error(
                "Could not resume checkpoint %s; metainfo is not dict %s %s",
                filename, type(metainfo), repr(metainfo))
            return

        try:
            url = metainfo.get(b'url', None)
            url = url.decode('utf-8') if url else url
            tdef = (TorrentDefNoMetainfo(metainfo[b'infohash'],
                                         metainfo[b'name'], url) if b'infohash'
                    in metainfo else TorrentDef.load_from_dict(metainfo))
        except (KeyError, ValueError) as e:
            self._logger.exception(
                "Could not restore tdef from metainfo dict: %s %s ", e,
                metainfo)
            return

        if config.get_bootstrap_download():
            # In case the download is marked as bootstrap, remove it if its infohash does not
            # match the configured bootstrap infohash
            if hexlify(tdef.get_infohash()) != self.bootstrap_infohash:
                self.remove_config(tdef.get_infohash())
                return

        config.state_dir = self.state_dir
        if config.get_dest_dir() == '':  # removed torrent ignoring
            self._logger.info("Removing checkpoint %s destdir is %s", filename,
                              config.get_dest_dir())
            os.remove(filename)
            return

        try:
            if self.download_exists(tdef.get_infohash()):
                self._logger.info(
                    "Not resuming checkpoint because download has already been added"
                )
            else:
                self.start_download(tdef=tdef, config=config)
        except Exception:
            self._logger.exception(
                "Not resume checkpoint due to exception while adding download")
Example #6
0
    async def start_download_from_uri(self, uri, config=None):
        scheme = scheme_from_uri(uri)

        if scheme in (HTTP_SCHEME, HTTPS_SCHEME):
            tdef = await TorrentDef.load_from_url(uri)
            return self.start_download(tdef=tdef, config=config)
        if scheme == MAGNET_SCHEME:
            name, infohash, _ = parse_magnetlink(uri)
            if infohash is None:
                raise RuntimeError("Missing infohash")
            if infohash in self.metainfo_cache:
                tdef = TorrentDef.load_from_dict(self.metainfo_cache[infohash]['meta_info'])
            else:
                tdef = TorrentDefNoMetainfo(infohash, "Unknown name" if name is None else name, url=uri)
            return self.start_download(tdef=tdef, config=config)
        if scheme == FILE_SCHEME:
            file = uri_to_path(uri)
            return self.start_download(torrent_file=file, config=config)
        raise Exception("invalid uri")
Example #7
0
async def test_start_download_existing_download(fake_dlmgr):
    """
    Testing the addition of a torrent to the libtorrent manager, if there is a pre-existing download.
    """
    infohash = b'a' * 20

    mock_download = MagicMock()
    mock_download.get_def = lambda: MagicMock(get_trackers_as_single_tuple=
                                              lambda: ())

    mock_ltsession = MagicMock()

    fake_dlmgr.downloads[infohash] = mock_download
    fake_dlmgr.get_session = lambda *_: mock_ltsession

    download = fake_dlmgr.start_download(tdef=TorrentDefNoMetainfo(
        infohash, 'name'),
                                         checkpoint_disabled=True)
    assert download == mock_download
    fake_dlmgr.downloads.clear()
Example #8
0
 async def start_download_from_uri(self, uri, config=None):
     if uri.startswith("http"):
         tdef = await TorrentDef.load_from_url(uri)
         return self.start_download(tdef=tdef, config=config)
     if uri.startswith("magnet:"):
         name, infohash, _ = parse_magnetlink(uri)
         if infohash is None:
             raise RuntimeError("Missing infohash")
         if infohash in self.metainfo_cache:
             tdef = TorrentDef.load_from_dict(
                 self.metainfo_cache[infohash]['meta_info'])
         else:
             tdef = TorrentDefNoMetainfo(
                 infohash,
                 "Unknown name" if name is None else name,
                 url=uri)
         return self.start_download(tdef=tdef, config=config)
     if uri.startswith("file:"):
         argument = uri_to_path(uri)
         return self.start_download(torrent_file=argument, config=config)
     raise Exception("invalid uri")
Example #9
0
async def test_start_download_existing_handle(fake_dlmgr):
    """
    Testing the addition of a torrent to the libtorrent manager, if there is a pre-existing handle.
    """
    infohash = b'a' * 20

    mock_handle = MagicMock()
    mock_handle.info_hash = lambda: hexlify(infohash)
    mock_handle.is_valid = lambda: True

    mock_ltsession = MagicMock()
    mock_ltsession.get_torrents = lambda: [mock_handle]

    fake_dlmgr.get_session = lambda *_: mock_ltsession

    download = fake_dlmgr.start_download(tdef=TorrentDefNoMetainfo(
        infohash, 'name'),
                                         checkpoint_disabled=True)
    handle = await download.get_handle()
    assert handle == mock_handle
    fake_dlmgr.downloads.clear()
    await download.shutdown()
Example #10
0
def test_torrent_no_metainfo():
    tdef = TorrentDefNoMetainfo(b"12345678901234567890", VIDEO_FILE_NAME,
                                "http://google.com")
    assert tdef.get_name() == VIDEO_FILE_NAME
    assert tdef.get_infohash() == b"12345678901234567890"
    assert tdef.get_length() == 0  # there are no files
    assert not tdef.get_metainfo()
    assert tdef.get_url() == "http://google.com"
    assert not tdef.is_multifile_torrent()
    assert tdef.get_name_as_unicode() == VIDEO_FILE_NAME
    assert not tdef.get_files()
    assert tdef.get_files_with_length() == []
    assert not tdef.get_trackers_as_single_tuple()
    assert not tdef.is_private()
    assert tdef.get_name_utf8() == "video.avi"
    assert tdef.get_nr_pieces() == 0

    torrent2 = TorrentDefNoMetainfo(b"12345678901234567890", VIDEO_FILE_NAME,
                                    "magnet:")
    assert not torrent2.get_trackers_as_single_tuple()
Example #11
0
def test_tdef_no_metainfo(state_dir):
    tdef = TorrentDefNoMetainfo(b"1" * 20, "test")
    return tdef