1
0

#6 Directory download as ZIP

This commit is contained in:
2022-08-11 22:53:01 +02:00
parent 530b2f6198
commit e47af45211
3 changed files with 86 additions and 16 deletions

View File

@@ -10,6 +10,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -22,6 +23,8 @@ import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Service @Service
public class FileSystemService { public class FileSystemService {
@@ -148,7 +151,35 @@ public class FileSystemService {
try { try {
Files.copy(this.locationTracker.resolve(name), outputStream); Files.copy(this.locationTracker.resolve(name), outputStream);
} catch (IOException e) { } 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<Path>() {
@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 { try {
return Files.newInputStream(this.locationTracker.resolve(name)); return Files.newInputStream(this.locationTracker.resolve(name));
} catch (IOException e) { } 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) { public String getMimeType(String name) {
try { try {
final String detectedMimeType = this.mimeTypeDetector.detectMimeType(this.locationTracker.resolve(name)); final String detectedMimeType = this.mimeTypeDetector.detectMimeType(this.locationTracker.resolve(name));

View File

@@ -17,8 +17,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@@ -30,10 +30,10 @@ import org.springframework.web.util.UriUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.swing.text.html.Option;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
@@ -44,6 +44,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.zip.ZipOutputStream;
@Controller @Controller
public class FilesController implements InitializingBean { public class FilesController implements InitializingBean {
@@ -147,22 +148,45 @@ public class FilesController implements InitializingBean {
} }
@GetMapping("/files/download") @GetMapping("/files/download")
public ResponseEntity downloadFile(String filename) { public ResponseEntity downloadFile(HttpServletResponse response, String filename) {
// TODO download of directories via .zip? Also needs to be streaming final Path targetPath = this.locationTracker.resolve(filename);
try { try {
return ResponseEntity.ok() if(Files.isDirectory(targetPath)) {
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") downloadDirectory(targetPath, filename, response);
.header(HttpHeaders.CONTENT_TYPE, this.fileSystemService.getMimeType(filename))
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(filename))) return ResponseEntity.ok(":)");
.body(new InputStreamResource(this.fileSystemService.stream(filename))); }
} catch (RuntimeException e) { else {
return downloadSingleFile(filename);
}
} catch (RuntimeException | IOException e) {
logger.error("Could not get file", e); logger.error("Could not get file", e);
return ResponseEntity.internalServerError().body(":("); 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") @PostMapping("/files/upload")
public String uploadFiles(@RequestParam("files") MultipartFile[] files) { public String uploadFiles(@RequestParam("files") MultipartFile[] files) {
for (MultipartFile file : files) { for (MultipartFile file : files) {
@@ -256,7 +280,7 @@ public class FilesController implements InitializingBean {
} }
return ResponseEntity.ok() 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_TYPE, this.fileSystemService.getMimeType(filename))
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(filename))) .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(filename)))
.body(new InputStreamResource(new ObservableInputStream(this.fileSystemService.stream(filename), new ObservableInputStream.Observer() { .body(new InputStreamResource(new ObservableInputStream(this.fileSystemService.stream(filename), new ObservableInputStream.Observer() {
@@ -279,7 +303,7 @@ public class FilesController implements InitializingBean {
try { try {
response.setContentType(this.fileSystemService.getMimeType(filename)); response.setContentType(this.fileSystemService.getMimeType(filename));
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(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()); this.fileSystemService.stream(filename, response.getOutputStream());
} catch (FileSystemServiceException | IOException e) { } catch (FileSystemServiceException | IOException e) {

View File

@@ -90,8 +90,7 @@
<a th:href="@{/files/delete(filename=${fileEntry.name})}" <a th:href="@{/files/delete(filename=${fileEntry.name})}"
th:text="#{nbscloud.files-content-table.table.actions.delete}"/> th:text="#{nbscloud.files-content-table.table.actions.delete}"/>
</div> </div>
<div th:if="${!fileEntry.directory}" <div id="files-content-table-show-actions-detail-container-download">
id="files-content-table-show-actions-detail-container-download">
<a th:href="@{/files/download(filename=${fileEntry.name})}" <a th:href="@{/files/download(filename=${fileEntry.name})}"
th:text="#{nbscloud.files-content-table.table.actions.download}"/> th:text="#{nbscloud.files-content-table.table.actions.download}"/>
</div> </div>