shrooms-vb-web/Bundle.java

186 lines
5.7 KiB
Java

import java.io.*;
import java.nio.charset.*;
import java.time.*;
import java.util.*;
import java.util.zip.*;
// Web application asset packager
public class Bundle {
// Read a file from disk into a byte buffer
static byte[] fileRead(File file) {
try (var stream = new FileInputStream(file)) {
return stream.readAllBytes();
} catch (Exception e) { return null; }
}
// Retrieve the canonical form of a file
static File getFile(File file) {
try { return file.getCanonicalFile(); }
catch (Exception e) { return null; }
}
// Retrieve the canonical file for a relative filename
static File getFile(String filename) {
return getFile(new File(filename));
}
// List all canonical files within a directory
static File[] listFiles(File dir) {
var ret = dir.listFiles();
for (int x = 0; x < ret.length; x++)
ret[x] = getFile(ret[x]);
return ret;
}
// List all asset files to be bundled
static Asset[] listAssets(File root, File main) {
var assets = new ArrayList<Asset>();
var dirs = new ArrayList<File >();
// Initial directories
dirs.add(new File(root, "shrooms-vb-core"));
dirs.add(new File(root, "shrooms-vb-web" ));
// Process all directories
while (dirs.size() != 0) {
var dir = dirs.remove(0);
// Process all child files and directories
for (var file : listFiles(dir)) {
// Exclude this file or directory
if (file.equals(main) || file.getName().startsWith(".git"))
continue;
// Include this directory
if (file.isDirectory())
dirs.add(file);
// Include this file
else assets.add(new Asset(root, file));
}
}
Collections.sort(assets);
return assets.toArray(new Asset[assets.size()]);
}
// Determine the relative path of a file from the root directory
static String relativePath(File root, File file) {
// Work backwards to identify the full path
var path = new ArrayList<String>();
while (!root.equals(file)) {
path.add(0, file.getName());
file = file.getParentFile();
}
// Join the path parts with forward slashes
var ret = new StringBuilder();
for (String part : path)
ret.append("/" + part);
return ret.toString().substring(1);
}
// Express a byte array as a Base64 string
static String toBase64(byte[] data) {
return Base64.getMimeEncoder(0, new byte[0]).encodeToString(data);
}
// Encode a byte array as a zlib buffer
static byte[] toZlib(byte[] data) {
try {
var comp = new Deflater(Deflater.BEST_COMPRESSION, false);
comp.setInput(data);
comp.finish();
var ret = new byte[data.length];
ret = Arrays.copyOf(ret, comp.deflate(ret));
comp.end();
return ret;
} catch (Exception e) { throw new RuntimeException(e.getMessage()); }
}
// Program entry point
public static void main(String[] args) {
// Select all assets
var root = getFile("../");
var main = getFile("main.js");
var assets = listAssets(root, main);
// Resolve the current date code
var today = ZonedDateTime.now(Clock.systemUTC());
String dateCode = String.format("%04d%02d%02d",
today.getYear(), today.getMonthValue(), today.getDayOfMonth());
// Process the manifest
var manifest = new StringBuilder();
manifest.append("[");
for (var asset : assets) {
manifest.append(String.format("%s\"%s\",%d",
asset == assets[0] ? "" : ",", asset.name, asset.length));
}
manifest.append(",\"" + dateCode + "\"]");
// Encode the bundle
var bundle = new ByteArrayOutputStream();
try {
bundle.write(fileRead(main));
bundle.write(0);
bundle.write(manifest.toString().getBytes(StandardCharsets.UTF_8));
bundle.write(0);
for (var asset : assets)
bundle.write(fileRead(asset.file));
} catch (Exception e) {}
// Read the HTML template
var template = new String(
fileRead(new File("template.html")), StandardCharsets.UTF_8)
.split("\\\"\\\"");
// Generate the output HTML file
String filename = "../acid-shroom_" + dateCode + ".html";
try (var stream = new FileOutputStream(filename)) {
stream.write(template[0].getBytes(StandardCharsets.UTF_8));
stream.write('"');
stream.write(toBase64(toZlib(bundle.toByteArray()))
.getBytes(StandardCharsets.UTF_8));
stream.write('"');
stream.write(template[1].getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {}
}
///////////////////////////////// Classes /////////////////////////////////
// Packaged asset file
static class Asset implements Comparable<Asset> {
File file; // File on disk
int length; // Size in bytes
String name; // Filename without path
Asset(File root, File file) {
this.file = file;
length = (int) file.length();
name = relativePath(root, file);
}
public int compareTo(Asset o) {
return name.compareTo(o.name);
}
public boolean equals(Object o) {
return o instanceof Asset && compareTo((Asset) o) == 0;
}
public int hashCode() {
return name.hashCode();
}
}
}