204 lines
6.6 KiB
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);
|
|
}
|
|
|
|
}
|