From 859da3d1955f2c5fb4fa75d45d1880a89b61954d Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Fri, 16 Dec 2022 08:21:40 +0530 Subject: [PATCH] Add database for downloads Database keep trac of downloaded item `Audio`, `Video` and `Subtitle`. --- .../10.json | 470 ++++++++++++++++++ .../com/github/libretube/db/AppDatabase.kt | 17 +- .../github/libretube/db/dao/DownloadDao.kt | 51 ++ .../com/github/libretube/db/obj/Download.kt | 15 + .../github/libretube/db/obj/DownloadItem.kt | 32 ++ .../libretube/db/obj/DownloadWithItems.kt | 13 + .../com/github/libretube/enums/FileType.kt | 7 + .../github/libretube/extensions/Normalize.kt | 7 + 8 files changed, 609 insertions(+), 3 deletions(-) create mode 100644 app/schemas/com.github.libretube.db.AppDatabase/10.json create mode 100644 app/src/main/java/com/github/libretube/db/dao/DownloadDao.kt create mode 100644 app/src/main/java/com/github/libretube/db/obj/Download.kt create mode 100644 app/src/main/java/com/github/libretube/db/obj/DownloadItem.kt create mode 100644 app/src/main/java/com/github/libretube/db/obj/DownloadWithItems.kt create mode 100644 app/src/main/java/com/github/libretube/enums/FileType.kt diff --git a/app/schemas/com.github.libretube.db.AppDatabase/10.json b/app/schemas/com.github.libretube.db.AppDatabase/10.json new file mode 100644 index 000000000..632e2b7d0 --- /dev/null +++ b/app/schemas/com.github.libretube.db.AppDatabase/10.json @@ -0,0 +1,470 @@ +{ + "formatVersion": 1, + "database": { + "version": 10, + "identityHash": "3df3f5c01e36e4e7fd3e02ba708e5d86", + "entities": [ + { + "tableName": "watchHistoryItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `title` TEXT, `uploadDate` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `thumbnailUrl` TEXT, `duration` INTEGER, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "videoId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "watchPosition", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "videoId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "searchHistoryItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`query` TEXT NOT NULL, PRIMARY KEY(`query`))", + "fields": [ + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "query" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "customInstance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `apiUrl` TEXT NOT NULL, `frontendUrl` TEXT NOT NULL, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "apiUrl", + "columnName": "apiUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "frontendUrl", + "columnName": "frontendUrl", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "name" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "localSubscription", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channelId` TEXT NOT NULL, PRIMARY KEY(`channelId`))", + "fields": [ + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "channelId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "playlistBookmark", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlistId` TEXT NOT NULL, `playlistName` TEXT, `thumbnailUrl` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, PRIMARY KEY(`playlistId`))", + "fields": [ + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playlistName", + "columnName": "playlistName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "playlistId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalPlaylist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `thumbnailUrl` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalPlaylistItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `playlistId` INTEGER NOT NULL, `videoId` TEXT NOT NULL, `title` TEXT, `uploadDate` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `thumbnailUrl` TEXT, `duration` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "download", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `uploader` TEXT NOT NULL, `uploadDate` TEXT, `thumbnailPath` TEXT, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailPath", + "columnName": "thumbnailPath", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "videoId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "downloadItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` TEXT NOT NULL, `videoId` TEXT NOT NULL, `fileName` TEXT NOT NULL, `path` TEXT NOT NULL, `url` TEXT, `format` TEXT, `quality` TEXT, `downloadSize` INTEGER NOT NULL, FOREIGN KEY(`videoId`) REFERENCES `download`(`videoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fileName", + "columnName": "fileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "quality", + "columnName": "quality", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadSize", + "columnName": "downloadSize", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_downloadItem_path", + "unique": true, + "columnNames": [ + "path" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_downloadItem_path` ON `${TABLE_NAME}` (`path`)" + } + ], + "foreignKeys": [ + { + "table": "download", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "videoId" + ], + "referencedColumns": [ + "videoId" + ] + } + ] + } + ], + "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, '3df3f5c01e36e4e7fd3e02ba708e5d86')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/db/AppDatabase.kt b/app/src/main/java/com/github/libretube/db/AppDatabase.kt index 9342e51b4..91326a615 100644 --- a/app/src/main/java/com/github/libretube/db/AppDatabase.kt +++ b/app/src/main/java/com/github/libretube/db/AppDatabase.kt @@ -4,6 +4,7 @@ import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase import com.github.libretube.db.dao.CustomInstanceDao +import com.github.libretube.db.dao.DownloadDao import com.github.libretube.db.dao.LocalPlaylistsDao import com.github.libretube.db.dao.LocalSubscriptionDao import com.github.libretube.db.dao.PlaylistBookmarkDao @@ -11,6 +12,8 @@ import com.github.libretube.db.dao.SearchHistoryDao import com.github.libretube.db.dao.WatchHistoryDao import com.github.libretube.db.dao.WatchPositionDao import com.github.libretube.db.obj.CustomInstance +import com.github.libretube.db.obj.Download +import com.github.libretube.db.obj.DownloadItem import com.github.libretube.db.obj.LocalPlaylist import com.github.libretube.db.obj.LocalPlaylistItem import com.github.libretube.db.obj.LocalSubscription @@ -28,12 +31,15 @@ import com.github.libretube.db.obj.WatchPosition LocalSubscription::class, PlaylistBookmark::class, LocalPlaylist::class, - LocalPlaylistItem::class + LocalPlaylistItem::class, + Download::class, + DownloadItem::class ], - version = 9, + version = 10, autoMigrations = [ AutoMigration(from = 7, to = 8), - AutoMigration(from = 8, to = 9) + AutoMigration(from = 8, to = 9), + AutoMigration(from = 9, to = 10) ] ) abstract class AppDatabase : RoomDatabase() { @@ -71,4 +77,9 @@ abstract class AppDatabase : RoomDatabase() { * Local playlists */ abstract fun localPlaylistsDao(): LocalPlaylistsDao + + /** + * Downloads + */ + abstract fun downloadDao(): DownloadDao } diff --git a/app/src/main/java/com/github/libretube/db/dao/DownloadDao.kt b/app/src/main/java/com/github/libretube/db/dao/DownloadDao.kt new file mode 100644 index 000000000..6eedbfcf6 --- /dev/null +++ b/app/src/main/java/com/github/libretube/db/dao/DownloadDao.kt @@ -0,0 +1,51 @@ +package com.github.libretube.db.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update +import com.github.libretube.db.obj.Download +import com.github.libretube.db.obj.DownloadItem +import com.github.libretube.db.obj.DownloadWithItems + +@Dao +interface DownloadDao { + @Transaction + @Query("SELECT * FROM download") + fun getAll(): List + + @Transaction + @Query("SELECT * FROM download WHERE videoId = :videoId") + fun findById(videoId: String): DownloadWithItems + + @Query("SELECT * FROM downloaditem WHERE id = :id") + fun findDownloadItemById(id: Int): DownloadItem + + @Query("SELECT * FROM downloadItem WHERE path = :path") + fun findDownloadItemByFilePath(path: String): DownloadItem + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertDownload(download: Download) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertDownloadItem(downloadItem: DownloadItem): Long + + @Update(onConflict = OnConflictStrategy.REPLACE) + fun updateDownload(download: Download) + + @Update(onConflict = OnConflictStrategy.REPLACE) + fun updateDownloadItem(downloadItem: DownloadItem) + + @Transaction + @Delete + fun deleteDownload(download: Download) + + @Delete + fun deleteDownloadItem(downloadItem: DownloadItem) + + @Query("DELETE FROM downloadItem WHERE videoId = :videoId") + fun deleteDownloadItemsByVideoId(videoId: String) +} diff --git a/app/src/main/java/com/github/libretube/db/obj/Download.kt b/app/src/main/java/com/github/libretube/db/obj/Download.kt new file mode 100644 index 000000000..2ec979ddd --- /dev/null +++ b/app/src/main/java/com/github/libretube/db/obj/Download.kt @@ -0,0 +1,15 @@ +package com.github.libretube.db.obj + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "download") +data class Download( + @PrimaryKey(autoGenerate = false) + val videoId: String, + val title: String = "", + val description: String = "", + val uploader: String = "", + val uploadDate: String? = null, + val thumbnailPath: String? = null +) diff --git a/app/src/main/java/com/github/libretube/db/obj/DownloadItem.kt b/app/src/main/java/com/github/libretube/db/obj/DownloadItem.kt new file mode 100644 index 000000000..aadc27e1b --- /dev/null +++ b/app/src/main/java/com/github/libretube/db/obj/DownloadItem.kt @@ -0,0 +1,32 @@ +package com.github.libretube.db.obj + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import com.github.libretube.enums.FileType + +@Entity( + tableName = "downloadItem", + indices = [Index(value = ["path"], unique = true)], + foreignKeys = [ + ForeignKey( + entity = Download::class, + parentColumns = ["videoId"], + childColumns = ["videoId"], + onDelete = ForeignKey.CASCADE + ) + ] +) +data class DownloadItem( + @PrimaryKey(autoGenerate = true) + var id: Int = 0, + val type: FileType, + val videoId: String, + val fileName: String, + var path: String, + var url: String? = null, + var format: String? = null, + var quality: String? = null, + var downloadSize: Long = -1L +) diff --git a/app/src/main/java/com/github/libretube/db/obj/DownloadWithItems.kt b/app/src/main/java/com/github/libretube/db/obj/DownloadWithItems.kt new file mode 100644 index 000000000..1357a8dec --- /dev/null +++ b/app/src/main/java/com/github/libretube/db/obj/DownloadWithItems.kt @@ -0,0 +1,13 @@ +package com.github.libretube.db.obj + +import androidx.room.Embedded +import androidx.room.Relation + +data class DownloadWithItems( + @Embedded val download: Download, + @Relation( + parentColumn = "videoId", + entityColumn = "videoId" + ) + val downloadItems: List +) diff --git a/app/src/main/java/com/github/libretube/enums/FileType.kt b/app/src/main/java/com/github/libretube/enums/FileType.kt new file mode 100644 index 000000000..e81da8aa7 --- /dev/null +++ b/app/src/main/java/com/github/libretube/enums/FileType.kt @@ -0,0 +1,7 @@ +package com.github.libretube.enums + +enum class FileType { + AUDIO, + VIDEO, + SUBTITLE +} diff --git a/app/src/main/java/com/github/libretube/extensions/Normalize.kt b/app/src/main/java/com/github/libretube/extensions/Normalize.kt index 0550a62c0..650e35a5d 100644 --- a/app/src/main/java/com/github/libretube/extensions/Normalize.kt +++ b/app/src/main/java/com/github/libretube/extensions/Normalize.kt @@ -13,3 +13,10 @@ fun Float.normalize(oldMin: Float, oldMax: Float, newMin: Float, newMax: Float): return (this - oldMin) * newRange / oldRange + newMin } + +fun Long.normalize(oldMin: Long, oldMax: Long, newMin: Long, newMax: Long): Long { + val oldRange = oldMax - oldMin + val newRange = newMax - newMin + + return (this - oldMin) * newRange / oldRange + newMin +}