mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-25 04:22:30 +01:00
Fixed the bug by replacing the thumbnail_url with the thumbnail_stream_id
This commit is contained in:
parent
fceec71ad3
commit
68097568d5
737
app/schemas/org.schabi.newpipe.database.AppDatabase/7.json
Normal file
737
app/schemas/org.schabi.newpipe.database.AppDatabase/7.json
Normal file
@ -0,0 +1,737 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 7,
|
||||||
|
"identityHash": "012fc8e7ad3333f1597347f34e76a513",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "subscriptions",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceId",
|
||||||
|
"columnName": "service_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "avatarUrl",
|
||||||
|
"columnName": "avatar_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subscriberCount",
|
||||||
|
"columnName": "subscriber_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "notificationMode",
|
||||||
|
"columnName": "notification_mode",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_subscriptions_service_id_url",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"service_id",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "search_history",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "creationDate",
|
||||||
|
"columnName": "creation_date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceId",
|
||||||
|
"columnName": "service_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_search_history_search",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "streams",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceId",
|
||||||
|
"columnName": "service_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamType",
|
||||||
|
"columnName": "stream_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "uploader",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploaderUrl",
|
||||||
|
"columnName": "uploader_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbnailUrl",
|
||||||
|
"columnName": "thumbnail_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "viewCount",
|
||||||
|
"columnName": "view_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "textualUploadDate",
|
||||||
|
"columnName": "textual_upload_date",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploadDate",
|
||||||
|
"columnName": "upload_date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isUploadDateApproximation",
|
||||||
|
"columnName": "is_upload_date_approximation",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_streams_service_id_url",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"service_id",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "stream_history",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "streamUid",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accessDate",
|
||||||
|
"columnName": "access_date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "repeatCount",
|
||||||
|
"columnName": "repeat_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"stream_id",
|
||||||
|
"access_date"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_stream_history_stream_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"stream_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "streams",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"stream_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "stream_state",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "streamUid",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "progressMillis",
|
||||||
|
"columnName": "progress_time",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"stream_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "streams",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"stream_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "playlists",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, `thumbnail_stream_id` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isThumbnailPermanent",
|
||||||
|
"columnName": "is_thumbnail_permanent",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbnailStreamId",
|
||||||
|
"columnName": "thumbnail_stream_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_playlists_name",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "playlist_stream_join",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistUid",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamUid",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "index",
|
||||||
|
"columnName": "join_index",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"playlist_id",
|
||||||
|
"join_index"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_playlist_stream_join_playlist_id_join_index",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"playlist_id",
|
||||||
|
"join_index"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_playlist_stream_join_stream_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"stream_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "playlists",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"playlist_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "streams",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"stream_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "remote_playlists",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceId",
|
||||||
|
"columnName": "service_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbnailUrl",
|
||||||
|
"columnName": "thumbnail_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "uploader",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamCount",
|
||||||
|
"columnName": "stream_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_remote_playlists_name",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_remote_playlists_service_id_url",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"service_id",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "feed",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subscriptionId",
|
||||||
|
"columnName": "subscription_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"stream_id",
|
||||||
|
"subscription_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_feed_subscription_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"subscription_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "streams",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"stream_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "subscriptions",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"subscription_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "feed_group",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "icon",
|
||||||
|
"columnName": "icon_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sortOrder",
|
||||||
|
"columnName": "sort_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_feed_group_sort_order",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"sort_order"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "feed_group_subscription_join",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "feedGroupId",
|
||||||
|
"columnName": "group_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subscriptionId",
|
||||||
|
"columnName": "subscription_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"group_id",
|
||||||
|
"subscription_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_feed_group_subscription_join_subscription_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"subscription_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "feed_group",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"group_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "subscriptions",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"subscription_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "feed_last_updated",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "subscriptionId",
|
||||||
|
"columnName": "subscription_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastUpdated",
|
||||||
|
"columnName": "last_updated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"subscription_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "subscriptions",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"subscription_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"uid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '012fc8e7ad3333f1597347f34e76a513')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -101,6 +101,13 @@ class DatabaseMigrationTest {
|
|||||||
Migrations.MIGRATION_5_6
|
Migrations.MIGRATION_5_6
|
||||||
)
|
)
|
||||||
|
|
||||||
|
testHelper.runMigrationsAndValidate(
|
||||||
|
AppDatabase.DATABASE_NAME,
|
||||||
|
Migrations.DB_VER_7,
|
||||||
|
true,
|
||||||
|
Migrations.MIGRATION_6_7
|
||||||
|
)
|
||||||
|
|
||||||
val migratedDatabaseV3 = getMigratedDatabase()
|
val migratedDatabaseV3 = getMigratedDatabase()
|
||||||
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ 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_3_4;
|
||||||
import static org.schabi.newpipe.database.Migrations.MIGRATION_4_5;
|
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_5_6;
|
||||||
|
import static org.schabi.newpipe.database.Migrations.MIGRATION_6_7;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
@ -26,7 +27,7 @@ public final class NewPipeDatabase {
|
|||||||
return Room
|
return Room
|
||||||
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
|
||||||
MIGRATION_5_6)
|
MIGRATION_5_6, MIGRATION_6_7)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.schabi.newpipe.database;
|
package org.schabi.newpipe.database;
|
||||||
|
|
||||||
import static org.schabi.newpipe.database.Migrations.DB_VER_6;
|
import static org.schabi.newpipe.database.Migrations.DB_VER_7;
|
||||||
|
|
||||||
import androidx.room.Database;
|
import androidx.room.Database;
|
||||||
import androidx.room.RoomDatabase;
|
import androidx.room.RoomDatabase;
|
||||||
@ -38,7 +38,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
|||||||
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
|
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
|
||||||
FeedLastUpdatedEntity.class
|
FeedLastUpdatedEntity.class
|
||||||
},
|
},
|
||||||
version = DB_VER_6
|
version = DB_VER_7
|
||||||
)
|
)
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
public static final String DATABASE_NAME = "newpipe.db";
|
public static final String DATABASE_NAME = "newpipe.db";
|
||||||
|
@ -24,6 +24,7 @@ public final class Migrations {
|
|||||||
public static final int DB_VER_4 = 4;
|
public static final int DB_VER_4 = 4;
|
||||||
public static final int DB_VER_5 = 5;
|
public static final int DB_VER_5 = 5;
|
||||||
public static final int DB_VER_6 = 6;
|
public static final int DB_VER_6 = 6;
|
||||||
|
public static final int DB_VER_7 = 7;
|
||||||
|
|
||||||
private static final String TAG = Migrations.class.getName();
|
private static final String TAG = Migrations.class.getName();
|
||||||
public static final boolean DEBUG = MainActivity.DEBUG;
|
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
@ -197,6 +198,47 @@ public final class Migrations {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final Migration MIGRATION_6_7 = new Migration(DB_VER_6, DB_VER_7) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull final SupportSQLiteDatabase database) {
|
||||||
|
// Create a new column thumbnail_stream_id
|
||||||
|
database.execSQL("ALTER TABLE `playlists` ADD COLUMN `thumbnail_stream_id` "
|
||||||
|
+ "INTEGER NOT NULL DEFAULT -1");
|
||||||
|
|
||||||
|
// Migrate the thumbnail_url to the thumbnail_stream_id
|
||||||
|
database.execSQL("CREATE TEMPORARY TABLE temporary_table AS"
|
||||||
|
+ " SELECT p.uid AS playlist_uid, s.uid AS stream_uid"
|
||||||
|
+ " FROM playlists p"
|
||||||
|
+ " LEFT JOIN playlist_stream_join ps ON p.uid = ps.playlist_id"
|
||||||
|
+ " LEFT JOIN streams s ON s.uid = ps.stream_id"
|
||||||
|
+ " WHERE s.thumbnail_url = p.thumbnail_url");
|
||||||
|
|
||||||
|
database.execSQL("UPDATE playlists SET thumbnail_stream_id = ("
|
||||||
|
+ "SELECT CASE WHEN COUNT(*) != 0 then stream_uid ELSE -1 END "
|
||||||
|
+ "FROM temporary_table "
|
||||||
|
+ "WHERE playlist_uid = playlists.uid)");
|
||||||
|
|
||||||
|
database.execSQL("DROP TABLE temporary_table");
|
||||||
|
|
||||||
|
// Remove the thumbnail_url field in the playlist table
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `playlists_new`"
|
||||||
|
+ "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
|
||||||
|
+ "name TEXT, "
|
||||||
|
+ "is_thumbnail_permanent INTEGER NOT NULL, "
|
||||||
|
+ "thumbnail_stream_id INTEGER NOT NULL)");
|
||||||
|
|
||||||
|
database.execSQL("INSERT INTO playlists_new"
|
||||||
|
+ " SELECT uid, name, is_thumbnail_permanent, thumbnail_stream_id "
|
||||||
|
+ " FROM playlists");
|
||||||
|
|
||||||
|
|
||||||
|
database.execSQL("DROP TABLE playlists");
|
||||||
|
database.execSQL("ALTER TABLE playlists_new RENAME TO playlists");
|
||||||
|
database.execSQL("CREATE INDEX IF NOT EXISTS "
|
||||||
|
+ "`index_playlists_name` ON `playlists` (`name`)");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private Migrations() {
|
private Migrations() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,11 @@ import java.util.List;
|
|||||||
import io.reactivex.rxjava3.core.Flowable;
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
|
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.DEFAULT_THUMBNAIL;
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
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_NAME;
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||||
|
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 static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX;
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID;
|
||||||
@ -54,14 +56,14 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
|
|||||||
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||||
Flowable<Integer> getMaximumIndexOf(long playlistId);
|
Flowable<Integer> getMaximumIndexOf(long playlistId);
|
||||||
|
|
||||||
@Query("SELECT CASE WHEN COUNT(*) != 0 then " + STREAM_THUMBNAIL_URL + " ELSE :defaultUrl END"
|
@Query("SELECT CASE WHEN COUNT(*) != 0 then " + STREAM_ID + " ELSE -1 END"
|
||||||
+ " FROM " + STREAM_TABLE
|
+ " FROM " + STREAM_TABLE
|
||||||
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
|
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
|
||||||
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
|
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
|
||||||
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId "
|
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId "
|
||||||
+ " LIMIT 1"
|
+ " LIMIT 1"
|
||||||
)
|
)
|
||||||
Flowable<String> getAutomaticThumbnailUrl(long playlistId, String defaultUrl);
|
Flowable<Long> getAutomaticThumbnailUrl(long playlistId);
|
||||||
|
|
||||||
@RewriteQueriesToDropUnusedColumns
|
@RewriteQueriesToDropUnusedColumns
|
||||||
@Transaction
|
@Transaction
|
||||||
@ -84,12 +86,20 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
|
|||||||
Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
|
Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", "
|
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ","
|
||||||
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
|
|
||||||
|
|
||||||
|
+ " CASE WHEN " + PLAYLIST_THUMBNAIL_STREAM_ID + " = -1"
|
||||||
|
+ " THEN " + "'" + DEFAULT_THUMBNAIL + "'"
|
||||||
|
+ " ELSE (SELECT " + STREAM_THUMBNAIL_URL
|
||||||
|
+ " FROM " + STREAM_TABLE
|
||||||
|
+ " WHERE " + STREAM_TABLE + "." + STREAM_ID + " = " + PLAYLIST_THUMBNAIL_STREAM_ID
|
||||||
|
+ " ) END AS " + PLAYLIST_THUMBNAIL_URL + ", "
|
||||||
|
|
||||||
|
+ PLAYLIST_NAME + ", "
|
||||||
|
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
|
||||||
+ " FROM " + PLAYLIST_TABLE
|
+ " FROM " + PLAYLIST_TABLE
|
||||||
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
|
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
|
||||||
+ " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
|
+ " ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
|
||||||
+ " GROUP BY " + PLAYLIST_ID
|
+ " GROUP BY " + PLAYLIST_ID
|
||||||
+ " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
|
+ " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
|
||||||
Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
|
Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
|
||||||
|
@ -8,14 +8,20 @@ import androidx.room.PrimaryKey;
|
|||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
@Entity(tableName = PLAYLIST_TABLE,
|
@Entity(tableName = PLAYLIST_TABLE,
|
||||||
indices = {@Index(value = {PLAYLIST_NAME})})
|
indices = {@Index(value = {PLAYLIST_NAME})})
|
||||||
public class PlaylistEntity {
|
public class PlaylistEntity {
|
||||||
|
|
||||||
|
public static final String DEFAULT_THUMBNAIL = "drawable://"
|
||||||
|
+ R.drawable.placeholder_thumbnail_playlist;
|
||||||
public static final String PLAYLIST_TABLE = "playlists";
|
public static final String PLAYLIST_TABLE = "playlists";
|
||||||
public static final String PLAYLIST_ID = "uid";
|
public static final String PLAYLIST_ID = "uid";
|
||||||
public static final String PLAYLIST_NAME = "name";
|
public static final String PLAYLIST_NAME = "name";
|
||||||
public static final String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
|
public static final String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
|
||||||
public static final String PLAYLIST_THUMBNAIL_PERMANENT = "is_thumbnail_permanent";
|
public static final String PLAYLIST_THUMBNAIL_PERMANENT = "is_thumbnail_permanent";
|
||||||
|
public static final String PLAYLIST_THUMBNAIL_STREAM_ID = "thumbnail_stream_id";
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
@ColumnInfo(name = PLAYLIST_ID)
|
@ColumnInfo(name = PLAYLIST_ID)
|
||||||
@ -24,17 +30,17 @@ public class PlaylistEntity {
|
|||||||
@ColumnInfo(name = PLAYLIST_NAME)
|
@ColumnInfo(name = PLAYLIST_NAME)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
|
||||||
private String thumbnailUrl;
|
|
||||||
|
|
||||||
@ColumnInfo(name = PLAYLIST_THUMBNAIL_PERMANENT)
|
@ColumnInfo(name = PLAYLIST_THUMBNAIL_PERMANENT)
|
||||||
private boolean isThumbnailPermanent;
|
private boolean isThumbnailPermanent;
|
||||||
|
|
||||||
public PlaylistEntity(final String name, final String thumbnailUrl,
|
@ColumnInfo(name = PLAYLIST_THUMBNAIL_STREAM_ID)
|
||||||
final boolean isThumbnailPermanent) {
|
private long thumbnailStreamId;
|
||||||
|
|
||||||
|
public PlaylistEntity(final String name, final boolean isThumbnailPermanent,
|
||||||
|
final long thumbnailStreamId) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
|
||||||
this.isThumbnailPermanent = isThumbnailPermanent;
|
this.isThumbnailPermanent = isThumbnailPermanent;
|
||||||
|
this.thumbnailStreamId = thumbnailStreamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getUid() {
|
public long getUid() {
|
||||||
@ -53,12 +59,12 @@ public class PlaylistEntity {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getThumbnailUrl() {
|
public long getThumbnailStreamId() {
|
||||||
return thumbnailUrl;
|
return thumbnailStreamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setThumbnailUrl(final String thumbnailUrl) {
|
public void setThumbnailStreamId(final long thumbnailStreamId) {
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnailStreamId = thumbnailStreamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getIsThumbnailPermanent() {
|
public boolean getIsThumbnailPermanent() {
|
||||||
|
@ -280,10 +280,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
showDeleteDialog(selectedItem.name,
|
showDeleteDialog(selectedItem.name,
|
||||||
localPlaylistManager.deletePlaylist(selectedItem.uid));
|
localPlaylistManager.deletePlaylist(selectedItem.uid));
|
||||||
} else if (isThumbnailPermanent && items.get(index).equals(unsetThumbnail)) {
|
} else if (isThumbnailPermanent && items.get(index).equals(unsetThumbnail)) {
|
||||||
final String thumbnailUrl = localPlaylistManager
|
final long thumbnailStreamId = localPlaylistManager
|
||||||
.getAutomaticPlaylistThumbnail(selectedItem.uid);
|
.getAutomaticPlaylistThumbnailStreamId(selectedItem.uid);
|
||||||
localPlaylistManager
|
localPlaylistManager
|
||||||
.changePlaylistThumbnail(selectedItem.uid, thumbnailUrl, false)
|
.changePlaylistThumbnail(selectedItem.uid, thumbnailStreamId, false)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.local.LocalItemListAdapter;
|
import org.schabi.newpipe.local.LocalItemListAdapter;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
@ -131,17 +132,19 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||||||
final Toast successToast = Toast.makeText(getContext(),
|
final Toast successToast = Toast.makeText(getContext(),
|
||||||
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
|
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
|
||||||
|
|
||||||
if (playlist.thumbnailUrl
|
|
||||||
.equals("drawable://" + R.drawable.placeholder_thumbnail_playlist)) {
|
|
||||||
playlistDisposables.add(manager
|
|
||||||
.changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl(), false)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(ignored -> successToast.show()));
|
|
||||||
}
|
|
||||||
|
|
||||||
playlistDisposables.add(manager.appendToPlaylist(playlist.uid, streams)
|
playlistDisposables.add(manager.appendToPlaylist(playlist.uid, streams)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> successToast.show()));
|
.subscribe(ignored -> {
|
||||||
|
successToast.show();
|
||||||
|
|
||||||
|
if (playlist.thumbnailUrl.equals(PlaylistEntity.DEFAULT_THUMBNAIL)) {
|
||||||
|
playlistDisposables.add(manager
|
||||||
|
.changePlaylistThumbnail(playlist.uid, streams.get(0).getUid(),
|
||||||
|
false)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(ignore -> successToast.show()));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
requireDialog().dismiss();
|
requireDialog().dismiss();
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
|
|||||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||||
|
|
||||||
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> {
|
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> {
|
||||||
|
public static final long DEFAULT_THUMBNAIL_ID = -1;
|
||||||
|
public static final long NO_THUMBNAIL_ID = -2;
|
||||||
// Save the list 10 seconds after the last change occurred
|
// Save the list 10 seconds after the last change occurred
|
||||||
private static final long SAVE_DEBOUNCE_MILLIS = 10000;
|
private static final long SAVE_DEBOUNCE_MILLIS = 10000;
|
||||||
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
|
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
|
||||||
@ -417,8 +419,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
if (indexInHistory < 0) {
|
if (indexInHistory < 0) {
|
||||||
itemsToKeep.add(playlistItem);
|
itemsToKeep.add(playlistItem);
|
||||||
} else if (!isThumbnailPermanent && !thumbnailVideoRemoved
|
} else if (!isThumbnailPermanent && !thumbnailVideoRemoved
|
||||||
&& playlistManager.getPlaylistThumbnail(playlistId)
|
&& playlistManager.getPlaylistThumbnailStreamId(playlistId)
|
||||||
.equals(playlistItem.getStreamEntity().getThumbnailUrl())) {
|
== playlistItem.getStreamEntity().getUid()) {
|
||||||
thumbnailVideoRemoved = true;
|
thumbnailVideoRemoved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -438,8 +440,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
&& !streamStateEntity.isFinished(duration))) {
|
&& !streamStateEntity.isFinished(duration))) {
|
||||||
itemsToKeep.add(playlistItem);
|
itemsToKeep.add(playlistItem);
|
||||||
} else if (!isThumbnailPermanent && !thumbnailVideoRemoved
|
} else if (!isThumbnailPermanent && !thumbnailVideoRemoved
|
||||||
&& playlistManager.getPlaylistThumbnail(playlistId)
|
&& playlistManager.getPlaylistThumbnailStreamId(playlistId)
|
||||||
.equals(playlistItem.getStreamEntity().getThumbnailUrl())) {
|
== playlistItem.getStreamEntity().getUid()) {
|
||||||
thumbnailVideoRemoved = true;
|
thumbnailVideoRemoved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -587,7 +589,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeThumbnailUrl(final String thumbnailUrl, final boolean isPermanent) {
|
private void changeThumbnailStreamId(final long thumbnailStreamId, final boolean isPermanent) {
|
||||||
if (playlistManager == null || (!isPermanent && playlistManager
|
if (playlistManager == null || (!isPermanent && playlistManager
|
||||||
.getIsPlaylistThumbnailPermanent(playlistId))) {
|
.getIsPlaylistThumbnailPermanent(playlistId))) {
|
||||||
return;
|
return;
|
||||||
@ -599,11 +601,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Updating playlist id=[" + playlistId + "] "
|
Log.d(TAG, "Updating playlist id=[" + playlistId + "] "
|
||||||
+ "with new thumbnail url=[" + thumbnailUrl + "]");
|
+ "with new thumbnail stream id=[" + thumbnailStreamId + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
final Disposable disposable = playlistManager
|
final Disposable disposable = playlistManager
|
||||||
.changePlaylistThumbnail(playlistId, thumbnailUrl, isPermanent)
|
.changePlaylistThumbnail(playlistId, thumbnailStreamId, isPermanent)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignore -> successToast.show(), throwable ->
|
.subscribe(ignore -> successToast.show(), throwable ->
|
||||||
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
@ -616,16 +618,16 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String newThumbnailUrl;
|
final long thumbnailStreamId;
|
||||||
|
|
||||||
if (!itemListAdapter.getItemsList().isEmpty()) {
|
if (!itemListAdapter.getItemsList().isEmpty()) {
|
||||||
newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0))
|
thumbnailStreamId = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0))
|
||||||
.getStreamEntity().getThumbnailUrl();
|
.getStreamEntity().getUid();
|
||||||
} else {
|
} else {
|
||||||
newThumbnailUrl = "drawable://" + R.drawable.placeholder_thumbnail_playlist;
|
thumbnailStreamId = DEFAULT_THUMBNAIL_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
changeThumbnailUrl(newThumbnailUrl, false);
|
changeThumbnailStreamId(thumbnailStreamId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteItem(final PlaylistStreamEntry item) {
|
private void deleteItem(final PlaylistStreamEntry item) {
|
||||||
@ -634,8 +636,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemListAdapter.removeItem(item);
|
itemListAdapter.removeItem(item);
|
||||||
if (playlistManager.getPlaylistThumbnail(playlistId)
|
if (playlistManager.getPlaylistThumbnailStreamId(playlistId) == item.getStreamId()) {
|
||||||
.equals(item.getStreamEntity().getThumbnailUrl())) {
|
|
||||||
updateThumbnailUrl();
|
updateThumbnailUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -793,7 +794,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
.setAction(
|
.setAction(
|
||||||
StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
|
StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
|
||||||
(f, i) ->
|
(f, i) ->
|
||||||
changeThumbnailUrl(item.getStreamEntity().getThumbnailUrl(),
|
changeThumbnailStreamId(item.getStreamEntity().getUid(),
|
||||||
true))
|
true))
|
||||||
.setAction(
|
.setAction(
|
||||||
StreamDialogDefaultEntry.DELETE,
|
StreamDialogDefaultEntry.DELETE,
|
||||||
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.local.playlist;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import org.schabi.newpipe.database.AppDatabase;
|
import org.schabi.newpipe.database.AppDatabase;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||||
@ -42,28 +41,34 @@ public class LocalPlaylistManager {
|
|||||||
}
|
}
|
||||||
final StreamEntity defaultStream = streams.get(0);
|
final StreamEntity defaultStream = streams.get(0);
|
||||||
final PlaylistEntity newPlaylist =
|
final PlaylistEntity newPlaylist =
|
||||||
new PlaylistEntity(name, defaultStream.getThumbnailUrl(), false);
|
new PlaylistEntity(name, false, defaultStream.getUid());
|
||||||
|
|
||||||
return Maybe.fromCallable(() -> database.runInTransaction(() ->
|
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
upsertStreams(playlistTable.insert(newPlaylist), streams, 0))
|
final List<Long> streamIds = streamTable.upsertAll(streams);
|
||||||
).subscribeOn(Schedulers.io());
|
newPlaylist.setThumbnailStreamId(streamIds.get(0));
|
||||||
|
|
||||||
|
return insertJoinEntities(playlistTable.insert(newPlaylist),
|
||||||
|
streamIds, 0);
|
||||||
|
}
|
||||||
|
)).subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Maybe<List<Long>> appendToPlaylist(final long playlistId,
|
public Maybe<List<Long>> appendToPlaylist(final long playlistId,
|
||||||
final List<StreamEntity> streams) {
|
final List<StreamEntity> streams) {
|
||||||
return playlistStreamTable.getMaximumIndexOf(playlistId)
|
return playlistStreamTable.getMaximumIndexOf(playlistId)
|
||||||
.firstElement()
|
.firstElement()
|
||||||
.map(maxJoinIndex -> database.runInTransaction(() ->
|
.map(maxJoinIndex -> database.runInTransaction(() -> {
|
||||||
upsertStreams(playlistId, streams, maxJoinIndex + 1))
|
final List<Long> streamIds = streamTable.upsertAll(streams);
|
||||||
).subscribeOn(Schedulers.io());
|
return insertJoinEntities(playlistId, streamIds, maxJoinIndex + 1);
|
||||||
|
}
|
||||||
|
)).subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Long> upsertStreams(final long playlistId,
|
private List<Long> insertJoinEntities(final long playlistId, final List<Long> streamIds,
|
||||||
final List<StreamEntity> streams,
|
final int indexOffset) {
|
||||||
final int indexOffset) {
|
|
||||||
|
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
|
||||||
|
|
||||||
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
|
|
||||||
final List<Long> streamIds = streamTable.upsertAll(streams);
|
|
||||||
for (int index = 0; index < streamIds.size(); index++) {
|
for (int index = 0; index < streamIds.size(); index++) {
|
||||||
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(index),
|
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(index),
|
||||||
index + indexOffset));
|
index + indexOffset));
|
||||||
@ -97,17 +102,17 @@ public class LocalPlaylistManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Maybe<Integer> renamePlaylist(final long playlistId, final String name) {
|
public Maybe<Integer> renamePlaylist(final long playlistId, final String name) {
|
||||||
return modifyPlaylist(playlistId, name, null, false);
|
return modifyPlaylist(playlistId, name, LocalPlaylistFragment.NO_THUMBNAIL_ID, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Maybe<Integer> changePlaylistThumbnail(final long playlistId,
|
public Maybe<Integer> changePlaylistThumbnail(final long playlistId,
|
||||||
final String thumbnailUrl,
|
final long thumbnailStreamId,
|
||||||
final boolean isPermanent) {
|
final boolean isPermanent) {
|
||||||
return modifyPlaylist(playlistId, null, thumbnailUrl, isPermanent);
|
return modifyPlaylist(playlistId, null, thumbnailStreamId, isPermanent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPlaylistThumbnail(final long playlistId) {
|
public long getPlaylistThumbnailStreamId(final long playlistId) {
|
||||||
return playlistTable.getPlaylist(playlistId).blockingFirst().get(0).getThumbnailUrl();
|
return playlistTable.getPlaylist(playlistId).blockingFirst().get(0).getThumbnailStreamId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getIsPlaylistThumbnailPermanent(final long playlistId) {
|
public boolean getIsPlaylistThumbnailPermanent(final long playlistId) {
|
||||||
@ -115,14 +120,18 @@ public class LocalPlaylistManager {
|
|||||||
.getIsThumbnailPermanent();
|
.getIsThumbnailPermanent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAutomaticPlaylistThumbnail(final long playlistId) {
|
public long getAutomaticPlaylistThumbnailStreamId(final long playlistId) {
|
||||||
final String def = "drawable://" + R.drawable.placeholder_thumbnail_playlist;
|
final long streamId = playlistStreamTable.getAutomaticThumbnailUrl(playlistId)
|
||||||
return playlistStreamTable.getAutomaticThumbnailUrl(playlistId, def).blockingFirst();
|
.blockingFirst();
|
||||||
|
if (streamId < 0) {
|
||||||
|
return LocalPlaylistFragment.DEFAULT_THUMBNAIL_ID;
|
||||||
|
}
|
||||||
|
return streamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Maybe<Integer> modifyPlaylist(final long playlistId,
|
private Maybe<Integer> modifyPlaylist(final long playlistId,
|
||||||
@Nullable final String name,
|
@Nullable final String name,
|
||||||
@Nullable final String thumbnailUrl,
|
final long thumbnailStreamId,
|
||||||
final boolean isPermanent) {
|
final boolean isPermanent) {
|
||||||
return playlistTable.getPlaylist(playlistId)
|
return playlistTable.getPlaylist(playlistId)
|
||||||
.firstElement()
|
.firstElement()
|
||||||
@ -132,8 +141,8 @@ public class LocalPlaylistManager {
|
|||||||
if (name != null) {
|
if (name != null) {
|
||||||
playlist.setName(name);
|
playlist.setName(name);
|
||||||
}
|
}
|
||||||
if (thumbnailUrl != null) {
|
if (thumbnailStreamId != LocalPlaylistFragment.NO_THUMBNAIL_ID) {
|
||||||
playlist.setThumbnailUrl(thumbnailUrl);
|
playlist.setThumbnailStreamId(thumbnailStreamId);
|
||||||
playlist.setIsThumbnailPermanent(isPermanent);
|
playlist.setIsThumbnailPermanent(isPermanent);
|
||||||
}
|
}
|
||||||
return playlistTable.update(playlist);
|
return playlistTable.update(playlist);
|
||||||
|
Loading…
Reference in New Issue
Block a user