From e47af45211ca9f447e69e46d19d7d7b4702f0b98 Mon Sep 17 00:00:00 2001 From: MK13 Date: Thu, 11 Aug 2022 22:53:01 +0200 Subject: [PATCH] #6 Directory download as ZIP --- .../de/nbscloud/files/FileSystemService.java | 51 ++++++++++++++++++- .../files/controller/FilesController.java | 48 ++++++++++++----- .../resources/templates/files/filesIndex.html | 3 +- 3 files changed, 86 insertions(+), 16 deletions(-) diff --git a/files/src/main/java/de/nbscloud/files/FileSystemService.java b/files/src/main/java/de/nbscloud/files/FileSystemService.java index 3e6c6cc..69f1cad 100644 --- a/files/src/main/java/de/nbscloud/files/FileSystemService.java +++ b/files/src/main/java/de/nbscloud/files/FileSystemService.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; import java.io.IOException; import java.io.InputStream; @@ -22,6 +23,8 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; @Service public class FileSystemService { @@ -148,7 +151,35 @@ public class FileSystemService { try { Files.copy(this.locationTracker.resolve(name), outputStream); } catch (IOException e) { - throw new FileSystemServiceException("Could not get file", e); + throw new FileSystemServiceException("Could not stream file", e); + } + } + + public void stream(Path targetPath, ZipOutputStream outputStream) { + try { + Files.walkFileTree(targetPath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if(Files.isDirectory(file)) { + return FileVisitResult.CONTINUE; + } + + final ZipEntry e = new ZipEntry(targetPath.relativize(file).toString()); + + e.setSize(getSize(file)); + e.setTime(System.currentTimeMillis()); + + outputStream.putNextEntry(e); + + StreamUtils.copy(stream(file), outputStream); + + outputStream.closeEntry(); + + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + throw new FileSystemServiceException("Could not stream file", e); } } @@ -156,7 +187,15 @@ public class FileSystemService { try { return Files.newInputStream(this.locationTracker.resolve(name)); } catch (IOException e) { - throw new FileSystemServiceException("Could not get file", e); + throw new FileSystemServiceException("Could not stream file", e); + } + } + + private InputStream stream(Path name) { + try { + return Files.newInputStream(name); + } catch (IOException e) { + throw new FileSystemServiceException("Could not stream file", e); } } @@ -168,6 +207,14 @@ public class FileSystemService { } } + private long getSize(Path name) { + try { + return Files.size(name); + } catch (IOException e) { + throw new FileSystemServiceException("Could not get file", e); + } + } + public String getMimeType(String name) { try { final String detectedMimeType = this.mimeTypeDetector.detectMimeType(this.locationTracker.resolve(name)); diff --git a/files/src/main/java/de/nbscloud/files/controller/FilesController.java b/files/src/main/java/de/nbscloud/files/controller/FilesController.java index 228234c..ddee2a3 100644 --- a/files/src/main/java/de/nbscloud/files/controller/FilesController.java +++ b/files/src/main/java/de/nbscloud/files/controller/FilesController.java @@ -17,8 +17,8 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; -import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -30,10 +30,10 @@ import org.springframework.web.util.UriUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.swing.text.html.Option; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.time.LocalDate; @@ -44,6 +44,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.zip.ZipOutputStream; @Controller public class FilesController implements InitializingBean { @@ -147,22 +148,45 @@ public class FilesController implements InitializingBean { } @GetMapping("/files/download") - public ResponseEntity downloadFile(String filename) { - // TODO download of directories via .zip? Also needs to be streaming + public ResponseEntity downloadFile(HttpServletResponse response, String filename) { + final Path targetPath = this.locationTracker.resolve(filename); try { - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") - .header(HttpHeaders.CONTENT_TYPE, this.fileSystemService.getMimeType(filename)) - .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(filename))) - .body(new InputStreamResource(this.fileSystemService.stream(filename))); - } catch (RuntimeException e) { + if(Files.isDirectory(targetPath)) { + downloadDirectory(targetPath, filename, response); + + return ResponseEntity.ok(":)"); + } + else { + return downloadSingleFile(filename); + } + } catch (RuntimeException | IOException e) { logger.error("Could not get file", e); return ResponseEntity.internalServerError().body(":("); } } + private ResponseEntity downloadSingleFile(String filename) { + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename) + .header(HttpHeaders.CONTENT_TYPE, this.fileSystemService.getMimeType(filename)) + .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(filename))) + .body(new InputStreamResource(this.fileSystemService.stream(filename))); + } + + private void downloadDirectory(Path targetPath, String filename, HttpServletResponse response) throws IOException { + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename + ".zip"); + response.setStatus(HttpServletResponse.SC_OK); + + try (ZipOutputStream zippedOut = new ZipOutputStream(response.getOutputStream())) { + fileSystemService.stream(targetPath, zippedOut); + + zippedOut.finish(); + } + } + @PostMapping("/files/upload") public String uploadFiles(@RequestParam("files") MultipartFile[] files) { for (MultipartFile file : files) { @@ -256,7 +280,7 @@ public class FilesController implements InitializingBean { } return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename) .header(HttpHeaders.CONTENT_TYPE, this.fileSystemService.getMimeType(filename)) .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(filename))) .body(new InputStreamResource(new ObservableInputStream(this.fileSystemService.stream(filename), new ObservableInputStream.Observer() { @@ -279,7 +303,7 @@ public class FilesController implements InitializingBean { try { response.setContentType(this.fileSystemService.getMimeType(filename)); response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(filename))); - response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "filename=\"" + filename + "\""); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "filename=" + filename); this.fileSystemService.stream(filename, response.getOutputStream()); } catch (FileSystemServiceException | IOException e) { diff --git a/files/src/main/resources/templates/files/filesIndex.html b/files/src/main/resources/templates/files/filesIndex.html index 299217d..420f259 100644 --- a/files/src/main/resources/templates/files/filesIndex.html +++ b/files/src/main/resources/templates/files/filesIndex.html @@ -90,8 +90,7 @@ -