Skip to content

Commit

Permalink
Only create automatic backup is last one is old enough
Browse files Browse the repository at this point in the history
  • Loading branch information
theotherp committed May 16, 2024
1 parent 0dff187 commit 876fdaa
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 32 deletions.
68 changes: 38 additions & 30 deletions core/src/main/java/org/nzbhydra/backup/BackupAndRestore.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressWarnings("ResultOfMethodCallIgnored")
Expand All @@ -57,6 +58,7 @@ public class BackupAndRestore {
private static final Logger logger = LoggerFactory.getLogger(BackupAndRestore.class);
private static final Pattern FILE_PATTERN = Pattern.compile("nzbhydra-(\\d{4}-\\d{2}-\\d{2} \\d{2}-\\d{2}-\\d{2})\\.zip");
private static final DateTimeFormatter DATE_PATTERN = DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss");
public static final int MIN_AGE_MINUES_LAST_AUTOMATIC_BACKUP = 30;

@PersistenceContext
private EntityManager entityManager;
Expand Down Expand Up @@ -84,7 +86,7 @@ public void init() {
}

@Transactional
public File backup(boolean triggeredByUsed) throws Exception {
public File backup(boolean triggeredByUser) throws Exception {
Stopwatch stopwatch = Stopwatch.createStarted();
File backupFolder = getBackupFolder();
if (!backupFolder.exists()) {
Expand All @@ -93,13 +95,18 @@ public File backup(boolean triggeredByUsed) throws Exception {
throw new IOException("Unable to create backup target folder " + backupFolder.getAbsolutePath());
}
}
Optional<Map.Entry<File, LocalDateTime>> latest = getBackupFilesToDate().entrySet().stream().max(Map.Entry.comparingByValue());
if (!triggeredByUser && latest.isPresent() && latest.get().getValue().isAfter(LocalDateTime.now().minusMinutes(MIN_AGE_MINUES_LAST_AUTOMATIC_BACKUP))) {
logger.info("Not creating a new automatic backup because the last one ({}) was just created", latest.get().getKey().getName());
return latest.get().getKey();
}

deleteOldBackupFiles(backupFolder);

logger.info("Creating backup");

File backupZip = new File(backupFolder, "nzbhydra-" + LocalDateTime.now().format(DATE_PATTERN) + ".zip");
backupDatabase(backupZip, triggeredByUsed);
backupDatabase(backupZip, triggeredByUser);
if (!backupZip.exists()) {
throw new RuntimeException("Export to file " + backupZip + " was not executed by database");
}
Expand All @@ -126,38 +133,34 @@ protected void deleteOldBackupFiles(File backupFolder) {
if (configProvider.getBaseConfig().getMain().getDeleteBackupsAfterWeeks().isPresent()) {
logger.info("Deleting old backups if any exist");
Integer backupMaxAgeInWeeks = configProvider.getBaseConfig().getMain().getDeleteBackupsAfterWeeks().get();
File[] zips = backupFolder.listFiles((dir, name) -> name != null && name.startsWith("nzbhydra") && name.endsWith(".zip"));
if (zips != null) {
Map<File, LocalDateTime> fileToBackupTime = new HashMap<>();
for (File zip : zips) {
Matcher matcher = FILE_PATTERN.matcher(zip.getName());
if (!matcher.matches()) {
logger.warn("Backup ZIP file name {} does not match expected pattern", zip.getName());
continue;
}
LocalDateTime backupDate = LocalDateTime.from(DATE_PATTERN.parse(matcher.group(1)));
fileToBackupTime.put(zip, backupDate);
}
for (File zip : zips) {
if (!fileToBackupTime.containsKey(zip)) {
Map<File, LocalDateTime> fileToBackupTime = getBackupFilesToDate();

for (Map.Entry<File, LocalDateTime> entry : fileToBackupTime.entrySet()) {
File zip = entry.getKey();
LocalDateTime backupDate = entry.getValue();
if (backupDate.isBefore(LocalDateTime.now().minusSeconds(60L * 60 * 24 * 7 * backupMaxAgeInWeeks))) {
final boolean successfulNewerBackup = fileToBackupTime.entrySet().stream().anyMatch(x -> x.getKey() != zip && x.getValue().isAfter(backupDate));
if (!successfulNewerBackup) {
logger.warn("No successful backup was made after the creation of {}. Will not delete it.", zip.getAbsolutePath());
continue;
}
LocalDateTime backupDate = fileToBackupTime.get(zip);
if (backupDate.isBefore(LocalDateTime.now().minusSeconds(60L * 60 * 24 * 7 * backupMaxAgeInWeeks))) {
final boolean successfulNewerBackup = fileToBackupTime.entrySet().stream().anyMatch(x -> x.getKey() != zip && x.getValue().isAfter(backupDate));
if (!successfulNewerBackup) {
logger.warn("No successful backup was made after the creation of {}. Will not delete it.", zip.getAbsolutePath());
continue;
}
logger.info("Deleting backup file {} because it's older than {} weeks and a newer successful backup exists", zip, backupMaxAgeInWeeks);
boolean deleted = zip.delete();
if (!deleted) {
logger.warn("Unable to delete old backup file {}", zip.getName());
}
logger.info("Deleting backup file {} because it's older than {} weeks and a newer successful backup exists", zip, backupMaxAgeInWeeks);
boolean deleted = zip.delete();
if (!deleted) {
logger.warn("Unable to delete old backup file {}", zip.getName());
}
}
}
}

}

private Map<File, LocalDateTime> getBackupFilesToDate() {
Map<File, LocalDateTime> fileToBackupTime = new HashMap<>();
for (BackupEntry existingBackup : getExistingBackups()) {
fileToBackupTime.put(new File(getBackupFolder(), existingBackup.getFilename()), LocalDateTime.ofInstant(existingBackup.getCreationDate(), ZoneId.systemDefault()));
}
return fileToBackupTime;
}

@Reflective
Expand Down Expand Up @@ -251,15 +254,20 @@ public GenericResponse restore(String filename) {
}

public GenericResponse restoreFromFile(InputStream inputStream) {
File tempFile = null;
try {
File tempFile = tempFileProvider.getTempFile("restore", ".zip");
tempFile = tempFileProvider.get TempFile("restore", ".zip");
FileUtils.copyInputStreamToFile(inputStream, tempFile);
tempFile.deleteOnExit();
restoreFromFile(tempFile);
return GenericResponse.ok();
} catch (Exception e) {
logger.error("Error while restoring", e);
return GenericResponse.notOk("Error while restoring: " + e.getMessage());
} finally {
if (tempFile != null) {
tempFile.delete();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,9 @@ private double getUpTimeInMiliseconds() {

public byte[] getDebugInfosAsZip() throws IOException {
File tempFile = createDebugInfosZipFile();
return Files.readAllBytes(tempFile.toPath());
byte[] bytes = Files.readAllBytes(tempFile.toPath());
tempFile.delete();
return bytes;
}

public File createDebugInfosZipFile() throws IOException {
Expand Down Expand Up @@ -396,7 +398,9 @@ public String executeSqlQuery(String sql) throws IOException {
File tempFile = tempFileProvider.getTempFile("dbquery", "csv");
String path = tempFile.getAbsolutePath().replace("\\", "/");
entityManager.createNativeQuery(String.format("CALL CSVWRITE('%s', '%s')", path, sql.replace("'", "''"))).executeUpdate();
return new String(Files.readAllBytes(tempFile.toPath()));
String content = new String(Files.readAllBytes(tempFile.toPath()));
tempFile.delete();
return content;
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ public FileZipResponse getFilesAsZip(List<Long> guids) throws Exception {
return new FileZipResponse(false, null, "No files could be retrieved", Collections.emptyList(), guids);
}
File zip = createZip(nzbsDownload.files);
zip.deleteOnExit();
logger.info("Successfully added {}/{} files to ZIP", nzbsDownload.files.size(), guids.size());
if (nzbsDownload.tempDirectory != null) {
nzbsDownload.tempDirectory.toFile().delete();
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/org/nzbhydra/misc/TempFileProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class TempFileProvider {
@Autowired
private ConfigurableEnvironment environment;

// TODO sist 16.05.2024: Make autoclosable, delete file when closed

public File getTempFile(String postFix, String extension) throws IOException {
String nzbhydraTempFolder = environment.getProperty("NZBHYDRA_TEMP_FOLDER", String.class);
if (nzbhydraTempFolder != null) {
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/resources/changelog.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#@formatter:off
- version: "v6.2.2"
changes:
- type: "fix"
text: "With the update loop (see 6.2.1) for every update a new backup was created, in some cases filling the hard drive. I now make sure a backup is only created if the last one is old enough"
- version: "v6.2.1"
changes:
- type: "fix"
Expand Down

0 comments on commit 876fdaa

Please sign in to comment.