Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ dependencies {
androidTestRuntimeOnly(libs.android.test.runner)

implementation(core.coil.gif)
implementation(kotlin("reflect"))
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding kotlin-reflect to the app increases APK size and can add startup/runtime overhead. Since the current usage is to enumerate sealed subclasses for DriveError, consider replacing the reflection-based registry with an explicit listOf(...)/mapOf(key -> DriveError) so this dependency can be avoided.

Suggested change
implementation(kotlin("reflect"))

Copilot uses AI. Check for mistakes.

// Compose
implementation(libs.androidx.ui.android)
Expand Down
15 changes: 12 additions & 3 deletions app/src/main/java/com/infomaniak/drive/data/api/UploadTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.infomaniak.drive.data.api.ApiRepository.uploadEmptyFile
import com.infomaniak.drive.data.api.ApiRoutes.uploadChunkUrl
import com.infomaniak.drive.data.models.UploadFile
import com.infomaniak.drive.data.models.drive.Drive.MaintenanceReason
import com.infomaniak.drive.data.models.up.DriveError
import com.infomaniak.drive.data.models.upload.UploadSession
import com.infomaniak.drive.data.models.upload.ValidChunks
import com.infomaniak.drive.data.services.UploadWorker
Expand Down Expand Up @@ -225,6 +226,7 @@ class UploadTask(
okHttpClient = uploadFile.okHttpClient
)
if (!closedSessionResponse.isSuccess()) closedSessionResponse.manageUploadErrors()
else uploadFile.updateUploadErrorKey(null)
}
}

Expand Down Expand Up @@ -412,6 +414,8 @@ class UploadTask(
ApiResponse<Any>(error = ApiError(description = bodyResponse))
}
apiResponse.manageUploadErrors()
} else {
uploadFile.updateUploadErrorKey(null)
}
}

Expand Down Expand Up @@ -473,16 +477,21 @@ class UploadTask(
)

return ApiRepository.startUploadSession(driveId, sessionBody, okHttpClient).also {
if (it.isSuccess()) it.data?.token?.let { uploadToken ->
uploadFile.updateUploadToken(uploadToken, it.data!!.uploadHost)
if (it.isSuccess()) it.data?.run {
uploadFile.updateUploadToken(token, uploadHost)
uploadFile.updateUploadErrorKey(null)
} else {
it.manageUploadErrors()
Comment thread
benjaminVadon marked this conversation as resolved.
}
Comment thread
benjaminVadon marked this conversation as resolved.
}.data?.uploadHost
}

