package io.sloeber.core.api;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import io.sloeber.core.api.BoardsManager.PlatformTree.IndexFile;
import io.sloeber.core.api.BoardsManager.PlatformTree.InstallableVersion;
import io.sloeber.core.api.BoardsManager.PlatformTree.Platform;
import io.sloeber.core.common.Common;
import io.sloeber.core.common.ConfigurationPreferences;
import io.sloeber.core.common.Const;
import io.sloeber.core.common.InstancePreferences;
import io.sloeber.core.managers.ArduinoPlatform;
import io.sloeber.core.managers.Board;
import io.sloeber.core.managers.Manager;
import io.sloeber.core.managers.Package;
import io.sloeber.core.managers.PackageIndex;
import io.sloeber.core.tools.Messages;
import io.sloeber.core.tools.TxtFile;
/**
* This class groups both boards installed by the hardware manager and boards
* installed locally.
*
* @author jantje
*
*/
public class BoardsManager {
private static final String INO = "ino"; //$NON-NLS-1$
private static final String PDE = "pde";//$NON-NLS-1$
private static final String CPP = "cpp";//$NON-NLS-1$
private static final String C = "c";//$NON-NLS-1$
/**
* Gets the board descriptor based on the information provided. If
* jsonFileName="local" the board is assumed not to be installed by the
* boards manager. Otherwise the boardsmanager is queried to find the board
* descriptor. In this case the latest installed board will be returned
*
* @param jsonFileName
* equals to "local" or the name of the json file used by the
* boards manager to install the boards
* @param packageName
* if jsonFileName equals "local" the filename of the boards.txt
* containing the boards. otherwise the name of the package
* containing the board
* @param platformName
* ignored if jsonFileName equals "local" otherwise the name of
* the platform containing the board
* @param boardID
* the id of the board in the boards.txt file
* @param options
* the options to specify the board (the menu named on the
* boards.txt file)
* @return The class BoardDescriptor or null
*/
static public BoardDescriptor getBoardDescriptor(String jsonFileName, String packageName, String platformName,
String boardID, Map<String, String> options) {
if (jsonFileName.equals("local")) { //$NON-NLS-1$
return BoardDescriptor.makeBoardDescriptor(new File(packageName), boardID, options);
}
return getNewestBoardIDFromBoardsManager(jsonFileName, packageName, platformName, boardID, options);
}
static private BoardDescriptor getNewestBoardIDFromBoardsManager(String jsonFileName, String packageName,
String platformName, String boardID, Map<String, String> options) {
List<Board> boards = null;
Package thePackage = Manager.getPackage(jsonFileName, packageName);
if (thePackage == null) {
// fail("failed to find package:" + this.mPackageName);
return null;
}
ArduinoPlatform platform = thePackage.getLatestPlatform(platformName);
if (platform == null) {
// fail("failed to find platform " + this.mPlatform + " in
// package:" + this.mPackageName);
return null;
}
boards = platform.getBoards();
if (boards == null) {
// fail("No boards found");
return null;
}
for (Board curBoard : boards) {
if (curBoard.getId().equals(boardID)) {
java.io.File boardsFile = curBoard.getPlatform().getBoardsFile();
BoardDescriptor boardid = BoardDescriptor.makeBoardDescriptor(boardsFile, curBoard.getId(), options);
return boardid;
}
}
return null;
}
public static void addPackageURLs(HashSet<String> packageUrlsToAdd, boolean forceDownload) {
Manager.addJsonURLs(packageUrlsToAdd, forceDownload);
}
public static void removePackageURLs(Set<String> packageUrlsToRemove) {
Manager.removePackageURLs(packageUrlsToRemove);
}
public static void installAllLatestPlatforms() {
NullProgressMonitor monitor = new NullProgressMonitor();
List<Package> allPackages = Manager.getPackages();
for (Package curPackage : allPackages) {
Collection<ArduinoPlatform> latestPlatforms = curPackage.getLatestPlatforms();
for (ArduinoPlatform curPlatform : latestPlatforms) {
curPlatform.install(monitor);
}
}
}
public static void referenceLocallInstallation(String newHardwarePath) {
String currentPaths[] = InstancePreferences.getPrivateHardwarePaths();
String newPaths[] = new String[currentPaths.length + 1];
for (int i = 0; i < currentPaths.length; i++) {
if (currentPaths[i].equals(newHardwarePath)) {
return;
}
newPaths[i] = currentPaths[i];
}
newPaths[currentPaths.length] = newHardwarePath;
InstancePreferences.setPrivateHardwarePaths(newPaths);
}
public static boolean isReady() {
return Manager.isReady();
}
/**
* find all examples for this type of board. That is the examples provided
* by Arduino The examples provided by the common libraries The examples
* provided by the private libraries The examples provided by the platform
* the board belongs to
*
* If the boardID is null there will be no platform examples
*
* @param boardID
* @return
*/
public static TreeMap<String, IPath> getAllExamples(BoardDescriptor boardID) {
TreeMap<String, IPath> examples = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// Get the examples of the library manager installed libraries
String libLocations[] = InstancePreferences.getPrivateLibraryPaths();
Path exampleLocation = new Path(ConfigurationPreferences.getInstallationPathExamples().toString());
IPath CommonLibLocation = ConfigurationPreferences.getInstallationPathLibraries();
if (CommonLibLocation.toFile().exists()) {
examples.putAll(getLibExampleFolders(CommonLibLocation));
}
// get the examples from the user provide library locations
if (libLocations != null) {
for (String curLibLocation : libLocations) {
if (new File(curLibLocation).exists()) {
examples.putAll(getLibExampleFolders(new Path(curLibLocation)));
}
}
}
// Get the examples from the example locations
if (exampleLocation.toFile().exists()) {
examples.putAll(getExamplesFromFolder("", exampleLocation)); //$NON-NLS-1$
}
// Get the examples of the libraries from the selected hardware
// This one should be the last as hasmap overwrites doubles. This way
// hardware libraries are preferred to others
if (boardID != null) {
IPath platformPath = boardID.getPlatformPath();
if (platformPath.toFile().exists()) {
examples.putAll(getLibExampleFolders(platformPath.append(Const.LIBRARY_PATH_SUFFIX)));
}
}
return examples;
}
/***
* finds all the example folders for both the version including and without
* version libraries
*
* @param location
* The parent folder of the libraries
*/
private static TreeMap<String, IPath> getLibExampleFolders(IPath LibRoot) {
TreeMap<String, IPath> examples = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
String[] Libs = LibRoot.toFile().list();
if (Libs == null) {
// Either dir does not exist or is not a directory
} else {
for (String curLib : Libs) {
IPath Lib_examples = LibRoot.append(curLib).append("examples");//$NON-NLS-1$
IPath Lib_Examples = LibRoot.append(curLib).append("Examples");//$NON-NLS-1$
if (Lib_examples.toFile().isDirectory()) {
examples.putAll(getExamplesFromFolder(curLib, Lib_examples));
} else if (Lib_Examples.toFile().isDirectory()) {
examples.putAll(getExamplesFromFolder(curLib, Lib_Examples));
} else // nothing found directly so maybe this is a version
// based lib
{
String[] versions = LibRoot.append(curLib).toFile().list();
if (versions != null) {
if (versions.length == 1) {// There can only be 1
// version of a lib
Lib_examples = LibRoot.append(curLib).append(versions[0]).append("examples");//$NON-NLS-1$
Lib_Examples = LibRoot.append(curLib).append(versions[0]).append("Examples");//$NON-NLS-1$
if (Lib_examples.toFile().isDirectory()) {
examples.putAll(getExamplesFromFolder(curLib, Lib_examples));
} else if (Lib_Examples.toFile().isDirectory()) {
examples.putAll(getExamplesFromFolder(curLib, Lib_Examples));
}
}
}
}
}
}
return examples;
}
/**
* This method adds a folder recursively examples. Leaves containing ino
* files are assumed to be examples
*
* @param File
*/
private static TreeMap<String, IPath> getExamplesFromFolder(String prefix, IPath location) {
TreeMap<String, IPath> examples = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
File[] children = location.toFile().listFiles();
if (children == null) {
// Either dir does not exist or is not a directory
} else {
for (File exampleFolder : children) {
Path pt = new Path(exampleFolder.toString());
String extension = pt.getFileExtension();
if (exampleFolder.isDirectory()) {
examples.putAll(getExamplesFromFolder(prefix + ' ' + location.lastSegment() + '?',
new Path(exampleFolder.toString())));
} else if (INO.equalsIgnoreCase(extension) || PDE.equalsIgnoreCase(extension)
|| CPP.equalsIgnoreCase(extension) || C.equalsIgnoreCase(extension)) {
examples.put(prefix + location.lastSegment(), location);
}
}
}
return examples;
}
public static void setAutoImportLibraries(boolean booleanValue) {
InstancePreferences.setAutomaticallyImportLibraries(booleanValue);
}
public static boolean getAutoImportLibraries(boolean booleanValue) {
return InstancePreferences.getAutomaticallyImportLibraries();
}
public static String[] getBoardNames(String boardFile) {
TxtFile theBoardsFile = new TxtFile(new File(boardFile));
return theBoardsFile.getAllNames();
}
/**
* Searches for all boards.txt files from the hardware folders and the
* boards manager
*
* @return all the boards.txt files with full path and in a case insensitive
* order
*/
public static String[] getAllBoardsFiles() {
String hardwareFolders[] = getHardwarePaths();
TreeSet<String> boardFiles = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
for (String CurFolder : hardwareFolders) {
searchFiles(new File(CurFolder), boardFiles, Const.BOARDS_FILE_NAME, 6);
}
if (boardFiles.size() == 0) {
Common.log(new Status(IStatus.ERROR, Const.CORE_PLUGIN_ID,
Messages.Helpers_No_boards_txt_found + String.join("\n", hardwareFolders), null)); //$NON-NLS-1$
return null;
}
return boardFiles.toArray(new String[boardFiles.size()]);
}
private static void searchFiles(File folder, TreeSet<String> Hardwarelists, String Filename, int depth) {
if (depth > 0) {
File[] a = folder.listFiles();
if (a == null) {
Common.log(new Status(IStatus.INFO, Const.CORE_PLUGIN_ID,
Messages.Helpers_The_folder + folder + Messages.Helpers_is_empty, null));
return;
}
for (File f : a) {
if (f.isDirectory()) {
searchFiles(f, Hardwarelists, Filename, depth - 1);
} else if (f.getName().equals(Filename)) {
Hardwarelists.add(new Path(f.toString()).toString());
}
}
}
}
/**
* Gets all the folders that can contain hardware
*
* @return a list of all the folder locations that can contain hardware
*/
private static String[] getHardwarePaths() {
return (InstancePreferences.getPrivateHardwarePathsString() + File.pathSeparator
+ ConfigurationPreferences.getInstallationPath()).split(File.pathSeparator);
}
public static void setPrivateHardwarePaths(String[] hardWarePaths) {
InstancePreferences.setPrivateHardwarePaths(hardWarePaths);
}
public static void setPrivateLibraryPaths(String[] libraryPaths) {
InstancePreferences.setPrivateLibraryPaths(libraryPaths);
}
public static class PlatformTree {
private TreeMap<String, IndexFile> IndexFiles = new TreeMap<>();
public class InstallableVersion implements Comparable<InstallableVersion> {
private Platform platform;
private ArduinoPlatform myInternalPlatformm;
private boolean isInstalled;
public InstallableVersion(ArduinoPlatform internalPlatformm, Platform platform) {
this.platform = platform;
this.myInternalPlatformm = internalPlatformm;
this.isInstalled = this.myInternalPlatformm.isInstalled();
}
public VersionNumber getVersion() {
return new VersionNumber(this.myInternalPlatformm.getVersion());
}
public boolean isInstalled() {
return this.isInstalled;
}
public void setInstalled(boolean isInstalled) {
this.isInstalled = isInstalled;
}
@Override
public int compareTo(InstallableVersion o) {
return getVersion().compareTo(o.getVersion());
}
public Platform getPlatform() {
return this.platform;
}
public ArduinoPlatform getInternalPlatform() {
return this.myInternalPlatformm;
}
}
public class Platform implements Comparable<Platform> {
private String name;
private String architecture;
protected TreeSet<InstallableVersion> versions = new TreeSet<>();
protected String boards;
private Package pac;
public String getArchitecture() {
return this.architecture;
}
public String getBoards() {
return this.boards;
}
public Platform(ArduinoPlatform internalPlatformm, Package pac) {
this.name = internalPlatformm.getName();
this.architecture = internalPlatformm.getArchitecture();
this.versions.add(new InstallableVersion(internalPlatformm, this));
this.boards = String.join("\n", internalPlatformm.getBoardNames()); //$NON-NLS-1$
this.pac = pac;
}
public void addVersion(ArduinoPlatform internalPlatformm) {
this.versions.add(new InstallableVersion(internalPlatformm, this));
}
public Collection<InstallableVersion> getVersions() {
return this.versions;
}
public String getName() {
return this.name;
}
public String getLatest() {
return this.versions.last().toString();
}
@Override
public int compareTo(Platform other) {
return this.name.compareToIgnoreCase(other.name);
}
public InstallableVersion getVersion(VersionNumber versionNumber) {
for (InstallableVersion curVersion : this.versions) {
if (curVersion.getVersion().compareTo(versionNumber) == 0) {
return curVersion;
}
}
return null;
}
public boolean isInstalled() {
for (InstallableVersion curVersion : this.versions) {
if (curVersion.isInstalled()) {
return true;
}
}
return false;
}
public Package getPackage() {
return this.pac;
}
}
/**
* This class represents the json file on disk
*/
public class IndexFile implements Comparable<IndexFile> {
protected File jsonFile;
protected TreeMap<String, Package> packages = new TreeMap<>();
public IndexFile(File jsonFile) {
this.jsonFile = jsonFile;
}
@Override
public int compareTo(IndexFile other) {
return this.jsonFile.compareTo(other.jsonFile);
}
public Collection<Package> getAllPackages() {
return this.packages.values();
}
public String getNiceName() {
return this.jsonFile.getName();
}
public String getFullName() {
return this.jsonFile.getPath();
}
/**
* is one ore more packages of this index file installed
*
* @return returns true if at least one version of one child
* platform of a child packageis installed
*/
public boolean isInstalled() {
for (Package curpackage : this.packages.values()) {
if (curpackage.isInstalled()) {
return true;
}
}
return false;
}
}
public class Package implements Comparable<Package> {
public String getMaintainer() {
return this.maintainer;
}
public URL getWebsiteURL() {
return this.websiteURL;
}
public String getEmail() {
return this.email;
}
private String name;
private String maintainer;
private URL websiteURL;
private String email;
protected TreeMap<String, Platform> platforms = new TreeMap<>();
private IndexFile indexFile;
public Package(String name) {
this.name = name;
}
public Package(io.sloeber.core.managers.Package pack, IndexFile indexFile) {
this.name = pack.getName();
this.maintainer = pack.getMaintainer();
try {
this.websiteURL = new URL(pack.getWebsiteURL());
} catch (MalformedURLException e) {
// ignore this error
}
this.email = pack.getEmail();
this.indexFile = indexFile;
}
public String getName() {
return this.name;
}
public Collection<Platform> getPlatforms() {
return this.platforms.values();
}
@Override
public int compareTo(Package other) {
return this.name.compareToIgnoreCase(other.name);
}
/**
* is one ore more platforms of this package installed
*
* @return returns true if at least one version of one child
* platform is installed
*/
public boolean isInstalled() {
for (Platform curplatform : this.platforms.values()) {
if (curplatform.isInstalled()) {
return true;
}
}
return false;
}
public IndexFile getIndexFile() {
return this.indexFile;
}
}
public PlatformTree() {
List<PackageIndex> packageIndexes = Manager.getPackageIndices();
for (PackageIndex curPackageIndex : packageIndexes) {
IndexFile curIndexFile = new IndexFile(curPackageIndex.getJsonFile());
this.IndexFiles.put(curPackageIndex.getJsonFileName(), curIndexFile);
for (io.sloeber.core.managers.Package curInternalPackage : curPackageIndex.getPackages()) {
Package curPackage = new Package(curInternalPackage, curIndexFile);
curIndexFile.packages.put(curPackage.getName(), curPackage);
for (ArduinoPlatform curInternalPlatform : curInternalPackage.getPlatforms()) {
Platform curPlatform = new Platform(curInternalPlatform, curPackage);
Platform foundPlatform = curPackage.platforms.get(curPlatform.getName());
if (foundPlatform == null) {
curPackage.platforms.put(curPlatform.getName(), curPlatform);
} else {
foundPlatform.addVersion(curInternalPlatform);
}
}
}
}
}
public Collection<IndexFile> getAllIndexFiles() {
return this.IndexFiles.values();
}
public Set<Package> getAllPackages() {
Set<Package> all = new TreeSet<>();
for (IndexFile indexFile : this.IndexFiles.values()) {
all.addAll(indexFile.getAllPackages());
}
return all;
}
public Collection<Platform> getAllPlatforms() {
Set<Platform> all = new TreeSet<>();
for (IndexFile curIndexFile : this.IndexFiles.values()) {
for (Package curPackage : curIndexFile.getAllPackages()) {
all.addAll(curPackage.getPlatforms());
}
}
return all;
}
public IndexFile getIndexFile(PackageIndex packageIndex) {
return this.IndexFiles.get(packageIndex.getJsonFileName());
}
@SuppressWarnings("static-method")
public Package getPackage(IndexFile indexFile, io.sloeber.core.managers.Package curInternalPackage) {
return indexFile.packages.get(curInternalPackage.getName());
}
@SuppressWarnings("static-method")
public Platform getPlatform(Package curPackage, ArduinoPlatform curInternalPlatform) {
return curPackage.platforms.get(curInternalPlatform.getName());
}
}
public static IStatus setPlatformTree(PlatformTree platformTree, IProgressMonitor monitor, MultiStatus status) {
if (!Manager.isReady()) {
status.add(new Status(IStatus.ERROR, Const.CORE_PLUGIN_ID, "BoardsManager is still Bussy", null)); //$NON-NLS-1$
return status;
}
try {
Manager.setReady(false);
for (IndexFile curIndexFile : platformTree.getAllIndexFiles()) {
for (io.sloeber.core.api.BoardsManager.PlatformTree.Package curPackage : curIndexFile
.getAllPackages()) {
for (Platform curPlatform : curPackage.getPlatforms()) {
for (InstallableVersion curVersion : curPlatform.getVersions()) {
if (curVersion.isInstalled()) {
status.add(curVersion.getInternalPlatform().install(monitor));
} else {
status.add(curVersion.getInternalPlatform().remove(monitor));
}
}
}
}
}
} catch (Exception e) {
// do nothing
}
Manager.setReady(true);
return status;
}
public static boolean getAutoImportLibraries() {
return InstancePreferences.getAutomaticallyImportLibraries();
}
public static String getPrivateHardwarePathsString() {
return InstancePreferences.getPrivateHardwarePathsString();
}
/**
* returns all the menu names for all installed platforms. The return is
* sorted and unique
*
* @return
*/
public static Set<String> getAllMenuNames() {
Set<String> ret = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
String[] boardFiles = getAllBoardsFiles();
for (String curBoardFile : boardFiles) {
TxtFile txtFile = new TxtFile(new File(curBoardFile));
ret.addAll(txtFile.getMenuNames());
}
return ret;
}
public static TreeMap<String, String> getAllmenus() {
TreeMap<String, String> ret = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
String[] boardFiles = getAllBoardsFiles();
for (String curBoardFile : boardFiles) {
TxtFile txtFile = new TxtFile(new File(curBoardFile));
ret.putAll(txtFile.getMenus());
}
return ret;
}
/**
* Remove all packages that have a more recent version
*/
public static void onlyKeepLatestPlatforms() {
Manager.onlyKeepLatestPlatforms();
}
}