Skip to content

Commit 4c82388

Browse files
authored
Merge pull request #8221 from GGAutomaton/feature-7870
Sort bookmarked playlists
2 parents 3c0a200 + 38d4887 commit 4c82388

30 files changed

Lines changed: 1876 additions & 161 deletions

app/schemas/org.schabi.newpipe.database.AppDatabase/9.json

Lines changed: 730 additions & 0 deletions
Large diffs are not rendered by default.

app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import org.junit.Assert.assertNull
1313
import org.junit.Rule
1414
import org.junit.Test
1515
import org.junit.runner.RunWith
16+
import org.schabi.newpipe.database.playlist.model.PlaylistEntity
17+
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
1618
import org.schabi.newpipe.extractor.ServiceList
1719
import org.schabi.newpipe.extractor.stream.StreamType
1820

@@ -22,13 +24,17 @@ class DatabaseMigrationTest {
2224
private const val DEFAULT_SERVICE_ID = 0
2325
private const val DEFAULT_URL = "https://www.youtube.com/watch?v=cDphUib5iG4"
2426
private const val DEFAULT_TITLE = "Test Title"
27+
private const val DEFAULT_NAME = "Test Name"
2528
private val DEFAULT_TYPE = StreamType.VIDEO_STREAM
2629
private const val DEFAULT_DURATION = 480L
2730
private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
2831
private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
2932

30-
private const val DEFAULT_SECOND_SERVICE_ID = 0
33+
private const val DEFAULT_SECOND_SERVICE_ID = 1
3134
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
35+
36+
private const val DEFAULT_THIRD_SERVICE_ID = 2
37+
private const val DEFAULT_THIRD_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
3238
}
3339

3440
@get:Rule
@@ -115,6 +121,13 @@ class DatabaseMigrationTest {
115121
Migrations.MIGRATION_7_8
116122
)
117123

124+
testHelper.runMigrationsAndValidate(
125+
AppDatabase.DATABASE_NAME,
126+
Migrations.DB_VER_9,
127+
true,
128+
Migrations.MIGRATION_8_9
129+
)
130+
118131
val migratedDatabaseV3 = getMigratedDatabase()
119132
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
120133

@@ -198,6 +211,11 @@ class DatabaseMigrationTest {
198211
true, Migrations.MIGRATION_7_8
199212
)
200213

214+
testHelper.runMigrationsAndValidate(
215+
AppDatabase.DATABASE_NAME, Migrations.DB_VER_9,
216+
true, Migrations.MIGRATION_8_9
217+
)
218+
201219
val migratedDatabaseV8 = getMigratedDatabase()
202220
val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()
203221

@@ -207,6 +225,94 @@ class DatabaseMigrationTest {
207225
assertNotEquals(listFromDB[0].serviceId, listFromDB[1].serviceId)
208226
}
209227

