/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.tools.ui.feedback;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.actions.InstrumentedJob;
import com.google.dart.tools.ui.instrumentation.UIInstrumentationBuilder;
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 java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Submits feedback as a POST to an appengine back-end.
* <p>
* The flow here is:
* <li>ping the server. this ensures that there is a route to the server, and resolves any http
* forwarding
* <li>submit feedback
*/
public class FeedbackSubmissionJob extends InstrumentedJob {
private final FeedbackWriter writer;
public FeedbackSubmissionJob(FeedbackWriter writer) {
super(FeedbackMessages.FeedbackSubmissionJob_sending_feedback_job_label);
this.writer = writer;
}
public String[] getBaseUrlDomains() {
String domains = getResourceString("baseURLs"); //$NON-NLS-1$
return FeedbackUtils.splitString(domains, ",", true); //$NON-NLS-1$
}
public String[] getFeedbackSubmissionUrls() {
String urls = getResourceString("feedbackURLs"); //$NON-NLS-1$
return FeedbackUtils.splitString(urls, ",", true); //$NON-NLS-1$
}
@Override
protected IStatus doRun(IProgressMonitor monitor, UIInstrumentationBuilder instrumentation) {
final URL serverURL = pingServer();
// If we can't reach the server, return an error code
if (serverURL == null) {
//TODO (pquitslund): consider a retry
return DartToolsPlugin.createErrorStatus(FeedbackMessages.FeedbackSubmissionJob_unreachable_server_error_text);
}
try {
// Attempt to upload.
//long startTime = System.currentTimeMillis();
submitFeedback(serverURL, monitor, instrumentation);
// if (Activator.DEBUG) {
// Activator.log((dataLength / 1024) + "K feedback data uploaded in "
// + (System.currentTimeMillis() - startTime) + " ms.");
// }
} catch (Throwable exception) {
// this should not be show to the user
DartToolsPlugin.log("error sending feedback", exception);
monitor.done();
}
return Status.OK_STATUS;
}
private String getResourceString(String key) {
return Platform.getResourceString(DartToolsPlugin.getDefault().getBundle(), "%" + key); //$NON-NLS-1$
}
/**
* Ping the usage profiler server - make sure that we have a network connection and the server is
* reachable. This method returns the server URL, taking into account any redirects.
*
* @return the URL to use if the server is reachable, null otherwise
*/
private URL pingServer() {
String[] urls = getFeedbackSubmissionUrls();
for (int i = 0; i < urls.length; i++) {
try {
String url = urls[i];
URL respondedUrl = pingUrl(url);
if (respondedUrl != null) {
if (verifyUrl(respondedUrl)) {
return respondedUrl;
}
}
} catch (IOException ioe) {
// ignore this -
}
}
return null;
}
private URL pingUrl(String urlText) throws IOException {
URL url = new URL(urlText);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(true);
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return null;
}
connection.disconnect();
URL redirectedURL = connection.getURL();
return redirectedURL;
}
private void safeClose(InputStream in) {
try {
in.close();
} catch (IOException exception) {
}
}
private void safeClose(OutputStream out) {
try {
out.close();
} catch (IOException exception) {
}
}
private void submitFeedback(URL serverURL, IProgressMonitor monitor,
UIInstrumentationBuilder instrumentation) throws IOException {
submitFeedback_text(serverURL, monitor, instrumentation);
if (writer.sendScreenshotData()) {
submitFeedback_png(serverURL, monitor);
}
}
private int submitFeedback_png(URL serverURL, IProgressMonitor monitor) throws IOException {
byte[] data = writer.getImageByteArray();
monitor.beginTask(
FeedbackMessages.FeedbackSubmissionJob_job_starting_progress_text_png,
data.length);
HttpURLConnection connection = (HttpURLConnection) serverURL.openConnection();
connection.setInstanceFollowRedirects(false);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST"); //$NON-NLS-1$
connection.setFixedLengthStreamingMode(data.length);
connection.setUseCaches(false);
connection.setAllowUserInteraction(false);
connection.setRequestProperty("Connection", "close"); //$NON-NLS-1$ //$NON-NLS-2$
connection.setRequestProperty("Content-Type", "image/png"); //$NON-NLS-1$ //$NON-NLS-2$
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
final int packetSize = 1500;
for (int i = 0; i < data.length; i += packetSize) {
int writeSize = Math.min(data.length - i, packetSize);
out.write(data, i, writeSize);
monitor.worked(writeSize);
}
out.flush();
safeClose(out);
InputStream in = connection.getInputStream();
byte[] temp = new byte[8192];
String str = null;
int len = in.read(temp);
if (len > 0) {
str = new String(temp, 0, len);
}
safeClose(in);
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
DartToolsPlugin.log("Error sending feedback:" + connection.getResponseMessage() + "\n" + str);//$NON-NLS-1$ //$NON-NLS-2$
}
connection.disconnect();
return data.length;
}
private int submitFeedback_text(URL serverURL, IProgressMonitor monitor,
UIInstrumentationBuilder instrumentation) throws IOException {
instrumentation.metric("SubmitText", "Start");
ByteArrayOutputStream bout = new ByteArrayOutputStream(4096);
writer.writeFeedback(bout);
byte[] data = bout.toByteArray();
String crc32Hash = FeedbackUtils.calculateCRC32(data);
monitor.beginTask(
FeedbackMessages.FeedbackSubmissionJob_job_starting_progress_text_text,
data.length);
HttpURLConnection connection = (HttpURLConnection) serverURL.openConnection();
connection.setInstanceFollowRedirects(false);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST"); //$NON-NLS-1$
connection.setFixedLengthStreamingMode(data.length);
connection.setUseCaches(false);
connection.setAllowUserInteraction(false);
connection.setRequestProperty("Connection", "close"); //$NON-NLS-1$ //$NON-NLS-2$
connection.setRequestProperty("Content-Type", "text/plain"); //$NON-NLS-1$ //$NON-NLS-2$
if (crc32Hash != null) {
connection.setRequestProperty("X-Signature", crc32Hash); //$NON-NLS-1$
}
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
final int packetSize = 1500;
for (int i = 0; i < data.length; i += packetSize) {
int writeSize = Math.min(data.length - i, packetSize);
out.write(data, i, writeSize);
monitor.worked(writeSize);
}
out.flush();
safeClose(out);
InputStream in = connection.getInputStream();
byte[] temp = new byte[8192];
String str = null;
int len = in.read(temp);
if (len > 0) {
str = new String(temp, 0, len);
}
safeClose(in);
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
DartToolsPlugin.log("Error sending feedback:" + connection.getResponseMessage() + "\n" + str);//$NON-NLS-1$ //$NON-NLS-2$
}
connection.disconnect();
instrumentation.metric("data.length", data.length);
instrumentation.data("Feedback", bout.toString());
return data.length;
}
/**
* Verify that the URL is in the correct domain - it may have come from a URL redirection.
*
* @param url
* @return
*/
private boolean verifyUrl(URL url) {
String urlHost = url.getHost();
if (urlHost == null) {
return false;
}
// Make sure the url is in the correct domain - i.e. that it ends with "google.com".
String[] domains = getBaseUrlDomains();
for (int i = 0; i < domains.length; i++) {
if (urlHost.endsWith("." + domains[i])) { //$NON-NLS-1$
return true;
}
}
return false;
}
}