From 8525442df5c4124c42b0b1ba83b3f598c249deb3 Mon Sep 17 00:00:00 2001 From: y20k Date: Tue, 21 Jul 2020 16:44:00 +0200 Subject: [PATCH] do not override an existing collection with an empty one - except when last podcast is deleted --- app/build.gradle | 4 +- app/src/main/java/org/y20k/escapepod/Keys.kt | 1 + .../java/org/y20k/escapepod/PlayerService.kt | 1 - .../collection/CollectionViewModel.kt | 19 +++++--- .../org/y20k/escapepod/database/EpisodeDao.kt | 43 ++++++++++++----- .../y20k/escapepod/database/EpisodeEntity.kt | 8 ++-- .../org/y20k/escapepod/database/PodcastDao.kt | 48 ++++++++++++++----- .../y20k/escapepod/database/PodcastEntity.kt | 3 +- .../y20k/escapepod/helpers/DatabaseHelper.kt | 33 +++++++++++-- .../org/y20k/escapepod/helpers/FileHelper.kt | 33 +++++++------ .../escapepod/helpers/PreferencesHelper.kt | 16 +++++++ 11 files changed, 156 insertions(+), 53 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 476d1eba..526bc36c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,8 +63,8 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4" implementation "com.google.android.material:material:1.2.0-beta01" diff --git a/app/src/main/java/org/y20k/escapepod/Keys.kt b/app/src/main/java/org/y20k/escapepod/Keys.kt index ca866b1c..8354eed3 100644 --- a/app/src/main/java/org/y20k/escapepod/Keys.kt +++ b/app/src/main/java/org/y20k/escapepod/Keys.kt @@ -67,6 +67,7 @@ object Keys { const val PREF_ONE_TIME_HOUSEKEEPING_NECESSARY = "ONE_TIME_HOUSEKEEPING_NECESSARY_VERSIONCODE_26" // increment to current app version code to trigger housekeeping that runs only once const val PREF_THEME_SELECTION: String= "THEME_SELECTION" const val PREF_LAST_UPDATE_COLLECTION: String = "LAST_UPDATE_COLLECTION" + const val PREF_COLLECTION_SIZE: String = "COLLECTION_SIZE" const val PREF_COLLECTION_MODIFICATION_DATE: String = "COLLECTION_MODIFICATION_DATE" const val PREF_CURRENT_MEDIA_ID: String = "CURRENT_MEDIA_ID" const val PREF_CURRENT_PLAYBACK_STATE: String = "CURRENT_PLAYBACK_STATE" diff --git a/app/src/main/java/org/y20k/escapepod/PlayerService.kt b/app/src/main/java/org/y20k/escapepod/PlayerService.kt index 049e5b0c..b6844d36 100644 --- a/app/src/main/java/org/y20k/escapepod/PlayerService.kt +++ b/app/src/main/java/org/y20k/escapepod/PlayerService.kt @@ -611,7 +611,6 @@ class PlayerService(): MediaBrowserServiceCompat(), Player.EventListener, Corout } } - override fun onFastForward() { LogHelper.d(TAG, "Skipping forward") // update position diff --git a/app/src/main/java/org/y20k/escapepod/collection/CollectionViewModel.kt b/app/src/main/java/org/y20k/escapepod/collection/CollectionViewModel.kt index 847768ac..9bc80936 100644 --- a/app/src/main/java/org/y20k/escapepod/collection/CollectionViewModel.kt +++ b/app/src/main/java/org/y20k/escapepod/collection/CollectionViewModel.kt @@ -45,7 +45,7 @@ class CollectionViewModel(application: Application) : AndroidViewModel(applicati private var collectionChangedReceiver: BroadcastReceiver private val backgroundJob = Job() private val uiScope = CoroutineScope(Dispatchers.Main + backgroundJob) - //private var collectionDatabase: CollectionDatabase +// private var collectionDatabase: CollectionDatabase /* Init constructor */ @@ -56,7 +56,7 @@ class CollectionViewModel(application: Application) : AndroidViewModel(applicati collectionChangedReceiver = createCollectionChangedReceiver() LocalBroadcastManager.getInstance(application).registerReceiver(collectionChangedReceiver, IntentFilter(Keys.ACTION_COLLECTION_CHANGED)) // get instance of collection database // todo remove: just a database test - //collectionDatabase = CollectionDatabase.getInstance(application) +// collectionDatabase = CollectionDatabase.getInstance(application) } @@ -98,17 +98,24 @@ class CollectionViewModel(application: Application) : AndroidViewModel(applicati // update collection view model collectionLiveData.value = collection // Todo remove: just a database test - //insertPodcasts(collection) +// insertPodcasts(collection) } } + // todo read this :-) https://itnext.io/room-persistence-library-using-mutablelivedata-observable-to-update-the-ui-after-a-database-6836d25e8267 -///* Todo remove: just a database test */ + +/* Todo remove: just a database test */ //private fun insertPodcasts(collection: Collection) { // GlobalScope.launch { -// collectionDatabase.podcastDao().insertAll(DatabaseHelper.convertToPodcastEntityList(collection)) +// //collectionDatabase.clearAllTables() +// +// val result: Pair, List> = DatabaseHelper.convertToPodcastEntityList(collection) +// collectionDatabase.podcastDao().insertAll(result.first) +// collectionDatabase.episodeDao().insertAll(result.second) // -// LogHelper.e(TAG, "${collectionDatabase.podcastDao().findByName("Subnet").pid}") +// val numberOfEpisodes = collectionDatabase.episodeDao().getSize() +// val numberOfPodcasts = collectionDatabase.podcastDao().getSize() // } //} diff --git a/app/src/main/java/org/y20k/escapepod/database/EpisodeDao.kt b/app/src/main/java/org/y20k/escapepod/database/EpisodeDao.kt index eb6f85f0..7f5c382a 100644 --- a/app/src/main/java/org/y20k/escapepod/database/EpisodeDao.kt +++ b/app/src/main/java/org/y20k/escapepod/database/EpisodeDao.kt @@ -1,30 +1,51 @@ package org.y20k.escapepod.database +import androidx.lifecycle.LiveData import androidx.room.* @Dao interface EpisodeDao { - @Query("SELECT * FROM episode") - fun getAll(): List + @Query("SELECT COUNT(*) FROM episodes") + fun getSize(): Int - @Query("SELECT * FROM episode WHERE eid IN (:eids)") + @Query("SELECT * FROM episodes") + fun getAll(): LiveData> + + @Query("SELECT * FROM episodes WHERE eid IN (:eids)") fun loadAllByIds(eids: IntArray): List - @Query("SELECT * FROM episode WHERE episode_title LIKE :title LIMIT 1") + @Query("SELECT * FROM episodes WHERE episode_title LIKE :title LIMIT 1") fun findByTitle(title: String): EpisodeEntity - @Query("SELECT * FROM episode WHERE episode_guid IS :guid LIMIT 1") + @Query("SELECT * FROM episodes WHERE episode_guid IS :guid LIMIT 1") fun findByGuid(guid: String): EpisodeEntity - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(episode: EpisodeEntity) + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insert(episode: EpisodeEntity): Long - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAll(vararg episodes: EpisodeEntity) + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insertAll(episodes: List): List< Long> - @Update - fun add(episode: EpisodeEntity) + @Update(onConflict = OnConflictStrategy.REPLACE) + fun update(episode: EpisodeEntity) @Delete fun delete(episode: EpisodeEntity) + + @Transaction + fun upsert(episode: EpisodeEntity) { + val rowId = insert(episode) + if (rowId == -1L) { + update(episode) + } + } + + @Transaction + fun upsertAll(episodes: List) { + val rowIds = insertAll(episodes) + val episodesToUpdate = rowIds.mapIndexedNotNull { index, rowId -> + if (rowId == -1L) null else episodes[index] } + episodesToUpdate.forEach { update(it) } + } + } \ No newline at end of file diff --git a/app/src/main/java/org/y20k/escapepod/database/EpisodeEntity.kt b/app/src/main/java/org/y20k/escapepod/database/EpisodeEntity.kt index 731749ff..bfe0cc24 100644 --- a/app/src/main/java/org/y20k/escapepod/database/EpisodeEntity.kt +++ b/app/src/main/java/org/y20k/escapepod/database/EpisodeEntity.kt @@ -2,12 +2,12 @@ package org.y20k.escapepod.database import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Index import androidx.room.PrimaryKey -@Entity(tableName = "episode") +@Entity(tableName = "episodes", indices = arrayOf(Index(value = ["episode_guid"], unique = true))) data class EpisodeEntity ( - @PrimaryKey (autoGenerate = true) val eid: Long, @ColumnInfo (name = "podcast_id") val podcastId: Long, @ColumnInfo (name = "episode_guid") val guid: String, @ColumnInfo (name = "episode_title") val title: String, @@ -24,4 +24,6 @@ data class EpisodeEntity ( @ColumnInfo (name = "episode_remote_cover_file_location") val remoteCoverFileLocation: String, @ColumnInfo (name = "episode_remote_audio_file_location") val remoteAudioFileLocation: String -) \ No newline at end of file +) { + @PrimaryKey (autoGenerate = true) var eid: Long = 0L +} \ No newline at end of file diff --git a/app/src/main/java/org/y20k/escapepod/database/PodcastDao.kt b/app/src/main/java/org/y20k/escapepod/database/PodcastDao.kt index 0cc330c7..4e06bd57 100644 --- a/app/src/main/java/org/y20k/escapepod/database/PodcastDao.kt +++ b/app/src/main/java/org/y20k/escapepod/database/PodcastDao.kt @@ -1,32 +1,58 @@ package org.y20k.escapepod.database +import androidx.lifecycle.LiveData import androidx.room.* @Dao interface PodcastDao { - @Query("SELECT * FROM podcast") - fun getAll(): List + @Query("SELECT COUNT(*) FROM podcasts") + fun getSize(): Int - @Query("SELECT * FROM podcast WHERE pid IN (:pids)") + @Query("SELECT * FROM podcasts") + fun getAll(): LiveData> + + @Query("SELECT * FROM podcasts WHERE pid IN (:pids)") fun loadAllByIds(pids: IntArray): List - @Query("SELECT * FROM podcast WHERE podcast_name LIKE :name LIMIT 1") + @Query("SELECT * FROM podcasts WHERE podcast_name LIKE :name LIMIT 1") fun findByName(name: String): PodcastEntity - @Insert - fun insert(podcast: PodcastEntity) + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insert(podcast: PodcastEntity): Long - @Insert - fun insertAll(podcasts: List) + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insertAll(podcasts: List): List - @Update - fun add(podcast: PodcastEntity) + @Update(onConflict = OnConflictStrategy.REPLACE) + fun update(podcast: PodcastEntity) @Delete fun delete(user: PodcastEntity) @Transaction - @Query("SELECT * FROM podcast") + fun upsert(podcast: PodcastEntity) { + val rowId = insert(podcast) + if (rowId == -1L) { + update(podcast) + } + } + + @Transaction + fun upsertAll(podcasts: List) { + val rowIds = insertAll(podcasts) + val podcastsToUpdate = rowIds.mapIndexedNotNull { index, rowId -> + if (rowId == -1L) null else podcasts[index] } + podcastsToUpdate.forEach { update(it) } + } + + + /** + * This query will tell Room to query both the Podcast and Episode tables and handle + * the object mapping. + */ + @Transaction + //@Query("SELECT * FROM episodes WHERE podcast_id IN (SELECT DISTINCT(pid) FROM podcasts)") + @Query("SELECT * FROM podcasts") fun getPodcastsWithEpisodes(): List } \ No newline at end of file diff --git a/app/src/main/java/org/y20k/escapepod/database/PodcastEntity.kt b/app/src/main/java/org/y20k/escapepod/database/PodcastEntity.kt index 7b73ece2..bfcca598 100644 --- a/app/src/main/java/org/y20k/escapepod/database/PodcastEntity.kt +++ b/app/src/main/java/org/y20k/escapepod/database/PodcastEntity.kt @@ -2,10 +2,11 @@ package org.y20k.escapepod.database import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Index import androidx.room.PrimaryKey -@Entity(tableName = "podcast") +@Entity(tableName = "podcasts", indices = arrayOf(Index(value = ["podcast_remote_podcast_feed_location"], unique = true))) data class PodcastEntity( @ColumnInfo (name = "podcast_name") val name: String, diff --git a/app/src/main/java/org/y20k/escapepod/helpers/DatabaseHelper.kt b/app/src/main/java/org/y20k/escapepod/helpers/DatabaseHelper.kt index 924fb1b6..2b8307db 100644 --- a/app/src/main/java/org/y20k/escapepod/helpers/DatabaseHelper.kt +++ b/app/src/main/java/org/y20k/escapepod/helpers/DatabaseHelper.kt @@ -1,12 +1,14 @@ package org.y20k.escapepod.helpers import org.y20k.escapepod.core.Collection +import org.y20k.escapepod.database.EpisodeEntity import org.y20k.escapepod.database.PodcastEntity object DatabaseHelper { - fun convertToPodcastEntityList(collection: Collection): List { - val list: MutableList = mutableListOf() + fun convertToPodcastEntityList(collection: Collection): Pair, List> { + val podcastList: MutableList = mutableListOf() + val episodeList: MutableList = mutableListOf() collection.podcasts.forEach { podcast -> val podcastEntity: PodcastEntity = PodcastEntity( name = podcast.name, @@ -18,9 +20,32 @@ object DatabaseHelper { remoteImageFileLocation = podcast.remoteImageFileLocation, remotePodcastFeedLocation = podcast.remotePodcastFeedLocation ) - list.add(podcastEntity) + podcastList.add(podcastEntity) + + podcast.episodes.forEach { episode -> + val episodeEntity: EpisodeEntity = EpisodeEntity( + podcastId = podcastEntity.pid, + guid = episode.guid, + title = episode.title, + description = episode.description, + audio = episode.audio, + cover = episode.cover, + smallCover = episode.smallCover, + publicationDate = episode.publicationDate.time, + playbackState = episode.playbackState, + playbackPosition = episode.playbackPosition, + duration = episode.duration, + manuallyDownloaded = episode.manuallyDownloaded, + manuallyDeleted = episode.manuallyDeleted, + remoteCoverFileLocation = episode.remoteCoverFileLocation, + remoteAudioFileLocation = episode.remoteAudioFileLocation + ) + episodeList.add(episodeEntity) + } + + } - return list + return Pair(podcastList, episodeList) } } \ No newline at end of file diff --git a/app/src/main/java/org/y20k/escapepod/helpers/FileHelper.kt b/app/src/main/java/org/y20k/escapepod/helpers/FileHelper.kt index 9b6cac1a..6617435b 100644 --- a/app/src/main/java/org/y20k/escapepod/helpers/FileHelper.kt +++ b/app/src/main/java/org/y20k/escapepod/helpers/FileHelper.kt @@ -181,23 +181,28 @@ object FileHelper { /* Saves podcast collection as JSON text file */ fun saveCollection(context: Context, collection: Collection, lastSave: Date) { LogHelper.v(TAG, "Saving collection - Thread: ${Thread.currentThread().name}") - // convert to JSON - val gson: Gson = getCustomGson() - var json: String = String() - try { - json = gson.toJson(collection) - } catch (e: Exception) { - e.printStackTrace() - } - if (json.isNotBlank()) { - // save modification date - PreferencesHelper.saveCollectionModificationDate(context, lastSave) - // write text file - writeTextFile(context, json, Keys.FOLDER_COLLECTION, Keys.COLLECTION_FILE) + val collectionSize: Int = collection.podcasts.size + // do not override an existing collection with an empty one - except when last podcast is deleted + if (collectionSize > 0 || PreferencesHelper.loadCollectionSize(context) == 1) { + // convert to JSON + val gson: Gson = getCustomGson() + var json: String = String() + try { + json = gson.toJson(collection) + } catch (e: Exception) { + e.printStackTrace() + } + if (json.isNotBlank()) { + // save modification date + PreferencesHelper.saveCollectionModificationDate(context, lastSave) + // write text file + writeTextFile(context, json, Keys.FOLDER_COLLECTION, Keys.COLLECTION_FILE) + } + } else { + LogHelper.w(TAG, "Not saving collection. Collection is empty.") } } - /* Reads podcast collection from storage using GSON */ fun readCollection(context: Context): Collection { LogHelper.v(TAG, "Reading collection - Thread: ${Thread.currentThread().name}") diff --git a/app/src/main/java/org/y20k/escapepod/helpers/PreferencesHelper.kt b/app/src/main/java/org/y20k/escapepod/helpers/PreferencesHelper.kt index cab29f90..8146726d 100644 --- a/app/src/main/java/org/y20k/escapepod/helpers/PreferencesHelper.kt +++ b/app/src/main/java/org/y20k/escapepod/helpers/PreferencesHelper.kt @@ -132,6 +132,22 @@ object PreferencesHelper { } + /* Loads size of collection from shared preferences */ + fun loadCollectionSize(context: Context): Int { + val settings = PreferenceManager.getDefaultSharedPreferences(context) + return settings.getInt(Keys.PREF_COLLECTION_SIZE, -1) + } + + + /* Saves site of collection to shared preferences */ + fun saveCollectionSize(context: Context, size: Int) { + val settings = PreferenceManager.getDefaultSharedPreferences(context) + val editor = settings.edit() + editor.putInt(Keys.PREF_COLLECTION_SIZE, size) + editor.apply() + } + + /* Loads date of last save operation from shared preferences */ fun loadCollectionModificationDate(context: Context): Date { val settings = PreferenceManager.getDefaultSharedPreferences(context)