Skip to content

Commit 3e636b5

Browse files
committed
Fix restoration of torrent adding parameters in AddTorrentFileFragment (#365)
1 parent 726fcdf commit 3e636b5

3 files changed

Lines changed: 41 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [Unreleased]
4+
### Fixed
5+
- Restoration of torrent adding parameters (download directory, etc) when adding torrent file
6+
37
## [2.13.0] - 2025-12-29
48
### Added
59
- Support of adding multiple torrent links

app/src/main/kotlin/org/equeim/tremotesf/ui/addtorrent/AddTorrentFileFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class AddTorrentFileFragment : ComposeFragment() {
102102
needStoragePermission = model.needStoragePermission,
103103

104104
loadTorrentFile = model::load,
105-
loadingState = model.loadingState,
105+
loadingState = model.loadingState.collectAsStateWithLifecycle(),
106106
downloadDirectory = model.downloadDirectory,
107107
allDownloadDirectories = model.allDownloadDirectories,
108108
downloadDirectoryFreeSpace = model.downloadDirectoryFreeSpace.collectAsStateWithLifecycle(),

app/src/main/kotlin/org/equeim/tremotesf/ui/addtorrent/AddTorrentFileModel.kt

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,23 @@ import androidx.compose.runtime.mutableStateOf
1818
import androidx.core.os.BundleCompat
1919
import androidx.core.os.bundleOf
2020
import androidx.lifecycle.SavedStateHandle
21+
import androidx.lifecycle.application
2122
import androidx.lifecycle.viewModelScope
2223
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
2324
import androidx.lifecycle.viewmodel.compose.saveable
2425
import kotlinx.coroutines.Dispatchers
26+
import kotlinx.coroutines.flow.MutableStateFlow
27+
import kotlinx.coroutines.flow.SharingStarted
28+
import kotlinx.coroutines.flow.StateFlow
29+
import kotlinx.coroutines.flow.combine
30+
import kotlinx.coroutines.flow.stateIn
2531
import kotlinx.coroutines.launch
2632
import kotlinx.coroutines.withContext
2733
import kotlinx.parcelize.Parcelize
2834
import org.equeim.tremotesf.R
2935
import org.equeim.tremotesf.rpc.GlobalRpcClient
3036
import org.equeim.tremotesf.rpc.RpcRequestError
37+
import org.equeim.tremotesf.rpc.RpcRequestState
3138
import org.equeim.tremotesf.rpc.requests.addTorrentFile
3239
import org.equeim.tremotesf.rpc.requests.checkIfTorrentsExist
3340
import org.equeim.tremotesf.rpc.requests.torrentproperties.addTorrentTrackers
@@ -56,7 +63,7 @@ interface AddTorrentFileModel {
5663
data object Aborted : LoadingState
5764
}
5865

59-
val loadingState: State<LoadingState>
66+
val loadingState: StateFlow<LoadingState>
6067
val needStoragePermission: Boolean
6168
val filesTree: TorrentFilesTree
6269

@@ -74,7 +81,18 @@ class AddTorrentFileModelImpl(
7481
override val needStoragePermission: Boolean =
7582
uri.scheme == ContentResolver.SCHEME_FILE && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q
7683

77-
override val loadingState = mutableStateOf<LoadingState>(LoadingState.Initial)
84+
private val torrentLoadingState = MutableStateFlow<LoadingState>(LoadingState.Initial)
85+
override val loadingState: StateFlow<LoadingState> = combine(torrentLoadingState, initialRpcInputs) { torrentLoadingState, initialRpcInputs ->
86+
if (torrentLoadingState is LoadingState.Loaded) {
87+
when (initialRpcInputs) {
88+
is RpcRequestState.Error -> LoadingState.InitialRpcInputsError(initialRpcInputs.error)
89+
is RpcRequestState.Loading -> LoadingState.Loading
90+
is RpcRequestState.Loaded -> torrentLoadingState
91+
}
92+
} else {
93+
torrentLoadingState
94+
}
95+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), LoadingState.Initial)
7896

7997
private var fd: AssetFileDescriptor? = null
8098

@@ -106,45 +124,39 @@ class AddTorrentFileModelImpl(
106124
}
107125

108126
override fun load() {
109-
if (loadingState.value == LoadingState.Initial) {
110-
Timber.i("load: loading $uri")
111-
loadingState.value = LoadingState.Loading
112-
viewModelScope.launch {
113-
doLoad(uri, getApplication())
114-
}
115-
} else {
127+
if (!torrentLoadingState.compareAndSet(LoadingState.Initial, LoadingState.Loading)) {
116128
Timber.e("load: loadingState is not Initial")
129+
return
130+
}
131+
viewModelScope.launch {
132+
Timber.i("load: loading $uri")
133+
torrentLoadingState.value = doLoad(uri, application)
117134
}
118135
}
119136

120-
private suspend fun doLoad(uri: Uri, context: Context) = withContext(Dispatchers.IO) {
137+
private suspend fun doLoad(uri: Uri, context: Context): LoadingState = withContext(Dispatchers.IO) {
121138
Timber.d("Parsing torrent file from URI $uri")
122139

123140
val fd = try {
124141
context.contentResolver.openAssetFileDescriptor(uri, "r")
125142
} catch (e: Exception) {
126143
Timber.e(e, "Failed to open file descriptor")
127-
loadingState.value = LoadingState.FileLoadingError.ReadingError
128-
return@withContext
144+
return@withContext LoadingState.FileLoadingError.ReadingError
129145
}
130146
if (fd == null) {
131147
Timber.e("File descriptor is null")
132-
loadingState.value = LoadingState.FileLoadingError.ReadingError
133-
return@withContext
148+
return@withContext LoadingState.FileLoadingError.ReadingError
134149
}
135150
var closeFd = true
136151
try {
137152
val parseResult = try {
138153
TorrentFileParser.parseTorrentFile(fd.fileDescriptor)
139154
} catch (_: FileReadException) {
140-
loadingState.value = LoadingState.FileLoadingError.ReadingError
141-
return@withContext
155+
return@withContext LoadingState.FileLoadingError.ReadingError
142156
} catch (_: FileIsTooLargeException) {
143-
loadingState.value = LoadingState.FileLoadingError.FileIsTooLarge
144-
return@withContext
157+
return@withContext LoadingState.FileLoadingError.FileIsTooLarge
145158
} catch (_: FileParseException) {
146-
loadingState.value = LoadingState.FileLoadingError.ParsingError
147-
return@withContext
159+
return@withContext LoadingState.FileLoadingError.ParsingError
148160
}
149161

150162
Timber.d("Parsed torrent file from URI $uri, its info hash is ${parseResult.infoHashV1}")
@@ -154,11 +166,10 @@ class AddTorrentFileModelImpl(
154166

155167
checkIfTorrentExists()
156168
if (checkIfTorrentExists()) {
157-
loadingState.value = LoadingState.Aborted
158-
return@withContext
169+
return@withContext LoadingState.Aborted
159170
}
160171

161-
try {
172+
return@withContext try {
162173
val (rootNode, files) = TorrentFileParser.createFilesTree(parseResult)
163174
withContext(Dispatchers.Main) {
164175
closeFd = false
@@ -182,11 +193,10 @@ class AddTorrentFileModelImpl(
182193
}
183194
}
184195
}
185-
loadingState.value = LoadingState.Loaded(torrentName)
196+
LoadingState.Loaded(torrentName)
186197
}
187198
} catch (_: FileParseException) {
188-
loadingState.value = LoadingState.FileLoadingError.ParsingError
189-
return@withContext
199+
LoadingState.FileLoadingError.ParsingError
190200
}
191201
} finally {
192202
if (closeFd) {

0 commit comments

Comments
 (0)