Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
package com.marginallyclever.makelangelo.firmwareuploader;
import com.marginallyclever.convenience.helpers.OSHelper;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class AVRDudeDownloader {
private static final Logger logger = LoggerFactory.getLogger(AVRDudeDownloader.class);
public static final String WINDOWS = "mingw";
public static final String LINUX = "linux";
public static final String MACOS = "apple";
private static final String ARDUINO_PACKAGE_INDEX = "https://downloads.arduino.cc/packages/package_index.json";
public static void main(String[] args) throws IOException {
downloadAVRDude(getArch());
}
/**
* Download AVRDude for the current OS architecture.
* @return the path to the first folder. If the target is c:\a\b\ and the extracted files are in c:\a\b\c\d\
* then this will return c:\a\b\c
* @throws IOException if the download fails.
*/
public static String downloadAVRDude() throws IOException {
return downloadAVRDude(getArch());
}
/**
* Download AVRDude for the given OS architecture.
* @param arch one of WINDOWS, LINUX, MACOS
* @return the path to the extracted avrdude executable.
* @throws IOException if the download fails.
*/
public static String downloadAVRDude(String arch) throws IOException {
String url = getURLforOS(arch);
if (url == null) return null;
try {
return downloadAndExtract(url);
} catch (IOException e) {
logger.error("Error downloading avrdude", e);
throw e;
}
}
public static String getArch() {
if (OSHelper.isWindows()) return WINDOWS;
if (OSHelper.isOSX()) return MACOS;
return LINUX;
}
/**
* @param arch one of WINDOWS, LINUX, MACOS
* @return URL to download the latest avrdude package for the given OS.
* @throws IOException
*/
public static String getURLforOS(String arch) throws IOException {
JSONObject jsonObject = getJSONFromURL(ARDUINO_PACKAGE_INDEX);
JSONObject arduinoPackage = getPackageNamedArduino(jsonObject);
if (arduinoPackage != null) {
JSONObject avrdudeTool = getLastToolNamedAVRDude(arduinoPackage);
if (avrdudeTool != null) {
JSONObject system = getSystemForHost(avrdudeTool, arch);
if (system != null) {
return system.getString("url");
}
}
}
return null;
}
/**
* Search the systems list for the one where host contains the given architecture.
* @param jsonObject the avrdude tool object
* @param arch the architecture to search for
* @return the system object that matches the given architecture
*/
private static JSONObject getSystemForHost(JSONObject jsonObject, String arch) {
JSONArray systems = jsonObject.getJSONArray("systems");
for (int i = 0; i < systems.length(); i++) {
JSONObject systemObject = systems.getJSONObject(i);
if (systemObject.getString("host").contains(arch)) {
return systemObject;
}
}
return null;
}
/**
* Search the arduinoPackage for element with name=avrdude. Compare element version values to get the latest.
* Versions may be in the format "6.3.0-avrdude9" or "6.3.0-avrdude18" so we need to extract the numeric version.
* @param arduinoPackage the package to search
* @return the last item in the tools list with name=avrdude
*/
private static JSONObject getLastToolNamedAVRDude(JSONObject arduinoPackage) {
JSONArray tools = arduinoPackage.getJSONArray("tools");
JSONObject avrdudeTool = null;
int bestVersion = -1;
for (int i = 0; i < tools.length(); i++) {
JSONObject toolObject = tools.getJSONObject(i);
if (!toolObject.getString("name").equals("avrdude")) continue;
var version = toolObject.getString("version");
logger.info("found avrdude tool at index {} with version {}", i, version);
int v = Integer.parseInt(version.replaceAll("\\D", ""));
if(v > bestVersion) {
logger.info("new best version: {}", v);
bestVersion = v;
avrdudeTool = toolObject;
}
}
return avrdudeTool;
}
// find the element in packages with name=arduino
private static JSONObject getPackageNamedArduino(JSONObject jsonObject) {
JSONArray packages = jsonObject.getJSONArray("packages");
JSONObject arduinoPackage = null;
for (int i = 0; i < packages.length(); i++) {
JSONObject packageObject = packages.getJSONObject(i);
if (packageObject.getString("name").equals("arduino")) {
arduinoPackage = packageObject;
break;
}
}
return arduinoPackage;
}
private static JSONObject getJSONFromURL(String url) throws IOException {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
String json = EntityUtils.toString(httpResponse.getEntity());
return new JSONObject(json);
}
}
public static String downloadAndExtract(String urlStr) throws IOException {
File toDeleteLater = downloadFileToTemp(urlStr);
String path = extractFile(toDeleteLater, urlStr);
logger.info("new path: {}", path);
makeExecutable(path);
return path;
}
private static void makeExecutable(String targetPath) throws IOException {
String app = "avrdude";
if(OSHelper.isWindows()) app+=".exe";
File avrDudeFile = tryPath(Paths.get(targetPath, app));
if(!avrDudeFile.exists()) avrDudeFile = tryPath(Paths.get(targetPath, "bin", app));
if(!avrDudeFile.exists()) {
throw new IOException("File " + app + " does not exist");
}
if(!avrDudeFile.setExecutable(true)) {
throw new IOException("Could not set executable permissions for " + avrDudeFile.getAbsolutePath());
}
}
private static File tryPath(Path avrdudePath) {
logger.info("trying path: " + avrdudePath.toAbsolutePath());
return avrdudePath.toFile();
}
private static File downloadFileToTemp(String urlStr) throws IOException {
logger.info("download from: " + urlStr);
try {
var url = new URI(urlStr);
String fileExtension = getFileExtension(url.getPath());
File toDeleteLater = File.createTempFile("download", fileExtension);
toDeleteLater.deleteOnExit();
Path tempFile = toDeleteLater.toPath();
logger.info("temp file: " + tempFile);
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(urlStr);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
try (InputStream inputStream = response.getEntity().getContent();
OutputStream outputStream = new FileOutputStream(tempFile.toFile())) {
byte[] buffer = new byte[4096];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
}
}
}
return toDeleteLater;
}
catch (Exception e) {
throw new IOException("Error downloading file: " + e.getMessage());
}
}
/**
* Extract the downloaded file to ~/.makelangelo
* @param toDeleteLater the file to delete after extraction
* @param urlStr the URL of the file
* @return the path to the extracted file, which should be ~/.makelangelo/firstSubDirectory
* @throws IOException if the extraction fails
*/
private static String extractFile(File toDeleteLater, String urlStr) throws IOException {
String targetDirStr = System.getProperty("user.home") + File.separator + ".makelangelo" + File.separator;
String newFolderName = "";
if (urlStr.endsWith(".zip")) {
logger.info("Extracting zip file");
newFolderName = extractZipFile(toDeleteLater, targetDirStr);
} else if (urlStr.endsWith(".tar.bz2")) {
logger.info("Extracting tar.bz2 file");
newFolderName = extractTarBz2File(toDeleteLater, targetDirStr);
} else if (urlStr.endsWith(".bz2")) {
logger.info("Extracting bz2 file");
newFolderName = extractBz2File(toDeleteLater, targetDirStr);
}
// sometimes the path returned might be /a/b/... and we only want /a/
if(newFolderName.contains("/")) {
newFolderName = newFolderName.split("/")[0];
}
return targetDirStr + newFolderName;
}
private static String getFileExtension(String fileName) {
int dotIndex = fileName.lastIndexOf('.');
return (dotIndex == -1) ? "" : fileName.substring(dotIndex);
}
/**
* Extract a zip file to the target directory.
* @param file the zip file
* @param targetDirStr the target directory
* @return the name of the first directory in the zip file
* @throws IOException if the extraction fails
*/
private static String extractZipFile(File file, String targetDirStr) throws IOException {
try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(file))) {
ZipEntry entry = zipIn.getNextEntry();
String newFolderName = "";
while (entry != null) {
Path filePath = Paths.get(targetDirStr, entry.getName());
if (entry.isDirectory()) {
Files.createDirectories(filePath);
// the first directory is the one we want to return
if (newFolderName.isEmpty()) {
newFolderName = entry.getName();
}
} else {
Files.createDirectories(filePath.getParent());
try (OutputStream outputStream = new FileOutputStream(filePath.toFile())) {
byte[] buffer = new byte[4096];
int len;
while ((len = zipIn.read(buffer)) > -1) {
outputStream.write(buffer, 0, len);
}
}
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
return newFolderName;
}
}
/**
* Extract a bz2 file to the target directory.
* @param file the zip file
* @param targetDirStr the target directory
* @return the name of the first directory in the bz2 file
* @throws IOException if the extraction fails
*/
private static String extractBz2File(File file, String targetDirStr) throws IOException {
try (BZip2CompressorInputStream bzIn = new BZip2CompressorInputStream(new FileInputStream(file))) {
Path targetFilePath = Paths.get(targetDirStr, file.getName().replace(".bz2", ""));
Files.createDirectories(targetFilePath.getParent());
try (OutputStream outputStream = new FileOutputStream(targetFilePath.toFile())) {
byte[] buffer = new byte[4096];
int len;
while ((len = bzIn.read(buffer)) > -1) {
outputStream.write(buffer, 0, len);
}
}
return targetFilePath.getFileName().toString();
}
}
/**
* Extract a .tar.bz2 file to the target directory.
* @param file the zip file
* @param targetDirStr the target directory
* @return the name of the first directory in the bz2 file
* @throws IOException if the extraction fails
*/
private static String extractTarBz2File(File file, String targetDirStr) throws IOException {
try (BZip2CompressorInputStream bzIn = new BZip2CompressorInputStream(new FileInputStream(file));
TarArchiveInputStream tarIn = new TarArchiveInputStream(bzIn)) {
TarArchiveEntry entry;
String newFolderName = "";
while ((entry = tarIn.getNextEntry()) != null) {
Path filePath = Paths.get(targetDirStr, entry.getName());
if (entry.isDirectory()) {
Files.createDirectories(filePath);
if (newFolderName.isEmpty()) {
newFolderName = getFolderNameFromEntry(entry.getName());
}
} else {
Files.createDirectories(filePath.getParent());
try (OutputStream outputStream = new FileOutputStream(filePath.toFile())) {
byte[] buffer = new byte[4096];
int len;
while ((len = tarIn.read(buffer)) > -1) {
outputStream.write(buffer, 0, len);
}
}
}
}
return newFolderName;
}
}
private static String getFolderNameFromEntry(String entryName) {
String[] parts = entryName.split("/");
if (parts.length > 1) {
return String.join(File.separator, Arrays.copyOfRange(parts, 0, parts.length - 1)) + File.separator;
} else {
return parts[0];
}
}
}
e2f662c63This file is pinned to the selected release. Download the original to open it in your design tool.