blob: d894855033be8fe05dff11256988e7693141090e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.arduino.core.internal.board;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.eclipse.cdt.arduino.core.internal.Activator;
import org.eclipse.cdt.arduino.core.internal.ArduinoPreferences;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.osgi.service.prefs.BackingStoreException;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
public class ArduinoManager {
// Build tool ids
public static final String BOARD_OPTION_ID = "org.eclipse.cdt.arduino.option.board"; //$NON-NLS-1$
public static final String PLATFORM_OPTION_ID = "org.eclipse.cdt.arduino.option.platform"; //$NON-NLS-1$
public static final String PACKAGE_OPTION_ID = "org.eclipse.cdt.arduino.option.package"; //$NON-NLS-1$
public static final String AVR_TOOLCHAIN_ID = "org.eclipse.cdt.arduino.toolChain.avr"; //$NON-NLS-1$
public static final String LIBRARIES_URL = "http://downloads.arduino.cc/libraries/library_index.json"; //$NON-NLS-1$
public static final String LIBRARIES_FILE = "library_index.json"; //$NON-NLS-1$
private static final String LIBRARIES = "libraries"; //$NON-NLS-1$
// arduinocdt install properties
private static final String VERSION_KEY = "version"; //$NON-NLS-1$
private static final String ACCEPTED_KEY = "accepted"; //$NON-NLS-1$
private static final String VERSION = "2"; //$NON-NLS-1$
private Properties props;
private Map<String, ArduinoPackage> packages;
private Map<String, ArduinoLibrary> installedLibraries;
private Path getVersionFile() {
return ArduinoPreferences.getArduinoHome().resolve(".version"); //$NON-NLS-1$
}
private synchronized void init() throws CoreException {
if (props == null) {
if (!Files.exists(ArduinoPreferences.getArduinoHome())) {
try {
Files.createDirectories(ArduinoPreferences.getArduinoHome());
} catch (IOException e) {
throw Activator.coreException(e);
}
}
props = new Properties();
Path propsFile = getVersionFile();
if (Files.exists(propsFile)) {
try (FileReader reader = new FileReader(propsFile.toFile())) {
props.load(reader);
} catch (IOException e) {
throw Activator.coreException(e);
}
}
// See if we need a conversion
int version = Integer.parseInt(props.getProperty(VERSION_KEY, "1")); //$NON-NLS-1$
if (version < Integer.parseInt(VERSION)) {
// Need to move the directories around
convertPackageDirs();
props.setProperty(VERSION_KEY, VERSION);
try (FileWriter writer = new FileWriter(getVersionFile().toFile())) {
props.store(writer, ""); //$NON-NLS-1$
} catch (IOException e) {
throw Activator.coreException(e);
}
}
}
}
private void convertPackageDirs() throws CoreException {
Path packagesDir = ArduinoPreferences.getArduinoHome().resolve("packages"); //$NON-NLS-1$
if (!Files.isDirectory(packagesDir)) {
return;
}
try {
Files.list(packagesDir).forEach(path -> {
try {
Path hardwarePath = path.resolve("hardware"); //$NON-NLS-1$
Path badPath = hardwarePath.resolve(path.getFileName());
Path tmpDir = Files.createTempDirectory(packagesDir, "tbd"); //$NON-NLS-1$
Path badPath2 = tmpDir.resolve(badPath.getFileName());
Files.move(badPath, badPath2);
Files.list(badPath2).forEach(archPath -> {
try {
Optional<Path> latest = Files.list(archPath)
.reduce((path1, path2) -> compareVersions(path1.getFileName().toString(),
path2.getFileName().toString()) > 0 ? path1 : path2);
if (latest.isPresent()) {
Files.move(latest.get(), hardwarePath.resolve(archPath.getFileName()));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
recursiveDelete(tmpDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
} catch (RuntimeException | IOException e) {
throw Activator.coreException(e);
}
}
public void convertLibrariesDir() throws CoreException {
Path librariesDir = ArduinoPreferences.getArduinoHome().resolve("libraries"); //$NON-NLS-1$
if (!Files.isDirectory(librariesDir)) {
return;
}
try {
Path tmpDir = Files.createTempDirectory("alib"); //$NON-NLS-1$
Path tmpLibDir = tmpDir.resolve("libraries"); //$NON-NLS-1$
Files.move(librariesDir, tmpLibDir);
Files.list(tmpLibDir).forEach(path -> {
try {
Optional<Path> latest = Files.list(path)
.reduce((path1, path2) -> compareVersions(path1.getFileName().toString(),
path2.getFileName().toString()) > 0 ? path1 : path2);
if (latest.isPresent()) {
Files.move(latest.get(), librariesDir.resolve(path.getFileName()));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
recursiveDelete(tmpDir);
} catch (RuntimeException | IOException e) {
throw Activator.coreException(e);
}
}
public boolean licenseAccepted() throws CoreException {
init();
return Boolean.getBoolean(props.getProperty(ACCEPTED_KEY, Boolean.FALSE.toString()));
}
public void acceptLicense() throws CoreException {
init();
props.setProperty(ACCEPTED_KEY, Boolean.TRUE.toString());
try (FileWriter writer = new FileWriter(getVersionFile().toFile())) {
props.store(writer, ""); //$NON-NLS-1$
} catch (IOException e) {
throw Activator.coreException(e);
}
}
public Collection<ArduinoPlatform> getInstalledPlatforms() throws CoreException {
List<ArduinoPlatform> platforms = new ArrayList<>();
for (ArduinoPackage pkg : getPackages()) {
platforms.addAll(pkg.getInstalledPlatforms());
}
return platforms;
}
public ArduinoPlatform getInstalledPlatform(String packageName, String architecture) throws CoreException {
ArduinoPackage pkg = getPackage(packageName);
return pkg != null ? pkg.getInstalledPlatform(architecture) : null;
}
public synchronized Collection<ArduinoPlatform> getAvailablePlatforms(IProgressMonitor monitor) throws CoreException {
List<ArduinoPlatform> platforms = new ArrayList<>();
URL[] urls = ArduinoPreferences.getBoardUrlList();
SubMonitor sub = SubMonitor.convert(monitor, urls.length + 1);
sub.beginTask("Downloading package descriptions", urls.length); //$NON-NLS-1$
for (URL url : urls) {
Path packagePath = ArduinoPreferences.getArduinoHome()
.resolve(Paths.get(url.getPath()).getFileName());
try {
Files.createDirectories(ArduinoPreferences.getArduinoHome());
try (InputStream in = url.openStream()) {
Files.copy(in, packagePath, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw Activator.coreException(String.format("Error loading %s", url.toString()), e); //$NON-NLS-1$
}
sub.worked(1);
}
sub.beginTask("Loading available packages", 1); //$NON-NLS-1$
resetPackages();
for (ArduinoPackage pkg : getPackages()) {
platforms.addAll(pkg.getAvailablePlatforms());
}
sub.done();
return platforms;
}
public void installPlatforms(Collection<ArduinoPlatform> platforms, IProgressMonitor monitor) throws CoreException {
SubMonitor sub = SubMonitor.convert(monitor, platforms.size());
for (ArduinoPlatform platform : platforms) {
sub.setTaskName(String.format("Installing %s %s", platform.getName(), platform.getVersion())); //$NON-NLS-1$
platform.install(sub);
sub.worked(1);
}
sub.done();
}
public void uninstallPlatforms(Collection<ArduinoPlatform> platforms, IProgressMonitor monitor) {
SubMonitor sub = SubMonitor.convert(monitor, platforms.size());
for (ArduinoPlatform platform : platforms) {
sub.setTaskName(String.format("Uninstalling %s", platform.getName())); //$NON-NLS-1$
platform.uninstall(sub);
sub.worked(1);
}
sub.done();
}
public static List<ArduinoPlatform> getSortedPlatforms(Collection<ArduinoPlatform> platforms) {
List<ArduinoPlatform> result = new ArrayList<>(platforms);
Collections.sort(result, (plat1, plat2) -> {
int c1 = plat1.getPackage().getName().compareToIgnoreCase(plat2.getPackage().getName());
if (c1 > 0) {
return 1;
} else if (c1 < 0) {
return -1;
} else {
return plat1.getArchitecture().compareToIgnoreCase(plat2.getArchitecture());
}
});
return result;
}
public static List<ArduinoLibrary> getSortedLibraries(Collection<ArduinoLibrary> libraries) {
List<ArduinoLibrary> result = new ArrayList<>(libraries);
Collections.sort(result, (lib1, lib2) -> {
return lib1.getName().compareToIgnoreCase(lib2.getName());
});
return result;
}
private synchronized void initPackages() throws CoreException {
if (packages == null) {
init();
packages = new HashMap<>();
try {
Files.list(ArduinoPreferences.getArduinoHome())
.filter(path -> path.getFileName().toString().startsWith("package_")) //$NON-NLS-1$
.forEach(path -> {
try (Reader reader = new FileReader(path.toFile())) {
PackageIndex index = new Gson().fromJson(reader, PackageIndex.class);
for (ArduinoPackage pkg : index.getPackages()) {
pkg.init();
packages.put(pkg.getName(), pkg);
}
} catch (IOException e) {
Activator.log(e);
}
});
} catch (IOException e) {
throw Activator.coreException(e);
}
}
}
private Collection<ArduinoPackage> getPackages() throws CoreException {
initPackages();
return packages.values();
}
public void resetPackages() {
packages = null;
}
public ArduinoPackage getPackage(String packageName) throws CoreException {
if (packageName == null) {
return null;
} else {
initPackages();
return packages.get(packageName);
}
}
public Collection<ArduinoBoard> getInstalledBoards() throws CoreException {
List<ArduinoBoard> boards = new ArrayList<>();
for (ArduinoPlatform platform : getInstalledPlatforms()) {
boards.addAll(platform.getBoards());
}
return boards;
}
public ArduinoBoard getBoard(String packageName, String architecture, String boardId) throws CoreException {
for (ArduinoPlatform platform : getInstalledPlatforms()) {
if (platform.getPackage().getName().equals(packageName)
&& platform.getArchitecture().equals(architecture)) {
return platform.getBoard(boardId);
}
}
// For backwards compat, check platform name
for (ArduinoPlatform platform : getInstalledPlatforms()) {
if (platform.getPackage().getName().equals(packageName)
&& platform.getName().equals(architecture)) {
return platform.getBoardByName(boardId);
}
}
return null;
}
public ArduinoTool getTool(String packageName, String toolName, String version) {
ArduinoPackage pkg = packages.get(packageName);
return pkg != null ? pkg.getTool(toolName, version) : null;
}
public void initInstalledLibraries() throws CoreException {
init();
if (installedLibraries == null) {
installedLibraries = new HashMap<>();
Path librariesDir = ArduinoPreferences.getArduinoHome().resolve("libraries"); //$NON-NLS-1$
if (Files.isDirectory(librariesDir)) {
try {
Files.find(librariesDir, 2,
(path, attrs) -> path.getFileName().toString().equals("library.properties")) //$NON-NLS-1$
.forEach(path -> {
try {
ArduinoLibrary library = new ArduinoLibrary(path);
installedLibraries.put(library.getName(), library);
} catch (CoreException e) {
throw new RuntimeException(e);
}
});
} catch (IOException e) {
throw Activator.coreException(e);
}
}
}
}
public Collection<ArduinoLibrary> getInstalledLibraries() throws CoreException {
initInstalledLibraries();
return installedLibraries.values();
}
public ArduinoLibrary getInstalledLibrary(String name) throws CoreException {
initInstalledLibraries();
return installedLibraries.get(name);
}
public Collection<ArduinoLibrary> getAvailableLibraries(IProgressMonitor monitor) throws CoreException {
try {
initInstalledLibraries();
Map<String, ArduinoLibrary> libs = new HashMap<>();
SubMonitor sub = SubMonitor.convert(monitor, "Downloading library index", 2);
Path librariesPath = ArduinoPreferences.getArduinoHome().resolve(LIBRARIES_FILE);
URL librariesUrl = new URL(LIBRARIES_URL);
Files.createDirectories(ArduinoPreferences.getArduinoHome());
Files.copy(librariesUrl.openStream(), librariesPath, StandardCopyOption.REPLACE_EXISTING);
sub.worked(1);
try (Reader reader = new FileReader(librariesPath.toFile())) {
sub.setTaskName("Calculating available libraries");
LibraryIndex libraryIndex = new Gson().fromJson(reader, LibraryIndex.class);
for (ArduinoLibrary library : libraryIndex.getLibraries()) {
String libraryName = library.getName();
if (!installedLibraries.containsKey(libraryName)) {
ArduinoLibrary current = libs.get(libraryName);
if (current == null || compareVersions(library.getVersion(), current.getVersion()) > 0) {
libs.put(libraryName, library);
}
}
}
}
sub.done();
return libs.values();
} catch (IOException e) {
throw Activator.coreException(e);
}
}
public void installLibraries(Collection<ArduinoLibrary> libraries, IProgressMonitor monitor) throws CoreException {
SubMonitor sub = SubMonitor.convert(monitor, libraries.size());
for (ArduinoLibrary library : libraries) {
sub.setTaskName(String.format("Installing %s", library.getName())); //$NON-NLS-1$
library.install(sub);
try {
ArduinoLibrary newLibrary = new ArduinoLibrary(library.getInstallPath().resolve("library.properties")); //$NON-NLS-1$
installedLibraries.put(newLibrary.getName(), newLibrary);
} catch (CoreException e) {
throw new RuntimeException(e);
}
sub.worked(1);
}
sub.done();
}
public void uninstallLibraries(Collection<ArduinoLibrary> libraries, IProgressMonitor monitor)
throws CoreException {
SubMonitor sub = SubMonitor.convert(monitor, libraries.size());
for (ArduinoLibrary library : libraries) {
sub.setTaskName(String.format("Installing %s", library.getName())); //$NON-NLS-1$
library.uninstall(sub);
installedLibraries.remove(library.getName());
sub.worked(1);
}
sub.done();
}
public Collection<ArduinoLibrary> getLibraries(IProject project)
throws CoreException {
initInstalledLibraries();
IEclipsePreferences settings = getSettings(project);
String librarySetting = settings.get(LIBRARIES, "[]"); //$NON-NLS-1$
JsonArray libArray = new JsonParser().parse(librarySetting).getAsJsonArray();
List<ArduinoLibrary> libraries = new ArrayList<>(libArray.size());
for (JsonElement libElement : libArray) {
if (libElement.isJsonPrimitive()) {
String libName = libElement.getAsString();
ArduinoLibrary lib = installedLibraries.get(libName);
if (lib != null) {
libraries.add(lib);
}
} else {
JsonObject libObj = libElement.getAsJsonObject();
String packageName = libObj.get("package").getAsString(); //$NON-NLS-1$
String platformName = libObj.get("platform").getAsString(); //$NON-NLS-1$
String libName = libObj.get("library").getAsString(); //$NON-NLS-1$
ArduinoPackage pkg = getPackage(packageName);
if (pkg != null) {
ArduinoPlatform platform = pkg.getInstalledPlatform(platformName);
if (platform != null) {
ArduinoLibrary lib = platform.getLibrary(libName);
if (lib != null) {
libraries.add(lib);
}
}
}
}
}
return libraries;
}
public void setLibraries(final IProject project, final Collection<ArduinoLibrary> libraries) throws CoreException {
JsonArray elements = new JsonArray();
for (ArduinoLibrary library : libraries) {
ArduinoPlatform platform = library.getPlatform();
if (platform != null) {
JsonObject libObj = new JsonObject();
libObj.addProperty("package", platform.getPackage().getName()); //$NON-NLS-1$
libObj.addProperty("platform", platform.getArchitecture()); //$NON-NLS-1$
libObj.addProperty("library", library.getName()); //$NON-NLS-1$
elements.add(libObj);
} else {
elements.add(new JsonPrimitive(library.getName()));
}
}
IEclipsePreferences settings = getSettings(project);
settings.put(LIBRARIES, new Gson().toJson(elements));
try {
settings.flush();
} catch (BackingStoreException e) {
throw Activator.coreException(e);
}
}
private IEclipsePreferences getSettings(IProject project) {
return new ProjectScope(project).getNode(Activator.getId());
}
public static void downloadAndInstall(String url, String archiveFileName, Path installPath,
IProgressMonitor monitor) throws IOException {
Exception error = null;
for (int retries = 3; retries > 0 && !monitor.isCanceled(); --retries) {
try {
URL dl = new URL(url);
Path dlDir = ArduinoPreferences.getArduinoHome().resolve("downloads"); //$NON-NLS-1$
Files.createDirectories(dlDir);
Path archivePath = dlDir.resolve(archiveFileName);
URLConnection conn = dl.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
Files.copy(conn.getInputStream(), archivePath, StandardCopyOption.REPLACE_EXISTING);
boolean isWin = Platform.getOS().equals(Platform.OS_WIN32);
// extract
ArchiveInputStream archiveIn = null;
try {
String compressor = null;
String archiver = null;
if (archiveFileName.endsWith("tar.bz2")) { //$NON-NLS-1$
compressor = CompressorStreamFactory.BZIP2;
archiver = ArchiveStreamFactory.TAR;
} else if (archiveFileName.endsWith(".tar.gz") || archiveFileName.endsWith(".tgz")) { //$NON-NLS-1$ //$NON-NLS-2$
compressor = CompressorStreamFactory.GZIP;
archiver = ArchiveStreamFactory.TAR;
} else if (archiveFileName.endsWith(".tar.xz")) { //$NON-NLS-1$
compressor = CompressorStreamFactory.XZ;
archiver = ArchiveStreamFactory.TAR;
} else if (archiveFileName.endsWith(".zip")) { //$NON-NLS-1$
archiver = ArchiveStreamFactory.ZIP;
}
InputStream in = new BufferedInputStream(new FileInputStream(archivePath.toFile()));
if (compressor != null) {
in = new CompressorStreamFactory().createCompressorInputStream(compressor, in);
}
archiveIn = new ArchiveStreamFactory().createArchiveInputStream(archiver, in);
for (ArchiveEntry entry = archiveIn.getNextEntry(); entry != null; entry = archiveIn
.getNextEntry()) {
if (entry.isDirectory()) {
continue;
}
// Magic file for git tarballs
Path path = Paths.get(entry.getName());
if (path.endsWith("pax_global_header")) { //$NON-NLS-1$
continue;
}
// Strip the first directory of the path
Path entryPath = installPath.resolve(path.subpath(1, path.getNameCount()));
Files.createDirectories(entryPath.getParent());
if (entry instanceof TarArchiveEntry) {
TarArchiveEntry tarEntry = (TarArchiveEntry) entry;
if (tarEntry.isLink()) {
Path linkPath = Paths.get(tarEntry.getLinkName());
linkPath = installPath.resolve(linkPath.subpath(1, linkPath.getNameCount()));
Files.deleteIfExists(entryPath);
Files.createSymbolicLink(entryPath, entryPath.getParent().relativize(linkPath));
} else if (tarEntry.isSymbolicLink()) {
Path linkPath = Paths.get(tarEntry.getLinkName());
Files.deleteIfExists(entryPath);
Files.createSymbolicLink(entryPath, linkPath);
} else {
Files.copy(archiveIn, entryPath, StandardCopyOption.REPLACE_EXISTING);
}
if (!isWin && !tarEntry.isSymbolicLink()) {
int mode = tarEntry.getMode();
Files.setPosixFilePermissions(entryPath, toPerms(mode));
}
} else {
Files.copy(archiveIn, entryPath, StandardCopyOption.REPLACE_EXISTING);
}
}
} finally {
if (archiveIn != null) {
archiveIn.close();
}
}
return;
} catch (IOException | CompressorException | ArchiveException e) {
error = e;
// retry
}
}
// out of retries
if (error instanceof IOException) {
throw (IOException) error;
} else {
throw new IOException(error);
}
}
public static int compareVersions(String version1, String version2) {
if (version1 == null) {
return version2 == null ? 0 : -1;
}
if (version2 == null) {
return 1;
}
String[] v1 = version1.split("\\."); //$NON-NLS-1$
String[] v2 = version2.split("\\."); //$NON-NLS-1$
for (int i = 0; i < Math.max(v1.length, v2.length); ++i) {
if (v1.length <= i) {
return v2.length < i ? 0 : -1;
}
if (v2.length <= i) {
return 1;
}
try {
int vi1 = Integer.parseInt(v1[i]);
int vi2 = Integer.parseInt(v2[i]);
if (vi1 < vi2) {
return -1;
}
if (vi1 > vi2) {
return 1;
}
} catch (NumberFormatException e) {
// not numbers, do string compares
int c = v1[i].compareTo(v2[i]);
if (c < 0) {
return -1;
}
if (c > 0) {
return 1;
}
}
}
return 0;
}
private static Set<PosixFilePermission> toPerms(int mode) {
Set<PosixFilePermission> perms = new HashSet<>();
if ((mode & 0400) != 0) {
perms.add(PosixFilePermission.OWNER_READ);
}
if ((mode & 0200) != 0) {
perms.add(PosixFilePermission.OWNER_WRITE);
}
if ((mode & 0100) != 0) {
perms.add(PosixFilePermission.OWNER_EXECUTE);
}
if ((mode & 0040) != 0) {
perms.add(PosixFilePermission.GROUP_READ);
}
if ((mode & 0020) != 0) {
perms.add(PosixFilePermission.GROUP_WRITE);
}
if ((mode & 0010) != 0) {
perms.add(PosixFilePermission.GROUP_EXECUTE);
}
if ((mode & 0004) != 0) {
perms.add(PosixFilePermission.OTHERS_READ);
}
if ((mode & 0002) != 0) {
perms.add(PosixFilePermission.OTHERS_WRITE);
}
if ((mode & 0001) != 0) {
perms.add(PosixFilePermission.OTHERS_EXECUTE);
}
return perms;
}
public static void recursiveDelete(Path directory) throws IOException {
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
}