Skip to content

Commit da3a817

Browse files
authored
Extend storage service (#903)
1 parent e2b86e7 commit da3a817

File tree

4 files changed

+60
-4
lines changed

4 files changed

+60
-4
lines changed

backend/src/main/kotlin/hu/bme/sch/cmsch/service/FilesystemStorageService.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import org.springframework.web.multipart.MultipartFile
1010
import org.springframework.web.util.UriComponentsBuilder
1111
import java.io.File
1212
import java.io.IOException
13+
import java.io.InputStream
1314
import java.nio.file.Files
1415
import java.nio.file.Path
1516
import java.nio.file.Paths
17+
import java.nio.file.StandardCopyOption
1618
import java.util.*
1719
import kotlin.io.path.deleteIfExists
1820
import kotlin.io.path.exists
1921
import kotlin.io.path.fileSize
22+
import kotlin.io.path.inputStream
2023

2124
@Service
2225
@ConditionalOnExpression("'\${hu.bme.sch.cmsch.startup.storage-implementation}'.equalsIgnoreCase(T(hu.bme.sch.cmsch.config.StorageImplementation).FILESYSTEM.name)")
@@ -53,23 +56,32 @@ class FilesystemStorageService(
5356
return Optional.empty()
5457
}
5558

56-
5759
override fun saveNamedObject(path: String, name: String, file: MultipartFile): Optional<String> {
5860
if (file.isEmpty || file.contentType == null)
5961
return Optional.empty()
6062

61-
return saveNamedObject(path, name, file.contentType ?: defaultContentType, file.bytes)
63+
return saveNamedObject(path, name, file.bytes.inputStream())
6264
}
6365

64-
override fun saveNamedObject(path: String, name: String, contentType: String, data: ByteArray): Optional<String> {
66+
override fun saveNamedObject(
67+
path: String,
68+
name: String,
69+
contentType: String,
70+
data: ByteArray
71+
): Optional<String> = saveNamedObject(path, name, data.inputStream())
72+
73+
override fun saveNamedObject(path: String, name: String, filesystemPath: Path): Optional<String> =
74+
saveNamedObject(path, name, filesystemPath.inputStream())
75+
76+
fun saveNamedObject(path: String, name: String, data: InputStream): Optional<String> {
6577
val storagePath = getFileStoragePath()
6678
val dir = File(storagePath, path)
6779
dir.mkdirs()
6880

6981
try {
7082
val filePath = getSanitizedPath(getObjectName(path, name))
7183
if (filePath != null) {
72-
Files.write(filePath, data)
84+
Files.copy(data, filePath, StandardCopyOption.REPLACE_EXISTING)
7385
return Optional.of(constructObjectUrl(path, name))
7486
}
7587
} catch (e: IOException) {

backend/src/main/kotlin/hu/bme/sch/cmsch/service/S3StorageService.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import software.amazon.awssdk.regions.Region
1212
import software.amazon.awssdk.services.s3.S3Client
1313
import software.amazon.awssdk.services.s3.model.*
1414
import java.net.URI
15+
import java.nio.file.Files
16+
import java.nio.file.Path
1517
import java.util.*
18+
import kotlin.io.path.exists
19+
import kotlin.io.path.isReadable
1620

1721
@Service
1822
@ConditionalOnExpression("'\${hu.bme.sch.cmsch.startup.storage-implementation}'.equalsIgnoreCase(T(hu.bme.sch.cmsch.config.StorageImplementation).S3.name)")
@@ -123,6 +127,30 @@ class S3StorageService(
123127
return Optional.empty()
124128
}
125129

130+
override fun saveNamedObject(
131+
path: String,
132+
name: String,
133+
filesystemPath: Path
134+
): Optional<String> {
135+
if (!filesystemPath.exists() || !filesystemPath.isReadable()) return Optional.empty()
136+
137+
try {
138+
val fullName = getObjectName(path, name)
139+
val contentType = runCatching { Files.probeContentType(filesystemPath) }.getOrNull()
140+
141+
val request = PutObjectRequest.builder()
142+
.bucket(startupPropertyConfig.s3Bucket)
143+
.key(fullName)
144+
.contentType(contentType)
145+
.build()
146+
s3.putObject(request, RequestBody.fromFile(filesystemPath))
147+
return Optional.of(getS3PublicUrl(fullName))
148+
} catch (error: Throwable) {
149+
log.error("Failed to upload file {} {}/{} to S3 bucket", filesystemPath, path, name, error)
150+
}
151+
return Optional.empty()
152+
}
153+
126154
override fun readObject(fullName: String): Optional<ByteArray> {
127155
try {
128156
val request = GetObjectRequest.builder()

backend/src/main/kotlin/hu/bme/sch/cmsch/service/StorageService.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory
55
import org.springframework.core.io.ClassPathResource
66
import org.springframework.http.MediaType
77
import org.springframework.web.multipart.MultipartFile
8+
import java.nio.file.Path
89
import java.util.*
910

1011
interface StorageService {
@@ -41,10 +42,15 @@ interface StorageService {
4142
data: ByteArray
4243
): Optional<String> = saveNamedObject(path, generateName(fileName), contentType, data)
4344

45+
fun saveObjectWithRandomName(path: String, fileName: String, filesystemPath: Path): Optional<String> =
46+
saveNamedObject(path, generateName(fileName), filesystemPath)
47+
4448
fun saveNamedObject(path: String, name: String, file: MultipartFile): Optional<String>
4549

4650
fun saveNamedObject(path: String, name: String, contentType: String, data: ByteArray): Optional<String>
4751

52+
fun saveNamedObject(path: String, name: String, filesystemPath: Path): Optional<String>
53+
4854
fun readObject(path: String, name: String): Optional<ByteArray> = readObject(getObjectName(path, name))
4955

5056
fun readObject(fullName: String): Optional<ByteArray>

backend/src/main/kotlin/hu/bme/sch/cmsch/util/Utility.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import org.springframework.core.io.ClassPathResource
1515
import org.springframework.security.core.Authentication
1616
import org.springframework.stereotype.Component
1717
import org.springframework.stereotype.Service
18+
import org.springframework.web.reactive.function.client.WebClient
19+
import org.springframework.web.reactive.function.client.awaitBody
20+
import org.springframework.web.reactive.function.client.bodyToMono
21+
import java.io.InputStream
1822
import java.net.URLEncoder
1923
import java.nio.charset.StandardCharsets
2024
import java.util.*
@@ -55,6 +59,12 @@ fun Map<String, String>.urlEncode(): String = this.entries.joinToString("&") {
5559
URLEncoder.encode(it.key, StandardCharsets.UTF_8) + "=" + URLEncoder.encode(it.value, StandardCharsets.UTF_8)
5660
}
5761

62+
fun fetchFile(url: String): Result<ByteArray?> = runCatching {
63+
WebClient.create()
64+
.get().uri(url)
65+
.retrieve().bodyToMono<ByteArray>().block()
66+
}
67+
5868
private val markdownExtensions = listOf(TablesExtension.create())
5969
private val markdownParser: Parser = Parser.builder().extensions(markdownExtensions).build()
6070
private val markdownRenderer = HtmlRenderer.builder().extensions(markdownExtensions).build()

0 commit comments

Comments
 (0)