From bc56d12ad6a7981579076b973dcbacc50e0b60a6 Mon Sep 17 00:00:00 2001 From: Peter Siegmund Date: Thu, 30 May 2024 22:02:42 +0200 Subject: [PATCH] add file up-/download Signed-off-by: Peter Siegmund --- .../firmware/controller/FilesController.java | 51 +++++++++++++++++++ .../firmware/service/FirmwareServiceImpl.java | 2 +- .../tide_display/backend/gcp/Firmware.java | 6 +++ .../backend/gcp/StorageService.java | 6 +++ .../backend/gcp/bean/GoogleCloudBean.java | 15 ++++++ .../gcp/service/StorageServiceImpl.java | 48 +++++++++++++++-- 6 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 backend/src/main/java/com/rdkr/tide_display/backend/firmware/controller/FilesController.java create mode 100644 backend/src/main/java/com/rdkr/tide_display/backend/gcp/bean/GoogleCloudBean.java diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/firmware/controller/FilesController.java b/backend/src/main/java/com/rdkr/tide_display/backend/firmware/controller/FilesController.java new file mode 100644 index 0000000..8d51c7c --- /dev/null +++ b/backend/src/main/java/com/rdkr/tide_display/backend/firmware/controller/FilesController.java @@ -0,0 +1,51 @@ +package com.rdkr.tide_display.backend.firmware.controller; + +import com.rdkr.tide_display.backend.gcp.StorageService; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.apache.coyote.BadRequestException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +@RestController +@RequestMapping("/files") +@RequiredArgsConstructor +public class FilesController { + + private final StorageService storageService; + + @GetMapping("/{version:\\d\\.\\d\\.\\d}/{filename:.+}") + @ResponseBody + public ResponseEntity download(@PathVariable String version, @PathVariable String filename) { + byte[] file = storageService.download(version, filename); + + if (file == null) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity + .ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") + .body(file); + } + + @PostMapping() + public String upload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) + throws Exception { + val version = storageService.upload(file); + if (version == null) { + throw new BadRequestException("Invalid firmware file"); + } + redirectAttributes.addFlashAttribute("message", "You successfully uploaded " + file.getOriginalFilename() + "!"); + return "redirect:/v1/versions/" + version; + } +} diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/firmware/service/FirmwareServiceImpl.java b/backend/src/main/java/com/rdkr/tide_display/backend/firmware/service/FirmwareServiceImpl.java index d1197d3..4d87a52 100644 --- a/backend/src/main/java/com/rdkr/tide_display/backend/firmware/service/FirmwareServiceImpl.java +++ b/backend/src/main/java/com/rdkr/tide_display/backend/firmware/service/FirmwareServiceImpl.java @@ -27,7 +27,7 @@ public class FirmwareServiceImpl implements FirmwareService { public Optional getFirmwareVersion(String version) { return storageService.getFirmwareVersions().stream() - //.filter(firmware -> firmware.version().equals(version)) + .filter(firmware -> firmware.getVersion().equals(version)) .findFirst(); } } diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/Firmware.java b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/Firmware.java index 82cf7a1..c077e20 100644 --- a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/Firmware.java +++ b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/Firmware.java @@ -22,6 +22,12 @@ public class Firmware { private String eTag; + private int flashSize; + + private String espIdf; + + private String compiled; + private OffsetDateTime lastModified; public String getVersion() { diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/StorageService.java b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/StorageService.java index fc113a8..e42c7bb 100644 --- a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/StorageService.java +++ b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/StorageService.java @@ -1,7 +1,13 @@ package com.rdkr.tide_display.backend.gcp; +import java.io.IOException; import java.util.List; +import org.springframework.web.multipart.MultipartFile; public interface StorageService { List getFirmwareVersions(); + + byte[] download(String version, String filename); + + String upload(MultipartFile file) throws IOException; } diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/bean/GoogleCloudBean.java b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/bean/GoogleCloudBean.java new file mode 100644 index 0000000..ff4f4c6 --- /dev/null +++ b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/bean/GoogleCloudBean.java @@ -0,0 +1,15 @@ +package com.rdkr.tide_display.backend.gcp.bean; + +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +@Component +public class GoogleCloudBean { + + @Bean + public Storage getStorage(){ + return StorageOptions.getDefaultInstance().getService(); + } +} diff --git a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/service/StorageServiceImpl.java b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/service/StorageServiceImpl.java index 4f437e5..c48940a 100644 --- a/backend/src/main/java/com/rdkr/tide_display/backend/gcp/service/StorageServiceImpl.java +++ b/backend/src/main/java/com/rdkr/tide_display/backend/gcp/service/StorageServiceImpl.java @@ -1,35 +1,56 @@ package com.rdkr.tide_display.backend.gcp.service; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage.BlobListOption; -import com.google.cloud.storage.StorageOptions; import com.rdkr.tide_display.backend.gcp.Firmware; import com.rdkr.tide_display.backend.gcp.StorageService; +import jakarta.annotation.Nullable; +import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import lombok.RequiredArgsConstructor; import lombok.val; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; @Service +@RequiredArgsConstructor public class StorageServiceImpl implements StorageService { + final Storage storage; + final String bucketName = "ec804bd8-38d2-4fcc-8f04-5addb55b3c90"; + + private String extract(byte[] bytes, int start, int length) { + val result = new StringBuilder(); + for (var pos = start; pos < Math.min(start + length, bytes.length); pos++) { + result.append((char) bytes[pos]); + } + return result.toString().trim(); + } + @Override public List getFirmwareVersions() { val result = new ArrayList(); - val storage = StorageOptions.getDefaultInstance().getService(); - String bucketName = "ec804bd8-38d2-4fcc-8f04-5addb55b3c90"; val bucket = storage.list(bucketName, BlobListOption.currentDirectory(), BlobListOption.prefix("firmware/")); for (val directory : bucket.iterateAll()) { val files = storage.list(bucketName, BlobListOption.currentDirectory(), BlobListOption.prefix(directory.getName())); for (val file : files.iterateAll()) { val name = file.getName(); if (name.endsWith(".bin")) { + val bytes = file.getContent(); + val flashSizeCode = bytes[3] >> 4; + val flashSize = (int) Math.pow(2, flashSizeCode); + val espIdf = extract(bytes, 144, 30); + val compiled = extract(bytes, 128, 15) + " " + extract(bytes, 112, 15); val version = directory.getName().replace("firmware/", "").replace("/", "").split("\\."); if (version.length == 3) { int major = Integer.parseInt(version[0]); int minor = Integer.parseInt(version[1]); int patch = Integer.parseInt(version[2]); - result.add(new Firmware(major, minor, patch, "/files/" + major + "." + minor + "." + patch + "/firmware.bin", file.getEtag(), file.getUpdateTimeOffsetDateTime())); + result.add( + new Firmware(major, minor, patch, "/files/" + major + "." + minor + "." + patch + "/firmware.bin", + file.getEtag(), flashSize, espIdf, compiled, file.getUpdateTimeOffsetDateTime())); } } } @@ -46,4 +67,21 @@ public class StorageServiceImpl implements StorageService { return result.reversed(); } + + @Override + public byte[] download(String version, String filename) { + val blob = storage.get(bucketName, "firmware/" + version + "/" + filename); + return blob != null ? blob.getContent() : null; + } + + @Override + public @Nullable String upload(MultipartFile file) throws IOException { + val bytes = file.getBytes(); + if (bytes[0] != -23) { + return null; + } + val version = extract(bytes, 48, 30); + storage.create(BlobInfo.newBuilder(bucketName, "firmware/" + version + "/firmware.bin").build(), bytes); + return version; + } }