mirror of
https://github.com/jlengrand/github-api.git
synced 2026-03-13 08:21:20 +00:00
Compare commits
17 Commits
github-api
...
github-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
277ccb5188 | ||
|
|
73119afeff | ||
|
|
8939179be8 | ||
|
|
adba2e68db | ||
|
|
0ef8b471a3 | ||
|
|
205950fc5f | ||
|
|
8835b2c745 | ||
|
|
74fda40764 | ||
|
|
687a36937e | ||
|
|
2c7b8bd6e8 | ||
|
|
e9417f5fa1 | ||
|
|
5e08b34c43 | ||
|
|
7b436ffb3b | ||
|
|
1ee2ec3728 | ||
|
|
ed28768146 | ||
|
|
f931835176 | ||
|
|
0cf9bc2814 |
4
pom.xml
4
pom.xml
@@ -7,7 +7,7 @@
|
|||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>github-api</artifactId>
|
<artifactId>github-api</artifactId>
|
||||||
<version>1.64-SNAPSHOT</version>
|
<version>1.66</version>
|
||||||
<name>GitHub API for Java</name>
|
<name>GitHub API for Java</name>
|
||||||
<url>http://github-api.kohsuke.org/</url>
|
<url>http://github-api.kohsuke.org/</url>
|
||||||
<description>GitHub API for Java</description>
|
<description>GitHub API for Java</description>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<connection>scm:git:git@github.com/kohsuke/${project.artifactId}.git</connection>
|
<connection>scm:git:git@github.com/kohsuke/${project.artifactId}.git</connection>
|
||||||
<developerConnection>scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git</developerConnection>
|
<developerConnection>scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git</developerConnection>
|
||||||
<url>http://${project.artifactId}.kohsuke.org/</url>
|
<url>http://${project.artifactId}.kohsuke.org/</url>
|
||||||
<tag>HEAD</tag>
|
<tag>github-api-1.66</tag>
|
||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<distributionManagement>
|
<distributionManagement>
|
||||||
|
|||||||
@@ -16,7 +16,13 @@ import javax.xml.bind.DatatypeConverter;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings({"UnusedDeclaration"})
|
@SuppressWarnings({"UnusedDeclaration"})
|
||||||
public class GHContent {
|
public class GHContent {
|
||||||
private GHRepository owner;
|
/*
|
||||||
|
In normal use of this class, repository field is set via wrap(),
|
||||||
|
but in the code search API, there's a nested 'repository' field that gets populated from JSON.
|
||||||
|
*/
|
||||||
|
private GHRepository repository;
|
||||||
|
|
||||||
|
private GitHub root;
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
private String encoding;
|
private String encoding;
|
||||||
@@ -31,7 +37,7 @@ public class GHContent {
|
|||||||
private String download_url;
|
private String download_url;
|
||||||
|
|
||||||
public GHRepository getOwner() {
|
public GHRepository getOwner() {
|
||||||
return owner;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
@@ -107,13 +113,16 @@ public class GHContent {
|
|||||||
* Retrieves the actual content stored here.
|
* Retrieves the actual content stored here.
|
||||||
*/
|
*/
|
||||||
public InputStream read() throws IOException {
|
public InputStream read() throws IOException {
|
||||||
return new Requester(owner.root).read(getDownloadUrl());
|
return new Requester(root).asStream(getDownloadUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL to retrieve the raw content of the file. Null if this is a directory.
|
* URL to retrieve the raw content of the file. Null if this is a directory.
|
||||||
*/
|
*/
|
||||||
public String getDownloadUrl() { return download_url; }
|
public String getDownloadUrl() throws IOException {
|
||||||
|
populate();
|
||||||
|
return download_url;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFile() {
|
public boolean isFile() {
|
||||||
return "file".equals(type);
|
return "file".equals(type);
|
||||||
@@ -123,6 +132,16 @@ public class GHContent {
|
|||||||
return "dir".equals(type);
|
return "dir".equals(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully populate the data by retrieving missing data.
|
||||||
|
*
|
||||||
|
* Depending on the original API call where this object is created, it may not contain everything.
|
||||||
|
*/
|
||||||
|
protected synchronized void populate() throws IOException {
|
||||||
|
if (download_url!=null) return; // already populated
|
||||||
|
root.retrieve().to(url, this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List immediate children of this directory.
|
* List immediate children of this directory.
|
||||||
*/
|
*/
|
||||||
@@ -132,10 +151,10 @@ public class GHContent {
|
|||||||
|
|
||||||
return new PagedIterable<GHContent>() {
|
return new PagedIterable<GHContent>() {
|
||||||
public PagedIterator<GHContent> iterator() {
|
public PagedIterator<GHContent> iterator() {
|
||||||
return new PagedIterator<GHContent>(owner.root.retrieve().asIterator(url, GHContent[].class)) {
|
return new PagedIterator<GHContent>(root.retrieve().asIterator(url, GHContent[].class)) {
|
||||||
@Override
|
@Override
|
||||||
protected void wrapUp(GHContent[] page) {
|
protected void wrapUp(GHContent[] page) {
|
||||||
GHContent.wrap(page,owner);
|
GHContent.wrap(page, repository);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -157,7 +176,7 @@ public class GHContent {
|
|||||||
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage, String branch) throws IOException {
|
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage, String branch) throws IOException {
|
||||||
String encodedContent = DatatypeConverter.printBase64Binary(newContentBytes);
|
String encodedContent = DatatypeConverter.printBase64Binary(newContentBytes);
|
||||||
|
|
||||||
Requester requester = new Requester(owner.root)
|
Requester requester = new Requester(root)
|
||||||
.with("path", path)
|
.with("path", path)
|
||||||
.with("message", commitMessage)
|
.with("message", commitMessage)
|
||||||
.with("sha", sha)
|
.with("sha", sha)
|
||||||
@@ -170,8 +189,8 @@ public class GHContent {
|
|||||||
|
|
||||||
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
|
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
|
||||||
|
|
||||||
response.getContent().wrap(owner);
|
response.getContent().wrap(repository);
|
||||||
response.getCommit().wrapUp(owner);
|
response.getCommit().wrapUp(repository);
|
||||||
|
|
||||||
this.content = encodedContent;
|
this.content = encodedContent;
|
||||||
return response;
|
return response;
|
||||||
@@ -182,7 +201,7 @@ public class GHContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GHContentUpdateResponse delete(String commitMessage, String branch) throws IOException {
|
public GHContentUpdateResponse delete(String commitMessage, String branch) throws IOException {
|
||||||
Requester requester = new Requester(owner.root)
|
Requester requester = new Requester(root)
|
||||||
.with("path", path)
|
.with("path", path)
|
||||||
.with("message", commitMessage)
|
.with("message", commitMessage)
|
||||||
.with("sha", sha)
|
.with("sha", sha)
|
||||||
@@ -194,18 +213,26 @@ public class GHContent {
|
|||||||
|
|
||||||
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
|
GHContentUpdateResponse response = requester.to(getApiRoute(), GHContentUpdateResponse.class);
|
||||||
|
|
||||||
response.getCommit().wrapUp(owner);
|
response.getCommit().wrapUp(repository);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getApiRoute() {
|
private String getApiRoute() {
|
||||||
return "/repos/" + owner.getOwnerName() + "/" + owner.getName() + "/contents/" + path;
|
return "/repos/" + repository.getOwnerName() + "/" + repository.getName() + "/contents/" + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
GHContent wrap(GHRepository owner) {
|
GHContent wrap(GHRepository owner) {
|
||||||
this.owner = owner;
|
this.repository = owner;
|
||||||
|
this.root = owner.root;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
GHContent wrap(GitHub root) {
|
||||||
|
this.root = root;
|
||||||
|
if (repository!=null)
|
||||||
|
repository.wrap(root);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static GHContent[] wrap(GHContent[] contents, GHRepository repository) {
|
public static GHContent[] wrap(GHContent[] contents, GHRepository repository) {
|
||||||
for (GHContent unwrappedContent : contents) {
|
for (GHContent unwrappedContent : contents) {
|
||||||
|
|||||||
74
src/main/java/org/kohsuke/github/GHContentSearchBuilder.java
Normal file
74
src/main/java/org/kohsuke/github/GHContentSearchBuilder.java
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search code for {@link GHContent}.
|
||||||
|
*
|
||||||
|
* @author Kohsuke Kawaguchi
|
||||||
|
* @see GitHub#searchContent()
|
||||||
|
*/
|
||||||
|
public class GHContentSearchBuilder extends GHSearchBuilder<GHContent> {
|
||||||
|
/*package*/ GHContentSearchBuilder(GitHub root) {
|
||||||
|
super(root,ContentSearchResult.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search terms.
|
||||||
|
*/
|
||||||
|
public GHContentSearchBuilder q(String term) {
|
||||||
|
super.q(term);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHContentSearchBuilder in(String v) {
|
||||||
|
return q("in:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHContentSearchBuilder language(String v) {
|
||||||
|
return q("language:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHContentSearchBuilder fork(String v) {
|
||||||
|
return q("fork:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHContentSearchBuilder size(String v) {
|
||||||
|
return q("size:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHContentSearchBuilder path(String v) {
|
||||||
|
return q("path:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHContentSearchBuilder filename(String v) {
|
||||||
|
return q("filename:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHContentSearchBuilder extension(String v) {
|
||||||
|
return q("extension:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHContentSearchBuilder user(String v) {
|
||||||
|
return q("user:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public GHContentSearchBuilder repo(String v) {
|
||||||
|
return q("repo:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ContentSearchResult extends SearchResult<GHContent> {
|
||||||
|
private GHContent[] items;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/*package*/ GHContent[] getItems(GitHub root) {
|
||||||
|
for (GHContent item : items)
|
||||||
|
item.wrap(root);
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getApiUrl() {
|
||||||
|
return "/search/code";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,21 +8,16 @@ import java.util.Locale;
|
|||||||
* @author Kohsuke Kawaguchi
|
* @author Kohsuke Kawaguchi
|
||||||
* @see GitHub#searchIssues()
|
* @see GitHub#searchIssues()
|
||||||
*/
|
*/
|
||||||
public class GHIssueSearchBuilder {
|
public class GHIssueSearchBuilder extends GHSearchBuilder<GHIssue> {
|
||||||
private final GitHub root;
|
|
||||||
private final Requester req;
|
|
||||||
private final List<String> terms = new ArrayList<String>();
|
|
||||||
|
|
||||||
/*package*/ GHIssueSearchBuilder(GitHub root) {
|
/*package*/ GHIssueSearchBuilder(GitHub root) {
|
||||||
this.root = root;
|
super(root,IssueSearchResult.class);
|
||||||
req = root.retrieve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search terms.
|
* Search terms.
|
||||||
*/
|
*/
|
||||||
public GHIssueSearchBuilder q(String term) {
|
public GHIssueSearchBuilder q(String term) {
|
||||||
terms.add(term);
|
super.q(term);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,25 +52,15 @@ public class GHIssueSearchBuilder {
|
|||||||
private GHIssue[] items;
|
private GHIssue[] items;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GHIssue[] getItems() {
|
/*package*/ GHIssue[] getItems(GitHub root) {
|
||||||
|
for (GHIssue i : items)
|
||||||
|
i.wrap(root);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Lists up the issues with the criteria built so far.
|
protected String getApiUrl() {
|
||||||
*/
|
return "/search/issues";
|
||||||
public PagedSearchIterable<GHIssue> list() {
|
|
||||||
return new PagedSearchIterable<GHIssue>() {
|
|
||||||
public PagedIterator<GHIssue> iterator() {
|
|
||||||
req.set("q", StringUtils.join(terms," "));
|
|
||||||
return new PagedIterator<GHIssue>(adapt(req.asIterator("/search/issues", IssueSearchResult.class))) {
|
|
||||||
protected void wrapUp(GHIssue[] page) {
|
|
||||||
for (GHIssue c : page)
|
|
||||||
c.wrap(root);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
207
src/main/java/org/kohsuke/github/GHNotificationStream.java
Normal file
207
src/main/java/org/kohsuke/github/GHNotificationStream.java
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens to GitHub notification stream.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This class supports two modes of retrieving notifications that can
|
||||||
|
* be controlled via {@link #nonBlocking(boolean)}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* In the blocking mode, which is the default, iterator will be infinite.
|
||||||
|
* The call to {@link Iterator#next()} will block until a new notification
|
||||||
|
* arrives. This is useful for application that runs perpetually and reacts
|
||||||
|
* to notifications.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* In the non-blocking mode, the iterator will only report the set of
|
||||||
|
* notifications initially retrieved from GitHub, then quit. This is useful
|
||||||
|
* for a batch application to process the current set of notifications.
|
||||||
|
*
|
||||||
|
* @author Kohsuke Kawaguchi
|
||||||
|
* @see GitHub#listNotifications()
|
||||||
|
* @see GHRepository#listNotifications()
|
||||||
|
*/
|
||||||
|
public class GHNotificationStream implements Iterable<GHThread> {
|
||||||
|
private final GitHub root;
|
||||||
|
|
||||||
|
private Boolean all, participating;
|
||||||
|
private String since;
|
||||||
|
private String apiUrl;
|
||||||
|
private boolean nonBlocking = false;
|
||||||
|
|
||||||
|
/*package*/ GHNotificationStream(GitHub root, String apiUrl) {
|
||||||
|
this.root = root;
|
||||||
|
this.apiUrl = apiUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the stream include notifications that are already read?
|
||||||
|
*/
|
||||||
|
public GHNotificationStream read(boolean v) {
|
||||||
|
all = v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the stream be restricted to notifications in which the user
|
||||||
|
* is directly participating or mentioned?
|
||||||
|
*/
|
||||||
|
public GHNotificationStream participating(boolean v) {
|
||||||
|
participating = v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHNotificationStream since(long timestamp) {
|
||||||
|
return since(new Date(timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHNotificationStream since(Date dt) {
|
||||||
|
since = GitHub.printDate(dt);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to true, {@link #iterator()} will stop iterating instead of blocking and
|
||||||
|
* waiting for the updates to arrive.
|
||||||
|
*/
|
||||||
|
public GHNotificationStream nonBlocking(boolean v) {
|
||||||
|
this.nonBlocking = v;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an infinite blocking {@link Iterator} that returns
|
||||||
|
* {@link GHThread} as notifications arrive.
|
||||||
|
*/
|
||||||
|
public Iterator<GHThread> iterator() {
|
||||||
|
// capture the configuration setting here
|
||||||
|
final Requester req = new Requester(root).method("GET")
|
||||||
|
.with("all", all).with("participating", participating).with("since", since);
|
||||||
|
|
||||||
|
return new Iterator<GHThread>() {
|
||||||
|
/**
|
||||||
|
* Stuff we've fetched but haven't returned to the caller.
|
||||||
|
* Newer ones first.
|
||||||
|
*/
|
||||||
|
private GHThread[] threads = EMPTY_ARRAY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Next element in {@link #threads} to return. This counts down.
|
||||||
|
*/
|
||||||
|
private int idx=-1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* threads whose updated_at is older than this should be ignored.
|
||||||
|
*/
|
||||||
|
private long lastUpdated = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Next request should have "If-Modified-Since" header with this value.
|
||||||
|
*/
|
||||||
|
private String lastModified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When is the next polling allowed?
|
||||||
|
*/
|
||||||
|
private long nextCheckTime = -1;
|
||||||
|
|
||||||
|
private GHThread next;
|
||||||
|
|
||||||
|
public GHThread next() {
|
||||||
|
if (next==null) {
|
||||||
|
next = fetch();
|
||||||
|
if (next==null)
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
GHThread r = next;
|
||||||
|
next = null;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (next==null)
|
||||||
|
next = fetch();
|
||||||
|
return next!=null;
|
||||||
|
}
|
||||||
|
|
||||||
|
GHThread fetch() {
|
||||||
|
try {
|
||||||
|
while (true) {// loop until we get new threads to return
|
||||||
|
|
||||||
|
// if we have fetched un-returned threads, use them first
|
||||||
|
while (idx>=0) {
|
||||||
|
GHThread n = threads[idx--];
|
||||||
|
long nt = n.getUpdatedAt().getTime();
|
||||||
|
if (nt >= lastUpdated) {
|
||||||
|
lastUpdated = nt;
|
||||||
|
return n.wrap(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonBlocking && nextCheckTime>=0)
|
||||||
|
return null; // nothing more to report, and we aren't blocking
|
||||||
|
|
||||||
|
// observe the polling interval before making the call
|
||||||
|
while (true) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (nextCheckTime < now) break;
|
||||||
|
long waitTime = Math.max(Math.min(nextCheckTime - now, 1000), 60 * 1000);
|
||||||
|
Thread.sleep(waitTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.setHeader("If-Modified-Since", lastModified);
|
||||||
|
|
||||||
|
threads = req.to(apiUrl, GHThread[].class);
|
||||||
|
if (threads==null) {
|
||||||
|
threads = EMPTY_ARRAY; // if unmodified, we get empty array
|
||||||
|
} else {
|
||||||
|
// we get a new batch, but we want to ignore the ones that we've seen
|
||||||
|
lastUpdated++;
|
||||||
|
}
|
||||||
|
idx = threads.length-1;
|
||||||
|
|
||||||
|
nextCheckTime = calcNextCheckTime();
|
||||||
|
lastModified = req.getResponseHeader("Last-Modified");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long calcNextCheckTime() {
|
||||||
|
String v = req.getResponseHeader("X-Poll-Interval");
|
||||||
|
if (v==null) v="60";
|
||||||
|
return System.currentTimeMillis()+Integer.parseInt(v)*1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markAsRead() throws IOException {
|
||||||
|
markAsRead(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks all the notifications as read.
|
||||||
|
*/
|
||||||
|
public void markAsRead(long timestamp) throws IOException {
|
||||||
|
final Requester req = new Requester(root).method("PUT");
|
||||||
|
if (timestamp>=0)
|
||||||
|
req.with("last_read_at", GitHub.printDate(new Date(timestamp)));
|
||||||
|
req.asHttpStatusCode(apiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final GHThread[] EMPTY_ARRAY = new GHThread[0];
|
||||||
|
}
|
||||||
@@ -1166,11 +1166,17 @@ public class GHRepository extends GHObject {
|
|||||||
.with("text", text)
|
.with("text", text)
|
||||||
.with("mode",mode==null?null:mode.toString())
|
.with("mode",mode==null?null:mode.toString())
|
||||||
.with("context", getFullName())
|
.with("context", getFullName())
|
||||||
.read("/markdown"),
|
.asStream("/markdown"),
|
||||||
"UTF-8");
|
"UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all the notifications in a repository for the current user.
|
||||||
|
*/
|
||||||
|
public GHNotificationStream listNotifications() {
|
||||||
|
return new GHNotificationStream(root,getApiTailUrl("/notifications"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search repositories.
|
||||||
|
*
|
||||||
|
* @author Kohsuke Kawaguchi
|
||||||
|
* @see GitHub#searchRepositories()
|
||||||
|
*/
|
||||||
|
public class GHRepositorySearchBuilder extends GHSearchBuilder<GHRepository> {
|
||||||
|
/*package*/ GHRepositorySearchBuilder(GitHub root) {
|
||||||
|
super(root,RepositorySearchResult.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search terms.
|
||||||
|
*/
|
||||||
|
public GHRepositorySearchBuilder q(String term) {
|
||||||
|
super.q(term);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder in(String v) {
|
||||||
|
return q("in:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder size(String v) {
|
||||||
|
return q("size:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder forks(String v) {
|
||||||
|
return q("forks:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder created(String v) {
|
||||||
|
return q("created:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder pushed(String v) {
|
||||||
|
return q("pushed:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder user(String v) {
|
||||||
|
return q("user:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder repo(String v) {
|
||||||
|
return q("repo:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder language(String v) {
|
||||||
|
return q("language:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder stars(String v) {
|
||||||
|
return q("stars:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepositorySearchBuilder sort(Sort sort) {
|
||||||
|
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Sort { STARS, FORKS, UPDATED }
|
||||||
|
|
||||||
|
private static class RepositorySearchResult extends SearchResult<GHRepository> {
|
||||||
|
private GHRepository[] items;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/*package*/ GHRepository[] getItems(GitHub root) {
|
||||||
|
for (GHRepository item : items)
|
||||||
|
item.wrap(root);
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getApiUrl() {
|
||||||
|
return "/search/repositories";
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/main/java/org/kohsuke/github/GHSearchBuilder.java
Normal file
54
src/main/java/org/kohsuke/github/GHSearchBuilder.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for various search builders.
|
||||||
|
*
|
||||||
|
* @author Kohsuke Kawaguchi
|
||||||
|
*/
|
||||||
|
public abstract class GHSearchBuilder<T> {
|
||||||
|
protected final GitHub root;
|
||||||
|
protected final Requester req;
|
||||||
|
protected final List<String> terms = new ArrayList<String>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data transfer object that receives the result of search.
|
||||||
|
*/
|
||||||
|
private final Class<? extends SearchResult<T>> receiverType;
|
||||||
|
|
||||||
|
/*package*/ GHSearchBuilder(GitHub root, Class<? extends SearchResult<T>> receiverType) {
|
||||||
|
this.root = root;
|
||||||
|
this.req = root.retrieve();
|
||||||
|
this.receiverType = receiverType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search terms.
|
||||||
|
*/
|
||||||
|
public GHSearchBuilder q(String term) {
|
||||||
|
terms.add(term);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the search.
|
||||||
|
*/
|
||||||
|
public PagedSearchIterable<T> list() {
|
||||||
|
return new PagedSearchIterable<T>(root) {
|
||||||
|
public PagedIterator<T> iterator() {
|
||||||
|
req.set("q", StringUtils.join(terms, " "));
|
||||||
|
return new PagedIterator<T>(adapt(req.asIterator(getApiUrl(), receiverType))) {
|
||||||
|
protected void wrapUp(T[] page) {
|
||||||
|
// SearchResult.getItems() should do it
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String getApiUrl();
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@ import java.io.IOException;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents your subscribing to a repository.
|
* Represents your subscribing to a repository / conversation thread..
|
||||||
*
|
*
|
||||||
* @author Kohsuke Kawaguchi
|
* @author Kohsuke Kawaguchi
|
||||||
|
* @see GHRepository#getSubscription()
|
||||||
|
* @see GHThread#getSubscription()
|
||||||
*/
|
*/
|
||||||
public class GHSubscription {
|
public class GHSubscription {
|
||||||
private String created_at, url, repository_url, reason;
|
private String created_at, url, repository_url, reason;
|
||||||
|
|||||||
98
src/main/java/org/kohsuke/github/GHThread.java
Normal file
98
src/main/java/org/kohsuke/github/GHThread.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A conversation in the notification API.
|
||||||
|
*
|
||||||
|
* @see <a href="https://developer.github.com/v3/activity/notifications/">documentation</a>
|
||||||
|
* @see GHNotificationStream
|
||||||
|
* @author Kohsuke Kawaguchi
|
||||||
|
*/
|
||||||
|
public class GHThread extends GHObject {
|
||||||
|
private GitHub root;
|
||||||
|
private GHRepository repository;
|
||||||
|
private Subject subject;
|
||||||
|
private String reason;
|
||||||
|
private boolean unread;
|
||||||
|
private String last_read_at;
|
||||||
|
private String url,subscription_url;
|
||||||
|
|
||||||
|
static class Subject {
|
||||||
|
String title;
|
||||||
|
String url;
|
||||||
|
String latest_comment_url;
|
||||||
|
String type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GHThread() {// no external construction allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns null if the entire thread has never been read.
|
||||||
|
*/
|
||||||
|
public Date getLastReadAt() {
|
||||||
|
return GitHub.parseDate(last_read_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHRepository getRepository() {
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: how to expose the subject?
|
||||||
|
|
||||||
|
public boolean isRead() {
|
||||||
|
return !unread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return subject.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return subject.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/ GHThread wrap(GitHub root) {
|
||||||
|
this.root = root;
|
||||||
|
if (this.repository!=null)
|
||||||
|
this.repository.wrap(root);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks this thread as read.
|
||||||
|
*/
|
||||||
|
public void markAsRead() throws IOException {
|
||||||
|
new Requester(root).method("PATCH").to(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to this conversation to get notifications.
|
||||||
|
*/
|
||||||
|
public GHSubscription subscribe(boolean subscribed, boolean ignored) throws IOException {
|
||||||
|
return new Requester(root)
|
||||||
|
.with("subscribed", subscribed)
|
||||||
|
.with("ignored", ignored)
|
||||||
|
.method("PUT").to(subscription_url, GHSubscription.class).wrapUp(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current subscription for this thread.
|
||||||
|
*
|
||||||
|
* @return null if no subscription exists.
|
||||||
|
*/
|
||||||
|
public GHSubscription getSubscription() throws IOException {
|
||||||
|
try {
|
||||||
|
return new Requester(root).to(subscription_url, GHSubscription.class).wrapUp(root);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/main/java/org/kohsuke/github/GHUserSearchBuilder.java
Normal file
72
src/main/java/org/kohsuke/github/GHUserSearchBuilder.java
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package org.kohsuke.github;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search users.
|
||||||
|
*
|
||||||
|
* @author Kohsuke Kawaguchi
|
||||||
|
* @see GitHub#searchUsers()
|
||||||
|
*/
|
||||||
|
public class GHUserSearchBuilder extends GHSearchBuilder<GHUser> {
|
||||||
|
/*package*/ GHUserSearchBuilder(GitHub root) {
|
||||||
|
super(root,UserSearchResult.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search terms.
|
||||||
|
*/
|
||||||
|
public GHUserSearchBuilder q(String term) {
|
||||||
|
super.q(term);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHUserSearchBuilder type(String v) {
|
||||||
|
return q("type:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHUserSearchBuilder in(String v) {
|
||||||
|
return q("in:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHUserSearchBuilder repos(String v) {
|
||||||
|
return q("repos:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHUserSearchBuilder location(String v) {
|
||||||
|
return q("location:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHUserSearchBuilder language(String v) {
|
||||||
|
return q("language:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHUserSearchBuilder created(String v) {
|
||||||
|
return q("created:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHUserSearchBuilder followers(String v) {
|
||||||
|
return q("followers:"+v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GHUserSearchBuilder sort(Sort sort) {
|
||||||
|
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Sort { FOLLOWERS, REPOSITORIES, JOINED }
|
||||||
|
|
||||||
|
private static class UserSearchResult extends SearchResult<GHUser> {
|
||||||
|
private GHUser[] items;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/*package*/ GHUser[] getItems(GitHub root) {
|
||||||
|
return GHUser.wrap(items,root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getApiUrl() {
|
||||||
|
return "/search/users";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -443,6 +443,34 @@ public class GitHub {
|
|||||||
return new GHIssueSearchBuilder(this);
|
return new GHIssueSearchBuilder(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search users.
|
||||||
|
*/
|
||||||
|
public GHUserSearchBuilder searchUsers() {
|
||||||
|
return new GHUserSearchBuilder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search repositories.
|
||||||
|
*/
|
||||||
|
public GHRepositorySearchBuilder searchRepositories() {
|
||||||
|
return new GHRepositorySearchBuilder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search content.
|
||||||
|
*/
|
||||||
|
public GHContentSearchBuilder searchContent() {
|
||||||
|
return new GHContentSearchBuilder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all the notifications.
|
||||||
|
*/
|
||||||
|
public GHNotificationStream listNotifications() {
|
||||||
|
return new GHNotificationStream(this,"/notifications");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This provides a dump of every public repository, in the order that they were created.
|
* This provides a dump of every public repository, in the order that they were created.
|
||||||
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
|
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
|
||||||
@@ -487,7 +515,7 @@ public class GitHub {
|
|||||||
new Requester(this)
|
new Requester(this)
|
||||||
.with(new ByteArrayInputStream(text.getBytes("UTF-8")))
|
.with(new ByteArrayInputStream(text.getBytes("UTF-8")))
|
||||||
.contentType("text/plain;charset=UTF-8")
|
.contentType("text/plain;charset=UTF-8")
|
||||||
.read("/markdown/raw"),
|
.asStream("/markdown/raw"),
|
||||||
"UTF-8");
|
"UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ import java.util.Iterator;
|
|||||||
* @author Kohsuke Kawaguchi
|
* @author Kohsuke Kawaguchi
|
||||||
*/
|
*/
|
||||||
public abstract class PagedSearchIterable<T> extends PagedIterable<T> {
|
public abstract class PagedSearchIterable<T> extends PagedIterable<T> {
|
||||||
|
private final GitHub root;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As soon as we have any result fetched, it's set here so that we can report the total count.
|
* As soon as we have any result fetched, it's set here so that we can report the total count.
|
||||||
*/
|
*/
|
||||||
private SearchResult<T> result;
|
private SearchResult<T> result;
|
||||||
|
|
||||||
|
/*package*/ PagedSearchIterable(GitHub root) {
|
||||||
|
this.root = root;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the total number of hit, including the results that's not yet fetched.
|
* Returns the total number of hit, including the results that's not yet fetched.
|
||||||
*/
|
*/
|
||||||
@@ -43,7 +49,7 @@ public abstract class PagedSearchIterable<T> extends PagedIterable<T> {
|
|||||||
public T[] next() {
|
public T[] next() {
|
||||||
SearchResult<T> v = base.next();
|
SearchResult<T> v = base.next();
|
||||||
if (result==null) result = v;
|
if (result==null) result = v;
|
||||||
return v.getItems();
|
return v.getItems(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove() {
|
public void remove() {
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.kohsuke.github;
|
package org.kohsuke.github;
|
||||||
|
|
||||||
import static org.kohsuke.github.GitHub.MAPPER;
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -43,6 +44,7 @@ import java.util.Collection;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
@@ -51,8 +53,7 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
import static org.kohsuke.github.GitHub.*;
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder pattern for making HTTP call and parsing its output.
|
* A builder pattern for making HTTP call and parsing its output.
|
||||||
@@ -62,6 +63,7 @@ import org.apache.commons.io.IOUtils;
|
|||||||
class Requester {
|
class Requester {
|
||||||
private final GitHub root;
|
private final GitHub root;
|
||||||
private final List<Entry> args = new ArrayList<Entry>();
|
private final List<Entry> args = new ArrayList<Entry>();
|
||||||
|
private final Map<String,String> headers = new LinkedHashMap<String, String>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request method.
|
* Request method.
|
||||||
@@ -70,6 +72,11 @@ class Requester {
|
|||||||
private String contentType = "application/x-www-form-urlencoded";
|
private String contentType = "application/x-www-form-urlencoded";
|
||||||
private InputStream body;
|
private InputStream body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current connection.
|
||||||
|
*/
|
||||||
|
private HttpURLConnection uc;
|
||||||
|
|
||||||
private static class Entry {
|
private static class Entry {
|
||||||
String key;
|
String key;
|
||||||
Object value;
|
Object value;
|
||||||
@@ -84,6 +91,15 @@ class Requester {
|
|||||||
this.root = root;
|
this.root = root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the request HTTP header.
|
||||||
|
*
|
||||||
|
* If a header of the same name is already set, this method overrides it.
|
||||||
|
*/
|
||||||
|
public void setHeader(String name, String value) {
|
||||||
|
headers.put(name,value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a request with authentication credential.
|
* Makes a request with authentication credential.
|
||||||
*/
|
*/
|
||||||
@@ -107,6 +123,10 @@ class Requester {
|
|||||||
public Requester with(String key, boolean value) {
|
public Requester with(String key, boolean value) {
|
||||||
return _with(key, value);
|
return _with(key, value);
|
||||||
}
|
}
|
||||||
|
public Requester with(String key, Boolean value) {
|
||||||
|
return _with(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Requester with(String key, String value) {
|
public Requester with(String key, String value) {
|
||||||
return _with(key, value);
|
return _with(key, value);
|
||||||
@@ -196,12 +216,12 @@ class Requester {
|
|||||||
}
|
}
|
||||||
tailApiUrl += qs.toString();
|
tailApiUrl += qs.toString();
|
||||||
}
|
}
|
||||||
HttpURLConnection uc = setupConnection(root.getApiURL(tailApiUrl));
|
setupConnection(root.getApiURL(tailApiUrl));
|
||||||
|
|
||||||
buildRequest(uc);
|
buildRequest();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
T result = parse(uc, type, instance);
|
T result = parse(type, instance);
|
||||||
if (type != null && type.isArray()) { // we might have to loop for pagination - done through recursion
|
if (type != null && type.isArray()) { // we might have to loop for pagination - done through recursion
|
||||||
final String links = uc.getHeaderField("link");
|
final String links = uc.getHeaderField("link");
|
||||||
if (links != null && links.contains("rel=\"next\"")) {
|
if (links != null && links.contains("rel=\"next\"")) {
|
||||||
@@ -222,7 +242,7 @@ class Requester {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
handleApiError(e,uc);
|
handleApiError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,33 +252,41 @@ class Requester {
|
|||||||
*/
|
*/
|
||||||
public int asHttpStatusCode(String tailApiUrl) throws IOException {
|
public int asHttpStatusCode(String tailApiUrl) throws IOException {
|
||||||
while (true) {// loop while API rate limit is hit
|
while (true) {// loop while API rate limit is hit
|
||||||
HttpURLConnection uc = setupConnection(root.getApiURL(tailApiUrl));
|
setupConnection(root.getApiURL(tailApiUrl));
|
||||||
|
|
||||||
buildRequest(uc);
|
buildRequest();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return uc.getResponseCode();
|
return uc.getResponseCode();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
handleApiError(e,uc);
|
handleApiError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream read(String tailApiUrl) throws IOException {
|
public InputStream asStream(String tailApiUrl) throws IOException {
|
||||||
while (true) {// loop while API rate limit is hit
|
while (true) {// loop while API rate limit is hit
|
||||||
HttpURLConnection uc = setupConnection(root.getApiURL(tailApiUrl));
|
setupConnection(root.getApiURL(tailApiUrl));
|
||||||
|
|
||||||
buildRequest(uc);
|
buildRequest();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return wrapStream(uc,uc.getInputStream());
|
return wrapStream(uc.getInputStream());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
handleApiError(e,uc);
|
handleApiError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildRequest(HttpURLConnection uc) throws IOException {
|
public String getResponseHeader(String header) {
|
||||||
|
return uc.getHeaderField(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the request parameters or POST payload.
|
||||||
|
*/
|
||||||
|
private void buildRequest() throws IOException {
|
||||||
if (!method.equals("GET")) {
|
if (!method.equals("GET")) {
|
||||||
uc.setDoOutput(true);
|
uc.setDoOutput(true);
|
||||||
uc.setRequestProperty("Content-type", contentType);
|
uc.setRequestProperty("Content-type", contentType);
|
||||||
@@ -347,14 +375,14 @@ class Requester {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
while (true) {// loop while API rate limit is hit
|
while (true) {// loop while API rate limit is hit
|
||||||
HttpURLConnection uc = setupConnection(url);
|
setupConnection(url);
|
||||||
try {
|
try {
|
||||||
next = parse(uc,type,null);
|
next = parse(type,null);
|
||||||
assert next!=null;
|
assert next!=null;
|
||||||
findNextURL(uc);
|
findNextURL();
|
||||||
return;
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
handleApiError(e,uc);
|
handleApiError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -365,7 +393,7 @@ class Requester {
|
|||||||
/**
|
/**
|
||||||
* Locate the next page from the pagination "Link" tag.
|
* Locate the next page from the pagination "Link" tag.
|
||||||
*/
|
*/
|
||||||
private void findNextURL(HttpURLConnection uc) throws MalformedURLException {
|
private void findNextURL() throws MalformedURLException {
|
||||||
url = null; // start defensively
|
url = null; // start defensively
|
||||||
String link = uc.getHeaderField("Link");
|
String link = uc.getHeaderField("Link");
|
||||||
if (link==null) return;
|
if (link==null) return;
|
||||||
@@ -386,14 +414,20 @@ class Requester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private HttpURLConnection setupConnection(URL url) throws IOException {
|
private void setupConnection(URL url) throws IOException {
|
||||||
HttpURLConnection uc = root.getConnector().connect(url);
|
uc = root.getConnector().connect(url);
|
||||||
|
|
||||||
// if the authentication is needed but no credential is given, try it anyway (so that some calls
|
// if the authentication is needed but no credential is given, try it anyway (so that some calls
|
||||||
// that do work with anonymous access in the reduced form should still work.)
|
// that do work with anonymous access in the reduced form should still work.)
|
||||||
if (root.encodedAuthorization!=null)
|
if (root.encodedAuthorization!=null)
|
||||||
uc.setRequestProperty("Authorization", root.encodedAuthorization);
|
uc.setRequestProperty("Authorization", root.encodedAuthorization);
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||||
|
String v = e.getValue();
|
||||||
|
if (v!=null)
|
||||||
|
uc.setRequestProperty(e.getKey(), v);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uc.setRequestMethod(method);
|
uc.setRequestMethod(method);
|
||||||
} catch (ProtocolException e) {
|
} catch (ProtocolException e) {
|
||||||
@@ -407,13 +441,14 @@ class Requester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
uc.setRequestProperty("Accept-Encoding", "gzip");
|
uc.setRequestProperty("Accept-Encoding", "gzip");
|
||||||
return uc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T parse(HttpURLConnection uc, Class<T> type, T instance) throws IOException {
|
private <T> T parse(Class<T> type, T instance) throws IOException {
|
||||||
|
if (uc.getResponseCode()==304)
|
||||||
|
return null; // special case handling for 304 unmodified, as the content will be ""
|
||||||
InputStreamReader r = null;
|
InputStreamReader r = null;
|
||||||
try {
|
try {
|
||||||
r = new InputStreamReader(wrapStream(uc, uc.getInputStream()), "UTF-8");
|
r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8");
|
||||||
String data = IOUtils.toString(r);
|
String data = IOUtils.toString(r);
|
||||||
if (type!=null)
|
if (type!=null)
|
||||||
try {
|
try {
|
||||||
@@ -432,7 +467,7 @@ class Requester {
|
|||||||
/**
|
/**
|
||||||
* Handles the "Content-Encoding" header.
|
* Handles the "Content-Encoding" header.
|
||||||
*/
|
*/
|
||||||
private InputStream wrapStream(HttpURLConnection uc, InputStream in) throws IOException {
|
private InputStream wrapStream(InputStream in) throws IOException {
|
||||||
String encoding = uc.getContentEncoding();
|
String encoding = uc.getContentEncoding();
|
||||||
if (encoding==null || in==null) return in;
|
if (encoding==null || in==null) return in;
|
||||||
if (encoding.equals("gzip")) return new GZIPInputStream(in);
|
if (encoding.equals("gzip")) return new GZIPInputStream(in);
|
||||||
@@ -443,19 +478,20 @@ class Requester {
|
|||||||
/**
|
/**
|
||||||
* Handle API error by either throwing it or by returning normally to retry.
|
* Handle API error by either throwing it or by returning normally to retry.
|
||||||
*/
|
*/
|
||||||
/*package*/ void handleApiError(IOException e, HttpURLConnection uc) throws IOException {
|
/*package*/ void handleApiError(IOException e) throws IOException {
|
||||||
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
|
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
|
||||||
root.rateLimitHandler.onError(e,uc);
|
root.rateLimitHandler.onError(e,uc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e instanceof FileNotFoundException)
|
InputStream es = wrapStream(uc.getErrorStream());
|
||||||
throw e; // pass through 404 Not Found to allow the caller to handle it intelligently
|
|
||||||
|
|
||||||
InputStream es = wrapStream(uc, uc.getErrorStream());
|
|
||||||
try {
|
try {
|
||||||
if (es!=null)
|
if (es!=null) {
|
||||||
throw (IOException)new IOException(IOUtils.toString(es,"UTF-8")).initCause(e);
|
if (e instanceof FileNotFoundException) {
|
||||||
else
|
// pass through 404 Not Found to allow the caller to handle it intelligently
|
||||||
|
throw (IOException) new FileNotFoundException(IOUtils.toString(es, "UTF-8")).initCause(e);
|
||||||
|
} else
|
||||||
|
throw (IOException) new IOException(IOUtils.toString(es, "UTF-8")).initCause(e);
|
||||||
|
} else
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(es);
|
IOUtils.closeQuietly(es);
|
||||||
|
|||||||
@@ -9,5 +9,8 @@ abstract class SearchResult<T> {
|
|||||||
int total_count;
|
int total_count;
|
||||||
boolean incomplete_results;
|
boolean incomplete_results;
|
||||||
|
|
||||||
public abstract T[] getItems();
|
/**
|
||||||
|
* Wraps up the retrieved object and return them. Only called once.
|
||||||
|
*/
|
||||||
|
/*package*/ abstract T[] getItems(GitHub root);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,4 +41,11 @@ This library comes with a pluggable connector to use different HTTP client imple
|
|||||||
through `HttpConnector`. In particular, this means you can use [OkHttp](http://square.github.io/okhttp/),
|
through `HttpConnector`. In particular, this means you can use [OkHttp](http://square.github.io/okhttp/),
|
||||||
so we can make use of it's HTTP response cache.
|
so we can make use of it's HTTP response cache.
|
||||||
Making a conditional request against the GitHub API and receiving a 304 response
|
Making a conditional request against the GitHub API and receiving a 304 response
|
||||||
[does not count against the rate limit](http://developer.github.com/v3/#conditional-requests).
|
[does not count against the rate limit](http://developer.github.com/v3/#conditional-requests).
|
||||||
|
|
||||||
|
The following code shows an example of how to set up persistent cache on the disk:
|
||||||
|
|
||||||
|
Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024); // 10MB cache
|
||||||
|
GitHub gitHub = GitHubBuilder.fromCredentials()
|
||||||
|
.withConnector(new OkHttpConnector(new OkUrlFactory(new OkHttpClient().setCache(cache))))
|
||||||
|
.build();
|
||||||
|
|||||||
@@ -777,6 +777,56 @@ public class AppTest extends AbstractGitHubApiTestBase {
|
|||||||
assertTrue(actual.contains("to fix issue"));
|
assertTrue(actual.contains("to fix issue"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchUsers() throws Exception {
|
||||||
|
PagedSearchIterable<GHUser> r = gitHub.searchUsers().q("tom").repos(">42").followers(">1000").list();
|
||||||
|
GHUser u = r.iterator().next();
|
||||||
|
System.out.println(u.getName());
|
||||||
|
assertNotNull(u.getId());
|
||||||
|
assertTrue(r.getTotalCount() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchRepositories() throws Exception {
|
||||||
|
PagedSearchIterable<GHRepository> r = gitHub.searchRepositories().q("tetris").language("assembly").sort(GHRepositorySearchBuilder.Sort.STARS).list();
|
||||||
|
GHRepository u = r.iterator().next();
|
||||||
|
System.out.println(u.getName());
|
||||||
|
assertNotNull(u.getId());
|
||||||
|
assertEquals("Assembly", u.getLanguage());
|
||||||
|
assertTrue(r.getTotalCount() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchContent() throws Exception {
|
||||||
|
PagedSearchIterable<GHContent> r = gitHub.searchContent().q("addClass").in("file").language("js").repo("jquery/jquery").list();
|
||||||
|
GHContent c = r.iterator().next();
|
||||||
|
System.out.println(c.getName());
|
||||||
|
assertNotNull(c.getDownloadUrl());
|
||||||
|
assertNotNull(c.getOwner());
|
||||||
|
assertEquals("jquery/jquery",c.getOwner().getFullName());
|
||||||
|
assertTrue(r.getTotalCount() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notifications() throws Exception {
|
||||||
|
boolean found=false;
|
||||||
|
for (GHThread t : gitHub.listNotifications().nonBlocking(true).read(true)) {
|
||||||
|
if (!found) {
|
||||||
|
found = true;
|
||||||
|
t.markAsRead(); // test this by calling it once on old nofication
|
||||||
|
}
|
||||||
|
assertNotNull(t.getTitle());
|
||||||
|
assertNotNull(t.getReason());
|
||||||
|
|
||||||
|
System.out.println(t.getTitle());
|
||||||
|
System.out.println(t.getLastReadAt());
|
||||||
|
System.out.println(t.getType());
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
assertTrue(found);
|
||||||
|
gitHub.listNotifications().markAsRead();
|
||||||
|
}
|
||||||
|
|
||||||
private void kohsuke() {
|
private void kohsuke() {
|
||||||
String login = getUser().getLogin();
|
String login = getUser().getLogin();
|
||||||
Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2"));
|
Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2"));
|
||||||
|
|||||||
Reference in New Issue
Block a user