228+
@Test
229+
fun migrateDatabaseFrom8to9() {
230+
val databaseInV8 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_8)
231+
232+
val localUid1: Long
233+
val localUid2: Long
234+
val remoteUid1: Long
235+
val remoteUid2: Long
236+
databaseInV8.run {
237+
localUid1 = insert(
238+
"playlists", SQLiteDatabase.CONFLICT_FAIL,
239+
ContentValues().apply {
240+
put("name", DEFAULT_NAME + "1")
241+
put("is_thumbnail_permanent", false)
242+
put("thumbnail_stream_id", -1)
243+
}
244+
)
245+
localUid2 = insert(
246+
"playlists", SQLiteDatabase.CONFLICT_FAIL,
247+
ContentValues().apply {
248+
put("name", DEFAULT_NAME + "2")
249+
put("is_thumbnail_permanent", false)
250+
put("thumbnail_stream_id", -1)
251+
}
252+
)
253+
delete(
254+
"playlists", "uid = ?",
255+
Array(1) { localUid1 }
256+
)
257+
remoteUid1 = insert(
258+
"remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
259+
ContentValues().apply {
260+
put("service_id", DEFAULT_SERVICE_ID)
261+
put("url", DEFAULT_URL)
262+
}
263+
)
264+
remoteUid2 = insert(
265+
"remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
266+
ContentValues().apply {
267+
put("service_id", DEFAULT_SECOND_SERVICE_ID)
268+
put("url", DEFAULT_SECOND_URL)
269+
}
270+
)
271+
delete(
272+
"remote_playlists", "uid = ?",
273+
Array(1) { remoteUid2 }
274+
)
275+
close()
276+
}
277+
278+
testHelper.runMigrationsAndValidate(
279+
AppDatabase.DATABASE_NAME,
280+
Migrations.DB_VER_9,
281+
true,
282+
Migrations.MIGRATION_8_9
283+
)
284+
285+
val migratedDatabaseV9 = getMigratedDatabase()
286+
var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
287+
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
288+
289+
assertEquals(1, localListFromDB.size)
290+
assertEquals(localUid2, localListFromDB[0].uid)
291+
assertEquals(-1, localListFromDB[0].displayIndex)
292+
assertEquals(1, remoteListFromDB.size)
293+
assertEquals(remoteUid1, remoteListFromDB[0].uid)
294+
assertEquals(-1, remoteListFromDB[0].displayIndex)
295+
296+
val localUid3 = migratedDatabaseV9.playlistDAO().insert(
297+
PlaylistEntity(DEFAULT_NAME + "3", false, -1, -1)
298+
)
299+
val remoteUid3 = migratedDatabaseV9.playlistRemoteDAO().insert(
300+
PlaylistRemoteEntity(
301+
DEFAULT_THIRD_SERVICE_ID, DEFAULT_NAME, DEFAULT_THIRD_URL,
302+
DEFAULT_THUMBNAIL, DEFAULT_UPLOADER_NAME, -1, 10
303+
)
304+
)
305+
306+
localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
307+
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
308+
assertEquals(2, localListFromDB.size)
309+
assertEquals(localUid3, localListFromDB[1].uid)
310+
assertEquals(-1, localListFromDB[1].displayIndex)
311+
assertEquals(2, remoteListFromDB.size)
312+
assertEquals(remoteUid3, remoteListFromDB[1].uid)
313+
assertEquals(-1, remoteListFromDB[1].displayIndex)
314+
}
315+
210316
private fun getMigratedDatabase(): AppDatabase {
211317
val database: AppDatabase = Room.databaseBuilder(
212318
ApplicationProvider.getApplicationContext(),

app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static org.schabi.newpipe.database.Migrations.MIGRATION_5_6;
99
import static org.schabi.newpipe.database.Migrations.MIGRATION_6_7;
1010
import static org.schabi.newpipe.database.Migrations.MIGRATION_7_8;
11+
import static org.schabi.newpipe.database.Migrations.MIGRATION_8_9;
1112

1213
import android.content.Context;
1314
import android.database.Cursor;
@@ -28,7 +29,7 @@ private static AppDatabase getDatabase(final Context context) {
2829
return Room
2930
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
3031
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
31-
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8)
32+
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9)
3233
.build();
3334
}
3435

app/src/main/java/org/schabi/newpipe/database/AppDatabase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.schabi.newpipe.database;
22

3-
import static org.schabi.newpipe.database.Migrations.DB_VER_8;
3+
import static org.schabi.newpipe.database.Migrations.DB_VER_9;
44

