From a06c02e8d731fed04f13f071b55dd4e268d1cc62 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Mon, 6 Feb 2023 16:45:44 +0530 Subject: [PATCH 1/7] Add search history item --- .../models/entity/SearchHistoryItem.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/entity/SearchHistoryItem.kt diff --git a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/entity/SearchHistoryItem.kt b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/entity/SearchHistoryItem.kt new file mode 100644 index 00000000000..a831416a0a9 --- /dev/null +++ b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/entity/SearchHistoryItem.kt @@ -0,0 +1,44 @@ +package au.com.shiftyjelly.pocketcasts.models.entity + +import androidx.room.ColumnInfo +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import java.util.Date + +@Entity( + tableName = "search_history", + indices = [ + Index(value = ["term"], unique = true), + Index(value = ["podcast_uuid"], unique = true), + Index(value = ["folder_uuid"], unique = true), + Index(value = ["episode_uuid"], unique = true), + ] +) +data class SearchHistoryItem( + @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") var id: Long? = null, + @ColumnInfo(name = "modified") var modified: Long = System.currentTimeMillis(), + @ColumnInfo(name = "term") var term: String? = null, + @Embedded(prefix = "podcast_") var podcast: Podcast? = null, + @Embedded(prefix = "folder_") var folder: Folder? = null, + @Embedded(prefix = "episode_") var episode: Episode? = null, +) { + data class Podcast( + val uuid: String, + val title: String, + val author: String, + ) + data class Folder( + val uuid: String, + val title: String, + val color: Int, + val podcastIds: String, + ) + data class Episode( + val uuid: String, + val title: String, + val publishedDate: Date, + val duration: Double + ) +} From f2fb6050de0958c98a2a4d87b14fb92b2beb6e85 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Mon, 6 Feb 2023 17:02:09 +0530 Subject: [PATCH 2/7] Add dao --- .../74.json | 1394 +++++++++++++++++ .../pocketcasts/models/db/AppDatabase.kt | 6 +- .../models/db/dao/SearchHistoryDao.kt | 25 + 3 files changed, 1424 insertions(+), 1 deletion(-) create mode 100644 modules/services/model/schemas/au.com.shiftyjelly.pocketcasts.models.db.AppDatabase/74.json create mode 100644 modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt diff --git a/modules/services/model/schemas/au.com.shiftyjelly.pocketcasts.models.db.AppDatabase/74.json b/modules/services/model/schemas/au.com.shiftyjelly.pocketcasts.models.db.AppDatabase/74.json new file mode 100644 index 00000000000..feb7a12a885 --- /dev/null +++ b/modules/services/model/schemas/au.com.shiftyjelly.pocketcasts.models.db.AppDatabase/74.json @@ -0,0 +1,1394 @@ +{ + "formatVersion": 1, + "database": { + "version": 74, + "identityHash": "aafaa8a64ee28bafd44a4e2e3bace027", + "entities": [ + { + "tableName": "bump_stats", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `event_time` INTEGER NOT NULL, `custom_event_props` TEXT NOT NULL, PRIMARY KEY(`name`, `event_time`, `custom_event_props`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "eventTime", + "columnName": "event_time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "customEventProps", + "columnName": "custom_event_props", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "name", + "event_time", + "custom_event_props" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "episodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `episode_description` TEXT NOT NULL, `published_date` INTEGER NOT NULL, `title` TEXT NOT NULL, `size_in_bytes` INTEGER NOT NULL, `episode_status` INTEGER NOT NULL, `file_type` TEXT, `duration` REAL NOT NULL, `download_url` TEXT, `downloaded_file_path` TEXT, `downloaded_error_details` TEXT, `play_error_details` TEXT, `played_up_to` REAL NOT NULL, `playing_status` INTEGER NOT NULL, `podcast_id` TEXT NOT NULL, `added_date` INTEGER NOT NULL, `auto_download_status` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail_status` INTEGER NOT NULL, `last_download_attempt_date` INTEGER, `playing_status_modified` INTEGER, `played_up_to_modified` INTEGER, `duration_modified` INTEGER, `starred_modified` INTEGER, `archived` INTEGER NOT NULL, `archived_modified` INTEGER, `season` INTEGER, `number` INTEGER, `type` TEXT, `cleanTitle` TEXT, `last_playback_interaction_date` INTEGER, `last_playback_interaction_sync_status` INTEGER NOT NULL, `exclude_from_episode_limit` INTEGER NOT NULL, `download_task_id` TEXT, `last_archive_interaction_date` INTEGER, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "episodeDescription", + "columnName": "episode_description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "publishedDate", + "columnName": "published_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sizeInBytes", + "columnName": "size_in_bytes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "episodeStatus", + "columnName": "episode_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fileType", + "columnName": "file_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "downloadUrl", + "columnName": "download_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadedFilePath", + "columnName": "downloaded_file_path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadErrorDetails", + "columnName": "downloaded_error_details", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playErrorDetails", + "columnName": "play_error_details", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playedUpTo", + "columnName": "played_up_to", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "playingStatus", + "columnName": "playing_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "podcastUuid", + "columnName": "podcast_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "added_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoDownloadStatus", + "columnName": "auto_download_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isStarred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thumbnailStatus", + "columnName": "thumbnail_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastDownloadAttemptDate", + "columnName": "last_download_attempt_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "playingStatusModified", + "columnName": "playing_status_modified", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "playedUpToModified", + "columnName": "played_up_to_modified", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "durationModified", + "columnName": "duration_modified", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "starredModified", + "columnName": "starred_modified", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isArchived", + "columnName": "archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "archivedModified", + "columnName": "archived_modified", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "season", + "columnName": "season", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cleanTitle", + "columnName": "cleanTitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastPlaybackInteraction", + "columnName": "last_playback_interaction_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastPlaybackInteractionSyncStatus", + "columnName": "last_playback_interaction_sync_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excludeFromEpisodeLimit", + "columnName": "exclude_from_episode_limit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadTaskId", + "columnName": "download_task_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastArchiveInteraction", + "columnName": "last_archive_interaction_date", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uuid" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "episode_last_download_attempt_date", + "unique": false, + "columnNames": [ + "last_download_attempt_date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `episode_last_download_attempt_date` ON `${TABLE_NAME}` (`last_download_attempt_date`)" + }, + { + "name": "episode_podcast_id", + "unique": false, + "columnNames": [ + "podcast_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `episode_podcast_id` ON `${TABLE_NAME}` (`podcast_id`)" + }, + { + "name": "episode_published_date", + "unique": false, + "columnNames": [ + "published_date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `episode_published_date` ON `${TABLE_NAME}` (`published_date`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "folders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL, `added_date` INTEGER NOT NULL, `sort_position` INTEGER NOT NULL, `podcasts_sort_type` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `sync_modified` INTEGER NOT NULL, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "added_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortPosition", + "columnName": "sort_position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "podcastsSortType", + "columnName": "podcasts_sort_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncModified", + "columnName": "sync_modified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "uuid" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "filters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `uuid` TEXT NOT NULL, `title` TEXT NOT NULL, `sortPosition` INTEGER, `manual` INTEGER NOT NULL, `unplayed` INTEGER NOT NULL, `partiallyPlayed` INTEGER NOT NULL, `finished` INTEGER NOT NULL, `audioVideo` INTEGER NOT NULL, `allPodcasts` INTEGER NOT NULL, `podcastUuids` TEXT, `downloaded` INTEGER NOT NULL, `downloading` INTEGER NOT NULL, `notDownloaded` INTEGER NOT NULL, `autoDownload` INTEGER NOT NULL, `autoDownloadWifiOnly` INTEGER NOT NULL, `autoDownloadPowerOnly` INTEGER NOT NULL, `sortId` INTEGER NOT NULL, `iconId` INTEGER NOT NULL, `filterHours` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `syncStatus` INTEGER NOT NULL, `autoDownloadLimit` INTEGER NOT NULL, `filterDuration` INTEGER NOT NULL, `longerThan` INTEGER NOT NULL, `shorterThan` INTEGER NOT NULL, `draft` INTEGER NOT NULL, PRIMARY KEY(`_id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sortPosition", + "columnName": "sortPosition", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "manual", + "columnName": "manual", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unplayed", + "columnName": "unplayed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "partiallyPlayed", + "columnName": "partiallyPlayed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finished", + "columnName": "finished", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "audioVideo", + "columnName": "audioVideo", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "allPodcasts", + "columnName": "allPodcasts", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "podcastUuids", + "columnName": "podcastUuids", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloaded", + "columnName": "downloaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloading", + "columnName": "downloading", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notDownloaded", + "columnName": "notDownloaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoDownload", + "columnName": "autoDownload", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoDownloadUnmeteredOnly", + "columnName": "autoDownloadWifiOnly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoDownloadPowerOnly", + "columnName": "autoDownloadPowerOnly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortId", + "columnName": "sortId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "iconId", + "columnName": "iconId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "filterHours", + "columnName": "filterHours", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "syncStatus", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autodownloadLimit", + "columnName": "autoDownloadLimit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "filterDuration", + "columnName": "filterDuration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "longerThan", + "columnName": "longerThan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shorterThan", + "columnName": "shorterThan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "draft", + "columnName": "draft", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "filters_uuid", + "unique": false, + "columnNames": [ + "uuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `filters_uuid` ON `${TABLE_NAME}` (`uuid`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "filter_episodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `playlistId` INTEGER NOT NULL, `episodeUuid` TEXT NOT NULL, `position` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "episodeUuid", + "columnName": "episodeUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "filter_episodes_playlist_id", + "unique": false, + "columnNames": [ + "playlistId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `filter_episodes_playlist_id` ON `${TABLE_NAME}` (`playlistId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "podcasts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `added_date` INTEGER, `thumbnail_url` TEXT, `title` TEXT NOT NULL, `podcast_url` TEXT, `podcast_description` TEXT NOT NULL, `podcast_category` TEXT NOT NULL, `podcast_language` TEXT NOT NULL, `media_type` TEXT, `latest_episode_uuid` TEXT, `author` TEXT NOT NULL, `sort_order` INTEGER NOT NULL, `episodes_sort_order` INTEGER NOT NULL, `latest_episode_date` INTEGER, `episodes_to_keep` INTEGER NOT NULL, `override_global_settings` INTEGER NOT NULL, `override_global_effects` INTEGER NOT NULL, `start_from` INTEGER NOT NULL, `playback_speed` REAL NOT NULL, `silence_removed` INTEGER NOT NULL, `volume_boosted` INTEGER NOT NULL, `is_folder` INTEGER NOT NULL, `subscribed` INTEGER NOT NULL, `show_notifications` INTEGER NOT NULL, `auto_download_status` INTEGER NOT NULL, `auto_add_to_up_next` INTEGER NOT NULL, `most_popular_color` INTEGER NOT NULL, `primary_color` INTEGER NOT NULL, `secondary_color` INTEGER NOT NULL, `light_overlay_color` INTEGER NOT NULL, `fab_for_light_bg` INTEGER NOT NULL, `link_for_dark_bg` INTEGER NOT NULL, `link_for_light_bg` INTEGER NOT NULL, `color_version` INTEGER NOT NULL, `color_last_downloaded` INTEGER NOT NULL, `sync_status` INTEGER NOT NULL, `exclude_from_auto_archive` INTEGER NOT NULL, `override_global_archive` INTEGER NOT NULL, `auto_archive_played_after` INTEGER NOT NULL, `auto_archive_inactive_after` INTEGER NOT NULL, `auto_archive_episode_limit` INTEGER, `estimated_next_episode` INTEGER, `episode_frequency` TEXT, `grouping` INTEGER NOT NULL, `skip_last` INTEGER NOT NULL, `show_archived` INTEGER NOT NULL, `trim_silence_level` INTEGER NOT NULL, `refresh_available` INTEGER NOT NULL, `folder_uuid` TEXT, `licensing` INTEGER NOT NULL, `isPaid` INTEGER NOT NULL, `bundleuuid` TEXT, `bundlebundleUrl` TEXT, `bundlepaymentUrl` TEXT, `bundledescription` TEXT, `bundlepodcastUuid` TEXT, `bundlepaidType` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "added_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnail_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "podcastUrl", + "columnName": "podcast_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "podcastDescription", + "columnName": "podcast_description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "podcastCategory", + "columnName": "podcast_category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "podcastLanguage", + "columnName": "podcast_language", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mediaType", + "columnName": "media_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "latestEpisodeUuid", + "columnName": "latest_episode_uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sortPosition", + "columnName": "sort_order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "episodesSortType", + "columnName": "episodes_sort_order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latestEpisodeDate", + "columnName": "latest_episode_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "episodesToKeep", + "columnName": "episodes_to_keep", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "overrideGlobalSettings", + "columnName": "override_global_settings", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "overrideGlobalEffects", + "columnName": "override_global_effects", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startFromSecs", + "columnName": "start_from", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playbackSpeed", + "columnName": "playback_speed", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "isSilenceRemoved", + "columnName": "silence_removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isVolumeBoosted", + "columnName": "volume_boosted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFolder", + "columnName": "is_folder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSubscribed", + "columnName": "subscribed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isShowNotifications", + "columnName": "show_notifications", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoDownloadStatus", + "columnName": "auto_download_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoAddToUpNext", + "columnName": "auto_add_to_up_next", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "backgroundColor", + "columnName": "most_popular_color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tintColorForLightBg", + "columnName": "primary_color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tintColorForDarkBg", + "columnName": "secondary_color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fabColorForDarkBg", + "columnName": "light_overlay_color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fabColorForLightBg", + "columnName": "fab_for_light_bg", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "linkColorForLightBg", + "columnName": "link_for_dark_bg", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "linkColorForDarkBg", + "columnName": "link_for_light_bg", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "colorVersion", + "columnName": "color_version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "colorLastDownloaded", + "columnName": "color_last_downloaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncStatus", + "columnName": "sync_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excludeFromAutoArchive", + "columnName": "exclude_from_auto_archive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "overrideGlobalArchive", + "columnName": "override_global_archive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoArchiveAfterPlaying", + "columnName": "auto_archive_played_after", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoArchiveInactive", + "columnName": "auto_archive_inactive_after", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoArchiveEpisodeLimit", + "columnName": "auto_archive_episode_limit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "estimatedNextEpisode", + "columnName": "estimated_next_episode", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "episodeFrequency", + "columnName": "episode_frequency", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "grouping", + "columnName": "grouping", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "skipLastSecs", + "columnName": "skip_last", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showArchived", + "columnName": "show_archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trimMode", + "columnName": "trim_silence_level", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "refreshAvailable", + "columnName": "refresh_available", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderUuid", + "columnName": "folder_uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "licensing", + "columnName": "licensing", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPaid", + "columnName": "isPaid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "singleBundle.uuid", + "columnName": "bundleuuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "singleBundle.bundleUrl", + "columnName": "bundlebundleUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "singleBundle.paymentUrl", + "columnName": "bundlepaymentUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "singleBundle.description", + "columnName": "bundledescription", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "singleBundle.podcastUuid", + "columnName": "bundlepodcastUuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "singleBundle.paidType", + "columnName": "bundlepaidType", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uuid" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "search_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `modified` INTEGER NOT NULL, `term` TEXT, `podcast_uuid` TEXT, `podcast_title` TEXT, `podcast_author` TEXT, `folder_uuid` TEXT, `folder_title` TEXT, `folder_color` INTEGER, `folder_podcastIds` TEXT, `episode_uuid` TEXT, `episode_title` TEXT, `episode_publishedDate` INTEGER, `episode_duration` REAL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "term", + "columnName": "term", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "podcast.uuid", + "columnName": "podcast_uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "podcast.title", + "columnName": "podcast_title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "podcast.author", + "columnName": "podcast_author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "folder.uuid", + "columnName": "folder_uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "folder.title", + "columnName": "folder_title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "folder.color", + "columnName": "folder_color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folder.podcastIds", + "columnName": "folder_podcastIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "episode.uuid", + "columnName": "episode_uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "episode.title", + "columnName": "episode_title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "episode.publishedDate", + "columnName": "episode_publishedDate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "episode.duration", + "columnName": "episode_duration", + "affinity": "REAL", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_search_history_term", + "unique": true, + "columnNames": [ + "term" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_term` ON `${TABLE_NAME}` (`term`)" + }, + { + "name": "index_search_history_podcast_uuid", + "unique": true, + "columnNames": [ + "podcast_uuid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_podcast_uuid` ON `${TABLE_NAME}` (`podcast_uuid`)" + }, + { + "name": "index_search_history_folder_uuid", + "unique": true, + "columnNames": [ + "folder_uuid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_folder_uuid` ON `${TABLE_NAME}` (`folder_uuid`)" + }, + { + "name": "index_search_history_episode_uuid", + "unique": true, + "columnNames": [ + "episode_uuid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_episode_uuid` ON `${TABLE_NAME}` (`episode_uuid`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "up_next_changes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` INTEGER NOT NULL, `uuid` TEXT, `uuids` TEXT, `modified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uuids", + "columnName": "uuids", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "up_next_episodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `episodeUuid` TEXT NOT NULL, `position` INTEGER NOT NULL, `playlistId` INTEGER, `title` TEXT NOT NULL, `publishedDate` INTEGER, `downloadUrl` TEXT, `podcastUuid` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "episodeUuid", + "columnName": "episodeUuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "publishedDate", + "columnName": "publishedDate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "downloadUrl", + "columnName": "downloadUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "podcastUuid", + "columnName": "podcastUuid", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "user_episodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `published_date` INTEGER NOT NULL, `episode_description` TEXT NOT NULL, `title` TEXT NOT NULL, `size_in_bytes` INTEGER NOT NULL, `episode_status` INTEGER NOT NULL, `file_type` TEXT, `duration` REAL NOT NULL, `download_url` TEXT, `played_up_to` REAL NOT NULL, `playing_status` INTEGER NOT NULL, `added_date` INTEGER NOT NULL, `auto_download_status` INTEGER NOT NULL, `last_download_attempt_date` INTEGER, `archived` INTEGER NOT NULL, `download_task_id` TEXT, `downloaded_file_path` TEXT, `playing_status_modified` INTEGER, `played_up_to_modified` INTEGER, `artwork_url` TEXT, `play_error_details` TEXT, `server_status` INTEGER NOT NULL, `upload_error_details` TEXT, `downloaded_error_details` TEXT, `tint_color_index` INTEGER NOT NULL, `has_custom_image` INTEGER NOT NULL, `upload_task_id` TEXT, PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "publishedDate", + "columnName": "published_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "episodeDescription", + "columnName": "episode_description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sizeInBytes", + "columnName": "size_in_bytes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "episodeStatus", + "columnName": "episode_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fileType", + "columnName": "file_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "downloadUrl", + "columnName": "download_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playedUpTo", + "columnName": "played_up_to", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "playingStatus", + "columnName": "playing_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addedDate", + "columnName": "added_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoDownloadStatus", + "columnName": "auto_download_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastDownloadAttemptDate", + "columnName": "last_download_attempt_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isArchived", + "columnName": "archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadTaskId", + "columnName": "download_task_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadedFilePath", + "columnName": "downloaded_file_path", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playingStatusModified", + "columnName": "playing_status_modified", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "playedUpToModified", + "columnName": "played_up_to_modified", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "artworkUrl", + "columnName": "artwork_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playErrorDetails", + "columnName": "play_error_details", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "serverStatus", + "columnName": "server_status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uploadErrorDetails", + "columnName": "upload_error_details", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadErrorDetails", + "columnName": "downloaded_error_details", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tintColorIndex", + "columnName": "tint_color_index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasCustomImage", + "columnName": "has_custom_image", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uploadTaskId", + "columnName": "upload_task_id", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uuid" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "user_episode_last_download_attempt_date", + "unique": false, + "columnNames": [ + "last_download_attempt_date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `user_episode_last_download_attempt_date` ON `${TABLE_NAME}` (`last_download_attempt_date`)" + }, + { + "name": "user_episode_published_date", + "unique": false, + "columnNames": [ + "published_date" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `user_episode_published_date` ON `${TABLE_NAME}` (`published_date`)" + } + ], + "foreignKeys": [] + } + ], + "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, 'aafaa8a64ee28bafd44a4e2e3bace027')" + ] + } +} \ No newline at end of file diff --git a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt index 88d73e2e116..4930d52a994 100644 --- a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt +++ b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt @@ -24,6 +24,7 @@ import au.com.shiftyjelly.pocketcasts.models.db.dao.EpisodeDao import au.com.shiftyjelly.pocketcasts.models.db.dao.FolderDao import au.com.shiftyjelly.pocketcasts.models.db.dao.PlaylistDao import au.com.shiftyjelly.pocketcasts.models.db.dao.PodcastDao +import au.com.shiftyjelly.pocketcasts.models.db.dao.SearchHistoryDao import au.com.shiftyjelly.pocketcasts.models.db.dao.UpNextChangeDao import au.com.shiftyjelly.pocketcasts.models.db.dao.UpNextDao import au.com.shiftyjelly.pocketcasts.models.db.dao.UserEpisodeDao @@ -33,6 +34,7 @@ import au.com.shiftyjelly.pocketcasts.models.entity.Folder import au.com.shiftyjelly.pocketcasts.models.entity.Playlist import au.com.shiftyjelly.pocketcasts.models.entity.PlaylistEpisode import au.com.shiftyjelly.pocketcasts.models.entity.Podcast +import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem import au.com.shiftyjelly.pocketcasts.models.entity.UpNextChange import au.com.shiftyjelly.pocketcasts.models.entity.UpNextEpisode import au.com.shiftyjelly.pocketcasts.models.entity.UserEpisode @@ -47,11 +49,12 @@ import au.com.shiftyjelly.pocketcasts.localization.R as LR Playlist::class, PlaylistEpisode::class, Podcast::class, + SearchHistoryItem::class, UpNextChange::class, UpNextEpisode::class, UserEpisode::class, ], - version = 73, + version = 74, exportSchema = true ) @TypeConverters( @@ -75,6 +78,7 @@ abstract class AppDatabase : RoomDatabase() { abstract fun userEpisodeDao(): UserEpisodeDao abstract fun folderDao(): FolderDao abstract fun bumpStatsDao(): BumpStatsDao + abstract fun searchHistoryDao(): SearchHistoryDao companion object { // This seems dodgy but I got it from Google, https://github.com/googlesamples/android-sunflower/blob/master/app/src/main/java/com/google/samples/apps/sunflower/data/AppDatabase.kt diff --git a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt new file mode 100644 index 00000000000..86150d4172f --- /dev/null +++ b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt @@ -0,0 +1,25 @@ +package au.com.shiftyjelly.pocketcasts.models.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 au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem + +@Dao +abstract class SearchHistoryDao { + @Transaction + @Query("SELECT * FROM search_history ORDER BY modified DESC") + abstract fun findAll(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract suspend fun insert(searchHistoryItem: SearchHistoryItem) + + @Delete + abstract fun delete(result: SearchHistoryItem) + + @Query("DELETE FROM search_history") + abstract suspend fun deleteAll() +} From 429155ee636fdb11b7f88b61a0c6c7cc9f0cf6af Mon Sep 17 00:00:00 2001 From: ashiagr Date: Mon, 6 Feb 2023 17:04:52 +0530 Subject: [PATCH 3/7] Add dao tests --- .../models/db/SearchHistoryDaoTest.kt | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/SearchHistoryDaoTest.kt diff --git a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/SearchHistoryDaoTest.kt b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/SearchHistoryDaoTest.kt new file mode 100644 index 00000000000..40421bcadac --- /dev/null +++ b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/SearchHistoryDaoTest.kt @@ -0,0 +1,274 @@ +package au.com.shiftyjelly.pocketcasts.models.db + +import androidx.room.Room +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import au.com.shiftyjelly.pocketcasts.models.db.dao.SearchHistoryDao +import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem +import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem.Folder +import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem.Podcast +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.util.Date +import java.util.UUID + +private const val SEARCH_TERM_TEST1 = "test1" +private const val SEARCH_TERM_TEST2 = "test2" + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class SearchHistoryDaoTest { + lateinit var searchHistoryDao: SearchHistoryDao + lateinit var testDb: AppDatabase + + @Before + fun setupDb() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build() + searchHistoryDao = testDb.searchHistoryDao() + } + + @After + fun closeDb() { + testDb.close() + } + + /* INSERT */ + @Test + fun testInsertSearchTerm() = runTest { + searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1)) + + assertTrue(searchHistoryDao.findAll().first().term == SEARCH_TERM_TEST1) + } + + @Test + fun testInsertPodcastSearchHistory() { + val uuid = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid)) + + assertTrue(searchHistoryDao.findAll().first().podcast?.uuid == uuid) + } + } + + @Test + fun testInsertFolderSearchHistory() { + val uuid = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createFolderSearchHistoryItem(uuid)) + + assertTrue(searchHistoryDao.findAll().first().folder?.uuid == uuid) + } + } + + @Test + fun testInsertEpisodeSearchHistory() { + val uuid = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid)) + + assertTrue(searchHistoryDao.findAll().first().episode?.uuid == uuid) + } + } + + /* MULTIPLE INSERT OR REPLACE */ + @Test + fun testMultipleInsertSameSearchTerms() { + runTest { + searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1)) + val modifiedPrevious = searchHistoryDao.findAll().first().modified + searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1)) + + val result = searchHistoryDao.findAll() + assertEquals("Insert should replace, count should be 1", 1, result.size) + assertEquals( + "Replaced search term should be on top", + true, + result.first().modified > modifiedPrevious + ) + } + } + + @Test + fun testMultipleInsertUniqueSearchTerms() { + runTest { + searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1)) + searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST2)) + + val result = searchHistoryDao.findAll() + assertEquals("Unique search terms should be inserted, count should be 2", 2, result.size) + assertEquals( + "Last search term inserted should be on top", + SEARCH_TERM_TEST2, + result.first().term + ) + } + } + + @Test + fun testMultipleInsertSamePodcastSearchHistory() { + val uuid = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid = uuid)) + val modifiedPrevious = searchHistoryDao.findAll().first().modified + searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid = uuid)) + + val result = searchHistoryDao.findAll() + assertEquals("Same podcast search insert should replace, count should be 1", 1, result.size) + assertEquals( + "Replaced podcast search history item should be on top", + true, + result.first().modified > modifiedPrevious + ) + } + } + + @Test + fun testMultipleInsertUniquePodcastSearchHistory() { + val uuid1 = UUID.randomUUID().toString() + val uuid2 = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid1)) + searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid2)) + + val result = searchHistoryDao.findAll() + assertEquals("Unique podcast search history should be inserted, count should be 2", 2, result.size) + assertEquals( + "Last podcast search history inserted should be on top", + uuid2, + result.first().podcast?.uuid + ) + } + } + + @Test + fun testMultipleInsertSameFolderSearchHistory() { + val uuid = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createFolderSearchHistoryItem(uuid)) + val modifiedPrevious = searchHistoryDao.findAll().first().modified + searchHistoryDao.insert(createFolderSearchHistoryItem(uuid)) + + val result = searchHistoryDao.findAll() + assertEquals("Same folder search insert should replace, count should be 1", 1, result.size) + assertEquals( + "Replaced folder search should be on top", + true, + result.first().modified > modifiedPrevious + ) + } + } + + @Test + fun testMultipleInsertUniqueFolderSearchHistory() { + val uuid1 = UUID.randomUUID().toString() + val uuid2 = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createFolderSearchHistoryItem(uuid1)) + searchHistoryDao.insert(createFolderSearchHistoryItem(uuid2)) + + val result = searchHistoryDao.findAll() + assertEquals("Unique folder search history should be inserted, count should be 2", 2, result.size) + assertEquals( + "Last folder search history inserted should be on top", + uuid2, + result.first().folder?.uuid + ) + } + } + + @Test + fun testMultipleInsertSameEpisodeSearchHistory() { + val uuid = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid)) + val modifiedPrevious = searchHistoryDao.findAll().first().modified + searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid)) + + val result = searchHistoryDao.findAll() + assertEquals("Same episode insert should replace, count should be 1", 1, result.size) + assertEquals( + "Replaced episode search should be on top", + true, + result.first().modified > modifiedPrevious + ) + } + } + + @Test + fun testMultipleInsertUniqueEpisodeSearchHistory() { + val uuid1 = UUID.randomUUID().toString() + val uuid2 = UUID.randomUUID().toString() + runTest { + searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid1)) + searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid2)) + + val result = searchHistoryDao.findAll() + assertEquals("Unique episode search history should be inserted, count should be 2", 2, result.size) + assertEquals( + "Last episode search history inserted should be on top", + uuid2, + result.first().episode?.uuid + ) + } + } + + /* DELETE */ + @Test + fun testDeleteSearchHistoryItem() { + runTest { + searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1)) + + searchHistoryDao.delete(searchHistoryDao.findAll().first()) + + assertTrue(searchHistoryDao.findAll().isEmpty()) + } + } + + @Test + fun testDeleteAllSearchHistory() { + runTest { + val uuid = UUID.randomUUID().toString() + searchHistoryDao.insert(createTermSearchHistoryItem(SEARCH_TERM_TEST1)) + searchHistoryDao.insert(createPodcastSearchHistoryItem(uuid)) + searchHistoryDao.insert(createFolderSearchHistoryItem(uuid)) + searchHistoryDao.insert(createEpisodeSearchHistoryItem(uuid)) + + searchHistoryDao.deleteAll() + + assertTrue(searchHistoryDao.findAll().isEmpty()) + } + } + + /* HELPER FUNCTIONS */ + private fun createTermSearchHistoryItem(term: String) = + SearchHistoryItem(term = term) + + private fun createPodcastSearchHistoryItem(uuid: String) = + SearchHistoryItem( + podcast = Podcast( + uuid = uuid, + title = "", + author = "", + ) + ) + + private fun createFolderSearchHistoryItem(uuid: String) = + SearchHistoryItem(folder = Folder(uuid = uuid, title = "", color = 0, podcastIds = "")) + + private fun createEpisodeSearchHistoryItem(uuid: String) = + SearchHistoryItem( + episode = SearchHistoryItem.Episode( + uuid = uuid, + title = "", + publishedDate = Date(), + duration = 0.0, + ) + ) +} From 8a37a8a732a7792154db5f3ec025aaaec00c514c Mon Sep 17 00:00:00 2001 From: ashiagr Date: Mon, 6 Feb 2023 17:04:41 +0530 Subject: [PATCH 4/7] Add migration --- .../pocketcasts/models/db/AppDatabaseTest.kt | 3 +- .../pocketcasts/models/db/AppDatabase.kt | 30 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabaseTest.kt b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabaseTest.kt index 42f1ec30752..73278924ee6 100644 --- a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabaseTest.kt +++ b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabaseTest.kt @@ -137,7 +137,8 @@ class AppDatabaseTest { AppDatabase.MIGRATION_69_70, AppDatabase.MIGRATION_70_71, AppDatabase.MIGRATION_71_72, - AppDatabase.MIGRATION_72_73 + AppDatabase.MIGRATION_72_73, + AppDatabase.MIGRATION_73_74 ) .build() // close the database and release any stream resources when the test finishes diff --git a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt index 4930d52a994..821d9024d37 100644 --- a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt +++ b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt @@ -378,6 +378,33 @@ abstract class AppDatabase : RoomDatabase() { ) } + val MIGRATION_73_74 = addMigration(73, 74) { database -> + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS search_history ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + modified INTEGER NOT NULL, + term TEXT, + podcast_uuid TEXT, + podcast_title TEXT, + podcast_author TEXT, + folder_uuid TEXT, + folder_title TEXT, + folder_color INTEGER, + folder_podcastIds TEXT, + episode_uuid TEXT, + episode_title TEXT, + episode_publishedDate INTEGER, + episode_duration REAL + ); + """.trimIndent() + ) + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_term` ON search_history (`term`)") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_podcast_uuid` ON search_history (`podcast_uuid`);") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_folder_uuid` ON search_history (`folder_uuid`)") + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_episode_uuid` ON search_history (`episode_uuid`)") + } + fun addMigrations(databaseBuilder: Builder, context: Context) { databaseBuilder.addMigrations( addMigration(1, 2) { }, @@ -740,7 +767,8 @@ abstract class AppDatabase : RoomDatabase() { MIGRATION_69_70, MIGRATION_70_71, MIGRATION_71_72, - MIGRATION_72_73 + MIGRATION_72_73, + MIGRATION_73_74 ) } From 6f50ad90dc00a8a285821ca18bf13237c01b46b4 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Mon, 6 Feb 2023 18:32:35 +0530 Subject: [PATCH 5/7] Add limit --- .../pocketcasts/models/db/dao/SearchHistoryDao.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt index 86150d4172f..fdde79a5bff 100644 --- a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt +++ b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt @@ -5,14 +5,12 @@ import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import androidx.room.Transaction import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem @Dao abstract class SearchHistoryDao { - @Transaction - @Query("SELECT * FROM search_history ORDER BY modified DESC") - abstract fun findAll(): List + @Query("SELECT * FROM search_history ORDER BY modified DESC LIMIT :limit") + abstract fun findAll(limit: Int = 10): List @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun insert(searchHistoryItem: SearchHistoryItem) From 24ea15cd01e42f234d38694ed56d2bf525b52825 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 7 Feb 2023 18:10:42 +0530 Subject: [PATCH 6/7] Add suspend --- .../shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt index fdde79a5bff..bdc2cde4190 100644 --- a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt +++ b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt @@ -10,13 +10,13 @@ import au.com.shiftyjelly.pocketcasts.models.entity.SearchHistoryItem @Dao abstract class SearchHistoryDao { @Query("SELECT * FROM search_history ORDER BY modified DESC LIMIT :limit") - abstract fun findAll(limit: Int = 10): List + abstract suspend fun findAll(limit: Int = 10): List @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun insert(searchHistoryItem: SearchHistoryItem) @Delete - abstract fun delete(result: SearchHistoryItem) + abstract suspend fun delete(result: SearchHistoryItem) @Query("DELETE FROM search_history") abstract suspend fun deleteAll() From 8f4ddd7caacc943dc7a249e23b0c62c0b5fa6f44 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 7 Feb 2023 18:12:36 +0530 Subject: [PATCH 7/7] Replace assertEquals with assertTrue --- .../pocketcasts/models/db/SearchHistoryDaoTest.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/SearchHistoryDaoTest.kt b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/SearchHistoryDaoTest.kt index 40421bcadac..5aaa6405631 100644 --- a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/SearchHistoryDaoTest.kt +++ b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/SearchHistoryDaoTest.kt @@ -87,9 +87,8 @@ class SearchHistoryDaoTest { val result = searchHistoryDao.findAll() assertEquals("Insert should replace, count should be 1", 1, result.size) - assertEquals( + assertTrue( "Replaced search term should be on top", - true, result.first().modified > modifiedPrevious ) } @@ -121,9 +120,8 @@ class SearchHistoryDaoTest { val result = searchHistoryDao.findAll() assertEquals("Same podcast search insert should replace, count should be 1", 1, result.size) - assertEquals( + assertTrue( "Replaced podcast search history item should be on top", - true, result.first().modified > modifiedPrevious ) } @@ -157,9 +155,8 @@ class SearchHistoryDaoTest { val result = searchHistoryDao.findAll() assertEquals("Same folder search insert should replace, count should be 1", 1, result.size) - assertEquals( + assertTrue( "Replaced folder search should be on top", - true, result.first().modified > modifiedPrevious ) } @@ -193,9 +190,8 @@ class SearchHistoryDaoTest { val result = searchHistoryDao.findAll() assertEquals("Same episode insert should replace, count should be 1", 1, result.size) - assertEquals( + assertTrue( "Replaced episode search should be on top", - true, result.first().modified > modifiedPrevious ) }