Исправлено множество предупреждений

This commit is contained in:
2026-05-19 01:42:22 +03:00
parent eecaf44b72
commit ffdab4563d
64 changed files with 241 additions and 567 deletions

View File

@@ -1,15 +0,0 @@
package com.github.nullptroma.wallenc.domain.vault.auth
/**
* Scope-ы Яндекс.OAuth, которые нам нужны: только app_folder + disk.info.
*
* Используется как ссылка для синхронизации с консолью Yandex OAuth.
* Сам Yandex Auth SDK для WEBVIEW-логина запрашивает scope-ы, выставленные
* у приложения в OAuth-консоли; мы держим список здесь, чтобы было одно место правды.
*/
object YandexOAuthScopes {
const val DISK_APP_FOLDER = "cloud_api:disk.app_folder"
const val DISK_INFO = "cloud_api:disk.info"
val ALL: Set<String> = setOf(DISK_APP_FOLDER, DISK_INFO)
}

View File

@@ -9,13 +9,13 @@ import java.io.IOException
fun Throwable.toVaultWallencException(): WallencException = when (this) {
is WallencException -> this
is YandexDiskAuthException -> WallencException.Auth.Failed
is YandexDiskAuthException -> WallencException.Auth.Failed()
is HttpException -> WallencException.Network.HttpFailed(
operation = "http",
statusCode = code(),
cause = this,
)
is FileNotFoundException -> WallencException.Storage.FileNotFound
is FileNotFoundException -> WallencException.Storage.FileNotFound()
is IOException -> mapVaultIo(this)
is IllegalStateException -> mapIllegalState(this)
is IllegalArgumentException -> mapIllegalArgument(this)
@@ -26,29 +26,29 @@ private fun mapVaultIo(e: IOException): WallencException {
val msg = e.message.orEmpty()
return when {
msg.contains("OAuth token is missing", ignoreCase = true) ->
WallencException.Auth.TokenMissing
WallencException.Auth.TokenMissing()
msg.contains("HTTP 423", ignoreCase = true) || msg.contains("423 after retries", ignoreCase = true) ->
WallencException.Network.ResourceLocked
WallencException.Network.ResourceLocked()
msg.contains("async operation timed out", ignoreCase = true) ->
WallencException.Network.OperationTimedOut
WallencException.Network.OperationTimedOut()
msg.contains("async operation failed", ignoreCase = true) ->
WallencException.Network.OperationFailed
WallencException.Network.OperationFailed()
else -> WallencException.Network.IoFailed(e)
}
}
private fun mapIllegalState(e: IllegalStateException): WallencException = when {
e.message == "Not a file" -> WallencException.Storage.NotAFile
e.message == "Not a directory" -> WallencException.Storage.NotADirectory
e.message?.startsWith("Path segment is a file:") == true -> WallencException.Storage.PathIsFile
e.message == "Not a file" -> WallencException.Storage.NotAFile()
e.message == "Not a directory" -> WallencException.Storage.NotADirectory()
e.message?.startsWith("Path segment is a file:") == true -> WallencException.Storage.PathIsFile()
e.message?.startsWith("Cannot openWrite over directory:") == true ->
WallencException.Storage.CannotWriteOverDirectory
WallencException.Storage.CannotWriteOverDirectory()
e.message?.startsWith("Expected file after upload:") == true ->
WallencException.Storage.UnexpectedState
else -> WallencException.Storage.UnexpectedState
WallencException.Storage.UnexpectedState()
else -> WallencException.Storage.UnexpectedState()
}
private fun mapIllegalArgument(e: IllegalArgumentException): WallencException = when {
e.message == "Deleting root path is forbidden" -> WallencException.Storage.DeleteRootForbidden
e.message == "Deleting root path is forbidden" -> WallencException.Storage.DeleteRootForbidden()
else -> WallencException.Unknown(e)
}

View File

@@ -12,7 +12,6 @@ import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Query
import retrofit2.http.Url
@@ -46,13 +45,6 @@ interface YandexDiskApi {
@Query("permanently") permanently: Boolean,
): Response<ResponseBody>
@POST("v1/disk/resources/move")
suspend fun moveResource(
@Query("from") from: String,
@Query("path") toPath: String,
@Query("overwrite") overwrite: Boolean = false,
): Response<ResponseBody>
@GET("v1/disk/resources/upload")
suspend fun getUploadLink(
@Query("path") path: String,

View File

@@ -33,7 +33,7 @@ class YandexDiskApiFactory(
/**
* [tokenProvider] вызывается на каждый HTTP-запрос к cloud-api (свежий токен из БД).
*/
fun createAuthenticatedApi(tokenProvider: () -> String?): com.github.nullptroma.wallenc.domain.vault.network.yandexdisk.YandexDiskApi {
fun createAuthenticatedApi(tokenProvider: () -> String?): YandexDiskApi {
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val token = tokenProvider()

View File

@@ -47,13 +47,6 @@ data class OperationStatusDto(
val status: String? = null,
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class ApiErrorDto(
val message: String? = null,
val description: String? = null,
val error: String? = null,
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class CustomPropertiesPatchDto(
@param:JsonProperty("custom_properties") val customProperties: Map<String, String>,

View File

@@ -110,9 +110,9 @@ class UnlockManager(
rememberPassword: Boolean
): EncryptedStorage = withContext(ioDispatcher) {
return@withContext mutex.withLock {
val encInfo = storage.metaInfo.value.encInfo ?: throw WallencException.Storage.EncInfoMissing
val encInfo = storage.metaInfo.value.encInfo ?: throw WallencException.Storage.EncInfoMissing()
if (!Encryptor.checkKey(key, encInfo))
throw WallencException.Storage.IncorrectKey
throw WallencException.Storage.IncorrectKey()
val opened = _openedStorages.value.toMutableMap()
val cur = opened[storage.uuid]

View File

@@ -29,7 +29,7 @@ class EncryptedStorage private constructor(
private val scope = CoroutineScope(ioDispatcher + job)
private val encInfo =
source.metaInfo.value.encInfo ?: throw WallencException.Storage.NotEncrypted
source.metaInfo.value.encInfo ?: throw WallencException.Storage.NotEncrypted()
override val isVirtualStorage: Boolean = true
@@ -52,7 +52,7 @@ class EncryptedStorage private constructor(
private fun checkKey() {
if (!Encryptor.checkKey(key, encInfo))
throw WallencException.Storage.IncorrectKey
throw WallencException.Storage.IncorrectKey()
}
fun getKey(): EncryptKey = EncryptKey(key.bytes)

View File

@@ -128,18 +128,10 @@ class EncryptedStorageAccessor(
return source.getFiles(encryptPath(systemHiddenDirName))
}
private fun encryptEntity(file: IFile): IFile {
return CommonFile(encryptMeta(file.metaInfo))
}
private fun decryptEntity(file: IFile): IFile {
return CommonFile(decryptMeta(file.metaInfo))
}
private fun encryptEntity(dir: IDirectory): IDirectory {
return CommonDirectory(encryptMeta(dir.metaInfo), dir.elementsCount)
}
private fun decryptEntity(dir: IDirectory): IDirectory {
return CommonDirectory(decryptMeta(dir.metaInfo), dir.elementsCount)
}
@@ -303,12 +295,11 @@ class EncryptedStorageAccessor(
return emptyList()
}
return runCatching {
val javaType = jackson.typeFactory.constructCollectionType(
val journalType = jackson.typeFactory.constructCollectionType(
List::class.java,
StorageSyncJournalEntry::class.java,
)
@Suppress("UNCHECKED_CAST")
(jackson.readValue(bytes, javaType) as List<StorageSyncJournalEntry>)
jackson.readValue<List<StorageSyncJournalEntry>>(bytes, journalType)
}.getOrElse {
emptyList()
}

View File

@@ -214,10 +214,6 @@ class LocalStorageAccessor(
val filePath = Path(filesystemBasePath.pathString, storagePath)
return from(filesystemBasePath, filePath.toFile())
}
fun from(filesystemBasePath: Path, meta: IMetaInfo): LocalStorageFilePair? {
return from(filesystemBasePath, meta.path)
}
}
}
@@ -232,7 +228,7 @@ class LocalStorageAccessor(
dirCallback: (suspend (File, CommonDirectory) -> Unit)? = null
) {
if (!checkAvailable())
throw WallencException.Storage.NotAvailable
throw WallencException.Storage.NotAvailable()
val basePath = Path(_filesystemBasePath.pathString, baseStoragePath)
val workedFiles = mutableSetOf<String>()
val workedMetaFiles = mutableSetOf<String>()
@@ -398,7 +394,7 @@ class LocalStorageAccessor(
override suspend fun getFileInfo(path: String): IFile {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw WallencException.Storage.UnexpectedState
?: throw WallencException.Storage.UnexpectedState()
return CommonFile(
metaInfo = pair.meta,
)
@@ -406,7 +402,7 @@ class LocalStorageAccessor(
override suspend fun getDirInfo(path: String): IDirectory {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw WallencException.Storage.UnexpectedState
?: throw WallencException.Storage.UnexpectedState()
return CommonDirectory(
metaInfo = pair.meta,
elementsCount = null
@@ -415,7 +411,7 @@ class LocalStorageAccessor(
override suspend fun setHidden(path: String, hidden: Boolean) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw WallencException.Storage.UnexpectedState
?: throw WallencException.Storage.UnexpectedState()
if (pair.meta.isHidden == hidden)
return
val newMeta = pair.meta.copy(isHidden = hidden)
@@ -442,7 +438,7 @@ class LocalStorageAccessor(
val path = Path(_filesystemBasePath.pathString, storagePath)
val file = path.toFile()
if (file.exists() && file.isDirectory) {
throw WallencException.Storage.UnexpectedState
throw WallencException.Storage.UnexpectedState()
} else if(!file.exists()) {
val parent = Path(storagePath).parent
createDir(parent.pathString)
@@ -453,7 +449,7 @@ class LocalStorageAccessor(
}
val pair = LocalStorageFilePair.from(_filesystemBasePath, file)
?: throw WallencException.Storage.UnexpectedState
?: throw WallencException.Storage.UnexpectedState()
val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant(), size = Files.size(pair.file.toPath()))
writeMeta(pair.metaFile, newMeta)
_filesUpdates.emit(
@@ -470,13 +466,13 @@ class LocalStorageAccessor(
val path = Path(_filesystemBasePath.pathString, storagePath)
val file = path.toFile()
if (file.exists() && !file.isDirectory) {
throw WallencException.Storage.UnexpectedState
throw WallencException.Storage.UnexpectedState()
} else if(!file.exists()) {
Files.createDirectories(path)
}
val pair = LocalStorageFilePair.from(_filesystemBasePath, file)
?: throw WallencException.Storage.UnexpectedState
?: throw WallencException.Storage.UnexpectedState()
val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant())
writeMeta(pair.metaFile, newMeta)
_dirsUpdates.emit(
@@ -514,7 +510,7 @@ class LocalStorageAccessor(
override suspend fun delete(path: String) = withContext(ioDispatcher) {
if (path == "/" || path.isBlank()) {
throw WallencException.Storage.DeleteRootForbidden
throw WallencException.Storage.DeleteRootForbidden()
}
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
if (pair != null) {
@@ -529,7 +525,7 @@ class LocalStorageAccessor(
override suspend fun openWrite(path: String): OutputStream = withContext(ioDispatcher) {
touchFileInternal(path, recordJournal = false)
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw WallencException.Storage.FileNotFound
?: throw WallencException.Storage.FileNotFound()
return@withContext pair.file.outputStream().onClosed {
CoroutineScope(ioDispatcher).launch {
touchFileInternal(path, recordJournal = false)
@@ -541,13 +537,13 @@ class LocalStorageAccessor(
override suspend fun openRead(path: String): InputStream = withContext(ioDispatcher) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw WallencException.Storage.FileNotFound
?: throw WallencException.Storage.FileNotFound()
return@withContext pair.file.inputStream()
}
override suspend fun moveToTrash(path: String) = withContext(ioDispatcher) {
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
?: throw WallencException.Storage.FileNotFound
?: throw WallencException.Storage.FileNotFound()
val newMeta = pair.meta.copy(isDeleted = true)
writeMeta(pair.metaFile, newMeta)
appendSyncEntry(path = path, operation = StorageSyncOperation.DELETE)

View File

@@ -107,7 +107,7 @@ class YandexStorageAccessor(
} catch (e: YandexDiskAuthException) {
reportAuthFailure()
_storageReady.value = false
throw WallencException.Auth.Failed
throw WallencException.Auth.Failed()
} catch (e: Exception) {
_storageReady.value = false
throw e.toVaultWallencException()
@@ -121,7 +121,7 @@ class YandexStorageAccessor(
throw e
} catch (e: YandexDiskAuthException) {
reportAuthFailure()
throw WallencException.Auth.Failed
throw WallencException.Auth.Failed()
} catch (e: Throwable) {
throw e.toVaultWallencException()
}
@@ -176,9 +176,7 @@ class YandexStorageAccessor(
systemDirEnsured = true
}
private fun statsFileRel(): String = "/$SYSTEM_HIDDEN_DIRNAME/$STATS_FILENAME"
private fun statsDiskPath(): String = toDiskPath(statsFileRel())
private fun statsDiskPath(): String = toDiskPath("/$SYSTEM_HIDDEN_DIRNAME/$STATS_FILENAME")
private suspend fun readPersistedStats(): YandexVaultPersistedStats? {
val meta = guard { repo.getOrNull(statsDiskPath()) } ?: return null
@@ -452,13 +450,13 @@ class YandexStorageAccessor(
override suspend fun getFileInfo(path: String): IFile = withContext(ioDispatcher) {
val r = guard { repo.get(toDiskPath(path)) }
if (r.type != "file") throw WallencException.Storage.NotAFile
if (r.type != "file") throw WallencException.Storage.NotAFile()
r.toCommonFile(path)
}
override suspend fun getDirInfo(path: String): IDirectory = withContext(ioDispatcher) {
val r = guard { repo.get(toDiskPath(path)) }
if (r.type != "dir") throw WallencException.Storage.NotADirectory
if (r.type != "dir") throw WallencException.Storage.NotADirectory()
r.toCommonDir(path)
}
@@ -497,7 +495,7 @@ class YandexStorageAccessor(
val diskPath = toDiskPath(acc)
when (guard { repo.getOrNull(diskPath) }?.type) {
"dir" -> continue
"file" -> throw WallencException.Storage.PathIsFile
"file" -> throw WallencException.Storage.PathIsFile()
else -> guard { repo.createFolder(diskPath) }
}
}
@@ -505,7 +503,7 @@ class YandexStorageAccessor(
override suspend fun delete(path: String) = withContext(ioDispatcher) {
if (path == "/" || path.isBlank()) {
throw WallencException.Storage.DeleteRootForbidden
throw WallencException.Storage.DeleteRootForbidden()
}
val diskPath = toDiskPath(path)
val prior = guard { repo.getOrNull(diskPath) }
@@ -538,14 +536,14 @@ class YandexStorageAccessor(
val diskPath = toDiskPath(path)
val prior = guard { repo.getOrNull(diskPath) }
if (prior?.type == "dir") {
throw WallencException.Storage.CannotWriteOverDirectory
throw WallencException.Storage.CannotWriteOverDirectory()
}
val hadFile = prior?.type == "file"
val priorSize = if (prior?.type == "file") prior.size ?: 0L else 0L
guard { repo.uploadFile(diskPath, tmp, overwrite = true) }
val after = guard { getMetadataAfterWrite(diskPath) }
if (after.type != "file") {
throw WallencException.Storage.UnexpectedState
throw WallencException.Storage.UnexpectedState()
}
val newSize = after.size ?: 0L
_size.value = ((_size.value ?: 0L) + newSize - priorSize).coerceAtLeast(0L)
@@ -605,12 +603,11 @@ class YandexStorageAccessor(
return@withContext emptyList()
}
return@withContext runCatching {
val javaType = statsMapper.typeFactory.constructCollectionType(
val journalType = statsMapper.typeFactory.constructCollectionType(
List::class.java,
StorageSyncJournalEntry::class.java,
)
@Suppress("UNCHECKED_CAST")
(statsMapper.readValue(bytes, javaType) as List<StorageSyncJournalEntry>)
statsMapper.readValue<List<StorageSyncJournalEntry>>(bytes, journalType)
}.getOrElse {
emptyList()
}

View File

@@ -89,16 +89,8 @@ class CloseHandledStreamExtension {
return CloseHandledOutputStream(this, {}, callback)
}
fun InputStream.onClosed(callback: ()->Unit): InputStream {
return CloseHandledInputStream(this, {}, callback)
}
fun OutputStream.onClosing(callback: ()->Unit): OutputStream {
return CloseHandledOutputStream(this, callback) {}
}
fun InputStream.onClosing(callback: ()->Unit): InputStream {
return CloseHandledInputStream(this, callback) {}
}
}
}

View File

@@ -66,7 +66,7 @@ class LocalVault(
private suspend fun readStorages() {
val path = path.value
if (path == null || !_isAvailable.value) {
throw WallencException.Storage.NotAvailable
throw WallencException.Storage.NotAvailable()
}
val dirs = path.listFiles()?.filter { it.isDirectory }
@@ -81,7 +81,7 @@ class LocalVault(
override suspend fun createStorage(): LocalStorage = withContext(ioDispatcher) {
val path = path.value
if (path == null || !_isAvailable.value) {
throw WallencException.Storage.NotAvailable
throw WallencException.Storage.NotAvailable()
}
val storageUuid = UUID.randomUUID()
@@ -106,7 +106,7 @@ class LocalVault(
override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) {
val path = path.value
if (path == null || !_isAvailable.value) {
throw WallencException.Storage.NotAvailable
throw WallencException.Storage.NotAvailable()
}
val curStorages = _storages.value.toMutableList()

View File

@@ -16,7 +16,7 @@ class VaultThrowableMappingTest {
@Test
fun mapsYandexDiskAuthToAuthFailed() {
val mapped = YandexDiskAuthException("unauthorized").toVaultWallencException()
assertEquals(WallencException.Auth.Failed, mapped)
assertTrue(mapped is WallencException.Auth.Failed)
}
@Test
@@ -30,18 +30,18 @@ class VaultThrowableMappingTest {
@Test
fun mapsMissingOAuthTokenIoToTokenMissing() {
val mapped = IOException("Yandex OAuth token is missing").toVaultWallencException()
assertEquals(WallencException.Auth.TokenMissing, mapped)
assertTrue(mapped is WallencException.Auth.TokenMissing)
}
@Test
fun mapsFileNotFoundToStorageFileNotFound() {
val mapped = FileNotFoundException("x").toVaultWallencException()
assertEquals(WallencException.Storage.FileNotFound, mapped)
assertTrue(mapped is WallencException.Storage.FileNotFound)
}
@Test
fun mapsIllegalStateNotAFile() {
val mapped = IllegalStateException("Not a file").toVaultWallencException()
assertEquals(WallencException.Storage.NotAFile, mapped)
assertTrue(mapped is WallencException.Storage.NotAFile)
}
}

View File

@@ -1,9 +1,6 @@
package com.github.nullptroma.wallenc.domain.vault.network.yandexdisk
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.github.nullptroma.wallenc.domain.vault.network.yandexdisk.repository.YandexDiskRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
@@ -13,16 +10,6 @@ object YandexDiskRepositoryTestFactory {
private val jackson = jacksonObjectMapper().findAndRegisterModules()
fun create(
baseUrl: String,
oauthToken: String,
ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
rawHttp: OkHttpClient = OkHttpClient(),
): YandexDiskRepository {
val api = createApi(baseUrl) { oauthToken }
return YandexDiskRepository(api, rawHttp, ioDispatcher)
}
fun createApi(
baseUrl: String,
tokenProvider: () -> String?,