private fun <T> ApiResponse<T>.manageUploadErrors() {
if (error?.exception is ApiControllerNetworkException) throw NetworkException()
if (error?.exception is ApiControllerNetworkException) {
uploadFile.updateUploadErrorKey(DriveError.Network.NetworkError.key)
throw NetworkException()
}
uploadFile.updateUploadErrorKey(error?.code)
when (error?.code) {
"file_already_exists_error" -> Unit
"lock_error" -> throw LockErrorException()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.infomaniak.drive.data.api.ApiRepository
import com.infomaniak.drive.data.api.UploadTask
import com.infomaniak.drive.data.api.UploadTask.Companion.ConflictOption
import com.infomaniak.drive.data.cache.DriveInfosController
import com.infomaniak.drive.data.models.up.DriveError
import com.infomaniak.drive.data.sync.UploadMigration
import com.infomaniak.drive.utils.AccountUtils
import com.infomaniak.drive.utils.IOFile
Expand Down Expand Up @@ -59,6 +60,7 @@ open class UploadFile(
@PrimaryKey var uri: String = "",
var deletedAt: Date? = null,
var driveId: Int = -1,
private var _driveErrorKey: String? = null,
var fileCreatedAt: Date? = null,
var fileModifiedAt: Date = Date(),
var fileName: String = "",
Expand All @@ -72,6 +74,9 @@ open class UploadFile(
var userId: Int = -1,
) : RealmObject() {

val error: DriveError?
get() = DriveError.find(_driveErrorKey)

@delegate:Ignore
val okHttpClient: OkHttpClient by lazy { runBlocking { AccountUtils.getHttpClient(userId = userId, timeout = 120) } }

Expand Down Expand Up @@ -138,6 +143,10 @@ open class UploadFile(
}
}

fun updateUploadErrorKey(driveErrorKey: String?) {
update { it._driveErrorKey = driveErrorKey }
}

fun deleteIfExists(keepFile: Boolean = false, customRealm: Realm? = null) {
when (customRealm) {
null -> getRealmInstance().use { deleteIfExistsInternal(keepFile = keepFile, it) }
Expand Down
133 changes: 133 additions & 0 deletions app/src/main/java/com/infomaniak/drive/data/models/up/DriveError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.drive.data.models.up

import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import com.infomaniak.drive.R
import com.infomaniak.drive.data.models.up.DriveError.Server.ServerError
import com.infomaniak.drive.data.models.up.DriveError.Server.ServerPlurals
import splitties.init.appCtx
import kotlin.reflect.KClass

sealed interface DriveError {
val key: String

fun interface Plurals {
fun message(quantity: Int): String
}

interface Message {
val message: String
}

sealed class Local(
override val key: String,
@field:StringRes private val id: Int = R.string.errorGeneric
) : DriveError, Message {
override val message: String
get() = appCtx.getString(id)

object CachingFailed : Local(key = "cachingFailed", id = R.string.errorCache)
object DownloadFailed : Local(key = "downloadFailed", id = R.string.errorDownload)
object ErrorDeviceStorage : Local(key = "errorDeviceStorage", id = R.string.errorDeviceStorage)
object FileNotFound : Local(key = "fileNotFound")
object LocalError : Local(key = "localError")
object MoveLocalError : Local(key = "moveLocalError", id = R.string.errorMove)
object PhotoAssetNoLongerExists : Local(key = "photoAssetNoLongerExists")
object PhotoLibraryWriteAccessDenied : Local(key = "writeAccessDenied", id = R.string.errorPhotoLibraryAccessLimited)
object SearchCancelled : Local(key = "searchCancelled")
object TaskCancelled : Local(key = "taskCancelled")
object TaskExpirationCancelled : Local(key = "taskExpirationCancelled")
object TaskRescheduled : Local(key = "taskRescheduled")
object UnknownError : Local(key = "unknownError")
object UnknownToken : Local(key = "unknownToken")
object UploadOverDataRestricted : Local(key = "uploadOverDataRestricted", id = R.string.uploadOverDataRestrictedError)
}

sealed interface Network : DriveError, Message {
override val key: String
get() = "networkError"
override val message: String
get() = appCtx.getString(R.string.errorNetwork)

object NetworkError : Network
}

sealed interface Server : DriveError {
sealed class ServerPlurals(
override val key: String,
@field:PluralsRes private val id: Int
) : Server, Plurals {
override fun message(quantity: Int): String = appCtx.resources.getQuantityString(id, quantity, quantity)
}

sealed class ServerError(
override val key: String,
@field:StringRes private val id: Int = R.string.errorGeneric
) : Server, Message {
override val message: String
get() = appCtx.getString(id)
}

object CategoryAlreadyExists : ServerError(key = "category_already_exist_error", id = R.string.errorCategoryAlreadyExists)
object Conflict : ServerError(key = "conflict_error", id = R.string.errorConflict)
object DestinationAlreadyExists : ServerError(key = "destination_already_exists", id = R.string.errorFileAlreadyExists)
object DownloadPermission : ServerError(key = "you_must_add_at_least_one_file", id = R.string.errorDownloadPermission)
object DriveMaintenance : ServerError(key = "drive_is_in_maintenance_error", id = R.string.driveMaintenanceDescription)
object FileAlreadyExistsError : ServerError(key = "file_already_exists_error", id = R.string.errorFileAlreadyExists)
object Forbidden : ServerError(key = "forbidden_error", id = R.string.accessDeniedTitle)
object InvalidCursorError : ServerError(key = "invalid_cursor_error")
object InvalidUploadTokenError : ServerError(key = "invalid_upload_token_error")
object LimitExceededError : ServerError(key = "limit_exceeded_error", id = R.string.errorLimitExceeded)
object LockError : ServerError(key = "lock_error", id = R.string.errorFileLocked)
object NoDrive : ServerError(key = "no_drive")
object NotAuthorized : ServerError(key = "not_authorized")
object ObjectNotFound : ServerError(key = "object_not_found", id = R.string.uploadFolderNotFoundError)
object ProductBlocked : ServerPlurals(key = "product_blocked", id = R.plurals.driveBlockedDescription)
object ProductMaintenance : ServerError(key = "product_maintenance", id = R.string.driveMaintenanceDescription)
object QuotaExceededError : ServerError(key = "quota_exceeded_error", id = R.string.notEnoughStorageDescription1)
object RefreshToken : ServerError(key = "refreshToken")
object Generic : ServerError(key = "serverError")
object ShareLinkAlreadyExists : ServerError(key = "file_share_link_already_exists", id = R.string.errorShareLink)
object StillUploadingError : ServerError(key = "still_uploading_error", id = R.string.errorStillUploading)
object UploadDestinationNotFoundError : ServerError(key = "upload_destination_not_found_error")
object UploadDestinationNotWritableError : ServerError(key = "upload_destination_not_writable_error")
object UploadError : ServerError(key = "upload_error")
object UploadFailedError : ServerError(key = "upload_failed_error")
object UploadNotTerminated : ServerError(key = "upload_not_terminated")
object UploadNotTerminatedError : ServerError(key = "upload_not_terminated_error")
object UploadTokenCanceled : ServerError(key = "upload_token_canceled")
object UploadTokenIsNotValid : ServerError(key = "upload_token_is_not_valid")
}


companion object {
fun find(key: String?): DriveError? = key?.let { allValues[key] }

private val allValues: Map<String, DriveError> by lazy { buildAllValuesList() }

private fun buildAllValuesList(): Map<String, DriveError> {
return Local::class.values() + Network::class.values() + ServerError::class.values() + ServerPlurals::class.values()
}

private inline fun <reified T : DriveError> KClass<T>.values(): Map<String, DriveError> =
sealedSubclasses.map { it.objectInstance as DriveError }.associateBy { it.key }

Comment on lines +120 to +131
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DriveError builds its registry via Kotlin reflection (sealedSubclasses/objectInstance) and then does a linear search in find(). This is brittle (will crash if a non-object subclass is added) and requires kotlin-reflect. Prefer an explicit Map<String, DriveError> registry (can be generated manually) to remove reflection and guarantee O(1) lookups. Also note that many server keys here duplicate data/api/ErrorCode.kt; reusing that source of truth would reduce drift.

Copilot uses AI. Check for mistakes.
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak kDrive - Android
* Copyright (C) 2022-2024 Infomaniak Network SA
* Copyright (C) 2022-2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -116,9 +116,13 @@ class UploadMigration : RealmMigration {
?.addField("onlyWifiSyncMedia", Boolean::class.java, FieldAttribute.REQUIRED)
}

if (oldVersion < 9L) { //stop use of oldVersionTemp as it is useless
schema.get(UploadFile::class.java.simpleName)
?.addField("_driveErrorKey", String::class.java)
}
}

companion object {
const val DB_VERSION = 8L // Must be bumped when the schema changes
const val DB_VERSION = 9L // Must be bumped when the schema changes
}
}
3 changes: 3 additions & 0 deletions app/src/main/res/values-da/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@
<string name="errorFileAlreadyExists">Filen eller mappen findes allerede</string>
<string name="errorFileLocked">Filen er i øjeblikket åben af en anden bruger og kan ikke ændres</string>
<string name="errorFilesLimitExceeded">Grænsen for filer i mappen nået</string>
<string name="errorGeneric">Der er opstået en fejl</string>
<string name="errorGetBookmarkURL">Ikke-gendannelseslink</string>
<string name="errorLeaveShare">Fejl ved afslutning af deling</string>
<string name="errorLimitExceeded">Grænsen nået</string>
Expand All @@ -277,6 +278,7 @@
<string name="errorMove">Flytningsfejl</string>
<string name="errorNetwork">Netværksfejl</string>
<string name="errorNetworkDescription">Resultaterne kan være ufuldstændige</string>
<string name="errorPhotoLibraryAccessLimited">kDrive har ikke adgang til dit fotobibliotek</string>
<string name="errorPreviewDeleted">Denne fil er blevet slettet permanent</string>
<string name="errorPreviewTrash">Forhåndsvisning af en fil i papirkurven er ikke mulig</string>
<string name="errorQuotaExceeded">Ikke nok plads på din kDrive</string>
Expand Down Expand Up @@ -760,6 +762,7 @@
<string name="uploadNetworkErrorTitle">Upload suspenderet</string>
<string name="uploadNetworkErrorWifiRequired">Venter på WiFi-netværk</string>
<string name="uploadOutOfMemoryError">Utilstrækkelig tilgængelig RAM-hukommelse</string>
<string name="uploadOverDataRestrictedError">Venter på Wi-Fi</string>
<string name="uploadPausedDescription">Nogle uploads er stadig i gang i kDrive. Åbn appen og hold enheden tilsluttet for at fremskynde upload.</string>
<string name="uploadPausedTitle">Upload suspenderet</string>
<string name="uploadPermissionError">Yderligere tilladelser anmodet</string>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@
<string name="errorFileAlreadyExists">Datei oder Ordner existiert bereits</string>
<string name="errorFileLocked">Diese Datei ist bereits von einem Benutzer geöffnet und kann nicht verändert werden</string>
<string name="errorFilesLimitExceeded">Limit der Dateien im Ordner erreicht</string>
<string name="errorGeneric">Ein Fehler ist aufgetreten</string>
<string name="errorGetBookmarkURL">Link nicht wiederherstellbar</string>
<string name="errorLeaveShare">Fehler beim Abbrechen der Freigabe</string>
<string name="errorLimitExceeded">Limit erreicht</string>
Expand All @@ -277,6 +278,7 @@
<string name="errorMove">Fehler beim Verschieben</string>
<string name="errorNetwork">Verbindungsproblem</string>
<string name="errorNetworkDescription">Die Ergebnisse können unvollständig sein</string>
<string name="errorPhotoLibraryAccessLimited">kDrive hat keinen Zugriff auf Ihre Fotobibliothek</string>
<string name="errorPreviewDeleted">Diese Datei wurde endgültig gelöscht</string>
<string name="errorPreviewTrash">Die Vorschau auf eine Datei im Papierkorb ist nicht verfügbar</string>
<string name="errorQuotaExceeded">Unzureichender Platz auf Ihrem kDrive</string>
Expand Down Expand Up @@ -760,6 +762,7 @@
<string name="uploadNetworkErrorTitle">Import ausgesetzt</string>
<string name="uploadNetworkErrorWifiRequired">Warten auf WLAN</string>
<string name="uploadOutOfMemoryError">Unzureichend verfügbarer RAM</string>
<string name="uploadOverDataRestrictedError">Warten auf Wi-Fi</string>
<string name="uploadPausedDescription">Einige Uploads sind in kDrive noch im Gange. Öffnen Sie die App und lassen Sie das Gerät verbunden, um den Upload zu beschleunigen.</string>
<string name="uploadPausedTitle">Pause importieren</string>
<string name="uploadPermissionError">Zusätzlich beantragte Genehmigungen</string>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values-el/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@
<string name="errorFileAlreadyExists">Το αρχείο ή ο φάκελος υπάρχει ήδη</string>
<string name="errorFileLocked">Αυτό το αρχείο είναι αυτή τη στιγμή ανοιχτό από άλλον χρήστη και δεν μπορεί να τροποποιηθεί</string>
<string name="errorFilesLimitExceeded">Έχει επιτευχθεί το όριο αρχείων στον φάκελο</string>
<string name="errorGeneric">Παρουσιάστηκε σφάλμα</string>
<string name="errorGetBookmarkURL">Σύνδεσμος μη ανακτήσιμος</string>
<string name="errorLeaveShare">Σφάλμα κατά τη διακοπή της κοινής χρήσης</string>
<string name="errorLimitExceeded">Υπέρβαση ορίου</string>
Expand All @@ -277,6 +278,7 @@
<string name="errorMove">Σφάλμα μετακίνησης</string>
<string name="errorNetwork">Σφάλμα δικτύου</string>
<string name="errorNetworkDescription">Τα αποτελέσματα ενδέχεται να είναι ελλιπή</string>
<string name="errorPhotoLibraryAccessLimited">Το kDrive δεν έχει πρόσβαση στη βιβλιοθήκη φωτογραφιών σας</string>
<string name="errorPreviewDeleted">Αυτό το αρχείο διαγράφηκε οριστικά</string>
<string name="errorPreviewTrash">Η προεπισκόπηση αρχείου στον κάδο δεν είναι δυνατή</string>
<string name="errorQuotaExceeded">Δεν υπάρχει αρκετός χώρος στο kDrive σας</string>
Expand Down Expand Up @@ -760,6 +762,7 @@
<string name="uploadNetworkErrorTitle">Η μεταφόρτωση ανεστάλη</string>
<string name="uploadNetworkErrorWifiRequired">Αναμονή για δίκτυο Wi-Fi</string>
<string name="uploadOutOfMemoryError">Ανεπαρκής διαθέσιμη μνήμη RAM</string>
<string name="uploadOverDataRestrictedError">Αναμονή για Wi-Fi</string>
<string name="uploadPausedDescription">Κάποιες μεταφορτώσεις εξακολουθούν να βρίσκονται σε εξέλιξη στο kDrive. Ανοίξτε την εφαρμογή και κρατήστε τη συσκευή συνδεδεμένη για να επιταχύνετε τη μεταφόρτωση.</string>
<string name="uploadPausedTitle">Η μεταφόρτωση ανεστάλη</string>
<string name="uploadPermissionError">Ζητούνται πρόσθετες άδειες</string>
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@
<string name="errorFileAlreadyExists">Archivo o carpeta ya existente</string>
<string name="errorFileLocked">El archivo está actualmente abierto por un usuario y no puede ser modificado</string>
<string name="errorFilesLimitExceeded">Límite de archivos alcanzado en la carpeta</string>
<string name="errorGeneric">Se ha producido un error</string>
<string name="errorGetBookmarkURL">Enlace no recuperable</string>
<string name="errorLeaveShare">Error al salir del uso compartido</string>
<string name="errorLimitExceeded">Límite alcanzado</string>
Expand All @@ -277,6 +278,7 @@
<string name="errorMove">Error al moverlo</string>
<string name="errorNetwork">Problema de conexión</string>
<string name="errorNetworkDescription">Los resultados pueden ser incompletos</string>
<string name="errorPhotoLibraryAccessLimited">kDrive no tiene acceso a tu biblioteca de fotos</string>
<string name="errorPreviewDeleted">Se ha eliminado definitivamente este archivo</string>
<string name="errorPreviewTrash">La vista previa de un archivo en la papelera no está disponible</string>
<string name="errorQuotaExceeded">Espacio insuficiente en tu kDrive</string>
Expand Down Expand Up @@ -760,6 +762,7 @@
<string name="uploadNetworkErrorTitle">Importación suspendida</string>
<string name="uploadNetworkErrorWifiRequired">A la espera de red WiFi</string>
<string name="uploadOutOfMemoryError">Insuficiente memoria RAM disponible</string>
<string name="uploadOverDataRestrictedError">A la espera de Wi-Fi</string>
<string name="uploadPausedDescription">Algunas subidas siguen en curso en kDrive. Abre la aplicación y mantén el dispositivo conectado para acelerar la carga.</string>
<string name="uploadPausedTitle">Pausa de importación</string>
<string name="uploadPermissionError">Permisos adicionales solicitados</string>
Expand Down
Loading
Loading