Skip to content

Commit 6096fe4

Browse files
authored
Virtual Remote Datasets (#8657)
1 parent b7af87a commit 6096fe4

17 files changed

+619
-10
lines changed

app/controllers/UserTokenController.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ class UserTokenController @Inject()(datasetDAO: DatasetDAO,
9696
answer <- accessRequest.resourceType match {
9797
case AccessResourceType.datasource =>
9898
handleDataSourceAccess(accessRequest.resourceId, accessRequest.mode, userBox)(sharingTokenAccessCtx)
99+
case AccessResourceType.dataset =>
100+
handleDataSetAccess(accessRequest.resourceId.directoryName, accessRequest.mode, userBox)(
101+
sharingTokenAccessCtx)
99102
case AccessResourceType.tracing =>
100103
handleTracingAccess(accessRequest.resourceId.directoryName, accessRequest.mode, userBox, token)
101104
case AccessResourceType.annotation =>
@@ -158,6 +161,34 @@ class UserTokenController @Inject()(datasetDAO: DatasetDAO,
158161
}
159162
}
160163

164+
def handleDataSetAccess(id: String, mode: AccessMode.Value, userBox: Box[User])(
165+
implicit ctx: DBAccessContext): Fox[UserAccessAnswer] = {
166+
167+
def tryRead: Fox[UserAccessAnswer] =
168+
for {
169+
datasetId <- ObjectId.fromString(id)
170+
datasetBox <- datasetDAO.findOne(datasetId).shiftBox
171+
} yield
172+
datasetBox match {
173+
case Full(_) => UserAccessAnswer(granted = true)
174+
case _ => UserAccessAnswer(granted = false, Some("No read access on dataset"))
175+
}
176+
177+
def tryWrite: Fox[UserAccessAnswer] =
178+
for {
179+
datasetId <- ObjectId.fromString(id)
180+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> "dataset.notFound"
181+
user <- userBox.toFox ?~> "auth.token.noUser"
182+
isAllowed <- datasetService.isEditableBy(dataset, Some(user))
183+
} yield UserAccessAnswer(isAllowed)
184+
185+
mode match {
186+
case AccessMode.read => tryRead
187+
case AccessMode.write => tryWrite
188+
case _ => Fox.successful(UserAccessAnswer(granted = false, Some("invalid access token")))
189+
}
190+
}
191+
161192
private def handleTracingAccess(tracingId: String,
162193
mode: AccessMode,
163194
userBox: Box[User],

app/controllers/WKRemoteDataStoreController.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,17 @@ class WKRemoteDataStoreController @Inject()(
259259
}
260260
}
261261

262+
def getDataSource(name: String, key: String, datasetId: ObjectId): Action[AnyContent] =
263+
Action.async { implicit request =>
264+
dataStoreService.validateAccess(name, key) { _ =>
265+
for {
266+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext)
267+
dataSource <- datasetService.fullDataSourceFor(dataset)
268+
} yield Ok(Json.toJson(dataSource))
269+
}
270+
271+
}
272+
262273
def jobExportProperties(name: String, key: String, jobId: ObjectId): Action[AnyContent] = Action.async {
263274
implicit request =>
264275
dataStoreService.validateAccess(name, key) { _ =>

app/models/annotation/AnnotationService.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,7 @@ class AnnotationService @Inject()(
804804
id = annotation._id,
805805
annotationLayers = annotation.annotationLayers,
806806
datasetDirectoryName = dataset.directoryName,
807+
datasetId = dataset._id,
807808
organizationId = organization._id,
808809
dataStoreUrl = dataStore.publicUrl,
809810
tracingStoreUrl = tracingStore.publicUrl,

app/models/dataset/Dataset.scala

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import com.scalableminds.util.geometry.{BoundingBox, Vec3Double, Vec3Int}
55
import com.scalableminds.util.objectid.ObjectId
66
import com.scalableminds.util.time.Instant
77
import com.scalableminds.util.tools.{Fox, JsonHelper}
8+
import com.scalableminds.webknossos.datastore.dataformats.MagLocator
9+
import com.scalableminds.webknossos.datastore.datareaders.AxisOrder
810
import com.scalableminds.webknossos.datastore.helpers.DataSourceMagInfo
911
import com.scalableminds.webknossos.datastore.models.{LengthUnit, VoxelSize}
1012
import com.scalableminds.webknossos.datastore.models.datasource.DatasetViewConfiguration.DatasetViewConfiguration
@@ -17,19 +19,23 @@ import com.scalableminds.webknossos.datastore.models.datasource.{
1719
Category,
1820
CoordinateTransformation,
1921
CoordinateTransformationType,
22+
DataFormat,
2023
DataSourceId,
2124
ElementClass,
2225
LayerAttachment,
26+
LayerAttachmentDataformat,
2327
LayerAttachmentType,
2428
ThinPlateSplineCorrespondences,
25-
DataLayerLike => DataLayer
29+
DataLayerLike => DataLayer,
30+
DatasetLayerAttachments => AttachmentWrapper
2631
}
2732
import com.scalableminds.webknossos.datastore.services.MagPathInfo
2833
import com.scalableminds.webknossos.schema.Tables._
2934
import controllers.DatasetUpdateParameters
3035

3136
import javax.inject.Inject
3237
import models.organization.OrganizationDAO
38+
import net.liftweb.common.Box.tryo
3339
import play.api.i18n.{Messages, MessagesProvider}
3440
import play.api.libs.json._
3541
import slick.dbio.DBIO
@@ -40,6 +46,7 @@ import slick.lifted.Rep
4046
import slick.sql.SqlAction
4147
import utils.sql.{SQLDAO, SimpleSQLDAO, SqlClient, SqlToken}
4248

49+
import java.net.URI
4350
import scala.concurrent.ExecutionContext
4451

4552
case class Dataset(_id: ObjectId,
@@ -841,6 +848,30 @@ class DatasetMagsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionConte
841848
magInfos <- rowsToMagInfos(rows)
842849
} yield magInfos
843850

851+
def parseMagLocator(row: DatasetMagsRow): Fox[MagLocator] =
852+
for {
853+
mag <- parseMag(row.mag)
854+
axisOrderParsed = row.axisorder match {
855+
case Some(axisOrder) => JsonHelper.parseAs[AxisOrder](axisOrder).toOption
856+
case None => None
857+
}
858+
} yield
859+
MagLocator(
860+
mag,
861+
row.path,
862+
None,
863+
axisOrderParsed,
864+
row.channelindex,
865+
row.credentialid
866+
)
867+
868+
def findAllByDatasetId(datasetId: ObjectId): Fox[Seq[(String, MagLocator)]] =
869+
for {
870+
// We assume non-WKW Datasets here (WKW Resolutions are not handled)
871+
rows <- run(q"""SELECT * FROM webknossos.dataset_mags WHERE _dataset = $datasetId""".as[DatasetMagsRow])
872+
mags <- Fox.combined(rows.map(parseMagLocator))
873+
} yield rows.map(r => r.datalayername).zip(mags)
874+
844875
}
845876

846877
class DatasetLayerDAO @Inject()(sqlClient: SqlClient,
@@ -855,7 +886,7 @@ class DatasetLayerDAO @Inject()(sqlClient: SqlClient,
855886
category <- Category.fromString(row.category).toFox ?~> "Could not parse Layer Category"
856887
boundingBox <- BoundingBox
857888
.fromSQL(parseArrayLiteral(row.boundingbox).map(_.toInt))
858-
.toFox ?~> "Could not parse boundingbox"
889+
.toFox ?~> "Could not parse bounding box"
859890
elementClass <- ElementClass.fromString(row.elementclass).toFox ?~> "Could not parse Layer ElementClass"
860891
mags <- datasetMagsDAO.findMagForLayer(datasetId, row.name) ?~> "Could not find mag for layer"
861892
defaultViewConfigurationOpt <- Fox.runOptional(row.defaultviewconfiguration)(
@@ -867,6 +898,10 @@ class DatasetLayerDAO @Inject()(sqlClient: SqlClient,
867898
coordinateTransformationsOpt = if (coordinateTransformations.isEmpty) None else Some(coordinateTransformations)
868899
additionalAxes <- datasetLayerAdditionalAxesDAO.findAllForDatasetAndDataLayerName(datasetId, row.name)
869900
additionalAxesOpt = if (additionalAxes.isEmpty) None else Some(additionalAxes)
901+
attachments <- datasetLayerAttachmentsDAO.findAllForDatasetAndDataLayerName(datasetId, row.name)
902+
attachmentsOpt = if (attachments.isEmpty) None else Some(attachments)
903+
904+
dataFormat = row.dataformat.flatMap(df => DataFormat.fromString(df))
870905
} yield {
871906
category match {
872907
case Category.segmentation =>
@@ -883,7 +918,10 @@ class DatasetLayerDAO @Inject()(sqlClient: SqlClient,
883918
defaultViewConfigurationOpt,
884919
adminViewConfigurationOpt,
885920
coordinateTransformationsOpt,
886-
additionalAxesOpt
921+
additionalAxesOpt,
922+
numChannels = row.numchannels,
923+
dataFormat = dataFormat,
924+
attachments = attachmentsOpt
887925
))
888926
case Category.color =>
889927
Fox.successful(
@@ -896,7 +934,10 @@ class DatasetLayerDAO @Inject()(sqlClient: SqlClient,
896934
defaultViewConfigurationOpt,
897935
adminViewConfigurationOpt,
898936
coordinateTransformationsOpt,
899-
additionalAxesOpt
937+
additionalAxesOpt,
938+
numChannels = row.numchannels,
939+
dataFormat = dataFormat,
940+
attachments = attachmentsOpt
900941
))
901942
case _ => Fox.failure(s"Could not match dataset layer with category $category")
902943
}
@@ -907,7 +948,7 @@ class DatasetLayerDAO @Inject()(sqlClient: SqlClient,
907948
def findAllForDataset(datasetId: ObjectId): Fox[List[DataLayer]] =
908949
for {
909950
rows <- run(q"""SELECT _dataset, name, category, elementClass, boundingBox, largestSegmentId, mappings,
910-
defaultViewConfiguration, adminViewConfiguration
951+
defaultViewConfiguration, adminViewConfiguration, numChannels, dataFormat
911952
FROM webknossos.dataset_layers
912953
WHERE _dataset = $datasetId
913954
ORDER BY name""".as[DatasetLayersRow])
@@ -1015,6 +1056,39 @@ class DatasetLastUsedTimesDAO @Inject()(sqlClient: SqlClient)(implicit ec: Execu
10151056

10161057
class DatasetLayerAttachmentsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext)
10171058
extends SimpleSQLDAO(sqlClient) {
1059+
1060+
def parseRow(row: DatasetLayerAttachmentsRow): Fox[LayerAttachment] =
1061+
for {
1062+
dataFormat <- LayerAttachmentDataformat.fromString(row.dataformat).toFox ?~> "Could not parse data format"
1063+
uri <- tryo(new URI(row.path)).toFox
1064+
} yield LayerAttachment(row.name, uri, dataFormat)
1065+
1066+
def parseAttachments(rows: List[DatasetLayerAttachmentsRow]): Fox[AttachmentWrapper] =
1067+
for {
1068+
meshFiles <- Fox.serialCombined(rows.filter(_.`type` == LayerAttachmentType.mesh.toString))(parseRow)
1069+
agglomerateFiles <- Fox.serialCombined(rows.filter(_.`type` == LayerAttachmentType.agglomerate.toString))(
1070+
parseRow)
1071+
connectomeFiles <- Fox.serialCombined(rows.filter(_.`type` == LayerAttachmentType.connectome.toString))(parseRow)
1072+
segmentIndexFiles <- Fox.serialCombined(rows.filter(_.`type` == LayerAttachmentType.segmentIndex.toString))(
1073+
parseRow)
1074+
cumsumFiles <- Fox.serialCombined(rows.filter(_.`type` == LayerAttachmentType.cumsum.toString))(parseRow)
1075+
} yield
1076+
AttachmentWrapper(
1077+
agglomerates = agglomerateFiles,
1078+
connectomes = connectomeFiles,
1079+
segmentIndex = segmentIndexFiles.headOption,
1080+
meshes = meshFiles,
1081+
cumsum = cumsumFiles.headOption
1082+
)
1083+
1084+
def findAllForDatasetAndDataLayerName(datasetId: ObjectId, layerName: String): Fox[AttachmentWrapper] =
1085+
for {
1086+
rows <- run(q"""SELECT _dataset, layerName, name, path, type, dataFormat
1087+
FROM webknossos.dataset_layer_attachments
1088+
WHERE _dataset = $datasetId AND layerName = $layerName""".as[DatasetLayerAttachmentsRow])
1089+
attachments <- parseAttachments(rows.toList) ?~> "Could not parse attachments"
1090+
} yield attachments
1091+
10181092
def updateAttachments(datasetId: ObjectId, dataLayersOpt: Option[List[DataLayer]]): Fox[Unit] = {
10191093
def insertQuery(attachment: LayerAttachment, layerName: String, fileType: String) =
10201094
q"""INSERT INTO webknossos.dataset_layer_attachments(_dataset, layerName, name, path, type, dataFormat)

0 commit comments

Comments
 (0)