Example #1
0
def test_hashability(singlefile_content):
    d = {
        't1': torf.Torrent(singlefile_content.path, comment='One'),
        't2': torf.Torrent(singlefile_content.path, comment='Two')
    }
    assert d['t1'].comment == 'One'
    assert d['t2'].comment == 'Two'
Example #2
0
def test_randomize_infohash(singlefile_content):
    t1 = torf.Torrent(singlefile_content.path)
    t2 = torf.Torrent(singlefile_content.path)
    t1.generate()
    t2.generate()

    t1.randomize_infohash = False
    t2.randomize_infohash = False
    assert t1.infohash == t2.infohash

    t1.randomize_infohash = True
    t2.randomize_infohash = True
    assert t1.infohash != t2.infohash
Example #3
0
def assert_callback_called(torrent):
    t = torf.Torrent(torrent.path)
    cb = mock.Mock()
    cb.return_value = None
    success = t.generate(callback=cb, interval=0)
    assert success

    # Compare number of callback calls
    number_of_pieces = math.ceil(t.size / t.piece_size)
    exp_call_count = pytest.approx(number_of_pieces, abs=1)
    assert cb.call_count == exp_call_count

    # Compare arguments without filepaths (too complex for me)
    stripped_call_args_list = [
        mock.call(args[0][0], args[0][2], args[0][3])
        for args in cb.call_args_list
    ]
    exp_call_args_list = [
        mock.call(t, i, number_of_pieces)
        for i in range(1, number_of_pieces + 1)
    ]
    # There can be slightly more actual calls than expected calls or vice versa
    call_num = min(len(stripped_call_args_list), len(exp_call_args_list))
    stripped_call_args_list = stripped_call_args_list[:call_num]
    exp_call_args_list = exp_call_args_list[:call_num]
    assert stripped_call_args_list == exp_call_args_list

    # Make sure that the expected filepaths were reported to callback
    processed_filepaths = []
    for args in cb.call_args_list:
        filepath = args[0][1]
        if filepath not in processed_filepaths:
            processed_filepaths.append(filepath)
    exp_filepaths = list(t.filepaths)
    assert processed_filepaths == exp_filepaths
Example #4
0
def test_reuse_with_empty_file_list(with_callback, existing_torrents,
                                    create_file):
    # Create and prepare existing torrents
    existing_torrents = existing_torrents(my_torrents=(
        ('a.jpg', 'foo', {
            'creation_date': 123
        }),
        ('b.txt', 'bar', {
            'creation_date': 456
        }),
        ('c.mp4', 'baz', {
            'creation_date': 789
        }),
    ), )

    # Create and prepare the torrent we want to generate
    new_torrent = torf.Torrent(
        path=create_file('just_a_file.jpg', 'foo'),
        exclude_globs=['*.jpg'],
    )

    # Expect identical metainfo
    exp_joined_metainfo = copy.deepcopy(new_torrent.metainfo)

    exp_exception = RuntimeError('reuse() called while file list is empty')
    with pytest.raises(type(exp_exception),
                       match=rf'^{re.escape(str(exp_exception))}$'):
        if with_callback:
            new_torrent.reuse(existing_torrents.location_paths,
                              callback=Mock())
        else:
            new_torrent.reuse(existing_torrents.location_paths)

    assert new_torrent.metainfo == exp_joined_metainfo
