diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 21d0df712..5f3507159 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -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); } diff --git a/app/src/processing/app/contrib/AvailableContribution.java b/app/src/processing/app/contrib/AvailableContribution.java index 046c49678..b4cb57cd4 100644 --- a/app/src/processing/app/contrib/AvailableContribution.java +++ b/app/src/processing/app/contrib/AvailableContribution.java @@ -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 diff --git a/app/src/processing/app/contrib/Contribution.java b/app/src/processing/app/contrib/Contribution.java index e09098add..0bc88268b 100644 --- a/app/src/processing/app/contrib/Contribution.java +++ b/app/src/processing/app/contrib/Contribution.java @@ -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. diff --git a/app/src/processing/app/contrib/ContributionListPanel.java b/app/src/processing/app/contrib/ContributionListPanel.java index e25537cf7..2fc5922fa 100644 --- a/app/src/processing/app/contrib/ContributionListPanel.java +++ b/app/src/processing/app/contrib/ContributionListPanel.java @@ -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 = - "This tool has been flagged for deletion. " + - "Restart Proessing to finalize the removal process."; - static public final String INSTALL_FAILURE_TITLE = "Install Failed"; static public final String MALFORMED_URL_MESSAGE = diff --git a/app/src/processing/app/contrib/ContributionManager.java b/app/src/processing/app/contrib/ContributionManager.java index 7659a1dfc..fdc5c9b39 100644 --- a/app/src/processing/app/contrib/ContributionManager.java +++ b/app/src/processing/app/contrib/ContributionManager.java @@ -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); + } + } } diff --git a/app/src/processing/app/contrib/ContributionManagerDialog.java b/app/src/processing/app/contrib/ContributionManagerDialog.java index 19e5eb3b4..52987fe61 100644 --- a/app/src/processing/app/contrib/ContributionManagerDialog.java +++ b/app/src/processing/app/contrib/ContributionManagerDialog.java @@ -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 libraries = new ArrayList(editor.getMode().contribLibraries); + ArrayList contributions = new ArrayList(); + + ArrayList libraries = + new ArrayList(editor.getMode().contribLibraries); + contributions.addAll(libraries); + + ArrayList tools = editor.contribTools; + contributions.addAll(tools); + + ArrayList modes = editor.getBase().getModeContribs(); + contributions.addAll(modes); + // ArrayList compilations = LibraryCompilation.list(libraries); // // // Remove libraries from the list that are part of a compilations @@ -332,11 +347,6 @@ public class ContributionManagerDialog { // } // } - ArrayList contributions = new ArrayList(); - contributions.addAll(editor.contribTools); - contributions.addAll(libraries); -// contributions.addAll(compilations); - contribListing.updateInstalledList(contributions); } } diff --git a/app/src/processing/app/contrib/ContributionPanel.java b/app/src/processing/app/contrib/ContributionPanel.java index 9f0ff9a60..09ba24faf 100644 --- a/app/src/processing/app/contrib/ContributionPanel.java +++ b/app/src/processing/app/contrib/ContributionPanel.java @@ -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 = + "Please restart Processing to finish removing this item."; + + static public final String INSTALL_RESTART_MESSAGE = + "Please restart Processing to finish installing this item."; + 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("
"); - 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(""); - 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")); diff --git a/app/src/processing/app/contrib/ContributionType.java b/app/src/processing/app/contrib/ContributionType.java index a2a943665..8b882ee59 100644 --- a/app/src/processing/app/contrib/ContributionType.java +++ b/app/src/processing/app/contrib/ContributionType.java @@ -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; diff --git a/app/src/processing/app/contrib/LocalContribution.java b/app/src/processing/app/contrib/LocalContribution.java index 20595a7ba..cc494b613 100644 --- a/app/src/processing/app/contrib/LocalContribution.java +++ b/app/src/processing/app/contrib/LocalContribution.java @@ -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 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
"+ "has been found in your sketchbook. Clicking “Yes”
"+ "will move the existing library to a backup folder
" + - " in libraries/old before replacing it."); + "in libraries/old 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 diff --git a/todo.txt b/todo.txt index 60018df58..5b6f5d020 100644 --- a/todo.txt +++ b/todo.txt @@ -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