diff --git a/app/schemas/com.github.libretube.db.AppDatabase/20.json b/app/schemas/com.github.libretube.db.AppDatabase/20.json new file mode 100644 index 000000000..1b30957f0 --- /dev/null +++ b/app/schemas/com.github.libretube.db.AppDatabase/20.json @@ -0,0 +1,684 @@ +{ + "formatVersion": 1, + "database": { + "version": 20, + "identityHash": "ebb79071f4df6ea4543f2d0967b3aa7f", + "entities": [ + { + "tableName": "watchHistoryItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `title` TEXT, `uploadDate` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `thumbnailUrl` TEXT, `duration` INTEGER, `isShort` INTEGER NOT NULL, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isShort", + "columnName": "isShort", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "watchPosition", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "searchHistoryItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`query` TEXT NOT NULL, PRIMARY KEY(`query`))", + "fields": [ + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "query" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "customInstance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `apiUrl` TEXT NOT NULL, `frontendUrl` TEXT NOT NULL, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "apiUrl", + "columnName": "apiUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "frontendUrl", + "columnName": "frontendUrl", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "localSubscription", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channelId` TEXT NOT NULL, `name` TEXT DEFAULT NULL, `avatar` TEXT DEFAULT NULL, `verified` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`channelId`))", + "fields": [ + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "verified", + "columnName": "verified", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "channelId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "playlistBookmark", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlistId` TEXT NOT NULL, `playlistName` TEXT, `thumbnailUrl` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `videos` INTEGER NOT NULL, PRIMARY KEY(`playlistId`))", + "fields": [ + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playlistName", + "columnName": "playlistName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "videos", + "columnName": "videos", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "playlistId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalPlaylist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `thumbnailUrl` TEXT NOT NULL, `description` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalPlaylistItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `playlistId` INTEGER NOT NULL, `videoId` TEXT NOT NULL, `title` TEXT, `uploadDate` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `thumbnailUrl` TEXT, `duration` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "download", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `uploader` TEXT NOT NULL, `duration` INTEGER DEFAULT NULL, `uploadDate` TEXT, `thumbnailPath` TEXT, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailPath", + "columnName": "thumbnailPath", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "downloadItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` TEXT NOT NULL, `videoId` TEXT NOT NULL, `fileName` TEXT NOT NULL, `path` TEXT NOT NULL, `url` TEXT, `format` TEXT, `quality` TEXT, `language` TEXT, `downloadSize` INTEGER NOT NULL, FOREIGN KEY(`videoId`) REFERENCES `download`(`videoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fileName", + "columnName": "fileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "quality", + "columnName": "quality", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadSize", + "columnName": "downloadSize", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_downloadItem_path", + "unique": true, + "columnNames": [ + "path" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_downloadItem_path` ON `${TABLE_NAME}` (`path`)" + } + ], + "foreignKeys": [ + { + "table": "download", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "videoId" + ], + "referencedColumns": [ + "videoId" + ] + } + ] + }, + { + "tableName": "downloadChapters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `videoId` TEXT NOT NULL, `name` TEXT NOT NULL, `start` INTEGER NOT NULL, `thumbnailUrl` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "subscriptionGroups", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `channels` TEXT NOT NULL, `index` INTEGER NOT NULL, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "channels", + "columnName": "channels", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index", + "columnName": "index", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "feedItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `title` TEXT, `thumbnail` TEXT, `uploaderName` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `duration` INTEGER, `views` INTEGER, `uploaderVerified` INTEGER NOT NULL, `uploaded` INTEGER NOT NULL, `shortDescription` TEXT, `isShort` INTEGER NOT NULL, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnail", + "columnName": "thumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderName", + "columnName": "uploaderName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "views", + "columnName": "views", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uploaderVerified", + "columnName": "uploaderVerified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uploaded", + "columnName": "uploaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortDescription", + "columnName": "shortDescription", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isShort", + "columnName": "isShort", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ebb79071f4df6ea4543f2d0967b3aa7f')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/api/SubscriptionHelper.kt b/app/src/main/java/com/github/libretube/api/SubscriptionHelper.kt index 60c4fb9c5..c0436bf52 100644 --- a/app/src/main/java/com/github/libretube/api/SubscriptionHelper.kt +++ b/app/src/main/java/com/github/libretube/api/SubscriptionHelper.kt @@ -11,6 +11,7 @@ import com.github.libretube.repo.FeedRepository import com.github.libretube.repo.LocalFeedRepository import com.github.libretube.repo.LocalSubscriptionsRepository import com.github.libretube.repo.PipedAccountFeedRepository +import com.github.libretube.repo.PipedLocalSubscriptionsRepository import com.github.libretube.repo.PipedNoAccountFeedRepository import com.github.libretube.repo.SubscriptionsRepository import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -23,19 +24,20 @@ object SubscriptionHelper { */ const val GET_SUBSCRIPTIONS_LIMIT = 100 + private val localFeedExtraction get() = PreferenceHelper.getBoolean( + PreferenceKeys.LOCAL_FEED_EXTRACTION, + false + ) private val token get() = PreferenceHelper.getToken() private val subscriptionsRepository: SubscriptionsRepository get() = when { + localFeedExtraction -> LocalSubscriptionsRepository() token.isNotEmpty() -> AccountSubscriptionsRepository() - else -> LocalSubscriptionsRepository() + else -> PipedLocalSubscriptionsRepository() } private val feedRepository: FeedRepository get() = when { - PreferenceHelper.getBoolean( - PreferenceKeys.LOCAL_FEED_EXTRACTION, - false - ) -> LocalFeedRepository() - + localFeedExtraction -> LocalFeedRepository() token.isNotEmpty() -> PipedAccountFeedRepository() else -> PipedNoAccountFeedRepository() } diff --git a/app/src/main/java/com/github/libretube/db/AppDatabase.kt b/app/src/main/java/com/github/libretube/db/AppDatabase.kt index 9a016dae5..16deab911 100644 --- a/app/src/main/java/com/github/libretube/db/AppDatabase.kt +++ b/app/src/main/java/com/github/libretube/db/AppDatabase.kt @@ -44,14 +44,15 @@ import com.github.libretube.db.obj.WatchPosition SubscriptionGroup::class, SubscriptionsFeedItem::class ], - version = 19, + version = 20, autoMigrations = [ AutoMigration(from = 7, to = 8), AutoMigration(from = 8, to = 9), AutoMigration(from = 9, to = 10), AutoMigration(from = 10, to = 11), AutoMigration(from = 16, to = 17), - AutoMigration(from = 18, to = 19) + AutoMigration(from = 18, to = 19), + AutoMigration(from = 19, to = 20) ] ) @TypeConverters(Converters::class) @@ -101,5 +102,8 @@ abstract class AppDatabase : RoomDatabase() { */ abstract fun subscriptionGroupsDao(): SubscriptionGroupsDao + /** + * Locally cached subscription feed + */ abstract fun feedDao(): SubscriptionsFeedDao } diff --git a/app/src/main/java/com/github/libretube/db/dao/LocalSubscriptionDao.kt b/app/src/main/java/com/github/libretube/db/dao/LocalSubscriptionDao.kt index 5b3f7ed9e..de7403b21 100644 --- a/app/src/main/java/com/github/libretube/db/dao/LocalSubscriptionDao.kt +++ b/app/src/main/java/com/github/libretube/db/dao/LocalSubscriptionDao.kt @@ -1,7 +1,6 @@ package com.github.libretube.db.dao import androidx.room.Dao -import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query @@ -18,8 +17,14 @@ interface LocalSubscriptionDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(localSubscriptions: List) - @Delete - suspend fun delete(localSubscription: LocalSubscription) + @Query("DELETE FROM localSubscription WHERE channelId = :channelId") + suspend fun deleteById(channelId: String) + + /** + * Get all channels that DO NOT contain any meta info (such as their name) yet. + */ + @Query("SELECT * FROM localSubscription WHERE name IS NULL") + suspend fun getChannelsWithoutMetaInfo(): List @Query("SELECT EXISTS(SELECT * FROM localSubscription WHERE channelId = :channelId)") suspend fun includes(channelId: String): Boolean diff --git a/app/src/main/java/com/github/libretube/db/obj/LocalSubscription.kt b/app/src/main/java/com/github/libretube/db/obj/LocalSubscription.kt index aa12bed09..9f79966f5 100644 --- a/app/src/main/java/com/github/libretube/db/obj/LocalSubscription.kt +++ b/app/src/main/java/com/github/libretube/db/obj/LocalSubscription.kt @@ -1,5 +1,6 @@ package com.github.libretube.db.obj +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey @@ -10,9 +11,16 @@ import kotlinx.serialization.Serializable @Entity(tableName = "localSubscription") data class LocalSubscription( @PrimaryKey val channelId: String, - @Ignore val url: String = "" + @Ignore val url: String = "", + + @ColumnInfo(defaultValue = "NULL") val name: String? = null, + @ColumnInfo(defaultValue = "NULL") val avatar: String? = null, + @ColumnInfo(defaultValue = "false") val verified: Boolean = false ) { constructor( - channelId: String - ) : this(channelId, "${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/$channelId") + channelId: String, + name: String? = null, + avatar: String? = null, + verified: Boolean = false + ) : this(channelId, "${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/$channelId", name, avatar, verified) } diff --git a/app/src/main/java/com/github/libretube/repo/LocalSubscriptionsRepository.kt b/app/src/main/java/com/github/libretube/repo/LocalSubscriptionsRepository.kt index f64d59963..d380c7813 100644 --- a/app/src/main/java/com/github/libretube/repo/LocalSubscriptionsRepository.kt +++ b/app/src/main/java/com/github/libretube/repo/LocalSubscriptionsRepository.kt @@ -1,18 +1,29 @@ package com.github.libretube.repo -import com.github.libretube.api.RetrofitInstance -import com.github.libretube.api.SubscriptionHelper.GET_SUBSCRIPTIONS_LIMIT import com.github.libretube.api.obj.Subscription import com.github.libretube.db.DatabaseHolder.Database import com.github.libretube.db.obj.LocalSubscription +import com.github.libretube.extensions.parallelMap +import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_FRONTEND_URL +import org.schabi.newpipe.extractor.channel.ChannelInfo class LocalSubscriptionsRepository: SubscriptionsRepository { override suspend fun subscribe(channelId: String) { - Database.localSubscriptionDao().insert(LocalSubscription(channelId)) + val channelUrl = "$YOUTUBE_FRONTEND_URL/channel/${channelId}" + val channelInfo = ChannelInfo.getInfo(channelUrl) + + val localSubscription = LocalSubscription( + channelId = channelInfo.id, + name = channelInfo.name, + avatar = channelInfo.avatars.maxByOrNull { it.height }?.url, + verified = channelInfo.isVerified + ) + + Database.localSubscriptionDao().insert(localSubscription) } override suspend fun unsubscribe(channelId: String) { - Database.localSubscriptionDao().delete(LocalSubscription(channelId)) + Database.localSubscriptionDao().deleteById(channelId) } override suspend fun isSubscribed(channelId: String): Boolean { @@ -20,19 +31,26 @@ class LocalSubscriptionsRepository: SubscriptionsRepository { } override suspend fun importSubscriptions(newChannels: List) { - Database.localSubscriptionDao().insertAll(newChannels.map { LocalSubscription(it) }) + for (chunk in newChannels.chunked(CHANNEL_CHUNK_SIZE)) { + chunk.parallelMap { channelId -> + runCatching { subscribe(channelId) } + } + } } override suspend fun getSubscriptions(): List { - val channelIds = getSubscriptionChannelIds() + // load all channels that have not been fetched yet + val unfinished = Database.localSubscriptionDao().getChannelsWithoutMetaInfo() + runCatching { + importSubscriptions(unfinished.map { it.channelId }) + } - return when { - channelIds.size > GET_SUBSCRIPTIONS_LIMIT -> - RetrofitInstance.authApi - .unauthenticatedSubscriptions(channelIds) - - else -> RetrofitInstance.authApi.unauthenticatedSubscriptions( - channelIds.joinToString(",") + return Database.localSubscriptionDao().getAll().map { + Subscription( + url = it.channelId, + name = it.name.orEmpty(), + avatar = it.avatar, + verified = it.verified ) } } @@ -40,4 +58,8 @@ class LocalSubscriptionsRepository: SubscriptionsRepository { override suspend fun getSubscriptionChannelIds(): List { return Database.localSubscriptionDao().getAll().map { it.channelId } } + + companion object { + private const val CHANNEL_CHUNK_SIZE = 2 + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/repo/PipedLocalSubscriptionsRepository.kt b/app/src/main/java/com/github/libretube/repo/PipedLocalSubscriptionsRepository.kt new file mode 100644 index 000000000..d19645519 --- /dev/null +++ b/app/src/main/java/com/github/libretube/repo/PipedLocalSubscriptionsRepository.kt @@ -0,0 +1,45 @@ +package com.github.libretube.repo + +import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.SubscriptionHelper.GET_SUBSCRIPTIONS_LIMIT +import com.github.libretube.api.obj.Subscription +import com.github.libretube.db.DatabaseHolder.Database +import com.github.libretube.db.obj.LocalSubscription + +class PipedLocalSubscriptionsRepository: SubscriptionsRepository { + override suspend fun subscribe(channelId: String) { + // further meta info is not needed when using Piped local subscriptions + Database.localSubscriptionDao().insert(LocalSubscription(channelId)) + } + + override suspend fun importSubscriptions(newChannels: List) { + // further meta info is not needed when using Piped local subscriptions + Database.localSubscriptionDao().insertAll(newChannels.map { LocalSubscription(it) }) + } + + override suspend fun isSubscribed(channelId: String): Boolean { + return Database.localSubscriptionDao().includes(channelId) + } + + override suspend fun unsubscribe(channelId: String) { + Database.localSubscriptionDao().deleteById(channelId) + } + + override suspend fun getSubscriptions(): List { + val channelIds = getSubscriptionChannelIds() + + return when { + channelIds.size > GET_SUBSCRIPTIONS_LIMIT -> + RetrofitInstance.authApi + .unauthenticatedSubscriptions(channelIds) + + else -> RetrofitInstance.authApi.unauthenticatedSubscriptions( + channelIds.joinToString(",") + ) + } + } + + override suspend fun getSubscriptionChannelIds(): List { + return Database.localSubscriptionDao().getAll().map { it.channelId } + } +} \ No newline at end of file