Example #5
0
def _verify_mode(cfg):
    # Read existing torrent
    try:
        infile_torrent = torf.Torrent.read(cfg['in'])
    except torf.TorfError as e:
        raise MainError(e, errno=e.errno)

    # Create torrent from PATH
    try:
        path_torrent = torf.Torrent(path=cfg['PATH'],
                                    piece_size=infile_torrent.piece_size,
                                    private=infile_torrent.private)
    except torf.TorfError as e:
        raise MainError(e, errno=e.errno)

    if infile_torrent.mode is None:
        raise MainError(f'{cfg["in"]}: Empty torrent', errno=errno.ENOENT)

    # Instead of hashing the content on disk, we do some quick checks first.
    elif infile_torrent.mode == 'singlefile':
        # Single-file torrent
        if not os.path.isfile(path_torrent.path):
            raise MainError(f'{cfg["PATH"]}: {os.strerror(errno.EISDIR)}',
                            errno=errno.EISDIR)

        local_path_size = os.path.getsize(os.path.realpath(path_torrent.path))
        if local_path_size != infile_torrent.size:
            raise MainError(f'{cfg["PATH"]}: Mismatching size',
                            errno=errno.EFBIG)

    elif infile_torrent.mode == 'multifile':
        # Multi-file torrent
        if not os.path.isdir(path_torrent.path):
            raise MainError(f'{cfg["PATH"]}: {os.strerror(errno.ENOTDIR)}',
                            errno=errno.ENOTDIR)

        for file in infile_torrent.files:
            # Remove first directory in path to get path inside the torrent
            path_in_torrent = os.path.join(*(file.split(os.path.sep)[1:]))
            local_path = os.path.join(cfg['PATH'], path_in_torrent)

            # Check if file exists
            if not os.path.exists(local_path):
                raise MainError(f'{local_path}: {os.strerror(errno.ENOENT)}',
                                errno=errno.ENOENT)

            # Check file sizes
            path_in_torrent_size = infile_torrent.file_size(path_in_torrent)
            local_path_size = os.path.getsize(local_path)
            if local_path_size != path_in_torrent_size:
                raise MainError(f'{local_path}: Mismatching size',
                                errno=errno.EFBIG)

    # Finally, compare the metainfo hashes
    path_torrent.generate()
    if infile_torrent.infohash != path_torrent.infohash:
        raise MainError(f'{path_torrent.infohash}: Mismatching hash',
                        errno=errno.EADV)

    print('{cfg["PATH"]} contains everything described in {cfg["in"]}')
Example #6
0
def test_partial_size__singlefile__providing_path(tmp_path):
    (tmp_path / 'content.jpg').write_text('some data')
    t = torf.Torrent(tmp_path / 'content.jpg')
    for path in ('bar/foo.jpg', ['bar', 'foo.jpg']):
        with pytest.raises(torf.PathError) as excinfo:
            t.partial_size(path)
        assert excinfo.match(f'^bar/foo.jpg: Not specified in metainfo$')
Example #7
0
def test_from_torrent_without_size(singlefile_content, multifile_content):
    for content in singlefile_content, multifile_content:
        t = torf.Torrent(content.path, trackers=['http://foo', 'http://bar'])
        t.generate()
        assert str(t.magnet(size=False)) == (f'magnet:?xt=urn:btih:{t.infohash}'
                                             f'&dn={quote_plus(t.name)}'
                                             f'&tr=http%3A%2F%2Ffoo&tr=http%3A%2F%2Fbar')
Example #8
0
def test_partial_size__singlefile__providing_wrong_name(tmp_path):
    (tmp_path / 'content.jpg').write_text('some data')
    t = torf.Torrent(tmp_path / 'content.jpg')
    for path in ('foo.jpg', ['foo.jpg']):
        with pytest.raises(torf.PathError) as excinfo:
            t.partial_size(path)
        assert excinfo.match('^foo.jpg: Unknown path$')
Example #9
0
def test_nonexisting_path(create_file):
    content_path = create_file('file.jpg', '<image data>')
    t = torf.Torrent(content_path)
    content_path.unlink()
    with pytest.raises(torf.ReadError) as e:
        t.generate()
    assert str(e.value) == f'{content_path}: No such file or directory'
Example #10
0
def _create_mode(cfg):
    try:
        torrent = torf.Torrent(
            path=cfg['PATH'],
            name=cfg['name'] or None,
            exclude=cfg['exclude'],
            trackers=() if cfg['notracker'] else cfg['tracker'],
            webseeds=() if cfg['nowebseed'] else cfg['webseed'],
            private=False if cfg['noprivate'] else cfg['private'],
            source=None if cfg['nosource'] or not cfg['source'] else cfg['source'],
            randomize_infohash=False if cfg['noxseed'] else cfg['xseed'],
            comment=None if cfg['nocomment'] else cfg['comment'],
            created_by=None if cfg['nocreator'] else _config.DEFAULT_CREATOR
        )
    except torf.TorfError as e:
        raise MainError(e, errno=e.errno)

    if not cfg['nodate']:
        try:
            torrent.creation_date = _util.parse_date(cfg['date'] or 'now')
        except ValueError:
            raise MainError(f'{cfg["date"]}: Invalid date', errno=errno.EINVAL)

    if cfg['max_piece_size']:
        max_piece_size = cfg['max_piece_size'] * 1048576
        if torrent.piece_size > max_piece_size:
            try:
                torrent.piece_size = max_piece_size
            except torf.PieceSizeError as e:
                raise MainError(e, errno=errno.EINVAL)

    _check_output_file_exists(torrent, cfg)
    _show_torrent_info(torrent, cfg)
    _hash_pieces(torrent, cfg)
    _write_torrent(torrent, cfg)
