add file up-/download

Signed-off-by: Peter Siegmund <peter@rdkr.com>
This commit is contained in:
Peter Siegmund
2024-05-30 22:02:42 +02:00
parent 78d2bc7b71
commit bc56d12ad6
6 changed files with 122 additions and 6 deletions

View File

@@ -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<byte[]> 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;
}
}

View File

@@ -27,7 +27,7 @@ public class FirmwareServiceImpl implements FirmwareService {
public Optional<Firmware> getFirmwareVersion(String version) {
return
storageService.getFirmwareVersions().stream()
//.filter(firmware -> firmware.version().equals(version))
.filter(firmware -> firmware.getVersion().equals(version))
.findFirst();
}
}

View File

@@ -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() {

View File

@@ -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<Firmware> getFirmwareVersions();
byte[] download(String version, String filename);
String upload(MultipartFile file) throws IOException;
}

View File

@@ -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();
}
}

View File

@@ -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<Firmware> getFirmwareVersions() {
val result = new ArrayList<Firmware>();
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;
}
}