diff --git a/src/main/java/org/teacon/sync/SyncedModLocator.java b/src/main/java/org/teacon/sync/SyncedModLocator.java index db47380..65f25e8 100644 --- a/src/main/java/org/teacon/sync/SyncedModLocator.java +++ b/src/main/java/org/teacon/sync/SyncedModLocator.java @@ -45,6 +45,7 @@ import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -80,26 +81,32 @@ public SyncedModLocator() throws Exception { this.keyStore.debugDump(); this.modDirBase = Files.createDirectories(gameDir.resolve(cfg.modDir)); this.fetchPathsTask = CompletableFuture.supplyAsync(() -> { + Path localCache = gameDir.resolve(cfg.localModList); try { this.progressFeed.accept("RemoteSync: fetching mod list"); // Intentionally do not use config value to ensure that the mod list is always up-to-date - return Utils.fetch(cfg.modList, gameDir.resolve(cfg.localModList), cfg.timeout, false); + return Utils.fetch(cfg.modList, localCache, cfg.timeout, false); } catch (IOException e) { - LOGGER.warn("Failed to download mod list", e); - throw new RuntimeException(e); + LOGGER.warn("Failed to download mod list, will try using locally cached mod list instead. Mods may be outdated.", e); + System.setProperty("org.teacon.sync.failed", "true"); + try { + return FileChannel.open(localCache); + } catch (Exception e2) { + throw new RuntimeException("Failed to open locally cached mod list", e2); + } } }).thenApplyAsync((fcModList) -> { try (Reader reader = Channels.newReader(fcModList, "UTF-8")) { return GSON.fromJson(reader, ModEntry[].class); } catch (JsonParseException e) { - LOGGER.warn("Error parsing modlist", e); + LOGGER.warn("Error parsing mod list", e); throw e; } catch (IOException e) { - LOGGER.warn("Failed to fetch mod list from remote", e); + LOGGER.warn("Failed to open mod list file", e); throw new RuntimeException(e); } }).thenComposeAsync(entries -> { - var futures = Arrays.stream(entries).flatMap(e -> Stream.of( + List> futures = Arrays.stream(entries).flatMap(e -> Stream.of( Utils.downloadIfMissingAsync(this.modDirBase.resolve(e.name), e.file, cfg.timeout, cfg.preferLocalCache, this.progressFeed), Utils.downloadIfMissingAsync(this.modDirBase.resolve(e.name + ".sig"), e.sig, cfg.timeout, cfg.preferLocalCache, this.progressFeed) )).toList(); @@ -131,8 +138,8 @@ public Stream scanCandidates() { try { return this.fetchPathsTask.join().stream(); } catch (Exception e) { - LOGGER.error("Mod downloading worker encountered error and cannot continue. " + - "No mod will be loaded from the remote-synced locator. ", e instanceof CompletionException ? e.getCause() : e); + LOGGER.error("Mod downloading worker encountered error. " + + "You may observe missing mods or outdated mods. ", e instanceof CompletionException ? e.getCause() : e); System.setProperty("org.teacon.sync.failed", "true"); return Stream.empty(); } diff --git a/src/main/java/org/teacon/sync/Utils.java b/src/main/java/org/teacon/sync/Utils.java index 844bab0..6909f5d 100644 --- a/src/main/java/org/teacon/sync/Utils.java +++ b/src/main/java/org/teacon/sync/Utils.java @@ -155,14 +155,18 @@ public static FileChannel fetch(URL src, Path dst, int timeout, boolean biasedTo conn.connect(); } catch (IOException e) { // Connection failed, prefer local copy instead - LOGGER.debug(MARKER, "Failed to download {}, fallback to local copy at {}", src, dst); + LOGGER.debug(MARKER, "Failed to connect to {}, fallback to local copy at {}", src, dst); return FileChannel.open(dst, StandardOpenOption.READ); } if (conn instanceof HttpURLConnection) { - if (((HttpURLConnection) conn).getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { + int resp = ((HttpURLConnection) conn).getResponseCode(); + if (resp == HttpURLConnection.HTTP_NOT_MODIFIED) { // If remote does not update, we use local copy. LOGGER.debug(MARKER, "Remote {} does not have updates, prefer use local copy at {}", src, dst); return FileChannel.open(dst, StandardOpenOption.READ); + } else if (resp >= 400) { + LOGGER.warn(MARKER, "Remote {} fails with status code {}, prefer use local copy at {}", src, resp, dst); + return FileChannel.open(dst, StandardOpenOption.READ); } } }