• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

WindhoverLabs / yamcs-cfs / #150

05 Mar 2024 07:27PM UTC coverage: 0.0%. First build
#150

push

lorenzo-gomez-windhover
-Archive Beb plugin. WIP.

0 of 49 new or added lines in 3 files covered. (0.0%)

0 of 6900 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/com/windhoverlabs/beb/WebFileDeployer.java
1
package com.windhoverlabs.beb;
2

3
import static java.nio.charset.StandardCharsets.UTF_8;
4
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
5
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
6

7
import com.google.common.io.CharStreams;
8
import java.io.IOException;
9
import java.io.InputStream;
10
import java.io.InputStreamReader;
11
import java.io.UncheckedIOException;
12
import java.math.BigInteger;
13
import java.nio.file.Files;
14
import java.nio.file.Path;
15
import java.nio.file.WatchKey;
16
import java.nio.file.WatchService;
17
import java.security.MessageDigest;
18
import java.security.NoSuchAlgorithmException;
19
import java.util.ArrayList;
20
import java.util.HashMap;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.stream.Stream;
24
import org.yamcs.Experimental;
25
import org.yamcs.YConfiguration;
26
import org.yamcs.YamcsServer;
27
import org.yamcs.logging.Log;
28
// import org.yamcs.templating.ParseException;
29
import org.yamcs.templating.ParseException;
30
import org.yamcs.templating.TemplateProcessor;
31
import org.yamcs.utils.FileUtils;
32

33
/**
34
 * Deploys web files from a source to a target directory, while tweaking some files.
35
 *
36
 * <p>The source is determined in order to be either:
37
 *
38
 * <ul>
39
 *   <li>(1) Check system property <code>yamcs.web.staticRoot</code>
40
 *   <li>(2) Check a property in <code>etc/yamcs.yaml</code>
41
 *   <li>(3) Load from classpath (packaged inside yamcs-web jar)
42
 * </ul>
43
 *
44
 * A production deployment will use a precompiled web application, and use (3).
45
 *
46
 * <p>(1) and (2) are intended for enabling local development on the web sources. (1) allows doing
47
 * so without needing to modify the <code>etc/yamcs.yaml</code>.
48
 */
