|
| 1 | +/* |
| 2 | + * Hello Minecraft! Launcher |
| 3 | + * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors |
| 4 | + * |
| 5 | + * This program is free software: you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation, either version 3 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License |
| 16 | + * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 17 | + */ |
1 | 18 | package org.jackhuang.hmcl.util.logging; |
2 | 19 |
|
3 | | -import org.jackhuang.hmcl.util.Pair; |
| 20 | +import org.jetbrains.annotations.NotNull; |
| 21 | +import org.jetbrains.annotations.Nullable; |
4 | 22 | import org.tukaani.xz.LZMA2Options; |
5 | 23 | import org.tukaani.xz.XZOutputStream; |
6 | 24 |
|
@@ -127,47 +145,10 @@ private void onExit() { |
127 | 145 | String caller = CLASS_NAME + ".onExit"; |
128 | 146 |
|
129 | 147 | if (logRetention > 0 && logFile != null) { |
130 | | - List<Pair<Path, int[]>> list = new ArrayList<>(); |
131 | | - Pattern fileNamePattern = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})T(?<hour>\\d{2})-(?<minute>\\d{2})-(?<second>\\d{2})(\\.(?<n>\\d+))?\\.log(\\.(gz|xz))?"); |
132 | | - Path dir = logFile.getParent(); |
133 | | - try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { |
134 | | - for (Path path : stream) { |
135 | | - Matcher matcher = fileNamePattern.matcher(path.getFileName().toString()); |
136 | | - if (matcher.matches() && Files.isRegularFile(path)) { |
137 | | - int year = Integer.parseInt(matcher.group("year")); |
138 | | - int month = Integer.parseInt(matcher.group("month")); |
139 | | - int day = Integer.parseInt(matcher.group("day")); |
140 | | - int hour = Integer.parseInt(matcher.group("hour")); |
141 | | - int minute = Integer.parseInt(matcher.group("minute")); |
142 | | - int second = Integer.parseInt(matcher.group("second")); |
143 | | - int n = Optional.ofNullable(matcher.group("n")).map(Integer::parseInt).orElse(0); |
144 | | - |
145 | | - list.add(Pair.pair(path, new int[]{year, month, day, hour, minute, second, n})); |
146 | | - } |
147 | | - } |
148 | | - } catch (IOException e) { |
149 | | - log(Level.WARNING, caller, "Failed to list log files in " + dir, e); |
150 | | - } |
151 | | - |
| 148 | + var list = findRecentLogFiles(Integer.MAX_VALUE); |
152 | 149 | if (list.size() > logRetention) { |
153 | | - list.sort((a, b) -> { |
154 | | - int[] v1 = a.getValue(); |
155 | | - int[] v2 = b.getValue(); |
156 | | - |
157 | | - assert v1.length == v2.length; |
158 | | - |
159 | | - for (int i = 0; i < v1.length; i++) { |
160 | | - int c = Integer.compare(v1[i], v2[i]); |
161 | | - if (c != 0) |
162 | | - return c; |
163 | | - } |
164 | | - |
165 | | - return 0; |
166 | | - }); |
167 | | - |
168 | 150 | for (int i = 0, end = list.size() - logRetention; i < end; i++) { |
169 | | - Path file = list.get(i).getKey(); |
170 | | - |
| 151 | + Path file = list.get(i); |
171 | 152 | try { |
172 | 153 | if (!Files.isSameFile(file, logFile)) { |
173 | 154 | log(Level.INFO, caller, "Delete old log file " + file, null); |
@@ -276,6 +257,40 @@ public Path getLogFile() { |
276 | 257 | return logFile; |
277 | 258 | } |
278 | 259 |
|
| 260 | + public @NotNull List<Path> findRecentLogFiles(int n) { |
| 261 | + if (n <= 0 || logFile == null) |
| 262 | + return List.of(); |
| 263 | + |
| 264 | + var currentLogFile = LogFile.ofFile(logFile); |
| 265 | + |
| 266 | + Path logDir = logFile.getParent(); |
| 267 | + if (logDir == null || !Files.isDirectory(logDir)) |
| 268 | + return List.of(); |
| 269 | + |
| 270 | + var logFiles = new ArrayList<LogFile>(); |
| 271 | + try (DirectoryStream<Path> stream = Files.newDirectoryStream(logDir)) { |
| 272 | + for (Path path : stream) { |
| 273 | + LogFile item = LogFile.ofFile(path); |
| 274 | + if (item != null && (currentLogFile == null || item.compareTo(currentLogFile) < 0)) { |
| 275 | + logFiles.add(item); |
| 276 | + } |
| 277 | + } |
| 278 | + } catch (IOException e) { |
| 279 | + log(Level.WARNING, CLASS_NAME + ".findRecentLogFiles", "Failed to list log files in " + logDir, e); |
| 280 | + return List.of(); |
| 281 | + } |
| 282 | + logFiles.sort(null); |
| 283 | + |
| 284 | + final int resultLength = Math.min(n, logFiles.size()); |
| 285 | + final int offset = logFiles.size() - resultLength; |
| 286 | + |
| 287 | + var result = new Path[resultLength]; |
| 288 | + for (int i = 0; i < resultLength; i++) { |
| 289 | + result[i] = logFiles.get(i + offset).file; |
| 290 | + } |
| 291 | + return List.of(result); |
| 292 | + } |
| 293 | + |
279 | 294 | public void exportLogs(OutputStream output) throws IOException { |
280 | 295 | Objects.requireNonNull(output); |
281 | 296 | LogEvent.ExportLog event = new LogEvent.ExportLog(output); |
@@ -352,4 +367,69 @@ public void trace(String msg) { |
352 | 367 | public void trace(String msg, Throwable exception) { |
353 | 368 | log(Level.TRACE, CallerFinder.getCaller(), msg, exception); |
354 | 369 | } |
| 370 | + |
| 371 | + private static final class LogFile implements Comparable<LogFile> { |
| 372 | + private static final Pattern FILE_NAME_PATTERN = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})T(?<hour>\\d{2})-(?<minute>\\d{2})-(?<second>\\d{2})(\\.(?<n>\\d+))?\\.log(\\.(gz|xz))?"); |
| 373 | + |
| 374 | + private static @Nullable LogFile ofFile(Path file) { |
| 375 | + if (!Files.isRegularFile(file)) |
| 376 | + return null; |
| 377 | + |
| 378 | + Matcher matcher = FILE_NAME_PATTERN.matcher(file.getFileName().toString()); |
| 379 | + if (!matcher.matches()) |
| 380 | + return null; |
| 381 | + |
| 382 | + int year = Integer.parseInt(matcher.group("year")); |
| 383 | + int month = Integer.parseInt(matcher.group("month")); |
| 384 | + int day = Integer.parseInt(matcher.group("day")); |
| 385 | + int hour = Integer.parseInt(matcher.group("hour")); |
| 386 | + int minute = Integer.parseInt(matcher.group("minute")); |
| 387 | + int second = Integer.parseInt(matcher.group("second")); |
| 388 | + int n = Optional.ofNullable(matcher.group("n")).map(Integer::parseInt).orElse(0); |
| 389 | + |
| 390 | + return new LogFile(file, year, month, day, hour, minute, second, n); |
| 391 | + } |
| 392 | + |
| 393 | + private final Path file; |
| 394 | + private final int year; |
| 395 | + private final int month; |
| 396 | + private final int day; |
| 397 | + private final int hour; |
| 398 | + private final int minute; |
| 399 | + private final int second; |
| 400 | + private final int n; |
| 401 | + |
| 402 | + private LogFile(Path file, int year, int month, int day, int hour, int minute, int second, int n) { |
| 403 | + this.file = file; |
| 404 | + this.year = year; |
| 405 | + this.month = month; |
| 406 | + this.day = day; |
| 407 | + this.hour = hour; |
| 408 | + this.minute = minute; |
| 409 | + this.second = second; |
| 410 | + this.n = n; |
| 411 | + } |
| 412 | + |
| 413 | + @Override |
| 414 | + public int compareTo(@NotNull Logger.LogFile that) { |
| 415 | + if (this.year != that.year) return Integer.compare(this.year, that.year); |
| 416 | + if (this.month != that.month) return Integer.compare(this.month, that.month); |
| 417 | + if (this.day != that.day) return Integer.compare(this.day, that.day); |
| 418 | + if (this.hour != that.hour) return Integer.compare(this.hour, that.hour); |
| 419 | + if (this.minute != that.minute) return Integer.compare(this.minute, that.minute); |
| 420 | + if (this.second != that.second) return Integer.compare(this.second, that.second); |
| 421 | + if (this.n != that.n) return Integer.compare(this.n, that.n); |
| 422 | + return 0; |
| 423 | + } |
| 424 | + |
| 425 | + @Override |
| 426 | + public int hashCode() { |
| 427 | + return Objects.hash(year, month, day, hour, minute, second, n); |
| 428 | + } |
| 429 | + |
| 430 | + @Override |
| 431 | + public boolean equals(Object obj) { |
| 432 | + return obj instanceof LogFile && compareTo((LogFile) obj) == 0; |
| 433 | + } |
| 434 | + } |
355 | 435 | } |
0 commit comments