From 8c8b45b8992cf29f0746f5829aa4890207ebd3b8 Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Mon, 24 Oct 2022 21:52:09 +0200 Subject: [PATCH 01/11] chore: add JSoup and Apache Commons library --- pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pom.xml b/pom.xml index 9ef9fd5..99539db 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,18 @@ 5.6.2 test + + + org.jsoup + jsoup + 1.15.3 + + + + org.apache.commons + commons-lang3 + 3.12.0 + From 8f578c6e4f405be0e90040528ab7a3e9ae80eba9 Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Mon, 24 Oct 2022 21:56:21 +0200 Subject: [PATCH 02/11] feat: add CLITools service data extraction --- .../boxedroid/service/CLIToolsService.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java b/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java new file mode 100644 index 0000000..295764c --- /dev/null +++ b/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java @@ -0,0 +1,66 @@ +package fr.bmarsaud.boxedroid.service; + +import org.apache.commons.lang3.SystemUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + +import java.io.IOException; + +public class CLIToolsService { + private static final String ANDROID_STUDIO_DOWNLOAD_PAGE = "https://developer.android.com/studio"; + private Document downloadPageDocument; + + /** + * Extract command line tools download URL for the current platform from the android studio download page + * @return The command line tools download URL + */ + public String getCommandLineToolsDownloadURL() throws IOException { + Document document = getDocument(); + Element element = document.select(getDownloadModalSelector() + " a[data-action=download]").first(); + if(element != null) { + return element.attr("href"); + } + return null; + } + + /** + * Extract HTML licence terms for the current platform from the android studio download page + * @return The command line tools download URL + */ + public String getCommandLineToolsHTMLTerms() throws IOException { + Document document = getDocument(); + Element element = document.select(getDownloadModalSelector() + " .sdk-terms").first(); + if(element != null) { + return element.html(); + } + return null; + } + + /** + * Retrieve the Android Studio download page document + * @return The download page document + */ + private Document getDocument() throws IOException { + if(downloadPageDocument == null) { + downloadPageDocument = Jsoup.connect(ANDROID_STUDIO_DOWNLOAD_PAGE).get(); + } + return downloadPageDocument; + } + + /** + * Get command line tools download modal selector from the current platform + * @return The download modal selector + */ + private String getDownloadModalSelector() { + String platform = ""; + if(SystemUtils.IS_OS_LINUX) { + platform = "linux"; + } else if(SystemUtils.IS_OS_MAC) { + platform = "mac"; + } else if(SystemUtils.IS_OS_WINDOWS) { + platform = "win"; + } + return "#sdk_" + platform + "_download"; + } +} From b40c2e84d01215050651d5fff0244f21bc7289b5 Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Thu, 27 Oct 2022 20:55:34 +0200 Subject: [PATCH 03/11] feat: add IO utils --- .../bmarsaud/boxedroid/service/IOUtils.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java b/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java new file mode 100644 index 0000000..4d445a4 --- /dev/null +++ b/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java @@ -0,0 +1,69 @@ +package fr.bmarsaud.boxedroid.service; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class IOUtils { + /** + * Download a file from a source to a destination. + * @param sourceUrl The source URL to download the file from. + * @param destination The destination to download the file to. + */ + public static void downloadFile(String sourceUrl, Path destination) throws IOException { + URL url = new URL(sourceUrl); + ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream()); + try(FileOutputStream fileOutputStream = new FileOutputStream(destination.toFile())) { + fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + } + } + + /** + * Unzip all files from a zip file to a destination. + * @param zipFile The zip file to unzip. + * @param destination The destination where to unzip the archive. + */ + public static void unzipFile(File zipFile, Path destination) throws IOException { + try(FileInputStream fileInputStream = new FileInputStream(zipFile); + ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) { + for(ZipEntry zipEntry = zipInputStream.getNextEntry(); zipEntry != null; zipEntry = zipInputStream.getNextEntry()) { + File outputFile = destination.resolve(zipEntry.getName()).toFile(); + if(zipEntry.isDirectory()) { + if (!outputFile.isDirectory() && !outputFile.mkdirs()) { + throw new IOException("Failed to create directory " + outputFile); + } + } else { + File parent = outputFile.getParentFile(); + if(!parent.isDirectory() && !parent.mkdirs()) { + throw new IOException("Failed to create directory " + parent); + } + try(FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { + fileOutputStream.getChannel().transferFrom(Channels.newChannel(zipInputStream), 0, Long.MAX_VALUE); + } + } + } + } + } + + /** + * Read the first line from the standard input. + * @return The first line of the standard input, null if an {@link IOException} occurred. + */ + public static String readFromStandardInput() { + try { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + return br.readLine(); + } catch (IOException e) { + return null; + } + } +} From 3f0f9f29f86da7b6bb698be6b1c06874e2b0a609 Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Thu, 27 Oct 2022 20:57:02 +0200 Subject: [PATCH 04/11] feat: add cli tools service entrypoint --- .../boxedroid/service/CLIToolsService.java | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java b/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java index 295764c..9fc50b1 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java @@ -6,16 +6,64 @@ import org.jsoup.nodes.Element; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; public class CLIToolsService { private static final String ANDROID_STUDIO_DOWNLOAD_PAGE = "https://developer.android.com/studio"; + + private final String sdkPath; private Document downloadPageDocument; + private String commandLineToolsPath; + + public CLIToolsService(String sdkPath) { + this.sdkPath = sdkPath; + } + + /** + * Checks if cmdline-tools are available from the SDK path. + * If not, download them from the Google website after asking the user to accept the agreements + * terms. + */ + public void acquireCommandLineTools() throws IOException { + commandLineToolsPath = resolveCommandLineToolsPath(sdkPath); + + if(commandLineToolsPath == null) { + System.out.println("Command Line Tools not found in " + sdkPath); + System.out.println("Do you want to download it from Google website? (y/n)"); + + String answer = IOUtils.readFromStandardInput(); + if (!"y".equalsIgnoreCase(answer)) { + System.exit(1); + } + + String terms = getCommandLineToolsHTMLTerms(); + if(terms != null) { + System.out.println(terms.replaceAll("
", "\n")); + System.out.println("I have read and agree with the above terms and conditions (y/n)"); + + answer = IOUtils.readFromStandardInput(); + if (!"y".equalsIgnoreCase(answer)) { + System.exit(1); + } + + String downloadUrl = getCommandLineToolsDownloadURL(); + Path downloadedFile = Paths.get(System.getProperty("java.io.tmpdir"), "cmdline-tools.zip"); + IOUtils.downloadFile(downloadUrl, downloadedFile); + IOUtils.unzipFile(downloadedFile.toFile(), Paths.get(sdkPath)); + + commandLineToolsPath = resolveCommandLineToolsPath(sdkPath); + } + } + } /** * Extract command line tools download URL for the current platform from the android studio download page * @return The command line tools download URL */ - public String getCommandLineToolsDownloadURL() throws IOException { + private String getCommandLineToolsDownloadURL() throws IOException { Document document = getDocument(); Element element = document.select(getDownloadModalSelector() + " a[data-action=download]").first(); if(element != null) { @@ -28,7 +76,7 @@ public String getCommandLineToolsDownloadURL() throws IOException { * Extract HTML licence terms for the current platform from the android studio download page * @return The command line tools download URL */ - public String getCommandLineToolsHTMLTerms() throws IOException { + private String getCommandLineToolsHTMLTerms() throws IOException { Document document = getDocument(); Element element = document.select(getDownloadModalSelector() + " .sdk-terms").first(); if(element != null) { @@ -37,6 +85,25 @@ public String getCommandLineToolsHTMLTerms() throws IOException { return null; } + /** + * Find the correct path to cmdline-tools binaries. + * In some versions of the Android SDK, cmdline-tools binaries are under the `latest` directory. + * @param sdkPath The path to the Android SDK + * @return The path to cmdline-tools binaries + */ + private String resolveCommandLineToolsPath(String sdkPath) { + List pathsToCheck = Arrays.asList( + "cmdline-tools/bin", + "cmdline-tools/latest/bin" + ); + for(String path : pathsToCheck) { + if(Paths.get(sdkPath, path, "sdkmanager").toFile().exists()) { + return path; + } + } + return null; + } + /** * Retrieve the Android Studio download page document * @return The download page document @@ -63,4 +130,8 @@ private String getDownloadModalSelector() { } return "#sdk_" + platform + "_download"; } + + public String getCommandLineToolsPath() { + return commandLineToolsPath; + } } From aca81ad1fe0f2217a8a5dff84b14f72f2fea2cba Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Fri, 28 Oct 2022 18:01:33 +0200 Subject: [PATCH 05/11] feat: switch to zip4J to handle zip unix permissions --- pom.xml | 6 ++++ .../bmarsaud/boxedroid/service/IOUtils.java | 28 ++++--------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 99539db..f44fdbe 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,12 @@ commons-lang3 3.12.0 + + + net.lingala.zip4j + zip4j + 2.11.2 + diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java b/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java index 4d445a4..8a27b10 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java @@ -1,5 +1,7 @@ package fr.bmarsaud.boxedroid.service; +import net.lingala.zip4j.ZipFile; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -10,8 +12,6 @@ import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.file.Path; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; public class IOUtils { /** @@ -29,28 +29,12 @@ public static void downloadFile(String sourceUrl, Path destination) throws IOExc /** * Unzip all files from a zip file to a destination. - * @param zipFile The zip file to unzip. + * @param file The zip file to unzip. * @param destination The destination where to unzip the archive. */ - public static void unzipFile(File zipFile, Path destination) throws IOException { - try(FileInputStream fileInputStream = new FileInputStream(zipFile); - ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) { - for(ZipEntry zipEntry = zipInputStream.getNextEntry(); zipEntry != null; zipEntry = zipInputStream.getNextEntry()) { - File outputFile = destination.resolve(zipEntry.getName()).toFile(); - if(zipEntry.isDirectory()) { - if (!outputFile.isDirectory() && !outputFile.mkdirs()) { - throw new IOException("Failed to create directory " + outputFile); - } - } else { - File parent = outputFile.getParentFile(); - if(!parent.isDirectory() && !parent.mkdirs()) { - throw new IOException("Failed to create directory " + parent); - } - try(FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { - fileOutputStream.getChannel().transferFrom(Channels.newChannel(zipInputStream), 0, Long.MAX_VALUE); - } - } - } + public static void unzipFile(File file, Path destination) throws IOException { + try(ZipFile zipFile = new ZipFile(file)) { + zipFile.extractAll(destination.toString()); } } From 2196c96fc63c3dc4eb770d4464c77ba25d4a974d Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Fri, 28 Oct 2022 18:02:31 +0200 Subject: [PATCH 06/11] feat: acquire cmdline-tools before launching emulator --- .../java/fr/bmarsaud/boxedroid/Boxedroid.java | 21 +++++++++++++++++-- .../boxedroid/service/AVDService.java | 7 ++++--- .../boxedroid/service/CLIToolsService.java | 12 ++++------- .../boxedroid/service/SDKService.java | 7 ++++--- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/main/java/fr/bmarsaud/boxedroid/Boxedroid.java b/src/main/java/fr/bmarsaud/boxedroid/Boxedroid.java index 47ae140..f3f11f6 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/Boxedroid.java +++ b/src/main/java/fr/bmarsaud/boxedroid/Boxedroid.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; @@ -24,6 +25,7 @@ import fr.bmarsaud.boxedroid.entity.exception.DeviceNotAvailableException; import fr.bmarsaud.boxedroid.entity.exception.SDKException; import fr.bmarsaud.boxedroid.service.AVDService; +import fr.bmarsaud.boxedroid.service.CLIToolsService; import fr.bmarsaud.boxedroid.service.SDKService; public class Boxedroid { @@ -33,6 +35,7 @@ public class Boxedroid { private Logger logger = LoggerFactory.getLogger(Boxedroid.class); private String sdkPath; + private String cmdlineToolsPath; public Boxedroid(String sdkPath) { this.sdkPath = sdkPath; @@ -60,8 +63,8 @@ public void launchEmulator(AndroidVersion version, ABI abi, Variant variant, Str System.exit(1); }; - SDKService sdkService = new SDKService(sdkPath); - AVDService avdService = new AVDService(sdkPath, currentDir); + SDKService sdkService = new SDKService(sdkPath, cmdlineToolsPath); + AVDService avdService = new AVDService(sdkPath, cmdlineToolsPath, currentDir); try { sdkService.install(version.getApiLevel(), abi, variant); @@ -79,6 +82,19 @@ public void launchEmulator(AndroidVersion version, ABI abi, Variant variant, Str } } + /** + * Find where cmdline-tools are installed. Install them if not found. + */ + public void acquireCommandLineTools() { + CLIToolsService cliToolsService = new CLIToolsService(sdkPath); + try { + cmdlineToolsPath = cliToolsService.acquireCommandLineTools(); + } catch (IOException e) { + logger.error(e.getMessage()); + System.exit(1); + } + } + public static void main(String[] args) { Options options = new Options(); options.addOption( @@ -178,6 +194,7 @@ public static void main(String[] args) { } Boxedroid boxedroid = new Boxedroid(sdkPath); + boxedroid.acquireCommandLineTools(); boxedroid.launchEmulator(androidVersion, abi, variant, device); } } diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/AVDService.java b/src/main/java/fr/bmarsaud/boxedroid/service/AVDService.java index 0429096..b917e2e 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/AVDService.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/AVDService.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -24,7 +25,7 @@ import fr.bmarsaud.boxedroid.util.IOUtils; public class AVDService { - private static final String AVD_MANAGER_PATH = "tools/bin/avdmanager"; + private static final String AVD_MANAGER_PATH = "avdmanager"; private static final String EMULATOR_PATH = "emulator/emulator"; private static final String AVD_DIR = "avd"; private static final String SYS_IMAGE_PREFIX = "Sdk/"; @@ -38,8 +39,8 @@ public class AVDService { private List avds; private String avdsPath; - public AVDService(String sdkPath, String boxedroidPath) { - this.avdManager = new AVDManager(new File(sdkPath, AVD_MANAGER_PATH).getAbsolutePath()); + public AVDService(String sdkPath, String cmdlineToolsPath, String boxedroidPath) { + this.avdManager = new AVDManager(Paths.get(sdkPath, cmdlineToolsPath, AVD_MANAGER_PATH).toAbsolutePath().toString()); this.emulator = new Emulator(new File(sdkPath, EMULATOR_PATH).getAbsolutePath(), sdkPath); this.avdsPath = new File(boxedroidPath, AVD_DIR).getAbsolutePath(); this.avds = new ArrayList<>(); diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java b/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java index 9fc50b1..4e9e2fc 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java @@ -16,7 +16,6 @@ public class CLIToolsService { private final String sdkPath; private Document downloadPageDocument; - private String commandLineToolsPath; public CLIToolsService(String sdkPath) { this.sdkPath = sdkPath; @@ -26,10 +25,10 @@ public CLIToolsService(String sdkPath) { * Checks if cmdline-tools are available from the SDK path. * If not, download them from the Google website after asking the user to accept the agreements * terms. + * @return The path to the cmdline-tools binaries */ - public void acquireCommandLineTools() throws IOException { - commandLineToolsPath = resolveCommandLineToolsPath(sdkPath); - + public String acquireCommandLineTools() throws IOException { + String commandLineToolsPath = resolveCommandLineToolsPath(sdkPath); if(commandLineToolsPath == null) { System.out.println("Command Line Tools not found in " + sdkPath); System.out.println("Do you want to download it from Google website? (y/n)"); @@ -57,6 +56,7 @@ public void acquireCommandLineTools() throws IOException { commandLineToolsPath = resolveCommandLineToolsPath(sdkPath); } } + return commandLineToolsPath; } /** @@ -130,8 +130,4 @@ private String getDownloadModalSelector() { } return "#sdk_" + platform + "_download"; } - - public String getCommandLineToolsPath() { - return commandLineToolsPath; - } } diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java b/src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java index 6a25122..397ff8d 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -28,12 +29,12 @@ public class SDKService { private List installedPackages; private List availableUpdates; - private static final String SDK_MANAGER_PATH = "tools/bin/sdkmanager"; + private static final String SDK_MANAGER_PATH = "sdkmanager"; private SDKManager sdkManager; - public SDKService(String sdkPath) { - this.sdkManager = new SDKManager(new File(sdkPath, SDK_MANAGER_PATH).getAbsolutePath(), sdkPath); + public SDKService(String sdkPath, String cmdlineToolsPath) { + this.sdkManager = new SDKManager(Paths.get(sdkPath, cmdlineToolsPath, SDK_MANAGER_PATH).toAbsolutePath().toString(), sdkPath); this.availablePackages = new ArrayList<>(); this.installedPackages = new ArrayList<>(); this.availableUpdates = new ArrayList<>(); From 30a424170500833e30368954959b2bdf7faf3f8c Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Sat, 29 Oct 2022 12:07:01 +0200 Subject: [PATCH 07/11] refactor: rename to cmdline-tools --- .../java/fr/bmarsaud/boxedroid/Boxedroid.java | 10 +++--- ...sService.java => CmdlineToolsService.java} | 34 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) rename src/main/java/fr/bmarsaud/boxedroid/service/{CLIToolsService.java => CmdlineToolsService.java} (77%) diff --git a/src/main/java/fr/bmarsaud/boxedroid/Boxedroid.java b/src/main/java/fr/bmarsaud/boxedroid/Boxedroid.java index f3f11f6..0fedf6e 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/Boxedroid.java +++ b/src/main/java/fr/bmarsaud/boxedroid/Boxedroid.java @@ -25,7 +25,7 @@ import fr.bmarsaud.boxedroid.entity.exception.DeviceNotAvailableException; import fr.bmarsaud.boxedroid.entity.exception.SDKException; import fr.bmarsaud.boxedroid.service.AVDService; -import fr.bmarsaud.boxedroid.service.CLIToolsService; +import fr.bmarsaud.boxedroid.service.CmdlineToolsService; import fr.bmarsaud.boxedroid.service.SDKService; public class Boxedroid { @@ -85,10 +85,10 @@ public void launchEmulator(AndroidVersion version, ABI abi, Variant variant, Str /** * Find where cmdline-tools are installed. Install them if not found. */ - public void acquireCommandLineTools() { - CLIToolsService cliToolsService = new CLIToolsService(sdkPath); + public void acquireCmdlineTools() { + CmdlineToolsService cmdlineToolsService = new CmdlineToolsService(sdkPath); try { - cmdlineToolsPath = cliToolsService.acquireCommandLineTools(); + cmdlineToolsPath = cmdlineToolsService.acquireCmdlineTools(); } catch (IOException e) { logger.error(e.getMessage()); System.exit(1); @@ -194,7 +194,7 @@ public static void main(String[] args) { } Boxedroid boxedroid = new Boxedroid(sdkPath); - boxedroid.acquireCommandLineTools(); + boxedroid.acquireCmdlineTools(); boxedroid.launchEmulator(androidVersion, abi, variant, device); } } diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java b/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java similarity index 77% rename from src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java rename to src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java index 4e9e2fc..646976a 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/CLIToolsService.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java @@ -11,13 +11,13 @@ import java.util.Arrays; import java.util.List; -public class CLIToolsService { +public class CmdlineToolsService { private static final String ANDROID_STUDIO_DOWNLOAD_PAGE = "https://developer.android.com/studio"; private final String sdkPath; private Document downloadPageDocument; - public CLIToolsService(String sdkPath) { + public CmdlineToolsService(String sdkPath) { this.sdkPath = sdkPath; } @@ -27,10 +27,10 @@ public CLIToolsService(String sdkPath) { * terms. * @return The path to the cmdline-tools binaries */ - public String acquireCommandLineTools() throws IOException { - String commandLineToolsPath = resolveCommandLineToolsPath(sdkPath); - if(commandLineToolsPath == null) { - System.out.println("Command Line Tools not found in " + sdkPath); + public String acquireCmdlineTools() throws IOException { + String cmdlineToolsPath = resolveCmdlineToolsPath(sdkPath); + if(cmdlineToolsPath == null) { + System.out.println("cmdline-tools not found in " + sdkPath); System.out.println("Do you want to download it from Google website? (y/n)"); String answer = IOUtils.readFromStandardInput(); @@ -38,7 +38,7 @@ public String acquireCommandLineTools() throws IOException { System.exit(1); } - String terms = getCommandLineToolsHTMLTerms(); + String terms = getCmdlineToolsHTMLTerms(); if(terms != null) { System.out.println(terms.replaceAll("
", "\n")); System.out.println("I have read and agree with the above terms and conditions (y/n)"); @@ -48,22 +48,22 @@ public String acquireCommandLineTools() throws IOException { System.exit(1); } - String downloadUrl = getCommandLineToolsDownloadURL(); + String downloadUrl = getCmdlineToolsDownloadURL(); Path downloadedFile = Paths.get(System.getProperty("java.io.tmpdir"), "cmdline-tools.zip"); IOUtils.downloadFile(downloadUrl, downloadedFile); IOUtils.unzipFile(downloadedFile.toFile(), Paths.get(sdkPath)); - commandLineToolsPath = resolveCommandLineToolsPath(sdkPath); + cmdlineToolsPath = resolveCmdlineToolsPath(sdkPath); } } - return commandLineToolsPath; + return cmdlineToolsPath; } /** - * Extract command line tools download URL for the current platform from the android studio download page - * @return The command line tools download URL + * Extract cmdline-tools download URL for the current platform from the android studio download page + * @return The cmdline-tools download URL */ - private String getCommandLineToolsDownloadURL() throws IOException { + private String getCmdlineToolsDownloadURL() throws IOException { Document document = getDocument(); Element element = document.select(getDownloadModalSelector() + " a[data-action=download]").first(); if(element != null) { @@ -74,9 +74,9 @@ private String getCommandLineToolsDownloadURL() throws IOException { /** * Extract HTML licence terms for the current platform from the android studio download page - * @return The command line tools download URL + * @return The cmdline-tools download URL */ - private String getCommandLineToolsHTMLTerms() throws IOException { + private String getCmdlineToolsHTMLTerms() throws IOException { Document document = getDocument(); Element element = document.select(getDownloadModalSelector() + " .sdk-terms").first(); if(element != null) { @@ -91,7 +91,7 @@ private String getCommandLineToolsHTMLTerms() throws IOException { * @param sdkPath The path to the Android SDK * @return The path to cmdline-tools binaries */ - private String resolveCommandLineToolsPath(String sdkPath) { + private String resolveCmdlineToolsPath(String sdkPath) { List pathsToCheck = Arrays.asList( "cmdline-tools/bin", "cmdline-tools/latest/bin" @@ -116,7 +116,7 @@ private Document getDocument() throws IOException { } /** - * Get command line tools download modal selector from the current platform + * Get cmdline-tools download modal selector from the current platform * @return The download modal selector */ private String getDownloadModalSelector() { From 6bc2005f4211059b83c45bb991f071455df27bf3 Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Sat, 29 Oct 2022 12:22:21 +0200 Subject: [PATCH 08/11] fix: auto close read channel --- src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java b/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java index 8a27b10..40cc96d 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java @@ -20,9 +20,8 @@ public class IOUtils { * @param destination The destination to download the file to. */ public static void downloadFile(String sourceUrl, Path destination) throws IOException { - URL url = new URL(sourceUrl); - ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream()); - try(FileOutputStream fileOutputStream = new FileOutputStream(destination.toFile())) { + try(ReadableByteChannel readableByteChannel = Channels.newChannel(new URL(sourceUrl).openStream()); + FileOutputStream fileOutputStream = new FileOutputStream(destination.toFile())) { fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE); } } From 54a431375df19b5692b220169389dfc7bcbbc253 Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Sat, 29 Oct 2022 12:22:45 +0200 Subject: [PATCH 09/11] chore: remove unused imports --- src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java | 1 - src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java b/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java index 40cc96d..069e090 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/IOUtils.java @@ -4,7 +4,6 @@ import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java b/src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java index 397ff8d..d2aee8f 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/SDKService.java @@ -3,7 +3,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; From d88cfb0fd9e0322efd8e597d6ff567bebae2f730 Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Sat, 29 Oct 2022 12:28:15 +0200 Subject: [PATCH 10/11] feat: use logger instead of sysout --- .../boxedroid/service/CmdlineToolsService.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java b/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java index 646976a..93bfa0c 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java @@ -4,6 +4,8 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.Path; @@ -14,6 +16,7 @@ public class CmdlineToolsService { private static final String ANDROID_STUDIO_DOWNLOAD_PAGE = "https://developer.android.com/studio"; + private final Logger logger = LoggerFactory.getLogger(CmdlineToolsService.class); private final String sdkPath; private Document downloadPageDocument; @@ -30,8 +33,8 @@ public CmdlineToolsService(String sdkPath) { public String acquireCmdlineTools() throws IOException { String cmdlineToolsPath = resolveCmdlineToolsPath(sdkPath); if(cmdlineToolsPath == null) { - System.out.println("cmdline-tools not found in " + sdkPath); - System.out.println("Do you want to download it from Google website? (y/n)"); + logger.info("cmdline-tools not found in " + sdkPath); + logger.info("Do you want to download it from Google website? (y/n)"); String answer = IOUtils.readFromStandardInput(); if (!"y".equalsIgnoreCase(answer)) { @@ -40,8 +43,8 @@ public String acquireCmdlineTools() throws IOException { String terms = getCmdlineToolsHTMLTerms(); if(terms != null) { - System.out.println(terms.replaceAll("
", "\n")); - System.out.println("I have read and agree with the above terms and conditions (y/n)"); + logger.info(terms.replaceAll("
", "\n")); + logger.info("I have read and agree with the above terms and conditions (y/n)"); answer = IOUtils.readFromStandardInput(); if (!"y".equalsIgnoreCase(answer)) { From c3b4daa6d58a1431094bdf05534896ab89b67f1f Mon Sep 17 00:00:00 2001 From: Bastien Marsaud Date: Sat, 29 Oct 2022 13:20:49 +0200 Subject: [PATCH 11/11] feat: transform HTML terms to plain text terms --- .../service/CmdlineToolsService.java | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java b/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java index 93bfa0c..7945b63 100644 --- a/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java +++ b/src/main/java/fr/bmarsaud/boxedroid/service/CmdlineToolsService.java @@ -12,6 +12,8 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class CmdlineToolsService { private static final String ANDROID_STUDIO_DOWNLOAD_PAGE = "https://developer.android.com/studio"; @@ -41,9 +43,9 @@ public String acquireCmdlineTools() throws IOException { System.exit(1); } - String terms = getCmdlineToolsHTMLTerms(); + String terms = getCmdlineToolTextTerms(); if(terms != null) { - logger.info(terms.replaceAll("
", "\n")); + logger.info(terms); logger.info("I have read and agree with the above terms and conditions (y/n)"); answer = IOUtils.readFromStandardInput(); @@ -76,8 +78,8 @@ private String getCmdlineToolsDownloadURL() throws IOException { } /** - * Extract HTML licence terms for the current platform from the android studio download page - * @return The cmdline-tools download URL + * Extract the HTML licence terms for the current platform from the android studio download page + * @return The cmdline-tools licence terms */ private String getCmdlineToolsHTMLTerms() throws IOException { Document document = getDocument(); @@ -88,6 +90,38 @@ private String getCmdlineToolsHTMLTerms() throws IOException { return null; } + /** + * Extract the plain-text licence terms for the current platform from the android studio download page + * @return The cmdline-tools licence terms + */ + private String getCmdlineToolTextTerms() throws IOException { + String terms = getCmdlineToolsHTMLTerms(); + if(terms != null) { + terms = terms.replace("
", "\n") + .replace("", "") + .replace("", ""); + + // Add line breaks on titles + Matcher matcher = Pattern.compile("(.*)").matcher(terms); + while(matcher.find()) { + terms = terms.replace(matcher.group(0), "\n" + matcher.group(1) + "\n"); + } + + // Add line breaks on numbered sections + matcher = Pattern.compile(" \\d+\\.\\d+ ").matcher(terms); + while(matcher.find()) { + terms = terms.replace(matcher.group(0), "\n" + matcher.group(0)); + } + + // Transform links to plain text links + matcher = Pattern.compile("(.*)").matcher(terms); + while(matcher.find()) { + terms = terms.replace(matcher.group(0), matcher.group(1)); + } + } + return terms; + } + /** * Find the correct path to cmdline-tools binaries. * In some versions of the Android SDK, cmdline-tools binaries are under the `latest` directory.