{"action":"created","comment":{"url":"https://api.github.com/repos/Tribler/tribler/pulls/comments/65187924","id":65187924,"diff_hunk":"@@ -0,0 +1,676 @@\n+# -*- coding: utf-8 -*-\n+# Written by Egbert Bouman, Mihai Capotă, Elric Milon, and Ardhi Putra Pratama H\n+\"\"\"Manage boosting of swarms\"\"\"\n+import ConfigParser\n+import json\n+import logging\n+import os\n+import random\n+from binascii import hexlify, unhexlify\n+\n+import errno\n+import libtorrent as lt\n+\n+from Tribler.Core.DownloadConfig import DownloadStartupConfig\n+from Tribler.Core.TorrentDef import TorrentDef\n+from Tribler.Core.Utilities.install_dir import determine_install_dir\n+from Tribler.Core.simpledefs import DLSTATUS_SEEDING, NTFY_TORRENTS, NTFY_UPDATE, NTFY_CHANNELCAST\n+from Tribler.Main.globals import DefaultDownloadStartupConfig\n+from Tribler.Policies.BoostingSource import ChannelSource\n+from Tribler.Policies.BoostingSource import DirectorySource\n+from Tribler.Policies.BoostingSource import RSSFeedSource\n+from Tribler.dispersy.taskmanager import TaskManager\n+\n+NUMBER_TYPES = (int, long, float)\n+\n+# CONFIG_FILE = \"boosting.ini\"\n+TRIBLER_ROOT = determine_install_dir()\n+CONFIG_FILE = os.path.join(TRIBLER_ROOT, \"boosting.ini\")\n+\n+\n+def lev(trnt_1, trnt_2):\n+    \"\"\"\n+    Calculates the Levenshtein distance between a and b.\n+    \"\"\"\n+    trnt_1_len, trnt_2_len = len(trnt_1), len(trnt_2)\n+    if trnt_1_len > trnt_2_len:\n+        # Make sure trnt_1_len <= trnt_2_len, to use O(min(trnt_1_len,trnt_2_len)) space\n+        trnt_1, trnt_2 = trnt_2, trnt_1\n+        trnt_1_len, trnt_2_len = trnt_2_len, trnt_1_len\n+\n+    current = range(trnt_1_len + 1)\n+    for i in range(1, trnt_2_len + 1):\n+        previous, current = current, [i] + [0] * trnt_1_len\n+        for j in range(1, trnt_1_len + 1):\n+            add, delete = previous[j] + 1, current[j - 1] + 1\n+            change = previous[j - 1]\n+            if trnt_1[j - 1] != trnt_2[i - 1]:\n+                change += 1\n+            current[j] = min(add, delete, change)\n+\n+    return current[trnt_1_len]\n+\n+def source_to_string(source_obj):\n+    return hexlify(source_obj) if len(source_obj) == 20 and not (source_obj.startswith('http://')\n+                                                                 or source_obj.startswith('https://')) else source_obj\n+\n+def string_to_source(source_str):\n+    return source_str.decode('hex') \\\n+        if len(source_str) == 40 and not (os.path.isdir(source_str) or source_str.startswith('http://')) else source_str\n+\n+class BoostingPolicy(object):\n+    \"\"\"\n+    Base class for determining what swarm selection policy will be applied\n+    \"\"\"\n+\n+    def __init__(self, session):\n+        self.session = session\n+        self.key = lambda x: None\n+        # function that checks if key can be applied to torrent\n+        self.key_check = lambda x: None\n+        self.reverse = None\n+\n+        self._logger = logging.getLogger(self.__class__.__name__)\n+\n+    def apply(self, torrents, max_active, force=False):\n+        \"\"\"\n+        apply the policy to the torrents stored\n+        \"\"\"\n+        sorted_torrents = sorted([torrent for torrent in torrents.itervalues()\n+                                  if self.key_check(torrent)],\n+                                 key=self.key, reverse=self.reverse)\n+\n+        torrents_start = []\n+        for torrent in sorted_torrents[:max_active]:\n+            if not self.session.get_download(torrent[\"metainfo\"].get_infohash()):\n+                torrents_start.append(torrent)\n+        torrents_stop = []\n+        for torrent in sorted_torrents[max_active:]:\n+            if self.session.get_download(torrent[\"metainfo\"].get_infohash()):\n+                torrents_stop.append(torrent)\n+\n+        if force:\n+            return torrents_start, torrents_stop\n+\n+        # if both results are empty for some reason (e.g, key_check too restrictive)\n+        # or torrent started less than half available torrent (try to keep boosting alive)\n+        # if it's already random, just let it be\n+        if not isinstance(self, RandomPolicy) and ((not torrents_start and not torrents_stop) or\n+                                                   (len(torrents_start) < len(torrents) / 2 and len(\n+                                                       torrents_start) < max_active / 2)):\n+            self._logger.error(\"Start and stop torrent list are empty. Fallback to Random\")\n+            # fallback to random policy\n+            torrents_start, torrents_stop = RandomPolicy(self.session).apply(torrents, max_active)\n+\n+        return torrents_start, torrents_stop\n+\n+\n+class RandomPolicy(BoostingPolicy):\n+    \"\"\"\n+    A credit mining policy that chooses swarm randomly\n+    \"\"\"\n+    def __init__(self, session):\n+        BoostingPolicy.__init__(self, session)\n+        self.key = lambda v: random.random()\n+        self.key_check = lambda v: True\n+        self.reverse = False\n+\n+\n+class CreationDatePolicy(BoostingPolicy):\n+    \"\"\"\n+    A credit mining policy that chooses swarm by its creation date\n+\n+    The idea is, older swarm need to be boosted.\n+    \"\"\"\n+    def __init__(self, session):\n+        BoostingPolicy.__init__(self, session)\n+        self.key = lambda v: v['creation_date']\n+        self.key_check = lambda v: v['creation_date'] > 0\n+        self.reverse = True\n+\n+\n+class SeederRatioPolicy(BoostingPolicy):\n+    \"\"\"\n+    Default policy. Find the most underseeded swarm to boost.\n+    \"\"\"\n+    def __init__(self, session):\n+        BoostingPolicy.__init__(self, session)\n+        self.key = lambda v: v['num_seeders'] / float(v['num_seeders'] +\n+                                                      v['num_leechers'])\n+        self.key_check = lambda v: isinstance(v['num_seeders'], NUMBER_TYPES) and isinstance(\n+            v['num_leechers'], NUMBER_TYPES) and v['num_seeders'] + v['num_leechers'] > 0\n+        self.reverse = False\n+\n+\n+class BoostingSettings(object):\n+    \"\"\"\n+    Class contains settings on boosting manager\n+    \"\"\"\n+    def __init__(self, session, policy=SeederRatioPolicy):\n+        self.session = session\n+\n+        self.config_file = CONFIG_FILE\n+        self.max_torrents_active = 30\n+        self.max_torrents_per_source = 100\n+        self.source_interval = 20\n+        self.swarm_interval = 20\n+        self.initial_swarm_interval = 30\n+        self.policy = policy(session)\n+        self.tracker_interval = 50\n+        self.initial_tracker_interval = 25\n+        self.logging_interval = 40\n+        self.initial_logging_interval = 20\n+\n+        self.min_connection_start = 5\n+        self.min_channels_start = 100\n+\n+        self.share_mode_target = 2\n+        self.credit_mining_path = os.path.join(DefaultDownloadStartupConfig.getInstance().get_dest_dir(),\n+                                               \"credit_mining\")\n+\n+\n+class BoostingManager(TaskManager):\n+    \"\"\"\n+    Class to manage all the credit mining activities\n+    \"\"\"\n+\n+    __single = None\n+    MULTIPLIER_DL = [0, 0.2, 0.6, 1, 1.5, 3]\n+    DEFAULT_PRIORITY_TORRENT = 5\n+\n+    def __init__(self, session, settings=None):\n+        super(BoostingManager, self).__init__()\n+        self._logger = logging.getLogger(self.__class__.__name__)\n+\n+        BoostingManager.__single = self\n+\n+        self._saved_attributes = [\"max_torrents_per_source\",\n+                                  \"max_torrents_active\", \"source_interval\",\n+                                  \"swarm_interval\", \"share_mode_target\",\n+                                  \"tracker_interval\", \"logging_interval\"]\n+\n+        self.session = session\n+        self.torrent_db = self.session.open_dbhandler(NTFY_TORRENTS)\n+        self.channelcast_db = self.session.open_dbhandler(NTFY_CHANNELCAST)\n+\n+        # use provided settings or a default one\n+        self.settings = settings or BoostingSettings(session)\n+\n+        if not os.path.exists(self.settings.credit_mining_path):\n+            os.makedirs(self.settings.credit_mining_path)\n+\n+\n+        self.boosting_sources = {}\n+        self.torrents = {}\n+\n+        local_settings = {}\n+        local_settings['share_mode_target'] = self.settings.share_mode_target\n+        self.session.lm.ltmgr.get_session().set_settings(local_settings)\n+\n+        if os.path.exists(self.settings.config_file):\n+            self._logger.info(\"Config file %s\", open(self.settings.config_file).read())\n+            self.load_config()\n+        else:\n+            self._logger.warning(\"Initial config file missing\")\n+\n+        self.session.add_observer(self.on_torrent_notify, NTFY_TORRENTS, [NTFY_UPDATE])\n+\n+        # TODO(emilon): Refactor this to use taskmanager\n+        self.session.lm.threadpool.add_task(self._select_torrent, self.settings.initial_swarm_interval,\n+                                            \"CreditMining_select_init\")\n+        self.session.lm.threadpool.add_task(self.scrape_trackers,\n+                                            self.settings.initial_tracker_interval, \"CreditMining_scrape\")\n+        self.session.lm.threadpool.add_task(self.log_statistics,\n+                                            self.settings.initial_logging_interval, \"CreditMining_log_init\")\n+\n+    @staticmethod\n+    def get_instance(*args, **kw):\n+        \"\"\"\n+        get single instance of Boostingmanager\n+        \"\"\"\n+        if BoostingManager.__single is None:\n+            BoostingManager(*args, **kw)\n+        return BoostingManager.__single\n+\n+    @staticmethod\n+    def del_instance():\n+        \"\"\"\n+        resetting, then deleting single instance\n+        \"\"\"\n+        BoostingManager.__single = None\n+\n+    def shutdown(self):\n+        \"\"\"\n+        save configuration before stopping stuffs\n+        \"\"\"\n+        self.save_config()\n+        self._logger.info(\"Shutting down boostingmanager\")\n+\n+        for sourcekey in self.boosting_sources.keys():\n+            self.remove_source(sourcekey)\n+\n+        if self.session.lm.threadpool.is_pending_task_active(\"CreditMining_select_init\"):\n+            self.session.lm.threadpool.cancel_pending_task(\"CreditMining_select_init\")\n+        if self.session.lm.threadpool.is_pending_task_active(\"CreditMining_scrape\"):\n+            self.session.lm.threadpool.cancel_pending_task(\"CreditMining_scrape\")\n+        if self.session.lm.threadpool.is_pending_task_active(\"CreditMining_log_init\"):\n+            self.session.lm.threadpool.cancel_pending_task(\"CreditMining_log_init\")\n+\n+        self.cancel_all_pending_tasks()\n+        # for torrent in self.torrents.itervalues():\n+        #     self.stop_download(torrent)\n+\n+    def get_source_object(self, sourcekey):\n+        \"\"\"\n+        Get the actual object of the source string/key\n+        \"\"\"\n+        # first, assume it's already source-friendly format\n+        ret = self.boosting_sources.get(sourcekey, None)\n+\n+        # we can't find source, lets assume it's string now\n+        if not ret:\n+            try:  # is it channel?\n+                int(sourcekey, 16)\n+                ret = self.boosting_sources.get(unhexlify(sourcekey), None)\n+            except ValueError:\n+                # it's not, so we can't find it\n+                self._logger.debug(\"Can't find source object in type %s\", type(sourcekey))\n+\n+        return ret\n+\n+    def set_enable_mining(self, source, mining_bool=True, force_restart=False):\n+        \"\"\"\n+        Dynamically enable/disable mining source.\n+        \"\"\"\n+        for ihash, tor in self.torrents.iteritems():\n+            if tor['source'] == source_to_string(source):\n+                self.torrents[ihash]['enabled'] = mining_bool\n+\n+                # pause torrent download from disabled source\n+                if not mining_bool:\n+                    self.stop_download(tor)\n+\n+        self.boosting_sources[string_to_source(source)].enabled = mining_bool\n+\n+        self._logger.info(\"Set mining source %s %s\", source, mining_bool)\n+\n+        if force_restart:\n+            self._select_torrent()\n+\n+    def add_source(self, source):\n+        \"\"\"\n+        add new source into the boosting manager\n+        \"\"\"\n+        if source not in self.boosting_sources:\n+            args = (self.session, source, self.settings, self.on_torrent_insert)\n+\n+            isdir = os.path.isdir(source)\n+\n+            if isdir:\n+                self.boosting_sources[source] = DirectorySource(*args)\n+            elif source.startswith('http://') or source.startswith('https://'):\n+                self.boosting_sources[source] = RSSFeedSource(*args)\n+            elif len(source) == 20:\n+                self.boosting_sources[source] = ChannelSource(*args)\n+            else:\n+                self._logger.error(\"Cannot add unknown source %s\", source)\n+                return\n+\n+            self._logger.info(\"Added source %s\", source)\n+        else:\n+            self._logger.info(\"Already have source %s\", source)\n+\n+    def remove_source(self, source_key):\n+        \"\"\"\n+        remove source by stop the downloading and remove its metainfo for all its swarms\n+        \"\"\"\n+        if source_key in self.boosting_sources:\n+            source = self.boosting_sources.pop(source_key)\n+            source.kill_tasks()\n+            self._logger.info(\"Removed source %s\", source_key)\n+\n+            rm_torrents = [torrent for _, torrent in self.torrents.items()\n+                           if torrent['source'] == source_to_string(source_key)]\n+\n+            for torrent in rm_torrents:\n+                self.stop_download(torrent)\n+                self.torrents.pop(torrent[\"metainfo\"].get_infohash(), None)\n+\n+            self._logger.info(\"Torrents download stopped and removed\")\n+\n+    def on_torrent_insert(self, source, infohash, torrent):\n+        \"\"\"\n+        This function called when a source finally determined. Fetch some torrents from it,\n+        then insert it to our data\n+        \"\"\"\n+        def compare_torrents(torrent_1, torrent_2):\n+            \"\"\"\n+            comparing swarms. We don't want to download same swarm with different infohash\n+            :return: whether those t1 and t2 similar enough\n+            \"\"\"\n+            files1 = [files for files in torrent_1['metainfo'].get_files_with_length() if files[1] > 1024 * 1024]\n+            files2 = [files for files in torrent_2['metainfo'].get_files_with_length() if files[1] > 1024 * 1024]\n+\n+            if len(files1) == len(files2):\n+                for ft1 in files1:\n+                    for ft2 in files2:\n+                        if ft1[1] != ft2[1] or lev(ft1[0], ft2[0]) > 5:\n+                            return False\n+                return True\n+            return False\n+\n+        # Remember where we got this torrent from\n+\n+        self._logger.debug(\"remember torrent %s from %s\", torrent, source_to_string(source))\n+\n+        torrent['source'] = source_to_string(source)\n+\n+        boost_source = self.boosting_sources.get(source, None)\n+        if not boost_source:\n+            self._logger.info(\"Dropping torrent insert from removed source: %s\", repr(torrent))\n+            return\n+        elif boost_source.archive:\n+            torrent['preload'] = True\n+            torrent['prio'] = 100\n+\n+        # If duplicates exist, set is_duplicate to True, except for the one with the most seeders.\n+        duplicates = [other for other in self.torrents.values() if compare_torrents(torrent, other)]\n+        if duplicates:\n+            duplicates += [torrent]\n+            healthiest_torrent = max([(torrent['num_seeders'], torrent) for torrent in duplicates])[1]\n+            for duplicate in duplicates:\n+                is_duplicate = healthiest_torrent != duplicate\n+                duplicate['is_duplicate'] = is_duplicate\n+                if is_duplicate and duplicate.get('download', None):\n+                    self.stop_download(duplicate)\n+\n+        self.torrents[infohash] = torrent\n+\n+    def on_torrent_notify(self, subject, change_type, infohash):\n+        \"\"\"\n+        Notify us when we have new seeder/leecher value in torrent from tracker\n+        \"\"\"\n+        if infohash not in self.torrents:\n+            return\n+\n+        self._logger.debug(\"infohash %s %s %s updated\", subject, change_type, hexlify(infohash))\n+\n+        tdict = self.torrent_db.getTorrent(infohash, keys=['C.torrent_id', 'infohash', 'name',\n+                                                           'length', 'category', 'status', 'num_seeders',\n+                                                           'num_leechers'])\n+\n+        if tdict:\n+            infohash_str = hexlify(tdict['infohash'])\n+\n+            new_seed = tdict['num_seeders']\n+            new_leecher = tdict['num_leechers']\n+\n+            if new_seed - self.torrents[tdict['infohash']]['num_seeders'] \\\n+                    or new_leecher - self.torrents[tdict['infohash']]['num_leechers']:\n+                self.torrents[tdict['infohash']]['num_seeders'] = new_seed\n+                self.torrents[tdict['infohash']]['num_leechers'] = new_leecher\n+                self._logger.info(\"infohash %s changed s:%d l:%d\", infohash_str, new_seed, new_leecher)\n+\n+    def scrape_trackers(self):\n+        \"\"\"\n+        Manually scrape tracker by requesting to tracker manager\n+        \"\"\"\n+\n+        for infohash, _ in self.torrents.iteritems():\n+            # tf_meta = torrent['metainfo']\n+\n+            # torrent handle\n+            # lt_torrent = self.session.lm.ltmgr.get_session().find_torrent(lt.big_number(infohash))\n+\n+            # check health(seeder/leecher)\n+            self.session.lm.torrent_checker.add_gui_request(infohash, True)\n+\n+        self.session.lm.threadpool.add_task(self.scrape_trackers, self.settings.tracker_interval, \"CreditMining_scrape\")\n+\n+    def set_archive(self, source, enable):\n+        \"\"\"\n+        setting archive of a particular source. Affect all the torrents in this source\n+        \"\"\"\n+        if source in self.boosting_sources:\n+            self.boosting_sources[source].archive = enable\n+            self._logger.info(\"Set archive mode for %s to %s\", source, enable)\n+        else:\n+            self._logger.error(\"Could not set archive mode for unknown source %s\", source)\n+\n+    def start_download(self, torrent):\n+        \"\"\"\n+        Start downloading a particular torrent and add it to download list in Tribler\n+        \"\"\"\n+        def do_start():\n+            \"\"\"\n+            add the actual torrent to the manager to download it later\n+            :return:\n+            \"\"\"\n+            dscfg = DownloadStartupConfig()\n+            dscfg.set_dest_dir(self.settings.credit_mining_path)\n+            dscfg.set_safe_seeding(False)\n+\n+            # just a debug variable\n+            tobj = torrent\n+\n+            preload = tobj.get('preload', False)\n+\n+            # not using Session.start_download because we need to specify pstate\n+            try:\n+                assert self.session.get_libtorrent()\n+                assert not self.session.lm.download_exists(tobj[\"metainfo\"].get_infohash())\n+            except AssertionError, err:\n+                self._logger.error(\"Can't fullfil assertion : %s. Not download %s\", err.message,\n+                                   hexlify(tobj[\"metainfo\"].get_infohash()))\n+                return\n+\n+            self._logger.info(\"Starting %s preload %s has pstate %s\",\n+                              hexlify(tobj[\"metainfo\"].get_infohash()), preload,\n+                              True if tobj.get('pstate', None) else False)\n+\n+            tobj['download'] = self.session.lm.add(tobj['metainfo'], dscfg, pstate=tobj.get('pstate', None),\n+                                                   hidden=True, share_mode=not preload, checkpoint_disabled=True)\n+            tobj['download'].set_priority(tobj.get('prio', 1))\n+\n+        self.session.lm.threadpool.add_task_in_thread(do_start, 0)\n+\n+    def stop_download(self, torrent):\n+        \"\"\"\n+        Stopping torrent that currently downloading\n+        \"\"\"\n+\n+        def do_stop():\n+            \"\"\"\n+            The actual function to stop torrent downloading\n+            :return:\n+            \"\"\"\n+            ihash = lt.big_number(torrent[\"metainfo\"].get_infohash())\n+            self._logger.info(\"Stopping %s\", str(ihash))\n+            download = torrent.pop('download', False)\n+            lt_torrent = self.session.lm.ltmgr.get_session().find_torrent(ihash)\n+            if download and lt_torrent.is_valid():\n+                self._logger.debug(\"Writing resume data\")\n+                torrent['pstate'] = {'engineresumedata': download.write_resume_data()}\n+                self.session.remove_download(download, hidden=True)\n+\n+        self.session.lm.threadpool.add_task_in_thread(do_stop, 0)\n+\n+    def _select_torrent(self):\n+        \"\"\"\n+        Function to select which torrent in the torrent list will be downloaded in the\n+        next iteration. It depends on the source and applied policy\n+        \"\"\"\n+        torrents = {}\n+        for infohash, torrent in self.torrents.iteritems():\n+            # we prioritize archive source\n+            if torrent.get('preload', False):\n+                if 'download' not in torrent:\n+                    self.start_download(torrent)\n+                elif torrent['download'].get_status() == DLSTATUS_SEEDING:\n+                    self.stop_download(torrent)\n+            elif not torrent.get('is_duplicate', False):\n+                if torrent.get('enabled', True):\n+                    torrents[infohash] = torrent\n+\n+        if self.settings.policy is not None and torrents:\n+            # Determine which torrent to start and which to stop.\n+            torrents_start, torrents_stop = self.settings.policy.apply(\n+                torrents, self.settings.max_torrents_active)\n+            for torrent in torrents_stop:\n+                self.stop_download(torrent)\n+            for torrent in torrents_start:\n+                self.start_download(torrent)\n+\n+            self._logger.info(\"Selecting from %s torrents %s start download\", len(torrents), len(torrents_start))\n+\n+        self.session.lm.threadpool.add_task(self._select_torrent, self.settings.swarm_interval)\n+\n+    def load_config(self):\n+        \"\"\"\n+        load config in file configuration and apply it to manager\n+        \"\"\"\n+        config = ConfigParser.ConfigParser()\n+        config.read(self.settings.config_file)\n+        validate_source = lambda s: unhexlify(s) if len(s) == 40 and not s.startswith(\"http\") else s\n+\n+        def _add_sources(value):\n+            \"\"\"\n+            adding sources in configuration file\n+            \"\"\"\n+            for boosting_source in json.loads(value):\n+                boosting_source = validate_source(boosting_source)\n+                self.add_source(boosting_source)\n+\n+        def _archive_sources(value):\n+            \"\"\"\n+            setting archive to sources\n+            \"\"\"\n+            for archive_source in json.loads(value):\n+                archive_source = validate_source(archive_source)\n+                self.set_archive(archive_source, True)\n+\n+        def _set_enable_boosting(value, enabled):\n+            \"\"\"\n+            set disable/enable source\n+            \"\"\"\n+            for boosting_source in json.loads(value):\n+                boosting_source = validate_source(boosting_source)\n+                if not self.boosting_sources[boosting_source]:\n+                    self.add_source(boosting_source)\n+                self.boosting_sources[boosting_source].enabled = enabled\n+\n+        switch = {\n+            \"boosting_sources\": {\n+                \"cmd\": _add_sources,\n+                \"args\": (None,)\n+            },\n+            \"archive_sources\": {\n+                \"cmd\": _archive_sources,\n+                \"args\": (None,)\n+            },\n+            \"boosting_enabled\": {\n+                \"cmd\": _set_enable_boosting,\n+                \"args\": (None, True)\n+            },\n+            \"boosting_disabled\": {\n+                \"cmd\": _set_enable_boosting,\n+                \"args\": (None, False)\n+            },\n+        }\n+\n+        switch_policy = {\n+            \"random\" : RandomPolicy,\n+            \"creation\" : CreationDatePolicy,\n+            \"seederratio\" : SeederRatioPolicy\n+        }\n+\n+        for k, val in config.items(__name__):\n+            try:\n+                if k in self._saved_attributes:\n+                    object.__setattr__(self.settings, k, int(val))\n+                elif k == \"policy\":\n+                    self.settings.policy = switch_policy[val](self.session)\n+                else:\n+                    switch[k][\"cmd\"](*((switch[k]['args'][0] or val,) + switch[k]['args'][1:]))\n+            except KeyError:\n+                self._logger.error(\"Key %s can't be applied\", k)\n+\n+    def save_config(self):","path":"Tribler/Policies/BoostingManager.py","position":598,"original_position":598,"commit_id":"7981c6202f6129676c03d64b38b1647ac0142185","original_commit_id":"7981c6202f6129676c03d64b38b1647ac0142185","user":{"login":"devos50","id":1707075,"avatar_url":"https://avatars.githubusercontent.com/u/1707075?v=3","gravatar_id":"","url":"https://api.github.com/users/devos50","html_url":"https://github.com/devos50","followers_url":"https://api.github.com/users/devos50/followers","following_url":"https://api.github.com/users/devos50/following{/other_user}","gists_url":"https://api.github.com/users/devos50/gists{/gist_id}","starred_url":"https://api.github.com/users/devos50/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/devos50/subscriptions","organizations_url":"https://api.github.com/users/devos50/orgs","repos_url":"https://api.github.com/users/devos50/repos","events_url":"https://api.github.com/users/devos50/events{/privacy}","received_events_url":"https://api.github.com/users/devos50/received_events","type":"User","site_admin":false},"body":"I think we should think about a new configuration approach. This method is full with work-arounds to get the data in the right form :/","created_at":"2016-05-31T14:08:07Z","updated_at":"2016-05-31T14:08:07Z","html_url":"https://github.com/Tribler/tribler/pull/2064#discussion_r65187924","pull_request_url":"https://api.github.com/repos/Tribler/tribler/pulls/2064","_links":{"self":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/comments/65187924"},"html":{"href":"https://github.com/Tribler/tribler/pull/2064#discussion_r65187924"},"pull_request":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/2064"}}},"pull_request":{"url":"https://api.github.com/repos/Tribler/tribler/pulls/2064","id":64983870,"html_url":"https://github.com/Tribler/tribler/pull/2064","diff_url":"https://github.com/Tribler/tribler/pull/2064.diff","patch_url":"https://github.com/Tribler/tribler/pull/2064.patch","issue_url":"https://api.github.com/repos/Tribler/tribler/issues/2064","number":2064,"state":"open","locked":false,"title":"WIP : First Credit mining prototype","user":{"login":"ardhipoetra","id":2739025,"avatar_url":"https://avatars.githubusercontent.com/u/2739025?v=3","gravatar_id":"","url":"https://api.github.com/users/ardhipoetra","html_url":"https://github.com/ardhipoetra","followers_url":"https://api.github.com/users/ardhipoetra/followers","following_url":"https://api.github.com/users/ardhipoetra/following{/other_user}","gists_url":"https://api.github.com/users/ardhipoetra/gists{/gist_id}","starred_url":"https://api.github.com/users/ardhipoetra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ardhipoetra/subscriptions","organizations_url":"https://api.github.com/users/ardhipoetra/orgs","repos_url":"https://api.github.com/users/ardhipoetra/repos","events_url":"https://api.github.com/users/ardhipoetra/events{/privacy}","received_events_url":"https://api.github.com/users/ardhipoetra/received_events","type":"User","site_admin":false},"body":"This PR handles first protoype of auto credit mining in Tribler. Actually, it has been mentioned in #573.\r\n\r\nSome GUI components change in home screen. Instead of showing most recent/popular video, list of popular channels is shown. Selecting (or unselect) those checkboxes will affect what channel that we will _mine_.\r\n\r\n![home-page](https://cloud.githubusercontent.com/assets/2739025/14215418/18113e48-f843-11e5-8a56-584a3899a6bd.png)\r\n\r\n\r\nA new GUI page called credit mining is there. Basically it didn't change much from #23. I just changed the adding/removing sources part (especially channels) to be done in single click. And now, each source has it's own statistics, and I keep the total statistics in the top part of the screen. Some source info also available in the right part.\r\n\r\n![cm-page](https://cloud.githubusercontent.com/assets/2739025/14214760/113b7358-f83f-11e5-897b-e5a83dabe058.png)\r\n\r\nMore in the core:\r\nWhen Tribler start the credit mining process, it will (by default) look for swarm that has less seeder ratio compared to all the peers. Tribler will auto-download those swarm, and maximize the ratio credit if possible. We also determined the ratio we want.\r\n\r\nSome progress shown in #1842. It includes (also some works that need to be done): \r\n\r\n- [x] revamped some GUI's (cc80e2619003e74ae037895c1bac7978e64799ee)\r\n- [x] Fixing known bugs from previous work (415efe5d773f69e6e780f396dbbe27498fbe3d69)\r\n- [ ] Dead tracker reduces performance (blacklisting OR completely integrate with central tracker management)\r\n- [x] DHT-only support (not perfect but will do for now) (ec2586ba6c8078d1211e2bfa199bc8760e3f3d23).\r\nBasically, it translates information we can get from peers discovered by DHT to swarm health (seeder/leecher)\r\n- [x] Main download (in _download_ screen) aware mechanism (cee0d5d0bfe4dc491a3434a28b83ba96f9e2f039).\r\nWe don't want main downloading activity become slower because of mining. Also, continue to download some stuff that already mined.\r\n- [x] DHT Performance evaluation -beta (nothing to do with Tribler code)\r\n","created_at":"2016-04-01T18:14:37Z","updated_at":"2016-05-31T14:08:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"714f1ee0825e7405d41bf88e1c7944359004c29d","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Tribler/tribler/pulls/2064/commits","review_comments_url":"https://api.github.com/repos/Tribler/tribler/pulls/2064/comments","review_comment_url":"https://api.github.com/repos/Tribler/tribler/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Tribler/tribler/issues/2064/comments","statuses_url":"https://api.github.com/repos/Tribler/tribler/statuses/7981c6202f6129676c03d64b38b1647ac0142185","head":{"label":"ardhipoetra:credit_mining","ref":"credit_mining","sha":"7981c6202f6129676c03d64b38b1647ac0142185","user":{"login":"ardhipoetra","id":2739025,"avatar_url":"https://avatars.githubusercontent.com/u/2739025?v=3","gravatar_id":"","url":"https://api.github.com/users/ardhipoetra","html_url":"https://github.com/ardhipoetra","followers_url":"https://api.github.com/users/ardhipoetra/followers","following_url":"https://api.github.com/users/ardhipoetra/following{/other_user}","gists_url":"https://api.github.com/users/ardhipoetra/gists{/gist_id}","starred_url":"https://api.github.com/users/ardhipoetra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ardhipoetra/subscriptions","organizations_url":"https://api.github.com/users/ardhipoetra/orgs","repos_url":"https://api.github.com/users/ardhipoetra/repos","events_url":"https://api.github.com/users/ardhipoetra/events{/privacy}","received_events_url":"https://api.github.com/users/ardhipoetra/received_events","type":"User","site_admin":false},"repo":{"id":47112361,"name":"tribler","full_name":"ardhipoetra/tribler","owner":{"login":"ardhipoetra","id":2739025,"avatar_url":"https://avatars.githubusercontent.com/u/2739025?v=3","gravatar_id":"","url":"https://api.github.com/users/ardhipoetra","html_url":"https://github.com/ardhipoetra","followers_url":"https://api.github.com/users/ardhipoetra/followers","following_url":"https://api.github.com/users/ardhipoetra/following{/other_user}","gists_url":"https://api.github.com/users/ardhipoetra/gists{/gist_id}","starred_url":"https://api.github.com/users/ardhipoetra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ardhipoetra/subscriptions","organizations_url":"https://api.github.com/users/ardhipoetra/orgs","repos_url":"https://api.github.com/users/ardhipoetra/repos","events_url":"https://api.github.com/users/ardhipoetra/events{/privacy}","received_events_url":"https://api.github.com/users/ardhipoetra/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ardhipoetra/tribler","description":"Privacy with BitTorrent and resilient to shut down","fork":true,"url":"https://api.github.com/repos/ardhipoetra/tribler","forks_url":"https://api.github.com/repos/ardhipoetra/tribler/forks","keys_url":"https://api.github.com/repos/ardhipoetra/tribler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ardhipoetra/tribler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ardhipoetra/tribler/teams","hooks_url":"https://api.github.com/repos/ardhipoetra/tribler/hooks","issue_events_url":"https://api.github.com/repos/ardhipoetra/tribler/issues/events{/number}","events_url":"https://api.github.com/repos/ardhipoetra/tribler/events","assignees_url":"https://api.github.com/repos/ardhipoetra/tribler/assignees{/user}","branches_url":"https://api.github.com/repos/ardhipoetra/tribler/branches{/branch}","tags_url":"https://api.github.com/repos/ardhipoetra/tribler/tags","blobs_url":"https://api.github.com/repos/ardhipoetra/tribler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ardhipoetra/tribler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ardhipoetra/tribler/git/refs{/sha}","trees_url":"https://api.github.com/repos/ardhipoetra/tribler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ardhipoetra/tribler/statuses/{sha}","languages_url":"https://api.github.com/repos/ardhipoetra/tribler/languages","stargazers_url":"https://api.github.com/repos/ardhipoetra/tribler/stargazers","contributors_url":"https://api.github.com/repos/ardhipoetra/tribler/contributors","subscribers_url":"https://api.github.com/repos/ardhipoetra/tribler/subscribers","subscription_url":"https://api.github.com/repos/ardhipoetra/tribler/subscription","commits_url":"https://api.github.com/repos/ardhipoetra/tribler/commits{/sha}","git_commits_url":"https://api.github.com/repos/ardhipoetra/tribler/git/commits{/sha}","comments_url":"https://api.github.com/repos/ardhipoetra/tribler/comments{/number}","issue_comment_url":"https://api.github.com/repos/ardhipoetra/tribler/issues/comments{/number}","contents_url":"https://api.github.com/repos/ardhipoetra/tribler/contents/{+path}","compare_url":"https://api.github.com/repos/ardhipoetra/tribler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ardhipoetra/tribler/merges","archive_url":"https://api.github.com/repos/ardhipoetra/tribler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ardhipoetra/tribler/downloads","issues_url":"https://api.github.com/repos/ardhipoetra/tribler/issues{/number}","pulls_url":"https://api.github.com/repos/ardhipoetra/tribler/pulls{/number}","milestones_url":"https://api.github.com/repos/ardhipoetra/tribler/milestones{/number}","notifications_url":"https://api.github.com/repos/ardhipoetra/tribler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ardhipoetra/tribler/labels{/name}","releases_url":"https://api.github.com/repos/ardhipoetra/tribler/releases{/id}","deployments_url":"https://api.github.com/repos/ardhipoetra/tribler/deployments","created_at":"2015-11-30T10:47:06Z","updated_at":"2016-01-26T15:20:10Z","pushed_at":"2016-05-30T19:53:16Z","git_url":"git://github.com/ardhipoetra/tribler.git","ssh_url":"git@github.com:ardhipoetra/tribler.git","clone_url":"https://github.com/ardhipoetra/tribler.git","svn_url":"https://github.com/ardhipoetra/tribler","homepage":"http://www.tribler.org","size":114377,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"devel"}},"base":{"label":"Tribler:devel","ref":"devel","sha":"dcccc8954bd3b250b5d86e69a8c7bbaff9215165","user":{"login":"Tribler","id":2913489,"avatar_url":"https://avatars.githubusercontent.com/u/2913489?v=3","gravatar_id":"","url":"https://api.github.com/users/Tribler","html_url":"https://github.com/Tribler","followers_url":"https://api.github.com/users/Tribler/followers","following_url":"https://api.github.com/users/Tribler/following{/other_user}","gists_url":"https://api.github.com/users/Tribler/gists{/gist_id}","starred_url":"https://api.github.com/users/Tribler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tribler/subscriptions","organizations_url":"https://api.github.com/users/Tribler/orgs","repos_url":"https://api.github.com/users/Tribler/repos","events_url":"https://api.github.com/users/Tribler/events{/privacy}","received_events_url":"https://api.github.com/users/Tribler/received_events","type":"Organization","site_admin":false},"repo":{"id":8411137,"name":"tribler","full_name":"Tribler/tribler","owner":{"login":"Tribler","id":2913489,"avatar_url":"https://avatars.githubusercontent.com/u/2913489?v=3","gravatar_id":"","url":"https://api.github.com/users/Tribler","html_url":"https://github.com/Tribler","followers_url":"https://api.github.com/users/Tribler/followers","following_url":"https://api.github.com/users/Tribler/following{/other_user}","gists_url":"https://api.github.com/users/Tribler/gists{/gist_id}","starred_url":"https://api.github.com/users/Tribler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tribler/subscriptions","organizations_url":"https://api.github.com/users/Tribler/orgs","repos_url":"https://api.github.com/users/Tribler/repos","events_url":"https://api.github.com/users/Tribler/events{/privacy}","received_events_url":"https://api.github.com/users/Tribler/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Tribler/tribler","description":"Privacy enhanced BitTorrent client with P2P content discovery","fork":false,"url":"https://api.github.com/repos/Tribler/tribler","forks_url":"https://api.github.com/repos/Tribler/tribler/forks","keys_url":"https://api.github.com/repos/Tribler/tribler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Tribler/tribler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Tribler/tribler/teams","hooks_url":"https://api.github.com/repos/Tribler/tribler/hooks","issue_events_url":"https://api.github.com/repos/Tribler/tribler/issues/events{/number}","events_url":"https://api.github.com/repos/Tribler/tribler/events","assignees_url":"https://api.github.com/repos/Tribler/tribler/assignees{/user}","branches_url":"https://api.github.com/repos/Tribler/tribler/branches{/branch}","tags_url":"https://api.github.com/repos/Tribler/tribler/tags","blobs_url":"https://api.github.com/repos/Tribler/tribler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Tribler/tribler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Tribler/tribler/git/refs{/sha}","trees_url":"https://api.github.com/repos/Tribler/tribler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Tribler/tribler/statuses/{sha}","languages_url":"https://api.github.com/repos/Tribler/tribler/languages","stargazers_url":"https://api.github.com/repos/Tribler/tribler/stargazers","contributors_url":"https://api.github.com/repos/Tribler/tribler/contributors","subscribers_url":"https://api.github.com/repos/Tribler/tribler/subscribers","subscription_url":"https://api.github.com/repos/Tribler/tribler/subscription","commits_url":"https://api.github.com/repos/Tribler/tribler/commits{/sha}","git_commits_url":"https://api.github.com/repos/Tribler/tribler/git/commits{/sha}","comments_url":"https://api.github.com/repos/Tribler/tribler/comments{/number}","issue_comment_url":"https://api.github.com/repos/Tribler/tribler/issues/comments{/number}","contents_url":"https://api.github.com/repos/Tribler/tribler/contents/{+path}","compare_url":"https://api.github.com/repos/Tribler/tribler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Tribler/tribler/merges","archive_url":"https://api.github.com/repos/Tribler/tribler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Tribler/tribler/downloads","issues_url":"https://api.github.com/repos/Tribler/tribler/issues{/number}","pulls_url":"https://api.github.com/repos/Tribler/tribler/pulls{/number}","milestones_url":"https://api.github.com/repos/Tribler/tribler/milestones{/number}","notifications_url":"https://api.github.com/repos/Tribler/tribler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Tribler/tribler/labels{/name}","releases_url":"https://api.github.com/repos/Tribler/tribler/releases{/id}","deployments_url":"https://api.github.com/repos/Tribler/tribler/deployments","created_at":"2013-02-25T14:51:58Z","updated_at":"2016-05-29T11:08:48Z","pushed_at":"2016-05-31T13:59:13Z","git_url":"git://github.com/Tribler/tribler.git","ssh_url":"git@github.com:Tribler/tribler.git","clone_url":"https://github.com/Tribler/tribler.git","svn_url":"https://github.com/Tribler/tribler","homepage":"http://www.tribler.org","size":132738,"stargazers_count":1875,"watchers_count":1875,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":202,"mirror_url":null,"open_issues_count":288,"forks":202,"open_issues":288,"watchers":1875,"default_branch":"devel"}},"_links":{"self":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/2064"},"html":{"href":"https://github.com/Tribler/tribler/pull/2064"},"issue":{"href":"https://api.github.com/repos/Tribler/tribler/issues/2064"},"comments":{"href":"https://api.github.com/repos/Tribler/tribler/issues/2064/comments"},"review_comments":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/2064/comments"},"review_comment":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/2064/commits"},"statuses":{"href":"https://api.github.com/repos/Tribler/tribler/statuses/7981c6202f6129676c03d64b38b1647ac0142185"}}}}
{"actor":{"display_login":"devos50"}}