Example #11
0
 def _create_torrents(directory, *items):
     torrents_directory = tmp_path / directory
     torrents_directory.mkdir(exist_ok=True)
     torrents = []
     for item in items:
         torrent_name = item[0]
         create_args = item[1]
         torrent_kwargs = item[2]
         if isinstance(create_args,
                       collections.abc.Sequence) and not isinstance(
                           create_args, str):
             content_path = create_dir(torrent_name, *create_args)
         else:
             content_path = create_file(torrent_name, create_args)
         torrent = torf.Torrent(path=content_path, **torrent_kwargs)
         torrent_filepath = torrents_directory / f'{torrent_name}.torrent'
         torrent.generate()
         torrent.write(torrent_filepath)
         torrents.append(
             SimpleNamespace(
                 torrent=torrent,
                 torrent_path=torrent_filepath,
                 content_path=content_path,
             ))
         print('created torrent:\n', torrents[-1].torrent_path, '\n',
               torrents[-1].torrent.metainfo)
     return torrents
Example #12
0
def test_multifile_with_trackers(multifile_content):
    t = torf.Torrent(multifile_content.path,
                     trackers=['http://foo', 'http://bar'])
    t.generate()
    assert t.magnet() == (
        f'magnet:?xt=urn:btih:{t.infohash}&dn={quote_plus(t.name)}&xl={t.size}'
        '&tr=http%3A%2F%2Ffoo&tr=http%3A%2F%2Fbar')
Example #13
0
def test_partial_size__singlefile__providing_path(tmpdir):
    content_path = tmpdir.join('content.jpg')
    content_path.write('some data')
    t = torf.Torrent(content_path)
    for path in ('bar/foo.jpg', ['bar', 'foo.jpg']):
        with pytest.raises(torf.PathNotFoundError) as excinfo:
            t.partial_size(path)
        assert excinfo.match(f'^bar/foo.jpg: No such file or directory$')
Example #14
0
def test_with_empty_file(create_file):
    # Create content so we can set path
    content_path = create_file('file.jpg', '<image data>')
    t = torf.Torrent(content_path)
    content_path.write_text('')
    with pytest.raises(torf.PathError) as e:
        t.generate()
    assert str(e.value) == f'{t.path}: Empty or all files excluded'
Example #15
0
def test_validate_is_called_first(monkeypatch):
    torrent = torf.Torrent()
    mock_validate = mock.MagicMock(side_effect=torf.MetainfoError('Mock error'))
    monkeypatch.setattr(torrent, 'validate', mock_validate)
    with pytest.raises(torf.MetainfoError) as excinfo:
        torrent.verify_filesize('some/path')
    assert excinfo.match(f'^Invalid metainfo: Mock error$')
    mock_validate.assert_called_once_with()
Example #16
0
def test_validate_is_called_first(monkeypatch):
    torrent = torf.Torrent()
    mock_validate = mock.Mock(side_effect=torf.MetainfoError('Mock error'))
    monkeypatch.setattr(torrent, 'validate', mock_validate)
    with pytest.raises(torf.MetainfoError) as excinfo:
        torrent.verify('some/path')
    assert str(excinfo.value) == 'Invalid metainfo: Mock error'
    mock_validate.assert_called_once_with()
