{"action":"created","comment":{"url":"https://api.github.com/repos/Tribler/tribler/pulls/comments/67495771","id":67495771,"diff_hunk":"@@ -0,0 +1,505 @@\n+# coding=utf-8\n+\"\"\"\n+Written by Egbert Bouman, Mihai Capotă, Elric Milon, and Ardhi Putra Pratama H\n+Supported boosting sources\n+\"\"\"\n+import glob\n+import logging\n+import os\n+import re\n+import urllib\n+from binascii import hexlify\n+from hashlib import sha1\n+\n+import libtorrent as lt\n+from twisted.internet import defer\n+from twisted.internet import reactor\n+from twisted.internet.defer import CancelledError\n+from twisted.internet.task import LoopingCall\n+from twisted.web.client import Agent, readBody\n+from twisted.web.http_headers import Headers\n+\n+from Tribler.Core.TorrentDef import TorrentDef\n+from Tribler.Core.exceptions import TriblerException\n+from Tribler.Core.simpledefs import NTFY_INSERT, NTFY_TORRENTS, NTFY_UPDATE\n+from Tribler.Core.version import version_id\n+from Tribler.Main.Utility.GuiDBTuples import Torrent, Channel\n+from Tribler.Policies.credit_mining_util import TorrentManagerCM, ent2chr\n+from Tribler.community.allchannel.community import AllChannelCommunity\n+from Tribler.community.channel.community import ChannelCommunity\n+from Tribler.dispersy.exception import CommunityNotFoundException\n+from Tribler.dispersy.taskmanager import TaskManager\n+from Tribler.dispersy.util import blocking_call_on_reactor_thread\n+\n+\n+class BoostingSource(TaskManager):\n+    \"\"\"\n+    Base class for boosting source. For now, it can be RSS, directory, and channel\n+    \"\"\"\n+\n+    def __init__(self, session, source, boost_settings, torrent_insert_cb):\n+        super(BoostingSource, self).__init__()\n+        self.session = session\n+        self.channelcast_db = session.lm.channelcast_db\n+\n+        self.torrents = {}\n+        self.source = source\n+        self.interval = boost_settings.source_interval\n+        self.max_torrents = boost_settings.max_torrents_per_source\n+        self.torrent_insert_callback = torrent_insert_cb\n+        self.archive = False\n+\n+        self.enabled = True\n+\n+        self.av_uprate = 0\n+        self.av_dwnrate = 0\n+        self.storage_used = 0\n+        self.ready = False\n+\n+        self.min_connection = boost_settings.min_connection_start\n+        self.min_channels = boost_settings.min_channels_start\n+\n+        self.torrent_mgr = TorrentManagerCM.get_instance(session)\n+\n+        self._logger = logging.getLogger(BoostingSource.__name__)\n+\n+        self.boosting_manager = self.session.lm.boosting_manager\n+\n+    def start(self):\n+        \"\"\"\n+        Start operating mining for this source\n+        \"\"\"\n+        d = self._load_if_ready(self.source)\n+        self.register_task(str(self.source) + \"_load\", d, value=self.source)\n+        self._logger.debug(\"Start mining on %s\", self.source)\n+\n+    def kill_tasks(self):\n+        \"\"\"\n+        kill tasks on this source\n+        \"\"\"\n+        self.cancel_all_pending_tasks()\n+        self.ready = False\n+        self.torrent_mgr.del_instance()\n+\n+    def _load_if_ready(self, source):\n+        \"\"\"\n+        load source if and only if the overall system is ready.\n+\n+        This is useful so we don't burden the application during the startup\n+        \"\"\"\n+        def check_system(defer_param=None):\n+            \"\"\"\n+            function that check the system whether it's ready or not\n+\n+            it depends on #connection and #channel\n+            \"\"\"\n+            if defer_param is None:\n+                defer_param = defer.Deferred()\n+\n+            nr_channels = self.channelcast_db.getNrChannels()\n+            nr_connections = 0\n+\n+            for community in self.session.lm.dispersy.get_communities():\n+                from Tribler.community.search.community import SearchCommunity\n+                if isinstance(community, SearchCommunity):\n+                    nr_connections = community.get_nr_connections()\n+\n+            if nr_channels > self.min_channels and nr_connections > self.min_connection:\n+                defer_param.callback(source)\n+            else:\n+                self.register_task(str(self.source)+\"_check_sys\",\n+                                                    reactor.callLater(10, check_system, defer_param))\n+\n+            return defer_param\n+\n+        defer_check = check_system()\n+        defer_check.addCallbacks(self._load, self._on_err)\n+        return defer_check\n+\n+    def _load(self, source):\n+        pass\n+\n+    def _update(self):\n+        pass\n+\n+    def get_source_text(self):\n+        \"\"\"\n+        returning 'raw' source. May be overriden\n+        \"\"\"\n+        return self.source\n+\n+    def _on_err(self, err_msg):\n+        self._logger.error(err_msg)\n+\n+    def check_and_register_task(self, name, task, delay=None, value=None, interval=None):\n+        \"\"\"\n+        Helper function to avoid assertion in register task.\n+\n+        It will register task if it has not already registered\n+        \"\"\"\n+        task_ret = None\n+        if not self.is_pending_task_active(name):\n+            task_ret = self.register_task(name, task, delay, value, interval)\n+\n+        return task_ret\n+\n+class ChannelSource(BoostingSource):\n+    \"\"\"\n+    Credit mining source from a channel.\n+    \"\"\"\n+    def __init__(self, session, dispersy_cid, boost_settings, torrent_insert_cb):\n+        BoostingSource.__init__(self, session, dispersy_cid, boost_settings, torrent_insert_cb)\n+\n+        self.channel_id = None\n+\n+        self.channel = None\n+        self.community = None\n+        self.database_updated = True\n+\n+        self.check_torrent_interval = 10\n+        self.dispersy_cid = dispersy_cid\n+\n+        self.session.add_observer(self._on_database_updated, NTFY_TORRENTS, [NTFY_INSERT, NTFY_UPDATE])\n+\n+        self.unavail_torrent = {}\n+\n+    def kill_tasks(self):\n+        BoostingSource.kill_tasks(self)\n+\n+        self.session.remove_observer(self._on_database_updated)\n+\n+    def _load(self, dispersy_cid):\n+        dispersy = self.session.get_dispersy_instance()\n+\n+        def join_community():\n+            \"\"\"\n+            find the community/channel id, then join\n+            \"\"\"\n+            try:\n+                self.community = dispersy.get_community(dispersy_cid, True)\n+                self.register_task(str(self.source) + \"_get_id\",\n+                                                    reactor.callLater(1, get_channel_id))\n+\n+            except CommunityNotFoundException:\n+\n+                allchannelcommunity = None\n+                for community in dispersy.get_communities():\n+                    if isinstance(community, AllChannelCommunity):\n+                        allchannelcommunity = community\n+                        break\n+\n+                if allchannelcommunity:\n+                    self.community = ChannelCommunity.init_community(dispersy, dispersy.get_member(mid=dispersy_cid),\n+                                                                     allchannelcommunity._my_member, self.session)\n+                    self._logger.info(\"Joined channel community %s\", dispersy_cid.encode(\"HEX\"))\n+                    self.register_task(str(self.source) + \"_get_id\",\n+                                                        reactor.callLater(1, get_channel_id))\n+                else:\n+                    self._logger.error(\"Could not find AllChannelCommunity\")\n+\n+        def get_channel_id():\n+            \"\"\"\n+            find channel id by looking at the network\n+            \"\"\"\n+            if self.community and self.community._channel_id:\n+                self.channel_id = self.community._channel_id\n+\n+                channel_dict = self.channelcast_db.getChannel(self.channel_id)\n+                self.channel = Channel(*channel_dict)\n+\n+                task_call = self.check_and_register_task(str(self.source) + \"_update\",\n+                                                         LoopingCall(self._update)).start(self.interval, now=True)\n+                if task_call:\n+                    self._logger.debug(\"Registering update call\")\n+\n+                self._logger.info(\"Got channel id %s\", self.channel_id)\n+\n+                self.ready = True\n+            else:\n+                self._logger.warning(\"Could not get channel id, retrying in 10 s\")\n+                self.register_task(str(self.source) + \"_get_id\",\n+                                                    reactor.callLater(10, get_channel_id))\n+\n+        self.register_task(str(self.source) + \"_join_comm\",\n+                                            reactor.callLater(1, join_community))\n+\n+    def _check_tor(self):\n+        \"\"\"\n+        periodically check torrents in channel. Will return the torrent data if finished.\n+        \"\"\"\n+        def showtorrent(torrent):\n+            \"\"\"\n+            assembly torrent data, call the callback\n+            \"\"\"\n+            if torrent.files:\n+                if len(self.torrents) >= self.max_torrents:\n+                    self._logger.debug(\"Max torrents in source reached. Not adding %s\", torrent.infohash)\n+                    del self.unavail_torrent[torrent.infohash]\n+                    return\n+\n+                infohash = torrent.infohash\n+                self._logger.debug(\"[ChannelSource] Got torrent %s\", hexlify(infohash))\n+                self.torrents[infohash] = {}\n+                self.torrents[infohash]['name'] = torrent.name\n+                self.torrents[infohash]['metainfo'] = torrent.tdef\n+                self.torrents[infohash]['creation_date'] = torrent.creation_date\n+                self.torrents[infohash]['length'] = torrent.tdef.get_length()\n+                self.torrents[infohash]['num_files'] = len(torrent.files)\n+                self.torrents[infohash]['num_seeders'] = torrent.swarminfo[0] or 0\n+                self.torrents[infohash]['num_leechers'] = torrent.swarminfo[1] or 0\n+                self.torrents[infohash]['enabled'] = self.enabled\n+\n+                # seeding stats from DownloadState\n+                self.torrents[infohash]['last_seeding_stats'] = {}\n+\n+                del self.unavail_torrent[infohash]\n+\n+                if self.torrent_insert_callback:\n+                    self.torrent_insert_callback(self.source, infohash, self.torrents[infohash])\n+                self.database_updated = False\n+\n+        self._logger.debug(\"Unavailable #torrents : %d from %s\", len(self.unavail_torrent), hexlify(self.source))\n+\n+        if len(self.unavail_torrent) and self.enabled:\n+            for torrent in self.unavail_torrent.values():\n+                self.torrent_mgr.load_torrent(torrent, showtorrent)\n+\n+    def _update(self):\n+        if len(self.torrents) < self.max_torrents and self.database_updated:\n+            CHANTOR_DB = ['ChannelTorrents.channel_id', 'Torrent.torrent_id', 'infohash', '\"\"', 'length',\n+                          'category', 'status', 'num_seeders', 'num_leechers', 'ChannelTorrents.id',\n+                          'ChannelTorrents.dispersy_id', 'ChannelTorrents.name', 'Torrent.name',\n+                          'ChannelTorrents.description', 'ChannelTorrents.time_stamp', 'ChannelTorrents.inserted']\n+\n+            torrent_values = self.channelcast_db.getTorrentsFromChannelId(self.channel_id, True, CHANTOR_DB,\n+                                                                          self.max_torrents)\n+\n+            listtor = self.torrent_mgr.create_torrents(torrent_values, True,\n+                                                       {self.channel_id: self.channelcast_db.getChannel(\n+                                                           self.channel_id)})\n+\n+            # dict {key_infohash(binary):Torrent(object-GUIDBTuple)}\n+            self.unavail_torrent.update({t.infohash: t for t in listtor if t.infohash not in self.torrents})\n+\n+            # it's highly probable the checktor function is running at this time (if it's already running)\n+            # if not running, start the checker\n+\n+            task_call = self.check_and_register_task(hexlify(self.source) + \"_checktor\", LoopingCall(self._check_tor))\n+            if task_call:\n+                self._logger.debug(\"Registering check torrent function\")\n+                task_call.start(self.check_torrent_interval, now=True)\n+\n+    def _on_database_updated(self, dummy_subject, dummy_change_type, dummy_infohash):\n+        self.database_updated = True\n+\n+    def get_source_text(self):\n+        return self.channel.name if self.channel else None\n+\n+\n+class RSSFeedSource(BoostingSource):\n+    \"\"\"\n+    Credit mining source from a RSS feed.\n+\n+    # supported list (tested) :\n+    # http://bt.etree.org/rss/bt_etree_org.rdf\n+    # http://www.mininova.org/rss.xml\n+    # https://kat.cr (via torcache)\n+\n+    # not supported (till now)\n+    # https://eztv.ag/ezrss.xml (https link)\n+    \"\"\"\n+\n+    def __init__(self, session, rss_feed, boost_settings, torrent_insert_cb):\n+        BoostingSource.__init__(self, session, rss_feed, boost_settings, torrent_insert_cb)\n+\n+        self.feed_handle = None\n+\n+        self.torrent_store = self.session.lm.torrent_store\n+        # keys : fake infohash, value : real infohash. Type : (length 20 string, binary)\n+        self.fake_infohash_id = {}\n+\n+        self.title = \"\"\n+        self.description = \"\"\n+        self.total_torrents = 0\n+\n+        self.torrent_db = self.session.open_dbhandler(NTFY_TORRENTS)\n+\n+    def _load(self, rss_feed):\n+        self.feed_handle = self.session.lm.ltmgr.get_session().add_feed(\n+            {'url': rss_feed, 'auto_download': False, 'auto_map_handles': False})\n+\n+        def wait_for_feed(d=None):\n+            \"\"\"\n+            Wait until the RSS feed is no longer updating.","path":"Tribler/Policies/BoostingSource.py","position":327,"original_position":333,"commit_id":"82569284441af83aa72aed927fdd7e0db8c6c8b0","original_commit_id":"ad489ca074ce5b83c916f2b95d4d64882e2460af","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":"Digging through it, it seems RSS function was removed/deprecated in libtorrent. I'm think to use custom feed reader/parser. Is there something like that in Tribler? Is it related to `ChannelRssParser`?","created_at":"2016-06-17T11:20:53Z","updated_at":"2016-06-17T11:20:53Z","html_url":"https://github.com/Tribler/tribler/pull/2064#discussion_r67495771","pull_request_url":"https://api.github.com/repos/Tribler/tribler/pulls/2064","_links":{"self":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/comments/67495771"},"html":{"href":"https://github.com/Tribler/tribler/pull/2064#discussion_r67495771"},"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-06-17T11:20:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"424b0a255125e3fb2bb7c08bcc667c28d83e2d53","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/82569284441af83aa72aed927fdd7e0db8c6c8b0","head":{"label":"ardhipoetra:credit_mining","ref":"credit_mining","sha":"82569284441af83aa72aed927fdd7e0db8c6c8b0","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-06-17T10:56:32Z","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":114749,"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":"6fe89f7779c355901299d3eacd2f8f074b077f95","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-06-16T13:46:53Z","pushed_at":"2016-06-17T11:10:49Z","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":133083,"stargazers_count":1885,"watchers_count":1885,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":202,"mirror_url":null,"open_issues_count":297,"forks":202,"open_issues":297,"watchers":1885,"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/82569284441af83aa72aed927fdd7e0db8c6c8b0"}}}}
{"actor":{"display_login":"ardhipoetra"}}