|
3 | 3 | import com.google.gson.Gson; |
4 | 4 | import com.google.gson.JsonArray; |
5 | 5 | import io.nodelink.server.NodeLink; |
| 6 | +import reactor.core.publisher.Mono; |
| 7 | +import reactor.core.scheduler.Schedulers; |
6 | 8 |
|
7 | | -import java.io.File; |
8 | 9 | import java.io.FileOutputStream; |
9 | | -import java.io.IOException; |
10 | 10 | import java.net.URI; |
11 | 11 | import java.net.URL; |
12 | 12 | import java.net.http.HttpClient; |
13 | 13 | import java.net.http.HttpRequest; |
14 | 14 | import java.net.http.HttpResponse; |
15 | 15 | import java.nio.channels.Channels; |
16 | 16 | import java.nio.channels.ReadableByteChannel; |
| 17 | +import java.nio.file.Files; |
17 | 18 | import java.nio.file.Path; |
18 | 19 | import java.nio.file.Paths; |
| 20 | +import java.time.Duration; |
19 | 21 |
|
20 | 22 | public class Updater { |
21 | 23 |
|
22 | 24 | private static final Updater INSTANCE = new Updater(); |
| 25 | + private final String PREFIX = "\u001B[94m[NodeLink] \u001B[0m"; |
23 | 26 |
|
24 | | - public void checkForUpdates() { |
25 | | - try { |
26 | | - NodeLink.getInstance().getLogger().INFO("\u001B[94m[NodeLink] \u001B[0mChecking for updates..."); |
27 | | - |
28 | | - Thread.sleep(1000); |
29 | | - |
30 | | - Class<?> classReference = Updater.class; |
31 | | - |
32 | | - URL url = classReference.getProtectionDomain().getCodeSource().getLocation(); |
33 | | - |
34 | | - Path filePath = Paths.get(url.toURI()); |
35 | | - Path FolderParent = filePath.getParent(); |
| 27 | + public static Updater getUpdaterSingleton() { |
| 28 | + return INSTANCE; |
| 29 | + } |
36 | 30 |
|
37 | | - if (isLatestVersion()) { |
38 | | - downloadFile("https://raw.githubusercontent.com/NodeLink-Project/nodelink-project.github.io/main/nodelink-server/jar/io/nodelink/server/NodeLink-Server/" + fetchVersion() + "/NodeLink-Server-" + fetchVersion() + "-fat.jar", FolderParent.toString() + "/NodeLink-Server-" + fetchVersion() + ".jar"); |
39 | | - removeAndStartNewVersion(); |
| 31 | + public void handleArguments(String[] args) { |
| 32 | + for (int i = 0; i < args.length; i++) { |
| 33 | + if (args[i].equalsIgnoreCase("--delete-old") && (i + 1) < args.length) { |
| 34 | + try { |
| 35 | + Path oldPath = Paths.get(args[i + 1]); |
| 36 | + // On attend un peu que l'ancien OS libère le verrou sur le fichier |
| 37 | + Thread.sleep(1500); |
| 38 | + if (Files.exists(oldPath)) { |
| 39 | + Files.delete(oldPath); |
| 40 | + System.out.println(PREFIX + "Cleanup: Old version deleted."); |
| 41 | + } |
| 42 | + } catch (Exception ignored) {} |
40 | 43 | } |
41 | | - |
42 | | - } catch (Exception exception) { |
43 | | - NodeLink.getInstance().getLogger().ERROR(exception.getMessage()); |
44 | 44 | } |
45 | 45 | } |
46 | 46 |
|
47 | | - private String fetchVersion() { |
48 | | - try { |
49 | | - final String API_URL = "https://api.github.com/repos/NodeLink-Project/NodeLink-Server/tags"; |
50 | | - |
51 | | - |
52 | | - //final String authorizationHeader = "Bearer " + TOKEN; |
53 | | - Thread.sleep(1000); |
| 47 | + public Mono<Void> checkForUpdates() { |
| 48 | + return fetchVersionMono() |
| 49 | + .flatMap(latest -> { |
| 50 | + if (latest.equals(Version.VERSION)) { |
| 51 | + System.out.println(PREFIX + "Latest version: " + Version.VERSION); |
| 52 | + NodeLink.getHelper().INITIALIZE(); |
| 53 | + return Mono.empty(); |
| 54 | + } |
| 55 | + return prepareAndDownload(latest).flatMap(this::launchNewProcess); |
| 56 | + }) |
| 57 | + .doOnError(e -> { |
| 58 | + System.err.println("Update failed: " + e.getMessage()); |
| 59 | + NodeLink.getHelper().INITIALIZE(); |
| 60 | + }); |
| 61 | + } |
54 | 62 |
|
55 | | - HttpClient client = HttpClient.newHttpClient(); |
56 | | - HttpRequest request = HttpRequest.newBuilder() |
57 | | - .uri(URI.create(API_URL)) |
58 | | - //.header("Authorization", authorizationHeader) |
59 | | - .GET() |
| 63 | + private Mono<String> fetchVersionMono() { |
| 64 | + return Mono.fromCallable(() -> { |
| 65 | + System.out.println(PREFIX + "Checking updates..."); |
| 66 | + HttpClient client = HttpClient.newBuilder() |
| 67 | + .connectTimeout(Duration.ofSeconds(5)) |
60 | 68 | .build(); |
| 69 | + HttpRequest request = HttpRequest.newBuilder() |
| 70 | + .uri(URI.create("https://api.github.com/repos/NodeLink-Project/NodeLink-Server/tags")) |
| 71 | + .GET().build(); |
61 | 72 |
|
62 | 73 | HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); |
63 | | - |
64 | | - Gson gson = new Gson(); |
65 | | - JsonArray data = gson.fromJson(response.body(), JsonArray.class); |
66 | | - |
67 | | - if (data != null && !data.isEmpty()) { |
68 | | - return data.get(0).getAsJsonObject().get("name").getAsString(); |
69 | | - } else { |
70 | | - return "No tags found"; |
71 | | - } |
72 | | - |
73 | | - } catch (Exception exception) { |
74 | | - NodeLink.getInstance().getLogger().ERROR(exception.getMessage()); |
75 | | - return "Error"; |
76 | | - } |
77 | | - } |
78 | | - |
79 | | - private boolean isLatestVersion() { |
80 | | - try { |
81 | | - String NODELINK = "\u001B[94m[NodeLink] \u001B[0m"; |
82 | | - String fetchedVersion = fetchVersion(); |
83 | | - |
84 | | - if (!fetchedVersion.equals(Version.VERSION)) { |
85 | | - NodeLink.getInstance().getLogger().INFO(NODELINK + "A new version is available: " + fetchedVersion + " (You are using " + Version.VERSION + ")"); |
86 | | - Thread.sleep(1000); |
87 | | - |
88 | | - return true; |
89 | | - } else { |
90 | | - NodeLink.getInstance().getLogger().INFO(NODELINK + "You are using the latest version: " + Version.VERSION); |
91 | | - |
92 | | - //NodeLink.getHelper().INITIALIZE(); |
93 | | - } |
94 | | - |
95 | | - } catch (Exception exception) { |
96 | | - NodeLink.getInstance().getLogger().ERROR(exception.getMessage()); |
97 | | - } |
98 | | - return false; |
| 74 | + JsonArray data = new Gson().fromJson(response.body(), JsonArray.class); |
| 75 | + return (data != null && !data.isEmpty()) ? data.get(0).getAsJsonObject().get("name").getAsString() : Version.VERSION; |
| 76 | + }).subscribeOn(Schedulers.boundedElastic()).onErrorReturn(Version.VERSION); |
99 | 77 | } |
100 | 78 |
|
101 | | - private void downloadFile(String urlStr, String filePath) { |
102 | | - try { |
103 | | - Thread.sleep(1000); |
104 | | - |
105 | | - URL url = new URL(urlStr); |
106 | | - |
107 | | - try (ReadableByteChannel rbc = Channels.newChannel(url.openStream()); |
108 | | - FileOutputStream fos = new FileOutputStream(filePath)) { |
| 79 | + private Mono<UpdateContext> prepareAndDownload(String latestVersion) { |
| 80 | + return Mono.fromCallable(() -> { |
| 81 | + System.out.println(PREFIX + "Downloading: " + latestVersion); |
| 82 | + URL url = Updater.class.getProtectionDomain().getCodeSource().getLocation(); |
| 83 | + Path currentJarPath = Paths.get(url.toURI()); |
| 84 | + Path folder = currentJarPath.getParent(); |
| 85 | + Path newJarPath = folder.resolve("NodeLink-Server-" + latestVersion + ".jar"); |
109 | 86 |
|
110 | | - long bytesTransferred = fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); |
| 87 | + String downloadUrl = "https://raw.githubusercontent.com/NodeLink-Project/nodelink-project.github.io/main/nodelink-server/jar/io/nodelink/server/NodeLink-Server/" |
| 88 | + + latestVersion + "/NodeLink-Server-" + latestVersion + "-fat.jar"; |
111 | 89 |
|
112 | | - fos.flush(); |
113 | | - fos.getFD().sync(); |
114 | | - |
115 | | - System.out.println("Downloaded " + bytesTransferred + " bytes to " + filePath); |
| 90 | + try (ReadableByteChannel rbc = Channels.newChannel(new URL(downloadUrl).openStream()); |
| 91 | + FileOutputStream fos = new FileOutputStream(newJarPath.toFile())) { |
| 92 | + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); |
116 | 93 | } |
117 | | - |
118 | | - Thread.sleep(500); |
119 | | - |
120 | | - File downloadedFile = new File(filePath); |
121 | | - if (!downloadedFile.exists() || downloadedFile.length() == 0) { |
122 | | - throw new IOException("Download failed: file is missing or empty"); |
123 | | - } |
124 | | - |
125 | | - System.out.println("File downloaded successfully: " + downloadedFile.length() + " bytes"); |
126 | | - |
127 | | - } catch (Exception exception) { |
128 | | - exception.printStackTrace(); |
129 | | - NodeLink.getInstance().getLogger().ERROR("Download error: " + exception.getMessage()); |
130 | | - |
131 | | - File file = new File(filePath); |
132 | | - if (file.exists()) { |
133 | | - file.delete(); |
134 | | - } |
135 | | - } |
| 94 | + return new UpdateContext(currentJarPath.toAbsolutePath().toString(), newJarPath.toAbsolutePath().toString()); |
| 95 | + }).subscribeOn(Schedulers.boundedElastic()); |
136 | 96 | } |
137 | 97 |
|
138 | | - private void removeAndStartNewVersion() { |
139 | | - try { |
140 | | - Thread.sleep(1000); |
| 98 | + private Mono<Void> launchNewProcess(UpdateContext ctx) { |
| 99 | + return Mono.fromRunnable(() -> { |
| 100 | + try { |
| 101 | + System.out.println(PREFIX + "Starting new version..."); |
| 102 | + String javaBin = ProcessHandle.current().info().command().orElse("java"); |
141 | 103 |
|
142 | | - Class<?> classReference = Updater.class; |
| 104 | + ProcessBuilder pb = new ProcessBuilder(javaBin, "-jar", ctx.newJar, "--delete-old", ctx.oldJar); |
| 105 | + pb.inheritIO(); |
143 | 106 |
|
144 | | - URL url = classReference.getProtectionDomain().getCodeSource().getLocation(); |
| 107 | + // On démarre le processus |
| 108 | + pb.start(); |
145 | 109 |
|
146 | | - Path filePath = Paths.get(url.toURI()); |
147 | | - Path FolderParent = filePath.getParent(); |
148 | | - |
149 | | - File LOCAL_JAR = new File(FolderParent.toString() + "/NodeLink-Server-" + Version.VERSION + ".jar"); |
150 | | - String newJarPath = FolderParent + "/NodeLink-Server-" + fetchVersion() + ".jar"; |
151 | | - |
152 | | - ProcessBuilder pb = new ProcessBuilder( |
153 | | - "java", |
154 | | - "-jar", |
155 | | - newJarPath, |
156 | | - "--delete-old", |
157 | | - LOCAL_JAR.getAbsolutePath() |
158 | | - ); |
159 | | - |
160 | | - pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); |
161 | | - pb.redirectError(ProcessBuilder.Redirect.INHERIT); |
162 | | - |
163 | | - Process process = pb.start(); |
164 | | - |
165 | | - Thread.sleep(2000); |
166 | | - |
167 | | - if (!process.isAlive()) { |
168 | | - System.err.println("ERROR: New process exited with code: " + process.exitValue()); |
169 | | - System.err.println("The new version failed to start. Check the output above."); |
170 | | - } else { |
171 | | - System.exit(1); |
| 110 | + // On laisse un délai pour que le nouveau processus s'imprime à l'écran |
| 111 | + // avant de tuer brutalement celui-ci. |
| 112 | + Thread.sleep(1000); |
| 113 | + System.out.println(PREFIX + "Update complete. Goodbye!"); |
| 114 | + System.exit(0); |
| 115 | + } catch (Exception e) { |
| 116 | + e.printStackTrace(); |
| 117 | + NodeLink.getHelper().INITIALIZE(); |
172 | 118 | } |
173 | | - |
174 | | - } catch (Exception exception) { |
175 | | - exception.printStackTrace(); |
176 | | - NodeLink.getInstance().getLogger().ERROR(exception.getMessage()); |
177 | | - } |
| 119 | + }); |
178 | 120 | } |
179 | 121 |
|
180 | | - public static Updater getUpdaterSingleton() { |
181 | | - return INSTANCE; |
182 | | - } |
| 122 | + private record UpdateContext(String oldJar, String newJar) {} |
183 | 123 | } |
0 commit comments