1 package processing.app;
2 
3 import cc.arduino.Constants;
4 import cc.arduino.contributions.GPGDetachedSignatureVerifier;
5 import cc.arduino.contributions.SignatureVerificationFailedException;
6 import cc.arduino.contributions.VersionComparator;
7 import cc.arduino.contributions.libraries.LibrariesIndexer;
8 import cc.arduino.contributions.packages.ContributedPlatform;
9 import cc.arduino.contributions.packages.ContributedTool;
10 import cc.arduino.contributions.packages.ContributionsIndexer;
11 import cc.arduino.packages.DiscoveryManager;
12 import com.fasterxml.jackson.core.JsonProcessingException;
13 import org.apache.commons.compress.utils.IOUtils;
14 import org.apache.commons.logging.impl.LogFactoryImpl;
15 import org.apache.commons.logging.impl.NoOpLog;
16 import processing.app.debug.*;
17 import processing.app.helpers.*;
18 import processing.app.helpers.filefilters.OnlyDirs;
19 import processing.app.helpers.filefilters.OnlyFilesWithExtension;
20 import processing.app.legacy.PApplet;
21 import processing.app.packages.LibraryList;
22 import processing.app.packages.UserLibrary;
23 
24 import cc.arduino.files.DeleteFilesOnShutdown;
25 import processing.app.helpers.FileUtils;
26 
27 import java.beans.PropertyChangeListener;
28 import java.beans.PropertyChangeSupport;
29 import java.io.File;
30 import java.io.FileWriter;
31 import java.io.IOException;
32 import java.util.*;
33 import java.util.logging.Level;
34 import java.util.logging.Logger;
35 
36 import cc.arduino.packages.BoardPort;
37 
38 import static processing.app.I18n.tr;
39 import static processing.app.helpers.filefilters.OnlyDirs.ONLY_DIRS;
40 
41 public class BaseNoGui {
42 
43   /** Version string to be used for build */
44   public static final int REVISION = 10802;
45   /** Extended version string displayed on GUI */
46   public static final String VERSION_NAME = "1.8.2";
47   public static final String VERSION_NAME_LONG;
48 
49   // Current directory to use for relative paths specified on the
50   // commandline
51   static String currentDirectory = System.getProperty("user.dir");
52 
53   static {
54     String versionNameLong = VERSION_NAME;
55     File hourlyBuildTxt = new File(getContentFile("lib"), "hourlyBuild.txt");
56     if (hourlyBuildTxt.exists() && hourlyBuildTxt.canRead()) {
57       versionNameLong += " Hourly Build";
58       try {
59         versionNameLong += " " + FileUtils.readFileToString(hourlyBuildTxt).trim();
60       } catch (IOException e) {
61         //noop
62       }
63     }
64 
65     File windowsStoreConfig = new File(getContentFile("lib"), "windowsStore.txt");
66     if (windowsStoreConfig.exists()) {
67       try {
68         PreferencesMap conf = new PreferencesMap(windowsStoreConfig);
69         PreferencesData.setBoolean("runtime.is-windows-store-app", true);
70         PreferencesData.set("runtime.windows-store-app.id", conf.get("appid"));
71         versionNameLong += " (Windows Store " + conf.get("version") + ")";
72       } catch (IOException e1) {
73         e1.printStackTrace();
74       }
75     }
76 
77     VERSION_NAME_LONG = versionNameLong;
78   }
79 
80   private static DiscoveryManager discoveryManager;
81 
82   // these are static because they're used by Sketch
83   static private File examplesFolder;
84   static private File toolsFolder;
85 
86   // maps #included files to their library folder
87   public static Map<String, LibraryList> importToLibraryTable;
88 
89   // XXX: Remove this field
90   static private List<File> librariesFolders;
91 
92   static UserNotifier notifier = new BasicUserNotifier();
93 
94   static public Map<String, TargetPackage> packages;
95 
96   static Platform platform;
97 
98   static File portableFolder = null;
99   static final String portableSketchbookFolder = "sketchbook";
100 
101   public static ContributionsIndexer indexer;
102   public static LibrariesIndexer librariesIndexer;
103 
104   private static String boardManagerLink = "";
105 
106   private static File buildCache;
107 
108   // Returns a File object for the given pathname. If the pathname
109   // is not absolute, it is interpreted relative to the current
110   // directory when starting the IDE (which is not the same as the
111   // current working directory!).
absoluteFile(String path)112   static public File absoluteFile(String path) {
113     if (path == null) return null;
114 
115     File file = new File(path);
116     if (!file.isAbsolute()) {
117       file = new File(currentDirectory, path);
118     }
119     return file;
120   }
121 
122   /**
123    * Get the number of lines in a file by counting the number of newline
124    * characters inside a String (and adding 1).
125    */
countLines(String what)126   static public int countLines(String what) {
127     int count = 1;
128     for (char c : what.toCharArray()) {
129       if (c == '\n') count++;
130     }
131     return count;
132   }
133 
getBoardPreferences()134   static public PreferencesMap getBoardPreferences() {
135     TargetBoard board = getTargetBoard();
136     if (board == null)
137       return null;
138     String boardId = board.getId();
139 
140     PreferencesMap prefs = new PreferencesMap(board.getPreferences());
141 
142     String extendedName = prefs.get("name");
143     for (String menuId : board.getMenuIds()) {
144       if (!board.hasMenu(menuId))
145         continue;
146 
147       // Get "custom_[MENU_ID]" preference (for example "custom_cpu")
148       String entry = PreferencesData.get("custom_" + menuId);
149       if (entry != null && entry.startsWith(boardId)) {
150 
151         String selectionId = entry.substring(boardId.length() + 1);
152         prefs.putAll(board.getMenuPreferences(menuId, selectionId));
153 
154         // Update the name with the extended configuration
155         extendedName += ", " + board.getMenuLabel(menuId, selectionId);
156       }
157     }
158     prefs.put("name", extendedName);
159 
160     // Resolve tools needed for this board
161     List<ContributedTool> requiredTools = new ArrayList<>();
162 
163     // Add all tools dependencies specified in package index
164     ContributedPlatform p = indexer.getContributedPlaform(getTargetPlatform());
165     if (p != null)
166       requiredTools.addAll(p.getResolvedTools());
167 
168     // Add all tools dependencies from the (possibily) referenced core
169     String core = prefs.get("build.core");
170     if (core != null && core.contains(":")) {
171       String split[] = core.split(":");
172       TargetPlatform referenced = BaseNoGui.getCurrentTargetPlatformFromPackage(split[0]);
173       if (referenced != null) {
174         ContributedPlatform referencedPlatform = indexer.getContributedPlaform(referenced);
175         if (referencedPlatform != null)
176           requiredTools.addAll(referencedPlatform.getResolvedTools());
177       } else {
178         String msg = tr("The current selected board needs the core '{0}' that is not installed.");
179         System.out.println(I18n.format(msg, core));
180       }
181     }
182 
183     String prefix = "runtime.tools.";
184     for (ContributedTool tool : requiredTools) {
185       File folder = tool.getDownloadableContribution(getPlatform()).getInstalledFolder();
186       if (folder == null) {
187         continue;
188       }
189       String toolPath = folder.getAbsolutePath();
190       prefs.put(prefix + tool.getName() + ".path", toolPath);
191       PreferencesData.set(prefix + tool.getName() + ".path", toolPath);
192       PreferencesData.set(prefix + tool.getName() + "-" + tool.getVersion() + ".path", toolPath);
193     }
194     return prefs;
195   }
196 
getContentFile(String name)197   static public File getContentFile(String name) {
198     String appDir = System.getProperty("APP_DIR");
199     if (appDir == null || appDir.length() == 0) {
200       appDir = currentDirectory;
201     }
202     File installationFolder = new File(appDir);
203     return new File(installationFolder, name);
204   }
205 
getCurrentTargetPlatformFromPackage(String pack)206   static public TargetPlatform getCurrentTargetPlatformFromPackage(String pack) {
207     return getTargetPlatform(pack, PreferencesData.get("target_platform"));
208   }
209 
getDefaultSketchbookFolder()210   static public  File getDefaultSketchbookFolder() {
211     if (getPortableFolder() != null)
212       return new File(getPortableFolder(), getPortableSketchbookFolder());
213 
214     File sketchbookFolder = null;
215     try {
216       sketchbookFolder = getPlatform().getDefaultSketchbookFolder();
217     } catch (Exception e) { }
218 
219     return sketchbookFolder;
220   }
221 
getDiscoveryManager()222   public static DiscoveryManager getDiscoveryManager() {
223     if (discoveryManager == null) {
224       discoveryManager = new DiscoveryManager();
225     }
226     return discoveryManager;
227   }
228 
getExamplesFolder()229   static public File getExamplesFolder() {
230     return examplesFolder;
231   }
232 
getExamplesPath()233   static public String getExamplesPath() {
234     return examplesFolder.getAbsolutePath();
235   }
236 
getHardwareFolder()237   static public File getHardwareFolder() {
238     // calculate on the fly because it's needed by Preferences.init() to find
239     // the boards.txt and programmers.txt preferences files (which happens
240     // before the other folders / paths get cached).
241     return getContentFile("hardware");
242   }
243 
getHardwarePath()244   static public String getHardwarePath() {
245     return getHardwareFolder().getAbsolutePath();
246   }
247 
getLibrariesPath()248   static public List<File> getLibrariesPath() {
249     return librariesFolders;
250   }
251 
getPlatform()252   static public Platform getPlatform() {
253     return platform;
254   }
255 
getPortableFolder()256   static public File getPortableFolder() {
257     return portableFolder;
258   }
259 
getPortableSketchbookFolder()260   static public String getPortableSketchbookFolder() {
261     return portableSketchbookFolder;
262   }
263 
getCachePath()264   static public File getCachePath() {
265     if (buildCache == null) {
266       try {
267         buildCache = FileUtils.createTempFolder("arduino_cache_");
268         DeleteFilesOnShutdown.add(buildCache);
269       } catch (IOException e) {
270         return null;
271       }
272     }
273     return buildCache;
274   }
275 
276   /**
277    * Convenience method to get a File object for the specified filename inside
278    * the settings folder.
279    * For now, only used by Preferences to get the preferences.txt file.
280    * @param filename A file inside the settings folder.
281    * @return filename wrapped as a File object inside the settings folder
282    */
getSettingsFile(String filename)283   static public File getSettingsFile(String filename) {
284     return new File(getSettingsFolder(), filename);
285   }
286 
getSettingsFolder()287   static public File getSettingsFolder() {
288     if (getPortableFolder() != null)
289       return getPortableFolder();
290 
291     File settingsFolder = null;
292 
293     String preferencesPath = PreferencesData.get("settings.path");
294     if (preferencesPath != null) {
295       settingsFolder = absoluteFile(preferencesPath);
296 
297     } else {
298       try {
299         settingsFolder = getPlatform().getSettingsFolder();
300       } catch (Exception e) {
301         showError(tr("Problem getting data folder"),
302                   tr("Error getting the Arduino data folder."), e);
303       }
304     }
305 
306     // create the folder if it doesn't exist already
307     if (!settingsFolder.exists()) {
308       if (!settingsFolder.mkdirs()) {
309         showError(tr("Settings issues"),
310                 tr("Arduino cannot run because it could not\n" +
311                         "create a folder to store your settings."), null);
312       }
313     }
314     return settingsFolder;
315   }
316 
getSketchbookFolder()317   static public File getSketchbookFolder() {
318     String sketchBookPath = PreferencesData.get("sketchbook.path");
319     if (getPortableFolder() != null && !new File(sketchBookPath).isAbsolute()) {
320       return new File(getPortableFolder(), sketchBookPath);
321     }
322     return absoluteFile(sketchBookPath);
323   }
324 
getSketchbookHardwareFolder()325   static public File getSketchbookHardwareFolder() {
326     return new File(getSketchbookFolder(), "hardware");
327   }
328 
getSketchbookLibrariesFolder()329   static public File getSketchbookLibrariesFolder() {
330     File libdir = new File(getSketchbookFolder(), "libraries");
331     if (!libdir.exists()) {
332       FileWriter freadme = null;
333       try {
334         libdir.mkdirs();
335         freadme = new FileWriter(new File(libdir, "readme.txt"));
336         freadme.write(tr("For information on installing libraries, see: " +
337                         "http://www.arduino.cc/en/Guide/Libraries\n"));
338       } catch (Exception e) {
339       } finally {
340         IOUtils.closeQuietly(freadme);
341       }
342     }
343     return libdir;
344   }
345 
getSketchbookPath()346   static public String getSketchbookPath() {
347     // Get the sketchbook path, and make sure it's set properly
348     String sketchbookPath = PreferencesData.get("sketchbook.path");
349 
350     // If a value is at least set, first check to see if the folder exists.
351     // If it doesn't, warn the user that the sketchbook folder is being reset.
352     if (sketchbookPath != null) {
353       File sketchbookFolder;
354       if (getPortableFolder() != null && !new File(sketchbookPath).isAbsolute()) {
355         sketchbookFolder = new File(getPortableFolder(), sketchbookPath);
356       } else {
357         sketchbookFolder = absoluteFile(sketchbookPath);
358       }
359       if (!sketchbookFolder.exists()) {
360         showWarning(tr("Sketchbook folder disappeared"),
361                     tr("The sketchbook folder no longer exists.\n" +
362                       "Arduino will switch to the default sketchbook\n" +
363                       "location, and create a new sketchbook folder if\n" +
364                       "necessary. Arduino will then stop talking about\n" +
365                       "himself in the third person."), null);
366         sketchbookPath = null;
367       }
368     }
369 
370     return sketchbookPath;
371   }
372 
getTargetBoard()373   public static TargetBoard getTargetBoard() {
374     TargetPlatform targetPlatform = getTargetPlatform();
375     if (targetPlatform == null)
376       return null;
377     String boardId = PreferencesData.get("board");
378     return targetPlatform.getBoard(boardId);
379   }
380 
381   /**
382    * Returns a specific TargetPackage
383    *
384    * @param packageName
385    * @return
386    */
getTargetPackage(String packageName)387   static public TargetPackage getTargetPackage(String packageName) {
388     return packages.get(packageName);
389   }
390 
391   /**
392    * Returns the currently selected TargetPlatform.
393    *
394    * @return
395    */
getTargetPlatform()396   static public TargetPlatform getTargetPlatform() {
397     String packageName = PreferencesData.get("target_package");
398     String platformName = PreferencesData.get("target_platform");
399     return getTargetPlatform(packageName, platformName);
400   }
401 
402   /**
403    * Returns a specific TargetPlatform searching Package/Platform
404    *
405    * @param packageName
406    * @param platformName
407    * @return
408    */
getTargetPlatform(String packageName, String platformName)409   static public TargetPlatform getTargetPlatform(String packageName,
410                                                  String platformName) {
411     TargetPackage p = packages.get(packageName);
412     if (p == null)
413       return null;
414     return p.get(platformName);
415   }
416 
getToolsFolder()417   static public File getToolsFolder() {
418     return toolsFolder;
419   }
420 
getToolsPath()421   static public String getToolsPath() {
422     return toolsFolder.getAbsolutePath();
423   }
424 
getUserLibs()425   static public LibraryList getUserLibs() {
426     LibraryList libs = BaseNoGui.librariesIndexer.getInstalledLibraries();
427     return libs.filterLibrariesInSubfolder(getSketchbookFolder());
428   }
429 
getBoardManagerLink()430   static public String getBoardManagerLink() {
431 	  return boardManagerLink;
432   }
433 
434   protected static PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(BaseNoGui.class);
435 
setBoardManagerLink(String temp)436   public static void setBoardManagerLink(String temp) {
437 	  boardManagerLink = temp;
438 	  propertyChangeSupport.firePropertyChange("boardManagerLink", "", boardManagerLink);
439   }
440 
addPropertyChangeListener(PropertyChangeListener listener)441   public static void addPropertyChangeListener(PropertyChangeListener listener) {
442 	  propertyChangeSupport.addPropertyChangeListener(listener);
443   }
444 
445   /**
446    * Given a folder, return a list of the header files in that folder (but not
447    * the header files in its sub-folders, as those should be included from
448    * within the header files at the top-level).
449    */
headerListFromIncludePath(File path)450   static public String[] headerListFromIncludePath(File path) throws IOException {
451     String[] list = path.list(new OnlyFilesWithExtension(".h", ".hh", ".hpp"));
452     if (list == null) {
453       throw new IOException();
454     }
455     return list;
456   }
457 
dumpPrefs(CommandlineParser parser)458   protected static void dumpPrefs(CommandlineParser parser) {
459     if (parser.getGetPref() != null) {
460       String value = PreferencesData.get(parser.getGetPref(), null);
461       if (value != null) {
462         System.out.println(value);
463         System.exit(0);
464       } else {
465         System.exit(4);
466       }
467     } else {
468       System.out.println("#PREFDUMP#");
469       PreferencesMap prefs = PreferencesData.getMap();
470       for (Map.Entry<String, String> entry : prefs.entrySet()) {
471         System.out.println(entry.getKey() + "=" + entry.getValue());
472       }
473       System.exit(0);
474     }
475   }
476 
initLogger()477   static public void initLogger() {
478     System.setProperty(LogFactoryImpl.LOG_PROPERTY, NoOpLog.class.getCanonicalName());
479     Logger.getLogger("javax.jmdns").setLevel(Level.OFF);
480   }
481 
initPackages()482   static public void initPackages() throws Exception {
483     indexer = new ContributionsIndexer(getSettingsFolder(), getHardwareFolder(), getPlatform(),
484         new GPGDetachedSignatureVerifier());
485 
486     try {
487       indexer.parseIndex();
488     } catch (JsonProcessingException | SignatureVerificationFailedException e) {
489       File indexFile = indexer.getIndexFile(Constants.DEFAULT_INDEX_FILE_NAME);
490       File indexSignatureFile = indexer.getIndexFile(Constants.DEFAULT_INDEX_FILE_NAME + ".sig");
491       FileUtils.deleteIfExists(indexFile);
492       FileUtils.deleteIfExists(indexSignatureFile);
493       throw e;
494     }
495     indexer.syncWithFilesystem();
496 
497     packages = new LinkedHashMap<>();
498     loadHardware(getHardwareFolder());
499     loadContributedHardware(indexer);
500     loadHardware(getSketchbookHardwareFolder());
501     createToolPreferences(indexer.getInstalledTools(), true);
502 
503     librariesIndexer = new LibrariesIndexer(getSettingsFolder());
504     try {
505       librariesIndexer.parseIndex();
506     } catch (JsonProcessingException e) {
507       File librariesIndexFile = librariesIndexer.getIndexFile();
508       FileUtils.deleteIfExists(librariesIndexFile);
509     }
510 
511     if (discoveryManager == null) {
512       discoveryManager = new DiscoveryManager();
513     }
514   }
515 
initPlatform()516   static protected void initPlatform() {
517     try {
518       Class<?> platformClass = Class.forName("processing.app.Platform");
519       if (OSUtils.isMacOS()) {
520         platformClass = Class.forName("processing.app.macosx.Platform");
521       } else if (OSUtils.isWindows()) {
522         platformClass = Class.forName("processing.app.windows.Platform");
523       } else if (OSUtils.isLinux()) {
524         platformClass = Class.forName("processing.app.linux.Platform");
525       }
526       platform = (Platform) platformClass.newInstance();
527     } catch (Exception e) {
528       showError(tr("Problem Setting the Platform"),
529                 tr("An unknown error occurred while trying to load\n" +
530                   "platform-specific code for your machine."), e);
531     }
532   }
533 
initPortableFolder()534   static public void initPortableFolder() {
535     // Portable folder
536     portableFolder = getContentFile("portable");
537     if (!portableFolder.exists()) {
538       portableFolder = null;
539     }
540   }
541 
initVersion()542   static public void initVersion() {
543     // help 3rd party installers find the correct hardware path
544     PreferencesData.set("last.ide." + VERSION_NAME + ".hardwarepath", getHardwarePath());
545     PreferencesData.set("last.ide." + VERSION_NAME + ".daterun", "" + (new Date()).getTime() / 1000);
546   }
547 
548   /**
549    * Return true if the name is valid for a Processing sketch.
550    */
isSanitaryName(String name)551   static public boolean isSanitaryName(String name) {
552     return sanitizeName(name).equals(name);
553   }
554 
loadHardware(File folder)555   static protected void loadHardware(File folder) {
556     if (!folder.isDirectory()) {
557       return;
558     }
559 
560     String list[] = folder.list(new OnlyDirs());
561 
562     // if a bad folder or something like that, this might come back null
563     if (list == null) {
564       return;
565     }
566 
567     // alphabetize list, since it's not always alpha order
568     // replaced hella slow bubble sort with this feller for 0093
569     Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
570 
571     for (String target : list) {
572       // Skip reserved 'tools' folder.
573       if (target.equals("tools")) {
574         continue;
575       }
576       File subfolder = new File(folder, target);
577 
578       TargetPackage targetPackage;
579       if (packages.containsKey(target)) {
580         targetPackage = packages.get(target);
581       } else {
582         targetPackage = new LegacyTargetPackage(target);
583         packages.put(target, targetPackage);
584       }
585       try {
586         loadTargetPackage(targetPackage, subfolder);
587       } catch (TargetPlatformException e) {
588         System.out.println("WARNING: Error loading hardware folder " + new File(folder, target));
589         System.out.println("  " + e.getMessage());
590       }
591     }
592   }
593 
loadTargetPackage(TargetPackage targetPackage, File _folder)594   private static void loadTargetPackage(TargetPackage targetPackage, File _folder) throws TargetPlatformException {
595     File[] folders = _folder.listFiles(ONLY_DIRS);
596     if (folders == null) {
597       return;
598     }
599 
600     for (File subFolder : folders) {
601       if (!subFolder.exists() || !subFolder.canRead()) {
602         continue;
603       }
604       String arch = subFolder.getName();
605       try {
606         TargetPlatform p = new LegacyTargetPlatform(arch, subFolder, targetPackage);
607         targetPackage.getPlatforms().put(arch, p);
608       } catch (TargetPlatformException e) {
609         System.err.println(e.getMessage());
610       }
611     }
612 
613     if (targetPackage.getPlatforms().size() == 0) {
614       throw new TargetPlatformException(I18n.format(tr("No valid hardware definitions found in folder {0}."), _folder.getName()));
615     }
616   }
617 
618   /**
619    * Grab the contents of a file as a string.
620    */
loadFile(File file)621   static public String loadFile(File file) throws IOException {
622     String[] contents = PApplet.loadStrings(file);
623     if (contents == null) return null;
624     return PApplet.join(contents, "\n");
625   }
626 
checkInstallationFolder()627   public static void checkInstallationFolder() {
628     if (isIDEInstalledIntoSettingsFolder()) {
629       showError(tr("Incorrect IDE installation folder"), tr("Your copy of the IDE is installed in a subfolder of your settings folder.\nPlease move the IDE to another folder."), 10);
630     }
631     if (isIDEInstalledIntoSketchbookFolder()) {
632       showError(tr("Incorrect IDE installation folder"), tr("Your copy of the IDE is installed in a subfolder of your sketchbook.\nPlease move the IDE to another folder."), 10);
633     }
634   }
635 
isIDEInstalledIntoSketchbookFolder()636   public static boolean isIDEInstalledIntoSketchbookFolder() {
637     return PreferencesData.has("sketchbook.path") && FileUtils.isSubDirectory(new File(PreferencesData.get("sketchbook.path")), new File(PreferencesData.get("runtime.ide.path")));
638   }
639 
isIDEInstalledIntoSettingsFolder()640   public static boolean isIDEInstalledIntoSettingsFolder() {
641     try {
642       return FileUtils.isSubDirectory(BaseNoGui.getPlatform().getSettingsFolder(), new File(PreferencesData.get("runtime.ide.path")));
643     } catch (Exception e) {
644       return false;
645     }
646   }
647 
onBoardOrPortChange()648   static public void onBoardOrPortChange() {
649     examplesFolder = getContentFile("examples");
650     toolsFolder = getContentFile("tools");
651     librariesFolders = new ArrayList<>();
652 
653     // Add IDE libraries folder
654     librariesFolders.add(getContentFile("libraries"));
655 
656     TargetPlatform targetPlatform = getTargetPlatform();
657     if (targetPlatform != null) {
658       String core = getBoardPreferences().get("build.core", "arduino");
659       if (core.contains(":")) {
660         String referencedCore = core.split(":")[0];
661         TargetPlatform referencedPlatform = getTargetPlatform(referencedCore, targetPlatform.getId());
662         if (referencedPlatform != null) {
663           File referencedPlatformFolder = referencedPlatform.getFolder();
664           // Add libraries folder for the referenced platform
665           File folder = new File(referencedPlatformFolder, "libraries");
666           librariesFolders.add(folder);
667         }
668       }
669       File platformFolder = targetPlatform.getFolder();
670       // Add libraries folder for the selected platform
671       File folder = new File(platformFolder, "libraries");
672       librariesFolders.add(folder);
673     }
674 
675     // Add libraries folder for the sketchbook
676     librariesFolders.add(getSketchbookLibrariesFolder());
677 
678     // Scan for libraries in each library folder.
679     // Libraries located in the latest folders on the list can override
680     // other libraries with the same name.
681     librariesIndexer.setSketchbookLibrariesFolder(getSketchbookLibrariesFolder());
682     librariesIndexer.setLibrariesFolders(librariesFolders);
683     librariesIndexer.rescanLibraries();
684 
685     populateImportToLibraryTable();
686   }
687 
loadContributedHardware(ContributionsIndexer idx)688   static protected void loadContributedHardware(ContributionsIndexer idx) {
689     for (TargetPackage pack : idx.createTargetPackages()) {
690       packages.put(pack.getId(), pack);
691     }
692   }
693 
createToolPreferences(Collection<ContributedTool> installedTools, boolean removeOldKeys)694   public static void createToolPreferences(Collection<ContributedTool> installedTools, boolean removeOldKeys) {
695     String prefix = "runtime.tools.";
696     if (removeOldKeys) {
697       PreferencesData.removeAllKeysWithPrefix(prefix);
698     }
699 
700     Map<String, String> latestVersions = new HashMap<>();
701     VersionComparator comparator = new VersionComparator();
702     for (ContributedTool tool : installedTools) {
703       File installedFolder = tool.getDownloadableContribution(getPlatform()).getInstalledFolder();
704       String toolPath;
705       if (installedFolder != null) {
706         toolPath = installedFolder.getAbsolutePath();
707       } else {
708         toolPath = Constants.PREF_REMOVE_PLACEHOLDER;
709       }
710       String toolName = tool.getName();
711       String toolVersion = tool.getVersion();
712       PreferencesData.set(prefix + toolName + "-" + toolVersion + ".path", toolPath);
713       PreferencesData.set(prefix + tool.getPackager() + "-" + toolName + "-" + toolVersion + ".path", toolPath);
714       // In the generic tool property put the path of the latest version if more are available
715       try {
716         if (!latestVersions.containsKey(toolName) || comparator.greaterThan(toolVersion, latestVersions.get(toolName))) {
717           latestVersions.put(toolName, toolVersion);
718           PreferencesData.set(prefix + toolName + ".path", toolPath);
719         }
720       } catch (Exception e) {
721         // Ignore invalid versions
722       }
723     }
724   }
725 
populateImportToLibraryTable()726   static public void populateImportToLibraryTable() {
727     // Populate importToLibraryTable. Each header filename maps to
728     // a list of libraries. Compiler.java will use only the first
729     // library on each list. The others are used only to advise
730     // user of ambiguously matched and duplicate libraries.
731     importToLibraryTable = new HashMap<>();
732     for (UserLibrary lib : librariesIndexer.getInstalledLibraries()) {
733       try {
734         String headers[] = headerListFromIncludePath(lib.getSrcFolder());
735         for (String header : headers) {
736           LibraryList list = importToLibraryTable.get(header);
737           if (list == null) {
738             // This is the first library found with this header
739             list = new LibraryList();
740             list.addFirst(lib);
741             importToLibraryTable.put(header, list);
742           } else {
743             UserLibrary old = list.peekFirst();
744             boolean useThisLib = true;
745             // This is the case where 2 libraries have a .h header
746             // with the same name.  We must decide which library to
747             // use when a sketch has #include "name.h"
748             //
749             // When all other factors are equal, "libName" is
750             // used in preference to "oldName", because getLibraries()
751             // gives the library list in order from less specific to
752             // more specific locations.
753             //
754             // But often one library is more clearly the user's
755             // intention to use.  Many cases are tested, always first
756             // for "libName", then for "oldName".
757             //
758             String name = header.substring(0, header.length() - 2); // name without ".h"
759             String oldName = old.getInstalledFolder().getName();  // just the library folder name
760             String libName = lib.getInstalledFolder().getName();  // just the library folder name
761             //System.out.println("name conflict: " + name);
762             //System.out.println(" old = " + oldName + " -> " + old.getInstalledFolder().getPath());
763             //System.out.println(" new = " + libName + " -> " + lib.getInstalledFolder().getPath());
764             String name_lc = name.toLowerCase();
765             String oldName_lc = oldName.toLowerCase();
766             String libName_lc = libName.toLowerCase();
767             // always favor a perfect name match
768             if (libName.equals(name)) {
769             } else if (oldName.equals(name)) {
770                 useThisLib = false;
771             // check for "-master" appended (zip file from github)
772             } else if (libName.equals(name+"-master")) {
773             } else if (oldName.equals(name+"-master")) {
774                 useThisLib = false;
775             // next, favor a match with other stuff appended
776             } else if (libName.startsWith(name)) {
777             } else if (oldName.startsWith(name)) {
778                 useThisLib = false;
779             // otherwise, favor a match with stuff prepended
780             } else if (libName.endsWith(name)) {
781             } else if (oldName.endsWith(name)) {
782                 useThisLib = false;
783             // as a last resort, match if stuff prepended and appended
784             } else if (libName.contains(name)) {
785             } else if (oldName.contains(name)) {
786                 useThisLib = false;
787             // repeat all the above tests, with case insensitive matching
788             } else if (libName_lc.equals(name_lc)) {
789             } else if (oldName_lc.equals(name_lc)) {
790                 useThisLib = false;
791             } else if (libName_lc.equals(name_lc+"-master")) {
792             } else if (oldName_lc.equals(name_lc+"-master")) {
793                 useThisLib = false;
794             } else if (libName_lc.startsWith(name_lc)) {
795             } else if (oldName_lc.startsWith(name_lc)) {
796                 useThisLib = false;
797             } else if (libName_lc.endsWith(name_lc)) {
798             } else if (oldName_lc.endsWith(name_lc)) {
799                 useThisLib = false;
800             } else if (libName_lc.contains(name_lc)) {
801             } else if (oldName_lc.contains(name_lc)) {
802                 useThisLib = false;
803             } else {
804               // none of these tests matched, so just default to "libName".
805             }
806             if (useThisLib) {
807               list.addFirst(lib);
808             } else {
809               list.addLast(lib);
810             }
811           }
812         }
813       } catch (IOException e) {
814         showWarning(tr("Error"), I18n
815             .format("Unable to list header files in {0}", lib.getSrcFolder()), e);
816       }
817     }
818     // repeat for ALL libraries, to pick up duplicates not visible normally.
819     // any new libraries found here are NEVER used, but they are added to the
820     // end of already-found headers, to allow Compiler to report them if
821     // the sketch tries to use them.
822     for (UserLibrary lib : librariesIndexer.getInstalledLibrariesWithDuplicates()) {
823       try {
824         String headers[] = headerListFromIncludePath(lib.getSrcFolder());
825         for (String header : headers) {
826           LibraryList list = importToLibraryTable.get(header);
827           if (list != null) {
828             if (!(list.hasLibrary(lib))) {
829               list.addLast(lib);
830               //System.out.println(" duplicate lib: " + lib.getInstalledFolder().getPath());
831             }
832           }
833         }
834         } catch (IOException e) {
835       }
836     }
837   }
838 
initParameters(String args[])839   static public void initParameters(String args[]) throws Exception {
840     String preferencesFile = null;
841 
842     // Do a first pass over the commandline arguments, the rest of them
843     // will be processed by the Base constructor. Note that this loop
844     // does not look at the last element of args, to prevent crashing
845     // when no parameter was specified to an option. Later, Base() will
846     // then show an error for these.
847     for (int i = 0; i < args.length - 1; i++) {
848       if (args[i].equals("--preferences-file")) {
849         ++i;
850         preferencesFile = args[i];
851         continue;
852       }
853     }
854 
855     // run static initialization that grabs all the prefs
856     PreferencesData.init(absoluteFile(preferencesFile));
857   }
858 
859   /**
860    * Produce a sanitized name that fits our standards for likely to work.
861    * <p/>
862    * Java classes have a wider range of names that are technically allowed
863    * (supposedly any Unicode name) than what we support. The reason for
864    * going more narrow is to avoid situations with text encodings and
865    * converting during the process of moving files between operating
866    * systems, i.e. uploading from a Windows machine to a Linux server,
867    * or reading a FAT32 partition in OS X and using a thumb drive.
868    * <p/>
869    * This helper function replaces everything but A-Z, a-z, and 0-9 with
870    * underscores. Also disallows starting the sketch name with a digit.
871    */
sanitizeName(String origName)872   static public String sanitizeName(String origName) {
873     char c[] = origName.toCharArray();
874     StringBuffer buffer = new StringBuffer();
875 
876     // can't lead with a digit, so start with an underscore
877     if ((c[0] >= '0') && (c[0] <= '9')) {
878       buffer.append('_');
879     }
880     for (int i = 0; i < c.length; i++) {
881       if (((c[i] >= '0') && (c[i] <= '9')) ||
882           ((c[i] >= 'a') && (c[i] <= 'z')) ||
883           ((c[i] >= 'A') && (c[i] <= 'Z')) ||
884           ((i > 0) && (c[i] == '-')) ||
885           ((i > 0) && (c[i] == '.'))) {
886         buffer.append(c[i]);
887       } else {
888         buffer.append('_');
889       }
890     }
891     // let's not be ridiculous about the length of filenames.
892     // in fact, Mac OS 9 can handle 255 chars, though it can't really
893     // deal with filenames longer than 31 chars in the Finder.
894     // but limiting to that for sketches would mean setting the
895     // upper-bound on the character limit here to 25 characters
896     // (to handle the base name + ".class")
897     if (buffer.length() > 63) {
898       buffer.setLength(63);
899     }
900     return buffer.toString();
901   }
902 
903   /**
904    * Save the content of a String into a file
905    * - Save the content into a temp file
906    * - Find the canonical path of the file (if it's a symlink, follow it)
907    * - Remove the original file
908    * - Move temp file to original path
909    * This ensures that the file is not getting truncated if the disk is full
910    */
saveFile(String str, File file)911   static public void saveFile(String str, File file) throws IOException {
912     File temp = File.createTempFile(file.getName(), null, file.getParentFile());
913     PApplet.saveStrings(temp, new String[] { str });
914 
915     try {
916       file = file.getCanonicalFile();
917     } catch (IOException e) {
918     }
919 
920     if (file.exists()) {
921       boolean result = file.delete();
922       if (!result) {
923         throw new IOException(
924       I18n.format(
925         tr("Could not remove old version of {0}"),
926         file.getAbsolutePath()));
927       }
928     }
929     boolean result = temp.renameTo(file);
930     if (!result) {
931       throw new IOException(
932     I18n.format(
933       tr("Could not replace {0}"),
934       file.getAbsolutePath()));
935     }
936   }
937 
selectBoard(TargetBoard targetBoard)938   static public void selectBoard(TargetBoard targetBoard) {
939     TargetPlatform targetPlatform = targetBoard.getContainerPlatform();
940     TargetPackage targetPackage = targetPlatform.getContainerPackage();
941 
942     PreferencesData.set("target_package", targetPackage.getId());
943     PreferencesData.set("target_platform", targetPlatform.getId());
944     PreferencesData.set("board", targetBoard.getId());
945 
946     File platformFolder = targetPlatform.getFolder();
947     PreferencesData.set("runtime.platform.path", platformFolder.getAbsolutePath());
948     PreferencesData.set("runtime.hardware.path", platformFolder.getParentFile().getAbsolutePath());
949   }
950 
selectSerialPort(String port)951   public static void selectSerialPort(String port) {
952     PreferencesData.set("serial.port", port);
953     BoardPort boardPort = getDiscoveryManager().find(port, true);
954     if (boardPort != null) {
955       PreferencesData.set("serial.port.iserial", boardPort.getPrefs().get("iserial"));
956     }
957     String portFile = port;
958     if (port.startsWith("/dev/")) {
959       portFile = portFile.substring(5);
960     }
961     PreferencesData.set("serial.port.file", portFile);
962   }
963 
showError(String title, String message, int exit_code)964   static public void showError(String title, String message, int exit_code) {
965     showError(title, message, null, exit_code);
966   }
967 
showError(String title, String message, Throwable e)968   static public void showError(String title, String message, Throwable e) {
969     notifier.showError(title, message, e, 1);
970   }
971 
972   /**
973    * Show an error message that's actually fatal to the program.
974    * This is an error that can't be recovered. Use showWarning()
975    * for errors that allow P5 to continue running.
976    */
showError(String title, String message, Throwable e, int exit_code)977   static public void showError(String title, String message, Throwable e, int exit_code) {
978     notifier.showError(title, message, e, exit_code);
979   }
980 
981   /**
982    * "No cookie for you" type messages. Nothing fatal or all that
983    * much of a bummer, but something to notify the user about.
984    */
showMessage(String title, String message)985   static public void showMessage(String title, String message) {
986     notifier.showMessage(title, message);
987   }
988 
989   /**
990    * Non-fatal error message with optional stack trace side dish.
991    */
showWarning(String title, String message, Exception e)992   static public void showWarning(String title, String message, Exception e) {
993     notifier.showWarning(title, message, e);
994   }
995 
996 }
997