fix issues with mode/lib/tool manager, require restart msg, also #1782

This commit is contained in:
Ben Fry
2013-08-07 16:06:55 -04:00
parent 55e99d68f3
commit 0bca425db3
10 changed files with 242 additions and 112 deletions
+24 -11
View File
@@ -198,9 +198,19 @@ public class Base {
}
log("about to create base..."); //$NON-NLS-1$
Base base = new Base(args);
// Prevent more than one copy of the PDE from running.
SingleInstance.startServer(base);
try {
Base base = new Base(args);
// Prevent more than one copy of the PDE from running.
SingleInstance.startServer(base);
} catch (Exception e) {
// Catch-all to hopefully pick up some of the weirdness we've been
// running into lately.
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
Base.showError("We're off on the wrong foot",
"An error occurred during startup.\n" + sw, e);
}
log("done creating base..."); //$NON-NLS-1$
}
}
@@ -324,7 +334,7 @@ public class Base {
}
public Base(String[] args) {
public Base(String[] args) throws Exception {
// // Get the sketchbook path, and make sure it's set properly
// determineSketchbookFolder();
@@ -338,7 +348,7 @@ public class Base {
// removeDir(contrib.getFolder());
// }
// }
ContributionManager.deleteFlagged();
ContributionManager.cleanup();
buildCoreModes();
rebuildContribModes();
@@ -354,7 +364,7 @@ public class Base {
} else {
for (Mode m : getModeList()) {
if (m.getIdentifier().equals(lastModeIdentifier)) {
log("Setting next mode to " + lastModeIdentifier); //$NON-NLS-1$
logf("Setting next mode to {0}.", lastModeIdentifier); //$NON-NLS-1$
nextMode = m;
}
}
@@ -2584,18 +2594,21 @@ public class Base {
/**
* Remove a File object (a file or directory) from the system by placing it
* in the Trash or Recycle Bin (if available) or simply deleting it (if not).
* Delete a file or directory in a platform-specific manner. Removes a File
* object (a file or directory) from the system by placing it in the Trash
* or Recycle Bin (if available) or simply deleting it (if not).
*
* When the file/folder is on another file system, it may simply be removed
* immediately, without additional warning. So only use this if you want to,
* you know, "remove" the subject in question.
* you know, "delete" the subject in question.
*
* NOTE: Not yet tested nor ready for prime-time.
*
* @param file the victim
* @param file the victim (a directory or individual file)
* @return true if all ends well
* @throws IOException what went wrong
*/
static public boolean removeFile(File file) throws IOException {
static public boolean platformDelete(File file) throws IOException {
return platform.deleteFile(file);
}
@@ -128,19 +128,25 @@ class AvailableContribution extends Contribution {
// backup old if needed, then move things into place and reload.
installedContrib =
newContrib.copyAndLoad(editor, confirmReplace, status);
if (newContrib != null && type.requiresRestart()) {
installedContrib.setRestartFlag();
//status.setMessage("Restart Processing to finish the installation.");
}
// 3. Delete the newContrib, do a garbage collection, hope and pray
// that Java will unlock the temp folder on Windows now
newContrib = null;
System.gc();
// we'll even give it a second to finish up ... because file ops are
// just that flaky on Windows.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
if (Base.isWindows()) {
// we'll even give it a second to finish up ... because file ops are
// just that flaky on Windows.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 4. Okay, now actually delete that temp folder
@@ -133,21 +133,26 @@ abstract public class Contribution {
abstract public boolean isInstalled();
/**
* Returns true if the type of contribution requires the PDE to restart
* when being added or removed.
*/
public boolean requiresRestart() {
return getType() == ContributionType.TOOL || getType() == ContributionType.MODE;
}
// /**
// * Returns true if the type of contribution requires the PDE to restart
// * when being added or removed.
// */
// public boolean requiresRestart() {
// return getType() == ContributionType.TOOL || getType() == ContributionType.MODE;
// }
/** Overridden by InstalledContribution. */
boolean isDeletionFlagged() {
boolean isRestartFlagged() {
return false;
}
/** Overridden by LocalContribution. */
boolean isDeletionFlagged() {
return false;
}
/**
* @return the list of categories that this contribution is part of
* (e.g. "Typography / Geometry"). "Unknown" if the category null.
@@ -36,14 +36,11 @@ import processing.app.Base;
// The "Scrollable" implementation and its methods here take care of preventing
// the scrolling area from running exceptionally slowly. Not sure why they're
// necessary in the first place, however; seems like odd behavior.
// necessary in the first place, however; seems like odd behavior.
// It also allows the description text in the panels to wrap properly.
public class ContributionListPanel extends JPanel implements Scrollable, ContributionChangeListener {
static public final String DELETION_MESSAGE =
"<i>This tool has been flagged for deletion. " +
"Restart Proessing to finalize the removal process.</i>";
static public final String INSTALL_FAILURE_TITLE = "Install Failed";
static public final String MALFORMED_URL_MESSAGE =
@@ -202,15 +202,21 @@ public class ContributionManager {
}
/** Called by Base to clean up entries previously marked for deletion. */
static public void deleteFlagged() {
/**
* Called by Base to clean up entries previously marked for deletion
* and remove any "requires restart" flags.
*/
static public void cleanup() throws Exception {
deleteFlagged(Base.getSketchbookLibrariesFolder());
deleteFlagged(Base.getSketchbookModesFolder());
deleteFlagged(Base.getSketchbookToolsFolder());
clearRestartFlags(Base.getSketchbookModesFolder());
clearRestartFlags(Base.getSketchbookToolsFolder());
}
static private void deleteFlagged(File root) {
static private void deleteFlagged(File root) throws Exception {
File[] markedForDeletion = root.listFiles(new FileFilter() {
public boolean accept(File folder) {
return (folder.isDirectory() &&
@@ -221,4 +227,16 @@ public class ContributionManager {
Base.removeDir(folder);
}
}
static private void clearRestartFlags(File root) throws Exception {
File[] folderList = root.listFiles(new FileFilter() {
public boolean accept(File folder) {
return folder.isDirectory();
}
});
for (File folder : folderList) {
LocalContribution.clearRestartFlags(folder);
}
}
}
@@ -274,11 +274,15 @@ public class ContributionManagerDialog {
// }
Collections.sort(categories);
// categories.add(0, ContributionManagerDialog.ANY_CATEGORY);
boolean categoriesFound = false;
categoryChooser.addItem(ContributionManagerDialog.ANY_CATEGORY);
for (String s : categories) {
categoryChooser.addItem(s);
if (!s.equals("Unknown")) {
categoriesFound = true;
}
}
categoryChooser.setEnabled(categories.size() != 0);
categoryChooser.setEnabled(categoriesFound);
}
}
@@ -318,7 +322,18 @@ public class ContributionManagerDialog {
protected void updateContributionListing() {
if (editor != null) {
ArrayList<Library> libraries = new ArrayList<Library>(editor.getMode().contribLibraries);
ArrayList<Contribution> contributions = new ArrayList<Contribution>();
ArrayList<Library> libraries =
new ArrayList<Library>(editor.getMode().contribLibraries);
contributions.addAll(libraries);
ArrayList<ToolContribution> tools = editor.contribTools;
contributions.addAll(tools);
ArrayList<ModeContribution> modes = editor.getBase().getModeContribs();
contributions.addAll(modes);
// ArrayList<LibraryCompilation> compilations = LibraryCompilation.list(libraries);
//
// // Remove libraries from the list that are part of a compilations
@@ -332,11 +347,6 @@ public class ContributionManagerDialog {
// }
// }
ArrayList<Contribution> contributions = new ArrayList<Contribution>();
contributions.addAll(editor.contribTools);
contributions.addAll(libraries);
// contributions.addAll(compilations);
contribListing.updateInstalledList(contributions);
}
}
@@ -44,6 +44,12 @@ import processing.app.Base;
* Panel that expands and gives a brief overview of a library when clicked.
*/
class ContributionPanel extends JPanel {
static public final String REMOVE_RESTART_MESSAGE =
"<i>Please restart Processing to finish removing this item.</i>";
static public final String INSTALL_RESTART_MESSAGE =
"<i>Please restart Processing to finish installing this item.</i>";
private final ContributionListPanel listPanel;
private final ContributionListing contribListing = ContributionListing.getInstance();
@@ -95,6 +101,7 @@ class ContributionPanel extends JPanel {
public void actionPerformed(ActionEvent e) {
if (contrib instanceof AvailableContribution) {
installContribution((AvailableContribution) contrib);
contribListing.replaceContribution(contrib, contrib);
}
}
};
@@ -103,7 +110,7 @@ class ContributionPanel extends JPanel {
public void actionPerformed(ActionEvent e) {
if (contrib instanceof LocalContribution) {
LocalContribution installed = (LocalContribution) contrib;
installed.unsetDeletionFlag();
installed.setDeletionFlag(false);
contribListing.replaceContribution(contrib, contrib); // ??
}
}
@@ -336,9 +343,11 @@ class ContributionPanel extends JPanel {
}
description.append("<br/>");
boolean isFlagged = contrib.isDeletionFlagged();
if (isFlagged) {
description.append(ContributionListPanel.DELETION_MESSAGE);
//System.out.println("checking restart flag for " + contrib + " " + contrib.getName() + " and it's " + contrib.isRestartFlagged());
if (contrib.isDeletionFlagged()) {
description.append(REMOVE_RESTART_MESSAGE);
} else if (contrib.isRestartFlagged()) {
description.append(INSTALL_RESTART_MESSAGE);
} else {
String sentence = contrib.getSentence();
if (sentence == null || sentence.isEmpty()) {
@@ -357,11 +366,14 @@ class ContributionPanel extends JPanel {
if (contribListing.hasUpdates(contrib)) {
StringBuilder versionText = new StringBuilder();
versionText.append("<html><body><i>");
if (isFlagged) {
versionText.append("To finish an update, reinstall this contribution after the restart.");
if (contrib.isDeletionFlagged()) {
// Already marked for deletion, see requiresRestart() notes below.
versionText.append("To finish an update, reinstall this contribution after restarting.");
} else {
versionText.append("New version available!");
if (contrib.requiresRestart()) {
if (contrib.getType().requiresRestart()) {
// If a contribution can't be reinstalled in-place, the user may need
// to remove the current version, restart Processing, then install.
versionText.append(" To update, first remove the current version.");
}
}
@@ -374,7 +386,7 @@ class ContributionPanel extends JPanel {
}
updateButton.setEnabled(true);
if (contrib != null && !contrib.requiresRestart()) {
if (contrib != null && !contrib.getType().requiresRestart()) {
updateButton.setVisible(isSelected() && contribListing.hasUpdates(contrib));
}
@@ -382,7 +394,7 @@ class ContributionPanel extends JPanel {
installRemoveButton.removeActionListener(removeActionListener);
installRemoveButton.removeActionListener(undoActionListener);
if (isFlagged) {
if (contrib.isDeletionFlagged()) {
installRemoveButton.addActionListener(undoActionListener);
installRemoveButton.setText("Undo");
} else if (contrib.isInstalled()) {
@@ -449,7 +461,8 @@ class ContributionPanel extends JPanel {
} catch (MalformedURLException e) {
Base.showWarning(ContributionListPanel.INSTALL_FAILURE_TITLE,
ContributionListPanel.MALFORMED_URL_MESSAGE, e);
installRemoveButton.setEnabled(true);
// not sure why we'd re-enable the button if it had an error...
// installRemoveButton.setEnabled(true);
}
}
@@ -487,7 +500,7 @@ class ContributionPanel extends JPanel {
// now a hyperlink, it will be opened as the mouse is released.
enableHyperlinks = alreadySelected;
if (contrib != null && !contrib.requiresRestart()) {
if (contrib != null && !contrib.getType().requiresRestart()) {
updateButton.setVisible(isSelected() && contribListing.hasUpdates(contrib));
}
installRemoveButton.setVisible(isSelected() || installRemoveButton.getText().equals("Remove"));
@@ -163,6 +163,15 @@ public enum ContributionType {
}
/**
* Returns true if the type of contribution requires the PDE to restart
* when being added or removed.
*/
boolean requiresRestart() {
return this == ContributionType.TOOL || this == ContributionType.MODE;
}
LocalContribution load(Base base, File folder) {
switch (this) {
case LIBRARY:
@@ -193,15 +202,20 @@ public enum ContributionType {
return contribs;
}
File getBackupFolder() {
return new File(getSketchbookFolder(), "old");
}
File createBackupFolder(StatusPanel status) {
File backupFolder = new File(getSketchbookFolder(), "old");
if (backupFolder.isDirectory()) {
status.setErrorMessage("First remove the folder named \"old\" from the " +
getFolderName() + " folder in the sketchbook.");
return null;
}
if (!backupFolder.mkdirs()) {
File backupFolder = getBackupFolder();
// if (backupFolder.isDirectory()) {
// status.setErrorMessage("First remove the folder named \"old\" from the " +
// getFolderName() + " folder in the sketchbook.");
// return null;
// }
if (!backupFolder.exists() && !backupFolder.mkdirs()) {
status.setErrorMessage("Could not create a backup folder in the " +
"sketchbook " + toString() + " folder.");
return null;
@@ -38,14 +38,15 @@ import processing.app.*;
* be installed.
*/
public abstract class LocalContribution extends Contribution {
static public final String DELETION_FLAG = "flagged_for_deletion";
static public final String DELETION_FLAG = "marked_for_deletion";
static public final String RESTART_FLAG = "requires_restart";
protected String id; // 1 (unique id for this library)
protected int latestVersion; // 103
protected File folder;
protected HashMap<String, String> properties;
protected ClassLoader loader;
public LocalContribution(File folder) {
this.folder = folder;
@@ -198,7 +199,7 @@ public abstract class LocalContribution extends Contribution {
if ((oldContrib.getFolder().exists() && oldContrib.getFolder().equals(contribFolder)) ||
(oldContrib.getId() != null && oldContrib.getId().equals(getId()))) {
if (oldContrib.requiresRestart()) {
if (oldContrib.getType().requiresRestart()) {
// XXX: We can't replace stuff, soooooo.... do something different
if (!oldContrib.backup(editor, false, status)) {
return null;
@@ -213,7 +214,7 @@ public abstract class LocalContribution extends Contribution {
"A pre-existing copy of the \"" + oldContrib.getName() + "\" library<br>"+
"has been found in your sketchbook. Clicking “Yes”<br>"+
"will move the existing library to a backup folder<br>" +
" in <i>libraries/old</i> before replacing it.");
"in <i>libraries/old</i> before replacing it.");
if (result != JOptionPane.YES_OPTION || !oldContrib.backup(editor, true, status)) {
return null;
}
@@ -275,15 +276,15 @@ public abstract class LocalContribution extends Contribution {
* should instead be copied, leaving the original in place
*/
boolean backup(Editor editor, boolean deleteOriginal, StatusPanel status) {
boolean success = false;
File backupFolder = getType().createBackupFolder(status);
boolean success = false;
if (backupFolder != null) {
String libFolderName = getFolder().getName();
String prefix = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
final String backupName = prefix + " " + libFolderName;
File backupSubFolder = ContributionManager.getUniqueName(backupFolder, backupName);
File backupSubFolder =
ContributionManager.getUniqueName(backupFolder, backupName);
if (deleteOriginal) {
success = getFolder().renameTo(backupSubFolder);
@@ -325,9 +326,9 @@ public abstract class LocalContribution extends Contribution {
pm.startTask("Removing", ProgressMonitor.UNKNOWN);
boolean doBackup = Preferences.getBoolean("contribution.backup.on_remove");
if (requiresRestart()) {
if (getType().requiresRestart()) {
if (!doBackup || (doBackup && backup(editor, false, status))) {
if (setDeletionFlag()) {
if (setDeletionFlag(true)) {
contribListing.replaceContribution(this, this);
}
}
@@ -370,33 +371,6 @@ public abstract class LocalContribution extends Contribution {
return folder != null;
}
boolean setDeletionFlag() {
// Only returns false if the file already exists, so we can
// ignore the return value.
try {
new File(getFolder(), DELETION_FLAG).createNewFile();
return true;
} catch (IOException e) {
return false;
}
}
boolean unsetDeletionFlag() {
return new File(getFolder(), DELETION_FLAG).delete();
}
boolean isDeletionFlagged() {
return isDeletionFlagged(getFolder());
}
static boolean isDeletionFlagged(File folder) {
return new File(folder, DELETION_FLAG).exists();
}
// public String getCategory() {
// return category;
@@ -464,8 +438,77 @@ public abstract class LocalContribution extends Contribution {
return null;
}
*/
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
boolean setDeletionFlag(boolean flag) {
return setFlag(DELETION_FLAG, flag);
}
boolean isDeletionFlagged() {
return isDeletionFlagged(getFolder());
}
static boolean isDeletionFlagged(File folder) {
return isFlagged(folder, DELETION_FLAG);
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
boolean setRestartFlag() {
//System.out.println("setting restart flag for " + folder);
return setFlag(RESTART_FLAG, true);
}
@Override
boolean isRestartFlagged() {
//System.out.println("checking for restart inside LocalContribution for " + getName());
return isFlagged(getFolder(), RESTART_FLAG);
}
static void clearRestartFlags(File folder) {
File restartFlag = new File(folder, RESTART_FLAG);
if (restartFlag.exists()) {
restartFlag.delete();
}
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
private boolean setFlag(String flagFilename, boolean flag) {
if (flag) {
// Only returns false if the file already exists, so we can
// ignore the return value.
try {
new File(getFolder(), flagFilename).createNewFile();
return true;
} catch (IOException e) {
return false;
}
} else {
return new File(getFolder(), flagFilename).delete();
}
}
static private boolean isFlagged(File folder, String flagFilename) {
return new File(folder, flagFilename).exists();
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
*
* @param base name of the class, with or without the package
+28 -17
View File
@@ -26,11 +26,8 @@ X UnsatisfiedLinkError causes huge message...
X error report cleanups haven't been fixed yet
X reported by Dan
X this should be better now
X add exception wrapper for startup
X Add methods to move files to Trash/Recycle Bin where available
_ verify that the OS X version uses the real call
_ and doesn't just look for .Trash
_ getCoreLibrary() is breaking OpenGL
_ "new Library()" constructor needs to go back to private
_ remove ability to export cross-platform apps
_ add ability to embed the current JRE
@@ -55,15 +52,27 @@ X restrict library categories to the ones in the document
X if it's not correct, shows up as 'other'
X catch Error (not just Exception) objects during load
X handles UnsupportedClassVersionError and others
_ we shouldn't use .properties for modes et al
X argh.. the 'old' folder is really poorly done
X attempt to install multiple will cause havoc (fail because 'old' exists)
o remove flagging for deletion
o half-installed mode causes a lot of trouble
o maybe it's reading from tmp folders?
o https://github.com/processing/processing/issues/1875
X can't fix, no response
X remove "Compilations" category for libraries
X modes shouldn't have categories?
X was counting "Unknown" as a category
X modes and tools require restart (per ContributionType class)
X but no message is provided anywhere?
X mode install requires restart *and* still doesn't show as installed
X even though it gets added to the modes menu properly after the restart
X https://github.com/processing/processing/issues/1782
_ we shouldn't use .properties extension for modes, et al
_ because a .properties file is iso8859-1
_ remove "Compilations" category for libraries
_ make note that .properties file *must* be utf-8
_ if not it'll make things gross (andre sier flob library)
_ argh.. the 'old' folder is really poorly done
_ attempt to install multiple will cause havoc (fail because 'old' exists)
_ modes and tools require restart (per ContributionType class)
_ but no message is provided anywhere?
_ why wasn't Library moved to LibraryContribution?
_ or that LibraryContribution needs to be a wrapper around it?
_ send info on 'check for updates' so we know about libs/modes/etc?
@@ -71,12 +80,7 @@ _ how to disclose to users?
_ only send for items that are part of the public list
_ otherwise we're sending private libraries/installs
_ although this won't pick up old libraries not on the new system
_ half-installed mode causes a lot of trouble
_ maybe it's reading from tmp folders?
_ https://github.com/processing/processing/issues/1875
_ mode install requires restart *and* still doesn't show as installed
_ even though it gets added to the modes menu properly after the restart
_ https://github.com/processing/processing/issues/1782
_ classpath conflicts..
_ getPackageList.. from Library... maybe others?
_ really need to make sure that a weird core.jar isn't being imported
@@ -95,7 +99,6 @@ _ "Update 4 items" as a button name
high
_ MovieMaker needs to be compiling as 1.6
_ add .bat file to lib on windows so that we can get better debugging info
_ move old Google Code SVN back to processing.org
_ then cull out the old branches/tags from the Github repo
_ proxy server requirement causes problems
@@ -109,6 +112,14 @@ _ move this into Android mode?
_ "String index out of range" error
_ https://github.com/processing/processing/issues/1940
medium
_ change to using platformDelete() instead of Base.removeDir() where possible
_ verify that the OS X version uses the real call
_ and doesn't just look for .Trash
_ getCoreLibrary() is breaking OpenGL
_ "new Library()" constructor needs to go back to private
_ add .bat file to lib on windows so that we can get better debugging info
_ appbundler fixes/changes
_ icon location uses path, even when embedded
_ add indents to the Info.plist output file