Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add a way to restore incremental backups
closes #20
- Loading branch information
MelanX
committed
Feb 20, 2024
1 parent
0ee293e
commit 036d5c3
Showing
7 changed files
with
183 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
src/main/java/de/melanx/simplebackups/commands/MergeCommand.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package de.melanx.simplebackups.commands; | ||
|
||
import com.mojang.brigadier.Command; | ||
import com.mojang.brigadier.builder.ArgumentBuilder; | ||
import com.mojang.brigadier.context.CommandContext; | ||
import com.mojang.brigadier.exceptions.CommandSyntaxException; | ||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; | ||
import de.melanx.simplebackups.BackupData; | ||
import de.melanx.simplebackups.config.BackupType; | ||
import de.melanx.simplebackups.config.CommonConfig; | ||
import net.minecraft.commands.CommandSourceStack; | ||
import net.minecraft.commands.Commands; | ||
import net.minecraft.network.chat.Component; | ||
|
||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.UncheckedIOException; | ||
import java.nio.file.FileVisitResult; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.SimpleFileVisitor; | ||
import java.nio.file.attribute.BasicFileAttributes; | ||
import java.nio.file.attribute.FileTime; | ||
import java.util.Enumeration; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.UUID; | ||
import java.util.zip.ZipEntry; | ||
import java.util.zip.ZipFile; | ||
import java.util.zip.ZipOutputStream; | ||
|
||
public class MergeCommand implements Command<CommandSourceStack> { | ||
|
||
|
||
public static ArgumentBuilder<CommandSourceStack, ?> register() { | ||
return Commands.literal("mergeBackups") | ||
.executes(new MergeCommand()); | ||
} | ||
|
||
@Override | ||
public int run(CommandContext<CommandSourceStack> commandContext) throws CommandSyntaxException { | ||
// Check if only modified files should be backed up | ||
if (CommonConfig.backupType() == BackupType.FULL_BACKUPS) { | ||
throw new SimpleCommandExceptionType(Component.translatable("simplebackups.commands.only_modified")).create(); | ||
} | ||
|
||
BackupData data = BackupData.get(commandContext.getSource().getServer()); | ||
|
||
// Check if a merge operation is already in progress | ||
if (data.isMerging()) { | ||
throw new SimpleCommandExceptionType(Component.translatable("simplebackups.commands.is_merging")).create(); | ||
} | ||
|
||
MergingThread mergingThread = new MergingThread(commandContext); | ||
try { | ||
data.startMerging(); | ||
mergingThread.start(); | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
data.stopMerging(); | ||
return 0; | ||
} | ||
|
||
data.stopMerging(); | ||
return 1; | ||
} | ||
|
||
private static class MergingThread extends Thread { | ||
|
||
private final CommandContext<CommandSourceStack> commandContext; | ||
|
||
public MergingThread(CommandContext<CommandSourceStack> commandContext) { | ||
this.commandContext = commandContext; | ||
} | ||
|
||
@Override | ||
public void run() { | ||
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("merged_backup-" + UUID.randomUUID() + ".zip"))) { | ||
Map<String, Path> zipFiles = new HashMap<>(); | ||
|
||
// Walk the file tree of the output path | ||
Files.walkFileTree(CommonConfig.getOutputPath(), new SimpleFileVisitor<>() { | ||
@Override | ||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { | ||
MergingThread.this.processFile(file, zipFiles); | ||
return FileVisitResult.CONTINUE; | ||
} | ||
}); | ||
|
||
// Write the merged zip file | ||
this.writeMergedZipFile(zos, zipFiles); | ||
} catch (IOException e) { | ||
throw new IllegalStateException("Error while processing backups", e); | ||
} finally { | ||
commandContext.getSource().sendSuccess(() -> Component.translatable("simplebackups.commands.finished"), false); | ||
} | ||
} | ||
|
||
private void processFile(Path file, Map<String, Path> zipFiles) throws IOException { | ||
if (file.toString().endsWith(".zip")) { | ||
try (ZipFile zipFile = new ZipFile(file.toFile())) { | ||
Enumeration<? extends ZipEntry> entries = zipFile.entries(); | ||
|
||
while (entries.hasMoreElements()) { | ||
ZipEntry entry = entries.nextElement(); | ||
String name = entry.getName(); | ||
|
||
zipFiles.merge(name, file, this::getLatestModifiedFile); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private Path getLatestModifiedFile(Path existingFile, Path newFile) { | ||
try { | ||
FileTime existingFileTime = Files.getLastModifiedTime(existingFile); | ||
FileTime newFileTime = Files.getLastModifiedTime(newFile); | ||
return existingFileTime.compareTo(newFileTime) > 0 ? existingFile : newFile; | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
|
||
private void writeMergedZipFile(ZipOutputStream zos, Map<String, Path> zipFiles) throws IOException { | ||
for (Map.Entry<String, Path> entry : zipFiles.entrySet()) { | ||
String fileName = entry.getKey(); | ||
Path zipFilePath = entry.getValue(); | ||
|
||
try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) { | ||
ZipEntry zipEntry = zipFile.getEntry(fileName); | ||
if (zipEntry != null) { | ||
zos.putNextEntry(new ZipEntry(fileName)); | ||
|
||
try (InputStream is = zipFile.getInputStream(zipEntry)) { | ||
byte[] buffer = new byte[1024]; | ||
int len; | ||
while ((len = is.read(buffer)) > 0) { | ||
zos.write(buffer, 0, len); | ||
} | ||
} | ||
|
||
zos.closeEntry(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
{ | ||
"simplebackups.backup_started": "Backup gestartet...", | ||
"simplebackups.backup_finished": "Backup fertiggestellt in %s (%s | %s)", | ||
"simplebackups.backups_paused": "Backups pausiert" | ||
"simplebackups.backups_paused": "Backups pausiert", | ||
"simplebackups.commands.only_modified": "Es werden nicht nur veränderte Dateien gesichert, prüfe deine Konfigurationsdatei", | ||
"simplebackups.commands.is_merging": "Ein Zusammenführungsvorgang ist bereits im Gange", | ||
"simplebackups.commands.finished": "Zusammenführen der Backups erfolgreich abgeschlossen" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
{ | ||
"simplebackups.backup_started": "Backup started...", | ||
"simplebackups.backup_finished": "Backup completed in %s (%s | %s)", | ||
"simplebackups.backups_paused": "Backups paused" | ||
"simplebackups.backups_paused": "Backups paused", | ||
"simplebackups.commands.only_modified": "Not only modified files are being backed up, please check your configuration file", | ||
"simplebackups.commands.is_merging": "A merge operation is already in progress", | ||
"simplebackups.commands.finished": "Merging backups completed successfully" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
{ | ||
"simplebackups.backup_started": "Backup iniciado...", | ||
"simplebackups.backup_finished": "Backup concluído em %s (%s | %s)", | ||
"simplebackups.backups_paused": "Backups pausados" | ||
"simplebackups.backups_paused": "Backups pausados", | ||
"simplebackups.commands.only_modified": "Não apenas arquivos modificados estão sendo salvos, por favor, verifique seu arquivo de configuração", | ||
"simplebackups.commands.is_merging": "Uma operação de mesclagem já está em andamento", | ||
"simplebackups.commands.finished": "Mesclagem de backups concluída com sucesso" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
{ | ||
"simplebackups.backup_started": "Запущено резервное копирование...", | ||
"simplebackups.backup_finished": "Резервное копирование завершено в %s (%s | %s)", | ||
"simplebackups.backups_paused": "Резервное копирование приостановлено" | ||
"simplebackups.backups_paused": "Резервное копирование приостановлено", | ||
"simplebackups.commands.only_modified": "Не только изменённые файлы сохраняются, пожалуйста, проверьте ваш файл конфигурации", | ||
"simplebackups.commands.is_merging": "Операция слияния уже выполняется", | ||
"simplebackups.commands.finished": "Слияние резервных копий успешно завершено" | ||
} |