55
import androidx.room.Database;
66
import androidx.room.RoomDatabase;
@@ -38,7 +38,7 @@
3838
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
3939
FeedLastUpdatedEntity.class
4040
},
41-
version = DB_VER_8
41+
version = DB_VER_9
4242
)
4343
public abstract class AppDatabase extends RoomDatabase {
4444
public static final String DATABASE_NAME = "newpipe.db";

app/src/main/java/org/schabi/newpipe/database/Migrations.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public final class Migrations {
2626
public static final int DB_VER_6 = 6;
2727
public static final int DB_VER_7 = 7;
2828
public static final int DB_VER_8 = 8;
29+
public static final int DB_VER_9 = 9;
2930

3031
private static final String TAG = Migrations.class.getName();
3132
public static final boolean DEBUG = MainActivity.DEBUG;
@@ -187,7 +188,7 @@ public void migrate(@NonNull final SupportSQLiteDatabase database) {
187188
@Override
188189
public void migrate(@NonNull final SupportSQLiteDatabase database) {
189190
database.execSQL("ALTER TABLE `subscriptions` ADD COLUMN `notification_mode` "
190-
+ "INTEGER NOT NULL DEFAULT 0");
191+
+ "INTEGER NOT NULL DEFAULT 0");
191192
}
192193
};
193194

@@ -245,6 +246,62 @@ public void migrate(@NonNull final SupportSQLiteDatabase database) {
245246
}
246247
};
247248

