blob: f47d4e6e36d65f7ec541de4b7d8d035f812fa893 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2005 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.ide;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
/**
* This class stores the information behind the "Launch Workspace" dialog. The
* class is able to read and write itself to a well known configuration file.
*/
public class ChooseWorkspaceData {
/**
* The default max length of the recent workspace mru list.
*/
private static final int RECENT_MAX_LENGTH = 5;
/**
* The directory within the config area that will be used for the
* receiver's persisted data.
*/
private static final String PERS_FOLDER = "org.eclipse.ui.ide"; //$NON-NLS-1$
/**
* The name of the file within the config area that will be used for
* the recever's persisted data.
* @see PERS_FOLDER
*/
private static final String PERS_FILENAME = "recentWorkspaces.xml"; //$NON-NLS-1$
/**
* In the past a file was used to store persist these values. This file was written
* with this value as its protocol identifier.
*/
private static final int PERS_ENCODING_VERSION = 1;
/**
* This is the first version of the encode/decode protocol that uses the config area
* preference store for persistence. The only encoding done is to convert the recent
* workspace list into a comma-separated list.
*/
private static final int PERS_ENCODING_VERSION_CONFIG_PREFS = 2;
private boolean showDialog = true;
private String initialDefault;
private String selection;
private String[] recentWorkspaces;
// xml tags
private static interface XML {
public static final String PROTOCOL = "protocol"; //$NON-NLS-1$
public static final String VERSION = "version"; //$NON-NLS-1$
public static final String ALWAYS_ASK = "alwaysAsk"; //$NON-NLS-1$
public static final String SHOW_DIALOG = "showDialog"; //$NON-NLS-1$
public static final String WORKSPACE = "workspace"; //$NON-NLS-1$
public static final String RECENT_WORKSPACES = "recentWorkspaces"; //$NON-NLS-1$
public static final String MAX_LENGTH = "maxLength"; //$NON-NLS-1$
public static final String PATH = "path"; //$NON-NLS-1$
}
/**
* Creates a new instance, loading persistent data if its found.
*/
public ChooseWorkspaceData(String initialDefault) {
readPersistedData();
setInitialDefault(initialDefault);
}
/**
* Creates a new instance, loading persistent data if its found.
*/
public ChooseWorkspaceData(URL instanceUrl) {
readPersistedData();
if (instanceUrl != null)
setInitialDefault(new File(instanceUrl.getFile()).toString());
}
/**
* Return the folder to be used as a default if no other information
* exists. Does not return null.
*/
public String getInitialDefault() {
if (initialDefault == null)
setInitialDefault(System.getProperty("user.dir") //$NON-NLS-1$
+ File.separator + "workspace"); //$NON-NLS-1$
return initialDefault;
}
/**
* Set this data's initialDefault parameter to a properly formatted version
* of the argument directory string. The proper format is to the platform
* appropriate separator character without meaningless leading or trailing
* separator characters.
*/
private void setInitialDefault(String dir) {
if (dir == null || dir.length() <= 0) {
initialDefault = null;
return;
}
dir = new Path(dir).toOSString();
while (dir.charAt(dir.length() - 1) == File.separatorChar)
dir = dir.substring(0, dir.length() - 1);
initialDefault = dir;
}
/**
* Return the currently selected workspace or null if nothing is selected.
*/
public String getSelection() {
return selection;
}
/**
* Return the currently selected workspace or null if nothing is selected.
*/
public boolean getShowDialog() {
return showDialog;
}
/**
* Return an array of recent workspaces sorted with the most recently used at
* the start.
*/
public String[] getRecentWorkspaces() {
return recentWorkspaces;
}
/**
* The argument workspace has been selected, update the receiver. Does not
* persist the new values.
*/
public void workspaceSelected(String dir) {
// this just stores the selection, it is not inserted and persisted
// until the workspace is actually selected
selection = dir;
}
/**
* Toggle value of the showDialog persistent setting.
*/
public void toggleShowDialog() {
showDialog = !showDialog;
}
/**
* Update the persistent store. Call this function after the currently
* selected value has been found to be ok.
*/
public void writePersistedData() {
// 1. get config pref node
Preferences node = new ConfigurationScope().getNode(IDEWorkbenchPlugin.IDE_WORKBENCH);
// 2. get value for showDialog
node.putBoolean(
IDE.Preferences.SHOW_WORKSPACE_SELECTION_DIALOG,
showDialog);
// 3. use value of numRecent to create proper length array
node.putInt(IDE.Preferences.MAX_RECENT_WORKSPACES,
recentWorkspaces.length);
// move the new selection to the front of the list
if (selection != null) {
String oldEntry = recentWorkspaces[0];
recentWorkspaces[0] = selection;
for (int i = 1; i < recentWorkspaces.length && oldEntry != null; ++i) {
if (selection.equals(oldEntry))
break;
String tmp = recentWorkspaces[i];
recentWorkspaces[i] = oldEntry;
oldEntry = tmp;
}
}
// 4. store values of recent workspaces into array
String encodedRecentWorkspaces = encodeStoredWorkspacePaths(recentWorkspaces);
node.put(IDE.Preferences.RECENT_WORKSPACES,
encodedRecentWorkspaces);
// 5. store the protocol version used to encode the list
node.putInt(IDE.Preferences.RECENT_WORKSPACES_PROTOCOL,
PERS_ENCODING_VERSION_CONFIG_PREFS);
// 6. store the node
try {
node.flush();
} catch (BackingStoreException e) {
// do nothing
}
}
/**
* Look for and read data that might have been persisted from some previous
* run. Leave the receiver in a default state if no persistent data is
* found.
*
* @return true if a file was successfully read and false otherwise
*/
private boolean readPersistedData_file() {
URL persUrl = null;
Location configLoc = Platform.getConfigurationLocation();
if (configLoc != null)
persUrl = getPersistenceUrl(configLoc.getURL(), false);
try {
// inside try to get the safe default creation in the finally
// clause
if (persUrl == null)
return false;
// E.g.,
// <launchWorkspaceData>
// <protocol version="1"/>
// <alwaysAsk showDialog="1"/>
// <recentWorkspaces maxLength="5">
// <workspace path="C:\eclipse\workspace0"/>
// <workspace path="C:\eclipse\workspace1"/>
// </recentWorkspaces>
// </launchWorkspaceData>
Reader reader = new FileReader(persUrl.getFile());
XMLMemento memento = XMLMemento.createReadRoot(reader);
if (memento == null || !compatibleFileProtocol(memento))
return false;
IMemento alwaysAskTag = memento.getChild(XML.ALWAYS_ASK);
showDialog = alwaysAskTag == null ? true : alwaysAskTag.getInteger(
XML.SHOW_DIALOG).intValue() == 1;
IMemento recent = memento.getChild(XML.RECENT_WORKSPACES);
if (recent == null)
return false;
Integer maxLength = recent.getInteger(XML.MAX_LENGTH);
int max = RECENT_MAX_LENGTH;
if (maxLength != null)
max = maxLength.intValue();
IMemento indices[] = recent.getChildren(XML.WORKSPACE);
if (indices == null || indices.length <= 0)
return false;
// if a user has edited maxLength to be shorter than the listed
// indices, accept the list (its tougher for them to retype a long
// list of paths than to update a max number)
max = Math.max(max, indices.length);
recentWorkspaces = new String[max];
for (int i = 0; i < indices.length; ++i) {
String path = indices[i].getString(XML.PATH);
if (path == null)
break;
recentWorkspaces[i] = path;
}
} catch (IOException e) {
// cannot log because instance area has not been set
return false;
} catch (WorkbenchException e) {
// cannot log because instance area has not been set
return false;
} finally {
// create safe default if needed
if (recentWorkspaces == null)
recentWorkspaces = new String[RECENT_MAX_LENGTH];
}
return true;
}
/**
* Return the current (persisted) value of the "showDialog on startup"
* preference. Return the global default if the file cannot be accessed.
*/
public static boolean getShowDialogValue() {
// TODO See the long comment in #readPersistedData -- when the
// transition time is over this method can be changed to
// read the preference directly.
ChooseWorkspaceData data = new ChooseWorkspaceData(""); //$NON-NLS-1$
// return either the value in the file or true, which is the global
// default
return data.readPersistedData() ? data.showDialog : true;
}
/**
* Return the current (persisted) value of the "showDialog on startup"
* preference. Return the global default if the file cannot be accessed.
*/
public static void setShowDialogValue(boolean showDialog) {
// TODO See the long comment in #readPersistedData -- when the
// transition time is over this method can be changed to
// read the preference directly.
ChooseWorkspaceData data = new ChooseWorkspaceData(""); //$NON-NLS-1$
// update the value and write the new settings
data.showDialog = showDialog;
data.writePersistedData();
}
/**
* Look in the config area preference store for the list of recently used
* workspaces.
*
* NOTE: During the transition phase the file will be checked if no config
* preferences are found.
*
* @return true if the values were successfully retrieved and false
* otherwise
*/
public boolean readPersistedData() {
IPreferenceStore store = new ScopedPreferenceStore(
new ConfigurationScope(), IDEWorkbenchPlugin.IDE_WORKBENCH);
// The old way was to store this information in a file, the new is to
// use the configuration area preference store. To help users with the
// transition, this code always looks for values in the preference
// store; they are used if found. If there aren't any related
// preferences, then the file method is used instead. This class always
// writes to the preference store, so the fall-back should be needed no
// more than once per-user, per-configuration.
// This code always sets the value of the protocol to a non-zero value
// (currently at 2). If the value comes back as the default (0), then
// none of the preferences were set, revert to the file method.
int protocol = store
.getInt(IDE.Preferences.RECENT_WORKSPACES_PROTOCOL);
if (protocol == IPreferenceStore.INT_DEFAULT_DEFAULT
&& readPersistedData_file())
return true;
// 2. get value for showDialog
showDialog = store
.getBoolean(IDE.Preferences.SHOW_WORKSPACE_SELECTION_DIALOG);
// 3. use value of numRecent to create proper length array
int max = store
.getInt(IDE.Preferences.MAX_RECENT_WORKSPACES);
max = Math.max(max, RECENT_MAX_LENGTH);
// 4. load values of recent workspaces into array
String workspacePathPref = store
.getString(IDE.Preferences.RECENT_WORKSPACES);
recentWorkspaces = decodeStoredWorkspacePaths(max, workspacePathPref);
return true;
}
/**
* The the list of recent workspaces must be stored as a string in the preference node.
*/
private static String encodeStoredWorkspacePaths(String[] recent) {
StringBuffer buff = new StringBuffer();
String path = null;
for (int i = 0; i < recent.length; ++i) {
if (recent[i] == null)
break;
if (path != null)
buff.append(","); //$NON-NLS-1$
path = recent[i];
buff.append(path);
}
return buff.toString();
}
/**
* The the preference for recent workspaces must be converted from the
* storage string into an array.
*/
private static String[] decodeStoredWorkspacePaths(int max, String prefValue) {
String[] paths = new String[max];
if (prefValue == null || prefValue.length() <= 0)
return paths;
StringTokenizer tokenizer = new StringTokenizer(prefValue, ","); //$NON-NLS-1$
for (int i = 0; i < paths.length && tokenizer.hasMoreTokens(); ++i)
paths[i] = tokenizer.nextToken();
return paths;
}
/**
* Return true if the protocol used to encode the argument memento is
* compatible with the receiver's implementation and false otherwise.
*/
private static boolean compatibleFileProtocol(IMemento memento) {
IMemento protocolMemento = memento.getChild(XML.PROTOCOL);
if (protocolMemento == null)
return false;
Integer version = protocolMemento.getInteger(XML.VERSION);
return version != null && version.intValue() == PERS_ENCODING_VERSION;
}
/**
* The workspace data is stored in the well known file pointed to by the result
* of this method.
* @param create If the directory and file does not exist this parameter
* controls whether it will be created.
* @return An url to the file and null if it does not exist or could not
* be created.
*/
private static URL getPersistenceUrl(URL baseUrl, boolean create) {
if (baseUrl == null)
return null;
try {
// make sure the directory exists
URL url = new URL(baseUrl, PERS_FOLDER);
File dir = new File(url.getFile());
if (!dir.exists() && (!create || !dir.mkdir()))
return null;
// make sure the file exists
url = new URL(dir.toURL(), PERS_FILENAME);
File persFile = new File(url.getFile());
if (!persFile.exists() && (!create || !persFile.createNewFile()))
return null;
return persFile.toURL();
} catch (IOException e) {
// cannot log because instance area has not been set
return null;
}
}
}