Skip to content

Commit 946e5e3

Browse files
committed
Add XML configuration files for migration states and refactor Updater for improved update handling
1 parent 971f495 commit 946e5e3

6 files changed

Lines changed: 108 additions & 167 deletions

File tree

.idea/copilot.data.migration.agent.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/copilot.data.migration.edit.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/java/io/nodelink/server/NodeLink.java

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,14 @@
33
import io.nodelink.server.log.Logger;
44
import io.nodelink.server.update.Updater;
55

6-
import java.io.File;
7-
86
public class NodeLink extends NodeLinkHelper {
97

108
private static final NodeLink INSTANCE = new NodeLink();
119

1210
static void main(String[] args) {
1311
try {
14-
NodeLink.getInstance().getUpdater().checkForUpdates();
15-
16-
for (int i = 0; i < args.length; i++) {
17-
if ("--delete-old".equals(args[i]) && (i + 1) < args.length) {
18-
String oldJarPath = args[i + 1];
19-
File oldFile = new File(oldJarPath);
20-
Thread.sleep(2000);
21-
22-
if (oldFile.exists()) {
23-
boolean deleted = oldFile.delete();
24-
if (!deleted) {
25-
System.err.println("Failed to delete old JAR: " + oldJarPath);
26-
} else {
27-
System.out.println("Old JAR deleted successfully: " + oldJarPath);
28-
}
29-
}
30-
break;
31-
}
32-
}
33-
34-
// Runtime Code //
35-
36-
getHelper().INITIALIZE();
12+
NodeLink.getInstance().getUpdater().handleArguments(args);
13+
NodeLink.getInstance().getUpdater().checkForUpdates().block();
3714

3815
} catch (Exception e) {
3916
getInstance().getLogger().ERROR("Error deleting old JAR: " + e.getMessage());

src/main/java/io/nodelink/server/NodeLinkHelper.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,15 @@ private void drawStaticInterface(Terminal terminal) {
9999

100100
int logoHeight = logoLines.length;
101101
for (int i = 0; i < logoHeight; i++) {
102-
String leftPart = (i >= 12 && (i - 12) < headerLines.length) ? BLUE + headerLines[i - 12] + RESET : "";
102+
103+
String leftPart = (i >= 10 && (i - 10) < headerLines.length) ? BLUE + headerLines[i - 10] + RESET : "";
103104
String rightPart = logoLines[i];
104105

105-
int padding = width - getPlainTextLength(leftPart) - getPlainTextLength(rightPart);
106-
if (padding < 0) padding = 0;
106+
int leftLen = getPlainTextLength(leftPart);
107+
int rightLen = getPlainTextLength(rightPart);
108+
109+
int padding = width - leftLen - rightLen;
110+
if (padding < 1) padding = 1;
107111

108112
terminal.writer().println(leftPart + " ".repeat(padding) + rightPart);
109113
}

src/main/java/io/nodelink/server/update/Updater.java

Lines changed: 79 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -3,181 +3,121 @@
33
import com.google.gson.Gson;
44
import com.google.gson.JsonArray;
55
import io.nodelink.server.NodeLink;
6+
import reactor.core.publisher.Mono;
7+
import reactor.core.scheduler.Schedulers;
68

7-
import java.io.File;
89
import java.io.FileOutputStream;
9-
import java.io.IOException;
1010
import java.net.URI;
1111
import java.net.URL;
1212
import java.net.http.HttpClient;
1313
import java.net.http.HttpRequest;
1414
import java.net.http.HttpResponse;
1515
import java.nio.channels.Channels;
1616
import java.nio.channels.ReadableByteChannel;
17+
import java.nio.file.Files;
1718
import java.nio.file.Path;
1819
import java.nio.file.Paths;
20+
import java.time.Duration;
1921

2022
public class Updater {
2123

2224
private static final Updater INSTANCE = new Updater();
25+
private final String PREFIX = "\u001B[94m[NodeLink] \u001B[0m";
2326

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+
}
3630

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) {}
4043
}
41-
42-
} catch (Exception exception) {
43-
NodeLink.getInstance().getLogger().ERROR(exception.getMessage());
4444
}
4545
}
4646

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+
}
5462

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))
6068
.build();
69+
HttpRequest request = HttpRequest.newBuilder()
70+
.uri(URI.create("https://api.github.com/repos/NodeLink-Project/NodeLink-Server/tags"))
71+
.GET().build();
6172

6273
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);
9977
}
10078

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");
10986

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";
11189

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);
11693
}
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());
13696
}
13797

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");
141103

142-
Class<?> classReference = Updater.class;
104+
ProcessBuilder pb = new ProcessBuilder(javaBin, "-jar", ctx.newJar, "--delete-old", ctx.oldJar);
105+
pb.inheritIO();
143106

144-
URL url = classReference.getProtectionDomain().getCodeSource().getLocation();
107+
// On démarre le processus
108+
pb.start();
145109

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();
172118
}
173-
174-
} catch (Exception exception) {
175-
exception.printStackTrace();
176-
NodeLink.getInstance().getLogger().ERROR(exception.getMessage());
177-
}
119+
});
178120
}
179121

180-
public static Updater getUpdaterSingleton() {
181-
return INSTANCE;
182-
}
122+
private record UpdateContext(String oldJar, String newJar) {}
183123
}

0 commit comments

Comments
 (0)