mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 19:20:54 +01:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			dev
			...
			kspMigrati
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b2730d05fb | ||
|   | acc43eee14 | ||
|   | dcff65a5e8 | ||
|   | 03bee18259 | 
| @@ -6,7 +6,7 @@ | ||||
| plugins { | ||||
|     alias(libs.plugins.android.application) | ||||
|     alias(libs.plugins.jetbrains.kotlin.android) | ||||
|     alias(libs.plugins.jetbrains.kotlin.kapt) | ||||
|     alias(libs.plugins.google.ksp) | ||||
|     alias(libs.plugins.jetbrains.kotlin.parcelize) | ||||
|     alias(libs.plugins.sonarqube) | ||||
|     checkstyle | ||||
| @@ -40,12 +40,6 @@ android { | ||||
|         System.getProperty("versionNameSuffix")?.let { versionNameSuffix = it } | ||||
|  | ||||
|         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | ||||
|  | ||||
|         javaCompileOptions { | ||||
|             annotationProcessorOptions { | ||||
|                 arguments["room.schemaLocation"] = "$projectDir/schemas" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     buildTypes { | ||||
| @@ -124,6 +118,11 @@ android { | ||||
|     } | ||||
| } | ||||
|  | ||||
| ksp { | ||||
|     arg("room.schemaLocation", "$projectDir/schemas") | ||||
| } | ||||
|  | ||||
|  | ||||
| // Custom dependency configuration for ktlint | ||||
| val ktlint by configurations.creating | ||||
|  | ||||
| @@ -218,7 +217,7 @@ dependencies { | ||||
|     implementation(libs.androidx.recyclerview) | ||||
|     implementation(libs.androidx.room.runtime) | ||||
|     implementation(libs.androidx.room.rxjava3) | ||||
|     kapt(libs.androidx.room.compiler) | ||||
|     ksp(libs.androidx.room.compiler) | ||||
|     implementation(libs.androidx.swiperefreshlayout) | ||||
|     implementation(libs.androidx.viewpager2) | ||||
|     implementation(libs.androidx.work.runtime) | ||||
| @@ -229,7 +228,7 @@ dependencies { | ||||
|     /** Third-party libraries **/ | ||||
|     implementation(libs.livefront.bridge) | ||||
|     implementation(libs.evernote.statesaver.core) | ||||
|     kapt(libs.evernote.statesaver.compiler) | ||||
|     ksp(libs.evernote.statesaver.compiler) | ||||
|  | ||||
|     // HTML parser | ||||
|     implementation(libs.jsoup) | ||||
| @@ -249,7 +248,7 @@ dependencies { | ||||
|  | ||||
|     // Metadata generator for service descriptors | ||||
|     compileOnly(libs.google.autoservice.annotations) | ||||
|     kapt(libs.google.autoservice.compiler) | ||||
|     ksp(libs.google.autoservice.compiler) | ||||
|  | ||||
|     // Manager for complex RecyclerView layouts | ||||
|     implementation(libs.lisawray.groupie.core) | ||||
|   | ||||
| @@ -458,7 +458,7 @@ | ||||
|             "notNull": true | ||||
|           }, | ||||
|           { | ||||
|             "fieldPath": "name", | ||||
|             "fieldPath": "orderingName", | ||||
|             "columnName": "name", | ||||
|             "affinity": "TEXT", | ||||
|             "notNull": false | ||||
|   | ||||
| @@ -129,7 +129,7 @@ class DatabaseMigrationTest { | ||||
|         ) | ||||
|  | ||||
|         val migratedDatabaseV3 = getMigratedDatabase() | ||||
|         val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst() | ||||
|         val listFromDB = migratedDatabaseV3.streamDAO().getAll().blockingFirst() | ||||
|  | ||||
|         // Only expect 2, the one with the null url will be ignored | ||||
|         assertEquals(2, listFromDB.size) | ||||
| @@ -217,7 +217,7 @@ class DatabaseMigrationTest { | ||||
|         ) | ||||
|  | ||||
|         val migratedDatabaseV8 = getMigratedDatabase() | ||||
|         val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst() | ||||
|         val listFromDB = migratedDatabaseV8.searchHistoryDAO().getAll().blockingFirst() | ||||
|  | ||||
|         assertEquals(2, listFromDB.size) | ||||
|         assertEquals("abc", listFromDB[0].search) | ||||
| @@ -283,8 +283,8 @@ class DatabaseMigrationTest { | ||||
|         ) | ||||
|  | ||||
|         val migratedDatabaseV9 = getMigratedDatabase() | ||||
|         var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst() | ||||
|         var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst() | ||||
|         var localListFromDB = migratedDatabaseV9.playlistDAO().getAll().blockingFirst() | ||||
|         var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().blockingFirst() | ||||
|  | ||||
|         assertEquals(1, localListFromDB.size) | ||||
|         assertEquals(localUid2, localListFromDB[0].uid) | ||||
| @@ -294,17 +294,27 @@ class DatabaseMigrationTest { | ||||
|         assertEquals(-1, remoteListFromDB[0].displayIndex) | ||||
|  | ||||
|         val localUid3 = migratedDatabaseV9.playlistDAO().insert( | ||||
|             PlaylistEntity(DEFAULT_NAME + "3", false, -1, -1) | ||||
|             PlaylistEntity( | ||||
|                 name = "${DEFAULT_NAME}3", | ||||
|                 isThumbnailPermanent = false, | ||||
|                 thumbnailStreamId = -1, | ||||
|                 displayIndex = -1 | ||||
|             ) | ||||
|         ) | ||||
|         val remoteUid3 = migratedDatabaseV9.playlistRemoteDAO().insert( | ||||
|             PlaylistRemoteEntity( | ||||
|                 DEFAULT_THIRD_SERVICE_ID, DEFAULT_NAME, DEFAULT_THIRD_URL, | ||||
|                 DEFAULT_THUMBNAIL, DEFAULT_UPLOADER_NAME, -1, 10 | ||||
|                 serviceId = DEFAULT_THIRD_SERVICE_ID, | ||||
|                 orderingName = DEFAULT_NAME, | ||||
|                 url = DEFAULT_THIRD_URL, | ||||
|                 thumbnailUrl = DEFAULT_THUMBNAIL, | ||||
|                 uploader = DEFAULT_UPLOADER_NAME, | ||||
|                 displayIndex = -1, | ||||
|                 streamCount = 10 | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|         localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst() | ||||
|         remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst() | ||||
|         localListFromDB = migratedDatabaseV9.playlistDAO().getAll().blockingFirst() | ||||
|         remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().blockingFirst() | ||||
|         assertEquals(2, localListFromDB.size) | ||||
|         assertEquals(localUid3, localListFromDB[1].uid) | ||||
|         assertEquals(-1, localListFromDB[1].displayIndex) | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class HistoryRecordManagerTest { | ||||
|         // For some reason the Flowable returned by getAll() never completes, so we can't assert | ||||
|         // that the number of Lists it returns is exactly 1, we can only check if the first List is | ||||
|         // correct. Why on earth has a Flowable been used instead of a Single for getAll()?!? | ||||
|         val entities = database.searchHistoryDAO().all.blockingFirst() | ||||
|         val entities = database.searchHistoryDAO().getAll().blockingFirst() | ||||
|         assertThat(entities).hasSize(1) | ||||
|         assertThat(entities[0].id).isEqualTo(1) | ||||
|         assertThat(entities[0].serviceId).isEqualTo(0) | ||||
| @@ -51,50 +51,50 @@ class HistoryRecordManagerTest { | ||||
|     @Test | ||||
|     fun deleteSearchHistory() { | ||||
|         val entries = listOf( | ||||
|             SearchHistoryEntry(time.minusSeconds(1), 0, "A"), | ||||
|             SearchHistoryEntry(time.minusSeconds(2), 2, "A"), | ||||
|             SearchHistoryEntry(time.minusSeconds(3), 1, "B"), | ||||
|             SearchHistoryEntry(time.minusSeconds(4), 0, "B"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 0, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 2, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 1, search = "B"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 0, search = "B"), | ||||
|         ) | ||||
|  | ||||
|         // make sure all 4 were inserted | ||||
|         database.searchHistoryDAO().insertAll(entries) | ||||
|         assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries) | ||||
|         assertThat(database.searchHistoryDAO().getAll().blockingFirst()).hasSameSizeAs(entries) | ||||
|  | ||||
|         // try to delete only "A" entries, "B" entries should be untouched | ||||
|         manager.deleteSearchHistory("A").test().await().assertValue(2) | ||||
|         val entities = database.searchHistoryDAO().all.blockingFirst() | ||||
|         val entities = database.searchHistoryDAO().getAll().blockingFirst() | ||||
|         assertThat(entities).hasSize(2) | ||||
|         assertThat(entities).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 } | ||||
|             .containsExactly(*entries.subList(2, 4).toTypedArray()) | ||||
|  | ||||
|         // assert that nothing happens if we delete a search query that does exist in the db | ||||
|         manager.deleteSearchHistory("A").test().await().assertValue(0) | ||||
|         val entities2 = database.searchHistoryDAO().all.blockingFirst() | ||||
|         val entities2 = database.searchHistoryDAO().getAll().blockingFirst() | ||||
|         assertThat(entities2).hasSize(2) | ||||
|         assertThat(entities2).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 } | ||||
|             .containsExactly(*entries.subList(2, 4).toTypedArray()) | ||||
|  | ||||
|         // delete all remaining entries | ||||
|         manager.deleteSearchHistory("B").test().await().assertValue(2) | ||||
|         assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty() | ||||
|         assertThat(database.searchHistoryDAO().getAll().blockingFirst()).isEmpty() | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     fun deleteCompleteSearchHistory() { | ||||
|         val entries = listOf( | ||||
|             SearchHistoryEntry(time.minusSeconds(1), 1, "A"), | ||||
|             SearchHistoryEntry(time.minusSeconds(2), 2, "B"), | ||||
|             SearchHistoryEntry(time.minusSeconds(3), 0, "C"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 1, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 2, search = "B"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 0, search = "C"), | ||||
|         ) | ||||
|  | ||||
|         // make sure all 3 were inserted | ||||
|         database.searchHistoryDAO().insertAll(entries) | ||||
|         assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries) | ||||
|         assertThat(database.searchHistoryDAO().getAll().blockingFirst()).hasSameSizeAs(entries) | ||||
|  | ||||
|         // should remove everything | ||||
|         manager.deleteCompleteSearchHistory().test().await().assertValue(entries.size) | ||||
|         assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty() | ||||
|         assertThat(database.searchHistoryDAO().getAll().blockingFirst()).isEmpty() | ||||
|     } | ||||
|  | ||||
|     private fun insertShuffledRelatedSearches(relatedSearches: Collection<SearchHistoryEntry>) { | ||||
| @@ -107,7 +107,7 @@ class HistoryRecordManagerTest { | ||||
|         // make sure all entries were inserted | ||||
|         assertEquals( | ||||
|             relatedSearches.size, | ||||
|             database.searchHistoryDAO().all.blockingFirst().size | ||||
|             database.searchHistoryDAO().getAll().blockingFirst().size | ||||
|         ) | ||||
|     } | ||||
|  | ||||
| @@ -127,19 +127,18 @@ class HistoryRecordManagerTest { | ||||
|  | ||||
|     @Test | ||||
|     fun getRelatedSearches_emptyQuery_manyDuplicates() { | ||||
|         insertShuffledRelatedSearches( | ||||
|             listOf( | ||||
|                 SearchHistoryEntry(time.minusSeconds(9), 3, "A"), | ||||
|                 SearchHistoryEntry(time.minusSeconds(8), 3, "AB"), | ||||
|                 SearchHistoryEntry(time.minusSeconds(7), 3, "A"), | ||||
|                 SearchHistoryEntry(time.minusSeconds(6), 3, "A"), | ||||
|                 SearchHistoryEntry(time.minusSeconds(5), 3, "BA"), | ||||
|                 SearchHistoryEntry(time.minusSeconds(4), 3, "A"), | ||||
|                 SearchHistoryEntry(time.minusSeconds(3), 3, "A"), | ||||
|                 SearchHistoryEntry(time.minusSeconds(2), 0, "A"), | ||||
|                 SearchHistoryEntry(time.minusSeconds(1), 2, "AA"), | ||||
|             ) | ||||
|         val relatedSearches = listOf( | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(9), serviceId = 3, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(8), serviceId = 3, search = "AB"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(7), serviceId = 3, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(6), serviceId = 3, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(5), serviceId = 3, search = "BA"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 3, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 3, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 0, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 2, search = "AA"), | ||||
|         ) | ||||
|         insertShuffledRelatedSearches(relatedSearches) | ||||
|  | ||||
|         val searches = manager.getRelatedSearches("", 9, 3).blockingFirst() | ||||
|         assertThat(searches).containsExactly("AA", "A", "BA") | ||||
| @@ -166,13 +165,13 @@ class HistoryRecordManagerTest { | ||||
|         private val time = OffsetDateTime.of(LocalDateTime.of(2000, 1, 1, 1, 1), ZoneOffset.UTC) | ||||
|  | ||||
|         private val RELATED_SEARCHES_ENTRIES = listOf( | ||||
|             SearchHistoryEntry(time.minusSeconds(7), 2, "AC"), | ||||
|             SearchHistoryEntry(time.minusSeconds(6), 0, "ABC"), | ||||
|             SearchHistoryEntry(time.minusSeconds(5), 1, "BA"), | ||||
|             SearchHistoryEntry(time.minusSeconds(4), 3, "A"), | ||||
|             SearchHistoryEntry(time.minusSeconds(2), 0, "B"), | ||||
|             SearchHistoryEntry(time.minusSeconds(3), 2, "AA"), | ||||
|             SearchHistoryEntry(time.minusSeconds(1), 1, "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(7), serviceId = 2, search = "AC"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(6), serviceId = 0, search = "ABC"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(5), serviceId = 1, search = "BA"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 3, search = "A"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 0, search = "B"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 2, search = "AA"), | ||||
|             SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 1, search = "A"), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -72,6 +72,6 @@ class LocalPlaylistManagerTest { | ||||
|         val result = manager.createPlaylist("name", listOf(stream, upserted)) | ||||
|  | ||||
|         result.test().await().assertComplete() | ||||
|         database.streamDAO().all.test().awaitCount(1).assertValue(listOf(stream, upserted)) | ||||
|         database.streamDAO().getAll().test().awaitCount(1).assertValue(listOf(stream, upserted)) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,72 +0,0 @@ | ||||
| package org.schabi.newpipe; | ||||
|  | ||||
| import static org.schabi.newpipe.database.AppDatabase.DATABASE_NAME; | ||||
| import static org.schabi.newpipe.database.Migrations.MIGRATION_1_2; | ||||
| import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3; | ||||
| import static org.schabi.newpipe.database.Migrations.MIGRATION_3_4; | ||||
| import static org.schabi.newpipe.database.Migrations.MIGRATION_4_5; | ||||
| import static org.schabi.newpipe.database.Migrations.MIGRATION_5_6; | ||||
| import static org.schabi.newpipe.database.Migrations.MIGRATION_6_7; | ||||
| import static org.schabi.newpipe.database.Migrations.MIGRATION_7_8; | ||||
| import static org.schabi.newpipe.database.Migrations.MIGRATION_8_9; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.database.Cursor; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.room.Room; | ||||
|  | ||||
| import org.schabi.newpipe.database.AppDatabase; | ||||
|  | ||||
| public final class NewPipeDatabase { | ||||
|     private static volatile AppDatabase databaseInstance; | ||||
|  | ||||
|     private NewPipeDatabase() { | ||||
|         //no instance | ||||
|     } | ||||
|  | ||||
|     private static AppDatabase getDatabase(final Context context) { | ||||
|         return Room | ||||
|                 .databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME) | ||||
|                 .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, | ||||
|                         MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     public static AppDatabase getInstance(@NonNull final Context context) { | ||||
|         AppDatabase result = databaseInstance; | ||||
|         if (result == null) { | ||||
|             synchronized (NewPipeDatabase.class) { | ||||
|                 result = databaseInstance; | ||||
|                 if (result == null) { | ||||
|                     databaseInstance = getDatabase(context); | ||||
|                     result = databaseInstance; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     public static void checkpoint() { | ||||
|         if (databaseInstance == null) { | ||||
|             throw new IllegalStateException("database is not initialized"); | ||||
|         } | ||||
|         final Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null); | ||||
|         if (c.moveToFirst() && c.getInt(0) == 1) { | ||||
|             throw new RuntimeException("Checkpoint was blocked from completing"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void close() { | ||||
|         if (databaseInstance != null) { | ||||
|             synchronized (NewPipeDatabase.class) { | ||||
|                 if (databaseInstance != null) { | ||||
|                     databaseInstance.close(); | ||||
|                     databaseInstance = null; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										80
									
								
								app/src/main/java/org/schabi/newpipe/NewPipeDatabase.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								app/src/main/java/org/schabi/newpipe/NewPipeDatabase.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2017-2024 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe | ||||
|  | ||||
| import android.content.Context | ||||
| import androidx.room.Room.databaseBuilder | ||||
| import org.schabi.newpipe.database.AppDatabase | ||||
| import org.schabi.newpipe.database.Migrations.MIGRATION_1_2 | ||||
| import org.schabi.newpipe.database.Migrations.MIGRATION_2_3 | ||||
| import org.schabi.newpipe.database.Migrations.MIGRATION_3_4 | ||||
| import org.schabi.newpipe.database.Migrations.MIGRATION_4_5 | ||||
| import org.schabi.newpipe.database.Migrations.MIGRATION_5_6 | ||||
| import org.schabi.newpipe.database.Migrations.MIGRATION_6_7 | ||||
| import org.schabi.newpipe.database.Migrations.MIGRATION_7_8 | ||||
| import org.schabi.newpipe.database.Migrations.MIGRATION_8_9 | ||||
| import kotlin.concurrent.Volatile | ||||
|  | ||||
| object NewPipeDatabase { | ||||
|  | ||||
|     @Volatile | ||||
|     private var databaseInstance: AppDatabase? = null | ||||
|  | ||||
|     private fun getDatabase(context: Context): AppDatabase { | ||||
|         return databaseBuilder( | ||||
|             context.applicationContext, | ||||
|             AppDatabase::class.java, | ||||
|             AppDatabase.Companion.DATABASE_NAME | ||||
|         ).addMigrations( | ||||
|             MIGRATION_1_2, | ||||
|             MIGRATION_2_3, | ||||
|             MIGRATION_3_4, | ||||
|             MIGRATION_4_5, | ||||
|             MIGRATION_5_6, | ||||
|             MIGRATION_6_7, | ||||
|             MIGRATION_7_8, | ||||
|             MIGRATION_8_9 | ||||
|         ).build() | ||||
|     } | ||||
|  | ||||
|     @JvmStatic | ||||
|     fun getInstance(context: Context): AppDatabase { | ||||
|         var result = databaseInstance | ||||
|         if (result == null) { | ||||
|             synchronized(NewPipeDatabase::class.java) { | ||||
|                 result = databaseInstance | ||||
|                 if (result == null) { | ||||
|                     databaseInstance = getDatabase(context) | ||||
|                     result = databaseInstance | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return result!! | ||||
|     } | ||||
|  | ||||
|     @JvmStatic | ||||
|     fun checkpoint() { | ||||
|         checkNotNull(databaseInstance) { "database is not initialized" } | ||||
|         val c = databaseInstance!!.query("pragma wal_checkpoint(full)", null) | ||||
|         if (c.moveToFirst() && c.getInt(0) == 1) { | ||||
|             throw RuntimeException("Checkpoint was blocked from completing") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @JvmStatic | ||||
|     fun close() { | ||||
|         if (databaseInstance != null) { | ||||
|             synchronized(NewPipeDatabase::class.java) { | ||||
|                 if (databaseInstance != null) { | ||||
|                     databaseInstance!!.close() | ||||
|                     databaseInstance = null | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,65 +0,0 @@ | ||||
| package org.schabi.newpipe.database; | ||||
|  | ||||
| import static org.schabi.newpipe.database.Migrations.DB_VER_9; | ||||
|  | ||||
| import androidx.room.Database; | ||||
| import androidx.room.RoomDatabase; | ||||
| import androidx.room.TypeConverters; | ||||
|  | ||||
| import org.schabi.newpipe.database.feed.dao.FeedDAO; | ||||
| import org.schabi.newpipe.database.feed.dao.FeedGroupDAO; | ||||
| import org.schabi.newpipe.database.feed.model.FeedEntity; | ||||
| import org.schabi.newpipe.database.feed.model.FeedGroupEntity; | ||||
| import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity; | ||||
| import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity; | ||||
| import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; | ||||
| import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; | ||||
| import org.schabi.newpipe.database.history.model.SearchHistoryEntry; | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity; | ||||
| import org.schabi.newpipe.database.playlist.dao.PlaylistDAO; | ||||
| import org.schabi.newpipe.database.playlist.dao.PlaylistRemoteDAO; | ||||
| import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO; | ||||
| import org.schabi.newpipe.database.playlist.model.PlaylistEntity; | ||||
| import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; | ||||
| import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity; | ||||
| import org.schabi.newpipe.database.stream.dao.StreamDAO; | ||||
| import org.schabi.newpipe.database.stream.dao.StreamStateDAO; | ||||
| import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||
| import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||
| import org.schabi.newpipe.database.subscription.SubscriptionDAO; | ||||
| import org.schabi.newpipe.database.subscription.SubscriptionEntity; | ||||
|  | ||||
| @TypeConverters({Converters.class}) | ||||
| @Database( | ||||
|         entities = { | ||||
|                 SubscriptionEntity.class, SearchHistoryEntry.class, | ||||
|                 StreamEntity.class, StreamHistoryEntity.class, StreamStateEntity.class, | ||||
|                 PlaylistEntity.class, PlaylistStreamEntity.class, PlaylistRemoteEntity.class, | ||||
|                 FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class, | ||||
|                 FeedLastUpdatedEntity.class | ||||
|         }, | ||||
|         version = DB_VER_9 | ||||
| ) | ||||
| public abstract class AppDatabase extends RoomDatabase { | ||||
|     public static final String DATABASE_NAME = "newpipe.db"; | ||||
|  | ||||
|     public abstract SearchHistoryDAO searchHistoryDAO(); | ||||
|  | ||||
|     public abstract StreamDAO streamDAO(); | ||||
|  | ||||
|     public abstract StreamHistoryDAO streamHistoryDAO(); | ||||
|  | ||||
|     public abstract StreamStateDAO streamStateDAO(); | ||||
|  | ||||
|     public abstract PlaylistDAO playlistDAO(); | ||||
|  | ||||
|     public abstract PlaylistStreamDAO playlistStreamDAO(); | ||||
|  | ||||
|     public abstract PlaylistRemoteDAO playlistRemoteDAO(); | ||||
|  | ||||
|     public abstract FeedDAO feedDAO(); | ||||
|  | ||||
|     public abstract FeedGroupDAO feedGroupDAO(); | ||||
|  | ||||
|     public abstract SubscriptionDAO subscriptionDAO(); | ||||
| } | ||||
							
								
								
									
										78
									
								
								app/src/main/java/org/schabi/newpipe/database/AppDatabase.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								app/src/main/java/org/schabi/newpipe/database/AppDatabase.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2017-2024 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database | ||||
|  | ||||
| import androidx.room.Database | ||||
| import androidx.room.RoomDatabase | ||||
| import androidx.room.TypeConverters | ||||
| import org.schabi.newpipe.database.feed.dao.FeedDAO | ||||
| import org.schabi.newpipe.database.feed.dao.FeedGroupDAO | ||||
| import org.schabi.newpipe.database.feed.model.FeedEntity | ||||
| import org.schabi.newpipe.database.feed.model.FeedGroupEntity | ||||
| import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity | ||||
| import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity | ||||
| import org.schabi.newpipe.database.history.dao.SearchHistoryDAO | ||||
| import org.schabi.newpipe.database.history.dao.StreamHistoryDAO | ||||
| import org.schabi.newpipe.database.history.model.SearchHistoryEntry | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity | ||||
| import org.schabi.newpipe.database.playlist.dao.PlaylistDAO | ||||
| import org.schabi.newpipe.database.playlist.dao.PlaylistRemoteDAO | ||||
| import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO | ||||
| import org.schabi.newpipe.database.playlist.model.PlaylistEntity | ||||
| import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity | ||||
| import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity | ||||
| import org.schabi.newpipe.database.stream.dao.StreamDAO | ||||
| import org.schabi.newpipe.database.stream.dao.StreamStateDAO | ||||
| import org.schabi.newpipe.database.stream.model.StreamEntity | ||||
| import org.schabi.newpipe.database.stream.model.StreamStateEntity | ||||
| import org.schabi.newpipe.database.subscription.SubscriptionDAO | ||||
| import org.schabi.newpipe.database.subscription.SubscriptionEntity | ||||
|  | ||||
| @TypeConverters(Converters::class) | ||||
| @Database( | ||||
|     version = Migrations.DB_VER_9, | ||||
|     entities = [ | ||||
|         SubscriptionEntity::class, | ||||
|         SearchHistoryEntry::class, | ||||
|         StreamEntity::class, | ||||
|         StreamHistoryEntity::class, | ||||
|         StreamStateEntity::class, | ||||
|         PlaylistEntity::class, | ||||
|         PlaylistStreamEntity::class, | ||||
|         PlaylistRemoteEntity::class, | ||||
|         FeedEntity::class, | ||||
|         FeedGroupEntity::class, | ||||
|         FeedGroupSubscriptionEntity::class, | ||||
|         FeedLastUpdatedEntity::class | ||||
|     ] | ||||
| ) | ||||
| abstract class AppDatabase : RoomDatabase() { | ||||
|  | ||||
|     abstract fun searchHistoryDAO(): SearchHistoryDAO | ||||
|  | ||||
|     abstract fun streamDAO(): StreamDAO | ||||
|  | ||||
|     abstract fun streamHistoryDAO(): StreamHistoryDAO | ||||
|  | ||||
|     abstract fun streamStateDAO(): StreamStateDAO | ||||
|  | ||||
|     abstract fun playlistDAO(): PlaylistDAO | ||||
|  | ||||
|     abstract fun playlistStreamDAO(): PlaylistStreamDAO | ||||
|  | ||||
|     abstract fun playlistRemoteDAO(): PlaylistRemoteDAO | ||||
|  | ||||
|     abstract fun feedDAO(): FeedDAO | ||||
|  | ||||
|     abstract fun feedGroupDAO(): FeedGroupDAO | ||||
|  | ||||
|     abstract fun subscriptionDAO(): SubscriptionDAO | ||||
|  | ||||
|     companion object { | ||||
|         const val DATABASE_NAME: String = "newpipe.db" | ||||
|     } | ||||
| } | ||||
| @@ -1,39 +0,0 @@ | ||||
| package org.schabi.newpipe.database; | ||||
|  | ||||
| import androidx.room.Dao; | ||||
| import androidx.room.Delete; | ||||
| import androidx.room.Insert; | ||||
| import androidx.room.Update; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
|  | ||||
| import io.reactivex.rxjava3.core.Flowable; | ||||
|  | ||||
| @Dao | ||||
| public interface BasicDAO<Entity> { | ||||
|     /* Inserts */ | ||||
|     @Insert | ||||
|     long insert(Entity entity); | ||||
|  | ||||
|     @Insert | ||||
|     List<Long> insertAll(Collection<Entity> entities); | ||||
|  | ||||
|     /* Searches */ | ||||
|     Flowable<List<Entity>> getAll(); | ||||
|  | ||||
|     Flowable<List<Entity>> listByService(int serviceId); | ||||
|  | ||||
|     /* Deletes */ | ||||
|     @Delete | ||||
|     void delete(Entity entity); | ||||
|  | ||||
|     int deleteAll(); | ||||
|  | ||||
|     /* Updates */ | ||||
|     @Update | ||||
|     int update(Entity entity); | ||||
|  | ||||
|     @Update | ||||
|     void update(Collection<Entity> entities); | ||||
| } | ||||
							
								
								
									
										42
									
								
								app/src/main/java/org/schabi/newpipe/database/BasicDAO.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/src/main/java/org/schabi/newpipe/database/BasicDAO.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2017-2022 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database | ||||
|  | ||||
| import androidx.room.Dao | ||||
| import androidx.room.Delete | ||||
| import androidx.room.Insert | ||||
| import androidx.room.Update | ||||
| import io.reactivex.rxjava3.core.Flowable | ||||
|  | ||||
| @Dao | ||||
| interface BasicDAO<Entity> { | ||||
|  | ||||
|     /* Inserts */ | ||||
|     @Insert | ||||
|     fun insert(entity: Entity): Long | ||||
|  | ||||
|     @Insert | ||||
|     fun insertAll(entities: Collection<Entity>): List<Long> | ||||
|  | ||||
|     /* Searches */ | ||||
|     fun getAll(): Flowable<List<Entity>> | ||||
|  | ||||
|     fun listByService(serviceId: Int): Flowable<List<Entity>> | ||||
|  | ||||
|     /* Deletes */ | ||||
|     @Delete | ||||
|     fun delete(entity: Entity) | ||||
|  | ||||
|     fun deleteAll(): Int | ||||
|  | ||||
|     /* Updates */ | ||||
|     @Update | ||||
|     fun update(entity: Entity): Int | ||||
|  | ||||
|     @Update | ||||
|     fun update(entities: Collection<Entity>) | ||||
| } | ||||
| @@ -1,13 +0,0 @@ | ||||
| package org.schabi.newpipe.database; | ||||
|  | ||||
| public interface LocalItem { | ||||
|     LocalItemType getLocalItemType(); | ||||
|  | ||||
|     enum LocalItemType { | ||||
|         PLAYLIST_LOCAL_ITEM, | ||||
|         PLAYLIST_REMOTE_ITEM, | ||||
|  | ||||
|         PLAYLIST_STREAM_ITEM, | ||||
|         STATISTIC_STREAM_ITEM, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								app/src/main/java/org/schabi/newpipe/database/LocalItem.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/src/main/java/org/schabi/newpipe/database/LocalItem.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2018-2020 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database | ||||
|  | ||||
| interface LocalItem { | ||||
|     val localItemType: LocalItemType | ||||
|  | ||||
|     enum class LocalItemType { | ||||
|         PLAYLIST_LOCAL_ITEM, | ||||
|         PLAYLIST_REMOTE_ITEM, | ||||
|  | ||||
|         PLAYLIST_STREAM_ITEM, | ||||
|         STATISTIC_STREAM_ITEM, | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										412
									
								
								app/src/main/java/org/schabi/newpipe/database/Migrations.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								app/src/main/java/org/schabi/newpipe/database/Migrations.kt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -168,10 +168,10 @@ abstract class FeedDAO { | ||||
|         ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId | ||||
|         """ | ||||
|     ) | ||||
|     abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>> | ||||
|     abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime?>> | ||||
|  | ||||
|     @Query("SELECT MIN(last_updated) FROM feed_last_updated") | ||||
|     abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>> | ||||
|     abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime?>> | ||||
|  | ||||
|     @Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL") | ||||
|     abstract fun notLoadedCount(): Flowable<Long> | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| package org.schabi.newpipe.database.history.dao; | ||||
|  | ||||
| import org.schabi.newpipe.database.BasicDAO; | ||||
|  | ||||
| public interface HistoryDAO<T> extends BasicDAO<T> { | ||||
|     T getLatestEntry(); | ||||
| } | ||||
| @@ -0,0 +1,13 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2017 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.history.dao | ||||
|  | ||||
| import org.schabi.newpipe.database.BasicDAO | ||||
|  | ||||
| interface HistoryDAO<T> : BasicDAO<T> { | ||||
|     val latestEntry: T | ||||
| } | ||||
| @@ -1,52 +0,0 @@ | ||||
| package org.schabi.newpipe.database.history.dao; | ||||
|  | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.room.Dao; | ||||
| import androidx.room.Query; | ||||
|  | ||||
| import org.schabi.newpipe.database.history.model.SearchHistoryEntry; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import io.reactivex.rxjava3.core.Flowable; | ||||
|  | ||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE; | ||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID; | ||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH; | ||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SERVICE_ID; | ||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE_NAME; | ||||
|  | ||||
| @Dao | ||||
| public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> { | ||||
|     String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC"; | ||||
|     String ORDER_BY_MAX_CREATION_DATE = " ORDER BY MAX(" + CREATION_DATE + ") DESC"; | ||||
|  | ||||
|     @Query("SELECT * FROM " + TABLE_NAME | ||||
|             + " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")") | ||||
|     @Nullable | ||||
|     SearchHistoryEntry getLatestEntry(); | ||||
|  | ||||
|     @Query("DELETE FROM " + TABLE_NAME) | ||||
|     @Override | ||||
|     int deleteAll(); | ||||
|  | ||||
|     @Query("DELETE FROM " + TABLE_NAME + " WHERE " + SEARCH + " = :query") | ||||
|     int deleteAllWhereQuery(String query); | ||||
|  | ||||
|     @Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE) | ||||
|     @Override | ||||
|     Flowable<List<SearchHistoryEntry>> getAll(); | ||||
|  | ||||
|     @Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " GROUP BY " + SEARCH | ||||
|             + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit") | ||||
|     Flowable<List<String>> getUniqueEntries(int limit); | ||||
|  | ||||
|     @Query("SELECT * FROM " + TABLE_NAME | ||||
|             + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) | ||||
|     @Override | ||||
|     Flowable<List<SearchHistoryEntry>> listByService(int serviceId); | ||||
|  | ||||
|     @Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'" | ||||
|             + " GROUP BY " + SEARCH + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit") | ||||
|     Flowable<List<String>> getSimilarEntries(String query, int limit); | ||||
| } | ||||
| @@ -0,0 +1,42 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2017-2021 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.history.dao | ||||
|  | ||||
| import androidx.room.Dao | ||||
| import androidx.room.Query | ||||
| import io.reactivex.rxjava3.core.Flowable | ||||
| import org.schabi.newpipe.database.history.model.SearchHistoryEntry | ||||
|  | ||||
| @Dao | ||||
| interface SearchHistoryDAO : HistoryDAO<SearchHistoryEntry> { | ||||
|  | ||||
|     @get:Query("SELECT * FROM search_history WHERE id = (SELECT MAX(id) FROM search_history)") | ||||
|     override val latestEntry: SearchHistoryEntry | ||||
|  | ||||
|     @Query("DELETE FROM search_history") | ||||
|     override fun deleteAll(): Int | ||||
|  | ||||
|     @Query("DELETE FROM search_history WHERE search = :query") | ||||
|     fun deleteAllWhereQuery(query: String): Int | ||||
|  | ||||
|     @Query("SELECT * FROM search_history ORDER BY creation_date DESC") | ||||
|     override fun getAll(): Flowable<List<SearchHistoryEntry>> | ||||
|  | ||||
|     @Query("SELECT search FROM search_history GROUP BY search ORDER BY MAX(creation_date) DESC LIMIT :limit") | ||||
|     fun getUniqueEntries(limit: Int): Flowable<MutableList<String>> | ||||
|  | ||||
|     @Query("SELECT * FROM search_history WHERE service_id = :serviceId ORDER BY creation_date DESC") | ||||
|     override fun listByService(serviceId: Int): Flowable<List<SearchHistoryEntry>> | ||||
|  | ||||
|     @Query( | ||||
|         """ | ||||
|         SELECT search FROM search_history WHERE search LIKE :query || | ||||
|         '%' GROUP BY search ORDER BY MAX(creation_date) DESC LIMIT :limit | ||||
|         """ | ||||
|     ) | ||||
|     fun getSimilarEntries(query: String, limit: Int): Flowable<MutableList<String>> | ||||
| } | ||||
| @@ -1,89 +0,0 @@ | ||||
| package org.schabi.newpipe.database.history.dao; | ||||
|  | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.room.Dao; | ||||
| import androidx.room.Query; | ||||
| import androidx.room.RewriteQueriesToDropUnusedColumns; | ||||
|  | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity; | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntry; | ||||
| import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import io.reactivex.rxjava3.core.Flowable; | ||||
|  | ||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; | ||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; | ||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; | ||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT; | ||||
| import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE; | ||||
| import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT; | ||||
| import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID; | ||||
| import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE; | ||||
| import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS; | ||||
| import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS; | ||||
| import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE; | ||||
|  | ||||
| @Dao | ||||
| public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> { | ||||
|     @Query("SELECT * FROM " + STREAM_HISTORY_TABLE | ||||
|             + " WHERE " + STREAM_ACCESS_DATE + " = " | ||||
|             + "(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")") | ||||
|     @Override | ||||
|     @Nullable | ||||
|     public abstract StreamHistoryEntity getLatestEntry(); | ||||
|  | ||||
|     @Override | ||||
|     @Query("SELECT * FROM " + STREAM_HISTORY_TABLE) | ||||
|     public abstract Flowable<List<StreamHistoryEntity>> getAll(); | ||||
|  | ||||
|     @Override | ||||
|     @Query("DELETE FROM " + STREAM_HISTORY_TABLE) | ||||
|     public abstract int deleteAll(); | ||||
|  | ||||
|     @Override | ||||
|     public Flowable<List<StreamHistoryEntity>> listByService(final int serviceId) { | ||||
|         throw new UnsupportedOperationException(); | ||||
|     } | ||||
|  | ||||
|     @Query("SELECT * FROM " + STREAM_TABLE | ||||
|             + " INNER JOIN " + STREAM_HISTORY_TABLE | ||||
|             + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID | ||||
|             + " ORDER BY " + STREAM_ACCESS_DATE + " DESC") | ||||
|     public abstract Flowable<List<StreamHistoryEntry>> getHistory(); | ||||
|  | ||||
|  | ||||
|     @Query("SELECT * FROM " + STREAM_TABLE | ||||
|             + " INNER JOIN " + STREAM_HISTORY_TABLE | ||||
|             + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID | ||||
|             + " ORDER BY " + STREAM_ID + " ASC") | ||||
|     public abstract Flowable<List<StreamHistoryEntry>> getHistorySortedById(); | ||||
|  | ||||
|     @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID | ||||
|             + " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1") | ||||
|     @Nullable | ||||
|     public abstract StreamHistoryEntity getLatestEntry(long streamId); | ||||
|  | ||||
|     @Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") | ||||
|     public abstract int deleteStreamHistory(long streamId); | ||||
|  | ||||
|     @RewriteQueriesToDropUnusedColumns | ||||
|     @Query("SELECT * FROM " + STREAM_TABLE | ||||
|  | ||||
|             // Select the latest entry and watch count for each stream id on history table | ||||
|             + " INNER JOIN " | ||||
|             + "(SELECT " + JOIN_STREAM_ID + ", " | ||||
|             + "  MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", " | ||||
|             + "  SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT | ||||
|             + " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")" | ||||
|  | ||||
|             + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID | ||||
|  | ||||
|             + " LEFT JOIN " | ||||
|             + "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", " | ||||
|             + STREAM_PROGRESS_MILLIS | ||||
|             + " FROM " + STREAM_STATE_TABLE + " )" | ||||
|             + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS) | ||||
|     public abstract Flowable<List<StreamStatisticsEntry>> getStatistics(); | ||||
| } | ||||
| @@ -0,0 +1,63 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2018-2022 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.history.dao | ||||
|  | ||||
| import androidx.room.Dao | ||||
| import androidx.room.Query | ||||
| import androidx.room.RewriteQueriesToDropUnusedColumns | ||||
| import io.reactivex.rxjava3.core.Flowable | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntry | ||||
| import org.schabi.newpipe.database.stream.StreamStatisticsEntry | ||||
|  | ||||
| @Dao | ||||
| abstract class StreamHistoryDAO : HistoryDAO<StreamHistoryEntity> { | ||||
|  | ||||
|     @get:Query("SELECT * FROM stream_history WHERE access_date = (SELECT MAX(access_date) FROM stream_history)") | ||||
|     abstract override val latestEntry: StreamHistoryEntity | ||||
|  | ||||
|     @Query("SELECT * FROM stream_history") | ||||
|     abstract override fun getAll(): Flowable<List<StreamHistoryEntity>> | ||||
|  | ||||
|     @Query("DELETE FROM stream_history") | ||||
|     abstract override fun deleteAll(): Int | ||||
|  | ||||
|     override fun listByService(serviceId: Int): Flowable<List<StreamHistoryEntity>> { | ||||
|         throw UnsupportedOperationException() | ||||
|     } | ||||
|  | ||||
|     @get:Query("SELECT * FROM streams INNER JOIN stream_history ON uid = stream_id ORDER BY access_date DESC") | ||||
|     abstract val history: Flowable<MutableList<StreamHistoryEntry>> | ||||
|  | ||||
|     @get:Query("SELECT * FROM streams INNER JOIN stream_history ON uid = stream_id ORDER BY uid ASC") | ||||
|     abstract val historySortedById: Flowable<MutableList<StreamHistoryEntry>> | ||||
|  | ||||
|     @Query("SELECT * FROM stream_history WHERE stream_id = :streamId ORDER BY access_date DESC LIMIT 1") | ||||
|     abstract fun getLatestEntry(streamId: Long): StreamHistoryEntity | ||||
|  | ||||
|     @Query("DELETE FROM stream_history WHERE stream_id = :streamId") | ||||
|     abstract fun deleteStreamHistory(streamId: Long): Int | ||||
|  | ||||
|     // Select the latest entry and watch count for each stream id on history table | ||||
|     @RewriteQueriesToDropUnusedColumns | ||||
|     @Query( | ||||
|         """ | ||||
|         SELECT * FROM streams | ||||
|  | ||||
|         INNER JOIN ( | ||||
|             SELECT stream_id, MAX(access_date) AS latestAccess, SUM(repeat_count) AS watchCount | ||||
|             FROM stream_history | ||||
|             GROUP BY stream_id | ||||
|         ) | ||||
|         ON uid = stream_id | ||||
|  | ||||
|         LEFT JOIN (SELECT stream_id AS stream_id_alias, progress_time FROM stream_state ) | ||||
|         ON uid = stream_id_alias | ||||
|         """ | ||||
|     ) | ||||
|     abstract fun getStatistics(): Flowable<MutableList<StreamStatisticsEntry>> | ||||
| } | ||||
| @@ -1,3 +1,9 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2022 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.history.model | ||||
|  | ||||
| import androidx.room.ColumnInfo | ||||
| @@ -11,23 +17,24 @@ import java.time.OffsetDateTime | ||||
|     tableName = SearchHistoryEntry.TABLE_NAME, | ||||
|     indices = [Index(value = [SearchHistoryEntry.SEARCH])] | ||||
| ) | ||||
| data class SearchHistoryEntry( | ||||
|     @field:ColumnInfo(name = CREATION_DATE) var creationDate: OffsetDateTime?, | ||||
|     @field:ColumnInfo( | ||||
|         name = SERVICE_ID | ||||
|     ) var serviceId: Int, | ||||
|     @field:ColumnInfo(name = SEARCH) var search: String? | ||||
| ) { | ||||
| data class SearchHistoryEntry @JvmOverloads constructor( | ||||
|     @ColumnInfo(name = CREATION_DATE) | ||||
|     var creationDate: OffsetDateTime?, | ||||
|  | ||||
|     @ColumnInfo(name = SERVICE_ID) | ||||
|     val serviceId: Int, | ||||
|  | ||||
|     @ColumnInfo(name = SEARCH) | ||||
|     val search: String?, | ||||
|  | ||||
|     @ColumnInfo(name = ID) | ||||
|     @PrimaryKey(autoGenerate = true) | ||||
|     var id: Long = 0 | ||||
|     val id: Long = 0, | ||||
| ) { | ||||
|  | ||||
|     @Ignore | ||||
|     fun hasEqualValues(otherEntry: SearchHistoryEntry): Boolean { | ||||
|         return ( | ||||
|             serviceId == otherEntry.serviceId && | ||||
|                 search == otherEntry.search | ||||
|             ) | ||||
|         return serviceId == otherEntry.serviceId && search == otherEntry.search | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|   | ||||
| @@ -1,81 +0,0 @@ | ||||
| package org.schabi.newpipe.database.history.model; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.room.ColumnInfo; | ||||
| import androidx.room.Entity; | ||||
| import androidx.room.ForeignKey; | ||||
| import androidx.room.Index; | ||||
|  | ||||
| import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||
|  | ||||
| import java.time.OffsetDateTime; | ||||
|  | ||||
| import static androidx.room.ForeignKey.CASCADE; | ||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; | ||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; | ||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; | ||||
|  | ||||
| @Entity(tableName = STREAM_HISTORY_TABLE, | ||||
|         primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE}, | ||||
|         // No need to index for timestamp as they will almost always be unique | ||||
|         indices = {@Index(value = {JOIN_STREAM_ID})}, | ||||
|         foreignKeys = { | ||||
|                 @ForeignKey(entity = StreamEntity.class, | ||||
|                         parentColumns = StreamEntity.STREAM_ID, | ||||
|                         childColumns = JOIN_STREAM_ID, | ||||
|                         onDelete = CASCADE, onUpdate = CASCADE) | ||||
|         }) | ||||
| public class StreamHistoryEntity { | ||||
|     public static final String STREAM_HISTORY_TABLE = "stream_history"; | ||||
|     public static final String JOIN_STREAM_ID = "stream_id"; | ||||
|     public static final String STREAM_ACCESS_DATE = "access_date"; | ||||
|     public static final String STREAM_REPEAT_COUNT = "repeat_count"; | ||||
|  | ||||
|     @ColumnInfo(name = JOIN_STREAM_ID) | ||||
|     private long streamUid; | ||||
|  | ||||
|     @NonNull | ||||
|     @ColumnInfo(name = STREAM_ACCESS_DATE) | ||||
|     private OffsetDateTime accessDate; | ||||
|  | ||||
|     @ColumnInfo(name = STREAM_REPEAT_COUNT) | ||||
|     private long repeatCount; | ||||
|  | ||||
|     /** | ||||
|      * @param streamUid the stream id this history item will refer to | ||||
|      * @param accessDate the last time the stream was accessed | ||||
|      * @param repeatCount the total number of views this stream received | ||||
|      */ | ||||
|     public StreamHistoryEntity(final long streamUid, | ||||
|                                @NonNull final OffsetDateTime accessDate, | ||||
|                                final long repeatCount) { | ||||
|         this.streamUid = streamUid; | ||||
|         this.accessDate = accessDate; | ||||
|         this.repeatCount = repeatCount; | ||||
|     } | ||||
|  | ||||
|     public long getStreamUid() { | ||||
|         return streamUid; | ||||
|     } | ||||
|  | ||||
|     public void setStreamUid(final long streamUid) { | ||||
|         this.streamUid = streamUid; | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     public OffsetDateTime getAccessDate() { | ||||
|         return accessDate; | ||||
|     } | ||||
|  | ||||
|     public void setAccessDate(@NonNull final OffsetDateTime accessDate) { | ||||
|         this.accessDate = accessDate; | ||||
|     } | ||||
|  | ||||
|     public long getRepeatCount() { | ||||
|         return repeatCount; | ||||
|     } | ||||
|  | ||||
|     public void setRepeatCount(final long repeatCount) { | ||||
|         this.repeatCount = repeatCount; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,56 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2018-2022 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.history.model | ||||
|  | ||||
| import androidx.room.ColumnInfo | ||||
| import androidx.room.Entity | ||||
| import androidx.room.ForeignKey | ||||
| import androidx.room.ForeignKey.Companion.CASCADE | ||||
| import androidx.room.Index | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity.Companion.JOIN_STREAM_ID | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity.Companion.STREAM_ACCESS_DATE | ||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity.Companion.STREAM_HISTORY_TABLE | ||||
| import org.schabi.newpipe.database.stream.model.StreamEntity | ||||
| import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID | ||||
| import java.time.OffsetDateTime | ||||
|  | ||||
| /** | ||||
|  * @param streamUid the stream id this history item will refer to | ||||
|  * @param accessDate the last time the stream was accessed | ||||
|  * @param repeatCount the total number of views this stream received | ||||
|  */ | ||||
| @Entity( | ||||
|     tableName = STREAM_HISTORY_TABLE, | ||||
|     primaryKeys = [JOIN_STREAM_ID, STREAM_ACCESS_DATE], | ||||
|     indices = [Index(value = [JOIN_STREAM_ID])], | ||||
|     foreignKeys = [ | ||||
|         ForeignKey( | ||||
|             entity = StreamEntity::class, | ||||
|             parentColumns = arrayOf(STREAM_ID), | ||||
|             childColumns = arrayOf(JOIN_STREAM_ID), | ||||
|             onDelete = CASCADE, | ||||
|             onUpdate = CASCADE | ||||
|         ) | ||||
|     ] | ||||
| ) | ||||
| data class StreamHistoryEntity( | ||||
|     @ColumnInfo(name = JOIN_STREAM_ID) | ||||
|     val streamUid: Long, | ||||
|  | ||||
|     @ColumnInfo(name = STREAM_ACCESS_DATE) | ||||
|     var accessDate: OffsetDateTime, | ||||
|  | ||||
|     @ColumnInfo(name = STREAM_REPEAT_COUNT) | ||||
|     var repeatCount: Long | ||||
| ) { | ||||
|     companion object { | ||||
|         const val STREAM_HISTORY_TABLE: String = "stream_history" | ||||
|         const val STREAM_ACCESS_DATE: String = "access_date" | ||||
|         const val JOIN_STREAM_ID: String = "stream_id" | ||||
|         const val STREAM_REPEAT_COUNT: String = "repeat_count" | ||||
|     } | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| package org.schabi.newpipe.database.playlist; | ||||
|  | ||||
| import androidx.room.ColumnInfo; | ||||
|  | ||||
| /** | ||||
|  * This class adds a field to {@link PlaylistMetadataEntry} that contains an integer representing | ||||
|  * how many times a specific stream is already contained inside a local playlist. Used to be able | ||||
|  * to grey out playlists which already contain the current stream in the playlist append dialog. | ||||
|  * @see org.schabi.newpipe.local.playlist.LocalPlaylistManager#getPlaylistDuplicates(String) | ||||
|  */ | ||||
| public class PlaylistDuplicatesEntry extends PlaylistMetadataEntry { | ||||
|     public static final String PLAYLIST_TIMES_STREAM_IS_CONTAINED = "timesStreamIsContained"; | ||||
|     @ColumnInfo(name = PLAYLIST_TIMES_STREAM_IS_CONTAINED) | ||||
|     public final long timesStreamIsContained; | ||||
|  | ||||
|     @SuppressWarnings("checkstyle:ParameterNumber") | ||||
|     public PlaylistDuplicatesEntry(final long uid, | ||||
|                                    final String name, | ||||
|                                    final String thumbnailUrl, | ||||
|                                    final boolean isThumbnailPermanent, | ||||
|                                    final long thumbnailStreamId, | ||||
|                                    final long displayIndex, | ||||
|                                    final long streamCount, | ||||
|                                    final long timesStreamIsContained) { | ||||
|         super(uid, name, thumbnailUrl, isThumbnailPermanent, thumbnailStreamId, displayIndex, | ||||
|                 streamCount); | ||||
|         this.timesStreamIsContained = timesStreamIsContained; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2023-2024 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.playlist | ||||
|  | ||||
| import androidx.room.ColumnInfo | ||||
|  | ||||
| /** | ||||
|  * This class adds a field to [PlaylistMetadataEntry] that contains an integer representing | ||||
|  * how many times a specific stream is already contained inside a local playlist. Used to be able | ||||
|  * to grey out playlists which already contain the current stream in the playlist append dialog. | ||||
|  * @see org.schabi.newpipe.local.playlist.LocalPlaylistManager.getPlaylistDuplicates | ||||
|  */ | ||||
| data class PlaylistDuplicatesEntry( | ||||
|     override val uid: Long, | ||||
|     override val thumbnailUrl: String?, | ||||
|     override val isThumbnailPermanent: Boolean?, | ||||
|     override val thumbnailStreamId: Long?, | ||||
|     override var displayIndex: Long?, | ||||
|     override val streamCount: Long, | ||||
|     override val orderingName: String?, | ||||
|  | ||||
|     @ColumnInfo(name = PLAYLIST_TIMES_STREAM_IS_CONTAINED) | ||||
|     val timesStreamIsContained: Long | ||||
| ) : PlaylistMetadataEntry( | ||||
|     uid = uid, | ||||
|     orderingName = orderingName, | ||||
|     thumbnailUrl = thumbnailUrl, | ||||
|     isThumbnailPermanent = isThumbnailPermanent, | ||||
|     thumbnailStreamId = thumbnailStreamId, | ||||
|     displayIndex = displayIndex, | ||||
|     streamCount = streamCount | ||||
| ) { | ||||
|     companion object { | ||||
|         const val PLAYLIST_TIMES_STREAM_IS_CONTAINED: String = "timesStreamIsContained" | ||||
|     } | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| package org.schabi.newpipe.database.playlist; | ||||
|  | ||||
| import androidx.annotation.Nullable; | ||||
|  | ||||
| import org.schabi.newpipe.database.LocalItem; | ||||
|  | ||||
| public interface PlaylistLocalItem extends LocalItem { | ||||
|     String getOrderingName(); | ||||
|  | ||||
|     long getDisplayIndex(); | ||||
|  | ||||
|     long getUid(); | ||||
|  | ||||
|     void setDisplayIndex(long displayIndex); | ||||
|  | ||||
|     @Nullable | ||||
|     String getThumbnailUrl(); | ||||
| } | ||||
| @@ -0,0 +1,16 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2018-2025 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.playlist | ||||
|  | ||||
| import org.schabi.newpipe.database.LocalItem | ||||
|  | ||||
| interface PlaylistLocalItem : LocalItem { | ||||
|     val orderingName: String? | ||||
|     val displayIndex: Long? | ||||
|     val uid: Long | ||||
|     val thumbnailUrl: String? | ||||
| } | ||||
| @@ -1,82 +0,0 @@ | ||||
| package org.schabi.newpipe.database.playlist; | ||||
|  | ||||
| import androidx.room.ColumnInfo; | ||||
|  | ||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_DISPLAY_INDEX; | ||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID; | ||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME; | ||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_PERMANENT; | ||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID; | ||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL; | ||||
|  | ||||
| import androidx.annotation.Nullable; | ||||
|  | ||||
| public class PlaylistMetadataEntry implements PlaylistLocalItem { | ||||
|     public static final String PLAYLIST_STREAM_COUNT = "streamCount"; | ||||
|  | ||||
|     @ColumnInfo(name = PLAYLIST_ID) | ||||
|     private final long uid; | ||||
|     @ColumnInfo(name = PLAYLIST_NAME) | ||||
|     public final String name; | ||||
|     @ColumnInfo(name = PLAYLIST_THUMBNAIL_PERMANENT) | ||||
|     private final boolean isThumbnailPermanent; | ||||
|     @ColumnInfo(name = PLAYLIST_THUMBNAIL_STREAM_ID) | ||||
|     private final long thumbnailStreamId; | ||||
|     @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL) | ||||
|     public final String thumbnailUrl; | ||||
|     @ColumnInfo(name = PLAYLIST_DISPLAY_INDEX) | ||||
|     private long displayIndex; | ||||
|     @ColumnInfo(name = PLAYLIST_STREAM_COUNT) | ||||
|     public final long streamCount; | ||||
|  | ||||
|     public PlaylistMetadataEntry(final long uid, final String name, final String thumbnailUrl, | ||||
|                                  final boolean isThumbnailPermanent, final long thumbnailStreamId, | ||||
|                                  final long displayIndex, final long streamCount) { | ||||
|         this.uid = uid; | ||||
|         this.name = name; | ||||
|         this.thumbnailUrl = thumbnailUrl; | ||||
|         this.isThumbnailPermanent = isThumbnailPermanent; | ||||
|         this.thumbnailStreamId = thumbnailStreamId; | ||||
|         this.displayIndex = displayIndex; | ||||
|         this.streamCount = streamCount; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public LocalItemType getLocalItemType() { | ||||
|         return LocalItemType.PLAYLIST_LOCAL_ITEM; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getOrderingName() { | ||||
|         return name; | ||||
|     } | ||||
|  | ||||
|     public boolean isThumbnailPermanent() { | ||||
|         return isThumbnailPermanent; | ||||
|     } | ||||
|  | ||||
|     public long getThumbnailStreamId() { | ||||
|         return thumbnailStreamId; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public long getDisplayIndex() { | ||||
|         return displayIndex; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public long getUid() { | ||||
|         return uid; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setDisplayIndex(final long displayIndex) { | ||||
|         this.displayIndex = displayIndex; | ||||
|     } | ||||
|  | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public String getThumbnailUrl() { | ||||
|         return thumbnailUrl; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,42 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2018-2025 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.playlist | ||||
|  | ||||
| import androidx.room.ColumnInfo | ||||
| import org.schabi.newpipe.database.LocalItem.LocalItemType | ||||
| import org.schabi.newpipe.database.playlist.model.PlaylistEntity | ||||
|  | ||||
| open class PlaylistMetadataEntry( | ||||
|     @ColumnInfo(name = PlaylistEntity.PLAYLIST_ID) | ||||
|     override val uid: Long, | ||||
|  | ||||
|     @ColumnInfo(name = PlaylistEntity.PLAYLIST_NAME) | ||||
|     override val orderingName: String?, | ||||
|  | ||||
|     @ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_URL) | ||||
|     override val thumbnailUrl: String?, | ||||
|  | ||||
|     @ColumnInfo(name = PlaylistEntity.PLAYLIST_DISPLAY_INDEX) | ||||
|     override var displayIndex: Long?, | ||||
|  | ||||
|     @ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_PERMANENT) | ||||
|     open val isThumbnailPermanent: Boolean?, | ||||
|  | ||||
|     @ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID) | ||||
|     open val thumbnailStreamId: Long?, | ||||
|  | ||||
|     @ColumnInfo(name = PLAYLIST_STREAM_COUNT) | ||||
|     open val streamCount: Long | ||||
| ) : PlaylistLocalItem { | ||||
|  | ||||
|     override val localItemType: LocalItemType | ||||
|         get() = LocalItemType.PLAYLIST_LOCAL_ITEM | ||||
|  | ||||
|     companion object { | ||||
|         const val PLAYLIST_STREAM_COUNT: String = "streamCount" | ||||
|     } | ||||
| } | ||||
| @@ -1,3 +1,9 @@ | ||||
| /* | ||||
|  * SPDX-FileCopyrightText: 2020-2023 NewPipe contributors <https://newpipe.net> | ||||
|  * SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de> | ||||
|  * SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package org.schabi.newpipe.database.playlist | ||||
|  | ||||
| import androidx.room.ColumnInfo | ||||
| @@ -23,18 +29,20 @@ data class PlaylistStreamEntry( | ||||
|     val joinIndex: Int | ||||
| ) : LocalItem { | ||||
|  | ||||
|     @Throws(IllegalArgumentException::class) | ||||
|     fun toStreamInfoItem(): StreamInfoItem { | ||||
|         val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType) | ||||
|         item.duration = streamEntity.duration | ||||
|         item.uploaderName = streamEntity.uploader | ||||
|         item.uploaderUrl = streamEntity.uploaderUrl | ||||
|         item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl) | ||||
|     override val localItemType: LocalItem.LocalItemType | ||||
|         get() = LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM | ||||
|  | ||||
|         return item | ||||
|     } | ||||
|  | ||||
|     override fun getLocalItemType(): LocalItem.LocalItemType { | ||||
|         return LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM | ||||
|     } | ||||
|     @get:Throws(IllegalArgumentException::class) | ||||
|     val streamInfoItem: StreamInfoItem | ||||
|         get() = StreamInfoItem( | ||||
|             streamEntity.serviceId, | ||||
|             streamEntity.url, | ||||
|             streamEntity.title, | ||||
|             streamEntity.streamType | ||||
|         ).apply { | ||||
|             duration = streamEntity.duration | ||||
|             uploaderName = streamEntity.uploader | ||||
|             uploaderUrl = streamEntity.uploaderUrl | ||||
|             thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl) | ||||
|         } | ||||
| } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user