Skip to content

Commit b586d53

Browse files
authored
Merge pull request libre-tube#7440 from Bnyro/master
fix: playlist/subscription imports don't complete for larger data
2 parents 445b420 + 83d95c3 commit b586d53

3 files changed

Lines changed: 45 additions & 42 deletions

File tree

app/src/main/java/com/github/libretube/helpers/ImportHelper.kt

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.github.libretube.helpers
22

3-
import android.app.Activity
43
import android.content.Context
54
import android.net.Uri
65
import android.util.Log
@@ -40,19 +39,19 @@ object ImportHelper {
4039
/**
4140
* Import subscriptions by a file uri
4241
*/
43-
suspend fun importSubscriptions(activity: Activity, uri: Uri, importFormat: ImportFormat) {
42+
suspend fun importSubscriptions(context: Context, uri: Uri, importFormat: ImportFormat) {
4443
try {
45-
SubscriptionHelper.importSubscriptions(getChannelsFromUri(activity, uri, importFormat))
46-
activity.toastFromMainDispatcher(R.string.importsuccess)
44+
SubscriptionHelper.importSubscriptions(getChannelsFromUri(context, uri, importFormat))
45+
context.toastFromMainDispatcher(R.string.importsuccess)
4746
} catch (e: IllegalArgumentException) {
4847
Log.e(TAG(), e.toString())
49-
val type = activity.contentResolver.getType(uri)
50-
val message = activity.getString(R.string.unsupported_file_format, type)
51-
activity.toastFromMainDispatcher(message)
48+
val type = context.contentResolver.getType(uri)
49+
val message = context.getString(R.string.unsupported_file_format, type)
50+
context.toastFromMainDispatcher(message)
5251
} catch (e: Exception) {
5352
Log.e(TAG(), e.toString())
5453
e.localizedMessage?.let {
55-
activity.toastFromMainDispatcher(it)
54+
context.toastFromMainDispatcher(it)
5655
}
5756
}
5857
}
@@ -62,13 +61,13 @@ object ImportHelper {
6261
*/
6362
@OptIn(ExperimentalSerializationApi::class)
6463
private fun getChannelsFromUri(
65-
activity: Activity,
64+
context: Context,
6665
uri: Uri,
6766
importFormat: ImportFormat
6867
): List<String> {
6968
return when (importFormat) {
7069
ImportFormat.NEWPIPE -> {
71-
val subscriptions = activity.contentResolver.openInputStream(uri)?.use {
70+
val subscriptions = context.contentResolver.openInputStream(uri)?.use {
7271
JsonHelper.json.decodeFromStream<NewPipeSubscriptions>(it)
7372
}
7473
subscriptions?.subscriptions.orEmpty().map {
@@ -77,7 +76,7 @@ object ImportHelper {
7776
}
7877

7978
ImportFormat.FREETUBE -> {
80-
val subscriptions = activity.contentResolver.openInputStream(uri)?.use {
79+
val subscriptions = context.contentResolver.openInputStream(uri)?.use {
8180
JsonHelper.json.decodeFromStream<FreetubeSubscriptions>(it)
8281
}
8382
subscriptions?.subscriptions.orEmpty().map {
@@ -87,7 +86,7 @@ object ImportHelper {
8786

8887
ImportFormat.YOUTUBECSV -> {
8988
// import subscriptions from Google/YouTube Takeout
90-
activity.contentResolver.openInputStream(uri)?.use {
89+
context.contentResolver.openInputStream(uri)?.use {
9190
it.bufferedReader().use { reader ->
9291
reader.lines().map { line -> line.substringBefore(",") }
9392
.filter { channelId -> channelId.length == 24 }
@@ -104,7 +103,7 @@ object ImportHelper {
104103
* Write the text to the document
105104
*/
106105
@OptIn(ExperimentalSerializationApi::class)
107-
suspend fun exportSubscriptions(activity: Activity, uri: Uri, importFormat: ImportFormat) {
106+
suspend fun exportSubscriptions(context: Context, uri: Uri, importFormat: ImportFormat) {
108107
val subs = SubscriptionHelper.getSubscriptions()
109108

110109
when (importFormat) {
@@ -113,7 +112,7 @@ object ImportHelper {
113112
NewPipeSubscription(it.name, 0, "$YOUTUBE_FRONTEND_URL/channel/${it.url}")
114113
}
115114
val newPipeSubscriptions = NewPipeSubscriptions(subscriptions = newPipeChannels)
116-
activity.contentResolver.openOutputStream(uri)?.use {
115+
context.contentResolver.openOutputStream(uri)?.use {
117116
JsonHelper.json.encodeToStream(newPipeSubscriptions, it)
118117
}
119118
}
@@ -127,27 +126,27 @@ object ImportHelper {
127126
)
128127
}
129128
val freeTubeSubscriptions = FreetubeSubscriptions(subscriptions = freeTubeChannels)
130-
activity.contentResolver.openOutputStream(uri)?.use {
129+
context.contentResolver.openOutputStream(uri)?.use {
131130
JsonHelper.json.encodeToStream(freeTubeSubscriptions, it)
132131
}
133132
}
134133

135134
else -> throw IllegalArgumentException()
136135
}
137136

138-
activity.toastFromMainDispatcher(R.string.exportsuccess)
137+
context.toastFromMainDispatcher(R.string.exportsuccess)
139138
}
140139

141140
/**
142141
* Import Playlists
143142
*/
144143
@OptIn(ExperimentalSerializationApi::class)
145-
suspend fun importPlaylists(activity: Activity, uri: Uri, importFormat: ImportFormat) {
144+
suspend fun importPlaylists(context: Context, uri: Uri, importFormat: ImportFormat) {
146145
val importPlaylists = mutableListOf<PipedImportPlaylist>()
147146

148147
when (importFormat) {
149148
ImportFormat.PIPED -> {
150-
val playlistFile = activity.contentResolver.openInputStream(uri)?.use {
149+
val playlistFile = context.contentResolver.openInputStream(uri)?.use {
151150
JsonHelper.json.decodeFromStream<PipedPlaylistFile>(it)
152151
}
153152
importPlaylists.addAll(playlistFile?.playlists.orEmpty())
@@ -160,7 +159,7 @@ object ImportHelper {
160159

161160
ImportFormat.FREETUBE -> {
162161
val playlistFile =
163-
activity.contentResolver.openInputStream(uri)?.use { inputStream ->
162+
context.contentResolver.openInputStream(uri)?.use { inputStream ->
164163
val text = inputStream.bufferedReader().readText()
165164
runCatching {
166165
text.lines().map { line ->
@@ -186,7 +185,7 @@ object ImportHelper {
186185

187186
ImportFormat.YOUTUBECSV -> {
188187
val playlist = PipedImportPlaylist()
189-
activity.contentResolver.openInputStream(uri)?.use { inputStream ->
188+
context.contentResolver.openInputStream(uri)?.use { inputStream ->
190189
val lines = inputStream.bufferedReader().readLines()
191190
// invalid playlist file, hence returning
192191
if (lines.size < 2) return
@@ -226,7 +225,7 @@ object ImportHelper {
226225
}
227226

228227
ImportFormat.URLSORIDS -> {
229-
activity.contentResolver.openInputStream(uri)?.use { inputStream ->
228+
context.contentResolver.openInputStream(uri)?.use { inputStream ->
230229
val playlist = PipedImportPlaylist(name = TextUtils.getFileSafeTimeStampNow())
231230

232231
playlist.videos = inputStream.bufferedReader().readLines()
@@ -249,17 +248,17 @@ object ImportHelper {
249248
}
250249

251250
if (importPlaylists.isEmpty()) {
252-
activity.toastFromMainDispatcher(R.string.emptyList)
251+
context.toastFromMainDispatcher(R.string.emptyList)
253252
return
254253
}
255254

256255
try {
257256
PlaylistsHelper.importPlaylists(importPlaylists)
258-
activity.toastFromMainDispatcher(R.string.success)
257+
context.toastFromMainDispatcher(R.string.success)
259258
} catch (e: Exception) {
260259
Log.e(TAG(), e.toString())
261260
e.localizedMessage?.let {
262-
activity.toastFromMainDispatcher(it)
261+
context.toastFromMainDispatcher(it)
263262
}
264263
}
265264
}
@@ -269,7 +268,7 @@ object ImportHelper {
269268
*/
270269
@OptIn(ExperimentalSerializationApi::class)
271270
suspend fun exportPlaylists(
272-
activity: Activity,
271+
context: Context,
273272
uri: Uri,
274273
importFormat: ImportFormat,
275274
selectedPlaylistIds: List<String>? = null
@@ -285,10 +284,10 @@ object ImportHelper {
285284
PipedImportPlaylist(it.name, "playlist", "private", videos)
286285
})
287286

288-
activity.contentResolver.openOutputStream(uri)?.use {
287+
context.contentResolver.openOutputStream(uri)?.use {
289288
JsonHelper.json.encodeToStream(playlistFile, it)
290289
}
291-
activity.toastFromMainDispatcher(R.string.exportsuccess)
290+
context.toastFromMainDispatcher(R.string.exportsuccess)
292291
}
293292

294293
ImportFormat.FREETUBE -> {
@@ -307,21 +306,21 @@ object ImportHelper {
307306
JsonHelper.json.encodeToString(playlist)
308307
}
309308

310-
activity.contentResolver.openOutputStream(uri)?.use {
309+
context.contentResolver.openOutputStream(uri)?.use {
311310
it.write(freeTubeExportDb.toByteArray())
312311
}
313-
activity.toastFromMainDispatcher(R.string.exportsuccess)
312+
context.toastFromMainDispatcher(R.string.exportsuccess)
314313
}
315314

316315
ImportFormat.URLSORIDS -> {
317316
val urlListExport = playlists
318317
.flatMap { it.relatedStreams }
319318
.joinToString("\n") { YOUTUBE_FRONTEND_URL + "/watch?v=" + it.url!!.toID() }
320319

321-
activity.contentResolver.openOutputStream(uri)?.use {
320+
context.contentResolver.openOutputStream(uri)?.use {
322321
it.write(urlListExport.toByteArray())
323322
}
324-
activity.toastFromMainDispatcher(R.string.exportsuccess)
323+
context.toastFromMainDispatcher(R.string.exportsuccess)
325324
}
326325

327326
else -> Unit

app/src/main/java/com/github/libretube/repo/LocalSubscriptionsRepository.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@ class LocalSubscriptionsRepository : SubscriptionsRepository {
3030
}
3131

3232
override suspend fun importSubscriptions(newChannels: List<String>) {
33-
for (chunk in newChannels.chunked(CHANNEL_CHUNK_SIZE)) {
33+
val subscribedChannels = getSubscriptionChannelIds()
34+
35+
val newFiltered = newChannels.filter { !subscribedChannels.contains(it) }
36+
for (chunk in newFiltered.chunked(CHANNEL_CHUNK_SIZE)) {
3437
chunk.parallelMap { channelId ->
3538
val channelUrl = "$YOUTUBE_FRONTEND_URL/channel/${channelId}"
3639
val channelInfo = ChannelInfo.getInfo(channelUrl)
3740

38-
runCatching { subscribe(channelId, channelInfo.name, channelInfo.avatars.maxByOrNull { it.height }?.url, channelInfo.isVerified) }
41+
val avatarUrl = channelInfo.avatars.maxByOrNull { it.height }?.url
42+
subscribe(channelId, channelInfo.name, avatarUrl, channelInfo.isVerified)
3943
}
4044
}
4145
}

app/src/main/java/com/github/libretube/ui/preferences/BackupRestoreSettings.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class BackupRestoreSettings : BasePreferenceFragment() {
3636
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
3737
if (uri == null) return@registerForActivityResult
3838
CoroutineScope(Dispatchers.IO).launch {
39-
BackupHelper.restoreAdvancedBackup(requireContext(), uri)
39+
BackupHelper.restoreAdvancedBackup(requireContext().applicationContext, uri)
4040
withContext(Dispatchers.Main) {
4141
// could fail if fragment is already closed
4242
runCatching {
@@ -47,8 +47,8 @@ class BackupRestoreSettings : BasePreferenceFragment() {
4747
}
4848
private val createBackupFile = registerForActivityResult(CreateDocument(FILETYPE_ANY)) { uri ->
4949
if (uri == null) return@registerForActivityResult
50-
CoroutineScope(Dispatchers.IO).launch {
51-
BackupHelper.createAdvancedBackup(requireContext(), uri, backupFile)
50+
lifecycleScope.launch(Dispatchers.IO) {
51+
BackupHelper.createAdvancedBackup(requireContext().applicationContext, uri, backupFile)
5252
}
5353
}
5454

@@ -59,15 +59,15 @@ class BackupRestoreSettings : BasePreferenceFragment() {
5959
ActivityResultContracts.GetContent()
6060
) { uri ->
6161
if (uri == null) return@registerForActivityResult
62-
lifecycleScope.launch(Dispatchers.IO) {
63-
ImportHelper.importSubscriptions(requireActivity(), uri, importFormat)
62+
CoroutineScope(Dispatchers.IO).launch {
63+
ImportHelper.importSubscriptions(requireContext().applicationContext, uri, importFormat)
6464
}
6565
}
6666

6767
private val createSubscriptionsFile = registerForActivityResult(CreateDocument(FILETYPE_ANY)) { uri ->
6868
if (uri == null) return@registerForActivityResult
6969
lifecycleScope.launch(Dispatchers.IO) {
70-
ImportHelper.exportSubscriptions(requireActivity(), uri, importFormat)
70+
ImportHelper.exportSubscriptions(requireContext().applicationContext, uri, importFormat)
7171
}
7272
}
7373

@@ -77,7 +77,7 @@ class BackupRestoreSettings : BasePreferenceFragment() {
7777
registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { files ->
7878
for (file in files) {
7979
CoroutineScope(Dispatchers.IO).launch {
80-
ImportHelper.importPlaylists(requireActivity(), file, importFormat)
80+
ImportHelper.importPlaylists(requireContext().applicationContext, file, importFormat)
8181
}
8282
}
8383
}
@@ -86,15 +86,15 @@ class BackupRestoreSettings : BasePreferenceFragment() {
8686
registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { files ->
8787
for (file in files) {
8888
CoroutineScope(Dispatchers.IO).launch {
89-
ImportHelper.importWatchHistory(requireActivity(), file, importFormat)
89+
ImportHelper.importWatchHistory(requireContext().applicationContext, file, importFormat)
9090
}
9191
}
9292
}
9393

9494
private val createPlaylistsFile = registerForActivityResult(CreateDocument(FILETYPE_ANY)) { uri ->
9595
uri?.let {
9696
lifecycleScope.launch(Dispatchers.IO) {
97-
ImportHelper.exportPlaylists(requireActivity(), uri, importFormat)
97+
ImportHelper.exportPlaylists(requireContext().applicationContext, uri, importFormat)
9898
}
9999
}
100100
}

0 commit comments

Comments
 (0)