249+
public static final Migration MIGRATION_8_9 = new Migration(DB_VER_8, DB_VER_9) {
250+
@Override
251+
public void migrate(@NonNull final SupportSQLiteDatabase database) {
252+
try {
253+
database.beginTransaction();
254+
255+
// Update playlists.
256+
// Create a temp table to initialize display_index.
257+
database.execSQL("CREATE TABLE `playlists_tmp` "
258+
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
259+
+ "`name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, "
260+
+ "`thumbnail_stream_id` INTEGER NOT NULL, "
261+
+ "`display_index` INTEGER NOT NULL)");
262+
database.execSQL("INSERT INTO `playlists_tmp` "
263+
+ "(`uid`, `name`, `is_thumbnail_permanent`, `thumbnail_stream_id`, "
264+
+ "`display_index`) "
265+
+ "SELECT `uid`, `name`, `is_thumbnail_permanent`, `thumbnail_stream_id`, "
266+
+ "-1 "
267+
+ "FROM `playlists`");
268+
269+
// Replace the old table, note that this also removes the index on the name which
270+
// we don't need anymore.
271+
database.execSQL("DROP TABLE `playlists`");
272+
database.execSQL("ALTER TABLE `playlists_tmp` RENAME TO `playlists`");
273+
274+
275+
// Update remote_playlists.
276+
// Create a temp table to initialize display_index.
277+
database.execSQL("CREATE TABLE `remote_playlists_tmp` "
278+
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
279+
+ "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, "
280+
+ "`thumbnail_url` TEXT, `uploader` TEXT, "
281+
+ "`display_index` INTEGER NOT NULL,"
282+
+ "`stream_count` INTEGER)");
283+
database.execSQL("INSERT INTO `remote_playlists_tmp` (`uid`, `service_id`, "
284+
+ "`name`, `url`, `thumbnail_url`, `uploader`, `display_index`, "
285+
+ "`stream_count`)"
286+
+ "SELECT `uid`, `service_id`, `name`, `url`, `thumbnail_url`, `uploader`, "
287+
+ "-1, `stream_count` FROM `remote_playlists`");
288+
289+
// Replace the old table, note that this also removes the index on the name which
290+
// we don't need anymore.
291+
database.execSQL("DROP TABLE `remote_playlists`");
292+
database.execSQL("ALTER TABLE `remote_playlists_tmp` RENAME TO `remote_playlists`");
293+
294+
// Create index on the new table.
295+
database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` "
296+
+ "ON `remote_playlists` (`service_id`, `url`)");
297+
298+
database.setTransactionSuccessful();
299+
} finally {
300+
database.endTransaction();
301+
}
302+
}
303+
};
304+
248305
private Migrations() {
249306
}
250307
}

app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistDuplicatesEntry.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ public class PlaylistDuplicatesEntry extends PlaylistMetadataEntry {
1313
@ColumnInfo(name = PLAYLIST_TIMES_STREAM_IS_CONTAINED)
1414
public final long timesStreamIsContained;
1515

16+
@SuppressWarnings("checkstyle:ParameterNumber")
1617
public PlaylistDuplicatesEntry(final long uid,
1718
final String name,
1819
final String thumbnailUrl,
20+
final boolean isThumbnailPermanent,
21+
final long thumbnailStreamId,
22+
final long displayIndex,
1923
final long streamCount,
2024
final long timesStreamIsContained) {
21-
super(uid, name, thumbnailUrl, streamCount);
25+
super(uid, name, thumbnailUrl, isThumbnailPermanent, thumbnailStreamId, displayIndex,
26+
streamCount);
2227
this.timesStreamIsContained = timesStreamIsContained;
2328
}
2429
}
Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
package org.schabi.newpipe.database.playlist;
22

33
import org.schabi.newpipe.database.LocalItem;
4-
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
5-
6-
import java.util.Comparator;
7-
import java.util.List;
8-
import java.util.stream.Collectors;
9-
import java.util.stream.Stream;
104

115
public interface PlaylistLocalItem extends LocalItem {
126
String getOrderingName();
137

14-
static List<PlaylistLocalItem> merge(
15-
final List<PlaylistMetadataEntry> localPlaylists,
16-
final List<PlaylistRemoteEntity> remotePlaylists) {
17-
return Stream.concat(localPlaylists.stream(), remotePlaylists.stream())
18-
.sorted(Comparator.comparing(PlaylistLocalItem::getOrderingName,
19-
Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)))
20-
.collect(Collectors.toList());
21-
}
8+
long getDisplayIndex();
9+
10+
long getUid();
11+
12+
void setDisplayIndex(long displayIndex);
2213
}

app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,40 @@
22

33
import androidx.room.ColumnInfo;
44

5+
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_DISPLAY_INDEX;
56
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
67
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
8+
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_PERMANENT;
9+
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID;
710
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
811

912
public class PlaylistMetadataEntry implements PlaylistLocalItem {
1013
public static final String PLAYLIST_STREAM_COUNT = "streamCount";
1114

1215
@ColumnInfo(name = PLAYLIST_ID)
13-
public final long uid;
16+
private final long uid;
1417
@ColumnInfo(name = PLAYLIST_NAME)
1518
public final String name;
19+
@ColumnInfo(name = PLAYLIST_THUMBNAIL_PERMANENT)
20+
private final boolean isThumbnailPermanent;
21+
@ColumnInfo(name = PLAYLIST_THUMBNAIL_STREAM_ID)
22+
private final long thumbnailStreamId;
1623
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
1724
public final String thumbnailUrl;
25+
@ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
26+
private long displayIndex;
1827
@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
1928
public final long streamCount;
2029

2130
public PlaylistMetadataEntry(final long uid, final String name, final String thumbnailUrl,
22-
final long streamCount) {
31+
final boolean isThumbnailPermanent, final long thumbnailStreamId,
32+
final long displayIndex, final long streamCount) {
2333
this.uid = uid;
2434
this.name = name;
2535
this.thumbnailUrl = thumbnailUrl;
36+
this.isThumbnailPermanent = isThumbnailPermanent;
37+
this.thumbnailStreamId = thumbnailStreamId;
38+
this.displayIndex = displayIndex;
2639
this.streamCount = streamCount;
2740
}
2841

@@ -35,4 +48,27 @@ public LocalItemType getLocalItemType() {
3548
public String getOrderingName() {
3649
return name;
3750
}
51+
52+
public boolean isThumbnailPermanent() {
53+
return isThumbnailPermanent;
54+
}
55+
56+
public long getThumbnailStreamId() {
57+
return thumbnailStreamId;
58+
}
59+
60+
@Override
61+
public long getDisplayIndex() {
62+
return displayIndex;
63+
}
64+
65+
@Override
66+
public long getUid() {
67+
return uid;
68+
}
69+
70+
@Override
71+
public void setDisplayIndex(final long displayIndex) {
72+
this.displayIndex = displayIndex;
73+
}
3874
}

0 commit comments

Comments
 (0)