{"action":"created","comment":{"url":"https://api.github.com/repos/Tribler/tribler/pulls/comments/59863076","id":59863076,"diff_hunk":"@@ -198,309 +185,384 @@ def __init__(self, tracker_url, tracker_address, announce_page, on_result_callba\n         self._content_encoding = None\n         self._content_length = None\n         self._received_length = None\n+        self.result_deferred = None\n+        self.request = None\n \n     def max_retries(self):\n+        \"\"\"\n+        Returns the max amount of retries allowed for this session.\n+        :return: The maximum amount of retries.\n+        \"\"\"\n         return HTTP_TRACKER_MAX_RETRIES\n \n     def retry_interval(self):\n+        \"\"\"\n+        Returns the interval one has to wait before retrying to connect.\n+        :return: The interval before retrying.\n+        \"\"\"\n         return HTTP_TRACKER_RECHECK_INTERVAL\n \n     def create_connection(self):\n-        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n-        self._socket.setblocking(0)\n-\n-        return self.recreate_connection()\n-\n-    def recreate_connection(self):\n-        super(HttpTrackerSession, self).recreate_connection()\n-\n-        # an exception may be raised if the socket is non-blocking\n-        try:\n-            self._socket.connect(self._tracker_address)\n-        except Exception as e:\n-            # Error 115 means the operation is in progress.\n-            # Error 10035 is WSAEWOULDBLOCK on Windows.\n-            if e[0] not in (115, 10035):\n-                self._logger.debug(u\"%s Failed to connect to HTTP tracker [%s, %s]: %s\",\n-                                   self, self._tracker_url, self._tracker_address, e)\n-                self._is_failed = True\n-                return False\n-\n+        super(HttpTrackerSession, self).create_connection()\n         self._action = TRACKER_ACTION_CONNECT\n-        self._last_contact = int(time.time())\n         return True\n \n-    def _handle_connection(self):\n+    def connect_to_tracker(self):\n         # create the HTTP GET message\n         # Note: some trackers have strange URLs, e.g.,\n         #       http://moviezone.ws/announce.php?passkey=8ae51c4b47d3e7d0774a720fa511cc2a\n-        #       which has some sort of 'key' as parameter, so we need to check\n-        #       if there is already a parameter available\n-        message = 'GET '\n-        message += '/' + self._announce_page.replace(u'announce', u'scrape')\n-        if message.find('?') == -1:\n-            message += '?'\n-        else:\n-            message += '&'\n+        #       which has some sort of 'key' as parameter, so we need to use the add_url_params\n+        #       utility function to handle such cases.\n \n-        # append the infohashes as parameters\n-        for infohash in self._infohash_list:\n-            message += 'info_hash='\n-            message += urllib.quote(infohash)\n-            message += '&'\n-        message = message[:-1]  # remove the last AND '&'\n-        message += ' HTTP/1.1\\r\\n'\n-        message += '\\r\\n'\n-\n-        try:\n-            self._socket.sendall(message)\n-        except Exception as e:\n-            self._logger.info(u\"%s Failed to send HTTP SCRAPE message: %s\", self, e)\n-            self._is_failed = True\n-            return\n+        url = add_url_params(\"http://%s:%s/%s\" % (self._tracker_address[0], self._tracker_address[1], self._announce_page.replace(u'announce', u'scrape')),\n+                                                  {\"info_hash\" : self._infohash_list})\n \n-        self._logger.debug(u\"%s HTTP SCRAPE message sent\", self)\n+        agent = RedirectAgent(Agent(reactor, connectTimeout=15.0))\n+        self.request = agent.request('GET', bytes(url))\n+        self.request.addCallback(self.cbresponse)\n+        self.request.addErrback(self.on_error)\n+\n+        self._logger.debug(u\"%s HTTP SCRAPE message sent: %s\", self, url)\n \n         # no more requests can be appended to this session\n         self._action = TRACKER_ACTION_SCRAPE\n         self._is_initiated = True\n+        self._last_contact = int(time.time())\n \n-    def _handle_response(self):\n-        try:\n-            # TODO: this buffer size may be changed\n-            response = self._socket.recv(8192)\n-        except Exception as e:\n-            self._logger.info(u\"%s Failed to receive HTTP SCRAPE response: %s\", self, e)\n-            self._is_failed = True\n-            return\n+        # Return deferred that will evaluate when the whole chain is done.\n+        self.result_deferred = Deferred()\n+        return self.result_deferred\n \n-        self._logger.debug(u\"%s Got response\", self)\n+    def on_error(self, failure):\n+        self._logger.info(\"Error when querying http tracker: %s %s\" % (str(failure), self.tracker_url))\n+        self.failed()\n \n-        if not response:\n-            self._is_failed = True\n+    def cbresponse(self, response):\n+        # Check if this one was OK.\n+        if response.code != 200:\n+            # error response code\n+            self._logger.warning(u\"%s HTTP SCRAPE error response code [%s, %s]\", self, response.code, response.phrase)\n+            self.failed()\n             return\n \n-        # for the header message, we need to parse the content length in case\n-        # if the HTTP packets are partial.\n-        if not self._message_buffer:\n-            # append the header part\n-            if not self._header_buffer:\n-                self._header_buffer = response\n-            else:\n-                self._header_buffer += response\n-\n-            # check if the header part is over\n-            if self._header_buffer.find('\\r\\n\\r\\n') != -1:\n-                self._header_buffer, self._message_buffer = self._header_buffer.split('\\r\\n\\r\\n', 1)\n+        # All ok, parse the body\n+        d = readBody(response)\n+        d.addCallbacks(self._process_scrape_response, self.on_error)\n \n-                self._received_length = len(self._message_buffer)\n-                self._process_header()\n+    def failed(self):\n+        \"\"\"\n+        This method handles everything that needs to be done when one step\n+        in the session has failed and thus no data can be obtained.\n+        \"\"\"\n+        self._is_failed = True\n+        if self.result_deferred:\n+            self.result_deferred.errback(ValueError(\"HTTP tracker has failed for url %s\" % self._tracker_url))\n+        self.cleanup()\n \n-        # the remaining part\n-        else:\n-            self._message_buffer += response\n-            self._received_length += len(response)\n-\n-        # check the read count\n-        if self._received_length >= self._content_length:\n-            # process the retrieved information\n-            success = self._process_scrape_response()\n-            if success:\n-                self._is_finished = True\n-            else:\n-                self._is_failed = True\n-\n-        # wait for more\n-        else:\n-            pass\n-\n-    def _process_header(self):\n-        # get and check HTTP response code\n-        protocol, code, msg = self._header_buffer.split(' ', 2)\n-        if code == '301' or code == '302':\n-            idx = self._header_buffer.find('Location: ')\n-            if idx == -1:\n-                self._is_failed = True\n-            else:\n-                new_location = (self._header_buffer[idx:].split('\\r\\n')[0]).split(' ')[1]\n-                try:\n-                    idx = new_location.find('info_hash=')\n-                    if idx != -1:\n-                        new_location = new_location[:idx]\n-                    if new_location[-1] != '/':\n-                        new_location += \"/\"\n-                    new_location += \"announce\"\n-\n-                    tracker_type, tracker_address, announce_page = parse_tracker_url(new_location)\n-                    if tracker_type != self._tracker_type:\n-                        raise RuntimeError(u\"cannot redirect to a different tracker type: %s\", new_location)\n-\n-                    else:\n-                        self._logger.debug(u\"%s being redirected to %s\", self, new_location)\n-\n-                        self._tracker_address = tracker_address\n-                        self._announce_page = announce_page\n-                        self._socket.close()\n-                        self._socket = None\n-\n-                        self.recreate_connection()\n-\n-                except RuntimeError as run_err:\n-                    self._logger.info(u\"%s: Runtime Error: %s, address: %s, announce: %s\",\n-                                      self, run_err, self._tracker_address, self._announce_page)\n-                    self._is_failed = True\n-\n-                except Exception as err:\n-                    self._logger.exception(u\"Failed to process HTTP tracker header: [%s], Tracker: %s,\"\n-                                           u\" Tracker Address: %s, Tracker Announce: %s\",\n-                                           err, self._tracker_url, self._tracker_address, self._announce_page)\n-                    self._logger.debug(u\"Header: %s\", self._header_buffer)\n-                    self._is_failed = True\n+    def _process_scrape_response(self, body):\n+        \"\"\"\n+        This function handles the response body of a HTTP tracker,\n+        parsing the results.\n+        \"\"\"\n+        # parse the retrieved results\n+        if body is None:\n+            self.failed()","path":"Tribler/Core/TorrentChecker/session.py","position":333,"original_position":311,"commit_id":"c3cc4e7d82a2f8ecb12db8751b26f1891f19c8b8","original_commit_id":"ec6297425dc0324e5ed3068400339c93a7bc0b73","user":{"login":"lfdversluis","id":3618917,"avatar_url":"https://avatars.githubusercontent.com/u/3618917?v=3","gravatar_id":"","url":"https://api.github.com/users/lfdversluis","html_url":"https://github.com/lfdversluis","followers_url":"https://api.github.com/users/lfdversluis/followers","following_url":"https://api.github.com/users/lfdversluis/following{/other_user}","gists_url":"https://api.github.com/users/lfdversluis/gists{/gist_id}","starred_url":"https://api.github.com/users/lfdversluis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lfdversluis/subscriptions","organizations_url":"https://api.github.com/users/lfdversluis/orgs","repos_url":"https://api.github.com/users/lfdversluis/repos","events_url":"https://api.github.com/users/lfdversluis/events{/privacy}","received_events_url":"https://api.github.com/users/lfdversluis/received_events","type":"User","site_admin":false},"body":"Sounds fine!","created_at":"2016-04-15T11:51:09Z","updated_at":"2016-04-15T11:51:09Z","html_url":"https://github.com/Tribler/tribler/pull/2038#discussion_r59863076","pull_request_url":"https://api.github.com/repos/Tribler/tribler/pulls/2038","_links":{"self":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/comments/59863076"},"html":{"href":"https://github.com/Tribler/tribler/pull/2038#discussion_r59863076"},"pull_request":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/2038"}}},"pull_request":{"url":"https://api.github.com/repos/Tribler/tribler/pulls/2038","id":62923759,"html_url":"https://github.com/Tribler/tribler/pull/2038","diff_url":"https://github.com/Tribler/tribler/pull/2038.diff","patch_url":"https://github.com/Tribler/tribler/pull/2038.patch","issue_url":"https://api.github.com/repos/Tribler/tribler/issues/2038","number":2038,"state":"open","locked":false,"title":"Refactored tracker health check code to become asynchronous. Adjusted a part of the tribler shutdown procedure as a result.","user":{"login":"lfdversluis","id":3618917,"avatar_url":"https://avatars.githubusercontent.com/u/3618917?v=3","gravatar_id":"","url":"https://api.github.com/users/lfdversluis","html_url":"https://github.com/lfdversluis","followers_url":"https://api.github.com/users/lfdversluis/followers","following_url":"https://api.github.com/users/lfdversluis/following{/other_user}","gists_url":"https://api.github.com/users/lfdversluis/gists{/gist_id}","starred_url":"https://api.github.com/users/lfdversluis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lfdversluis/subscriptions","organizations_url":"https://api.github.com/users/lfdversluis/orgs","repos_url":"https://api.github.com/users/lfdversluis/repos","events_url":"https://api.github.com/users/lfdversluis/events{/privacy}","received_events_url":"https://api.github.com/users/lfdversluis/received_events","type":"User","site_admin":false},"body":"More deletions than additions while keeping the same functionality (I hope), but much more clean!\r\n\r\n- HTTP tracker code now makes use of Twisted's RedirectAgent. This means it will automatically redirect when a 30x code is returned, is asynchronous and has a timeout implemented. \r\n- The UDP tracker is now asynchronous, has a timeout added and has some bug fixes.\r\n- Shutdown of a UDP connection is now asynchronous, this is propogated in Tribler where it preparations are made for other components to shutdown asynchronous as well by using Deferreds.\r\n\r\nFixes #2035, contributing to #1848\r\n\r\nAdds **27** tests\r\nImproves responsiveness a lot, watchdog was tripping with > 10 seconds timeout. Not anymore.\r\nFixed **2** bugs\r\nImproved redirection by making use of Twisted's redirectAgent\r\nAdded lots of documentation for other generations to read (enchancement)\r\nFixed **50+** pylint style errors\r\n","created_at":"2016-03-15T15:24:18Z","updated_at":"2016-04-15T11:51:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":"02358b06cd783679a3129bd1a8eea727e3a0e319","assignee":null,"milestone":{"url":"https://api.github.com/repos/Tribler/tribler/milestones/13","html_url":"https://github.com/Tribler/tribler/milestones/V6.6%20WX3","labels_url":"https://api.github.com/repos/Tribler/tribler/milestones/13/labels","id":851033,"number":13,"title":"V6.6 WX3","description":"","creator":{"login":"synctext","id":325224,"avatar_url":"https://avatars.githubusercontent.com/u/325224?v=3","gravatar_id":"","url":"https://api.github.com/users/synctext","html_url":"https://github.com/synctext","followers_url":"https://api.github.com/users/synctext/followers","following_url":"https://api.github.com/users/synctext/following{/other_user}","gists_url":"https://api.github.com/users/synctext/gists{/gist_id}","starred_url":"https://api.github.com/users/synctext/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/synctext/subscriptions","organizations_url":"https://api.github.com/users/synctext/orgs","repos_url":"https://api.github.com/users/synctext/repos","events_url":"https://api.github.com/users/synctext/events{/privacy}","received_events_url":"https://api.github.com/users/synctext/received_events","type":"User","site_admin":false},"open_issues":39,"closed_issues":56,"state":"open","created_at":"2014-11-03T11:16:42Z","updated_at":"2016-04-15T11:03:09Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/Tribler/tribler/pulls/2038/commits","review_comments_url":"https://api.github.com/repos/Tribler/tribler/pulls/2038/comments","review_comment_url":"https://api.github.com/repos/Tribler/tribler/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Tribler/tribler/issues/2038/comments","statuses_url":"https://api.github.com/repos/Tribler/tribler/statuses/c3cc4e7d82a2f8ecb12db8751b26f1891f19c8b8","head":{"label":"lfdversluis:httptracker-async","ref":"httptracker-async","sha":"c3cc4e7d82a2f8ecb12db8751b26f1891f19c8b8","user":{"login":"lfdversluis","id":3618917,"avatar_url":"https://avatars.githubusercontent.com/u/3618917?v=3","gravatar_id":"","url":"https://api.github.com/users/lfdversluis","html_url":"https://github.com/lfdversluis","followers_url":"https://api.github.com/users/lfdversluis/followers","following_url":"https://api.github.com/users/lfdversluis/following{/other_user}","gists_url":"https://api.github.com/users/lfdversluis/gists{/gist_id}","starred_url":"https://api.github.com/users/lfdversluis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lfdversluis/subscriptions","organizations_url":"https://api.github.com/users/lfdversluis/orgs","repos_url":"https://api.github.com/users/lfdversluis/repos","events_url":"https://api.github.com/users/lfdversluis/events{/privacy}","received_events_url":"https://api.github.com/users/lfdversluis/received_events","type":"User","site_admin":false},"repo":{"id":45919494,"name":"tribler","full_name":"lfdversluis/tribler","owner":{"login":"lfdversluis","id":3618917,"avatar_url":"https://avatars.githubusercontent.com/u/3618917?v=3","gravatar_id":"","url":"https://api.github.com/users/lfdversluis","html_url":"https://github.com/lfdversluis","followers_url":"https://api.github.com/users/lfdversluis/followers","following_url":"https://api.github.com/users/lfdversluis/following{/other_user}","gists_url":"https://api.github.com/users/lfdversluis/gists{/gist_id}","starred_url":"https://api.github.com/users/lfdversluis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lfdversluis/subscriptions","organizations_url":"https://api.github.com/users/lfdversluis/orgs","repos_url":"https://api.github.com/users/lfdversluis/repos","events_url":"https://api.github.com/users/lfdversluis/events{/privacy}","received_events_url":"https://api.github.com/users/lfdversluis/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/lfdversluis/tribler","description":"Privacy with BitTorrent and resilient to shut down","fork":true,"url":"https://api.github.com/repos/lfdversluis/tribler","forks_url":"https://api.github.com/repos/lfdversluis/tribler/forks","keys_url":"https://api.github.com/repos/lfdversluis/tribler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lfdversluis/tribler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lfdversluis/tribler/teams","hooks_url":"https://api.github.com/repos/lfdversluis/tribler/hooks","issue_events_url":"https://api.github.com/repos/lfdversluis/tribler/issues/events{/number}","events_url":"https://api.github.com/repos/lfdversluis/tribler/events","assignees_url":"https://api.github.com/repos/lfdversluis/tribler/assignees{/user}","branches_url":"https://api.github.com/repos/lfdversluis/tribler/branches{/branch}","tags_url":"https://api.github.com/repos/lfdversluis/tribler/tags","blobs_url":"https://api.github.com/repos/lfdversluis/tribler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lfdversluis/tribler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lfdversluis/tribler/git/refs{/sha}","trees_url":"https://api.github.com/repos/lfdversluis/tribler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lfdversluis/tribler/statuses/{sha}","languages_url":"https://api.github.com/repos/lfdversluis/tribler/languages","stargazers_url":"https://api.github.com/repos/lfdversluis/tribler/stargazers","contributors_url":"https://api.github.com/repos/lfdversluis/tribler/contributors","subscribers_url":"https://api.github.com/repos/lfdversluis/tribler/subscribers","subscription_url":"https://api.github.com/repos/lfdversluis/tribler/subscription","commits_url":"https://api.github.com/repos/lfdversluis/tribler/commits{/sha}","git_commits_url":"https://api.github.com/repos/lfdversluis/tribler/git/commits{/sha}","comments_url":"https://api.github.com/repos/lfdversluis/tribler/comments{/number}","issue_comment_url":"https://api.github.com/repos/lfdversluis/tribler/issues/comments{/number}","contents_url":"https://api.github.com/repos/lfdversluis/tribler/contents/{+path}","compare_url":"https://api.github.com/repos/lfdversluis/tribler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lfdversluis/tribler/merges","archive_url":"https://api.github.com/repos/lfdversluis/tribler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lfdversluis/tribler/downloads","issues_url":"https://api.github.com/repos/lfdversluis/tribler/issues{/number}","pulls_url":"https://api.github.com/repos/lfdversluis/tribler/pulls{/number}","milestones_url":"https://api.github.com/repos/lfdversluis/tribler/milestones{/number}","notifications_url":"https://api.github.com/repos/lfdversluis/tribler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lfdversluis/tribler/labels{/name}","releases_url":"https://api.github.com/repos/lfdversluis/tribler/releases{/id}","deployments_url":"https://api.github.com/repos/lfdversluis/tribler/deployments","created_at":"2015-11-10T15:06:28Z","updated_at":"2016-01-11T13:41:55Z","pushed_at":"2016-04-15T11:49:24Z","git_url":"git://github.com/lfdversluis/tribler.git","ssh_url":"git@github.com:lfdversluis/tribler.git","clone_url":"https://github.com/lfdversluis/tribler.git","svn_url":"https://github.com/lfdversluis/tribler","homepage":"http://www.tribler.org","size":113039,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":3,"forks":0,"open_issues":3,"watchers":1,"default_branch":"devel"}},"base":{"label":"Tribler:devel","ref":"devel","sha":"7cda31f11fd181adc11485b19b81bb881927777b","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 with BitTorrent and resilient to shut down","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-04-13T09:56:08Z","pushed_at":"2016-04-15T11:49:25Z","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":126101,"stargazers_count":1853,"watchers_count":1853,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":197,"mirror_url":null,"open_issues_count":245,"forks":197,"open_issues":245,"watchers":1853,"default_branch":"devel"}},"_links":{"self":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/2038"},"html":{"href":"https://github.com/Tribler/tribler/pull/2038"},"issue":{"href":"https://api.github.com/repos/Tribler/tribler/issues/2038"},"comments":{"href":"https://api.github.com/repos/Tribler/tribler/issues/2038/comments"},"review_comments":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/2038/comments"},"review_comment":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Tribler/tribler/pulls/2038/commits"},"statuses":{"href":"https://api.github.com/repos/Tribler/tribler/statuses/c3cc4e7d82a2f8ecb12db8751b26f1891f19c8b8"}}}}