/*******************************************************************************
* 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.core.runtime.preferences.InstanceScope;
import org.eclipse.epp.usagedata.internal.gathering.UsageDataCaptureActivator;
import org.eclipse.epp.usagedata.internal.gathering.settings.UsageDataCaptureSettings;
import org.eclipse.epp.usagedata.internal.recording.UsageDataRecordingActivator;
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;
import org.osgi.service.prefs.BackingStoreException;
/**
* 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
*
* @author Mohsen Vakilian, nchen - Added an option for collecting but not uploading
*
*/
public class UsageDataRecordingSettings implements UploadSettings {
private static final String DEFAULT_ID= "unknown"; //$NON-NLS-1$
private static final String UPLOAD_FILE_PREFIX= "upload"; //$NON-NLS-1$
public static final String UPLOAD_PERIOD_KEY= UsageDataRecordingActivator.PLUGIN_ID + ".period"; //$NON-NLS-1$
public static final String LAST_UPLOAD_KEY= UsageDataRecordingActivator.PLUGIN_ID + ".last-upload"; //$NON-NLS-1$
public static final String ASK_TO_UPLOAD_KEY= UsageDataRecordingActivator.PLUGIN_ID + ".ask"; //$NON-NLS-1$
public static final String LOG_SERVER_ACTIVITY_KEY= UsageDataRecordingActivator.PLUGIN_ID + ".log-server"; //$NON-NLS-1$
public static final String FILTER_ECLIPSE_BUNDLES_ONLY_KEY= UsageDataRecordingActivator.PLUGIN_ID + ".filter-eclipse-only"; //$NON-NLS-1$
public static final String FILTER_PATTERNS_KEY= UsageDataRecordingActivator.PLUGIN_ID + ".filter-patterns"; //$NON-NLS-1$
static final String UPLOAD_URL_KEY= UsageDataRecordingActivator.PLUGIN_ID + ".upload-url"; //$NON-NLS-1$
public static final int PERIOD_REASONABLE_MINIMUM= 15 * 60 * 1000; // 15 minutes
static final int UPLOAD_PERIOD_DEFAULT= 5 * 24 * 60 * 60 * 1000; // five days
static final String UPLOAD_URL_DEFAULT= "http://udc.eclipse.org/upload.php"; //$NON-NLS-1$
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.
UsageDataRecordingActivator.getDefault().log(IStatus.WARNING,
e, "The UsageDataUploader cannot parse the %1$s system property (\"%2$s\"", UPLOAD_PERIOD_KEY, value); //$NON-NLS-1$
}
} 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);
UsageDataRecordingActivator.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"); //$NON-NLS-1$
}
/**
* 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"); //$NON-NLS-1$
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")), "." + UsageDataRecordingActivator.PLUGIN_ID //$NON-NLS-1$ //$NON-NLS-2$
+ ".userId"); //$NON-NLS-1$
}
/**
* 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(), "." //$NON-NLS-1$
+ UsageDataRecordingActivator.PLUGIN_ID + ".workspaceId"); //$NON-NLS-1$
}
/**
* 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)); //$NON-NLS-1$
}
/**
* 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());
UsageDataRecordingActivator.getDefault().savePluginPreferences();
}
/**
* <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) {
UsageDataRecordingActivator.getDefault().log(IStatus.WARNING, e, "Cannot read the existing id from %1$s; using the default.", file.toString()); //$NON-NLS-1$
}
private IPreferenceStore getPreferencesStore() {
return UsageDataRecordingActivator.getDefault().getPreferenceStore();
}
private File getWorkingDirectory() {
return UsageDataRecordingActivator.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)); //$NON-NLS-1$
} 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);
UsageDataRecordingActivator.getDefault().savePluginPreferences();
}
private UsageDataCaptureSettings getCaptureSettings() {
return org.eclipse.epp.usagedata.internal.gathering.UsageDataCaptureActivator.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);
UsageDataRecordingActivator.getDefault().savePluginPreferences();
}
public void setEnabled(boolean value) {
getCaptureSettings().setEnabled(value);
UsageDataRecordingActivator.getDefault().savePluginPreferences();
}
public void dispose() {
filter.dispose();
}
public String getUserAgent() {
return "Eclipse UDC/" + UsageDataRecordingActivator.getDefault().getBundle().getHeaders().get("Bundle-Version"); //$NON-NLS-1$ //$NON-NLS-2$
}
public String getUploadUrl() {
if (System.getProperties().containsKey(UPLOAD_URL_KEY)) {
return System.getProperty(UPLOAD_URL_KEY);
}
return UPLOAD_URL_DEFAULT;
}
/////////////////
//CODINGSPECTATOR
/////////////////
public static final String COLLECT_BUT_NEVER_UPLOAD_KEY= UsageDataRecordingActivator.PLUGIN_ID + ".collectbutneverupload"; //$NON-NLS-1$
public void setCollectButNeverUpload(boolean value) {
getPreferencesStore().setValue(COLLECT_BUT_NEVER_UPLOAD_KEY, value);
try {
new InstanceScope().getNode(UsageDataCaptureActivator.PLUGIN_ID).flush();
} catch (BackingStoreException e) {
UsageDataCaptureActivator.getDefault().logException("Unable to flush preferences for " + UsageDataCaptureActivator.PLUGIN_ID, e);
}
}
public boolean isCollectButNeverUpload() {
if (getPreferencesStore().contains(COLLECT_BUT_NEVER_UPLOAD_KEY))
return getPreferencesStore().getBoolean(COLLECT_BUT_NEVER_UPLOAD_KEY);
else
return false;
}
}