diff --git a/app/build.gradle b/app/build.gradle index 3cc4da12..7a7f9e02 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,8 +126,8 @@ android { buildConfig true } defaultConfig { - versionCode 3020214 - versionName "6.1.0" + versionCode 3020215 + versionName "6.1.1" applicationId "ac.mdiq.podcini.R" def commit = "" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlBackupAgent.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlBackupAgent.kt index fc265027..ec27d996 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlBackupAgent.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlBackupAgent.kt @@ -95,7 +95,8 @@ class OpmlBackupAgent : BackupAgentHelper() { IOUtils.closeQuietly(writer) } } - @OptIn(UnstableApi::class) override fun restoreEntity(data: BackupDataInputStream) { + @OptIn(UnstableApi::class) + override fun restoreEntity(data: BackupDataInputStream) { Logd(TAG, "Backup restore") if (OPML_ENTITY_KEY != data.key) { Logd(TAG, "Unknown entity key: " + data.key) @@ -103,6 +104,7 @@ class OpmlBackupAgent : BackupAgentHelper() { } var digester: MessageDigest? = null var reader: Reader + var linesRead = 0 try { digester = MessageDigest.getInstance("MD5") reader = InputStreamReader(DigestInputStream(data, digester), Charset.forName("UTF-8")) @@ -112,25 +114,30 @@ class OpmlBackupAgent : BackupAgentHelper() { try { mChecksum = digester?.digest() ?: byteArrayOf() BufferedReader(reader).use { bufferedReader -> - val tempFile = File.createTempFile("opml_restored", ".tmp", mContext.filesDir) + val tempFile = File(mContext.filesDir, "opml_restored.txt") +// val tempFile = File.createTempFile("opml_restored", ".tmp", mContext.filesDir) FileWriter(tempFile).use { fileWriter -> while (true) { val line = bufferedReader.readLine() ?: break + Logd(TAG, "restoreEntity: $linesRead $line") + linesRead++ fileWriter.write(line) fileWriter.write(System.lineSeparator()) // Write a newline character } } } - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext) - with(sharedPreferences.edit()) { - putBoolean(UserPreferences.Prefs.prefOPMLRestore.name, true) - apply() - } } catch (e: XmlPullParserException) { Log.e(TAG, "Error while parsing the OPML file", e) } catch (e: IOException) { Log.e(TAG, "Failed to restore OPML backup", e) } finally { + if (linesRead > 0) { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext) + with(sharedPreferences.edit()) { + putBoolean(UserPreferences.Prefs.prefOPMLRestore.name, true) + apply() + } + } IOUtils.closeQuietly(reader) } } @@ -166,11 +173,12 @@ class OpmlBackupAgent : BackupAgentHelper() { private const val OPML_BACKUP_KEY = "opml" val isOPMLRestared: Boolean - get() = appPrefs.getBoolean(UserPreferences.Prefs.prefOPMLRestore.name, true) + get() = appPrefs.getBoolean(UserPreferences.Prefs.prefOPMLRestore.name, false) fun performRestore(context: Context) { Logd(TAG, "performRestore") - val tempFile = File.createTempFile("opml_restored", ".tmp", context.filesDir) + val tempFile = File(context.filesDir, "opml_restored.txt") +// val tempFile = File.createTempFile("opml_restored", ".tmp", context.filesDir) if (tempFile.exists()) { val reader = FileReader(tempFile) val opmlElements = OpmlReader().readDocument(reader) @@ -179,6 +187,7 @@ class OpmlBackupAgent : BackupAgentHelper() { feed.episodes.clear() updateFeed(context, feed, false) } + Toast.makeText(context, "${opmlElements.size} feeds were restored", Toast.LENGTH_SHORT).show() runOnce(context) } else { Toast.makeText(context, "No backup data found", Toast.LENGTH_SHORT).show() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoCleanups.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoCleanups.kt index 0e84e6ab..9dcf74af 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoCleanups.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoCleanups.kt @@ -12,6 +12,7 @@ import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.EpisodeFilter import ac.mdiq.podcini.storage.model.EpisodeSortOrder +import ac.mdiq.podcini.util.Logd import android.content.Context import android.util.Log import androidx.annotation.OptIn @@ -68,7 +69,8 @@ object AutoCleanups { override fun getReclaimableItems(): Int { return candidates.size } - @OptIn(UnstableApi::class) public override fun performCleanup(context: Context, numToRemove: Int): Int { + @OptIn(UnstableApi::class) + public override fun performCleanup(context: Context, numToRemove: Int): Int { var candidates = candidates // in the absence of better data, we'll sort by item publication date candidates = candidates.sortedWith { lhs: Episode, rhs: Episode -> @@ -250,8 +252,12 @@ object AutoCleanups { * @return The number of episodes that were deleted. */ protected abstract fun performCleanup(context: Context, numToRemove: Int): Int + + // only used in tests fun performCleanup(context: Context): Int { - return performCleanup(context, getDefaultCleanupParameter()) + val numToRemove = getDefaultCleanupParameter() + if (numToRemove <= 0) return 0 + return performCleanup(context, numToRemove) } /** * Returns a parameter for performCleanup. The implementation of this interface should decide how much @@ -266,7 +272,10 @@ object AutoCleanups { * @return The number of epiosdes that were deleted */ fun makeRoomForEpisodes(context: Context, amountOfRoomNeeded: Int): Int { - return performCleanup(context, getNumEpisodesToCleanup(amountOfRoomNeeded)) + val numToRemove = getNumEpisodesToCleanup(amountOfRoomNeeded) + Logd("EpisodeCleanupAlgorithm", "makeRoomForEpisodes: $numToRemove") + if (numToRemove <= 0) return 0 + return performCleanup(context, numToRemove) } /** * @return the number of episodes/items that *could* be cleaned up, if needed diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt index 6343d975..74155f10 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt @@ -10,20 +10,17 @@ import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadOnBattery import ac.mdiq.podcini.storage.database.Episodes.getEpisodes import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount -import ac.mdiq.podcini.storage.database.Episodes.setPlayState import ac.mdiq.podcini.storage.database.Feeds.getFeedList import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.model.* -import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor import ac.mdiq.podcini.util.Logd import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager import androidx.media3.common.util.UnstableApi -import io.realm.kotlin.Realm import io.realm.kotlin.UpdatePolicy import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -149,27 +146,28 @@ object AutoDownloads { if (networkShouldAutoDl && powerShouldAutoDl) { Logd(Companion.TAG, "autoDownloadEpisodeMedia Performing auto-dl of undownloaded episodes") val candidates: MutableSet = mutableSetOf() - val queueItems = curQueue.episodes.filter { it.media?.downloaded != true } + val queueItems = realm.query(Episode::class).query("id IN $0 AND media.downloaded == false", curQueue.episodeIds).find() + Logd(TAG, "autoDownloadEpisodeMedia add from queue: ${queueItems.size}") if (queueItems.isNotEmpty()) candidates.addAll(queueItems) val feeds = feeds ?: getFeedList() feeds.forEach { f -> if (f.preferences?.autoDownload == true && !f.isLocalFeed) { var episodes = mutableListOf() val downloadedCount = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name), f.id) - val toDLCount = (f.preferences?.autoDLMaxEpisodes?:0) - downloadedCount - if (toDLCount > 0) { + val allowedDLCount = (f.preferences?.autoDLMaxEpisodes?:0) - downloadedCount + if (allowedDLCount > 0) { var queryString = "feedId == ${f.id} AND isAutoDownloadEnabled == true AND media != nil AND media.downloaded == false" when (f.preferences?.autoDLPolicy) { FeedPreferences.AutoDLPolicy.ONLY_NEW -> { - queryString += " AND playState == -1 SORT(pubDate DESC) LIMIT(${3*toDLCount})" + queryString += " AND playState == -1 SORT(pubDate DESC) LIMIT(${3*allowedDLCount})" episodes = realm.query(Episode::class).query(queryString).find().toMutableList() } FeedPreferences.AutoDLPolicy.NEWER -> { - queryString += " AND playState != 1 SORT(pubDate DESC) LIMIT(${3*toDLCount})" + queryString += " AND playState != 1 SORT(pubDate DESC) LIMIT(${3*allowedDLCount})" episodes = realm.query(Episode::class).query(queryString).find().toMutableList() } FeedPreferences.AutoDLPolicy.OLDER -> { - queryString += " AND playState != 1 SORT(pubDate ASC) LIMIT(${3*toDLCount})" + queryString += " AND playState != 1 SORT(pubDate ASC) LIMIT(${3*allowedDLCount})" episodes = realm.query(Episode::class).query(queryString).find().toMutableList() } else -> {} @@ -177,9 +175,11 @@ object AutoDownloads { if (episodes.isNotEmpty()) { var count = 0 for (e in episodes) { + if (isCurMedia(e.media)) continue if (f.preferences?.autoDownloadFilter?.shouldAutoDownload(e) == true) { + Logd(TAG, "autoDownloadEpisodeMedia add to cadidates: ${e.title} ${e.isDownloaded}") candidates.add(e) - if (++count >= toDLCount) break + if (++count >= allowedDLCount) break } else upsertBlk(e) { it.setPlayed(true)} } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt index d0bf9178..aebb5e20 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt @@ -110,6 +110,7 @@ class AddFeedFragment : Fragment() { .setPositiveButton("Yes") { dialog, _ -> performRestore(requireContext()) dialog.dismiss() + parentFragmentManager.popBackStack() } .setNegativeButton("No") { dialog, _ -> dialog.dismiss() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index 287b9493..6627658c 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -324,6 +324,17 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar (activity as MainActivity).setPlayerVisible(true) } + private fun onPlaybackPositionEvent(event: FlowEvent.PlaybackPositionEvent) { +// Logd(TAG, "onPlayEvent ${event.episode.title}") + val media = event.media + if (currentMedia?.getIdentifier() == null || media?.getIdentifier() != currentMedia?.getIdentifier()) { + currentMedia = media + playerDetailsFragment?.setItem(curEpisode!!) + } + playerUI?.onPositionUpdate(event) + if (!isCollapsed) playerDetailsFragment?.onPlaybackPositionEvent(event) + } + private var eventSink: Job? = null private fun cancelFlowEvents() { Logd(TAG, "cancelFlowEvents") @@ -346,10 +357,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar is FlowEvent.FavoritesEvent -> onFavoriteEvent(event) is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(activity as Activity, event) is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false) - is FlowEvent.PlaybackPositionEvent -> { - playerUI?.onPositionUpdate(event) - if (!isCollapsed) playerDetailsFragment?.onPlaybackPositionEvent(event) - } + is FlowEvent.PlaybackPositionEvent -> onPlaybackPositionEvent(event) is FlowEvent.SpeedChangedEvent -> playerUI?.updatePlaybackSpeedButton(event) else -> {} } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt index 1a6d884f..d1e73397 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/BaseEpisodesFragment.kt @@ -5,6 +5,7 @@ import ac.mdiq.podcini.databinding.BaseEpisodesListFragmentBinding import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding import ac.mdiq.podcini.net.feed.FeedUpdateManager import ac.mdiq.podcini.playback.base.InTheatre.isCurMedia +import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.EpisodeFilter @@ -343,7 +344,8 @@ import kotlinx.coroutines.flow.collectLatest curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id) if (pos >= 0) { - episodes[pos] = item + episodes[pos] = unmanaged(episodes[pos]) + episodes[pos].media?.position = event.media.position curIndex = pos adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt index bdaac9db..b4b74578 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt @@ -10,6 +10,7 @@ import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.storage.database.Episodes.getEpisodes import ac.mdiq.podcini.storage.database.RealmDB.realm +import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.EpisodeFilter import ac.mdiq.podcini.storage.model.EpisodeMedia @@ -310,7 +311,8 @@ import java.util.* curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id) if (pos >= 0) { - episodes[pos] = item + episodes[pos] = unmanaged(episodes[pos]) + episodes[pos].media?.position = event.media.position curIndex = pos adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt index cfa744d6..4bceb9c0 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt @@ -421,7 +421,8 @@ import java.util.concurrent.Semaphore curIndex else EpisodeUtil.indexOfItemWithId(episodes, item.id) if (pos >= 0) { - episodes[pos] = item + episodes[pos] = unmanaged(episodes[pos]) + episodes[pos].media?.position = event.media.position curIndex = pos adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueueFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueueFragment.kt index cdbe9817..8ea479df 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueueFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueueFragment.kt @@ -17,6 +17,7 @@ import ac.mdiq.podcini.storage.database.Queues.moveInQueueSync import ac.mdiq.podcini.storage.database.Queues.queueKeepSortedOrder import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope +import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.EpisodeFilter @@ -337,7 +338,8 @@ import java.util.* curIndex else EpisodeUtil.indexOfItemWithId(queueItems, item.id) if (pos >= 0) { - queueItems[pos] = item + queueItems[pos] = unmanaged(queueItems[pos]) + queueItems[pos].media?.position = event.media.position curIndex = pos adapter?.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt index 64387f00..c6feee81 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt @@ -8,6 +8,7 @@ import ac.mdiq.podcini.databinding.SearchFragmentBinding import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher import ac.mdiq.podcini.playback.base.InTheatre.isCurMedia import ac.mdiq.podcini.storage.database.RealmDB.realm +import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.Feed @@ -300,7 +301,8 @@ import java.lang.ref.WeakReference curIndex else EpisodeUtil.indexOfItemWithId(results, item.id) if (pos >= 0) { - results[pos] = item + results[pos] = unmanaged(results[pos]) + results[pos].media?.position = event.media.position curIndex = pos adapter.notifyItemChanged(pos, Bundle().apply { putString("PositionUpdate", "PlaybackPositionEvent") }) } diff --git a/changelog.md b/changelog.md index 0a635883..3551d793 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +# 6.1.1 + +* fixed player UI not updating on change of episode +* fixed the mal-function of restoring previously backed-up OPML +* reduced reactions to PlaybackPositionEvent +* tuned AutoCleanup a bit +* tuned and fixed some some issues in audo-downloaded + # 6.1.0 * in FeedEpisode view fixed filtering after an episode's play state is changed diff --git a/fastlane/metadata/android/en-US/changelogs/3020214.txt b/fastlane/metadata/android/en-US/changelogs/3020214.txt index fe4be406..1a31d4bc 100644 --- a/fastlane/metadata/android/en-US/changelogs/3020214.txt +++ b/fastlane/metadata/android/en-US/changelogs/3020214.txt @@ -1,5 +1,5 @@ -Version 6.0.14 brings several changes: +Version 6.1.0 brings several changes: * in FeedEpisode view fixed filtering after an episode's play state is changed * fixed refreshing a feed causes duplicate episodes in FeedEpisodes view diff --git a/fastlane/metadata/android/en-US/changelogs/3020215.txt b/fastlane/metadata/android/en-US/changelogs/3020215.txt new file mode 100644 index 00000000..39731029 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020215.txt @@ -0,0 +1,8 @@ + +Version 6.1.1 brings several changes: + +* fixed player UI not updating on change of episode +* fixed the mal-function of restoring previously backed-up OPML +* reduced reactions to PlaybackPositionEvent +* tuned AutoCleanup a bit +* tuned and fixed some some issues in audo-downloaded