Example #17
0
def parse_args(args):
    cfg = vars(_cliparser.parse_args(args))

    # Validate creation date
    if cfg['date']:
        try:
            cfg['date'] = _utils.parse_date(cfg['date'] or 'now')
        except ValueError:
            raise _errors.CliError(f'{cfg["date"]}: Invalid date')

    # Validate max piece size
    if cfg['max_piece_size']:
        cfg['max_piece_size'] = cfg['max_piece_size'] * 1048576
        if cfg['max_piece_size'] > torf.Torrent.piece_size_max:
            torf.Torrent.piece_size_max = cfg['max_piece_size']
        try:
            torf.Torrent().piece_size = cfg['max_piece_size']
        except torf.PieceSizeError as e:
            raise _errors.CliError(e)

    # Validate tracker URLs
    for tier in cfg['tracker']:
        for url in tier.split(','):
            try:
                torf.Torrent().trackers = url
            except torf.URLError as e:
                raise _errors.CliError(e)

    # Validate webseed URLs
    for webseed in cfg['webseed']:
        try:
            torf.Torrent().webseeds = (webseed, )
        except torf.URLError as e:
            raise _errors.CliError(e)

    # Validate regular expressions
    for regex in itertools.chain(cfg['exclude_regex'], cfg['include_regex']):
        try:
            re.compile(regex)
        except re.error as e:
            raise _errors.CliError(f'Invalid regular expression: {regex}: '
                                   f'{str(e)[0].upper()}{str(e)[1:]}')

    cfg['validate'] = not cfg['novalidate']

    return cfg
Example #18
0
def test_with_empty_directory(create_dir):
    # Create content so we can set path
    content_path = create_dir('empty', ('a file', '<data>'))
    t = torf.Torrent(content_path)
    (content_path / 'a file').unlink()
    with pytest.raises(torf.ReadError) as e:
        t.generate()
    assert str(
        e.value) == f'{content_path / "a file"}: No such file or directory'
Example #19
0
def test_copy_before_ready(singlefile_content):
    t1 = torf.Torrent(singlefile_content.path,
                      comment='Asdf.',
                      randomize_infohash=True,
                      webseeds=['http://foo'])
    assert not t1.is_ready
    t2 = t1.copy()
    assert t1 == t2
    assert t1 is not t2
Example #20
0
def test_with_all_files_excluded(create_dir):
    # Create content so we can set path
    content_path = create_dir('content', ('a.jpg', '<image data>'),
                              ('b.jpg', '<image data>'),
                              ('c.jpg', '<image data>'))
    t = torf.Torrent(content_path, exclude_globs=['*.jpg'])
    with pytest.raises(torf.PathError) as e:
        t.generate()
    assert str(e.value) == f'{t.path}: Empty or all files excluded'
Example #21
0
def test_nonexisting_path(singlefile_content):
    content_path = singlefile_content.path + '.deletable'
    shutil.copyfile(singlefile_content.path, content_path)
    t = torf.Torrent(content_path)
    os.remove(content_path)

    with pytest.raises(torf.PathNotFoundError) as excinfo:
        t.generate()
    assert excinfo.match(f'^{content_path}: No such file or directory$')
Example #22
0
def test_mode(singlefile_content, multifile_content):
    torrent = torf.Torrent()
    assert torrent.mode is None
    torrent.path = singlefile_content.path
    assert torrent.mode == 'singlefile'
    torrent.path = multifile_content.path
    assert torrent.mode == 'multifile'
    torrent.path = None
    assert torrent.mode == None
Example #23
0
 def _create_torrent_file(tmp_path, **kwargs):
     torrent_file = tmp_path / 'test.torrent'
     try:
         t = torf.Torrent(**kwargs)
         t.generate()
         t.write(torrent_file)
         yield torrent_file
     finally:
         if os.path.exists(torrent_file):
             os.remove(torrent_file)
Example #24
0
def _create_torrent(tmpdir, **kwargs):
    torrent_file = str(tmpdir.join('test.torrent'))
    try:
        t = torf.Torrent(**kwargs)
        t.generate()
        t.write(torrent_file)
        yield torrent_file
    finally:
        if os.path.exists(torrent_file):
            os.remove(torrent_file)
Example #25
0
def check_metainfo(content, tmpdir):
    t = torf.Torrent(content.path)
    t.piece_size = content.exp_metainfo['info']['piece length']
    t.generate()
    t.write(tmpdir.join('torf.torrent'), overwrite=True)
    assert t.metainfo['info']['piece length'] == content.exp_metainfo['info'][
        'piece length']
    assert t.metainfo['info']['pieces'] == content.exp_metainfo['info'][
        'pieces']
    assert t.infohash == content.exp_attrs.infohash
    assert t.infohash_base32 == content.exp_attrs.infohash_base32
