Skip to content
Open
Show file tree
Hide file tree
Changes from 79 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
63f1b69
Implement rudimentary upload of datasets to S3
frcroth Sep 10, 2025
f2dcd81
Fix that uploads would have 'aws-chunked' content encoding in files
frcroth Sep 11, 2025
6e62928
Use update data source route for uploading virtual datasets
frcroth Sep 11, 2025
f90002e
Use S3 transfer manager for file uploads
frcroth Sep 11, 2025
5d9eff4
Add application.conf
frcroth Sep 11, 2025
5e548c8
Clean up unsued code, remove todos
frcroth Sep 11, 2025
a62c986
Merge branch 'master' into s3-upload
frcroth Sep 11, 2025
71b3e30
Update changelog
frcroth Sep 11, 2025
5c64063
Add absolute paths to datasets with no mag paths
frcroth Sep 12, 2025
631bc4a
Do not upload unreferenced files to S3
frcroth Sep 15, 2025
cc0509d
Merge branch 'master' into s3-upload
normanrz Sep 16, 2025
acc56ba
Do not upload on conversion, simplify application.conf
frcroth Sep 17, 2025
77bd98f
Merge remote-tracking branch 'origin/master' into s3-upload
frcroth Sep 17, 2025
fc22245
Implement deletion of S3 datasets
frcroth Sep 15, 2025
063132d
Do not delete mags that are referenced in other layers
frcroth Sep 17, 2025
1b6493b
Lint
frcroth Sep 17, 2025
36533df
Merge branch 'master' into s3-upload
fm3 Sep 22, 2025
193609d
Merge branch 's3-upload' into s3-delete
fm3 Sep 22, 2025
a37534d
Merge branch 'master' into s3-upload
fm3 Sep 23, 2025
505550c
update migration guide
fm3 Sep 23, 2025
384513d
upath
fm3 Sep 23, 2025
a81f287
Merge branch 'master' into s3-upload
fm3 Sep 24, 2025
18a9fbd
WIP adapt upload to use datasetId where possible
fm3 Sep 24, 2025
b021cd8
move to target, pass linked layers to wk side
fm3 Sep 24, 2025
79a0c11
Merge branch 'master' into s3-upload
fm3 Sep 25, 2025
2fbe524
move to target depending on config, pass datasource to wk side, use n…
fm3 Sep 25, 2025
9e04d83
track new datasets also if added via add or uploadToPaths
fm3 Sep 25, 2025
d09c5f1
wip legacy api version
fm3 Sep 25, 2025
fa4a817
register dataset in db,cleanup,log to slack, validate linked layers
fm3 Sep 25, 2025
0f9e165
Merge branch 'master' into s3-upload
fm3 Sep 25, 2025
a50f98a
rename LegacyController to DSLegacyApiController
fm3 Sep 25, 2025
b79c5a3
fix legacy api adapter
fm3 Sep 25, 2025
174b9a7
cleanup
fm3 Sep 25, 2025
328d1cc
format
fm3 Sep 25, 2025
297459a
reset application.conf
fm3 Sep 25, 2025
a509ceb
re-add s3Upload block in application.conf
fm3 Sep 25, 2025
32ead33
improve compatibility with worker convert jobs
fm3 Sep 29, 2025
b48966d
unify logging
fm3 Sep 29, 2025
4bfb9a2
handle legacy layersToLink with dataset id
fm3 Sep 29, 2025
e5857c6
Merge branch 's3-upload' into s3-delete
fm3 Sep 29, 2025
b210c7d
Merge branch 'master' into s3-upload
fm3 Oct 2, 2025
2aebc34
implement pr feedback
fm3 Oct 2, 2025
28ec1a3
Merge branch 'master' into s3-upload
fm3 Oct 6, 2025
8dab124
remove this.synchronized around UploadService file operations
fm3 Oct 6, 2025
fe9c527
Revert "remove this.synchronized around UploadService file operations"
fm3 Oct 6, 2025
9efac9a
some more logging for finishUpload request
fm3 Oct 7, 2025
b3b87b0
Merge branch 'master' into s3-upload
MichaelBuessemeyer Oct 7, 2025
c3cf1d6
Merge branch 'master' into s3-upload
fm3 Oct 8, 2025
8d51044
add slow request slack notification for finishUpload
fm3 Oct 8, 2025
d0feb8d
Merge branch 's3-upload' into s3-delete
fm3 Oct 8, 2025
34846c4
Merge branch 'master' into s3-delete
fm3 Oct 8, 2025
4c78b88
Merge branch 'master' into s3-delete
fm3 Oct 9, 2025
8d480b2
cleanup
fm3 Oct 9, 2025
3860da3
draft
fm3 Oct 14, 2025
935b4e9
fix compiler errors, wip new deletion logic
fm3 Oct 14, 2025
5a08f02
Merge branch 'master' into s3-delete
fm3 Oct 15, 2025
e520cc9
implement delete paths on datastore side
fm3 Oct 15, 2025
e704481
cleanup
fm3 Oct 15, 2025
834e6e4
remove dataset also from postgres
fm3 Oct 15, 2025
96c43fe
cleanup
fm3 Oct 15, 2025
c3ac8c3
fix circular dependency
fm3 Oct 15, 2025
7335a0f
adapt frontend (changed api route)
fm3 Oct 15, 2025
f493207
Merge branch 'master' into s3-delete
fm3 Oct 16, 2025
1a032b2
WIP find unique paths
fm3 Oct 16, 2025
a684044
also attachments
fm3 Oct 16, 2025
aa5c994
wip also handle non-virtual datasets
fm3 Oct 23, 2025
84c49b2
check other datasets using deletee
fm3 Oct 23, 2025
0b17368
remove unused code
fm3 Oct 23, 2025
cb52d47
fixes
fm3 Oct 23, 2025
c90421d
Merge branch 'master' into s3-delete
fm3 Oct 23, 2025
6bda79d
reset application.conf
fm3 Oct 23, 2025
3783ce8
Merge branch 'master' into s3-delete
fm3 Oct 27, 2025
c50f346
extract stuff to S3UriUtils
fm3 Oct 27, 2025
beff7d7
use upath for prefix check
fm3 Oct 27, 2025
b91144f
avoid string prefix false positives in startsWith
fm3 Oct 27, 2025
376ef47
changelog
fm3 Oct 27, 2025
768402e
remove unused route
fm3 Oct 27, 2025
991c3af
remove unused code
fm3 Oct 27, 2025
df11bf1
Merge branch 'master' into s3-delete
fm3 Oct 27, 2025
542c5b7
Merge branch 'master' into s3-delete
fm3 Oct 28, 2025
d80fec9
add null checks, add comments, improve logging
fm3 Oct 28, 2025
cc9908c
remove snapshot test of no-longer existing route
fm3 Oct 28, 2025
6219144
add duration logging for dataset deletion
fm3 Oct 28, 2025
1af5aca
simplify UPath.startsWith
fm3 Oct 29, 2025
04b1de2
Merge branch 'master' into s3-delete
fm3 Oct 29, 2025
368893d
implement pr feedback
fm3 Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions app/controllers/DatasetController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -581,15 +581,19 @@ class DatasetController @Inject()(userService: UserService,
}
}

