Исправлено много варнингов
This commit is contained in:
@@ -17,7 +17,7 @@ dependencies {
|
||||
implementation(libs.retrofit.converter.scalars)
|
||||
implementation(libs.retrofit.converter.jackson)
|
||||
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
implementation(libs.okhttp3)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
|
||||
@@ -6,9 +6,9 @@ import java.time.Instant
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class DiskInfoDto(
|
||||
@JsonProperty("trash_size") val trashSize: Long? = null,
|
||||
@JsonProperty("total_space") val totalSpace: Long? = null,
|
||||
@JsonProperty("used_space") val usedSpace: Long? = null,
|
||||
@param:JsonProperty("trash_size") val trashSize: Long? = null,
|
||||
@param:JsonProperty("total_space") val totalSpace: Long? = null,
|
||||
@param:JsonProperty("used_space") val usedSpace: Long? = null,
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@@ -36,10 +36,10 @@ data class ResourceDto(
|
||||
val size: Long? = null,
|
||||
val modified: Instant? = null,
|
||||
val created: Instant? = null,
|
||||
@JsonProperty("mime_type") val mimeType: String? = null,
|
||||
@param:JsonProperty("mime_type") val mimeType: String? = null,
|
||||
val md5: String? = null,
|
||||
@JsonProperty("custom_properties") val customProperties: Map<String, Any?>? = null,
|
||||
@JsonProperty("_embedded") val embedded: EmbeddedResourceListDto? = null,
|
||||
@param:JsonProperty("custom_properties") val customProperties: Map<String, Any?>? = null,
|
||||
@param:JsonProperty("_embedded") val embedded: EmbeddedResourceListDto? = null,
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@@ -56,5 +56,5 @@ data class ApiErrorDto(
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class CustomPropertiesPatchDto(
|
||||
@JsonProperty("custom_properties") val customProperties: Map<String, String>,
|
||||
@param:JsonProperty("custom_properties") val customProperties: Map<String, String>,
|
||||
)
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.github.nullptroma.wallenc.infrastructure.network.yandexdisk.dto.Resou
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
@@ -21,6 +20,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
@@ -74,20 +74,6 @@ class YandexDiskRepository(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun move(from: String, toPath: String, overwrite: Boolean = false): Unit =
|
||||
withContext(ioDispatcher) {
|
||||
val resp = wrapAuth { api.moveResource(from, toPath, overwrite) }
|
||||
when (resp.code()) {
|
||||
201 -> Unit
|
||||
202 -> {
|
||||
val link = resp.body()?.use { body -> parseLink(body) }
|
||||
?: throw IOException("MOVE 202 without body")
|
||||
awaitOperation(link.href)
|
||||
}
|
||||
else -> throw failure("move", resp)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setCustomProperties(path: String, props: Map<String, String>): Unit =
|
||||
withContext(ioDispatcher) {
|
||||
val resp = wrapAuth {
|
||||
@@ -141,21 +127,17 @@ class YandexDiskRepository(
|
||||
resp.close()
|
||||
throw IOException("Download failed: HTTP ${resp.code}")
|
||||
}
|
||||
val body = resp.body ?: run {
|
||||
val body = resp.body
|
||||
val stream = body?.byteStream() ?: run {
|
||||
resp.close()
|
||||
throw IOException("Download: empty body")
|
||||
throw IOException("Download failed: missing body")
|
||||
}
|
||||
body.byteStream().let { stream ->
|
||||
object : InputStream() {
|
||||
override fun read(): Int = stream.read()
|
||||
override fun read(b: ByteArray): Int = stream.read(b)
|
||||
override fun read(b: ByteArray, off: Int, len: Int): Int = stream.read(b, off, len)
|
||||
override fun close() {
|
||||
try {
|
||||
stream.close()
|
||||
} finally {
|
||||
resp.close()
|
||||
}
|
||||
object : FilterInputStream(stream) {
|
||||
override fun close() {
|
||||
try {
|
||||
`in`.close()
|
||||
} finally {
|
||||
resp.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,16 +183,5 @@ class YandexDiskRepository(
|
||||
private val OCTET_STREAM = "application/octet-stream".toMediaType()
|
||||
private const val OPERATION_POLL_DELAY_MS = 300L
|
||||
private const val OPERATION_POLL_MAX = 200
|
||||
|
||||
fun parseOperationId(href: String): String? {
|
||||
val url = href.toHttpUrlOrNull() ?: return null
|
||||
url.queryParameter("id")?.let { return it }
|
||||
val segments = url.pathSegments
|
||||
val idx = segments.indexOf("operations")
|
||||
if (idx >= 0 && idx + 1 < segments.size) {
|
||||
return segments[idx + 1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ import java.io.InputStream
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Общий «скелет» storage'а: единая логика meta-info, rename, setEncInfo,
|
||||
* clearAllContent и делегирования размера/доступности к [accessor].
|
||||
* Общий «скелет» для [IStorage]: единая логика meta-info, rename, setEncInfo,
|
||||
* clearAllContent и делегирование размеров и доступности в [accessor].
|
||||
*
|
||||
* Подклассы определяют только как создаётся [accessor], значение
|
||||
* Подклассы определяют только способ создания [accessor], значение
|
||||
* [isVirtualStorage] и (при необходимости) расширяют [init] своими шагами
|
||||
* (например, проверкой ключа или инициализацией внешней связи).
|
||||
*/
|
||||
@@ -60,8 +60,8 @@ abstract class BaseStorage(
|
||||
protected open fun metaInfoUuidPart(): String = uuid.toString()
|
||||
|
||||
/**
|
||||
* Запускается единожды при старте storage'а. Подклассы могут переопределить,
|
||||
* добавив свои шаги (init accessor'а, проверка ключа и т.п.). Обязательно
|
||||
* Запускается единожды при первом использовании хранилища. Подклассы могут переопределить,
|
||||
* добавив свои шаги (инициализацию [accessor], проверку ключа и т.п.). Обязательно
|
||||
* должен в какой-то момент вызвать [readMetaInfo].
|
||||
*/
|
||||
open suspend fun init() {
|
||||
|
||||
@@ -34,10 +34,10 @@ import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* [IStorageAccessor] поверх папки приложения `app:/<storageUuid>/…` на Яндекс.Диске.
|
||||
* Реализация [IStorageAccessor] для дерева файлов `app:/<storageUuid>/…` на Яндекс.Диске.
|
||||
*
|
||||
* [isAvailable] = доступность vault'а ([vaultAvailability]) **и** успешная локальная
|
||||
* инициализация этого storage ([storageReady]).
|
||||
* [isAvailable] объединяет доступность удалённого хранилища ([vaultAvailability])
|
||||
* и успешную локальную инициализацию ([storageReady]) этого аксессора.
|
||||
*/
|
||||
class YandexStorageAccessor(
|
||||
private val storageUuid: UUID,
|
||||
@@ -265,7 +265,7 @@ class YandexStorageAccessor(
|
||||
while (queue.isNotEmpty()) {
|
||||
val rel = queue.removeFirst()
|
||||
if (isSystemRel(rel)) continue
|
||||
val (files, dirs) = listImmediateChildren(rel)
|
||||
val (_, dirs) = listImmediateChildren(rel)
|
||||
for (d in dirs) {
|
||||
if (!isSystemRel(d.metaInfo.path)) {
|
||||
out.add(d)
|
||||
@@ -402,10 +402,12 @@ class YandexStorageAccessor(
|
||||
override suspend fun openWriteSystemFile(name: String): OutputStream = withContext(ioDispatcher) {
|
||||
ensureSystemDirExists()
|
||||
val rel = "/$SYSTEM_HIDDEN_DIRNAME/$name"
|
||||
val baos = ByteArrayOutputStream()
|
||||
baos.onClosed {
|
||||
val uploadBuffer = ByteArrayOutputStream()
|
||||
uploadBuffer.onClosed {
|
||||
runBlocking(ioDispatcher) {
|
||||
guard { repo.uploadBytes(toDiskPath(rel), baos.toByteArray(), overwrite = true) }
|
||||
guard {
|
||||
repo.uploadBytes(toDiskPath(rel), uploadBuffer.toByteArray(), overwrite = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.UUID
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class VaultsManager(
|
||||
private val ioDispatcher: CoroutineDispatcher,
|
||||
localVault: IVault,
|
||||
private val localVault: IVault,
|
||||
keyRepo: StorageKeyMapStore,
|
||||
private val yandexAccountStore: YandexAccountStore,
|
||||
private val yandexUserInfoRepository: YandexUserInfoRepository,
|
||||
@@ -40,8 +40,6 @@ class VaultsManager(
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob() + ioDispatcher)
|
||||
|
||||
private val localVault: IVault = localVault
|
||||
|
||||
private val yandexVaults: StateFlow<List<IVault>> = yandexAccountStore.observeAll()
|
||||
.map { rows ->
|
||||
rows.map { row ->
|
||||
|
||||
@@ -20,7 +20,7 @@ import kotlin.io.path.pathString
|
||||
|
||||
class LocalVault(
|
||||
private val ioDispatcher: CoroutineDispatcher,
|
||||
private val vaultRoot: File?,
|
||||
vaultRoot: File?,
|
||||
) : DescribedVault {
|
||||
|
||||
override val uuid: UUID = vaultRoot?.let { root ->
|
||||
@@ -42,7 +42,7 @@ class LocalVault(
|
||||
private val _availableSpace = MutableStateFlow<Long?>(null)
|
||||
override val availableSpace: StateFlow<Long?> = _availableSpace
|
||||
|
||||
private val path = MutableStateFlow<File?>(vaultRoot)
|
||||
private val path = MutableStateFlow(vaultRoot)
|
||||
|
||||
init {
|
||||
CoroutineScope(ioDispatcher).launch {
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package com.github.nullptroma.wallenc.infrastructure.vaults.yandex
|
||||
|
||||
import com.github.nullptroma.wallenc.vault.contract.CloudBrand
|
||||
import com.github.nullptroma.wallenc.vault.contract.VaultRegistration
|
||||
|
||||
/**
|
||||
* Регистрация удалённого vault'а Яндекс.Диска по результату OAuth.
|
||||
* Регистрация удалённого хранилища Яндекс.Диска по результату OAuth.
|
||||
*
|
||||
* Живёт в `:data` (а не в `:vault-api`), потому что [VaultRegistration]
|
||||
* намеренно не sealed — конкретные реализации лежат рядом со своим поставщиком.
|
||||
* presentation никогда не открывает этот тип, только перепасовывает обратно
|
||||
* в `VaultRegistrar.register(...)`.
|
||||
* Слой presentation не раскрывает этот тип, только передаёт его в `VaultRegistrar.register(...)`.
|
||||
*/
|
||||
data class YandexRegistration(
|
||||
val oauthToken: String,
|
||||
@@ -17,6 +15,4 @@ data class YandexRegistration(
|
||||
init {
|
||||
require(oauthToken.isNotBlank()) { "oauthToken must not be blank" }
|
||||
}
|
||||
|
||||
val brand: CloudBrand get() = CloudBrand.YANDEX
|
||||
}
|
||||
|
||||
@@ -64,10 +64,10 @@ class YandexVault(
|
||||
_availableSpace.value = (total - used).coerceAtLeast(0L)
|
||||
_vaultReachable.value = true
|
||||
_storages.value = loadStoragesList()
|
||||
} catch (e: YandexDiskAuthException) {
|
||||
} catch (_: YandexDiskAuthException) {
|
||||
_vaultReachable.value = false
|
||||
_storages.value = emptyList()
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
_vaultReachable.value = false
|
||||
_storages.value = emptyList()
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class YandexVault(
|
||||
},
|
||||
)
|
||||
storage.init()
|
||||
_storages.value = _storages.value + storage
|
||||
_storages.value += storage
|
||||
storage
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user