-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add maintenance service for fixing top-left dimension values DE…
- Loading branch information
Showing
9 changed files
with
368 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/MaintenanceRequests.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.knora.webapi.slice.admin.api.model | ||
|
||
import eu.timepit.refined.api.Refined | ||
import eu.timepit.refined.api.RefinedTypeOps | ||
import eu.timepit.refined.numeric.Positive | ||
import eu.timepit.refined.refineV | ||
import eu.timepit.refined.string.MatchesRegex | ||
import zio.Chunk | ||
import zio.json.DeriveJsonCodec | ||
import zio.json.JsonCodec | ||
import zio.json.interop.refined._ | ||
|
||
import dsp.valueobjects.Project.Shortcode | ||
|
||
object MaintenanceRequests { | ||
|
||
type AssetId = String Refined MatchesRegex["^[a-zA-Z0-9-_]{4,}$"] | ||
object AssetId extends RefinedTypeOps[AssetId, String] { | ||
implicit val codec: JsonCodec[AssetId] = JsonCodec[String].transformOrFail(AssetId.from, _.toString) | ||
} | ||
|
||
final case class Dimensions(width: Int Refined Positive, height: Int Refined Positive) | ||
|
||
object Dimensions { | ||
def from(width: Int, height: Int): Either[String, Dimensions] = for { | ||
widthRefined <- refineV[Positive](width) | ||
heightRefined <- refineV[Positive](height) | ||
} yield Dimensions(widthRefined, heightRefined) | ||
|
||
implicit val codec: JsonCodec[Dimensions] = DeriveJsonCodec.gen[Dimensions] | ||
} | ||
|
||
case class ReportAsset(id: AssetId, dimensions: Dimensions) | ||
|
||
object ReportAsset { | ||
implicit val codec: JsonCodec[ReportAsset] = DeriveJsonCodec.gen[ReportAsset] | ||
} | ||
|
||
case class ProjectWithBakFiles(id: Shortcode, assetIds: Chunk[ReportAsset]) | ||
|
||
object ProjectWithBakFiles { | ||
implicit val codec: JsonCodec[ProjectWithBakFiles] = DeriveJsonCodec.gen[ProjectWithBakFiles] | ||
} | ||
|
||
case class ProjectsWithBakfilesReport(projects: Chunk[ProjectWithBakFiles]) | ||
|
||
object ProjectsWithBakfilesReport { | ||
implicit val codec: JsonCodec[ProjectsWithBakfilesReport] = DeriveJsonCodec.gen[ProjectsWithBakfilesReport] | ||
} | ||
} |
145 changes: 145 additions & 0 deletions
145
webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/MaintenanceService.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/* | ||
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.knora.webapi.slice.admin.api.service | ||
|
||
import zio.Task | ||
import zio.ZIO | ||
import zio.ZLayer | ||
import zio.macros.accessible | ||
import zio.stream.ZStream | ||
|
||
import dsp.valueobjects.Project.Shortcode | ||
import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.AssetId | ||
import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.Dimensions | ||
import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.ProjectsWithBakfilesReport | ||
import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.ReportAsset | ||
import org.knora.webapi.slice.admin.domain.model.KnoraProject | ||
import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo | ||
import org.knora.webapi.slice.admin.domain.service.ProjectADMService | ||
import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper | ||
import org.knora.webapi.slice.resourceinfo.domain.InternalIri | ||
import org.knora.webapi.store.triplestore.api.TriplestoreService | ||
import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Select | ||
import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update | ||
|
||
@accessible | ||
trait MaintenanceService { | ||
def fixTopLeftDimensions(report: ProjectsWithBakfilesReport): Task[Unit] | ||
|
||
def doNothing: Task[Unit] | ||
} | ||
|
||
final case class MaintenanceServiceLive( | ||
projectRepo: KnoraProjectRepo, | ||
triplestoreService: TriplestoreService, | ||
mapper: PredicateObjectMapper | ||
) extends MaintenanceService { | ||
override def doNothing: Task[Unit] = ZIO.unit | ||
override def fixTopLeftDimensions(report: ProjectsWithBakfilesReport): Task[Unit] = | ||
ZStream | ||
.fromIterable(report.projects) | ||
.flatMap { project => | ||
ZStream | ||
.fromIterable(project.assetIds) | ||
.flatMapPar(5)(assetId => | ||
ZStream.fromZIOOption( | ||
fixAsset(project.id, assetId) | ||
// None.type errors are just a sign that the assetId should be ignored. Some.type errors are real errors. | ||
.tapSomeError { case Some(e) => ZIO.logError(s"Error while processing ${project.id}, $assetId: $e") } | ||
// We have logged real errors above, from here on out ignore all errors so that the stream can continue. | ||
.orElseFail(None) | ||
) | ||
) | ||
} | ||
.runDrain | ||
|
||
private def fixAsset(shortcode: Shortcode, asset: ReportAsset): ZIO[Any, Option[Throwable], Unit] = | ||
for { | ||
project <- projectRepo.findByShortcode(shortcode).some | ||
stillImageFileValueIri <- checkDimensions(project, asset) | ||
_ <- transposeImageDimensions(project, stillImageFileValueIri) | ||
} yield () | ||
|
||
private def checkDimensions( | ||
project: KnoraProject, | ||
asset: ReportAsset | ||
): ZIO[Any, Option[Throwable], InternalIri] = | ||
for { | ||
res <- getDimensionAndStillImageValueIri(project, asset) | ||
(dim, iri) = res | ||
_ <- ZIO.when(dim == asset.dimensions)(ZIO.fail(None)) | ||
} yield iri | ||
|
||
private def getDimensionAndStillImageValueIri( | ||
project: KnoraProject, | ||
asset: ReportAsset | ||
): ZIO[Any, Option[Throwable], (Dimensions, InternalIri)] = | ||
for { | ||
result <- triplestoreService.query(checkDimensionsQuery(project, asset.id)).asSomeError | ||
rowMap <- ZIO.fromOption(result.results.bindings.headOption.map(_.rowMap)) | ||
iri <- ZIO.fromOption(rowMap.get("valueIri")).map(InternalIri) | ||
width <- ZIO.fromOption(rowMap.get("dimX").flatMap(_.toIntOption)) | ||
height <- ZIO.fromOption(rowMap.get("dimY").flatMap(_.toIntOption)) | ||
dim <- ZIO.fromOption(Dimensions.from(width, height).toOption) | ||
} yield (dim, iri) | ||
|
||
private def checkDimensionsQuery(project: KnoraProject, assetId: AssetId) = { | ||
val projectGraph = ProjectADMService.projectDataNamedGraphV2(project) | ||
Select(s""" | ||
|PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> | ||
|PREFIX knora-base: <http://www.knora.org/ontology/knora-base#> | ||
| | ||
|SELECT ?valueIri ?dimX ?dimY | ||
| FROM <${projectGraph.value}> | ||
|WHERE { | ||
| ?valueIri a knora-base:StillImageFileValue . | ||
| ?valueIri knora-base:internalFilename ?filename . | ||
| FILTER (strstarts(str(?filename), "$assetId")) | ||
| ?valueIri knora-base:dimX ?dimX . | ||
| ?valueIri knora-base:dimY ?dimY . | ||
|} | ||
|""".stripMargin) | ||
} | ||
|
||
private def transposeImageDimensions( | ||
project: KnoraProject, | ||
stillImageFileValueIri: InternalIri | ||
): ZIO[Any, Option[Throwable], Unit] = | ||
triplestoreService.query(transposeUpdate(project, stillImageFileValueIri)).asSomeError | ||
|
||
private def transposeUpdate(project: KnoraProject, stillImageFileValueIri: InternalIri) = { | ||
val projectGraph = ProjectADMService.projectDataNamedGraphV2(project) | ||
Update( | ||
s""" | ||
|PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> | ||
|PREFIX knora-base: <http://www.knora.org/ontology/knora-base#> | ||
| | ||
|WITH <${projectGraph.value}> | ||
|DELETE | ||
|{ | ||
| ?r knora-base:dimX ?oldX . | ||
| ?r knora-base:dimY ?oldY . | ||
|} | ||
|INSERT | ||
|{ | ||
| ?r knora-base:dimX ?oldY . | ||
| ?r knora-base:dimY ?oldX . | ||
|} | ||
|WHERE | ||
|{ | ||
| BIND (<${stillImageFileValueIri.value}> AS ?r) | ||
| ?r knora-base:dimX ?oldX . | ||
| ?r knora-base:dimY ?oldY . | ||
|} | ||
|""".stripMargin | ||
) | ||
} | ||
} | ||
|
||
object MaintenanceServiceLive { | ||
|
||
val layer = ZLayer.derive[MaintenanceServiceLive] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.