def deleteOnDisk(datasetId: ObjectId): Action[AnyContent] =
def delete(datasetId: ObjectId): Action[AnyContent] =
sil.SecuredAction.async { implicit request =>
for {
dataset <- datasetDAO.findOne(datasetId) ?~> notFoundMessage(datasetId.toString) ~> NOT_FOUND
_ <- Fox.fromBool(conf.Features.allowDeleteDatasets) ?~> "dataset.delete.disabled"
_ <- Fox.assertTrue(datasetService.isEditableBy(dataset, Some(request.identity))) ?~> "notAllowed" ~> FORBIDDEN
_ <- Fox.fromBool(request.identity.isAdminOf(dataset._organization)) ~> FORBIDDEN
_ <- datasetService.deleteVirtualOrDiskDataset(dataset)
} yield Ok
log() {
for {
dataset <- datasetDAO.findOne(datasetId) ?~> notFoundMessage(datasetId.toString) ~> NOT_FOUND
_ <- Fox.fromBool(conf.Features.allowDeleteDatasets) ?~> "dataset.delete.disabled"
_ <- Fox.assertTrue(datasetService.isEditableBy(dataset, Some(request.identity))) ?~> "notAllowed" ~> FORBIDDEN
_ <- Fox.fromBool(request.identity.isAdminOf(dataset._organization)) ?~> "delete.mustBeOrganizationAdmin" ~> FORBIDDEN
_ = logger.info(
s"Deleting dataset $datasetId (isVirtual=${dataset.isVirtual}) as requested by user ${request.identity._id}...")
_ <- datasetService.deleteDataset(dataset)
} yield Ok
}
}

