Skip to content
This repository was archived by the owner on Apr 20, 2025. It is now read-only.

Commit 0d2fbd9

Browse files
authored
Compression (#8)
1 parent 059a66e commit 0d2fbd9

4 files changed

Lines changed: 252 additions & 22 deletions

File tree

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: docker-test-perf build up clean compile update test
1+
.PHONY: docker-test-perf build up clean compile update test test-duplicate
22

33
build:
44
docker-compose build
@@ -22,5 +22,8 @@ update:
2222
docker-test-perf:
2323
docker-compose exec app mvn test -f /source/pom.xml -Dtest=ChunkingPerformanceTest
2424

25+
test-duplicate:
26+
docker-compose exec app mvn test -f /source/pom.xml -Dtest=DuplicationPerformanceTest
27+
2528
test:
2629
docker-compose exec app mvn test -f /source/pom.xml

java/src/main/java/com/goofy/GoofyFiles/controller/api/DuplicationController.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.springframework.web.bind.annotation.RestController;
1212
import org.springframework.web.multipart.MultipartFile;
1313

14+
import com.goofy.GoofyFiles.compression.CompressionService;
1415
import com.goofy.GoofyFiles.duplication.DuplicationService;
1516
import com.goofy.GoofyFiles.duplication.HashingAlgorithm;
1617

@@ -63,4 +64,28 @@ public ResponseEntity<?> processFile(
6364
.body(Map.of("error", "Échec du traitement et de l'enregistrement du fichier: " + e.getMessage()));
6465
}
6566
}
66-
}
67+
68+
@PostMapping("/process-compressed")
69+
public ResponseEntity<?> processFileCompressed(
70+
@RequestParam("file") MultipartFile file,
71+
@RequestParam(value = "algorithm", defaultValue = "SHA256") HashingAlgorithm algorithm,
72+
@RequestParam(value = "compression", defaultValue = "LZ4") CompressionService.CompressionType compression) {
73+
try {
74+
File tempFile = File.createTempFile("upload-", "-" + file.getOriginalFilename());
75+
file.transferTo(tempFile);
76+
77+
Map<String, Object> result = duplicationService.processAndStoreFileCompressed(
78+
tempFile,
79+
file.getOriginalFilename(),
80+
file.getSize(),
81+
algorithm,
82+
compression);
83+
84+
tempFile.delete();
85+
return ResponseEntity.ok(result);
86+
} catch (IOException e) {
87+
return ResponseEntity.internalServerError()
88+
.body(Map.of("error", "Échec du traitement et de l'enregistrement du fichier compressé: " + e.getMessage()));
89+
}
90+
}
91+
}

java/src/main/java/com/goofy/GoofyFiles/duplication/DuplicationService.java

Lines changed: 132 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import com.goofy.GoofyFiles.chunking.Chunk;
2020
import com.goofy.GoofyFiles.chunking.ChunkingService;
21+
import com.goofy.GoofyFiles.compression.CompressionService;
22+
import com.goofy.GoofyFiles.compression.CompressionService.CompressionType;
2123
import com.goofy.GoofyFiles.model.ChunkEntity;
2224
import com.goofy.GoofyFiles.model.FileChunkEntity;
2325
import com.goofy.GoofyFiles.model.FileEntity;
@@ -35,6 +37,7 @@ public class DuplicationService {
3537
private final FileRepository fileRepository;
3638
private final ChunkRepository chunkRepository;
3739
private final FileChunkRepository fileChunkRepository;
40+
private final CompressionService compressionService;
3841

3942
/**
4043
* Constructeur principal pour l'utilisation en production
@@ -44,11 +47,13 @@ public DuplicationService(
4447
ChunkingService chunkingService,
4548
FileRepository fileRepository,
4649
ChunkRepository chunkRepository,
47-
FileChunkRepository fileChunkRepository) {
50+
FileChunkRepository fileChunkRepository,
51+
CompressionService compressionService) {
4852
this.chunkingService = chunkingService;
4953
this.fileRepository = fileRepository;
5054
this.chunkRepository = chunkRepository;
5155
this.fileChunkRepository = fileChunkRepository;
56+
this.compressionService = compressionService;
5257
}
5358

5459
/**
@@ -57,10 +62,7 @@ public DuplicationService(
5762
* pas disponibles
5863
*/
5964
public DuplicationService(ChunkingService chunkingService) {
60-
this.chunkingService = chunkingService;
61-
this.fileRepository = null;
62-
this.chunkRepository = null;
63-
this.fileChunkRepository = null;
65+
this(chunkingService, null, null, null, null);
6466
}
6567

6668
public Map<String, Object> analyzeFile(File file, HashingAlgorithm algorithm) throws IOException {
@@ -74,8 +76,6 @@ public Map<String, Object> analyzeFile(File file, HashingAlgorithm algorithm) th
7476
chunk.getPosition(), chunk.getData().length, hash);
7577
}
7678

77-
// Filtrer les chunks qui apparaissent plus d'une fois (vous pouvez logguer ou
78-
// utiliser ce résultat)
7979
duplicates.entrySet().stream()
8080
.filter(e -> e.getValue() > 1);
8181

