pvbemu/web/Bundle.java

204 lines
6.6 KiB
Java

import java.awt.image.*;
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import javax.imageio.*;
public class Bundle {
/////////////////////////////// BundledFile ///////////////////////////////
// Individual packaged resource file
static class BundledFile implements Comparable<BundledFile> {
// Instance fields
byte[] data; // File data loaded from disk
File file; // Source file
String filename; // Logical filename
// Constructor
BundledFile(BundledFile parent, File file) {
// Configure instance fields
this.file = file;
filename = parent == null ? "" : parent.filename + file.getName();
// Load file data if file
if (file.isFile()) {
try (var stream = new FileInputStream(file)) {
data = stream.readAllBytes();
} catch (Exception e) { }
}
// Update filename if directory
else if (parent != null) filename += "/";
}
// Comparator
public int compareTo(BundledFile o) {
return
filename.equals("web/_boot.js") ? -1 :
o.filename.equals("web/_boot.js") ? +1 :
filename.compareTo(o.filename)
;
}
// Produce a list of child files or directories
BundledFile[] listFiles(String name, boolean isDirectory) {
// Produce a filtered list of files
var files = this.file.listFiles(f->{
// Does not satisfy the directory requirement
if (f.isDirectory() != isDirectory)
return false;
// Current directory is not root
if (!filename.equals(""))
return true;
// Filter specific files from being bundled
String filename = f.getName();
return !(
filename.startsWith(".git" ) ||
filename.startsWith(name + "_") &&
filename.endsWith (".html" )
);
});
// Process all files for bundling
var ret = new BundledFile[files.length];
for (int x = 0; x < files.length; x++)
ret[x] = new BundledFile(this, files[x]);
return ret;
}
}
///////////////////////////// Program Methods /////////////////////////////
// Program entry point
public static void main(String[] args) {
String name = name(args[0]);
var files = listFiles(args[0]);
var prepend = prepend(name, files);
var bundle = bundle(prepend, files);
var image = image(bundle);
var url = url(image);
patch(name, url);
}
// Produce a buffer of the bundled files
static byte[] bundle(byte[] prepend, BundledFile[] files) {
try (var stream = new ByteArrayOutputStream()) {
stream.write(prepend);
stream.write(files[0].data); // web/_boot.js
stream.write(0);
for (int x = 1; x < files.length; x++)
stream.write(files[x].data);
return stream.toByteArray();
} catch (Exception e) { return null; }
}
// Convert a bundle buffer into a PNG-encoded image buffer
static byte[] image(byte[] bundle) {
int width = (int) Math.ceil(Math.sqrt(bundle.length));
int height = (bundle.length + width - 1) / width;
var pixels = new int[width * height];
// Encode the buffer as a pixel array
for (int x = 0; x < bundle.length; x++) {
int b = bundle[x] & 0xFF;
pixels[x] = 0xFF000000 | b << 16 | b << 8 | b;
}
// Produce an image using the pixels
var image = new BufferedImage(
width, height, BufferedImage.TYPE_INT_RGB);
image.setRGB(0, 0, width, height, pixels, 0, width);
// Encode the image as a PNG buffer
try (var stream = new ByteArrayOutputStream()) {
ImageIO.write(image, "png", stream);
return stream.toByteArray();
} catch (Exception e) { return null; }
}
// List all files
static BundledFile[] listFiles(String name) {
var dirs = new ArrayList<BundledFile>();
var files = new ArrayList<BundledFile>();
// Propagate down the file system tree
dirs.add(new BundledFile(null, new File(".")));
while (!dirs.isEmpty()) {
var dir = dirs.remove(0);
for (var sub : dir.listFiles(name, true ))
dirs.add(sub );
for (var file : dir.listFiles(name, false))
files.add(file);
}
// Return the set of files as a sorted array
Collections.sort(files);
return files.toArray(new BundledFile[files.size()]);
}
// Generate a filename for the bundle
static String name(String name) {
var calendar = Calendar.getInstance();
return String.format("%s_%04d%02d%02d",
name,
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH) + 1,
calendar.get(Calendar.DAY_OF_MONTH)
);
}
// Produce the output HTML from the template
static void patch(String name, String url) {
String markup = null;
try (var stream = new FileInputStream("web/template.html")) {
markup = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
} catch (Exception e) { }
markup = markup.replace("src=\"\"", "src=\"" + url + "\"");
try (var stream = new FileOutputStream(name + ".html")) {
stream.write(markup.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) { }
}
// Produce source data to prepend to web/_boot.js
static byte[] prepend(String name, BundledFile[] files) {
var ret = new StringBuilder();
// Arguments
ret.append("let " +
"buffer=arguments[0]," +
"image=arguments[1]," +
"name=\"" + name + "\"" +
";");
// Bundle manifest
ret.append("let manifest=[");
for (var file : files) {
if (file != files[0])
ret.append(",");
ret.append("[\"" +
file.filename + "\", " + file.data.length + "]");
}
ret.append("];");
// Convert to byte array
return ret.toString().getBytes(StandardCharsets.UTF_8);
}
// Convert an image buffer to a data URL
static String url(byte[] image) {
return "data:image/png;base64," +
Base64.getMimeEncoder(0, new byte[0]).encodeToString(image);
}
}