def compose(): Action[ComposeRequest] =
Expand Down
32 changes: 2 additions & 30 deletions app/controllers/WKRemoteDataStoreController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ package controllers
import com.scalableminds.util.accesscontext.{AuthorizedAccessContext, DBAccessContext, GlobalAccessContext}
import com.scalableminds.util.objectid.ObjectId
import com.scalableminds.util.time.Instant
import com.scalableminds.util.tools.{Fox, Full}
import com.scalableminds.util.tools.Fox
import com.scalableminds.webknossos.datastore.controllers.JobExportProperties
import com.scalableminds.webknossos.datastore.helpers.{LayerMagLinkInfo, MagLinkInfo}
import com.scalableminds.webknossos.datastore.models.UnfinishedUpload
import com.scalableminds.webknossos.datastore.models.datasource.{
DataSource,
Expand All @@ -20,7 +19,6 @@ import com.scalableminds.webknossos.datastore.services.uploading.{
ReserveUploadInformation
}
import com.typesafe.scalalogging.LazyLogging
import models.annotation.AnnotationDAO
import models.dataset._
import models.dataset.credential.CredentialDAO
import models.job.JobDAO
Expand Down Expand Up @@ -50,7 +48,6 @@ class WKRemoteDataStoreController @Inject()(
teamDAO: TeamDAO,
jobDAO: JobDAO,
credentialDAO: CredentialDAO,
annotationDAO: AnnotationDAO,
wkSilhouetteEnvironment: WkSilhouetteEnvironment)(implicit ec: ExecutionContext, bodyParsers: PlayBodyParsers)
extends Controller
with LazyLogging {
Expand Down Expand Up @@ -211,17 +208,7 @@ class WKRemoteDataStoreController @Inject()(
implicit request =>
dataStoreService.validateAccess(name, key) { _ =>
for {
existingDatasetBox <- datasetDAO.findOne(request.body)(GlobalAccessContext).shiftBox
_ <- existingDatasetBox match {
case Full(dataset) =>
for {
annotationCount <- annotationDAO.countAllByDataset(dataset._id)(GlobalAccessContext)
_ = datasetDAO
.deleteDataset(dataset._id, onlyMarkAsDeleted = annotationCount > 0)
.flatMap(_ => usedStorageService.refreshStorageReportForDataset(dataset))
} yield ()
case _ => Fox.successful(())
}
_ <- datasetService.deleteDatasetFromDB(request.body)
} yield Ok
}
}
Expand All @@ -242,21 +229,6 @@ class WKRemoteDataStoreController @Inject()(
}
}

def getPaths(name: String, key: String, datasetId: ObjectId): Action[AnyContent] =
Action.async { implicit request =>
dataStoreService.validateAccess(name, key) { _ =>
for {
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> Messages("dataset.notFound", datasetId) ~> NOT_FOUND
layers <- datasetLayerDAO.findAllForDataset(dataset._id)
magsAndLinkedMags <- Fox.serialCombined(layers)(l => datasetService.getPathsForDataLayer(dataset._id, l.name))
magLinkInfos = magsAndLinkedMags.map(_.map { case (mag, linkedMags) => MagLinkInfo(mag, linkedMags) })
layersAndMagLinkInfos = layers.zip(magLinkInfos).map {
case (layer, magLinkInfo) => LayerMagLinkInfo(layer.name, magLinkInfo)
}
} yield Ok(Json.toJson(layersAndMagLinkInfos))
}
}

def getDataSource(name: String, key: String, datasetId: ObjectId): Action[AnyContent] =
Action.async { implicit request =>
dataStoreService.validateAccess(name, key) { _ =>
Expand Down
90 changes: 63 additions & 27 deletions app/models/dataset/Dataset.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.scalableminds.util.time.Instant
import com.scalableminds.util.tools.{Fox, JsonHelper}
import com.scalableminds.webknossos.datastore.dataformats.MagLocator
import com.scalableminds.webknossos.datastore.datareaders.AxisOrder
import com.scalableminds.webknossos.datastore.helpers.{DataSourceMagInfo, UPath}
import com.scalableminds.webknossos.datastore.helpers.UPath
import com.scalableminds.webknossos.datastore.models.{LengthUnit, VoxelSize}
import com.scalableminds.webknossos.datastore.models.datasource.DatasetViewConfiguration.DatasetViewConfiguration
import com.scalableminds.webknossos.datastore.models.datasource.LayerViewConfiguration.LayerViewConfiguration
Expand Down Expand Up @@ -868,33 +868,39 @@ class DatasetMagsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionConte
}
)

private def rowsToMagInfos(rows: Vector[DataSourceMagRow]): List[DataSourceMagInfo] = {
val mags = rows.map(_.mag)
val dataSources = rows.map(row => DataSourceId(row.directoryName, row._organization))
rows.toList.zip(mags).zip(dataSources).map {
case ((row, mag), dataSource) =>
DataSourceMagInfo(dataSource, row.dataLayerName, mag, row.path, row.realPath, row.hasLocalData)
}
}

def findPathsForDatasetAndDatalayer(datasetId: ObjectId, dataLayerName: String): Fox[List[DataSourceMagInfo]] =
def findPathsUsedOnlyByThisDataset(datasetId: ObjectId): Fox[Seq[UPath]] =
for {
rows <- run(q"""SELECT _dataset, dataLayerName, mag, path, realPath, hasLocalData, _organization, directoryName
FROM webknossos.dataset_mags
INNER JOIN webknossos.datasets ON webknossos.dataset_mags._dataset = webknossos.datasets._id
WHERE _dataset = $datasetId
AND dataLayerName = $dataLayerName""".as[DataSourceMagRow])
magInfos = rowsToMagInfos(rows)
} yield magInfos

def findAllByRealPath(realPath: String): Fox[List[DataSourceMagInfo]] =
for {
rows <- run(q"""SELECT _dataset, dataLayerName, mag, path, realPath, hasLocalData, _organization, directoryName
FROM webknossos.dataset_mags
INNER JOIN webknossos.datasets ON webknossos.dataset_mags._dataset = webknossos.datasets._id
WHERE realPath = $realPath""".as[DataSourceMagRow])
magInfos = rowsToMagInfos(rows)
} yield magInfos
pathsStr <- run(q"""
SELECT m1.path FROM webknossos.dataset_mags m1
WHERE m1._dataset = $datasetId
AND NOT EXISTS (
SELECT m2.path
FROM webknossos.dataset_mags m2
WHERE m2._dataset != $datasetId
AND (
m2.path = m1.path
OR
m2.realpath = m1.realpath
)
)
""".as[String])
paths <- pathsStr.map(UPath.fromString).toList.toSingleBox("Invalid UPath").toFox
} yield paths

def findDatasetsWithMagsInDir(absolutePath: UPath,
dataStore: DataStore,
ignoredDataset: ObjectId): Fox[Seq[ObjectId]] = {
// ensure trailing slash on absolutePath to avoid string prefix false positives
val absolutePathWithTrailingSlash =
if (absolutePath.toString.endsWith("/")) absolutePath.toString else absolutePath.toString + "/"
run(q"""
SELECT d._id FROM webknossos.dataset_mags m
JOIN webknossos.datasets d ON m._dataset = d._id
WHERE starts_with(m.realpath, $absolutePathWithTrailingSlash)
AND d._id != $ignoredDataset
AND d._datastore = ${dataStore.name.trim}
""".as[ObjectId])
}

private def parseMagLocator(row: DatasetMagsRow): Fox[MagLocator] =
for {
Expand Down Expand Up @@ -1265,6 +1271,36 @@ class DatasetLayerAttachmentsDAO @Inject()(sqlClient: SqlClient)(implicit ec: Ex
${datasetIdOpt.map(datasetId => q"AND ranked._dataset = $datasetId").getOrElse(q"")};
""".as[StorageRelevantDataLayerAttachment])
} yield storageRelevantAttachments.toList

def findPathsUsedOnlyByThisDataset(datasetId: ObjectId): Fox[Seq[UPath]] =
for {
pathsStr <- run(q"""
SELECT a1.path FROM webknossos.dataset_layer_attachments a1
WHERE a1._dataset = $datasetId
AND NOT EXISTS (
SELECT a2.path
FROM webknossos.dataset_layer_attachments a2
WHERE a2._dataset != $datasetId
AND a2.path = a1.path
)
""".as[String])
paths <- pathsStr.map(UPath.fromString).toList.toSingleBox("Invalid UPath").toFox
} yield paths

def findDatasetsWithAttachmentsInDir(absolutePath: UPath,
dataStore: DataStore,
ignoredDataset: ObjectId): Fox[Seq[ObjectId]] = {
// ensure trailing slash on absolutePath to avoid string prefix false positives
val absolutePathWithTrailingSlash =
if (absolutePath.toString.endsWith("/")) absolutePath.toString else absolutePath.toString + "/"
run(q"""
SELECT d._id FROM webknossos.dataset_layer_attachments a
JOIN webknossos.datasets d ON a._dataset = d._id
WHERE starts_with(a.path, $absolutePathWithTrailingSlash)
AND d._id != $ignoredDataset
AND d._datastore = ${dataStore.name.trim}
""".as[ObjectId])
}
}

class DatasetCoordinateTransformationsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext)
Expand Down
80 changes: 50 additions & 30 deletions app/models/dataset/DatasetService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.scalableminds.util.accesscontext.{AuthorizedAccessContext, DBAccessCo
import com.scalableminds.util.objectid.ObjectId
import com.scalableminds.util.time.Instant
import com.scalableminds.util.tools.{Empty, EmptyBox, Fox, FoxImplicits, Full, JsonHelper, TextUtils}
import com.scalableminds.webknossos.datastore.helpers.{DataSourceMagInfo, UPath}
import com.scalableminds.webknossos.datastore.helpers.UPath
import com.scalableminds.webknossos.datastore.models.datasource.{
DataSource,
DataSourceId,
Expand All @@ -25,6 +25,8 @@ import models.user.{MultiUserDAO, User, UserService}
import com.scalableminds.webknossos.datastore.controllers.PathValidationResult
import mail.{MailchimpClient, MailchimpTag}
import models.analytics.{AnalyticsService, UploadDatasetEvent}
import models.annotation.AnnotationDAO
import models.storage.UsedStorageService
import play.api.http.Status.NOT_FOUND
import play.api.i18n.{Messages, MessagesProvider}
import play.api.libs.json.{JsObject, Json}
Expand All @@ -42,6 +44,7 @@ class DatasetService @Inject()(organizationDAO: OrganizationDAO,
datasetLastUsedTimesDAO: DatasetLastUsedTimesDAO,
datasetDataLayerDAO: DatasetLayerDAO,
datasetMagsDAO: DatasetMagsDAO,
datasetLayerAttachmentsDAO: DatasetLayerAttachmentsDAO,
teamDAO: TeamDAO,
folderDAO: FolderDAO,
multiUserDAO: MultiUserDAO,
Expand All @@ -52,6 +55,8 @@ class DatasetService @Inject()(organizationDAO: OrganizationDAO,
teamService: TeamService,
thumbnailCachingService: ThumbnailCachingService,
userService: UserService,
annotationDAO: AnnotationDAO,
usedStorageService: UsedStorageService,
conf: WkConf,
rpc: RPC)(implicit ec: ExecutionContext)
extends FoxImplicits
Expand Down Expand Up @@ -492,28 +497,6 @@ class DatasetService @Inject()(organizationDAO: OrganizationDAO,
_ <- Fox.serialCombined(pathInfos)(updateRealPath)
} yield ()

/**
* Returns a list of tuples, where the first element is the magInfo and the second element is a list of all magInfos
* that share the same realPath but have a different dataSourceId. For each mag in the data layer there is one tuple.
* @param datasetId id of the dataset
* @param layerName name of the layer in the dataset
* @return
*/
def getPathsForDataLayer(datasetId: ObjectId,
layerName: String): Fox[List[(DataSourceMagInfo, List[DataSourceMagInfo])]] =
for {
magInfos <- datasetMagsDAO.findPathsForDatasetAndDatalayer(datasetId, layerName)
magInfosAndLinkedMags <- Fox.serialCombined(magInfos)(magInfo =>
magInfo.realPath match {
case Some(realPath) =>
for {
pathInfos <- datasetMagsDAO.findAllByRealPath(realPath)
filteredPathInfos = pathInfos.filter(_.dataSourceId != magInfo.dataSourceId)
} yield (magInfo, filteredPathInfos)
case None => Fox.successful((magInfo, List()))
})
} yield magInfosAndLinkedMags

def validatePaths(paths: Seq[UPath], dataStore: DataStore): Fox[Unit] =
for {
_ <- Fox.successful(())
Expand All @@ -525,18 +508,55 @@ class DatasetService @Inject()(organizationDAO: OrganizationDAO,
})
} yield ()

def deleteVirtualOrDiskDataset(dataset: Dataset)(implicit ctx: DBAccessContext): Fox[Unit] =
def deleteDataset(dataset: Dataset)(implicit ctx: DBAccessContext): Fox[Unit] =
for {
datastoreClient <- clientFor(dataset)
_ <- if (dataset.isVirtual) {
// At this point, we should also free space in S3 once implemented.
// Right now, we can just mark the dataset as deleted in the database.
datasetDAO.deleteDataset(dataset._id, onlyMarkAsDeleted = true)
for {
magPathsUsedOnlyByThisDataset <- datasetMagsDAO.findPathsUsedOnlyByThisDataset(dataset._id)
attachmentPathsUsedOnlyByThisDataset <- datasetLayerAttachmentsDAO.findPathsUsedOnlyByThisDataset(dataset._id)
pathsUsedOnlyByThisDataset = magPathsUsedOnlyByThisDataset ++ attachmentPathsUsedOnlyByThisDataset
// Note that the datastore only deletes local paths and paths on our managed S3 cloud storage
_ <- datastoreClient.deletePaths(pathsUsedOnlyByThisDataset)
} yield ()
} else {
for {
datastoreClient <- clientFor(dataset)
_ <- datastoreClient.deleteOnDisk(dataset._id)
datastoreBaseDirStr <- datastoreClient.getBaseDirAbsolute
datastoreBaseDir <- UPath.fromString(datastoreBaseDirStr).toFox
datasetDir = datastoreBaseDir / dataset._organization / dataset.directoryName
datastore <- dataStoreFor(dataset)
datasetsUsingDataFromThisDir <- findDatasetsUsingDataFromDir(datasetDir, datastore, dataset._id)
_ <- Fox.fromBool(datasetsUsingDataFromThisDir.isEmpty) ?~> s"Cannot delete dataset because ${datasetsUsingDataFromThisDir.length} other datasets reference its data: ${datasetsUsingDataFromThisDir
.mkString(",")}"
_ <- datastoreClient.deleteOnDisk(dataset._id) ?~> "dataset.delete.failed"
} yield ()
} ?~> "dataset.delete.failed"
}
_ <- deleteDatasetFromDB(dataset._id)
} yield ()

private def findDatasetsUsingDataFromDir(directory: UPath,
dataStore: DataStore,
ignoredDatasetId: ObjectId): Fox[Seq[ObjectId]] =
for {
datasetsWithMagsInDir <- datasetMagsDAO.findDatasetsWithMagsInDir(directory, dataStore, ignoredDatasetId)
datasetsWithAttachmentsInDir <- datasetLayerAttachmentsDAO.findDatasetsWithAttachmentsInDir(directory,
dataStore,
ignoredDatasetId)
} yield (datasetsWithMagsInDir ++ datasetsWithAttachmentsInDir).distinct

def deleteDatasetFromDB(datasetId: ObjectId): Fox[Unit] =
for {
existingDatasetBox <- datasetDAO.findOne(datasetId)(GlobalAccessContext).shiftBox
_ <- existingDatasetBox match {
case Full(dataset) =>
for {
annotationCount <- annotationDAO.countAllByDataset(dataset._id)(GlobalAccessContext)
_ = datasetDAO
.deleteDataset(dataset._id, onlyMarkAsDeleted = annotationCount > 0)
.flatMap(_ => usedStorageService.refreshStorageReportForDataset(dataset))
} yield ()
case _ => Fox.successful(())
}
} yield ()

def generateDirectoryName(datasetName: String, datasetId: ObjectId): String =
Expand Down
12 changes: 12 additions & 0 deletions app/models/dataset/WKRemoteDataStoreClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,16 @@ class WKRemoteDataStoreClient(dataStore: DataStore, rpc: RPC) extends LazyLoggin
.delete()
} yield ()

lazy val getBaseDirAbsolute: Fox[String] =
rpc(s"${dataStore.url}/data/datasets/baseDirAbsolute")
.addQueryParam("token", RpcTokenHolder.webknossosToken)
.getWithJsonResponse[String]

def deletePaths(paths: Seq[UPath]): Fox[Unit] =
for {
_ <- rpc(s"${dataStore.url}/data/datasets/deletePaths")
.addQueryParam("token", RpcTokenHolder.webknossosToken)
.deleteJson(paths)
} yield ()

}
4 changes: 1 addition & 3 deletions app/models/storage/UsedStorageService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import models.dataset.{
Dataset,
DatasetLayerAttachmentsDAO,
DatasetMagsDAO,
DatasetService,
StorageRelevantDataLayerAttachment,
WKRemoteDataStoreClient
}
Expand All @@ -34,7 +33,6 @@ import scala.concurrent.duration._
class UsedStorageService @Inject()(val actorSystem: ActorSystem,
val lifecycle: ApplicationLifecycle,
organizationDAO: OrganizationDAO,
datasetService: DatasetService,
dataStoreDAO: DataStoreDAO,
datasetMagDAO: DatasetMagsDAO,
datasetLayerAttachmentsDAO: DatasetLayerAttachmentsDAO,
Expand Down Expand Up @@ -213,7 +211,7 @@ class UsedStorageService @Inject()(val actorSystem: ActorSystem,
def refreshStorageReportForDataset(dataset: Dataset): Fox[Unit] =
for {
_ <- Fox.successful(())
dataStore <- datasetService.dataStoreFor(dataset)
dataStore <- dataStoreDAO.findOneByName(dataset._dataStore.trim) ?~> "datastore.notFound"
_ <- if (dataStore.reportUsedStorageEnabled) {
for {
organization <- organizationDAO.findOne(dataset._organization)
Expand Down
Loading
Loading