Example #26
0
def test_with_empty_path(tmpdir):
    content_path = tmpdir.mkdir('empty')
    # Create content so we can set path
    content_file = content_path.join('file.jpg')
    content_file.write('foo')
    t = torf.Torrent(content_path)
    content_file.write('')

    with pytest.raises(torf.PathEmptyError) as excinfo:
        t.generate()
    assert excinfo.match(f'^{str(t.path)}: Empty directory$')
Example #27
0
def test_from_torrent(singlefile_content, multifile_content):
    for content in singlefile_content, multifile_content:
        t = torf.Torrent(content.path,
                         trackers=['http://foo', 'http://bar'],
                         webseeds=['http://qux', 'http://quux'])
        t.generate()
        assert str(t.magnet()) == (f'magnet:?xt=urn:btih:{t.infohash}'
                                   f'&dn={quote_plus(t.name)}'
                                   f'&xl={t.size}'
                                   '&tr=http%3A%2F%2Ffoo&tr=http%3A%2F%2Fbar'
                                   '&ws=http%3A%2F%2Fqux&ws=http%3A%2F%2Fquux')
Example #28
0
def test_reading_invalid_torrent_data_from_stdin(capsys, tmp_path, monkeypatch, clear_ansi, regex):
    torrent = torf.Torrent(name='Foo', comment='Bar.')
    r, w = os.pipe()
    monkeypatch.setattr(sys, 'stdin', os.fdopen(r))
    os.fdopen(w, 'wb').write(torrent.dump(validate=False))

    with patch('sys.exit') as mock_exit:
        run(['-i', '-'])
    mock_exit.assert_called_once_with(_errors.Code.READ)
    cap = capsys.readouterr()
    assert cap.out == ''
    assert cap.err == f"{_vars.__appname__}: Invalid metainfo: Missing 'piece length' in ['info']\n"
Example #29
0
def test_max_torrent_file_size(create_file, existing_torrents, mocker):
    # Create and prepare existing torrents
    existing_torrents = existing_torrents(
        subpath1=(
            ('a', 'foo', {
                'creation_date': 123
            }),
            ('b', 'bar', {
                'creation_date': 456
            }),
            ('c', 'baz', {
                'creation_date': 789
            }),
        ),
        subpath2=(
            ('d', 'hey', {
                'private': True
            }),
            ('e', 'ho', {
                'comment': 'yo'
            }),
            ('f', 'oh', {
                'comment': 'oy'
            }),
            ('g', 'ohh', {
                'comment': 'oyy'
            }),
        ),
    )
    # Make some torrents really big
    with open(existing_torrents.torrent_filepaths[1], 'wb') as f:
        f.truncate(20 * 1048576)
    with open(existing_torrents.torrent_filepaths[3], 'wb') as f:
        f.truncate(30 * 1048576)

    callback = Mock(return_value=None)
    new_torrent = torf.Torrent(path=create_file('just_a_file', 'foo'))
    return_value = new_torrent.reuse(existing_torrents.location_paths,
                                     callback=callback)

    assert return_value is False
    assert callback.call_args_list == [
        call(new_torrent, str(existing_torrents.subpath1[0].torrent_path), 1,
             5, False, None),
        call(new_torrent, str(existing_torrents.subpath1[2].torrent_path), 2,
             5, False, None),
        call(new_torrent, str(existing_torrents.subpath2[1].torrent_path), 3,
             5, False, None),
        call(new_torrent, str(existing_torrents.subpath2[2].torrent_path), 4,
             5, False, None),
        call(new_torrent, str(existing_torrents.subpath2[3].torrent_path), 5,
             5, False, None),
    ]
Example #30
0
def test_equality(singlefile_content):
    kwargs = {
        'trackers': ['https://localhost/'],
        'comment': 'Foo',
        'created_by': 'Bar'
    }
    t1 = torf.Torrent(singlefile_content.path, **kwargs)
    t2 = torf.Torrent(singlefile_content.path, **kwargs)
    assert t1 == t2
    t1.metainfo['foo'] = 'bar'
    assert t1 != t2
    del t1.metainfo['foo']
    assert t1 == t2
    t2.comment = 'asdf'
    assert t1 != t2
    t2.comment = t1.comment
    assert t1 == t2
    t1.trackers += ['https://remotehost']
    assert t1 != t2
    del t1.trackers[-1]
    assert t1 == t2