001package org.jdrupes.builder.ext.nodejs;
002
003import java.io.File;
004import java.io.IOException;
005import java.io.InputStream;
006import java.net.URL;
007import java.nio.file.Files;
008import java.nio.file.Path;
009import java.nio.file.StandardCopyOption;
010import java.util.Locale;
011import java.util.zip.ZipEntry;
012import java.util.zip.ZipInputStream;
013import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
014import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
015import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
016
017public class NodeDownloader {
018
019    private final Path baseDir;
020
021    public NodeDownloader(Path baseDir) {
022        this.baseDir = baseDir;
023    }
024
025    public void ensureInstalled(String version) throws Exception {
026        Path installDir = getInstallDir(version);
027        if (Files.exists(installDir)) {
028            return; // already installed
029        }
030
031        Files.createDirectories(baseDir);
032
033        Platform platform = detectPlatform();
034        String fileName = buildFileName(version, platform);
035        String downloadUrl
036            = "https://nodejs.org/dist/v" + version + "/" + fileName;
037
038        Path archive = baseDir.resolve(fileName);
039
040        download(downloadUrl, archive);
041
042        if (fileName.endsWith(".zip")) {
043            unzip(archive, baseDir);
044        } else if (fileName.endsWith(".tar.gz")) {
045            untarGz(archive, baseDir);
046        }
047
048        Files.deleteIfExists(archive);
049    }
050
051    public File getNpmExecutable(String version) {
052        Platform platform = detectPlatform();
053        Path installDir = getInstallDir(version);
054
055        if (platform.isWindows) {
056            return installDir.resolve("npm.cmd").toFile();
057        } else {
058            return installDir.resolve("bin/npm").toFile();
059        }
060    }
061
062    private Path getInstallDir(String version) {
063        Platform p = detectPlatform();
064        String folderName = "node-v" + version + "-" + p.platformName;
065        return baseDir.resolve(folderName);
066    }
067
068    private void download(String url, Path target) throws IOException {
069        try (InputStream in = new URL(url).openStream()) {
070            Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
071        }
072    }
073
074    private void unzip(Path zipFile, Path targetDir) throws IOException {
075        try (ZipInputStream zis
076            = new ZipInputStream(Files.newInputStream(zipFile))) {
077            ZipEntry entry;
078            while ((entry = zis.getNextEntry()) != null) {
079                Path newPath = targetDir.resolve(entry.getName());
080                if (entry.isDirectory()) {
081                    Files.createDirectories(newPath);
082                } else {
083                    Files.createDirectories(newPath.getParent());
084                    Files.copy(zis, newPath,
085                        StandardCopyOption.REPLACE_EXISTING);
086                }
087            }
088        }
089    }
090
091    private void untarGz(Path tarGz, Path targetDir) throws IOException {
092        try (InputStream fi = Files.newInputStream(tarGz);
093                InputStream gzi = new GzipCompressorInputStream(fi);
094                TarArchiveInputStream tais = new TarArchiveInputStream(gzi)) {
095
096            TarArchiveEntry entry;
097            while ((entry = tais.getNextTarEntry()) != null) {
098                Path newPath = targetDir.resolve(entry.getName());
099                if (entry.isDirectory()) {
100                    Files.createDirectories(newPath);
101                } else {
102                    Files.createDirectories(newPath.getParent());
103                    Files.copy(tais, newPath,
104                        StandardCopyOption.REPLACE_EXISTING);
105                }
106            }
107        }
108    }
109
110    private Platform detectPlatform() {
111        String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
112        String arch
113            = System.getProperty("os.arch").contains("64") ? "x64" : "x86";
114
115        if (os.contains("win")) {
116            return new Platform("win-" + arch, true, ".zip");
117        } else if (os.contains("mac")) {
118            return new Platform("darwin-" + arch, false, ".tar.gz");
119        } else if (os.contains("nux")) {
120            return new Platform("linux-" + arch, false, ".tar.gz");
121        }
122
123        throw new RuntimeException("Unsupported OS: " + os);
124    }
125
126    private String buildFileName(String version, Platform p) {
127        return "node-v" + version + "-" + p.platformName + p.extension;
128    }
129
130    private static class Platform {
131        final String platformName;
132        final boolean isWindows;
133        final String extension;
134
135        Platform(String platformName, boolean isWindows, String extension) {
136            this.platformName = platformName;
137            this.isWindows = isWindows;
138            this.extension = extension;
139        }
140    }
141
142}