Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,42 @@ class DatabaseMigrationTest {
assertEquals(-1, remoteListFromDB[1].displayIndex)
}

@Test
fun migrateDatabaseFrom9to10() {
val databaseInV9 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_9)

val localUid1: Long
databaseInV9.run {
localUid1 = insert(
"playlists", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("name", DEFAULT_NAME + "1")
put("is_thumbnail_permanent", false)
put("thumbnail_stream_id", -1)
put("display_index", -1)
}
)
close()
}

testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_10,
true,
Migrations.MIGRATION_9_10
)

val migratedDatabaseV10 = getMigratedDatabase()
val localListFromDB = migratedDatabaseV10.playlistDAO().getAll().blockingFirst()

assertEquals(1, localListFromDB.size)
// folderId should exist and be null for existing playlists
assertNull(localListFromDB[0].folderId)

val foldersFromDB = migratedDatabaseV10.playlistFolderDAO().getAll().blockingFirst()
assertEquals(0, foldersFromDB.size)
}

private fun getMigratedDatabase(): AppDatabase {
val database: AppDatabase = Room.databaseBuilder(
ApplicationProvider.getApplicationContext(),
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/java/org/schabi/newpipe/NewPipeDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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 org.schabi.newpipe.database.Migrations.MIGRATION_9_10
import kotlin.concurrent.Volatile

object NewPipeDatabase {
Expand All @@ -27,8 +28,8 @@ object NewPipeDatabase {
private fun getDatabase(context: Context): AppDatabase {
return databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
AppDatabase.Companion.DATABASE_NAME
AppDatabase::class.java,
AppDatabase.Companion.DATABASE_NAME
).addMigrations(
MIGRATION_1_2,
MIGRATION_2_3,
Expand All @@ -37,7 +38,8 @@ object NewPipeDatabase {
MIGRATION_5_6,
MIGRATION_6_7,
MIGRATION_7_8,
MIGRATION_8_9
MIGRATION_8_9,
MIGRATION_9_10
).build()
}

Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/org/schabi/newpipe/database/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity

@TypeConverters(Converters::class)
@Database(
version = Migrations.DB_VER_9,
version = Migrations.DB_VER_10,
entities = [
SubscriptionEntity::class,
SearchHistoryEntry::class,
Expand All @@ -44,6 +44,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
PlaylistEntity::class,
PlaylistStreamEntity::class,
PlaylistRemoteEntity::class,
org.schabi.newpipe.database.playlist.model.PlaylistFolderEntity::class,
FeedEntity::class,
FeedGroupEntity::class,
FeedGroupSubscriptionEntity::class,
Expand All @@ -56,6 +57,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun playlistDAO(): PlaylistDAO
abstract fun playlistRemoteDAO(): PlaylistRemoteDAO
abstract fun playlistStreamDAO(): PlaylistStreamDAO
abstract fun playlistFolderDAO(): org.schabi.newpipe.database.playlist.dao.PlaylistFolderDAO
abstract fun searchHistoryDAO(): SearchHistoryDAO
abstract fun streamDAO(): StreamDAO
abstract fun streamHistoryDAO(): StreamHistoryDAO
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/java/org/schabi/newpipe/database/Migrations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ object Migrations {
const val DB_VER_7 = 7
const val DB_VER_8 = 8
const val DB_VER_9 = 9
const val DB_VER_10 = 10

private val TAG = Migrations::class.java.getName()
private val isDebug = MainActivity.DEBUG
Expand Down Expand Up @@ -365,4 +366,33 @@ object Migrations {
}
}
}

val MIGRATION_9_10 = object : Migration(DB_VER_9, DB_VER_10) {
override fun migrate(db: SupportSQLiteDatabase) {
// Add folder table and folder_id column to playlists
db.execSQL(
"CREATE TABLE IF NOT EXISTS `playlist_folders` (" +
"`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`name` TEXT NOT NULL, " +
"`sort_order` INTEGER NOT NULL DEFAULT 0)"
)

// Add nullable folder_id column to playlists
db.execSQL(
"ALTER TABLE `playlists` ADD COLUMN `folder_id` INTEGER"
)
}
}

val ALL_MIGRATIONS = arrayOf(
MIGRATION_1_2,
MIGRATION_2_3,
MIGRATION_3_4,
MIGRATION_4_5,
MIGRATION_5_6,
MIGRATION_6_7,
MIGRATION_7_8,
MIGRATION_8_9,
MIGRATION_9_10
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ open class PlaylistMetadataEntry(
@ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID)
open val thumbnailStreamId: Long?,

@ColumnInfo(name = PlaylistEntity.PLAYLIST_FOLDER_ID)
open val folderId: Long?,

@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
open val streamCount: Long
) : PlaylistLocalItem {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.schabi.newpipe.database.playlist.dao

import androidx.room.Dao
import androidx.room.Query
import androidx.room.Update
import androidx.room.Insert
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import org.schabi.newpipe.database.playlist.model.PlaylistFolderEntity

@Dao
interface PlaylistFolderDAO {
@Query("SELECT * FROM playlist_folders ORDER BY sort_order ASC")
fun getAll(): Flowable<List<PlaylistFolderEntity>>

@Insert
fun insert(folder: PlaylistFolderEntity): Long

@Update
fun update(folder: PlaylistFolderEntity): Int

@Query("DELETE FROM playlist_folders WHERE uid = :folderId")
fun delete(folderId: Long): Int

@Query("SELECT * FROM playlist_folders WHERE uid = :folderId LIMIT 1")
fun get(folderId: Long): Maybe<PlaylistFolderEntity>
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ interface PlaylistStreamDAO : BasicDAO<PlaylistStreamEntity> {
@Transaction
@Query(
"""
SELECT uid, name, is_thumbnail_permanent, thumbnail_stream_id, display_index,
SELECT uid, name,
(SELECT thumbnail_url FROM streams WHERE streams.uid = thumbnail_stream_id) AS thumbnail_url,

display_index, is_thumbnail_permanent, thumbnail_stream_id, folder_id,
COALESCE(COUNT(playlist_id), 0) AS streamCount FROM playlists

LEFT JOIN playlist_stream_join
Expand Down Expand Up @@ -106,8 +106,9 @@ interface PlaylistStreamDAO : BasicDAO<PlaylistStreamEntity> {
@Transaction
@Query(
"""
SELECT playlists.uid, name, is_thumbnail_permanent, thumbnail_stream_id, display_index,
SELECT playlists.uid, name,
(SELECT thumbnail_url FROM streams WHERE streams.uid = thumbnail_stream_id) AS thumbnail_url,
display_index, is_thumbnail_permanent, thumbnail_stream_id, folder_id,

COALESCE(COUNT(playlist_id), 0) AS streamCount,
COALESCE(SUM(url = :streamUrl), 0) AS timesStreamIsContained FROM playlists
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ data class PlaylistEntity @JvmOverloads constructor(

@ColumnInfo(name = PLAYLIST_THUMBNAIL_STREAM_ID)
var thumbnailStreamId: Long,


@ColumnInfo(name = PLAYLIST_FOLDER_ID)
@ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
var displayIndex: Long
var displayIndex: Long,

@ColumnInfo(name = PLAYLIST_FOLDER_ID)
var folderId: Long? = null,
) {

@Ignore
Expand All @@ -46,6 +50,7 @@ data class PlaylistEntity @JvmOverloads constructor(
const val PLAYLIST_TABLE = "playlists"
const val PLAYLIST_ID = "uid"
const val PLAYLIST_NAME = "name"
const val PLAYLIST_FOLDER_ID = "folder_id"
const val PLAYLIST_THUMBNAIL_URL = "thumbnail_url"
const val PLAYLIST_DISPLAY_INDEX = "display_index"
const val PLAYLIST_THUMBNAIL_PERMANENT = "is_thumbnail_permanent"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2025 NewPipe contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package org.schabi.newpipe.database.playlist.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = PlaylistFolderEntity.FOLDER_TABLE)
data class PlaylistFolderEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = FOLDER_ID)
var uid: Long = 0,

@ColumnInfo(name = FOLDER_NAME)
var name: String,

@ColumnInfo(name = FOLDER_SORT_ORDER)
var sortOrder: Long = 0
){
companion object {
const val FOLDER_TABLE = "playlist_folders"
const val FOLDER_ID = "uid"
const val FOLDER_NAME = "name"
const val FOLDER_SORT_ORDER = "sort_order"
}
}
Loading
Loading