1 /* 2 * This file is part of Arduino. 3 * 4 * Copyright 2014 Arduino LLC (http://www.arduino.cc/) 5 * 6 * Arduino is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 * 20 * As a special exception, you may use this file as part of a free software 21 * library without restriction. Specifically, if other files instantiate 22 * templates or use macros or inline functions from this file, or you compile 23 * this file and link it with other files to produce an executable, this 24 * file does not by itself cause the resulting executable to be covered by 25 * the GNU General Public License. This exception does not however 26 * invalidate any other reasons why the executable file might be covered by 27 * the GNU General Public License. 28 */ 29 30 package cc.arduino.contributions.libraries; 31 32 import cc.arduino.Constants; 33 import cc.arduino.contributions.libraries.filters.LibraryInstalledInsideCore; 34 import cc.arduino.contributions.libraries.filters.TypePredicate; 35 import cc.arduino.contributions.packages.ContributedPlatform; 36 import com.fasterxml.jackson.databind.DeserializationFeature; 37 import com.fasterxml.jackson.databind.ObjectMapper; 38 import com.fasterxml.jackson.module.mrbean.MrBeanModule; 39 import org.apache.commons.compress.utils.IOUtils; 40 import processing.app.BaseNoGui; 41 import processing.app.I18n; 42 import processing.app.helpers.FileUtils; 43 import processing.app.helpers.filefilters.OnlyDirs; 44 import processing.app.packages.LegacyUserLibrary; 45 import processing.app.packages.LibraryList; 46 import processing.app.packages.UserLibrary; 47 48 import java.io.File; 49 import java.io.FileInputStream; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.util.ArrayList; 53 import java.util.Collections; 54 import java.util.List; 55 56 import static processing.app.I18n.tr; 57 58 public class LibrariesIndexer { 59 60 private LibrariesIndex index; 61 private final LibraryList installedLibraries = new LibraryList(); 62 private final LibraryList installedLibrariesWithDuplicates = new LibraryList(); 63 private List<File> librariesFolders; 64 private final File indexFile; 65 private final File stagingFolder; 66 private File sketchbookLibrariesFolder; 67 68 private final List<String> badLibNotified = new ArrayList<>(); 69 LibrariesIndexer(File preferencesFolder)70 public LibrariesIndexer(File preferencesFolder) { 71 indexFile = new File(preferencesFolder, "library_index.json"); 72 stagingFolder = new File(new File(preferencesFolder, "staging"), "libraries"); 73 } 74 parseIndex()75 public void parseIndex() throws IOException { 76 if (!indexFile.exists()) { 77 index = new EmptyLibrariesIndex(); 78 } else { 79 parseIndex(indexFile); 80 } 81 // TODO: resolve libraries inner references 82 } 83 parseIndex(File file)84 private void parseIndex(File file) throws IOException { 85 InputStream indexIn = null; 86 try { 87 indexIn = new FileInputStream(file); 88 ObjectMapper mapper = new ObjectMapper(); 89 mapper.registerModule(new MrBeanModule()); 90 mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); 91 mapper.configure(DeserializationFeature.EAGER_DESERIALIZER_FETCH, true); 92 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 93 index = mapper.readValue(indexIn, LibrariesIndex.class); 94 95 index.getLibraries() 96 .stream() 97 .filter(library -> library.getCategory() == null || "".equals(library.getCategory()) || !Constants.LIBRARY_CATEGORIES.contains(library.getCategory())) 98 .forEach(library -> library.setCategory("Uncategorized")); 99 } finally { 100 IOUtils.closeQuietly(indexIn); 101 } 102 } 103 setLibrariesFolders(List<File> _librariesFolders)104 public void setLibrariesFolders(List<File> _librariesFolders) { 105 librariesFolders = _librariesFolders; 106 rescanLibraries(); 107 } 108 rescanLibraries()109 public void rescanLibraries() { 110 // Clear all installed flags 111 installedLibraries.clear(); 112 installedLibrariesWithDuplicates.clear(); 113 114 if (index.getLibraries() == null) { 115 return; 116 } 117 118 for (ContributedLibrary lib : index.getLibraries()) { 119 lib.setInstalled(false); 120 } 121 122 // Rescan libraries 123 for (File folder : librariesFolders) { 124 scanInstalledLibraries(folder, folder.equals(sketchbookLibrariesFolder)); 125 } 126 127 installedLibraries.stream().filter(new TypePredicate("Contributed")).filter(new LibraryInstalledInsideCore()).forEach(userLibrary -> { 128 ContributedPlatform platform = BaseNoGui.indexer.getPlatformByFolder(userLibrary.getInstalledFolder()); 129 userLibrary.setTypes(Collections.singletonList(platform.getCategory())); 130 }); 131 } 132 scanInstalledLibraries(File folder, boolean isSketchbook)133 private void scanInstalledLibraries(File folder, boolean isSketchbook) { 134 File list[] = folder.listFiles(OnlyDirs.ONLY_DIRS); 135 // if a bad folder or something like that, this might come back null 136 if (list == null) 137 return; 138 139 for (File subfolder : list) { 140 if (!BaseNoGui.isSanitaryName(subfolder.getName())) { 141 142 // Detect whether the current folder name has already had a notification. 143 if (!badLibNotified.contains(subfolder.getName())) { 144 145 badLibNotified.add(subfolder.getName()); 146 147 String mess = I18n.format(tr("The library \"{0}\" cannot be used.\n" 148 + "Library names must contain only basic letters and numbers.\n" 149 + "(ASCII only and no spaces, and it cannot start with a number)"), 150 subfolder.getName()); 151 BaseNoGui.showMessage(tr("Ignoring bad library name"), mess); 152 } 153 continue; 154 } 155 156 try { 157 scanLibrary(subfolder, isSketchbook); 158 } catch (IOException e) { 159 System.out.println(I18n.format(tr("Invalid library found in {0}: {1}"), subfolder, e.getMessage())); 160 } 161 } 162 } 163 scanLibrary(File folder, boolean isSketchbook)164 private void scanLibrary(File folder, boolean isSketchbook) throws IOException { 165 boolean readOnly = !FileUtils.isSubDirectory(sketchbookLibrariesFolder, folder); 166 167 // A library is considered "legacy" if it doesn't contains 168 // a file called "library.properties" 169 File check = new File(folder, "library.properties"); 170 if (!check.exists() || !check.isFile()) { 171 // Create a legacy library and exit 172 LegacyUserLibrary lib = LegacyUserLibrary.create(folder); 173 lib.setReadOnly(readOnly); 174 String[] headers = BaseNoGui.headerListFromIncludePath(lib.getSrcFolder()); 175 if (headers.length == 0) { 176 throw new IOException(lib.getSrcFolder().getAbsolutePath()); 177 } 178 installedLibraries.addOrReplace(lib); 179 if (isSketchbook) { 180 installedLibrariesWithDuplicates.add(lib); 181 } else { 182 installedLibrariesWithDuplicates.addOrReplace(lib); 183 } 184 return; 185 } 186 187 // Create a regular library 188 UserLibrary lib = UserLibrary.create(folder); 189 lib.setReadOnly(readOnly); 190 String[] headers = BaseNoGui.headerListFromIncludePath(lib.getSrcFolder()); 191 if (headers.length == 0) { 192 throw new IOException(lib.getSrcFolder().getAbsolutePath()); 193 } 194 installedLibraries.addOrReplaceArchAware(lib); 195 if (isSketchbook) { 196 installedLibrariesWithDuplicates.add(lib); 197 } else { 198 installedLibrariesWithDuplicates.addOrReplaceArchAware(lib); 199 } 200 201 // Check if we can find the same library in the index 202 // and mark it as installed 203 ContributedLibrary foundLib = index.find(lib.getName(), lib.getParsedVersion()); 204 if (foundLib != null) { 205 foundLib.setInstalled(true); 206 foundLib.setInstalledFolder(folder); 207 foundLib.setReadOnly(readOnly); 208 lib.setTypes(foundLib.getTypes()); 209 } 210 211 if (lib.isReadOnly() && lib.getTypes() == null && !lib.getDeclaredTypes().isEmpty()) { 212 lib.setTypes(lib.getDeclaredTypes()); 213 } 214 215 if (lib.getTypes() == null) { 216 lib.setTypes(Collections.singletonList("Contributed")); 217 } 218 } 219 getIndex()220 public LibrariesIndex getIndex() { 221 return index; 222 } 223 getInstalledLibraries()224 public LibraryList getInstalledLibraries() { 225 return new LibraryList(installedLibraries); 226 } 227 228 // Same as getInstalledLibraries(), but allow duplicates between 229 // builtin+package libraries and sketchbook installed libraries. 230 // However, do not report duplicates among builtin and packages, to 231 // allow any package to override builtin libraries without being 232 // reported as duplicates. getInstalledLibrariesWithDuplicates()233 public LibraryList getInstalledLibrariesWithDuplicates() { 234 return installedLibrariesWithDuplicates; 235 } 236 getStagingFolder()237 public File getStagingFolder() { 238 return stagingFolder; 239 } 240 241 /** 242 * Set the sketchbook library folder. <br /> 243 * New libraries will be installed here. <br /> 244 * Libraries not found on this folder will be marked as read-only. 245 */ setSketchbookLibrariesFolder(File folder)246 public void setSketchbookLibrariesFolder(File folder) { 247 this.sketchbookLibrariesFolder = folder; 248 } 249 getSketchbookLibrariesFolder()250 public File getSketchbookLibrariesFolder() { 251 return sketchbookLibrariesFolder; 252 } 253 getIndexFile()254 public File getIndexFile() { 255 return indexFile; 256 } 257 } 258