Files
sheepit-shadow-nabber/src/main/java/com/sheepit/client/Md5.java

167 lines
4.7 KiB
Java
Raw Normal View History

2023-01-04 17:08:35 +01:00
/*
* Copyright (C) 2023 Laurent CLOUET
* Author Laurent CLOUET <laurent.clouet@nopnop.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.sheepit.client;
import java.io.File;
2023-01-04 17:08:35 +01:00
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
2023-11-12 13:36:20 +00:00
import java.util.Iterator;
2023-01-04 17:08:35 +01:00
import java.util.Map;
2023-11-12 13:36:20 +00:00
import java.util.Objects;
2023-01-04 17:08:35 +01:00
2023-11-12 13:36:20 +00:00
/**
* Provides facilities to get an MD5 checksum and cache the results thereof
*/
2023-01-04 17:08:35 +01:00
public class Md5 {
2023-11-12 13:36:20 +00:00
/**
* Hashmap of all files uniqueMD5keys (last modification time + file)
* and their checksums that have been calculated so far
*/
private static final Map<UniqueMD5Key, String> cache = new HashMap<>();
private static final int cacheCleanTrigger = 64; //After how many cache hits to check and clean the cache
private static int cacheHits = 0;
2023-01-04 17:08:35 +01:00
2023-11-12 13:36:20 +00:00
2023-01-04 17:08:35 +01:00
2023-11-12 13:36:20 +00:00
/**
* Removes a file from cache
* @param path Path to the file
*/
public static void remove(String path) {
UniqueMD5Key key = new UniqueMD5Key(path);
cache.remove(key); //Doesn't handle entries that the files moved or had their lastModified changed
}
/**
* Gets a MD5 checksum either from the cache or computes it and puts it into the cache
*
* @param path Path of the file to get the MD5 checksum for
* @return the string The MD5 checksum
*/
2023-01-04 17:08:35 +01:00
public String get(String path) {
2023-11-12 13:36:20 +00:00
UniqueMD5Key key = new UniqueMD5Key(path);
if (cache.containsKey(key) == false) {
2023-01-04 17:08:35 +01:00
generate(path);
}
2023-11-12 13:36:20 +00:00
if (cacheHits >= cacheCleanTrigger){
cacheCheck();
cacheHits = 0;
} else {
cacheHits++;
}
return cache.get(key);
2023-01-04 17:08:35 +01:00
}
2023-11-12 13:36:20 +00:00
/**
* Checks cache for files that don't exist anymore according to cached values and removes them
* Catches entries tha fall through the cracks
*/
private void cacheCheck(){
Iterator<Map.Entry<UniqueMD5Key, String>> it = cache.entrySet().iterator();
while (it.hasNext()) {
UniqueMD5Key itKey = (UniqueMD5Key)it.next();
if (itKey.exists() == false) {
it.remove(); //Iterator instead of foreach because we remove items
}
}
}
/**
* Calculates the md5 checksum for a given file
* @param path Path to the file to calculate the checksum for
*/
private void generate(String path) {
2023-11-12 13:36:20 +00:00
UniqueMD5Key key = new UniqueMD5Key(path);
2023-01-04 17:08:35 +01:00
try {
MessageDigest md = MessageDigest.getInstance("MD5");
InputStream is = Files.newInputStream(Paths.get(path));
DigestInputStream dis = new DigestInputStream(is, md);
byte[] buffer = new byte[8192];
while (dis.read(buffer) > 0)
; // process the entire file
2023-11-12 13:36:20 +00:00
String checksum = Utils.convertBinaryToHex(md.digest());
2023-01-04 17:08:35 +01:00
dis.close();
is.close();
2023-11-12 13:36:20 +00:00
cache.put(key, checksum);
2023-01-04 17:08:35 +01:00
}
catch (NoSuchAlgorithmException | IOException e) {
cache.put(key, "");
2023-01-04 17:08:35 +01:00
}
}
2023-11-12 13:36:20 +00:00
}
/**
* Used as key for the hashmap that is the cache
* while also making it accessible to check if key is still valid
*/
class UniqueMD5Key {
private final long lastModified;
private final File file;
/**
* Returns a UniqueMD5Key for a given filepath
* Automatically fills in lastModified time
* @param filepath The filepath to the file
*/
public UniqueMD5Key(String filepath) {
this.file = new File(filepath);
this.lastModified = this.file.lastModified();
}
/**
* @see File#exists()
* also takes lastModified into account.
*/
public boolean exists(){
return file.exists() && lastModified == this.file.lastModified();
}
/**
* @see Object#toString()
*/
@Override public String toString() {
return "UniqueMD5Key{" + "lastModified=" + lastModified + ", file=" + file + '}';
}
/**
* @see Object#equals(Object)
*/
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
UniqueMD5Key that = (UniqueMD5Key) o;
return lastModified == that.lastModified && Objects.equals(file, that.file);
}
2023-11-12 13:36:20 +00:00
/**
* @see Object#hashCode()
*/
@Override public int hashCode() {
return Objects.hash(lastModified, file);
}
2023-01-04 17:08:35 +01:00
}