diff options
9 files changed, 173 insertions, 14 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java index e0ef4f7..16d95e6 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java @@ -1,8 +1,9 @@ package com.keuin.kbackupfabric.util.backup.incremental; +import java.io.Serializable; import java.util.*; -public class ObjectCollection { +public class ObjectCollection implements Serializable { private final String name; private final Map<String, ObjectElement> elements; private final Map<String, ObjectCollection> subCollections; diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java index be9e426..ea9edd9 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java @@ -16,6 +16,7 @@ import java.util.*; * The identifier should use hashes that are strong enough, to prevent possible collisions. */ public class ObjectCollectionFactory <T extends ObjectIdentifier> { + private final FileIdentifierFactory<T> identifierFactory; public ObjectCollectionFactory(FileIdentifierFactory<T> identifierFactory) { diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializer.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializer.java new file mode 100644 index 0000000..cc77837 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializer.java @@ -0,0 +1,30 @@ +package com.keuin.kbackupfabric.util.backup.incremental; + +import java.io.*; +import java.util.Objects; + +public class ObjectCollectionSerializer { + public static ObjectCollection fromFile(File file) throws IOException { + Objects.requireNonNull(file); + ObjectCollection collection; + try (FileInputStream fileInputStream = new FileInputStream(file)) { + try (ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { + collection = (ObjectCollection) objectInputStream.readObject(); + } catch (ClassNotFoundException ignored) { + // this should not happen + return null; + } + } + return collection; + } + + public static void toFile(ObjectCollection collection, File file) throws IOException { + Objects.requireNonNull(collection); + Objects.requireNonNull(file); + try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { + try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { + objectOutputStream.writeObject(collection); + } + } + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java index 1232fb9..cbb886e 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectElement.java @@ -2,13 +2,14 @@ package com.keuin.kbackupfabric.util.backup.incremental; import com.keuin.kbackupfabric.util.backup.incremental.identifier.ObjectIdentifier; +import java.io.Serializable; import java.util.Objects; /** * Representing a file in a ObjectCollection. * Immutable. */ -public class ObjectElement { +public class ObjectElement implements Serializable { private final String name; private final ObjectIdentifier identifier; diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java index 6744616..aece07d 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java @@ -1,11 +1,13 @@ package com.keuin.kbackupfabric.util.backup.incremental.identifier; +import java.io.Serializable; + /** * The identifier distinguishing files in the object collection. * It should be based on cryptographic hash function in order to prevent possible attacks to the backup system. * All identifiers should be immutable and implement their own equals method. * Immutable. */ -public interface ObjectIdentifier { +public interface ObjectIdentifier extends Serializable { String getIdentification(); } diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java index 64716ed..9ecf2d3 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java @@ -41,27 +41,28 @@ public class Sha256Identifier extends SingleHashIdentifier { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); - FileInputStream inputStream = new FileInputStream(file); - - // This does not work. I don't know why + try (FileInputStream inputStream = new FileInputStream(file)) { + // This does not work. I don't know why // FileChannel channel = inputStream.getChannel(); // ByteBuffer buffer = ByteBuffer.allocate(128); // int readLength; // while ((readLength = channel.read(buffer)) > 0) // digest.update(buffer); - // This also works, without warnings - byte[] readBuffer = new byte[1024 * 1024]; - int readLength; - while ((readLength = inputStream.read(readBuffer)) > 0) - digest.update(readBuffer,0, readLength); + // This also works, without warnings + byte[] readBuffer = new byte[1024 * 1024]; + int readLength; + while ((readLength = inputStream.read(readBuffer)) > 0) + digest.update(readBuffer, 0, readLength); - // The below lines also works, but the IDE will complain about the while loop + // The below lines also works, but the IDE will complain about the while loop // DigestInputStream digestInputStream = new DigestInputStream(inputStream, digest); // while(digestInputStream.read() > 0) // ; - return digest.digest(); + return digest.digest(); + } + } catch (NoSuchAlgorithmException ignored) { // this shouldn't happen return new byte[SHA256_LENGTH]; diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java new file mode 100644 index 0000000..cd15499 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java @@ -0,0 +1,95 @@ +package com.keuin.kbackupfabric.util.backup.incremental.manager; + +import com.keuin.kbackupfabric.util.backup.incremental.ObjectCollection; +import com.keuin.kbackupfabric.util.backup.incremental.ObjectElement; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Objects; + +public class IncrementalBackupStorageManager { + + private final Path backupStorageBase; + + public IncrementalBackupStorageManager(Path backupStorageBase) { + this.backupStorageBase = backupStorageBase; + } + + /** + * Add a object collection to storage base. + * @param collection the collection. + * @return objects copied to the base. + * @throws IOException I/O Error. + */ + public int addObjectCollection(ObjectCollection collection, File collectionBasePath) throws IOException { + Objects.requireNonNull(collection); + Objects.requireNonNull(collectionBasePath); + + int copyCount = 0; + + // copy sub files + for (Map.Entry<String, ObjectElement> entry : collection.getElementMap().entrySet()) { + File copyDestination = new File(backupStorageBase.toFile(), entry.getValue().getIdentifier().getIdentification()); + if (!baseContainsObject(entry.getValue())) { + // element does not exist. copy. + Files.copy(Paths.get(collectionBasePath.getAbsolutePath(), entry.getKey()), copyDestination.toPath()); + ++copyCount; + } + } + + //copy sub dirs recursively + for (Map.Entry<String, ObjectCollection> entry : collection.getSubCollectionMap().entrySet()) { + File newBase = new File(collectionBasePath, entry.getKey()); + copyCount += addObjectCollection(entry.getValue(), newBase); + } + + return copyCount; + } + + /** + * Restore an object collection from the storage base. i.e., restore the save from backup storage. + * @param collection the collection to be restored. + * @param collectionBasePath save path of the collection. + * @return objects restored from the base. + * @throws IOException I/O Error. + */ + public int restoreObjectCollection(ObjectCollection collection, File collectionBasePath) throws IOException { + Objects.requireNonNull(collection); + Objects.requireNonNull(collectionBasePath); + + int copyCount = 0; + + // copy sub files + for (Map.Entry<String, ObjectElement> entry : collection.getElementMap().entrySet()) { + File copySource = new File(backupStorageBase.toFile(), entry.getValue().getIdentifier().getIdentification()); + if (!baseContainsObject(entry.getValue())) { + throw new IOException(String.format("File %s does not exist in the base.", copySource.getName())); + } + Files.copy(copySource.toPath(), Paths.get(collectionBasePath.getAbsolutePath(), entry.getKey())); + ++copyCount; + } + + //copy sub dirs recursively + for (Map.Entry<String, ObjectCollection> entry : collection.getSubCollectionMap().entrySet()) { + File newBase = new File(collectionBasePath, entry.getKey()); + copyCount += restoreObjectCollection(entry.getValue(), newBase); + } + + return copyCount; + } + + /** + * Check if the backup base contains given element. + * @param objectElement the element. + * @return true or false. + */ + private boolean baseContainsObject(ObjectElement objectElement) { + // This may be extended to use more variants of hash functions and combinations of other attributes (such as file size) + return (new File(backupStorageBase.toFile(), objectElement.getIdentifier().getIdentification())).exists(); + } + +} diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java index 60a42cf..b7e0aad 100644 --- a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java +++ b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java @@ -72,7 +72,6 @@ public class ObjectCollectionFactoryTest { // check `12` validate(collection.getSubCollectionMap().get("1").getSubCollectionMap().get("12"), Collections.emptyList(), Collections.emptyMap()); - } catch (IOException e) { e.printStackTrace(); fail(); diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializerTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializerTest.java new file mode 100644 index 0000000..0edfe01 --- /dev/null +++ b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionSerializerTest.java @@ -0,0 +1,29 @@ +package com.keuin.kbackupfabric.util.backup.incremental; + +import com.keuin.kbackupfabric.util.backup.incremental.identifier.Sha256Identifier; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import static org.junit.Assert.assertEquals; + +public class ObjectCollectionSerializerTest { + @Test + public void testSerializationConsistency() throws IOException { + ObjectCollectionFactory<Sha256Identifier> factory = + new ObjectCollectionFactory<>(Sha256Identifier.getFactory()); + ObjectCollection collection = + factory.fromDirectory(new File("./testfile/ObjectCollectionFactoryTest")); + File file = new File("./testfile/serialized"); + if (file.exists()) { + Files.delete(file.toPath()); + } + ObjectCollectionSerializer.toFile(collection, file); + ObjectCollection collection2 = ObjectCollectionSerializer.fromFile(file); + Files.delete(file.toPath()); + assertEquals(collection, collection2); + } + +}
\ No newline at end of file |