示例#1
0
    def init_libtorrent(self, max_download_speed=0, max_upload_speed=0):
        """Perform the initialization of things that should be initialized once"""
        if self.session:
            return

        settings = libtorrent.session_settings()
        settings.user_agent = encode_utf8(
            'Torrent Launcher (libtorrent/{})'.format(
                decode_utf8(libtorrent.version)))
        """When running on a network where the bandwidth is in such an abundance
        that it's virtually infinite, this algorithm is no longer necessary, and
        might even be harmful to throughput. It is adviced to experiment with the
        session_setting::mixed_mode_algorithm, setting it to session_settings::prefer_tcp.
        This setting entirely disables the balancing and unthrottles all connections."""
        settings.mixed_mode_algorithm = 0

        # Fingerprint = 'LT1080' == LibTorrent 1.0.8.0
        fingerprint = libtorrent.fingerprint(
            b'LT', *(int(i) for i in libtorrent.version.split('.')))

        self.session = libtorrent.session(fingerprint=fingerprint)
        self.session.listen_on(
            6881, 6891
        )  # This is just a port suggestion. On failure, the port is automatically selected.

        # Prevent conversion to C int error
        settings.download_rate_limit = min(max_download_speed, 999999) * 1024
        settings.upload_rate_limit = min(max_upload_speed, 999999) * 1024

        self.session.set_settings(settings)
示例#2
0
def create_torrent(directory, announces=None, output=None, comment=None, web_seeds=None):
    if not output:
        output = directory + ".torrent"

    # "If a piece size of 0 is specified, a piece_size will be calculated such that the torrent file is roughly 40 kB."
    piece_size_multiplier = 0
    piece_size = (16 * 1024) * piece_size_multiplier  # Must be multiple of 16KB

    # http://www.libtorrent.org/make_torrent.html#create-torrent
    flags = libtorrent.create_torrent_flags_t.calculate_file_hashes

    if not os.path.isdir(directory):
        raise Exception("The path {} is not a directory".format(directory))

    fs = libtorrent.file_storage()
    is_not_whitelisted = lambda node: not is_whitelisted(unicode_helpers.decode_utf8(node))
    libtorrent.add_files(fs, unicode_helpers.encode_utf8(directory), is_not_whitelisted, flags=flags)
    t = libtorrent.create_torrent(fs, piece_size=piece_size, flags=flags)

    for announce in announces:
        t.add_tracker(unicode_helpers.encode_utf8(announce))

    if comment:
        t.set_comment(unicode_helpers.encode_utf8(comment))

    for web_seed in web_seeds:
        t.add_url_seed(unicode_helpers.encode_utf8(web_seed))
    # t.add_http_seed("http://...")

    libtorrent.set_piece_hashes(t, unicode_helpers.encode_utf8(os.path.dirname(directory)))

    with open(output, "wb") as file_handle:
        file_handle.write(libtorrent.bencode(t.generate()))

    return output
示例#3
0
    def log_torrent_progress(self, s, mod_name):
        """Just log the download progress for now.
        Do not log anything if the torrent is 100% completed to prevent spamming
        while seeding."""
        # download_fraction = s.progress
        download_kBps = s.download_rate / 1024
        upload_kBps = s.upload_rate / 1024
        state = decode_utf8(s.state.name)

        if s.progress == 1:
            return

        Logger.info(
            'Progress: [{}] {:.2f}% complete (down: {:.1f} kB/s up: {:.1f} kB/s connections: {}) {}'
            .format(mod_name, s.progress * 100, download_kBps, upload_kBps,
                    s.num_peers, state))
示例#4
0
    def get_session_logs(self):
        """Get alerts from torrent engine and forward them to the manager process"""
        torrent_log = []

        alerts = self.session.pop_alerts(
        )  # Important: these are messages for the whole session, not only one torrent!
        # Use alert.handle in the future to get the torrent handle
        for alert in alerts:
            # Filter with: alert.category() & libtorrent.alert.category_t.error_notification
            message = decode_utf8(alert.message(), errors='ignore')
            Logger.info("Alerts: Category: {}, Message: {}".format(
                alert.category(), message))
            torrent_log.append({
                'message': message,
                'category': alert.category()
            })

        return torrent_log
示例#5
0
    def sync(self, force_sync=False, just_seed=False):
        """
        Synchronize the mod directory contents to contain exactly the files that
        are described in the torrent file.

        force_sync - Assume no resume data is available. Manually recheck all the
                     checksums for all the files in the torrent description.

        Individual torrent states:
        1) Downloading    -> Wait until it starts seeding
        2) Seeding        -> Pause the torrent to sync it to disk
        3) Paused         -> Data has been synced, We can start seeding while
                             waiting for the other torrents to download.
        4) Waiting seed   -> When all torrents are waiting seeds, pause to stop
        5) Paused to stop -> When all torrents are paused to stop, stop syncing
        """

        sync_success = True

        self.result_queue.progress(
            {
                'msg': 'Downloading metadata...',
                'log': [],
            }, 0)

        for mod in self.mods:
            try:
                self.prepare_libtorrent_params(mod, force_sync, just_seed)
            except (PrepareParametersException,
                    torrent_utils.AdminRequiredError) as ex:
                self.result_queue.reject({'msg': ex.args[0]})
                sync_success = False
                return sync_success

        if self.force_termination:
            Logger.info(
                'Sync: Downloading process was requested to stop before starting the download.'
            )
            self.result_queue.reject({
                'details':
                'Downloading process was requested to stop before starting the download.'
            })
            return

        for mod in self.mods:
            # Launch the download of the torrent
            Logger.info('Sync: Downloading {} to {}'.format(
                mod.torrent_url, mod.parent_location))
            torrent_handle = self.session.add_torrent(mod.libtorrent_params)
            mod.torrent_handle = torrent_handle

        self.get_torrents_status()

        self.eta = Eta()

        # Loop until state (5). All torrents finished and paused
        while not self.is_syncing_finished():
            self.handle_messages()

            self.log_session_progress()

            for mod in self.mods:
                if not mod.torrent_handle.is_valid():
                    Logger.info(
                        'Sync: Torrent {} - torrent handle is invalid. Terminating'
                        .format(mod.foldername))
                    self.force_termination = True  # reject will be made once all torrents are done
                    continue

                # It is assumed that all torrents below have a valid torrent_handle
                self.log_torrent_progress(mod.status, mod.foldername)

                if mod.status.error:
                    Logger.info(
                        'Sync: Torrent {} in error state. Terminating. Error string: {}'
                        .format(mod.foldername, decode_utf8(mod.status.error)))
                    self.force_termination = True  # reject will be made once all torrents are done
                    continue  # Torrent is now paused

                # Allow saving fast-resume data only after finishing checking the files of the torrent
                # If we save the data from a torrent while being checked, this will result
                # in marking the torrent as having only a fraction of data it really has.
                if mod.status.state in (libtorrent.torrent_status.downloading,
                                        libtorrent.torrent_status.finished,
                                        libtorrent.torrent_status.seeding):
                    mod.can_save_resume_data = True

                # Shut the torrent if we are terminating
                if self.force_termination:
                    if not mod.torrent_handle.is_paused():  # Don't spam logs
                        Logger.info(
                            'Sync: Pausing torrent {} for termination'.format(
                                mod.foldername))
                    self.pause_torrent(mod)

                # If state (2). Request pausing the torrent to synchronize data to disk
                if not mod.finished_hook_ran and mod.torrent_handle.is_seed():
                    if not mod.torrent_handle.is_paused():
                        Logger.info(
                            'Sync: Pausing torrent {} for disk syncing'.format(
                                mod.foldername))
                    self.pause_torrent(mod)

                # If state (3). Run the hooks and maybe start waiting-seed
                if not mod.finished_hook_ran and mod.torrent_handle.is_seed(
                ) and mod.torrent_handle.is_paused():
                    Logger.info(
                        'Sync: Torrent {} paused. Running finished_hook'.
                        format(mod.foldername))

                    hook_successful = self.torrent_finished_hook(mod)
                    if not hook_successful:
                        self.result_queue.reject({
                            'msg':
                            'Could not perform mod {} cleanup. Make sure the files are not in use by another program.'
                            .format(mod.foldername)
                        })
                        Logger.info(
                            'Sync: Could not perform mod {} cleanup. Make sure the files are not in use by another program.'
                            .format(mod.foldername))
                        sync_success = False
                        self.force_termination = True

                    mod.finished_hook_ran = True

                    # Do not go into state (4) if we are terminating
                    if not self.force_termination:
                        Logger.info(
                            'Sync: Seeding {} again until all downloads are done.'
                            .format(mod.foldername))
                        self.resume_torrent(mod)

            # If all are in state (4)
            if self.all_torrents_ran_finished_hooks() and not just_seed:
                Logger.info('Sync: Pausing all torrents for syncing end.')
                self.pause_all_torrents()

            sleep(self._update_interval)
            self.get_torrents_status()

        Logger.info('Sync: Main loop exited')

        for mod in self.mods:
            if not mod.torrent_handle.is_valid():
                self.result_queue.reject({
                    'details':
                    'Mod {} torrent handle is invalid'.format(mod.foldername)
                })
                sync_success = False
                continue

            self.save_resume_data(mod)

            self.log_torrent_progress(mod.status, mod.foldername)
            if mod.status.error:
                self.result_queue.reject({
                    'details':
                    'An error occured: Libtorrent error: {}'.format(
                        decode_utf8(mod.status.error))
                })
                sync_success = False

        return sync_success
