Skip to content

Commit d83d5b9

Browse files
committed
Pagination Support (Now you can fetch all the things!)
1 parent 2f9a4fd commit d83d5b9

File tree

4 files changed

+99
-17
lines changed

4 files changed

+99
-17
lines changed

lib/src/client/github.dart

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,20 @@ class GitHub {
7777
/**
7878
* Fetches the repositories of the user specified by [user].
7979
*/
80-
Future<List<Repository>> userRepositories(String user, {String type: "owner", int limit: 5000, String sort: "full_name", String direction: "asc"}) {
80+
Future<List<Repository>> userRepositories(String user, {String type: "owner", int limit, String sort: "full_name", String direction: "asc"}) {
8181
var params = {
82-
"per_page": limit.toString(),
8382
"sort": sort,
8483
"direction": direction
8584
};
86-
return getJSON("/users/${user}/repos", params: params).then((List json) {
87-
return new List.from(json.map((it) => Repository.fromJSON(this, it)));
85+
86+
var pages = limit != null ? (limit / 30).ceil() : null;
87+
88+
return new PaginationHelper(this).fetch("GET", "/users/${user}/repos", pages: pages, params: params).then((List<http.Response> responses) {
89+
var list = <dynamic>[];
90+
for (var response in responses) {
91+
list.addAll(JSON.decode(response.body));
92+
}
93+
return new List.from(list.map((it) => Repository.fromJSON(this, it)));
8894
});
8995
}
9096

@@ -115,11 +121,10 @@ class GitHub {
115121
*
116122
* [name] is the organization name.
117123
* [limit] is the maximum number of teams to provide.
118-
* Currently the highest you can go is 30.
119124
*/
120125
Future<List<Team>> teams(String name, [int limit]) {
121126
var group = new FutureGroup<Team>();
122-
getJSON("/orgs/${name}/teams").then((teams) {
127+
getJSON("/orgs/${name}/teams?per_page=${limit}").then((teams) {
123128
for (var team in teams) {
124129
group.add(getJSON(team['url'], convert: Team.fromJSON, statusCode: 200, fail: (http.Response response) {
125130
if (response.statusCode == 404) {
@@ -178,12 +183,17 @@ class GitHub {
178183
* Gets a Repositories Releases.
179184
*
180185
* [slug] is the repository to fetch releases from.
181-
* [limit] is the maximum number of pages.
182-
* Currently the maximum limit is 100.
186+
* [limit] is the maximum number of releases to show.
183187
*/
184-
Future<List<Release>> releases(RepositorySlug slug, [int limit = 30]) {
185-
return getJSON("/repos/${slug.fullName}/releases", params: { "per_page": limit }).then((releases) {
186-
return copyOf(releases.map((it) => Release.fromJSON(this, it)));
188+
Future<List<Release>> releases(RepositorySlug slug, {int limit}) {
189+
var pages = limit != null ? (limit / 30).ceil() : null;
190+
191+
return new PaginationHelper(this).fetch("GET", "/repos/${slug.fullName}/releases", pages: pages, params: {}).then((List<http.Response> responses) {
192+
var list = <dynamic>[];
193+
for (var response in responses) {
194+
list.addAll(JSON.decode(response.body));
195+
}
196+
return new List.from(list.map((it) => Repository.fromJSON(this, it)));
187197
});
188198
}
189199

@@ -360,3 +370,52 @@ class GitHub {
360370
}
361371
}
362372
}
373+
374+
class PaginationHelper {
375+
final GitHub github;
376+
final List<http.Response> responses;
377+
final Completer<List<http.Response>> completer;
378+
379+
PaginationHelper(this.github) : responses = [], completer = new Completer<List<http.Response>>();
380+
381+
Future<List<http.Response>> fetch(String method, String path, {int pages, Map<String, String> headers, Map<String, dynamic> params, String body}) {
382+
Future<http.Response> actualFetch(String realPath) {
383+
return github.request(method, realPath, headers: headers, params: params, body: body);
384+
}
385+
386+
void done() => completer.complete(responses);
387+
388+
var count = 0;
389+
390+
var handleResponse;
391+
handleResponse = (http.Response response) {
392+
count++;
393+
responses.add(response);
394+
395+
if (!response.headers.containsKey("link")) {
396+
done();
397+
return;
398+
}
399+
400+
var info = parseLinkHeader(response.headers['link']);
401+
402+
if (!info.containsKey("next")) {
403+
done();
404+
return;
405+
}
406+
407+
if (pages != null && count == pages) {
408+
done();
409+
return;
410+
}
411+
412+
var nextUrl = info['next'];
413+
414+
actualFetch(nextUrl).then(handleResponse);
415+
};
416+
417+
actualFetch(path).then(handleResponse);
418+
419+
return completer.future;
420+
}
421+
}

lib/src/client/repo.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,13 @@ class Repository {
9898

9999
RepositorySlug get slug => new RepositorySlug(owner.login, name);
100100

101-
Future<List<Issue>> issues([int limit = 30]) => github.getJSON("/repos/${fullName}/issues", params: { "per_page": limit }).then((json) {
101+
Future<List<Issue>> issues({int limit: 30}) => github.getJSON("/repos/${fullName}/issues", params: { "per_page": limit }).then((json) {
102102
return json.map((it) => Issue.fromJSON(github, it));
103103
});
104104

105105
Future<List<Commit>> get commits => github.getJSON("/repos/${fullName}/commits", convert: (github, it) => it.map((i) => Commit.fromJSON(github, i)));
106106

107-
Future<List<ContributorStatistics>> contributorStatistics([int limit = 30]) {
107+
Future<List<ContributorStatistics>> contributorStatistics({int limit: 30}) {
108108
var completer = new Completer<List<ContributorStatistics>>();
109109
var path = "/repos/${fullName}/stats/contributors";
110110
var handle;
@@ -122,13 +122,13 @@ class Repository {
122122
return completer.future;
123123
}
124124

125-
Future<List<Repository>> forks([int limit = 30]) {
125+
Future<List<Repository>> forks({int limit: 30}) {
126126
return github.getJSON("/repos/${fullName}/forks", params: { "per_page": limit }).then((forks) {
127127
return copyOf(forks.map((it) => Repository.fromJSON(github, it)));
128128
});
129129
}
130130

131-
Future<List<PullRequest>> pullRequests([int limit = 30]) {
131+
Future<List<PullRequest>> pullRequests({int limit: 30}) {
132132
return github.getJSON("/repos/${fullName}/pulls", params: { "per_page": limit }).then((List<Map> pulls) {
133133
return copyOf(pulls.map((it) => PullRequest.fromJSON(github, it)));
134134
});
@@ -138,13 +138,13 @@ class Repository {
138138
return github.getJSON("/repos/${fullName}/pages", convert: RepositoryPages.fromJSON);
139139
}
140140

141-
Future<List<Hook>> hooks([int limit = 30]) {
141+
Future<List<Hook>> hooks({int limit: 30}) {
142142
return github.getJSON("/repos/${fullName}/hooks", params: { "per_page": limit }).then((hooks) {
143143
return copyOf(hooks.map((it) => Hook.fromJSON(github, fullName, it)));
144144
});
145145
}
146146

147-
Future<List<Release>> releases([int limit = 30]) => github.releases(slug, limit);
147+
Future<List<Release>> releases({int limit}) => github.releases(slug, limit: limit);
148148

149149
Future<Release> release(int id) => github.release(slug, id);
150150

lib/src/client/util.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@ void putValue(String name, dynamic value, Map map) {
8282
}
8383
}
8484

85+
Map<String, String> parseLinkHeader(String input) {
86+
var out = {};
87+
var parts = input.split(", ");
88+
for (var part in parts) {
89+
if (part[0] != "<") {
90+
throw new FormatException("Invalid Link Header");
91+
}
92+
var kv = part.split("; ");
93+
var url = kv[0].substring(1);
94+
url = url.substring(0, url.length - 1);
95+
var key = kv[1];
96+
key = key.replaceAll('"', "").substring(4);
97+
out[key] = url;
98+
}
99+
return out;
100+
}
101+
85102
String fullNameFromRepoApiUrl(String url) {
86103
var split = url.split("/");
87104
return split[4] + "/" + split[5];

test/link_header.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import 'package:github/src/client/util.dart';
2+
3+
void main() {
4+
var it = parseLinkHeader('<https://api.github.com/user/1342004/repos?sort=stars&page=2>; rel="next", <https://api.github.com/user/1342004/repos?sort=stars&page=7>; rel="last"');
5+
print(it);
6+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy