Skip to content

Commit

Permalink
Merge pull request #7040 from alvasw/persistence_rolling_backups
Browse files Browse the repository at this point in the history
persistence: Implement Rolling Backups
  • Loading branch information
alejandrogarcia83 authored Mar 27, 2024
2 parents 85e7f49 + 9d4d396 commit dc9f039
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.persistence;

public class RollingBackupCreationFailedException extends RuntimeException {
public RollingBackupCreationFailedException(String message) {
super(message);
}
}
69 changes: 69 additions & 0 deletions persistence/src/main/java/bisq/persistence/RollingBackups.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.persistence;

import java.io.File;

public class RollingBackups {
private final File baseFile;
private final int numberOfBackups;
private final File parentDirFile;
private final String baseFileName;

public RollingBackups(File baseFile, int numberOfBackups) {
if (numberOfBackups < 1) {
throw new IllegalArgumentException("Number of backup is " + numberOfBackups);
}

this.baseFile = baseFile;
this.numberOfBackups = numberOfBackups;
parentDirFile = baseFile.getParentFile();
baseFileName = baseFile.getName();
}

public void rollBackups() {
for (int i = numberOfBackups - 2; i >= 0; i--) {
File originalFile = new File(parentDirFile, baseFileName + "_" + i);
File backupFile = new File(parentDirFile, baseFileName + "_" + (i + 1));
renameFile(originalFile, backupFile);
}

File backupFile = new File(parentDirFile, baseFileName + "_0");
renameFile(baseFile, backupFile);
}

private void renameFile(File originalFile, File newFile) {
if (!originalFile.exists()) {
return;
}

if (newFile.exists()) {
boolean isSuccess = newFile.delete();
if (!isSuccess) {
throw new RollingBackupCreationFailedException("Couldn't delete " + newFile.getAbsolutePath() +
" before replacing it.");
}
}

boolean isSuccess = originalFile.renameTo(newFile);
if (!isSuccess) {
throw new RollingBackupCreationFailedException("Couldn't rename " + originalFile.getAbsolutePath() + " to "
+ newFile.getAbsolutePath());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.persistence;

import java.nio.file.Path;

import java.io.File;

import java.util.Objects;



import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

public class DirectoryHasNChildren extends TypeSafeMatcher<Path> {

private final int numberOfChildren;

public DirectoryHasNChildren(int numberOfChildren) {
this.numberOfChildren = numberOfChildren;
}

@Override
protected boolean matchesSafely(Path item) {
File[] files = item.toFile().listFiles();
return Objects.requireNonNull(files).length == numberOfChildren;
}

@Override
public void describeTo(Description description) {
description.appendText("has " + numberOfChildren + " children");
}

public static Matcher<Path> hasNChildren(int numberOfChildren) {
return new DirectoryHasNChildren(numberOfChildren);
}
}
179 changes: 179 additions & 0 deletions persistence/src/test/java/bisq/persistence/RollingBackupsTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.persistence;

import java.nio.file.Files;
import java.nio.file.Path;

import java.io.File;
import java.io.IOException;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import static bisq.persistence.DirectoryHasNChildren.hasNChildren;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class RollingBackupsTests {
private Path baseFilePath;

@BeforeEach
void setup(@TempDir Path tempDir) {
baseFilePath = tempDir.resolve("file");
}

@Test
void noBackup(@TempDir Path tempDir) {
File file = new File(tempDir.toFile(), "file");
assertThrows(IllegalArgumentException.class, () -> new RollingBackups(file, 0));
}

@Test
void firstBackup(@TempDir Path tempDir) throws IOException {
Files.writeString(baseFilePath, "ABC");
assertThat(tempDir, hasNChildren(1));

RollingBackups rollingBackups = new RollingBackups(baseFilePath.toFile(), 1);
rollingBackups.rollBackups();

assertThat(tempDir, hasNChildren(1));

File backupFile = tempDir.resolve("file_0").toFile();
String backupFileContent = Files.readString(backupFile.toPath());
assertThat(backupFileContent, is("ABC"));
}

@Test
void oneBackupWithExistingFiles(@TempDir Path tempDir) throws IOException {
Files.writeString(baseFilePath, "NEW_CONTENT");

Path backupPath = tempDir.resolve("file_0");
Files.writeString(backupPath, "OLD_CONTENT");

assertThat(tempDir, hasNChildren(2));

RollingBackups rollingBackups = new RollingBackups(baseFilePath.toFile(), 1);
rollingBackups.rollBackups();

assertThat(tempDir, hasNChildren(1));
String backupFileContent = Files.readString(backupPath);
assertThat(backupFileContent, is("NEW_CONTENT"));
}

@Test
void threeBackupsFirstBackup(@TempDir Path tempDir) throws IOException {
Files.writeString(baseFilePath, "NEW_CONTENT");
assertThat(tempDir, hasNChildren(1));

RollingBackups rollingBackups = new RollingBackups(baseFilePath.toFile(), 3);
rollingBackups.rollBackups();

assertThat(tempDir, hasNChildren(1));

Path backupPath = tempDir.resolve("file_0");
String backupFileContent = Files.readString(backupPath);
assertThat(backupFileContent, is("NEW_CONTENT"));
}

@Test
void threeBackupsWithExistingFiles(@TempDir Path tempDir) throws IOException {
Files.writeString(baseFilePath, "A");

Path firstBackupPath = tempDir.resolve("file_0");
Files.writeString(firstBackupPath, "B");

Path secondBackupPath = tempDir.resolve("file_1");
Files.writeString(secondBackupPath, "C");

Path thirdBackupPath = tempDir.resolve("file_2");
Files.writeString(thirdBackupPath, "D");

assertThat(tempDir, hasNChildren(4));

RollingBackups rollingBackups = new RollingBackups(baseFilePath.toFile(), 3);
rollingBackups.rollBackups();

assertThat(tempDir, hasNChildren(3));

String firstBackupFileContent = Files.readString(firstBackupPath);
assertThat(firstBackupFileContent, is("A"));

String secondBackupFileContent = Files.readString(secondBackupPath);
assertThat(secondBackupFileContent, is("B"));

String thirdBackupFileContent = Files.readString(thirdBackupPath);
assertThat(thirdBackupFileContent, is("C"));
}

@Test
void threeBackupsFirstBackupMissing(@TempDir Path tempDir) throws IOException {
Files.writeString(baseFilePath, "A");

Path secondBackupPath = tempDir.resolve("file_1");
Files.writeString(secondBackupPath, "C");

Path thirdBackupPath = tempDir.resolve("file_2");
Files.writeString(thirdBackupPath, "D");

assertThat(tempDir, hasNChildren(3));

RollingBackups rollingBackups = new RollingBackups(baseFilePath.toFile(), 3);
rollingBackups.rollBackups();

assertThat(tempDir, hasNChildren(2));

Path firstBackupPath = tempDir.resolve("file_0");
String firstBackupFileContent = Files.readString(firstBackupPath);
assertThat(firstBackupFileContent, is("A"));

String thirdBackupFileContent = Files.readString(thirdBackupPath);
assertThat(thirdBackupFileContent, is("C"));
}

@Test
void threeBackupsFileMissingInMiddles(@TempDir Path tempDir) throws IOException {
Files.writeString(baseFilePath, "A");

Path firstBackupPath = tempDir.resolve("file_0");
Files.writeString(firstBackupPath, "B");

Path thirdBackupPath = tempDir.resolve("file_2");
Files.writeString(thirdBackupPath, "D");

assertThat(tempDir, hasNChildren(3));

RollingBackups rollingBackups = new RollingBackups(baseFilePath.toFile(), 3);
rollingBackups.rollBackups();

assertThat(tempDir, hasNChildren(3));

String firstBackupFileContent = Files.readString(firstBackupPath);
assertThat(firstBackupFileContent, is("A"));

Path secondBackupPath = tempDir.resolve("file_1");
String secondBackupFileContent = Files.readString(secondBackupPath);
assertThat(secondBackupFileContent, is("B"));

// Stays the same
String thirdBackupFileContent = Files.readString(thirdBackupPath);
assertThat(thirdBackupFileContent, is("D"));
}
}

0 comments on commit dc9f039

Please sign in to comment.