示例#6
0
    def get_mod_torrent_metadata(self, mod, metadata_file):
        """Retrieve torrent metadata either from the metadata_file or from associated the file, if not present.
        return torrent_info, torrent_contents
        torrent_contents may be None. If that's the case, don't cache it.
        """

        torrent_info = None

        torrent_content = metadata_file.get_torrent_content()
        if torrent_content:
            try:
                torrent_info = torrent_utils.get_torrent_info_from_bytestring(
                    torrent_content)

            except RuntimeError as ex:  # Raised by libtorrent.torrent_info()
                error_message = decode_utf8(ex.args[0])
                Logger.error(
                    'TorrentSyncer: could not parse torrent cached metadata: {}'
                    .format(error_message))

        # If no cached torrent metadata content, download it now and cache it
        if not torrent_info:

            if mod.torrent_url.startswith(
                    'file://'):  # Local torrent from file
                try:
                    torrent_info = self.get_torrent_info_from_file(
                        mod.torrent_url[len('file://'):])

                except RuntimeError as ex:  # Raised by libtorrent.torrent_info()
                    error_message = 'Could not parse local torrent metadata: {}'.format(
                        decode_utf8(ex.args[0]))
                    Logger.error('TorrentSyncer: {}'.format(error_message))
                    raise PrepareParametersException(error_message)

                return torrent_info, None  # Don't cache torrent_content

            else:  # Torrent from url
                try:
                    Logger.info('TorrentSyncer: Fetching torrent: {}'.format(
                        mod.torrent_url))
                    res = requests_wrapper.download_url(None,
                                                        mod.torrent_url,
                                                        timeout=5)
                except requests_wrapper.DownloadException as ex:
                    error_message = 'Downloading metadata: {}'.format(
                        ex.args[0])
                    raise PrepareParametersException(error_message)

                if res.status_code == 404:
                    message = textwrap.dedent('''\
                        Torrent file could not be downloaded from the master server.
                        Reason: file not found on the server (HTTP 404).

                        This may be because the mods are updated on the server right now.
                        Please try again in a few minutes.
                        ''')
                    raise PrepareParametersException(message)

                elif res.status_code != 200:
                    message = textwrap.dedent('''\
                        Torrent file could not be downloaded from the master server.
                        HTTP error code: {}

                        Contact the master server owner to fix this issue.
                        '''.format(unicode(res.status_code)))
                    raise PrepareParametersException(message)

                try:
                    torrent_content = res.content
                    torrent_info = torrent_utils.get_torrent_info_from_bytestring(
                        res.content)

                except RuntimeError as ex:  # Raised by libtorrent.torrent_info()
                    error_message = 'Could not parse torrent metadata: {}\nContact the master server owner to fix this issue.'.format(
                        decode_utf8(ex.args[0]))
                    Logger.error('TorrentSyncer: {}'.format(error_message))
                    raise PrepareParametersException(error_message)

        return torrent_info, torrent_content