/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2013-16 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.io.*; import processing.app.Base; import processing.app.Language; import processing.app.Platform; import processing.app.Util; import processing.core.PApplet; import processing.data.StringDict; import processing.data.StringList; /** * A class to hold information about a Contribution that can be downloaded. */ public class AvailableContribution extends Contribution { protected final ContributionType type; // Library, tool, etc. protected final String link; // Direct link to download the file public AvailableContribution(ContributionType type, StringDict params) { this.type = type; this.link = params.get("download"); categories = parseCategories(params); imports = parseImports(params); name = params.get("name"); authors = params.get("authors"); // if (authors == null) { // authors = params.get("authorList"); // } url = params.get("url"); sentence = params.get("sentence"); paragraph = params.get("paragraph"); String versionStr = params.get("version"); if (versionStr != null) { version = PApplet.parseInt(versionStr, 0); } setPrettyVersion(params.get("prettyVersion")); String lastUpdatedStr = params.get("lastUpdated"); if (lastUpdatedStr != null) { try { lastUpdated = Long.parseLong(lastUpdatedStr); } catch (NumberFormatException e) { lastUpdated = 0; } } String minRev = params.get("minRevision"); if (minRev != null) { minRevision = PApplet.parseInt(minRev, 0); } String maxRev = params.get("maxRevision"); if (maxRev != null) { maxRevision = PApplet.parseInt(maxRev, 0); } } /** * @param contribArchive * a zip file containing the library to install * @param confirmReplace * true to open a dialog asking the user to confirm removing/moving * the library when a library by the same name already exists * @param status * the StatusPanel. Pass null if this function is called for an * install-on-startup * @return */ public LocalContribution install(Base base, File contribArchive, boolean confirmReplace, StatusPanel status) { // Unzip the file into the modes, tools, or libraries folder inside the // sketchbook. Unzipping to /tmp is problematic because it may be on // another file system, so move/rename operations will break. // File sketchbookContribFolder = type.getSketchbookFolder(); File tempFolder = null; try { tempFolder = type.createTempFolder(); } catch (IOException e) { if (status != null) status.setErrorMessage(Language.text("contrib.errors.temporary_directory")); return null; } Util.unzip(contribArchive, tempFolder); // System.out.println("temp folder is " + tempFolder); // Base.openFolder(tempFolder); // Now go looking for a legit contrib inside what's been unpacked. File contribFolder = null; // Sometimes contrib authors place all their folders in the base directory // of the .zip file instead of in single folder as the guidelines suggest. if (type.isCandidate(tempFolder)) { /* // Can't just rename the temp folder, because a contrib with this name // may already exist. Instead, create a new temp folder, and rename the // old one to be the correct folder. File enclosingFolder = null; try { enclosingFolder = Base.createTempFolder(type.toString(), "tmp", sketchbookContribFolder); } catch (IOException e) { status.setErrorMessage("Could not create a secondary folder to install."); return null; } contribFolder = new File(enclosingFolder, getName()); tempFolder.renameTo(contribFolder); tempFolder = enclosingFolder; */ if (status != null) { status.setErrorMessage(Language.interpolate("contrib.errors.needs_repackage", getName(), type.getTitle())); } return null; } // if (contribFolder == null) { // Find the first legitimate looking folder in what we just unzipped contribFolder = type.findCandidate(tempFolder); // } LocalContribution installedContrib = null; if (contribFolder == null) { if (status != null) { status.setErrorMessage(Language.interpolate("contrib.errors.no_contribution_found", type)); } } else { File propFile = new File(contribFolder, type + ".properties"); if (writePropertiesFile(propFile)) { // 1. contribFolder now has a legit contribution, load it to get info. LocalContribution newContrib = type.load(base, contribFolder); // 1.1. get info we need to delete the newContrib folder later File newContribFolder = newContrib.getFolder(); // 2. Check to make sure nothing has the same name already, // backup old if needed, then move things into place and reload. installedContrib = newContrib.copyAndLoad(base, confirmReplace, status); // Restart no longer needed. Yay! // if (newContrib != null && type.requiresRestart()) { // installedContrib.setRestartFlag(); // //status.setMessage("Restart Processing to finish the installation."); // } // 3.1 Unlock all the jars if it is a mode or tool if (newContrib.getType() == ContributionType.MODE) { ((ModeContribution) newContrib).clearClassLoader(base); } else if (newContrib.getType() == ContributionType.TOOL) { ((ToolContribution) newContrib).clearClassLoader(); } // 3.2 Delete the newContrib, do a garbage collection, hope and pray // that Java will unlock the temp folder on Windows now newContrib = null; System.gc(); if (Platform.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 Util.removeDir(newContribFolder, false); } else { if (status != null) { status.setErrorMessage(Language.text("contrib.errors.overwriting_properties")); } } } // Remove any remaining boogers if (tempFolder.exists()) { Util.removeDir(tempFolder, false); } return installedContrib; } public boolean isInstalled() { return false; } public ContributionType getType() { return type; } /** * We overwrite those fields that aren't proper in the properties file with * the curated version from the Processing site. This ensures that things have * been cleaned up (for instance, that the "sentence" is really a sentence) * and that bad data from the contrib's .properties file doesn't break the * manager. However, it also ensures that valid fields in the properties file * aren't overwritten, since the properties file may be more recent than the * contributions.txt file. */ public boolean writePropertiesFile(File propFile) { try { StringDict properties = Util.readSettings(propFile); String name = properties.get("name"); if (name == null || name.isEmpty()) { name = getName(); } String category; StringList categoryList = parseCategories(properties); if (categoryList.size() == 1 && categoryList.get(0).equals(UNKNOWN_CATEGORY)) { category = getCategoryStr(); } else { category = categoryList.join(","); } StringList importsList = parseImports(properties); String authors = properties.get(AUTHORS_PROPERTY); // if (authors == null) { // authors = properties.get("authorList"); // before 3.0a11 // } if (authors == null || authors.isEmpty()) { authors = getAuthorList(); } String url = properties.get("url"); if (url == null || url.isEmpty()) { url = getUrl(); } String sentence = properties.get("sentence"); if (sentence == null || sentence.isEmpty()) { sentence = getSentence(); } String paragraph = properties.get("paragraph"); if (paragraph == null || paragraph.isEmpty()) { paragraph = getParagraph(); } int version; try { version = Integer.parseInt(properties.get("version")); } catch (NumberFormatException e) { version = getVersion(); System.err.println("The version number for “" + name + "” is not a number."); System.err.println("Please contact the author to fix it according to the guidelines."); } String prettyVersion = properties.get("prettyVersion"); if (prettyVersion != null && prettyVersion.isEmpty()) { prettyVersion = null; } String compatibleContribsList = null; if (getType() == ContributionType.EXAMPLES) { compatibleContribsList = properties.get(MODES_PROPERTY); } long lastUpdated; try { lastUpdated = Long.parseLong(properties.get("lastUpdated")); } catch (NumberFormatException nfe) { lastUpdated = getLastUpdated(); // Better comment these out till all contribs have a lastUpdated // System.err.println("The last updated date for the “" + name // + "” contribution is not set properly."); // System.err // .println("Please contact the author to fix it according to the guidelines."); } int minRev; try { minRev = Integer.parseInt(properties.get("minRevision")); } catch (NumberFormatException e) { minRev = getMinRevision(); // System.err.println("The minimum compatible revision for the “" + name // + "” contribution is not set properly. Assuming minimum revision 0."); } int maxRev; try { maxRev = Integer.parseInt(properties.get("maxRevision")); } catch (NumberFormatException e) { maxRev = getMaxRevision(); // System.err.println("The maximum compatible revision for the “" + name // + "” contribution is not set properly. Assuming maximum revision INF."); } if (propFile.delete() && propFile.createNewFile() && propFile.setWritable(true)) { PrintWriter writer = PApplet.createWriter(propFile); writer.println("name=" + name); writer.println("category=" + category); writer.println(AUTHORS_PROPERTY + "=" + authors); writer.println("url=" + url); writer.println("sentence=" + sentence); writer.println("paragraph=" + paragraph); writer.println("version=" + version); if (prettyVersion != null) { writer.println("prettyVersion=" + prettyVersion); } writer.println("lastUpdated=" + lastUpdated); writer.println("minRevision=" + minRev); writer.println("maxRevision=" + maxRev); if ((getType() == ContributionType.LIBRARY || getType() == ContributionType.MODE) && importsList != null) { writer.println("imports=" + importsList.join(",")); } if (getType() == ContributionType.EXAMPLES) { if (compatibleContribsList != null) { writer.println(MODES_PROPERTY + "=" + compatibleContribsList); } } writer.flush(); writer.close(); } return true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } }