Java tutorial
/* * This file is part of Arduino. * * Copyright 2014 Arduino LLC (http://www.arduino.cc/) * * Arduino is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * As a special exception, you may use this file as part of a free software * library without restriction. Specifically, if other files instantiate * templates or use macros or inline functions from this file, or you compile * this file and link it with other files to produce an executable, this * file does not by itself cause the resulting executable to be covered by * the GNU General Public License. This exception does not however * invalidate any other reasons why the executable file might be covered by * the GNU General Public License. */ package cc.arduino.contributions.packages; import cc.arduino.Constants; import cc.arduino.contributions.DownloadableContribution; import cc.arduino.contributions.DownloadableContributionsDownloader; import cc.arduino.contributions.ProgressListener; import cc.arduino.contributions.SignatureVerifier; import cc.arduino.filters.FileExecutablePredicate; import cc.arduino.utils.ArchiveExtractor; import cc.arduino.utils.MultiStepProgress; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.Executor; import org.apache.commons.exec.PumpStreamHandler; import processing.app.BaseNoGui; import processing.app.I18n; import processing.app.Platform; import processing.app.PreferencesData; import processing.app.helpers.FileUtils; import processing.app.helpers.filefilters.OnlyDirs; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import static processing.app.I18n.format; import static processing.app.I18n.tr; public class ContributionInstaller { private final Platform platform; private final SignatureVerifier signatureVerifier; public ContributionInstaller(Platform platform, SignatureVerifier signatureVerifier) { this.platform = platform; this.signatureVerifier = signatureVerifier; } public synchronized List<String> install(ContributedPlatform contributedPlatform, ProgressListener progressListener) throws Exception { List<String> errors = new LinkedList<>(); if (contributedPlatform.isInstalled()) { throw new Exception("Platform is already installed!"); } // Do not download already installed tools List<ContributedTool> tools = new ArrayList<>(); for (ContributedTool tool : contributedPlatform.getResolvedTools()) { DownloadableContribution downloadable = tool.getDownloadableContribution(platform); if (downloadable == null) { throw new Exception( format(tr("Tool {0} is not available for your operating system."), tool.getName())); } // Download the tool if it's not installed or it's a built-in tool if (!tool.isInstalled() || tool.isBuiltIn()) { tools.add(tool); } } DownloadableContributionsDownloader downloader = new DownloadableContributionsDownloader( BaseNoGui.indexer.getStagingFolder()); // Calculate progress increases MultiStepProgress progress = new MultiStepProgress((tools.size() + 1) * 2); // Download all try { // Download platform downloader.download(contributedPlatform, progress, tr("Downloading boards definitions."), progressListener); progress.stepDone(); // Download tools int i = 1; for (ContributedTool tool : tools) { String msg = format(tr("Downloading tools ({0}/{1})."), i, tools.size()); i++; downloader.download(tool.getDownloadableContribution(platform), progress, msg, progressListener); progress.stepDone(); } } catch (InterruptedException e) { // Download interrupted... just exit return errors; } ContributedPackage pack = contributedPlatform.getParentPackage(); File packageFolder = new File(BaseNoGui.indexer.getPackagesFolder(), pack.getName()); // TODO: Extract to temporary folders and move to the final destination only // once everything is successfully unpacked. If the operation fails remove // all the temporary folders and abort installation. List<Map.Entry<ContributedToolReference, ContributedTool>> resolvedToolReferences = contributedPlatform .getResolvedToolReferences().entrySet().stream() .filter((entry) -> !entry.getValue().isInstalled() || entry.getValue().isBuiltIn()) .collect(Collectors.toList()); int i = 1; for (Map.Entry<ContributedToolReference, ContributedTool> entry : resolvedToolReferences) { progress.setStatus(format(tr("Installing tools ({0}/{1})..."), i, resolvedToolReferences.size())); progressListener.onProgress(progress); i++; ContributedTool tool = entry.getValue(); Path destFolder = Paths.get(BaseNoGui.indexer.getPackagesFolder().getAbsolutePath(), entry.getKey().getPackager(), "tools", tool.getName(), tool.getVersion()); Files.createDirectories(destFolder); DownloadableContribution toolContrib = tool.getDownloadableContribution(platform); assert toolContrib.getDownloadedFile() != null; new ArchiveExtractor(platform).extract(toolContrib.getDownloadedFile(), destFolder.toFile(), 1); try { findAndExecutePostInstallScriptIfAny(destFolder.toFile(), contributedPlatform.getParentPackage().isTrusted(), PreferencesData.getBoolean(Constants.PREF_CONTRIBUTIONS_TRUST_ALL)); } catch (IOException e) { errors.add(tr("Error running post install script")); } tool.setInstalled(true); tool.setInstalledFolder(destFolder.toFile()); progress.stepDone(); } // Unpack platform on the correct location progress.setStatus(tr("Installing boards...")); progressListener.onProgress(progress); File platformFolder = new File(packageFolder, "hardware" + File.separator + contributedPlatform.getArchitecture()); File destFolder = new File(platformFolder, contributedPlatform.getParsedVersion()); Files.createDirectories(destFolder.toPath()); new ArchiveExtractor(platform).extract(contributedPlatform.getDownloadedFile(), destFolder, 1); contributedPlatform.setInstalled(true); contributedPlatform.setInstalledFolder(destFolder); try { findAndExecutePostInstallScriptIfAny(destFolder, contributedPlatform.getParentPackage().isTrusted(), PreferencesData.getBoolean(Constants.PREF_CONTRIBUTIONS_TRUST_ALL)); } catch (IOException e) { e.printStackTrace(); errors.add(tr("Error running post install script")); } progress.stepDone(); progress.setStatus(tr("Installation completed!")); progressListener.onProgress(progress); return errors; } private void findAndExecutePostInstallScriptIfAny(File folder, boolean trusted, boolean trustAll) throws IOException { Collection<File> scripts = platform.postInstallScripts(folder).stream() .filter(new FileExecutablePredicate()).collect(Collectors.toList()); if (scripts.isEmpty()) { String[] subfolders = folder.list(new OnlyDirs()); if (subfolders.length != 1) { return; } findAndExecutePostInstallScriptIfAny(new File(folder, subfolders[0]), trusted, trustAll); return; } executeScripts(folder, scripts, trusted, trustAll); } private void findAndExecutePreUninstallScriptIfAny(File folder, boolean trusted, boolean trustAll) throws IOException { Collection<File> scripts = platform.preUninstallScripts(folder).stream() .filter(new FileExecutablePredicate()).collect(Collectors.toList()); if (scripts.isEmpty()) { String[] subfolders = folder.list(new OnlyDirs()); if (subfolders.length != 1) { return; } findAndExecutePreUninstallScriptIfAny(new File(folder, subfolders[0]), trusted, trustAll); return; } executeScripts(folder, scripts, trusted, trustAll); } private void executeScripts(File folder, Collection<File> postInstallScripts, boolean trusted, boolean trustAll) throws IOException { File script = postInstallScripts.iterator().next(); if (!trusted && !trustAll) { System.err.println( I18n.format(tr("Warning: non trusted contribution, skipping script execution ({0})"), script)); return; } if (trustAll) { System.err.println(I18n.format(tr("Warning: forced untrusted script execution ({0})"), script)); } ByteArrayOutputStream stdout = new ByteArrayOutputStream(); ByteArrayOutputStream stderr = new ByteArrayOutputStream(); Executor executor = new DefaultExecutor(); executor.setStreamHandler(new PumpStreamHandler(stdout, stderr)); executor.setWorkingDirectory(folder); executor.setExitValues(null); int exitValue = executor.execute(new CommandLine(script)); executor.setExitValues(new int[0]); System.out.write(stdout.toByteArray()); System.err.write(stderr.toByteArray()); if (executor.isFailure(exitValue)) { throw new IOException(); } } public synchronized List<String> remove(ContributedPlatform contributedPlatform) { if (contributedPlatform == null || contributedPlatform.isBuiltIn()) { return new LinkedList<>(); } List<String> errors = new LinkedList<>(); try { findAndExecutePreUninstallScriptIfAny(contributedPlatform.getInstalledFolder(), contributedPlatform.getParentPackage().isTrusted(), PreferencesData.getBoolean(Constants.PREF_CONTRIBUTIONS_TRUST_ALL)); } catch (IOException e) { errors.add(tr("Error running post install script")); } // Check if the tools are no longer needed for (ContributedTool tool : contributedPlatform.getResolvedTools()) { // Do not remove used tools if (BaseNoGui.indexer.isContributedToolUsed(contributedPlatform, tool)) continue; // Do not remove built-in tools if (tool.isBuiltIn()) continue; // Ok, delete the tool File destFolder = tool.getInstalledFolder(); FileUtils.recursiveDelete(destFolder); tool.setInstalled(false); tool.setInstalledFolder(null); // We removed the version folder (.../tools/TOOL_NAME/VERSION) // now try to remove the containing TOOL_NAME folder // (and silently fail if another version of the tool is installed) try { Files.delete(destFolder.getParentFile().toPath()); } catch (Exception e) { // ignore } } FileUtils.recursiveDelete(contributedPlatform.getInstalledFolder()); contributedPlatform.setInstalled(false); contributedPlatform.setInstalledFolder(null); return errors; } public synchronized List<String> updateIndex(ProgressListener progressListener) throws Exception { MultiStepProgress progress = new MultiStepProgress(1); List<String> downloadedPackageIndexFilesAccumulator = new LinkedList<>(); downloadIndexAndSignature(progress, downloadedPackageIndexFilesAccumulator, Constants.PACKAGE_INDEX_URL, progressListener); Set<String> packageIndexURLs = new HashSet<>(); String additionalURLs = PreferencesData.get(Constants.PREF_BOARDS_MANAGER_ADDITIONAL_URLS, ""); if (!"".equals(additionalURLs)) { packageIndexURLs.addAll(Arrays.asList(additionalURLs.split(","))); } for (String packageIndexURL : packageIndexURLs) { try { downloadIndexAndSignature(progress, downloadedPackageIndexFilesAccumulator, packageIndexURL, progressListener); } catch (Exception e) { System.err.println(e.getMessage()); } } progress.stepDone(); return downloadedPackageIndexFilesAccumulator; } private void downloadIndexAndSignature(MultiStepProgress progress, List<String> downloadedPackagedIndexFilesAccumulator, String packageIndexUrl, ProgressListener progressListener) throws Exception { File packageIndex = download(progress, packageIndexUrl, progressListener); downloadedPackagedIndexFilesAccumulator.add(packageIndex.getName()); try { File packageIndexSignature = download(progress, packageIndexUrl + ".sig", progressListener); boolean signatureVerified = signatureVerifier.isSigned(packageIndex); if (signatureVerified) { downloadedPackagedIndexFilesAccumulator.add(packageIndexSignature.getName()); } else { downloadedPackagedIndexFilesAccumulator.remove(packageIndex.getName()); Files.delete(packageIndex.toPath()); Files.delete(packageIndexSignature.toPath()); System.err.println( I18n.format(tr("{0} file signature verification failed. File ignored."), packageIndexUrl)); } } catch (Exception e) { //ignore errors } } private File download(MultiStepProgress progress, String packageIndexUrl, ProgressListener progressListener) throws Exception { String statusText = tr("Downloading platforms index..."); URL url = new URL(packageIndexUrl); String[] urlPathParts = url.getFile().split("/"); File outputFile = BaseNoGui.indexer.getIndexFile(urlPathParts[urlPathParts.length - 1]); File tmpFile = new File(outputFile.getAbsolutePath() + ".tmp"); DownloadableContributionsDownloader downloader = new DownloadableContributionsDownloader( BaseNoGui.indexer.getStagingFolder()); boolean noResume = true; downloader.download(url, tmpFile, progress, statusText, progressListener, noResume); Files.deleteIfExists(outputFile.toPath()); Files.move(tmpFile.toPath(), outputFile.toPath()); return outputFile; } public synchronized void deleteUnknownFiles(List<String> downloadedPackageIndexFiles) throws IOException { File preferencesFolder = BaseNoGui.indexer.getIndexFile(".").getParentFile(); File[] additionalPackageIndexFiles = preferencesFolder .listFiles(new PackageIndexFilenameFilter(Constants.DEFAULT_INDEX_FILE_NAME)); if (additionalPackageIndexFiles == null) { return; } for (File additionalPackageIndexFile : additionalPackageIndexFiles) { if (!downloadedPackageIndexFiles.contains(additionalPackageIndexFile.getName())) { Files.delete(additionalPackageIndexFile.toPath()); } } } }