@@ -106,7 +106,6 @@ private String calculateHash(byte[] data, HashingAlgorithm algorithm) {
106106
case SHA256:
107107
return Hashing.sha256().hashBytes(data).toString();
108108
case BLAKE3:
109-
// Utilisation de Apache Commons Codec pour BLAKE3
110109
byte[] hashBytes = Blake3.hash(data);
111110
return Hex.encodeHexString(hashBytes);
112111
default:
@@ -174,7 +173,6 @@ public Map<String, Object> processAndStoreFile(
174173
existingChunk = Optional.empty();
175174
}
176175

177-
// Traiter le chunk (nouveau ou existant)
178176
ChunkEntity chunkEntity;
179177
if (existingChunk.isPresent()) {
180178
chunkEntity = existingChunk.get();
@@ -228,4 +226,128 @@ public Map<String, Object> processAndStoreFile(
228226

229227
return result;
230228
}
231-
}
229+
230+
@Transactional
231+
public Map<String, Object> processAndStoreFileCompressed(
232+
File file,
233+
String fileName,
234+
long fileSize,
235+
HashingAlgorithm algorithm,
236+
CompressionType compressionType) throws IOException {
237+
if (fileRepository == null || chunkRepository == null || fileChunkRepository == null
238+
|| compressionService == null) {
239+
throw new UnsupportedOperationException(
240+
"Cette méthode nécessite les repositories et le service de compression qui n'ont pas été injectés. " +
241+
"Utilisez le constructeur avec tous les paramètres pour cette fonctionnalité.");
242+
}
243+
244+
// 1. Extraire le nom et l'extension
245+
String name = fileName;
246+
String extension = "";
247+
int lastDotIndex = fileName.lastIndexOf('.');
248+
if (lastDotIndex > 0) {
249+
name = fileName.substring(0, lastDotIndex);
250+
extension = fileName.substring(lastDotIndex + 1);
251+
}
252+
253+
// 2. Créer et sauvegarder l'entité de fichier
254+
FileEntity fileEntity = new FileEntity();
255+
fileEntity.setName(name);
256+
fileEntity.setExtension(extension);
257+
fileEntity.setSize(fileSize);
258+
fileEntity = fileRepository.save(fileEntity);
259+
260+
// 3. Découper le fichier
261+
List<Chunk> chunks = chunkingService.chunkFile(file);
262+
263+
// Statistiques pour le résultat
264+
int totalChunks = chunks.size();
265+
int duplicateChunks = 0;
266+
int uniqueChunks = 0;
267+
long savedStorage = 0;
268+
long totalCompressedSize = 0;
269+
270+
// 4. Traiter chaque chunk
271+
for (Chunk chunk : chunks) {
272+
String hash = calculateHash(chunk.getData(), algorithm);
273+
274+
// Chercher si ce chunk existe déjà en base
275+
Optional<ChunkEntity> existingChunk;
276+
switch (algorithm) {
277+
case SHA1:
278+
existingChunk = chunkRepository.findByHashSha1(hash);
279+
break;
280+
case SHA256:
281+
existingChunk = chunkRepository.findByHashSha256(hash);
282+
break;
283+
case BLAKE3:
284+
existingChunk = chunkRepository.findByHashBlake3(hash);
285+
break;
286+
default:
287+
existingChunk = Optional.empty();
288+
}
289+
290+
ChunkEntity chunkEntity;
291+
if (existingChunk.isPresent()) {
292+
chunkEntity = existingChunk.get();
293+
duplicateChunks++;
294+
savedStorage += chunk.getOriginalSize();
295+
logger.info("Chunk dupliqué trouvé: {}", hash);
296+
} else {
297+
// Compression du chunk
298+
byte[] compressedData = compressionService.compress(chunk.getData(), compressionType);
299+
totalCompressedSize += compressedData.length;
300+
301+
chunkEntity = new ChunkEntity();
302+
// Stocker les données compressées
303+
chunkEntity.setData(compressedData);
304+
// Vous pouvez ajouter une propriété pour stocker la taille originale si besoin,
305+
// ex :
306+
// chunkEntity.setOriginalSize(chunk.getData().length);
307+
308+
// Stocker le hash selon l'algorithme
309+
switch (algorithm) {
310+
case SHA1:
311+
chunkEntity.setHashSha1(hash);
312+
break;
313+
case SHA256:
314+
chunkEntity.setHashSha256(hash);
315+
break;
316+
case BLAKE3:
317+
chunkEntity.setHashBlake3(hash);
318+
break;
319+
}
320+
321+
chunkEntity = chunkRepository.save(chunkEntity);
322+
uniqueChunks++;
323+
}
324+
325+
// Créer la relation entre le fichier et le chunk
326+
FileChunkEntity fileChunk = new FileChunkEntity();
327+
fileChunk.setFile(fileEntity);
328+
fileChunk.setChunk(chunkEntity);
329+
fileChunk.setPosition(chunk.getPosition());
330+
fileChunkRepository.save(fileChunk);
331+
}
332+
333+
// 5. Préparer le résultat
334+
Map<String, Object> result = new HashMap<>();
335+
result.put("fileId", fileEntity.getId());
336+
result.put("fileName", fileEntity.getName());
337+
result.put("extension", fileEntity.getExtension());
338+
result.put("fileSize", fileEntity.getSize());
339+
result.put("algorithm", algorithm.name());
340+
result.put("compressionType", compressionType.name());
341+
result.put("totalChunks", totalChunks);
342+
result.put("uniqueChunks", uniqueChunks);
343+
result.put("duplicateChunks", duplicateChunks);
344+
result.put("savedStorage", savedStorage);
345+
result.put("deduplicationRatio", totalChunks > 0 ? (double) duplicateChunks / totalChunks : 0);
346+
result.put("totalCompressedSize", totalCompressedSize);
347+
348+
logger.info("Fichier compressé traité: id={}, nom={}, chunks={}, uniques={}, doublons={}, taille compressée={}",
349+
fileEntity.getId(), fileName, totalChunks, uniqueChunks, duplicateChunks, totalCompressedSize);
350+
351+
return result;
352+
}
353+
}

0 commit comments

Comments
 (0)