/* * Copyright 2013 Vladimir Rudev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ru.crazyproger.plugins.webtoper; import com.google.gson.Gson; import com.intellij.diagnostic.DiagnosticBundle; import com.intellij.diagnostic.IdeErrorsDialog; import com.intellij.diagnostic.ReportMessages; import com.intellij.diagnostic.errordialog.Attachment; import com.intellij.errorreport.bean.ErrorBean; import com.intellij.ide.DataManager; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.PluginManager; import com.intellij.idea.IdeaLogger; import com.intellij.notification.NotificationType; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.ex.ApplicationInfoEx; import com.intellij.openapi.diagnostic.ErrorReportSubmitter; import com.intellij.openapi.diagnostic.IdeaLoggingEvent; import com.intellij.openapi.diagnostic.SubmittedReportInfo; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.updateSettings.impl.UpdateSettings; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Consumer; import com.intellij.util.SystemProperties; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.awt.*; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class ErrorSubmitter extends ErrorReportSubmitter { public static final String ENCODING = "UTF8"; public static final String SUBMIT_URL = "https://script.google.com/macros/s/AKfycbxhP9JtHwKbvTFfV-AYgRxXyOTOLH0wgySwtv24lPzmcA5ae2ae/exec"; public static final String POST_DELIMITER = "&"; public static final String DONE = "Done"; private static final String HTTP_CONTENT_LENGTH = "Content-Length"; private static final String HTTP_CONTENT_TYPE = "Content-Type"; private static final String HTTP_WWW_FORM = "application/x-www-form-urlencoded"; private static final String HTTP_POST = "POST"; private static final String KEY = "0AoJktm18KZeydGNfUnpTUGJPTjhoYXltZlZISVVOTXc"; // adopted copy-paste from com.intellij.errorreport.itn.ITNProxy private static Map<String, String> createParametersFor(ErrorBean error, String compilationTimestamp, Application application, ApplicationInfoEx appInfo, ApplicationNamesInfo namesInfo, UpdateSettings updateSettings) { @NonNls Map<String, String> params = new HashMap<String, String>(); params.put("os.name", SystemProperties.getOsName()); params.put("java.version", SystemProperties.getJavaVersion()); params.put("java.vm.vendor", SystemProperties.getJavaVmVendor()); params.put("app.name", namesInfo.getProductName()); params.put("app.name.full", namesInfo.getFullProductName()); params.put("app.name.version", appInfo.getVersionName()); params.put("app.eap", Boolean.toString(appInfo.isEAP())); params.put("app.internal", Boolean.toString(application.isInternal())); params.put("app.build", appInfo.getBuild().asString()); params.put("app.version.major", appInfo.getMajorVersion()); params.put("app.version.minor", appInfo.getMinorVersion()); params.put("app.build.date", format(appInfo.getBuildDate())); params.put("app.build.date.release", format(appInfo.getMajorReleaseBuildDate())); params.put("app.compilation.timestamp", compilationTimestamp); params.put("update.channel.status", updateSettings.getSelectedChannelStatus().getCode()); params.put("update.ignored.builds", StringUtil.join(updateSettings.getIgnoredBuildNumbers(), ",")); params.put("plugin.name", error.getPluginName()); params.put("plugin.version", error.getPluginVersion()); params.put("last.action", error.getLastAction()); params.put("error.message", error.getMessage()); params.put("error.stacktrace", error.getStackTrace()); params.put("error.description", error.getDescription()); List<Attachment> attachments = error.getAttachments(); for (int i = 0; i < attachments.size(); i++) { Attachment attachment = attachments.get(i); params.put("attachment.name" + i, attachment.getName()); params.put("attachment.value" + i, attachment.getEncodedBytes()); } return params; } private static String format(Calendar calendar) { return calendar == null ? null : Long.toString(calendar.getTime().getTime()); } private static HttpURLConnection post(URL url, byte[] bytes) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setReadTimeout(10 * 1000); connection.setConnectTimeout(10 * 1000); connection.setRequestMethod(HTTP_POST); connection.setDoInput(true); connection.setDoOutput(true); connection.setRequestProperty(HTTP_CONTENT_TYPE, String.format("%s; charset=%s", HTTP_WWW_FORM, ENCODING)); connection.setRequestProperty(HTTP_CONTENT_LENGTH, Integer.toString(bytes.length)); OutputStream out = new BufferedOutputStream(connection.getOutputStream()); try { out.write(bytes); out.flush(); } finally { out.close(); } return connection; } private static String toJson(Map<String, String> params) throws UnsupportedEncodingException { Gson gson = new Gson(); return gson.toJson(params); } private static byte[] join(java.util.List<Pair<String, String>> params) throws UnsupportedEncodingException { StringBuilder builder = new StringBuilder(); Iterator<Pair<String, String>> it = params.iterator(); while (it.hasNext()) { Pair<String, String> param = it.next(); if (StringUtil.isEmpty(param.first)) throw new IllegalArgumentException(param.toString()); if (StringUtil.isNotEmpty(param.second)) builder.append(param.first).append("=").append(URLEncoder.encode(param.second, ENCODING)); if (it.hasNext()) builder.append(POST_DELIMITER); } return builder.toString().getBytes(); } private static String readFrom(InputStream is) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); int c; while ((c = is.read()) != -1) { out.write(c); } String s = out.toString(); out.close(); return s; } @Override public String getReportActionText() { return WebtoperBundle.message("webtoper.report.action.text"); } @Override public SubmittedReportInfo submit(IdeaLoggingEvent[] events, Component parentComponent) { // obsolete API return new SubmittedReportInfo(null, "0", SubmittedReportInfo.SubmissionStatus.FAILED); } @Override public boolean trySubmitAsync(final IdeaLoggingEvent[] events, final String additionalInfo, Component parentComponent, Consumer<SubmittedReportInfo> consumer) { final DataContext dataContext = DataManager.getInstance().getDataContext(parentComponent); final Project project = PlatformDataKeys.PROJECT.getData(dataContext); Task.Backgroundable task = new Task.Backgroundable(project, DiagnosticBundle.message("title.submitting.error.report")) { @Override public void run(@NotNull ProgressIndicator indicator) { sendReport(getErrorBean(additionalInfo, events[0]), project); } }; if (project == null) { task.run(new EmptyProgressIndicator()); } else { ProgressManager.getInstance().run(task); } return true; } private void sendReport(ErrorBean errorBean, Project project) { @NonNls Map<String, String> errorInfo = createParametersFor(errorBean, IdeaLogger.getOurCompilationTimestamp(), ApplicationManager.getApplication(), (ApplicationInfoEx) ApplicationInfo.getInstance(), ApplicationNamesInfo.getInstance(), UpdateSettings.getInstance()); String json; try { json = toJson(errorInfo); } catch (UnsupportedEncodingException e) { IdeaLogger.getInstance(this.getClass()).error(e); json = "{\"UnsupportedEncodingException\":\"" + e.getMessage() + "\"}"; } List<Pair<String, String>> params = new ArrayList<Pair<String, String>>(); params.add(new Pair<String, String>("data", json)); params.add(new Pair<String, String>("key", KEY)); SubmittedReportInfo.SubmissionStatus status = SubmittedReportInfo.SubmissionStatus.FAILED; HttpURLConnection connection; try { connection = post(new URL(SUBMIT_URL), join(params)); int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { String reply = getReply(connection); if (StringUtil.startsWith(reply, DONE)) { status = SubmittedReportInfo.SubmissionStatus.NEW_ISSUE; } else { IdeaLogger.getInstance(this.getClass()).error("Submission response:" + reply); } } } catch (IOException ignored) { } showNotification(status, project); } private String getReply(HttpURLConnection connection) throws IOException { InputStream is = new BufferedInputStream(connection.getInputStream()); try { return readFrom(is); } finally { is.close(); } } private ErrorBean getErrorBean(String additionalInfo, IdeaLoggingEvent event) { ErrorBean errorBean = new ErrorBean(event.getThrowable(), IdeaLogger.ourLastActionId); errorBean.setDescription(additionalInfo); errorBean.setMessage(event.getMessage()); Throwable t = event.getThrowable(); if (t != null) { final PluginId pluginId = IdeErrorsDialog.findPluginId(t); if (pluginId != null) { final IdeaPluginDescriptor ideaPluginDescriptor = PluginManager.getPlugin(pluginId); if (ideaPluginDescriptor != null && !ideaPluginDescriptor.isBundled()) { errorBean.setPluginName(ideaPluginDescriptor.getName()); errorBean.setPluginVersion(ideaPluginDescriptor.getVersion()); } } } return errorBean; } private void showNotification(final SubmittedReportInfo.SubmissionStatus status, final Project project) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { StringBuilder text = new StringBuilder("<html>"); if (status == SubmittedReportInfo.SubmissionStatus.FAILED) { text.append(DiagnosticBundle.message("error.list.message.submission.failed")); } else { text.append(DiagnosticBundle.message("error.report.gratitude")); } text.append("</html>"); NotificationType type = status == SubmittedReportInfo.SubmissionStatus.FAILED ? NotificationType.ERROR : NotificationType.INFORMATION; ReportMessages.GROUP.createNotification(ReportMessages.ERROR_REPORT, text.toString(), type, null).setImportant(false).notify(project); } }); } }