49
public class WebFileDeployer {
50

51
  private static final Log log = new Log(WebFileDeployer.class);
×
52
  public static final String PATH_INDEX_TEMPLATE = "index.template.html";
53
  public static final String PATH_INDEX = "index.html";
54
  public static final String PATH_NGSW = "ngsw.json";
55
  public static final String PATH_WEBMANIFEST = "manifest.webmanifest";
56

57
  // Optional, but immutable
58
  // (if null, webfiles are deployed from the classpath)
59
  private Path source;
60

61
  // Required, but with modified files
62
  private Path target;
63

64
  private List<Path> extraStaticRoots;
65
  private Map<String, Map<String, Object>> extraConfigs;
66

67
  private YConfiguration config;
68
  private String contextPath;
69

70
  public WebFileDeployer(
71
      YConfiguration config,
72
      String contextPath,
73
      List<Path> extraStaticRoots,
74
      Map<String, Map<String, Object>> extraConfigs)
75
      throws IOException {
×
76
    this.config = config;
×
77
    this.contextPath = contextPath;
×
78
    this.extraStaticRoots = extraStaticRoots;
×
79
    this.extraConfigs = extraConfigs;
×
80

81
    //    T
82
    System.out.println(
×
83
        "Beb web file deployer:" + YamcsServer.getServer().getConfigDirectory().getParent());
×
84
    target = YamcsServer.getServer().getConfigDirectory().getParent();
×
NEW
85
    target = target.resolve("beb");
×
86
    //    Eventually this target directory will come from the API, something like webDisplays config
87
    // in the YAMCS Webapp
88
    //    System.out.println("target:" + target.resolve("beb"));
89

90
    YamcsServer yamcs = YamcsServer.getServer();
×
91

92
    //    YConfiguration yamcsInstance = yamcs.getInstance(request.getInstance());
93
    //    if (yamcsInstance == null) {
94
    //      throw new NotFoundException("No such instance");
95
    //    }
96

97
    YConfiguration globalConfig =
×
98
        yamcs.getConfig().getConfigOrEmpty(com.windhoverlabs.beb.BebPlugin.CONFIG_SECTION);
×
99
    //    var instanceConfig = yamcsInstance.getConfig().getConfigOrEmpty(CONFIG_SECTION);
100

101
    //    var b = InstanceConfiguration.newBuilder();
102

103
    //    String displayBucket =
104
    // globalConfig.getString(com.windhoverlabs.beb.BebPlugin.CONFIG_DISPLAY_BUCKET);
105
    //            yamcs.get
106
    //    if (instanceConfig.containsKey(CONFIG_DISPLAY_BUCKET)) {
107
    //        displayBucket = instanceConfig.getString(CONFIG_DISPLAY_BUCKET);
108
    //    }
109
    //    target = YamcsServer.getServer().getCacheDirectory().resolve(BebPlugin.CONFIG_SECTION);
110
    //        FileUtils.deleteRecursivelyIfExists(target);
111
    //        Files.createDirectory(target);
112

113
    //        String sourceOverride = System.getProperty("yamcs.web.staticRoot");
114
    //        if (sourceOverride != null) {
115
    //            source = Path.of(sourceOverride);
116
    //            source = source.toAbsolutePath().normalize();
117
    //        } else if (config.containsKey("staticRoot")) {
118
    //            source = Path.of(config.getString("staticRoot"));
119
    //            source = source.toAbsolutePath().normalize();
120
    //        }
121

122
    //            TODO:Uncomment to deploy beb files
NEW
123
    boolean deployed = false;
×
NEW
124
    if (source != null) {
×
NEW
125
      if (Files.exists(source)) {
×
NEW
126
        log.debug("Deploying yamcs-web from {}", source);
×
127
        //                FileUtils.copyRecursively(source, target);
NEW
128
        deployed = true;
×
129

130
        // Watch for changes
NEW
131
        new Redeployer(source, target).start();
×
132
      } else {
NEW
133
        log.warn("Static root for yamcs-web not found at '{}'", source);
×
134
      }
135
    }
136

137
    //    Not needed by BEB for now
138
    //    if (!deployed) {
139
    //      deployWebsiteFromClasspath(target);
140
    //    }
141
    //
NEW
142
    prepareWebApplication();
×
143
  }
×
144

145
  /** Returns the directory where files are deployed */
146
  public Path getDirectory() {
147
    return target;
×
148
  }
149

150
  @Experimental
151
  public List<Path> getExtraStaticRoots() {
NEW
152
    extraStaticRoots.add(
×
NEW
153
        Path.of(
×
154
            "/home/lgomez/projects/beb_integration/squeaky-weasel/software/airliner/build/venus_aero/sassie/sitl_commander_workspace/beb/index.html"));
155
    return extraStaticRoots;
×
156
  }
157

158
  @Experimental
159
  public void setExtraSources(
160
      List<Path> extraStaticRoots, Map<String, Map<String, Object>> extraConfigs) {
161
    this.extraStaticRoots = extraStaticRoots;
×
162
    this.extraConfigs = extraConfigs;
×
163
    try { // Silent redeploy
164
      prepareWebApplication();
×
165
    } catch (IOException e) {
×
166
      log.error("Failed to deploy additional static roots", e);
×
167
    }
×
168
  }
×
169

170
  /**
171
   * Deploys all web files located in the classpath, as listed in a manifest.txt file. This file is
172
   * generated during the Maven build and enables us to skip having to do classpath listings.
173
   */
174
  private void deployWebsiteFromClasspath(Path target) throws IOException {
175
    try (InputStream in = getClass().getResourceAsStream("/static/manifest.txt");
×
176
        InputStreamReader reader = new InputStreamReader(in, UTF_8)) {
×
177

178
      String manifest = CharStreams.toString(reader);
×
179
      String[] staticFiles = manifest.split(";");
×
180

181
      log.debug("Unpacking {} webapp files", staticFiles.length);
×
182
      for (String staticFile : staticFiles) {
×
183
        try (InputStream resource = getClass().getResourceAsStream("/static/" + staticFile)) {
×
184
          Files.createDirectories(target.resolve(staticFile).getParent());
×
185
          Files.copy(resource, target.resolve(staticFile));
×
186
        }
187
      }
188
    }
189
  }
×
190

191
  private synchronized void prepareWebApplication() throws IOException {
192
    //    Path indexFile = target.resolve(PATH_INDEX);
193
    //    String content = renderIndex(indexFile);
194
    //    System.out.println("content:" + content);
195
    //    Files.writeString(indexFile, content, UTF_8);
196
    //                  hashTableOverrides.put("/" + PATH_INDEX, calculateSha1(content));
197
    //          NOTE:This complicated template logic is not really needed for beb
198
    // Keep track of SHA1 of modified files (for injection in ngsw.json)
199
    //    HashMap<String, String> hashTableOverrides = new HashMap<String, String>();
200
    //
201
    ////    Path indexTemplateFile = target.resolve(PATH_INDEX_TEMPLATE);
202
    ////    Path indexFile = target.resolve(PATH_INDEX);
203
    ////    if (Files.exists(indexTemplateFile)) {
204
    ////      String content = renderIndex(indexTemplateFile);
205
    ////      Files.writeString(indexFile, content, UTF_8);
206
    ////      hashTableOverrides.put("/" + PATH_INDEX, calculateSha1(content));
207
    ////    }
208
    ////
209
    //
210
    //    Path indexTemplateFile = target.resolve(PATH_INDEX_TEMPLATE);
211
    //    Path indexFile = target.resolve(PATH_INDEX);
212
    //    if (Files.exists(indexTemplateFile)) {
213
    //      String content = renderIndex(indexTemplateFile);
214
    ////      Files.writeString(indexFile, content, UTF_8);
215
    ////      hashTableOverrides.put("/" + PATH_INDEX, calculateSha1(content));
216
    //    }
217
    //    Path webManifestFile = target.resolve(PATH_WEBMANIFEST);
218
    //        if (Files.exists(webManifestFile)) {
219
    //            var content = renderWebManifest(webManifestFile);
220
    //            Files.writeString(webManifestFile, content, UTF_8);
221
    //            hashTableOverrides.put("/" + PATH_WEBMANIFEST, calculateSha1(content));
222
    //        }
223
    //        var ngswFile = target.resolve(PATH_NGSW);
224
    //        if (Files.exists(ngswFile)) {
225
    //            var ngswContent = renderNgsw(ngswFile, hashTableOverrides);
226
    //            Files.writeString(ngswFile, ngswContent, UTF_8);
227
    //        }
228
  }
×
229

230
  @SuppressWarnings("unchecked")
231
  private String renderIndex(Path file) throws IOException {
232
    String template = new String(Files.readAllBytes(file), UTF_8);
×
233

234
    ArrayList<Path> cssFiles = new ArrayList<Path>();
×
235
    ArrayList<Path> jsFiles = new ArrayList<Path>();
×
NEW
236
    System.out.println("extraStaticRoots:" + extraStaticRoots);
×
237
    for (Path extraStaticRoot : extraStaticRoots) {
×
238
      try (Stream<Path> listing = Files.list(extraStaticRoot)) {
×
239
        listing.forEachOrdered(
×
240
            extensionFile -> {
241
              String lcFilename = extensionFile.getFileName().toString().toLowerCase();
×
242
              if (lcFilename.endsWith(".css")) {
×
243
                cssFiles.add(extensionFile);
×
244
              } else if (lcFilename.endsWith(".js")) {
×
245
                jsFiles.add(extensionFile);
×
246
              }
247
            });
×
248
      }
249
    }
×
250

251
    StringBuilder extraHeaderHtml = new StringBuilder();
×
252
    for (Path cssFile : cssFiles) {
×
253
      extraHeaderHtml
×
254
          .append("<link rel=\"stylesheet\" href=\"")
×
255
          .append(contextPath)
×
256
          .append("/")
×
257
          .append(cssFile.getFileName())
×
258
          .append("\">\n");
×
259
    }
×
260
    for (Path jsFile : jsFiles) {
×
261
      extraHeaderHtml
×
262
          .append("<script src=\"")
×
263
          .append(contextPath)
×
264
          .append("/")
×
265
          .append(jsFile.getFileName())
×
266
          .append("\" type=\"module\"></script>\n");
×
267
    }
×
268

269
    //    extraHeaderHtml.append(config.getString("extraHeaderHTML", ""));
270

271
    // Don't use template for this, because Angular compiler messes up the HTML
272

NEW
273
    return extraHeaderHtml.toString();
×
274
    //        template = template.replace("<!--[[ EXTRA_HEADER_HTML ]]-->",
275
    // extraHeaderHtml.toString());
276
    //
277
    //    HashMap webConfig = new HashMap<>(config.toMap());
278
    //
279
    //    // Remove filesystem path from custom logo
280
    //    if (config.containsKey("logo")) {
281
    //      Path logo = Paths.get(config.getString("logo"));
282
    //      String filename = logo.getFileName().toString();
283
    //      webConfig.put("logo", contextPath + "/" + filename);
284
    //    }
285
    //
286
    //    AuthInfo authInfo = AuthHandler.createAuthInfo();
287
    //    String authJson = JsonFormat.printer().print(authInfo);
288
    //    Map authMap = new Gson().fromJson(authJson, Map.class);
289
    //    webConfig.put("auth", authMap);
290
    //
291
    //    YamcsServer yamcs = YamcsServer.getServer();
292
    //
293
    //    PluginManager pluginManager = yamcs.getPluginManager();
294
    //    ArrayList<String> plugins = new ArrayList<String>();
295
    //    for (Plugin plugin : pluginManager.getPlugins()) {
296
    //      String pluginName = pluginManager.getMetadata(plugin.getClass()).getName();
297
    //      plugins.add(pluginName);
298
    //    }
299
    //    webConfig.put("plugins", plugins);
300
    //
301
    //    ArrayList<Map<String, Object>> commandOptions = new ArrayList<Map<String, Object>>();
302
    //    for (CommandOption option : yamcs.getCommandOptions()) {
303
    //      String json = JsonFormat.printer().print(ServerApi.toCommandOptionInfo(option));
304
    //      commandOptions.add(new Gson().fromJson(json, Map.class));
305
    //    }
306
    //    webConfig.put("commandOptions", commandOptions);
307
    //    webConfig.put("serverId", yamcs.getServerId());
308
    //    webConfig.put("hasTemplates", !yamcs.getInstanceTemplates().isEmpty());
309
    //
310
    //    // Enable clearance-related UI only if there's potential for a processor
311
    //    // that has it enabled (we expect most people to not use this feature).
312
    //    boolean commandClearanceEnabled =
313
    //        ProcessorFactory.getProcessorTypes().entrySet().stream()
314
    //            .anyMatch(entry -> entry.getValue().checkCommandClearance());
315
    //    webConfig.put("commandClearanceEnabled", commandClearanceEnabled);
316
    //
317
    //    // Make queue names directly available without API request. It is used
318
    //    // for populating a command history combo box.
319
    //    TreeSet<String> queueNames = new TreeSet<String>(); // Sorted
320
    //    for (CommandQueueManager qmanager :
321
    // ManagementService.getInstance().getCommandQueueManagers()) {
322
    //      for (CommandQueue queue : qmanager.getQueues()) {
323
    //        queueNames.add(queue.getName());
324
    //      }
325
    //    }
326
    //    webConfig.put("queueNames", queueNames);
327
    //
328
    //    // May be used by web extensions to pass arbitrary information
329
    //    webConfig.put("extra", extraConfigs);
330
    //
331
    //    HashMap<String, Object> args = new HashMap<String, Object>();
332
    //    args.put("contextPath", contextPath);
333
    //    args.put("config", webConfig);
334
    //    args.put("configJson", new Gson().toJson(webConfig));
335
    //    try {
336
    //      return TemplateProcessor.process(template, args);
337
    //    } catch (Exception e) {
338
    //      // TODO Auto-generated catch block
339
    //      e.printStackTrace();
340
    //    }
341
    //    return "";
342
  }
343

344
  private String renderWebManifest(Path file) throws IOException, ParseException {
345
    String template = new String(Files.readAllBytes(file), UTF_8);
×
346
    HashMap<String, Object> args = new HashMap<String, Object>();
×
347
    args.put("contextPath", contextPath);
×
348
    return TemplateProcessor.process(template, args);
×
349
  }
350

351
  private String renderNgsw(Path file, Map<String, String> hashTableOverrides) throws IOException {
352
    //        var gson = new GsonBuilder().setPrettyPrinting().create();
353
    //        try (var reader = Files.newBufferedReader(file, UTF_8)) {
354
    //            var jsonObject = gson.fromJson(reader, JsonObject.class);
355
    //            if (jsonObject.get("configVersion").getAsInt() != 1) {
356
    //                log.warn("Unexpected ngsw.json config version");
357
    //            }
358
    //            jsonObject.addProperty("index", contextPath +
359
    // jsonObject.get("index").getAsString());
360
    //
361
    //            for (var assetGroupEl : jsonObject.get("assetGroups").getAsJsonArray()) {
362
    //                var assetGroup = assetGroupEl.getAsJsonObject();
363
    //
364
    //                var modifiedUrls = new JsonArray();
365
    //                for (var urlEl : assetGroup.get("urls").getAsJsonArray()) {
366
    //                    modifiedUrls.add(contextPath + urlEl.getAsString());
367
    //                }
368
    //                assetGroup.add("urls", modifiedUrls);
369
    //            }
370
    //
371
    //            for (var dataGroupEl : jsonObject.get("dataGroups").getAsJsonArray()) {
372
    //                var dataGroup = dataGroupEl.getAsJsonObject();
373
    //
374
    //                var modifiedPatterns = new JsonArray();
375
    //                for (var patternEl : dataGroup.get("patterns").getAsJsonArray()) {
376
    //                    modifiedPatterns.add(Pattern.quote(contextPath) +
377
    // patternEl.getAsString());
378
    //                }
379
    //                dataGroup.add("patterns", modifiedPatterns);
380
    //            }
381
    //
382
    //            var modifiedHashTable = new JsonObject();
383
    //            for (var hashEntry : jsonObject.get("hashTable").getAsJsonObject().entrySet()) {
384
    //                var sha1 = hashTableOverrides.getOrDefault(hashEntry.getKey(),
385
    // hashEntry.getValue().getAsString());
386
    //                modifiedHashTable.addProperty(contextPath + hashEntry.getKey(), sha1);
387
    //            }
388
    //            jsonObject.add("hashTable", modifiedHashTable);
389
    //            return gson.toJson(jsonObject);
390
    //        }
391

392
    return "TODO";
×
393
  }
394

395
  private String calculateSha1(String content) {
396
    try {
397
      MessageDigest digest = MessageDigest.getInstance("SHA-1");
×
398
      digest.update(content.getBytes(UTF_8));
×
399
      return String.format("%040x", new BigInteger(1, digest.digest()));
×
400
    } catch (NoSuchAlgorithmException e) {
×
401
      throw new UnsupportedOperationException(e);
×
402
    }
403
  }
404

405
  private class Redeployer extends Thread {
406

407
    private Path source;
408
    private Path target;
409

410
    private Redeployer(Path source, Path target) {
×
411
      this.source = source;
×
412
      this.target = target;
×
413
    }
×
414

415
    @Override
416
    public void run() {
417
      try {
418
        while (true) {
NEW
419
          if (Files.exists(source)) {
×
NEW
420
            WatchService watchService = source.getFileSystem().newWatchService();
×
NEW
421
            source.register(watchService, ENTRY_CREATE, ENTRY_MODIFY);
×
422

NEW
423
            boolean forceDeploy = false;
×
NEW
424
            boolean loop = true;
×
NEW
425
            while (loop) {
×
NEW
426
              WatchKey key = watchService.take();
×
NEW
427
              if (forceDeploy || !key.pollEvents().isEmpty()) {
×
NEW
428
                forceDeploy = false;
×
429

NEW
430
                log.debug("Redeploying yamcs-web from {}", source);
×
NEW
431
                FileUtils.deleteContents(target);
×
NEW
432
                FileUtils.copyRecursively(source, target);
×
NEW
433
                prepareWebApplication();
×
434
              }
NEW
435
              loop = key.reset();
×
NEW
436
            }
×
437

438
            // If the source directory goes away (webapp rebuild),
439
            // force a redeploy whenever the directory comes back.
NEW
440
            forceDeploy = true;
×
NEW
441
          } else {
×
NEW
442
            Thread.sleep(500);
×
443
          }
444
        }
NEW
445
      } catch (IOException e) {
×
NEW
446
        throw new UncheckedIOException(e);
×
NEW
447
      } catch (InterruptedException e) {
×
NEW
448
        Thread.currentThread().interrupt();
×
449
      }
450
    }
×
451
  }
452
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc