Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Commit

Permalink
Merge pull request #12 from lsafer-meemer/main
Browse files Browse the repository at this point in the history
feat(op): OpBucket
  • Loading branch information
LSafer authored Jul 5, 2023
2 parents 1736997 + 84ef435 commit 0832ea5
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 0 deletions.
78 changes: 78 additions & 0 deletions op/src/main/kotlin/gridfs/BucketOp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2023 cufy.org and meemer.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.cufy.monop.gridfs

import org.cufy.mongodb.gridfs.MongoBucket
import org.cufy.monop.Op
import org.cufy.monop.OpClient

/* ============= ------------------ ============= */

/**
* A type of ops that is focused on [MongoBucket].
* This type of operations only produces operations
* that are of type [BucketOperation].
*
* @author LSafer
* @since 2.0.0
*/
interface BucketOp<T> : Op<T> {
/**
* The name of the database of the bucket
* to operate on.
* Set to `null` to use [OpClient.defaultDatabase].
*
* @since 2.0.0
*/
val database: String?

/**
* The name of the bucket this operation
* is targeting.
*/
val bucket: String

override fun createOperation(): BucketOperation<T>
}

private fun BucketOp<*>.inferToString(): String {
val name = this::class.simpleName ?: "BucketOp"
val address = System.identityHashCode(this).toString(16)
return "$name($database, $bucket, ...)@$address"
}

/* ============= ------------------ ============= */

/**
* Create a custom [BucketOp] with the given [block] being its default behaviour.
*
* @param bucket the name of the bucket for the operation.
* @param database name of the database for the operation. (null for [OpClient.defaultDatabase])
* @since 2.0.0
*/
fun <T> BucketOp(bucket: String, database: String? = null, block: suspend (MongoBucket) -> T): BucketOp<T> {
return object : BucketOp<T> {
override val database = database
override val bucket = bucket

override fun toString() = inferToString()

override fun createOperation(): BucketOperation<T> =
BucketOperation(bucket, database, block)
}
}

/* ============= ------------------ ============= */
122 changes: 122 additions & 0 deletions op/src/main/kotlin/gridfs/BucketOperation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright 2023 cufy.org and meemer.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.cufy.monop.gridfs

import kotlinx.coroutines.*
import org.cufy.mongodb.gridfs.MongoBucket
import org.cufy.mongodb.gridfs.createMongoBucket
import org.cufy.monop.*

/* ============= ------------------ ============= */

/**
* A type of operations that is focused on [MongoBucket].
* This type of operations have a default behaviour that only
* require an instance of [MongoBucket].
*
* @author LSafer
* @since 2.0.0
*/
interface BucketOperation<T> : Operation<T> {
/**
* The name of the database of the bucket
* to operate on.
* Set to `null` to use [OpClient.defaultDatabase].
*
* @since 2.0.0
*/
val database: String?

/**
* The name of the bucket this operation
* is targeting.
*/
val bucket: String

/**
* The default behaviour of this operation.
*
* **NOTE: errors throw by this function won't be caught safely.**
*
* @since 2.0.0
*/
suspend fun completeWithDefaultBehaviour(bucket: MongoBucket)
}

private fun BucketOperation<*>.inferToString(): String {
val name = this::class.simpleName ?: "BucketOperation"
val address = System.identityHashCode(this).toString(16)
return "$name($database, $bucket, ...)@$address"
}

/* ============= ------------------ ============= */

/**
* Create a custom [BucketOperation] with the given [block] being its default behaviour.
*
* @param bucket the name of the bucket for the operation.
* @param database name of the database for the operation. (null for [OpClient.defaultDatabase])
* @since 2.0.0
*/
fun <T> BucketOperation(bucket: String, database: String? = null, block: suspend (MongoBucket) -> T): BucketOperation<T> {
return object : BucketOperation<T>, CompletableDeferred<T> by CompletableDeferred() {
override val database = database
override val bucket = bucket

override fun toString() = inferToString()

override suspend fun completeWithDefaultBehaviour(bucket: MongoBucket) {
completeWith(runCatching { block(bucket) })
}
}
}

/* ============= ------------------ ============= */

/**
* An operator performing operations of type [BucketOperation] in parallel
* using [BucketOperation.completeWithDefaultBehaviour].
*
* @author LSafer
* @since 2.0.0
*/
@ExperimentalMonopApi
val BucketOperator = createOperatorForType<BucketOperation<*>> { operations ->
val leftovers = mutableSetOf<BucketOperation<*>>()
for ((databaseName, databaseOperations) in operations.groupBy { it.database }) {
val database = databaseOrDefaultDatabase(databaseName)

// if databaseName is null yet no default database is set
if (database == null) {
leftovers += databaseOperations
continue
}

for ((bucketName, bucketOperations) in databaseOperations.groupBy { it.bucket }) {
val bucket = createMongoBucket(database, bucketName)

bucketOperations.forEach {
CoroutineScope(Dispatchers.IO).launch {
it.completeWithDefaultBehaviour(bucket)
}
}
}
}

leftovers
}

/* ============= ------------------ ============= */
106 changes: 106 additions & 0 deletions op/src/main/kotlin/gridfs/OpBucket.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2023 cufy.org and meemer.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.cufy.monop.gridfs

import org.cufy.mongodb.gridfs.MongoBucket
import org.cufy.mongodb.gridfs.createMongoBucket
import org.cufy.monop.Op
import org.cufy.monop.OpClient
import org.cufy.monop.databaseOrDefaultDatabase

/* ============= ------------------ ============= */

/**
* A convenient class that holds bare minimal
* data needed for using some bucket.
*
* @author LSafer
* @since 2.0.0
*/
interface OpBucket {
/**
* The default bucket.
*/
companion object : OpBucket {
override val name = "fs"
}

/**
* The name of the database of the bucket
* to operate on.
* Set to `null` to use [OpClient.defaultDatabase].
*
* @since 2.0.0
*/
val database: String? get() = null

/**
* The bucket name.
*
* @since 2.0.0
*/
val name: String get() = inferName()
}

private fun OpBucket.inferName(): String {
return this::class.simpleName ?: error("Cannot infer bucket name for $this")
}

private fun OpBucket.inferToString(): String {
return "OpBucket($database, $name)"
}

/* ============= ------------------ ============= */

/**
* Construct a new [OpBucket] with the given [name] and [database].
*
* @since 2.0.0
*/
fun OpBucket(name: String, database: String? = null): OpBucket {
return object : OpBucket {
override val database = database
override val name = name

override fun toString() = inferToString()
}
}

/* ============= ------------------ ============= */

/**
* Return a [MongoBucket] instance corresponding
* to this bucket using the given [client].
*/
suspend fun OpBucket.get(client: OpClient = OpClient): MongoBucket {
val database = client.databaseOrDefaultDatabase(database)
// if this.database is null yet no default database is set
database ?: error("Bucket requires default database yet default database not set.")
return createMongoBucket(database, name)
}

/**
* Create an [Op] that executes the given [block]
* with a [MongoBucket] corresponding to this
* bucket.
*
* @since 2.0.0
*/
fun <T> OpBucket.op(block: suspend MongoBucket.() -> T): Op<T> {
return BucketOp(name, database, block)
}

/* ============= ------------------ ============= */

0 comments on commit 0832ea5

Please sign in to comment.