Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Transfer manager #31

Merged
merged 24 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
381f2c8
network: Handle transfer by url or linkUuid
sirambd Oct 10, 2024
76108c4
database: Cast a Transfer to TransferDB
sirambd Oct 10, 2024
1d61eee
database: Get and upsert a Transfer
sirambd Oct 10, 2024
a4d82a2
database: Add unit test for TransferController
sirambd Oct 10, 2024
4dda277
database: Remove useless test class
sirambd Oct 10, 2024
76f2fe1
core: Handle transfers with transferManager
sirambd Oct 10, 2024
b729722
refactor: Rename TransfersController into TransferController
sirambd Oct 10, 2024
7dbd102
refactor: Update kDoc and add Dispatches.IO
sirambd Oct 10, 2024
861483f
refactor: Update Core README
sirambd Oct 10, 2024
1118141
feat: Sort transfers by created date
sirambd Oct 10, 2024
ecc6762
refactor: Rename canGetFrom into canGetTransfers
sirambd Oct 10, 2024
5427104
refactor: Add 'L' at end of 0 if needed
sirambd Oct 10, 2024
c565e11
fix: Realm for unit tests
sirambd Oct 10, 2024
54b956d
test: Support for Coroutines in Database module
sirambd Oct 10, 2024
015d6e0
test: Can remove a transfer from database
sirambd Oct 10, 2024
693c947
test(Database): Add more messages
sirambd Oct 10, 2024
bdff78e
core: Add kDoc for TransferManager
sirambd Oct 10, 2024
ee9bda3
test: Can Update an existing transfer
sirambd Oct 11, 2024
ada87a9
Update STNetwork/src/commonMain/kotlin/com/infomaniak/multiplatform_s…
sirambd Oct 11, 2024
2d34579
refactor: Rename UnknownApiException in UnexpectedApiErrorFormatExcep…
sirambd Oct 11, 2024
3fe5973
chore: Add kDoc for ApiException
sirambd Oct 11, 2024
7ff7f94
chore: Add kDoc for UnexpectedApiErrorFormatException
sirambd Oct 11, 2024
87a3856
chore: Add kDoc for NetworkException
sirambd Oct 11, 2024
c5e0ebc
refactor: Update NetworkException kDoc
sirambd Oct 15, 2024
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
3 changes: 1 addition & 2 deletions STCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ centralized access point to orchestrate transfer operations.

- **Type**: `TransferManager`
- **Description**:
- `transferManager` is a lazily initialized property that provides a manager to orchestrate all transfer operations. It
uses `realmProvider` and `apiClientProvider` to configure and manage Transfers efficiently.
- `transferManager` is a lazily initialized property that provides a manager to orchestrate all transfer operations.

