blob: 64eb2b07231c8567b97435abe5a0a3294b22a72e [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.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Type;
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.HashSet;
import java.util.List;
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.cdt.arduino.core.internal.Messages;
import org.eclipse.cdt.arduino.core.internal.build.ArduinoBuildConfiguration;
import org.eclipse.cdt.core.build.ICBuildConfiguration;
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.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.osgi.service.prefs.BackingStoreException;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
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$
private List<PackageIndex> packageIndices;
private LibraryIndex libraryIndex;
public void loadIndices() {
new Job(Messages.ArduinoBoardManager_0) {
@Override
protected IStatus run(IProgressMonitor monitor) {
synchronized (ArduinoManager.this) {
String[] boardUrls = ArduinoPreferences.getBoardUrls().split("\n"); //$NON-NLS-1$
packageIndices = new ArrayList<>(boardUrls.length);
for (String boardUrl : boardUrls) {
loadPackageIndex(boardUrl, true);
}
loadLibraryIndex(true);
return Status.OK_STATUS;
}
}
}.schedule();
}
private void loadPackageIndex(String url, boolean download) {
try {
URL packageUrl = new URL(url.trim());
Path packagePath = ArduinoPreferences.getArduinoHome()
.resolve(Paths.get(packageUrl.getPath()).getFileName());
File packageFile = packagePath.toFile();
if (download) {
Files.createDirectories(ArduinoPreferences.getArduinoHome());
Files.copy(packageUrl.openStream(), packagePath, StandardCopyOption.REPLACE_EXISTING);
}
if (packageFile.exists()) {
try (Reader reader = new FileReader(packageFile)) {
PackageIndex index = new Gson().fromJson(reader, PackageIndex.class);
index.setOwners(ArduinoManager.this);
packageIndices.add(index);
}
}
} catch (IOException e) {
Activator.log(e);
}
}
public synchronized List<PackageIndex> getPackageIndices() {
if (packageIndices == null) {
String[] boardUrls = ArduinoPreferences.getBoardUrls().split("\n"); //$NON-NLS-1$
packageIndices = new ArrayList<>(boardUrls.length);
for (String boardUrl : boardUrls) {
loadPackageIndex(boardUrl, false);
}
}
return packageIndices;
}
public void loadLibraryIndex(boolean download) {
try {
URL librariesUrl = new URL(LIBRARIES_URL);
Path librariesPath = ArduinoPreferences.getArduinoHome()
.resolve(Paths.get(librariesUrl.getPath()).getFileName());
File librariesFile = librariesPath.toFile();
if (download) {
Files.createDirectories(ArduinoPreferences.getArduinoHome());
Files.copy(librariesUrl.openStream(), librariesPath, StandardCopyOption.REPLACE_EXISTING);
}
if (librariesFile.exists()) {
try (Reader reader = new FileReader(librariesFile)) {
libraryIndex = new Gson().fromJson(reader, LibraryIndex.class);
libraryIndex.resolve();
}
}
} catch (IOException e) {
Activator.log(e);
}
}
public LibraryIndex getLibraryIndex() throws CoreException {
if (libraryIndex == null) {
loadLibraryIndex(false);
}
return libraryIndex;
}
public ArduinoBoard getBoard(String boardName, String platformName, String packageName) throws CoreException {
for (PackageIndex index : getPackageIndices()) {
ArduinoPackage pkg = index.getPackage(packageName);
if (pkg != null) {
ArduinoPlatform platform = pkg.getPlatform(platformName);
if (platform != null) {
ArduinoBoard board = platform.getBoard(boardName);
if (board != null) {
return board;
}
}
}
}
return null;
}
public List<ArduinoBoard> getInstalledBoards() throws CoreException {
List<ArduinoBoard> boards = new ArrayList<>();
for (PackageIndex index : getPackageIndices()) {
for (ArduinoPackage pkg : index.getPackages()) {
for (ArduinoPlatform platform : pkg.getInstalledPlatforms().values()) {
boards.addAll(platform.getBoards());
}
}
}
return boards;
}
public ArduinoPackage getPackage(String packageName) throws CoreException {
for (PackageIndex index : getPackageIndices()) {
ArduinoPackage pkg = index.getPackage(packageName);
if (pkg != null) {
return pkg;
}
}
return null;
}
public ArduinoTool getTool(String packageName, String toolName, String version) throws CoreException {
for (PackageIndex index : getPackageIndices()) {
ArduinoPackage pkg = index.getPackage(packageName);
if (pkg != null) {
ArduinoTool tool = pkg.getTool(toolName, version);
if (tool != null) {
return tool;
}
}
}
return null;
}
private static final String LIBRARIES = "libraries"; //$NON-NLS-1$
private IEclipsePreferences getSettings(IProject project) {
return new ProjectScope(project).getNode(Activator.getId());
}
public Collection<ArduinoLibrary> getLibraries(IProject project) throws CoreException {
IEclipsePreferences settings = getSettings(project);
String librarySetting = settings.get(LIBRARIES, "[]"); //$NON-NLS-1$
Type stringSet = new TypeToken<Set<String>>() {
}.getType();
Set<String> libraryNames = new Gson().fromJson(librarySetting, stringSet);
LibraryIndex index = Activator.getService(ArduinoManager.class).getLibraryIndex();
ICBuildConfiguration cconfig = project.getActiveBuildConfig().getAdapter(ICBuildConfiguration.class);
ArduinoPlatform platform = cconfig.getAdapter(ArduinoBuildConfiguration.class).getBoard().getPlatform();
List<ArduinoLibrary> libraries = new ArrayList<>(libraryNames.size());
for (String name : libraryNames) {
ArduinoLibrary lib = index.getLibrary(name);
if (lib == null) {
lib = platform.getLibrary(name);
}
if (lib != null) {
libraries.add(lib);
}
}
return libraries;
}
public void setLibraries(final IProject project, final Collection<ArduinoLibrary> libraries) throws CoreException {
List<String> libraryNames = new ArrayList<>(libraries.size());
for (ArduinoLibrary library : libraries) {
libraryNames.add(library.getName());
}
IEclipsePreferences settings = getSettings(project);
settings.put(LIBRARIES, new Gson().toJson(libraryNames));
try {
settings.flush();
} catch (BackingStoreException e) {
Activator.log(e);
}
new Job(Messages.ArduinoManager_0) {
@Override
protected IStatus run(IProgressMonitor monitor) {
MultiStatus mstatus = new MultiStatus(Activator.getId(), 0, Messages.ArduinoManager_1, null);
for (ArduinoLibrary library : libraries) {
IStatus status = library.install(monitor);
if (!status.isOK()) {
mstatus.add(status);
}
}
return mstatus;
}
}.schedule();
}
public static IStatus downloadAndInstall(String url, String archiveFileName, Path installPath,
IProgressMonitor monitor) {
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 Status.OK_STATUS;
} catch (IOException | CompressorException | ArchiveException e) {
error = e;
// retry
}
}
// out of retries
return new Status(IStatus.ERROR, Activator.getId(), Messages.ArduinoManager_2, 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;
}
});
}
}