blob: 9644345ebfc02c64a6d12f7b5c17f4ace88f127b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007 The Eclipse Foundation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* The Eclipse Foundation - initial API and implementation
*******************************************************************************/
package org.eclipse.epp.usagedata.internal.recording.settings;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.UUID;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.epp.usagedata.internal.gathering.settings.UsageDataCaptureSettings;
import org.eclipse.epp.usagedata.internal.recording.Activator;
import org.eclipse.epp.usagedata.internal.recording.filtering.PreferencesBasedFilter;
import org.eclipse.epp.usagedata.internal.recording.filtering.UsageDataEventFilter;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.PlatformUI;
/**
* This class provides a convenient location to find the settings
* for this bundle. Some settings are in the preferences; others
* are found in system properties. Still more are simply provided
* as constant values.
*
* @author Wayne Beaton
*
*/
public class UsageDataRecordingSettings implements UploadSettings {
private static final String DEFAULT_ID = "unknown";
private static final String UPLOAD_FILE_PREFIX = "upload";
public static final String UPLOAD_PERIOD_KEY = Activator.PLUGIN_ID + ".period";
public static final String LAST_UPLOAD_KEY = Activator.PLUGIN_ID + ".last-upload";
public static final String ASK_TO_UPLOAD_KEY = Activator.PLUGIN_ID + ".ask";
public static final String LOG_SERVER_ACTIVITY_KEY = Activator.PLUGIN_ID + ".log-server";
public static final String FILTER_ECLIPSE_BUNDLES_ONLY_KEY = Activator.PLUGIN_ID + ".filter-eclipse-only";
public static final String FILTER_PATTERNS_KEY = Activator.PLUGIN_ID + ".filter-patterns";
public static final int PERIOD_REASONABLE_MINIMUM = 60000;
// TODO 15 * 60 * 1000; // 15 minutes
static final int UPLOAD_PERIOD_DEFAULT = 1 * 24 * 60 * 60 * 1000;
// TODO 5 * 24 * 60 * 60 * 1000; // five days
static final String UPLOAD_URL_DEFAULT = "http://cortez.eclipse.org/upload.php";
static final boolean ASK_TO_UPLOAD_DEFAULT = true;
private PreferencesBasedFilter filter = new PreferencesBasedFilter();
/**
* First if the system property {@value #UPLOAD_PERIOD_KEY} has been set,
* use that value. Next, check to see if there is a value stored (same key)
* in the preferences store. Finally, use the default value,
* {@value #UPLOAD_PERIOD_DEFAULT}. If the obtained value is deemed to be
* unreasonable (less than {@value #PERIOD_REASONABLE_MINIMUM}), that a
* reasonable minimum value is returned instead.
*
* @return
*/
public long getPeriodBetweenUploads() {
long period = 0L;
if (System.getProperties().containsKey(UPLOAD_PERIOD_KEY)) {
String value = System.getProperty(UPLOAD_PERIOD_KEY);
try {
period = Long.valueOf(value);
} catch (NumberFormatException e) {
// If we can't get it from this source, we'll pick it up some
// other way. Long the problem and move on.
Activator.getDefault().log(IStatus.WARNING,
e, "The UsageDataUploader cannot parse the %1$s system property (\"%2$s\"", UPLOAD_PERIOD_KEY, value);
}
} else if (getPreferencesStore().contains(UPLOAD_PERIOD_KEY)) {
period = getPreferencesStore().getLong(UPLOAD_PERIOD_KEY);
} else {
period = UPLOAD_PERIOD_DEFAULT;
}
if (period < PERIOD_REASONABLE_MINIMUM)
period = PERIOD_REASONABLE_MINIMUM;
return period;
}
/**
* The last upload time is stored in the preferences. If no value is
* currently set, the current time is used (and is stored for the next time
* we're asked). Time is expressed in milliseconds. There is no mechanism
* for overriding this value.
*
* @return
*/
public long getLastUploadTime() {
if (getPreferencesStore().contains(LAST_UPLOAD_KEY)) {
return getPreferencesStore().getLong(LAST_UPLOAD_KEY);
}
long period = System.currentTimeMillis();
getPreferencesStore().setValue(LAST_UPLOAD_KEY, period);
Activator.getDefault().savePluginPreferences();
return period;
}
/**
* This method answers <code>true</code> if enough time has passed since
* the last upload to warrant starting a new one. If an upload has not yet
* occurred, it answers <code>true</code> if the required amount of time
* has passed since the first time this method was called. It answers
* <code>false</code> otherwise.
*
* @return <code>true</code> if it is time to upload; <code>false</code>
* otherwise.
*/
public boolean isTimeToUpload() {
if (PlatformUI.getWorkbench().isClosing())
return false;
return System.currentTimeMillis() - getLastUploadTime() > getPeriodBetweenUploads();
}
/**
* This method returns the {@link File} where usage data events should be persisted.
*
* @return the {@link File} where usage data events are persisted.
*/
public File getEventFile() {
return new File(getWorkingDirectory(), "usagedata.csv");
}
/**
* When it's time to start uploading the usage data, the file that's used
* to persist the data is moved (renamed) and a new file is created. The
* moved file is then uploaded to the server. This method finds an appropriate
* destination for the moved file. The destination {@link File} will be in the
* bundle's state location, but will not actually exist in the file system.
*
* @return a destination {@link File} for the move operation.
*/
public File computeDestinationFile() {
int index = 0;
File parent = getWorkingDirectory();
File file = null;
// TODO Unlikely (impossible?), but what if this spins forever.
while (true) {
file = new File(parent, UPLOAD_FILE_PREFIX + index++ + ".csv");
if (!file.exists())
return file;
}
}
/**
* This method returns an identifier for the workstation. This value
* is common to all workspaces on a single machine. The value
* is persisted (if possible) in a hidden file in the users's working
* directory. If an existing file cannot be read, or a new file cannot
* be written, this method returns "unknown".
*
* @return an identifier for the workstation.
*/
public String getUserId() {
return getExistingOrGenerateId(new File(System.getProperty("user.home")), "." + Activator.PLUGIN_ID
+ ".userId");
}
/**
* This method returns an identifier for the workspace. This value is unique
* to the workspace. It is persisted (if possible) in a hidden file in the bundle's
* state location.If an existing file cannot be read, or a new file cannot
* be written, this method returns "unknown".
*
* @return an identifier for the workspace.
*/
public String getWorkspaceId() {
return getExistingOrGenerateId(getWorkingDirectory(), "."
+ Activator.PLUGIN_ID + ".workspaceId");
}
/**
* This method answers whether or not we want to ask the server to
* provide a log of activity. This method only answers <code>true</code>
* if the "{@value #LOG_SERVER_ACTIVITY_KEY}" system property is set
* to "true". This is mostly useful for debugging.
*
* @return true if we're logging, false otherwise.
*
* @see UploadSettings#isLoggingServerActivity()
*/
public boolean isLoggingServerActivity() {
return "true".equals(System.getProperty(LOG_SERVER_ACTIVITY_KEY));
}
/**
* This method answers an array containing the files that are available
* for uploading.
*
* @return
*/
public File[] getUsageDataUploadFiles() {
return getWorkingDirectory().listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(UPLOAD_FILE_PREFIX);
}
});
}
/**
* This method sets the {@value #LAST_UPLOAD_KEY} property to the
* current time.
*/
public void setLastUploadTime() {
getPreferencesStore().setValue(LAST_UPLOAD_KEY, System.currentTimeMillis());
}
/**
* <p>
* This method either finds an existing id or generates a new one. The id is
* stored in file system at the given path and file. If the file exists, the
* id is extracted from it. If the file does not exist, or if an id cannot
* be determined from its contents, a new id is generated and then stored in
* the file. If the file cannot be read or written (i.e. an IOException
* occurs), the operation is aborted and "unknown" is returned.
* </p>
*
* @param directory
* the directory that will contain the stored id.
* @param fileName
* name of the file containing the id.
* @return a globally unique id.
*/
private String getExistingOrGenerateId(File directory, String fileName) {
if (!directory.exists()) return DEFAULT_ID;
if (!directory.isDirectory()) {
} // TODO Think of something else
File file = new File(directory, fileName);
if (file.exists()) {
FileReader reader = null;
try {
reader = new FileReader(file);
char[] buffer = new char[256];
int count = reader.read(buffer);
// TODO what if the file can't be read, or if there is no
// content?
return new String(buffer, 0, count);
} catch (IOException e) {
handleCannotReadFileException(file, e);
return DEFAULT_ID;
} finally {
close(reader);
}
} else {
String id = UUID.randomUUID().toString();
FileWriter writer = null;
try {
// TODO What if there is a collection with another process?
writer = new FileWriter(file);
writer.write(id);
return id;
} catch (IOException e) {
handleCannotReadFileException(file, e);
return DEFAULT_ID;
} finally {
close(writer);
}
}
}
private void handleCannotReadFileException(File file, IOException e) {
Activator.getDefault().log(IStatus.WARNING, e, "Cannot read the existing id from %1$s; using the default.", file.toString());
}
private IPreferenceStore getPreferencesStore() {
return Activator.getDefault().getPreferenceStore();
}
private File getWorkingDirectory() {
return Activator.getDefault().getStateLocation().toFile();
}
/**
* Convenience method for closing a {@link Writer} that could possibly be
* <code>null</code>.
*
* @param writer
* the {@link Writer} to close.
*/
private void close(Writer writer) {
if (writer == null)
return;
try {
writer.close();
} catch (IOException e) {
// TODO Handle exception
}
}
/**
* Convenience method for closing a {@link Reader} that could possibly be
* <code>null</code>.
*
* @param reader
* the {@link Reader} to close.
*/
private void close(Reader reader) {
if (reader == null)
return;
try {
reader.close();
} catch (IOException e) {
// TODO Handle exception
}
}
public boolean shouldAskBeforeUploading() {
if (System.getProperties().containsKey(ASK_TO_UPLOAD_KEY)) {
return "true".equals(System.getProperty(ASK_TO_UPLOAD_KEY));
} else if (getPreferencesStore().contains(ASK_TO_UPLOAD_KEY)) {
return getPreferencesStore().getBoolean(ASK_TO_UPLOAD_KEY);
} else {
return ASK_TO_UPLOAD_DEFAULT;
}
}
/* (non-Javadoc)
* @see org.eclipse.epp.usagedata.internal.recording.settings.UploadSettings#getFilter()
*/
public UsageDataEventFilter getFilter() {
return filter;
}
/* (non-Javadoc)
* @see org.eclipse.epp.usagedata.internal.recording.settings.UploadSettings#hasUserAcceptedTermsOfUse()
*/
public boolean hasUserAcceptedTermsOfUse() {
return getCaptureSettings().hasUserAcceptedTermsOfUse();
}
public void setUserAcceptedTermsOfUse(boolean value) {
getCaptureSettings().setUserAcceptedTermsOfUse(value);
}
private UsageDataCaptureSettings getCaptureSettings() {
return org.eclipse.epp.usagedata.internal.gathering.Activator.getDefault().getSettings();
}
/* (non-Javadoc)
* @see org.eclipse.epp.usagedata.internal.recording.settings.UploadSettings#isEnabled()
*/
public boolean isEnabled() {
return getCaptureSettings().isEnabled();
}
public void setAskBeforeUploading(boolean value) {
getPreferencesStore().setValue(ASK_TO_UPLOAD_KEY, value);
}
public void setEnabled(boolean value) {
getCaptureSettings().setEnabled(value);
}
public void dispose() {
filter.dispose();
}
public String getUserAgent() {
return "Eclipse UDC/" + Activator.getDefault().getBundle().getHeaders().get("Bundle-Version");
}
public String getUploadUrl() {
return UPLOAD_URL_DEFAULT;
}
}