1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package org.jf.smalidea.errorReporting; 18 19import com.google.common.io.CharStreams; 20import com.google.gson.Gson; 21import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; 22import com.intellij.ide.plugins.PluginManager; 23import com.intellij.openapi.extensions.PluginId; 24import com.intellij.openapi.progress.ProgressIndicator; 25import com.intellij.openapi.progress.Task; 26import com.intellij.openapi.project.Project; 27import com.intellij.util.Consumer; 28import org.jetbrains.annotations.NotNull; 29import org.jetbrains.annotations.Nullable; 30 31import java.io.IOException; 32import java.io.InputStream; 33import java.io.InputStreamReader; 34import java.io.OutputStream; 35import java.net.HttpURLConnection; 36import java.net.URL; 37import java.nio.charset.Charset; 38import java.util.LinkedHashMap; 39import java.util.Map; 40 41public class GithubFeedbackTask extends Task.Backgroundable { 42 private final Consumer<String> myCallback; 43 private final Consumer<Exception> myErrorCallback; 44 private final Map<String, String> myParams; 45 46 public GithubFeedbackTask(@Nullable Project project, 47 @NotNull String title, 48 boolean canBeCancelled, 49 Map<String, String> params, 50 final Consumer<String> callback, 51 final Consumer<Exception> errorCallback) { 52 super(project, title, canBeCancelled); 53 54 myParams = params; 55 myCallback = callback; 56 myErrorCallback = errorCallback; 57 } 58 59 @Override 60 public void run(@NotNull ProgressIndicator indicator) { 61 indicator.setIndeterminate(true); 62 try { 63 String token = sendFeedback(myParams); 64 myCallback.consume(token); 65 } 66 catch (Exception e) { 67 myErrorCallback.consume(e); 68 } 69 } 70 71 private static String getToken() { 72 InputStream stream = GithubFeedbackTask.class.getClassLoader().getResourceAsStream("token"); 73 if (stream == null) { 74 return null; 75 } 76 try { 77 return CharStreams.toString(new InputStreamReader(stream, "UTF-8")); 78 } catch (IOException ex) { 79 return null; 80 } 81 } 82 83 public static String sendFeedback(Map<String, String> environmentDetails) throws IOException { 84 String url = "https://api.github.com/repos/JesusFreke/smalidea-issues/issues"; 85 String userAgent = "smalidea plugin"; 86 87 IdeaPluginDescriptorImpl pluginDescriptor = 88 (IdeaPluginDescriptorImpl) PluginManager.getPlugin(PluginId.getId("org.jf.smalidea")); 89 90 if (pluginDescriptor != null) { 91 String name = pluginDescriptor.getName(); 92 String version = pluginDescriptor.getVersion(); 93 userAgent = name + " (" + version + ")"; 94 } 95 96 HttpURLConnection httpURLConnection = connect(url); 97 httpURLConnection.setDoOutput(true); 98 httpURLConnection.setRequestMethod("POST"); 99 httpURLConnection.setRequestProperty("User-Agent", userAgent); 100 httpURLConnection.setRequestProperty("Content-Type", "application/json"); 101 102 String token = getToken(); 103 if (token != null) { 104 httpURLConnection.setRequestProperty("Authorization", "token " + token); 105 } 106 OutputStream outputStream = httpURLConnection.getOutputStream(); 107 108 try { 109 outputStream.write(convertToGithubIssueFormat(environmentDetails)); 110 } finally { 111 outputStream.close(); 112 } 113 114 int responseCode = httpURLConnection.getResponseCode(); 115 if (responseCode != 201) { 116 throw new RuntimeException("Expected HTTP_CREATED (201), obtained " + responseCode); 117 } 118 119 return Long.toString(System.currentTimeMillis()); 120 } 121 122 private static byte[] convertToGithubIssueFormat(Map<String, String> environmentDetails) { 123 LinkedHashMap<String, String> result = new LinkedHashMap<String, String>(5); 124 result.put("title", "[auto-generated] Crash in plugin"); 125 result.put("body", generateGithubIssueBody(environmentDetails)); 126 127 return ((new Gson()).toJson(result)).getBytes(Charset.forName("UTF-8")); 128 } 129 130 private static String generateGithubIssueBody(Map<String, String> body) { 131 String errorDescription = body.get("error.description"); 132 if (errorDescription == null) { 133 errorDescription = ""; 134 } 135 body.remove("error.description"); 136 137 String errorMessage = body.get("error.message"); 138 if (errorMessage == null || errorMessage.isEmpty()) { 139 errorMessage = "invalid error"; 140 } 141 body.remove("error.message"); 142 143 String stackTrace = body.get("error.stacktrace"); 144 if (stackTrace == null || stackTrace.isEmpty()) { 145 stackTrace = "invalid stacktrace"; 146 } 147 body.remove("error.stacktrace"); 148 149 String result = ""; 150 151 if (!errorDescription.isEmpty()) { 152 result += errorDescription + "\n\n"; 153 } 154 155 for (Map.Entry<String, String> entry : body.entrySet()) { 156 result += entry.getKey() + ": " + entry.getValue() + "\n"; 157 } 158 159 result += "\n```\n" + stackTrace + "\n```\n"; 160 161 result += "\n```\n" + errorMessage + "\n```"; 162 163 return result; 164 } 165 166 private static HttpURLConnection connect(String url) throws IOException { 167 HttpURLConnection connection = (HttpURLConnection) ((new URL(url)).openConnection()); 168 connection.setConnectTimeout(5000); 169 connection.setReadTimeout(5000); 170 return connection; 171 } 172} 173