mirror of
https://github.com/pcvolkmer/grz-metadata-processor.git
synced 2025-07-01 12:02:54 +00:00
feat: auto import files
This commit is contained in:
@ -32,6 +32,7 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.flywaydb:flyway-database-postgresql")
|
implementation("org.flywaydb:flyway-database-postgresql")
|
||||||
implementation("org.flywaydb:flyway-mysql")
|
implementation("org.flywaydb:flyway-mysql")
|
||||||
|
implementation("org.springframework.integration:spring-integration-file")
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
developmentOnly("org.springframework.boot:spring-boot-docker-compose")
|
developmentOnly("org.springframework.boot:spring-boot-docker-compose")
|
||||||
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
|
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
package dev.pcvolkmer.onco.grzmetadataprocessor.config
|
||||||
|
|
||||||
|
import dev.pcvolkmer.onco.grzmetadataprocessor.data.File
|
||||||
|
import dev.pcvolkmer.onco.grzmetadataprocessor.data.FileRepository
|
||||||
|
import dev.pcvolkmer.onco.grzmetadataprocessor.data.FileType
|
||||||
|
import org.apache.tomcat.util.buf.HexUtils
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.integration.dsl.IntegrationFlow
|
||||||
|
import org.springframework.integration.dsl.Pollers
|
||||||
|
import org.springframework.integration.file.dsl.Files
|
||||||
|
import org.springframework.util.Assert
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.security.DigestInputStream
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.time.Duration
|
||||||
|
import kotlin.io.path.*
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
import kotlin.time.toJavaDuration
|
||||||
|
|
||||||
|
@ConfigurationProperties(AppSourceFsProperties.NAME)
|
||||||
|
data class AppSourceFsProperties(
|
||||||
|
val directory: Path? = null,
|
||||||
|
val pollDelay: Duration = 1.minutes.toJavaDuration(),
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val NAME = "app.source.fs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(AppSourceFsProperties::class)
|
||||||
|
class AppIntegrationConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
name = ["app.source.fs.directory"]
|
||||||
|
)
|
||||||
|
fun fileInputFlow(
|
||||||
|
applicationFsProperties: AppSourceFsProperties,
|
||||||
|
fileRepository: FileRepository
|
||||||
|
): IntegrationFlow {
|
||||||
|
val sourceDirectory = applicationFsProperties.directory
|
||||||
|
Assert.state(null != sourceDirectory && sourceDirectory.isDirectory()) {
|
||||||
|
"Property 'app.source.fs.active' is 'true' but source directory is not available"
|
||||||
|
}
|
||||||
|
return IntegrationFlow
|
||||||
|
.from(
|
||||||
|
Files.inboundAdapter(sourceDirectory!!.toFile()).useWatchService(true)
|
||||||
|
)
|
||||||
|
.log()
|
||||||
|
.handle { msg ->
|
||||||
|
val path = Path(msg.payload.toString())
|
||||||
|
val relativePath = applicationFsProperties.directory.relativize(Path(msg.payload.toString())).pathString
|
||||||
|
fileRepository.findByFilePath(relativePath).ifPresentOrElse({
|
||||||
|
// File already present
|
||||||
|
}, {
|
||||||
|
fileRepository.save(
|
||||||
|
File(
|
||||||
|
filePath = relativePath,
|
||||||
|
labDataId = null,
|
||||||
|
fileChecksum = calcFileChecksum(path),
|
||||||
|
fileSizeInBytes = path.fileSize(),
|
||||||
|
fileType = getFileType(path),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calcFileChecksum(path: Path): String {
|
||||||
|
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val digestInputStream = DigestInputStream(path.inputStream(), messageDigest)
|
||||||
|
digestInputStream.readAllBytes()
|
||||||
|
return HexUtils.toHexString(messageDigest.digest())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileType(path: Path): FileType? {
|
||||||
|
return if (path.toString().lowercase().endsWith(".fastq.gz")) {
|
||||||
|
FileType.FASTQ
|
||||||
|
} else if (path.toString().lowercase().endsWith(".bed")) {
|
||||||
|
FileType.BED
|
||||||
|
} else if (path.toString().lowercase().endsWith(".bam")) {
|
||||||
|
FileType.BAM
|
||||||
|
} else if (path.toString().lowercase().endsWith(".vcf")) {
|
||||||
|
FileType.VCF
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,8 +17,8 @@ data class File(
|
|||||||
val labDataId: Long?,
|
val labDataId: Long?,
|
||||||
val filePath: String? = null,
|
val filePath: String? = null,
|
||||||
val fileType: FileType? = null,
|
val fileType: FileType? = null,
|
||||||
var fileChecksum: String? = null,
|
var fileChecksum: String = "",
|
||||||
var fileSizeInBytes: Long? = null,
|
var fileSizeInBytes: Long = 0,
|
||||||
) {
|
) {
|
||||||
fun calcFileChecksum(): String {
|
fun calcFileChecksum(): String {
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
@ -26,7 +26,6 @@ data class File(
|
|||||||
}
|
}
|
||||||
val path = Path.of(filePath)
|
val path = Path.of(filePath)
|
||||||
val messageDigest = MessageDigest.getInstance("SHA-256")
|
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
|
|
||||||
val digestInputStream = DigestInputStream(path.inputStream(), messageDigest)
|
val digestInputStream = DigestInputStream(path.inputStream(), messageDigest)
|
||||||
digestInputStream.readAllBytes()
|
digestInputStream.readAllBytes()
|
||||||
return HexUtils.toHexString(messageDigest.digest())
|
return HexUtils.toHexString(messageDigest.digest())
|
||||||
@ -50,4 +49,5 @@ enum class FileType(val value: String) {
|
|||||||
interface FileRepository : CrudRepository<File, Long> {
|
interface FileRepository : CrudRepository<File, Long> {
|
||||||
fun findByLabDataId(labDataId: Long): MutableList<File>
|
fun findByLabDataId(labDataId: Long): MutableList<File>
|
||||||
fun findByLabDataIdIsNull(): List<File>
|
fun findByLabDataIdIsNull(): List<File>
|
||||||
|
fun findByFilePath(filePath: String): Optional<File>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE tbl_file DROP INDEX file_checksum;
|
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE tbl_file DROP CONSTRAINT tbl_file_file_checksum_key;
|
@ -40,7 +40,7 @@
|
|||||||
<label class="optional">
|
<label class="optional">
|
||||||
Zugehörige Probe (Einsendenummer)
|
Zugehörige Probe (Einsendenummer)
|
||||||
<select name="labDataId">
|
<select name="labDataId">
|
||||||
<option>Keine Zuordnung</option>
|
<option value="">Keine Zuordnung</option>
|
||||||
<option th:each="labData : ${labDatas}" th:selected="${file.labDataId == labData.id}" th:value="${labData.id}" th:text="${labData.einsendenummer}"></option>
|
<option th:each="labData : ${labDatas}" th:selected="${file.labDataId == labData.id}" th:value="${labData.id}" th:text="${labData.einsendenummer}"></option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
@ -50,10 +50,10 @@
|
|||||||
<label>
|
<label>
|
||||||
Dateityp
|
Dateityp
|
||||||
<select name="fileType">
|
<select name="fileType">
|
||||||
<option th:selected="${file.fileType.value == 'bam'}">BAM</option>
|
<option th:selected="${file.fileType != null and file.fileType.value == 'bam'}" value="BAM">BAM</option>
|
||||||
<option th:selected="${file.fileType.value == 'vcf'}">Vcf</option>
|
<option th:selected="${file.fileType != null and file.fileType.value == 'vcf'}" value="VCF">Vcf</option>
|
||||||
<option th:selected="${file.fileType.value == 'bed'}">BED</option>
|
<option th:selected="${file.fileType != null and file.fileType.value == 'bed'}" value="BED">BED</option>
|
||||||
<option th:selected="${file.fileType.value == 'fastq'}">FASTQ</option>
|
<option th:selected="${file.fileType != null and file.fileType.value == 'fastq'}" value="FASTQ" >FASTQ</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user