processing.app.contrib.LocalContribution Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pde Show documentation
Show all versions of pde Show documentation
Processing is a programming language, development environment, and online community.
This PDE package contains the Processing IDE.
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2013-15 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.app.contrib;
import java.awt.EventQueue;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.*;
import javax.swing.JOptionPane;
import processing.app.*;
import processing.app.ui.Editor;
import processing.core.PApplet;
import processing.data.StringDict;
import processing.data.StringList;
/**
* A contribution that has been downloaded to the disk, and may or may not
* be installed.
*/
public abstract class LocalContribution extends Contribution {
static public final String DELETION_FLAG = "marked_for_deletion";
static public final String UPDATE_FLAGGED = "marked_for_update";
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 StringDict properties;
protected ClassLoader loader;
public LocalContribution(File folder) {
this.folder = folder;
// required for contributed modes, but not for built-in core modes
File propertiesFile = new File(folder, getTypeName() + ".properties");
if (propertiesFile.exists()) {
properties = Util.readSettings(propertiesFile);
name = properties.get("name");
id = properties.get("id");
categories = parseCategories(properties);
imports = parseImports(properties);
if (name == null) {
name = folder.getName();
}
// changing to 'authors' in 3.0a11
authors = properties.get(AUTHORS_PROPERTY);
if (authors == null) {
authors = properties.get("authorList");
}
url = properties.get("url");
sentence = properties.get("sentence");
paragraph = properties.get("paragraph");
try {
version = Integer.parseInt(properties.get("version"));
} catch (NumberFormatException e) {
System.err.println("The version number for the “" + name + "” library is not set properly.");
System.err.println("Please contact the library author to fix it according to the guidelines.");
}
prettyVersion = properties.get("prettyVersion");
try {
lastUpdated = Long.parseLong(properties.get("lastUpdated"));
} catch (NumberFormatException e) {
lastUpdated = 0;
// Better comment these out till all contribs have a lastUpdated
// System.err.println("The last updated timestamp for the “" + name + "” library is not set properly.");
// System.err.println("Please contact the library author to fix it according to the guidelines.");
}
String minRev = properties.get("minRevision");
if (minRev != null) {
minRevision = PApplet.parseInt(minRev, 0);
}
String maxRev = properties.get("maxRevision");
if (maxRev != null) {
maxRevision = PApplet.parseInt(maxRev, 0);
}
} else {
Messages.log("No properties file at " + propertiesFile.getAbsolutePath());
// We'll need this to be set at a minimum.
name = folder.getName();
categories = unknownCategoryList();
}
if (categories.hasValue(SPECIAL_CATEGORY)) {
validateSpecial();
}
}
private void validateSpecial() {
for (AvailableContribution available : ContributionListing.getInstance().advertisedContributions) {
if (available.getName().equals(name)) {
if (!available.isSpecial()) {
categories.removeValue(SPECIAL_CATEGORY);
}
}
break;
}
}
public String initLoader(String className) throws Exception {
File modeDirectory = new File(folder, getTypeName());
if (modeDirectory.exists()) {
Messages.log("checking mode folder regarding " + className);
// If no class name specified, search the main .jar for the
// full name package and mode name.
if (className == null) {
String shortName = folder.getName();
File mainJar = new File(modeDirectory, shortName + ".jar");
if (mainJar.exists()) {
className = findClassInZipFile(shortName, mainJar);
} else {
throw new IgnorableException(mainJar.getAbsolutePath() + " does not exist.");
}
if (className == null) {
throw new IgnorableException("Could not find " + shortName +
" class inside " + mainJar.getAbsolutePath());
}
}
// Add .jar and .zip files from the "mode" folder into the classpath
File[] archives = Util.listJarFiles(modeDirectory);
if (archives != null && archives.length > 0) {
URL[] urlList = new URL[archives.length];
for (int j = 0; j < urlList.length; j++) {
Messages.log("Found archive " + archives[j] + " for " + getName());
urlList[j] = archives[j].toURI().toURL();
}
// loader = new URLClassLoader(urlList, Thread.currentThread().getContextClassLoader());
loader = new URLClassLoader(urlList);
Messages.log("loading above JARs with loader " + loader);
// System.out.println("listing classes for loader " + loader);
// listClasses(loader);
}
}
// If no archives were found, just use the regular ClassLoader
if (loader == null) {
loader = Thread.currentThread().getContextClassLoader();
}
return className;
}
/*
// doesn't work with URLClassLoader, but works with the system CL
static void listClasses(ClassLoader loader) {
// loader = Thread.currentThread().getContextClassLoader();
try {
Field f = ClassLoader.class.getDeclaredField("classes");
f.setAccessible(true);
Vector classes = (Vector) f.get(loader);
for (Class c : classes) {
System.out.println(c.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
*/
// static protected boolean isCandidate(File potential, final ContributionType type) {
// return (potential.isDirectory() &&
// new File(potential, type.getFolderName()).exists());
// }
//
//
// /**
// * Return a list of directories that have the necessary subfolder for this
// * contribution type. For instance, a list of folders that have a 'mode'
// * subfolder if this is a ModeContribution.
// */
// static protected File[] listCandidates(File folder, final ContributionType type) {
// return folder.listFiles(new FileFilter() {
// public boolean accept(File potential) {
// return isCandidate(potential, type);
// }
// });
// }
//
//
// /**
// * Return the first directory that has the necessary subfolder for this
// * contribution type. For instance, the first folder that has a 'mode'
// * subfolder if this is a ModeContribution.
// */
// static protected File findCandidate(File folder, final ContributionType type) {
// File[] folders = listCandidates(folder, type);
//
// if (folders.length == 0) {
// return null;
//
// } else if (folders.length > 1) {
// Base.log("More than one " + type.toString() + " found inside " + folder.getAbsolutePath());
// }
// return folders[0];
// }
LocalContribution copyAndLoad(Base base,
boolean confirmReplace,
StatusPanel status) {
// NOTE: null status => function is called on startup when Editor objects, et al. aren't ready
String contribFolderName = getFolder().getName();
File contribTypeFolder = getType().getSketchbookFolder();
File contribFolder = new File(contribTypeFolder, contribFolderName);
if (status != null) { // when status != null, install is not occurring on startup
Editor editor = base.getActiveEditor();
ArrayList oldContribs =
getType().listContributions(editor);
// In case an update marker exists, and the user wants to install, delete the update marker
if (contribFolder.exists() && !contribFolder.isDirectory()) {
contribFolder.delete();
contribFolder = new File(contribTypeFolder, contribFolderName);
}
for (LocalContribution oldContrib : oldContribs) {
if ((oldContrib.getFolder().exists() && oldContrib.getFolder().equals(contribFolder)) ||
(oldContrib.getId() != null && oldContrib.getId().equals(getId()))) {
if (oldContrib.getType().requiresRestart()) {
// XXX: We can't replace stuff, soooooo.... do something different
if (!oldContrib.backup(false, status)) {
return null;
}
} else {
int result = 0;
boolean doBackup = Preferences.getBoolean("contribution.backup.on_install");
if (confirmReplace) {
if (doBackup) {
result = Messages.showYesNoQuestion(editor, "Replace",
"Replace pre-existing \"" + oldContrib.getName() + "\" library?",
"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.");
if (result != JOptionPane.YES_OPTION || !oldContrib.backup(true, status)) {
return null;
}
} else {
result = Messages.showYesNoQuestion(editor, "Replace",
"Replace pre-existing \"" + oldContrib.getName() + "\" library?",
"A pre-existing copy of the \"" + oldContrib.getName() + "\" library
"+
"has been found in your sketchbook. Clicking “Yes”
"+
"will permanently delete this library and all of its contents
"+
"before replacing it.");
if (result != JOptionPane.YES_OPTION || !oldContrib.getFolder().delete()) {
return null;
}
}
} else {
if ((doBackup && !oldContrib.backup(true, status)) ||
(!doBackup && !oldContrib.getFolder().delete())) {
return null;
}
}
}
}
}
// At this point it should be safe to replace this fella
if (contribFolder.exists()) {
Util.removeDir(contribFolder);
}
}
else {
// This if should ideally never happen, since this function
// is to be called only when restarting on update
if (contribFolder.exists() && contribFolder.isDirectory()) {
Util.removeDir(contribFolder);
}
else if (contribFolder.exists()) {
contribFolder.delete();
contribFolder = new File(contribTypeFolder, contribFolderName);
}
}
File oldFolder = getFolder();
try {
Util.copyDir(oldFolder, contribFolder);
} catch (IOException e) {
status.setErrorMessage("Could not copy " + getTypeName() +
" \"" + getName() + "\" to the sketchbook.");
e.printStackTrace();
return null;
}
/*
if (!getFolder().renameTo(contribFolder)) {
status.setErrorMessage("Could not move " + getTypeName() +
" \"" + getName() + "\" to the sketchbook.");
return null;
}
*/
return getType().load(base, contribFolder);
}
/**
* Moves the given contribution to a backup folder.
* @param deleteOriginal
* true if the file should be moved to the directory, false if it
* should instead be copied, leaving the original in place
*/
boolean backup(boolean deleteOriginal, StatusPanel status) {
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);
if (deleteOriginal) {
success = getFolder().renameTo(backupSubFolder);
} else {
try {
Util.copyDir(getFolder(), backupSubFolder);
success = true;
} catch (IOException e) { }
}
if (!success) {
status.setErrorMessage("Could not move contribution to backup folder.");
}
}
return success;
}
/**
* Non-blocking call to remove a contribution in a new thread.
*/
void removeContribution(final Base base,
final ContribProgressMonitor pm,
final StatusPanel status) {
// TODO: replace with SwingWorker [jv]
new Thread(new Runnable() {
public void run() {
remove(base,
pm,
status,
ContributionListing.getInstance());
}
}, "Contribution Uninstaller").start();
}
void remove(final Base base,
final ContribProgressMonitor pm,
final StatusPanel status,
final ContributionListing contribListing) {
pm.startTask("Removing", ContribProgressMonitor.UNKNOWN);
boolean doBackup = Preferences.getBoolean("contribution.backup.on_remove");
// if (getType().requiresRestart()) {
// if (!doBackup || (doBackup && backup(editor, false, status))) {
// if (setDeletionFlag(true)) {
// contribListing.replaceContribution(this, this);
// }
// }
// } else {
boolean success = false;
if (getType() == ContributionType.MODE) {
boolean isModeActive = false;
ModeContribution m = (ModeContribution) this;
Iterator iter = base.getEditors().iterator();
while (iter.hasNext()) {
Editor e = iter.next();
if (e.getMode().equals(m.getMode())) {
isModeActive = true;
break;
}
}
if (!isModeActive) {
m.clearClassLoader(base);
} else {
pm.cancel();
Messages.showMessage("Mode Manager",
"Please save your Sketch and change the Mode of all Editor\n" +
"windows that have " + name + " as the active Mode.");
return;
}
}
if (getType() == ContributionType.TOOL) {
/*
ToolContribution t = (ToolContribution) this;
Iterator iter = editor.getBase().getEditors().iterator();
while (iter.hasNext()) {
Editor ed = iter.next();
ed.clearToolMenu();
}
t.clearClassLoader(editor.getBase());
*/
// menu will be rebuilt below with the refreshContribs() call
base.clearToolMenus();
((ToolContribution) this).clearClassLoader();
}
if (doBackup) {
success = backup(true, status);
} else {
success = Util.removeDir(getFolder(), false);
}
if (success) {
// this was just rebuilding the tool menu in one editor, which happens
// yet again down below with the call to refreshInstalled() [fry 150828]
// if (getType() == ContributionType.TOOL) {
// editor.removeTool();
// }
try {
// TODO: run this in SwingWorker done() [jv]
EventQueue.invokeAndWait(new Runnable() {
@Override
public void run() {
Contribution advertisedVersion =
contribListing.getAvailableContribution(LocalContribution.this);
if (advertisedVersion == null) {
contribListing.removeContribution(LocalContribution.this);
} else {
contribListing.replaceContribution(LocalContribution.this, advertisedVersion);
}
base.refreshContribs(LocalContribution.this.getType());
base.setUpdatesAvailable(contribListing.countUpdates(base));
}
});
} catch (InterruptedException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
cause.printStackTrace();
}
}
} else {
// There was a failure backing up the folder
if (!doBackup || (doBackup && backup(false, status))) {
if (setDeletionFlag(true)) {
try {
// TODO: run this in SwingWorker done() [jv]
EventQueue.invokeAndWait(new Runnable() {
@Override
public void run() {
contribListing.replaceContribution(LocalContribution.this,
LocalContribution.this);
base.refreshContribs(LocalContribution.this.getType());
base.setUpdatesAvailable(contribListing.countUpdates(base));
}
});
} catch (InterruptedException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
cause.printStackTrace();
}
}
}
} else {
status.setErrorMessage("Could not delete the contribution's files");
}
}
if (success) {
pm.finished();
} else {
pm.cancel();
}
}
public File getFolder() {
return folder;
}
public boolean isInstalled() {
return folder != null;
}
// public String getCategory() {
// return category;
// }
//
//
// public String getName() {
// return name;
// }
public String getId() {
return id;
}
// public String getAuthorList() {
// return authorList;
// }
//
//
// public String getUrl() {
// return url;
// }
//
//
// public String getSentence() {
// return sentence;
// }
//
//
// public String getParagraph() {
// return paragraph;
// }
//
//
// public int getVersion() {
// return version;
// }
public int getLatestVersion() {
return latestVersion;
}
// public String getPrettyVersion() {
// return prettyVersion;
// }
//
//
// public String getTypeName() {
// return getType().toString();
// }
/*
static protected String findClassInZipFileList(String base, File[] fileList) {
for (File file : fileList) {
String found = findClassInZipFile(base, file);
if (found != null) {
return found;
}
}
return null;
}
*/
/**
* Returns the imports (package-names) for a library, as specified in its library.properties
* (e.g., imports=libname.*,libname.support.*)
*
* @return String[] packageNames (without wildcards) or null if none are specified
*/
public StringList getImports() {
//return imports != null ? imports.toArray(new String[0]) : null;
return imports;
}
// this duplicates code found in Contribution (though that version doesn't check for .* at the end)
// /**
// * @return the list of Java imports to be added to the sketch when the library is imported
// * or null if none are specified
// */
// static StringList parseImports(String importsStr) {
// StringList outgoing = new StringList();
//
// if (importsStr != null) {
// String[] listing = PApplet.trim(PApplet.split(importsStr, ','));
// for (String imp : listing) {
//
// // In case the wildcard is specified, strip it, as it gets added later)
// if (imp.endsWith(".*")) {
//
// imp = imp.substring(0, imp.length() - 2);
// }
//
// outgoing.add(imp);
// }
// }
//// return (outgoing.size() > 0) ? outgoing : null;
// return outgoing;
// }
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 setUpdateFlag(boolean flag) {
return setFlag(UPDATE_FLAGGED, flag);
}
boolean isUpdateFlagged() {
return isUpdateFlagged(getFolder());
}
static boolean isUpdateFlagged(File folder) {
return isFlagged(folder, UPDATE_FLAGGED);
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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
* @param file
* @return name of class (with full package name) or null if not found
*/
static protected String findClassInZipFile(String base, File file) {
// Class file to search for
String classFileName = "/" + base + ".class";
try {
ZipFile zipFile = new ZipFile(file);
Enumeration> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
if (!entry.isDirectory()) {
String name = entry.getName();
// System.out.println("entry: " + name);
if (name.endsWith(classFileName)) {
//int slash = name.lastIndexOf('/');
//String packageName = (slash == -1) ? "" : name.substring(0, slash);
// Remove .class and convert slashes to periods.
zipFile.close();
return name.substring(0, name.length() - 6).replace('/', '.');
}
}
}
zipFile.close();
} catch (IOException e) {
//System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")");
e.printStackTrace();
}
return null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy