diff options
author | Keuin <[email protected]> | 2021-01-12 12:51:23 +0800 |
---|---|---|
committer | keuin <[email protected]> | 2021-01-12 12:51:23 +0800 |
commit | afd26cdd12fef4bd2aafa2ac8d708e18d277a2fe (patch) | |
tree | e389ccbe9fb48e03de25b38d3ef7d5c273c5ec97 /src | |
parent | 8b05c48d76bbb05c2b440e57df1d9b3323edc598 (diff) |
Implement object collection(not tested) and sha256(tested)
Diffstat (limited to 'src')
10 files changed, 220 insertions, 14 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/util/BytesUtil.java b/src/main/java/com/keuin/kbackupfabric/util/BytesUtil.java new file mode 100644 index 0000000..6ded7b8 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/BytesUtil.java @@ -0,0 +1,16 @@ +package com.keuin.kbackupfabric.util; + +import java.nio.charset.StandardCharsets; + +public class BytesUtil { + private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + public static String bytesToHex(byte[] bytes) { + byte[] hexChars = new byte[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/inc/ObjectCollectionManager.java b/src/main/java/com/keuin/kbackupfabric/util/backup/inc/ObjectCollectionManager.java deleted file mode 100644 index 9560261..0000000 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/inc/ObjectCollectionManager.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.keuin.kbackupfabric.util.backup.inc; - -/** - * Incremental backup is implemented as git-like file collection. - * Files are called `objects`, the collection contains all files distinguished by their - * identifiers. Usually, identifier is the combination of hash and other short information (such as size and another hash). - * The identifier should use hashes that are strong enough, to prevent possible collisions. - */ -public class ObjectCollectionManager { - -} 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 new file mode 100644 index 0000000..d5d766a --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollection.java @@ -0,0 +1,45 @@ +package com.keuin.kbackupfabric.util.backup.incremental; + +import com.keuin.kbackupfabric.util.backup.incremental.identifier.ObjectIdentifier; + +import java.util.Objects; +import java.util.Set; + +public class ObjectCollection { + private final String name; + private final Set<ObjectIdentifier> elements; + private final Set<ObjectCollection> subCollections; + + ObjectCollection(String name, Set<ObjectIdentifier> elements, Set<ObjectCollection> subCollections) { + this.name = Objects.requireNonNull(name); + this.elements = Objects.requireNonNull(elements); + this.subCollections = Objects.requireNonNull(subCollections); + } + + public String getName() { + return name; + } + + public Set<ObjectIdentifier> getElements() { + return elements; + } + + public Set<ObjectCollection> getSubCollections() { + return subCollections; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ObjectCollection that = (ObjectCollection) o; + return name.equals(that.name) && + elements.equals(that.elements) && + subCollections.equals(that.subCollections); + } + + @Override + public int hashCode() { + return Objects.hash(name, elements, 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 new file mode 100644 index 0000000..8b974db --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java @@ -0,0 +1,48 @@ +package com.keuin.kbackupfabric.util.backup.incremental; + +import com.keuin.kbackupfabric.util.backup.incremental.identifier.FileIdentifierFactory; +import com.keuin.kbackupfabric.util.backup.incremental.identifier.ObjectIdentifier; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; + +/** + * Incremental backup is implemented as git-like file collection. + * Files are called `objects`, the collection contains all files distinguished by their + * identifiers. Usually, identifier is the combination of hash and other short information (such as size and another hash). + * 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) { + this.identifierFactory = identifierFactory; + } + + public ObjectCollection fromDirectory(File directory) throws IOException { + final Set<ObjectIdentifier> subFiles = new HashSet<>(); + final Set<ObjectCollection> subCollections = new HashSet<>(); + + if (!Objects.requireNonNull(directory).isDirectory()) + throw new IllegalArgumentException("given file is not a directory"); + + for (Iterator<Path> iter = Files.walk(directory.toPath()).iterator(); iter.hasNext();) { + Path path = iter.next(); + File file = path.toFile(); + if (file.isDirectory()) { + subCollections.add(fromDirectory(file)); + } else { + subFiles.add(identifierFactory.fromFile(file)); + } + } + + return new ObjectCollection(directory.getName(), subFiles, subCollections); + } + +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/FileIdentifierFactory.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/FileIdentifierFactory.java new file mode 100644 index 0000000..34ad9a0 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/FileIdentifierFactory.java @@ -0,0 +1,8 @@ +package com.keuin.kbackupfabric.util.backup.incremental.identifier; + +import java.io.File; +import java.io.IOException; + +public interface FileIdentifierFactory<T extends ObjectIdentifier> { + T fromFile(File file) throws IOException; +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/inc/identifier/ObjectIdentifier.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java index 079f49d..62798e1 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/inc/identifier/ObjectIdentifier.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/ObjectIdentifier.java @@ -1,4 +1,4 @@ -package com.keuin.kbackupfabric.util.backup.inc.identifier; +package com.keuin.kbackupfabric.util.backup.incremental.identifier; /** * The identifier distinguishing files in the object collection. @@ -6,4 +6,5 @@ package com.keuin.kbackupfabric.util.backup.inc.identifier; * All identifiers should be immutable and implement their own equals method. */ public interface ObjectIdentifier { + 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 new file mode 100644 index 0000000..9f56b0e --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java @@ -0,0 +1,67 @@ +package com.keuin.kbackupfabric.util.backup.incremental.identifier; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; + +public class Sha256Identifier extends SingleHashIdentifier { + + private static final int SHA256_LENGTH = 32; + private static final Sha256Identifier DUMMY = new Sha256Identifier(new byte[SHA256_LENGTH]); // only for using its hash method + private static final FileIdentifierFactory<Sha256Identifier> factory = Sha256Identifier::fromFile; + + public static Sha256Identifier fromFile(File file) throws IOException { + if (!Objects.requireNonNull(file).isFile()) { + throw new IllegalArgumentException("file is not a file"); + } + return new Sha256Identifier(DUMMY.hash(file)); + } + + public static FileIdentifierFactory<Sha256Identifier> getFactory() { + return factory; + } + + protected Sha256Identifier(byte[] hash) { + super(hash); + Objects.requireNonNull(hash); + if (hash.length != SHA256_LENGTH) { + throw new IllegalStateException(String.format("SHA256 must be %d bytes", SHA256_LENGTH)); + } + } + + @Override + protected byte[] hash(File file) throws IOException { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + + 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); + + // 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(); + } catch (NoSuchAlgorithmException ignored) { + // this shouldn't happen + return new byte[SHA256_LENGTH]; + } + } + +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/inc/identifier/SingleHashIdentifier.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/SingleHashIdentifier.java index 50b23bb..62ba47c 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/inc/identifier/SingleHashIdentifier.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/SingleHashIdentifier.java @@ -1,6 +1,9 @@ -package com.keuin.kbackupfabric.util.backup.inc.identifier; +package com.keuin.kbackupfabric.util.backup.incremental.identifier; + +import com.keuin.kbackupfabric.util.BytesUtil; import java.io.File; +import java.io.IOException; import java.util.Arrays; /** @@ -20,7 +23,12 @@ public abstract class SingleHashIdentifier implements ObjectIdentifier { * @param file the file to be hashed. * @return the hash bytes. */ - protected abstract byte[] hash(File file); + protected abstract byte[] hash(File file) throws IOException; + + @Override + public String getIdentification() { + return BytesUtil.bytesToHex(hash); + } @Override public boolean equals(Object obj) { diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256IdentifierTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256IdentifierTest.java new file mode 100644 index 0000000..2296e01 --- /dev/null +++ b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256IdentifierTest.java @@ -0,0 +1,23 @@ +package com.keuin.kbackupfabric.util.backup.incremental.identifier; + +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class Sha256IdentifierTest { + + @Test + public void fromFile() { + try { + Sha256Identifier sha256 = Sha256Identifier.fromFile(new File("./src/test/sha256")); + String str = sha256.getIdentification().toUpperCase(); + assertEquals("315F5BDB76D078C43B8AC0064E4A0164612B1FCE77C869345BFC94C75894EDD3", str); + } catch (IOException e) { + e.printStackTrace(); + } + } + +}
\ No newline at end of file diff --git a/src/test/sha256 b/src/test/sha256 new file mode 100644 index 0000000..5dd01c1 --- /dev/null +++ b/src/test/sha256 @@ -0,0 +1 @@ +Hello, world!
\ No newline at end of file |