- **Usage Example**:
```kotlin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package com.infomaniak.multiplatform_swisstransfer

import com.infomaniak.multiplatform_swisstransfer.database.RealmProvider
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.AppSettingsController
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.TransfersController
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.TransferController
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.UploadController
import com.infomaniak.multiplatform_swisstransfer.managers.AccountManager
import com.infomaniak.multiplatform_swisstransfer.managers.AppSettingsManager
Expand Down Expand Up @@ -49,14 +49,14 @@ class SwissTransferInjection {

private val appSettingsController by lazy { AppSettingsController(realmProvider) }
private val uploadController by lazy { UploadController(realmProvider) }
private val transfersController by lazy { TransfersController(realmProvider) }
private val transferController by lazy { TransferController(realmProvider) }

/** A manager used to orchestrate Transfers operations. */
val transferManager by lazy { TransferManager(realmProvider, apiClientProvider) }
val transferManager by lazy { TransferManager(apiClientProvider, transferController, transferRepository) }

/** A manager used to orchestrate AppSettings operations. */
val appSettingsManager by lazy { AppSettingsManager(appSettingsController) }

/** A manager used to orchestrate Accounts operations. */
val accountManager by lazy { AccountManager(appSettingsController, uploadController, transfersController, realmProvider) }
val accountManager by lazy { AccountManager(appSettingsController, uploadController, transferController, realmProvider) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ package com.infomaniak.multiplatform_swisstransfer.managers

import com.infomaniak.multiplatform_swisstransfer.database.RealmProvider
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.AppSettingsController
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.TransfersController
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.TransferController
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.UploadController

/**
* AccountManager is responsible for orchestrating Accounts operations using Realm for local data management.
*
* @property appSettingsController The controller for managing AppSettings operations.
* @property uploadController The controller for managing Upload operations.
* @property transfersController The controller for managing Transfers operation.
* @property transferController The controller for managing Transfers operation.
* @property realmProvider The provider for managing Realm database operations.
*/
class AccountManager internal constructor(
private val appSettingsController: AppSettingsController,
private val uploadController: UploadController,
private val transfersController: TransfersController,
private val transferController: TransferController,
private val realmProvider: RealmProvider,
) {

Expand All @@ -54,7 +54,7 @@ class AccountManager internal constructor(

appSettingsController.removeData()
uploadController.removeData()
transfersController.removeData()
transferController.removeData()

realmProvider.closeAllRealms()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,20 @@
*/
package com.infomaniak.multiplatform_swisstransfer.managers

import com.infomaniak.multiplatform_swisstransfer.database.RealmProvider
import com.infomaniak.multiplatform_swisstransfer.common.exceptions.UnknownException
import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.Transfer
import com.infomaniak.multiplatform_swisstransfer.database.cache.setting.TransferController
import com.infomaniak.multiplatform_swisstransfer.network.ApiClientProvider
import com.infomaniak.multiplatform_swisstransfer.network.exceptions.ApiException
import com.infomaniak.multiplatform_swisstransfer.network.exceptions.NetworkException
import com.infomaniak.multiplatform_swisstransfer.network.exceptions.UnknownApiException
import com.infomaniak.multiplatform_swisstransfer.network.models.transfer.TransferApi
import com.infomaniak.multiplatform_swisstransfer.network.repositories.TransferRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import kotlin.coroutines.cancellation.CancellationException

/**
* TransferManager is responsible for orchestrating data transfer operations
Expand All @@ -28,12 +40,82 @@ import com.infomaniak.multiplatform_swisstransfer.network.ApiClientProvider
* smooth and efficient data transfers, providing a centralized management point
* for transfer-related activities.
*
* @property realmProvider The provider for managing Realm database operations.
* @property clientProvider The provider for creating and configuring HTTP clients for API communication.
* @property transferController The provider for transfer data from database.
* @property transferRepository The provider for transfer data from api.
*/
class TransferManager internal constructor(
private val realmProvider: RealmProvider,
private val clientProvider: ApiClientProvider,
private val transferController: TransferController,
private val transferRepository: TransferRepository,
) {
// TODO: Implement here

/**
* The `Flow` of [transfers] is used to receive updates for new transfers added in database.
* @see addTransferByLinkUuid
* @see addTransferByUrl
*/
val transfers get() = transferController.getTransfersFlow().flowOn(Dispatchers.IO)
sirambd marked this conversation as resolved.
Show resolved Hide resolved

/**
* Retrieves a transfer using the provided link UUID and saves it to the database.
*
* This function is typically used after a transfer has been uploaded. Once the upload is complete,
* a `linkUuid` is returned, which must be passed to this function to retrieve the corresponding transfer.
* After retrieving the transfer, it is saved to the database.
*
* @see transfers
*
* @param linkUuid The UUID corresponding to the uploaded transfer link.
* @throws CancellationException If the operation is cancelled.
* @throws ApiException If there is an error related to the API during transfer retrieval.
* @throws UnknownApiException If an unknown error occurs while interacting with the API.
* @throws NetworkException If there is a network issue during the transfer retrieval.
* @throws UnknownException For any other unexpected errors that occur during the process.
sirambd marked this conversation as resolved.
Show resolved Hide resolved
*/
@Throws(
CancellationException::class,
ApiException::class,
UnknownApiException::class,
NetworkException::class,
UnknownException::class,
)
suspend fun addTransferByLinkUuid(linkUuid: String) = withContext(Dispatchers.IO) {
addTransfer(transferRepository.getTransferByLinkUuid(linkUuid).data)
}

/**
* Retrieves a transfer using the provided URL and saves it to the database.
*
* This function is used when a transfer URL is available. The provided `url` is used to retrieve
* the corresponding transfer, and after the transfer is successfully retrieved, it is saved to
* the database.
*
* @see transfers
*
* @param url The URL associated with the transfer to retrieve.
* @throws CancellationException If the operation is cancelled.
* @throws ApiException If there is an error related to the API during transfer retrieval.
* @throws UnknownApiException If an unknown error occurs while interacting with the API.
* @throws NetworkException If there is a network issue during the transfer retrieval.
* @throws UnknownException For any other unexpected errors that occur during the process.
*/
@Throws(
CancellationException::class,
ApiException::class,
UnknownApiException::class,
NetworkException::class,
UnknownException::class,
)
suspend fun addTransferByUrl(url: String) = withContext(Dispatchers.IO) {
addTransfer(transferRepository.getTransferByUrl(url).data)
}

private suspend fun addTransfer(transferApi: TransferApi?) {
runCatching {
transferController.upsert(transferApi as Transfer<*>)
}.onFailure {
throw UnknownException(it)
}
}
}
1 change: 1 addition & 0 deletions STDatabase/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ kotlin {
}
commonTest.dependencies {
implementation(libs.kotlin.test)
implementation(libs.kotlinx.coroutines.test)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class RealmProvider {
var realmTransfers: Realm? = null
private set

fun openRealmTransfers(userId: Int) {
realmTransfers = Realm.open(realmTransfersConfiguration(userId))
fun openRealmTransfers(userId: Int, inMemory: Boolean = false) {
realmTransfers = Realm.open(realmTransfersConfiguration(userId, inMemory))
}

fun closeRealmAppSettings() {
Expand Down Expand Up @@ -64,9 +64,10 @@ class RealmProvider {
.name("Uploads")
.build()

private fun realmTransfersConfiguration(userId: Int) = RealmConfiguration
private fun realmTransfersConfiguration(userId: Int, inMemory: Boolean) = RealmConfiguration
.Builder(schema = setOf(TransferDB::class, ContainerDB::class, FileDB::class))
.name(transferRealmName(userId))
.apply { if (inMemory) inMemory() }
.build()

private fun transferRealmName(userId: Int) = "Transfers-$userId"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,44 @@
*/
package com.infomaniak.multiplatform_swisstransfer.database.cache.setting

import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.Transfer
import com.infomaniak.multiplatform_swisstransfer.database.RealmProvider
import com.infomaniak.multiplatform_swisstransfer.database.models.transfers.TransferDB
import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.ext.query
import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.query.Sort
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.mapLatest
import kotlin.coroutines.cancellation.CancellationException

class TransfersController(private val realmProvider: RealmProvider) {
@OptIn(ExperimentalCoroutinesApi::class)
class TransferController(private val realmProvider: RealmProvider) {

private val realm by lazy { realmProvider.realmTransfers }

//region Get data
@Throws(IllegalArgumentException::class, CancellationException::class)
fun getTransfers(): RealmResults<TransferDB>? = realm?.query<TransferDB>()?.find()
fun getTransfers(): RealmResults<TransferDB>? {
return realm?.query<TransferDB>()?.sort(TransferDB::createdDateTimestamp.name, Sort.DESCENDING)?.find()
}

@Throws(IllegalArgumentException::class, CancellationException::class)
fun getTransfersFlow(): Flow<List<TransferDB>> = getTransfers()?.asFlow()?.mapLatest { it.list } ?: emptyFlow()

fun getTransfer(linkUuid: String): TransferDB? {
return realm?.query<TransferDB>("${TransferDB::linkUuid.name} == '$linkUuid'")?.first()?.find()
}
//endregion

//region Upsert data
suspend fun upsert(transfer: Transfer<*>) {
realm?.write {
this.copyToRealm(TransferDB(transfer), UpdatePolicy.ALL)
}
}
//endregion

//region Update data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
package com.infomaniak.multiplatform_swisstransfer.database.models.transfers

import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.Container
import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.File
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey

class ContainerDB : Container<RealmList<FileDB>>, RealmObject {
class ContainerDB() : Container<RealmList<FileDB>>, RealmObject {
@PrimaryKey
override var uuid: String = ""
override var duration: Long = 0L
Expand All @@ -41,4 +42,21 @@ class ContainerDB : Container<RealmList<FileDB>>, RealmObject {
// @SerialName("WSUser") // TODO: What's this ?
// val wsUser: JsonElement?
override var files: RealmList<FileDB> = realmListOf()

constructor(container: Container<List<File>>) : this() {
this.uuid = container.uuid
this.duration = container.duration
this.createdDateTimestamp = container.createdDateTimestamp
this.expiredDateTimestamp = container.expiredDateTimestamp
this.numberOfFiles = container.numberOfFiles
this.message = container.message
this.needPassword = container.needPassword
this.language = container.language
this.sizeUploaded = container.sizeUploaded
this.deletedDateTimestamp = container.deletedDateTimestamp
this.swiftVersion = container.swiftVersion
this.downloadLimit = container.downloadLimit
this.source = container.source
this.files = container.files.mapTo(realmListOf()) { FileDB(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.Fi
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey

class FileDB : File, RealmObject {
class FileDB() : File, RealmObject {
@PrimaryKey
override var containerUuid: String = ""
override var uuid: String = ""
Expand All @@ -36,4 +36,20 @@ class FileDB : File, RealmObject {
override var receivedSizeInBytes: Long = 0L
override var path: String? = ""
override var thumbnailPath: String? = ""

constructor(file: File) : this() {
this.containerUuid = file.containerUuid
this.uuid = file.uuid
this.fileName = file.fileName
this.fileSizeInBytes = file.fileSizeInBytes
this.downloadCounter = file.downloadCounter
this.createdDateTimestamp = file.createdDateTimestamp
this.expiredDateTimestamp = file.expiredDateTimestamp
this.eVirus = file.eVirus
this.deletedDate = file.deletedDate
this.mimeType = file.mimeType
this.receivedSizeInBytes = file.receivedSizeInBytes
this.path = file.path
this.thumbnailPath = file.thumbnailPath
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
*/
package com.infomaniak.multiplatform_swisstransfer.database.models.transfers

import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.Container
import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.File
import com.infomaniak.multiplatform_swisstransfer.common.interfaces.transfers.Transfer
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey

class TransferDB : Transfer<ContainerDB?>, RealmObject {
class TransferDB() : Transfer<ContainerDB?>, RealmObject {
@PrimaryKey
override var linkUuid: String = ""
override var containerUuid: String = ""
Expand All @@ -32,4 +34,17 @@ class TransferDB : Transfer<ContainerDB?>, RealmObject {
override var isMailSent: Boolean = false
override var downloadHost: String = ""
override var container: ContainerDB? = null

@Suppress("UNCHECKED_CAST")
constructor(transfer: Transfer<*>) : this() {
this.linkUuid = transfer.linkUuid
this.containerUuid = transfer.containerUuid
this.downloadCounterCredit = transfer.downloadCounterCredit
this.createdDateTimestamp = transfer.createdDateTimestamp
this.expiredDateTimestamp = transfer.expiredDateTimestamp
this.hasBeenDownloadedOneTime = transfer.hasBeenDownloadedOneTime
this.isMailSent = transfer.isMailSent
this.downloadHost = transfer.downloadHost
this.container = ContainerDB(transfer.container as Container<List<File>>)
}
}

This file was deleted.

Loading
Loading