/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* 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 io.sarl.eclipse.log;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.eclipse.core.net.proxy.IProxyData;
import org.eclipse.core.net.proxy.IProxyService;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.xtext.util.Strings;
import org.osgi.util.tracker.ServiceTracker;
import io.sarl.eclipse.SARLEclipseConfig;
import io.sarl.eclipse.SARLEclipsePlugin;
/**
* Wizard for submiting an issue to the SARL community.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 0.5
*/
public class SubmitEclipseLogWizard extends Wizard {
private static final String GITHUB_PUBLIC_URL = "https://github.com/sarl/sarl/issues/new"; //$NON-NLS-1$
private static final String GITHUB_URL = "https://api.github.com/repos/sarl/sarl/issues"; //$NON-NLS-1$
//private static final String GITHUB_URL = "https://posttestserver.com/post.php?dump&html"; //$NON-NLS-1$
// Response code for Github API
private static final int RESPONSE_CODE = 201;
private IssueInformationPage detailPage;
private WeakReference<WizardDialog> wizardDialog;
/** Constructor.
*/
public SubmitEclipseLogWizard() {
setDefaultPageImageDescriptor(SARLEclipsePlugin.getDefault().getImageDescriptor(
SARLEclipseConfig.SUBMIT_ISSUE_WIZARD_DIALOG_IMAGE));
setWindowTitle(Messages.SubmitEclipseLogWizard_0);
}
/** Open the wizard.
*
* <p>This method waits until the window is closed by the end user, and then it returns the window's return code;
* otherwise, this method returns immediately. A window's return codes are
* window-specific, although two standard return codes are predefined:
* <code>OK</code> and <code>CANCEL</code>.
* </p>
*
* @param parentShell the parent shell.
* @return the return code.
*/
public static int open(Shell parentShell) {
final SubmitEclipseLogWizard wizard = new SubmitEclipseLogWizard();
final WizardDialog dialog = new WizardDialog(parentShell, wizard);
wizard.setWizardDialog(dialog);
return dialog.open();
}
/** Change the associated wieard dialog.
*
* @param dialog the dialog.
*/
void setWizardDialog(WizardDialog dialog) {
assert dialog != null;
this.wizardDialog = new WeakReference<>(dialog);
}
/** Replies the associated wieard dialog.
*
* @return the dialog.
*/
WizardDialog getWizardDialog() {
final WeakReference<WizardDialog> ref = this.wizardDialog;
return (ref == null) ? null : ref.get();
}
@Override
public void addPages() {
URL url = null;
try {
url = new URL(GITHUB_PUBLIC_URL);
} catch (MalformedURLException exception) {
//
}
this.detailPage = new IssueInformationPage(url);
addPage(this.detailPage);
}
@Override
public boolean performFinish() {
if (this.detailPage.performFinish()) {
try {
final String title = this.detailPage.getIssueTitle();
final String description = this.detailPage.getIssueDescription();
final String login = this.detailPage.getGithubLogin();
final String password = this.detailPage.getGithubPassword();
final Job job = Job.create(Messages.SubmitEclipseLogWizard_0, (monitor) -> {
try {
final SubMonitor subMonitor = SubMonitor.convert(monitor, 2);
subMonitor.setTaskName(Messages.SubmitEclipseLogWizard_1);
final Charset charset = Charset.defaultCharset();
final String content = buildContent(description, charset);
subMonitor.setWorkRemaining(1);
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return submit(charset,
title,
content,
login,
password,
subMonitor.split(1));
} catch (Exception exception) {
return SARLEclipsePlugin.getDefault().createStatus(IStatus.ERROR, exception);
}
});
job.schedule();
return true;
} catch (Exception e) {
ErrorDialog.openError(getShell(), e.getLocalizedMessage(), e.getLocalizedMessage(),
SARLEclipsePlugin.getDefault().createStatus(IStatus.ERROR, e));
}
}
return false;
}
/** Submit the issue to github.
*
* @param charset the encoding character set.
* @param title the issue title.
* @param body the issue description.
* @param login the Github login.
* @param password the Github password.
* @param progress the progress monitor (never {@code null}).
* @return the status.
* @throws Exception when error.
*/
@SuppressWarnings({"checkstyle:magicnumber", "checkstyle:npathcomplexity", "static-method"})
protected IStatus submit(Charset charset, String title, String body, String login, String password, IProgressMonitor progress) throws Exception {
final SubMonitor subMonitor = SubMonitor.convert(progress, 10);
subMonitor.setTaskName(Messages.SubmitEclipseLogWizard_15);
final Gson gson = new GsonBuilder().create();
final String json = gson.toJson(new GithubIssueJson(title, body));
subMonitor.setWorkRemaining(9);
subMonitor.setTaskName(Messages.SubmitEclipseLogWizard_2);
final ServiceTracker<IProxyService, IProxyService> proxyTracker = new ServiceTracker<>(
SARLEclipsePlugin.getDefault().getBundle().getBundleContext(),
IProxyService.class,
null);
proxyTracker.open();
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
final URL url;
try {
final URI uri = new URI(GITHUB_URL);
final IProxyData[] proxyDataForHost = proxyTracker.getService().select(uri);
for (final IProxyData data : proxyDataForHost) {
if (data.getHost() != null) {
System.setProperty("http.proxySet", "true"); //$NON-NLS-1$ //$NON-NLS-2$
System.setProperty("http.proxyHost", data.getHost()); //$NON-NLS-1$
}
if (data.getHost() != null) {
System.setProperty("http.proxyPort", String.valueOf(data //$NON-NLS-1$
.getPort()));
}
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
}
url = uri.toURL();
} finally {
proxyTracker.close();
}
subMonitor.setWorkRemaining(8);
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
subMonitor.setTaskName(Messages.SubmitEclipseLogWizard_3);
final HttpURLConnection con = (HttpURLConnection) url.openConnection();
subMonitor.setWorkRemaining(7);
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
//add request header
subMonitor.setTaskName(Messages.SubmitEclipseLogWizard_4);
con.setRequestMethod("POST"); //$NON-NLS-1$
con.setRequestProperty("User-Agent", "SARL IDE"); //$NON-NLS-1$ //$NON-NLS-2$
// Auth
final String auth = Base64.getEncoder().encodeToString((login + ":" + password).getBytes()); //$NON-NLS-1$
con.setRequestProperty("Authorization", "Basic " + auth); //$NON-NLS-1$ //$NON-NLS-2$
con.setDoOutput(true);
con.setDoInput(true);
subMonitor.setWorkRemaining(6);
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
// Send post request
try (DataOutputStream writer = new DataOutputStream(con.getOutputStream())) {
writer.writeBytes(json);
writer.flush();
}
subMonitor.setWorkRemaining(3);
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
//final int responseCode = con.getResponseCode();
subMonitor.setTaskName(Messages.SubmitEclipseLogWizard_6);
final int responseCode = con.getResponseCode();
final StringBuffer response = new StringBuffer();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
String inputLine;
while ((inputLine = reader.readLine()) != null) {
response.append(inputLine);
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
}
}
subMonitor.setWorkRemaining(0);
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
if (responseCode != RESPONSE_CODE) {
return SARLEclipsePlugin.getDefault().createStatus(IStatus.ERROR,
Messages.SubmitEclipseLogWizard_14,
new Exception(response.toString()));
}
//System.out.println(responseCode);
//Display.getDefault().asyncExec(() -> {
// final Shell shell = new Shell((Shell) null, SWT.SHELL_TRIM);
// shell.setLayout(new FillLayout());
// final Browser browser = new Browser(shell, SWT.NONE);
// browser.setText(response.toString());
// shell.layout();
// shell.open();
return Status.OK_STATUS;
}
/** Build the issue content.
*
* @param description the description of the issue.
* @param charset the charset to use.
* @return the content.
* @throws IOException if the content cannot be built.
*/
@SuppressWarnings("static-method")
protected String buildContent(String description, Charset charset) throws IOException {
final StringBuilder fullContent = new StringBuilder();
// User message
if (!Strings.isEmpty(description)) {
fullContent.append(description);
fullContent.append(Messages.SubmitEclipseLogWizard_8);
}
// Log
final SARLEclipsePlugin plugin = SARLEclipsePlugin.getDefault();
plugin.getLog().log(plugin.createStatus(IStatus.INFO, Messages.SubmitEclipseLogWizard_12));
fullContent.append(Messages.SubmitEclipseLogWizard_9);
final String filename = Platform.getLogFileLocation().toOSString();
final File log = new File(filename);
if (!log.exists()) {
throw new IOException("Unable to find the log file"); //$NON-NLS-1$
}
final List<String> logLines = Files.readLines(log, charset);
int logStartIndex = -1;
for (int i = logLines.size() - 1; logStartIndex == -1 && i >= 0; --i) {
final String line = logLines.get(i);
if (line.startsWith("!SESSION")) { //$NON-NLS-1$
logStartIndex = i;
}
}
for (int i = logStartIndex; i < logLines.size(); ++i) {
fullContent.append(logLines.get(i));
fullContent.append(Messages.SubmitEclipseLogWizard_8);
}
// Properties
fullContent.append(Messages.SubmitEclipseLogWizard_10);
for (final Entry<Object, Object> entry : System.getProperties().entrySet()) {
fullContent.append(Objects.toString(entry.getKey()));
fullContent.append(Messages.SubmitEclipseLogWizard_11);
fullContent.append(Objects.toString(entry.getValue()));
fullContent.append(Messages.SubmitEclipseLogWizard_8);
}
fullContent.append(Messages.SubmitEclipseLogWizard_13);
return fullContent.toString();
}
/** Definition of the Json entries for creating a Github issue.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 0.5
*/
public static class GithubIssueJson {
private final String title;
private final String body;
/** Constructor.
*
* @param title the title of the issue.
* @param body the description of the issue.
*/
public GithubIssueJson(String title, String body) {
this.title = title;
this.body = body;
}
/** Replies the title of the issue.
*
* @return the title.
*/
public String getTitle() {
return this.title;
}
/** Replies the description of the issue.
*
* @return the description.
*/
public String getBody() {
return this.body;
}
}
}