Rework how we bypass browser caches of the scouting app

This patch adds a new way to access files on the webserver. Users can
now request files by their sha256 checksum. The URL pattern follows
`/sha256/<checksum>/<path>`. The `<path>` portion doesn't actually
matter, but it helps with readability.

This patch migrates the main bundle and the pictures to use the new
checksum-addressable scheme.

Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I76eaa9b0f69af98e48e8e73e32c82ce2916fbe41
diff --git a/scouting/www/BUILD b/scouting/www/BUILD
index 726749b..647af14 100644
--- a/scouting/www/BUILD
+++ b/scouting/www/BUILD
@@ -66,6 +66,29 @@
     cmd = "cp $(location :main_bundle_compiled)/main_bundle.min.js $(OUTS)",
 )
 
+py_binary(
+    name = "index_html_generator",
+    srcs = ["index_html_generator.py"],
+)
+
+genrule(
+    name = "generate_index_html",
+    srcs = [
+        "index.template.html",
+        "main_bundle_file.js",
+    ],
+    outs = ["index.html"],
+    cmd = " ".join([
+        "$(location :index_html_generator)",
+        "--template $(location index.template.html)",
+        "--bundle $(location main_bundle_file.js)",
+        "--output $(location index.html)",
+    ]),
+    tools = [
+        ":index_html_generator",
+    ],
+)
+
 # Create a copy of zone.js here so that we can have a predictable path to
 # source it from on the webserver.
 genrule(
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index 0e76268..d0805d4 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -37,7 +37,10 @@
 
   <div *ngSwitchCase="'Auto'" id="auto" class="container-fluid">
     <div class="row">
-      <img src="/pictures/field/quadrants.jpeg" alt="Quadrants Image" />
+      <img
+        src="/sha256/cbb99a057a2504e80af526dae7a0a04121aed84c56a6f4889e9576fe1c20c61e/pictures/field/quadrants.jpeg"
+        alt="Quadrants Image"
+      />
       <form>
         <input
           type="radio"
@@ -75,7 +78,10 @@
       </form>
     </div>
     <div class="row">
-      <img src="/pictures/field/balls.jpeg" alt="Image" />
+      <img
+        src="/sha256/cbb99a057a2504e80af526dae7a0a04121aed84c56a6f4889e9576fe1c20c61e/pictures/field/balls.jpeg"
+        alt="Image"
+      />
       <form>
         <!--Choice for each ball location-->
         <input
diff --git a/scouting/www/index.html b/scouting/www/index.template.html
similarity index 79%
rename from scouting/www/index.html
rename to scouting/www/index.template.html
index c9e2bb3..303dca1 100644
--- a/scouting/www/index.html
+++ b/scouting/www/index.template.html
@@ -14,6 +14,7 @@
   </head>
   <body>
     <my-app></my-app>
-    <script src="./main_bundle_file.js"></script>
+    <!-- The path here is auto-generated to be /sha256/<checksum>/main_bundle_file.js. -->
+    <script src="{MAIN_BUNDLE_FILE}"></script>
   </body>
 </html>
diff --git a/scouting/www/index_html_generator.py b/scouting/www/index_html_generator.py
new file mode 100644
index 0000000..3b057fd
--- /dev/null
+++ b/scouting/www/index_html_generator.py
@@ -0,0 +1,28 @@
+"""Generates index.html with the right checksum for main_bundle_file.js filled in."""
+
+import argparse
+import hashlib
+import sys
+from pathlib import Path
+
+def compute_sha256(filepath):
+    return hashlib.sha256(filepath.read_bytes()).hexdigest()
+
+def main(argv):
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--template", type=str)
+    parser.add_argument("--bundle", type=str)
+    parser.add_argument("--output", type=str)
+    args = parser.parse_args(argv[1:])
+
+    template = Path(args.template).read_text()
+    bundle_path = Path(args.bundle)
+    bundle_sha256 = compute_sha256(bundle_path)
+
+    output = template.format(
+        MAIN_BUNDLE_FILE = f"/sha256/{bundle_sha256}/{bundle_path.name}",
+    )
+    Path(args.output).write_text(output)
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))