c77b7cf18acef2198ff1da774ee1108508f28814
[idea/community.git] / plugins / github / src / org / jetbrains / plugins / github / tasks / GithubRepository.java
1 package org.jetbrains.plugins.github.tasks;
2
3 import com.intellij.openapi.diagnostic.Logger;
4 import com.intellij.openapi.progress.ProgressIndicator;
5 import com.intellij.openapi.util.Comparing;
6 import com.intellij.openapi.util.PasswordUtil;
7 import com.intellij.openapi.util.text.StringUtil;
8 import com.intellij.tasks.Comment;
9 import com.intellij.tasks.Task;
10 import com.intellij.tasks.TaskRepository;
11 import com.intellij.tasks.TaskType;
12 import com.intellij.tasks.impl.BaseRepository;
13 import com.intellij.tasks.impl.BaseRepositoryImpl;
14 import com.intellij.util.Function;
15 import com.intellij.util.containers.ContainerUtil;
16 import com.intellij.util.xmlb.annotations.Tag;
17 import com.intellij.util.xmlb.annotations.Transient;
18 import icons.TasksIcons;
19 import org.jetbrains.annotations.NotNull;
20 import org.jetbrains.annotations.Nullable;
21 import org.jetbrains.plugins.github.api.GithubApiUtil;
22 import org.jetbrains.plugins.github.api.GithubConnection;
23 import org.jetbrains.plugins.github.api.GithubIssue;
24 import org.jetbrains.plugins.github.api.GithubIssueComment;
25 import org.jetbrains.plugins.github.exceptions.*;
26 import org.jetbrains.plugins.github.util.GithubAuthData;
27 import org.jetbrains.plugins.github.util.GithubUtil;
28
29 import javax.swing.*;
30 import java.util.Date;
31 import java.util.List;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34
35 /**
36  * @author Dennis.Ushakov
37  */
38 @Tag("GitHub")
39 public class GithubRepository extends BaseRepositoryImpl {
40   private static final Logger LOG = GithubUtil.LOG;
41
42   private Pattern myPattern = Pattern.compile("($^)");
43   @NotNull private String myRepoAuthor = "";
44   @NotNull private String myRepoName = "";
45   @NotNull private String myUser = "";
46   @NotNull private String myToken = "";
47
48   @SuppressWarnings({"UnusedDeclaration"})
49   public GithubRepository() {
50   }
51
52   public GithubRepository(GithubRepository other) {
53     super(other);
54     setRepoName(other.myRepoName);
55     setRepoAuthor(other.myRepoAuthor);
56     setToken(other.myToken);
57   }
58
59   public GithubRepository(GithubRepositoryType type) {
60     super(type);
61     setUrl(GithubApiUtil.DEFAULT_GITHUB_HOST);
62   }
63
64   @NotNull
65   @Override
66   public CancellableConnection createCancellableConnection() {
67     return new CancellableConnection() {
68       private final GithubConnection myConnection = new GithubConnection(getAuthData(), false);
69
70       @Override
71       protected void doTest() throws Exception {
72         try {
73           GithubApiUtil.getIssuesQueried(myConnection, getRepoAuthor(), getRepoName(), "", false);
74         }
75         catch (GithubOperationCanceledException ignore) {
76         }
77       }
78
79       @Override
80       public void cancel() {
81         myConnection.abort();
82       }
83     };
84   }
85
86   @Override
87   public boolean isConfigured() {
88     return super.isConfigured() &&
89            !StringUtil.isEmptyOrSpaces(getRepoAuthor()) &&
90            !StringUtil.isEmptyOrSpaces(getRepoName()) &&
91            !StringUtil.isEmptyOrSpaces(getToken());
92   }
93
94   @Override
95   public String getPresentableName() {
96     final String name = super.getPresentableName();
97     return name +
98            (!StringUtil.isEmpty(getRepoAuthor()) ? "/" + getRepoAuthor() : "") +
99            (!StringUtil.isEmpty(getRepoName()) ? "/" + getRepoName() : "");
100   }
101
102   @Override
103   public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed) throws Exception {
104     try {
105       return getIssues(query, offset + limit, withClosed);
106     }
107     catch (GithubRateLimitExceededException e) {
108       return new Task[0];
109     }
110     catch (GithubAuthenticationException e) {
111       throw new Exception(e.getMessage(), e); // Wrap to show error message
112     }
113     catch (GithubStatusCodeException e) {
114       throw new Exception(e.getMessage(), e);
115     }
116     catch (GithubJsonException e) {
117       throw new Exception("Bad response format", e);
118     }
119   }
120
121   @Override
122   public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed, @NotNull ProgressIndicator cancelled)
123     throws Exception {
124     return getIssues(query, offset, limit, withClosed);
125   }
126
127   @NotNull
128   private Task[] getIssues(@Nullable String query, int max, boolean withClosed) throws Exception {
129     GithubConnection connection = getConnection();
130
131     try {
132       List<GithubIssue> issues;
133       if (StringUtil.isEmptyOrSpaces(query)) {
134         if (StringUtil.isEmptyOrSpaces(myUser)) {
135           myUser = GithubApiUtil.getCurrentUser(connection).getLogin();
136         }
137         issues = GithubApiUtil.getIssuesAssigned(connection, getRepoAuthor(), getRepoName(), myUser, max, withClosed);
138       }
139       else {
140         issues = GithubApiUtil.getIssuesQueried(connection, getRepoAuthor(), getRepoName(), query, withClosed);
141       }
142
143       return ContainerUtil.map2Array(issues, Task.class, new Function<GithubIssue, Task>() {
144         @Override
145         public Task fun(GithubIssue issue) {
146           return createTask(issue);
147         }
148       });
149     }
150     finally {
151       connection.close();
152     }
153   }
154
155   @NotNull
156   private Task createTask(final GithubIssue issue) {
157     return new Task() {
158       @NotNull String myRepoName = getRepoName();
159
160       @Override
161       public boolean isIssue() {
162         return true;
163       }
164
165       @Override
166       public String getIssueUrl() {
167         return issue.getHtmlUrl();
168       }
169
170       @NotNull
171       @Override
172       public String getId() {
173         return myRepoName + "-" + issue.getNumber();
174       }
175
176       @NotNull
177       @Override
178       public String getSummary() {
179         return issue.getTitle();
180       }
181
182       public String getDescription() {
183         return issue.getBody();
184       }
185
186       @NotNull
187       @Override
188       public Comment[] getComments() {
189         try {
190           return fetchComments(issue.getNumber());
191         }
192         catch (Exception e) {
193           LOG.warn("Error fetching comments for " + issue.getNumber(), e);
194           return Comment.EMPTY_ARRAY;
195         }
196       }
197
198       @NotNull
199       @Override
200       public Icon getIcon() {
201         return TasksIcons.Github;
202       }
203
204       @NotNull
205       @Override
206       public TaskType getType() {
207         return TaskType.BUG;
208       }
209
210       @Override
211       public Date getUpdated() {
212         return issue.getUpdatedAt();
213       }
214
215       @Override
216       public Date getCreated() {
217         return issue.getCreatedAt();
218       }
219
220       @Override
221       public boolean isClosed() {
222         return !"open".equals(issue.getState());
223       }
224
225       @Override
226       public TaskRepository getRepository() {
227         return GithubRepository.this;
228       }
229
230       @Override
231       public String getPresentableName() {
232         return getId() + ": " + getSummary();
233       }
234     };
235   }
236
237   private Comment[] fetchComments(final long id) throws Exception {
238     GithubConnection connection = getConnection();
239     try {
240       List<GithubIssueComment> result = GithubApiUtil.getIssueComments(connection, getRepoAuthor(), getRepoName(), id);
241
242       return ContainerUtil.map2Array(result, Comment.class, new Function<GithubIssueComment, Comment>() {
243         @Override
244         public Comment fun(GithubIssueComment comment) {
245           return new GithubComment(comment.getCreatedAt(), comment.getUser().getLogin(), comment.getBodyHtml(),
246                                    comment.getUser().getGravatarId(),
247                                    comment.getUser().getHtmlUrl());
248         }
249       });
250     }
251     finally {
252       connection.close();
253     }
254   }
255
256   @Nullable
257   public String extractId(@NotNull String taskName) {
258     Matcher matcher = myPattern.matcher(taskName);
259     return matcher.find() ? matcher.group(1) : null;
260   }
261
262   @Nullable
263   @Override
264   public Task findTask(@NotNull String id) throws Exception {
265     GithubConnection connection = getConnection();
266     try {
267       return createTask(GithubApiUtil.getIssue(connection, getRepoAuthor(), getRepoName(), id));
268     }
269     finally {
270       connection.close();
271     }
272   }
273
274   @NotNull
275   @Override
276   public BaseRepository clone() {
277     return new GithubRepository(this);
278   }
279
280   @NotNull
281   public String getRepoName() {
282     return myRepoName;
283   }
284
285   public void setRepoName(@NotNull String repoName) {
286     myRepoName = repoName;
287     myPattern = Pattern.compile("(" + StringUtil.escapeToRegexp(repoName) + "\\-\\d+):\\s+");
288   }
289
290   @NotNull
291   public String getRepoAuthor() {
292     return myRepoAuthor;
293   }
294
295   public void setRepoAuthor(@NotNull String repoAuthor) {
296     myRepoAuthor = repoAuthor;
297   }
298
299   @NotNull
300   public String getUser() {
301     return myUser;
302   }
303
304   public void setUser(@NotNull String user) {
305     myUser = user;
306   }
307
308   @Transient
309   @NotNull
310   public String getToken() {
311     return myToken;
312   }
313
314   public void setToken(@NotNull String token) {
315     myToken = token;
316     setUser("");
317   }
318
319   @Tag("token")
320   public String getEncodedToken() {
321     return PasswordUtil.encodePassword(getToken());
322   }
323
324   public void setEncodedToken(String password) {
325     try {
326       setToken(PasswordUtil.decodePassword(password));
327     }
328     catch (NumberFormatException e) {
329       LOG.warn("Can't decode token", e);
330     }
331   }
332
333   private GithubAuthData getAuthData() {
334     return GithubAuthData.createTokenAuth(getUrl(), getToken(), isUseProxy());
335   }
336
337   private GithubConnection getConnection() {
338     return new GithubConnection(getAuthData(), true);
339   }
340
341   @Override
342   public boolean equals(Object o) {
343     if (!super.equals(o)) return false;
344     if (!(o instanceof GithubRepository)) return false;
345
346     GithubRepository that = (GithubRepository)o;
347     if (!Comparing.equal(getRepoAuthor(), that.getRepoAuthor())) return false;
348     if (!Comparing.equal(getRepoName(), that.getRepoName())) return false;
349     if (!Comparing.equal(getToken(), that.getToken())) return false;
350
351     return true;
352   }
353
354   @Override
355   protected int getFeatures() {
356     return super.getFeatures() | BASIC_HTTP_AUTHORIZATION;
357   }
358 }