Skip to content

Commit

Permalink
6.1.3 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed Jul 22, 2024
1 parent 3c2618a commit 742aa36
Show file tree
Hide file tree
Showing 31 changed files with 372 additions and 328 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
* Sort dialog no longer dims the main view
* download date can be used to sort both feeds and episodes
* Subscriptions view has a filter based on feed preferences, in the same style as episodes filter
* Subscriptions sorting is now bi-directional based on various explicit measures
* Subscriptions sorting is now bi-directional based on various explicit measures, and sorting info is shown on every feed (List Layout only)
* in Subscriptions view, click on cover image of a feed opens the FeedInfo view (not FeedEpisodes view)
* in all episodes list views, click on an episode image brings up the FeedInfo view
* in episode list view, if episode has no media, TTS button is shown for fetching transcript (if not exist) and then generating audio file from the transcript. TTS audio files are playable in the same way as local media (with speed setting, pause and rewind/forward)
* Long-press filter button in FeedEpisode view enables/disables filters without changing filter settings
* in Subscriptions view, click on cover image of a feed opens the FeedInfo view (not FeedEpisodes view)
* History view shows time of last play, and allows filters and sorts
* Multiple queues can be used: 5 queues are provided by default: Default queue, and Queues 1-4
* all queue operations are on the curQueue, which can be set in all episodes list views
Expand All @@ -90,8 +91,9 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
### Podcast/Episode

* New share notes menu option on various episode views
* Feed info view offers a link for direct search of feeds related to author
* FeedInfo view offers a link for direct search of feeds related to author
* FeedInfo view has button showing number of episodes to open the FeedEpisodes view
* FeedInfo view has feed setting in the header
* in EpisodeInfo view, "mark played/unplayed", "add to/remove from queue", and "favoraite/unfovorite" are at the action bar
* New episode home view with two display modes: webpage or reader
* In episode, in addition to "description" there is a new "transcript" field to save text (if any) fetched from the episode's website
Expand Down
6 changes: 4 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ android {
buildConfig true
}
defaultConfig {
versionCode 3020216
versionName "6.1.2"
versionCode 3020217
versionName "6.1.3"

applicationId "ac.mdiq.podcini.R"
def commit = ""
Expand Down Expand Up @@ -245,6 +245,8 @@ dependencies {

implementation "net.dankito.readability4j:readability4j:1.0.8"

// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'

// Non-free dependencies:
playImplementation 'com.google.android.play:core-ktx:1.8.1'
compileOnly "com.google.android.wearable:wearable:2.9.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,9 @@ object FeedUpdateManager {
if (isSuccessful) {
downloadStatus = DownloadResult(feed.id, feed.getTextIdentifier()?:"", DownloadError.SUCCESS, isSuccessful, reasonDetailed?:"")
return result
} else {
downloadStatus = DownloadResult(feed.id, feed.getTextIdentifier()?:"", reason?: DownloadError.ERROR_NOT_FOUND, isSuccessful, reasonDetailed?:"")
return null
}
downloadStatus = DownloadResult(feed.id, feed.getTextIdentifier()?:"", reason?: DownloadError.ERROR_NOT_FOUND, isSuccessful, reasonDetailed?:"")
return null
}
/**
* Checks if the feed was parsed correctly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize
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.Feeds.getFeedList
import ac.mdiq.podcini.storage.database.RealmDB.realm
Expand Down Expand Up @@ -73,46 +72,46 @@ object AutoDownloads {
@UnstableApi
open fun autoDownloadEpisodeMedia(context: Context, feeds: List<Feed>? = null): Runnable? {
return Runnable {
// true if we should auto download based on network status
// val networkShouldAutoDl = (isAutoDownloadAllowed)
val networkShouldAutoDl = (isAutoDownloadAllowed && isEnableAutodownload)
// true if we should auto download based on power status
val powerShouldAutoDl = (deviceCharging(context) || isEnableAutodownloadOnBattery)
Logd(TAG, "prepare autoDownloadUndownloadedItems $networkShouldAutoDl $powerShouldAutoDl")
// we should only auto download if both network AND power are happy
if (networkShouldAutoDl && powerShouldAutoDl) {
Logd(TAG, "Performing auto-dl of undownloaded episodes")
val queueItems = curQueue.episodes
val newItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.new.name), EpisodeSortOrder.DATE_NEW_OLD)
Logd(TAG, "newItems: ${newItems.size}")
val candidates: MutableList<Episode> = ArrayList(queueItems.size + newItems.size)
candidates.addAll(queueItems)
for (newItem in newItems) {
val feedPrefs = newItem.feed!!.preferences
if (feedPrefs!!.autoDownload && !candidates.contains(newItem) && feedPrefs.autoDownloadFilter!!.shouldAutoDownload(newItem)) candidates.add(newItem)
}
// filter items that are not auto downloadable
val it = candidates.iterator()
while (it.hasNext()) {
val item = it.next()
if (!item.isAutoDownloadEnabled || item.isDownloaded || item.media == null || isCurMedia(item.media) || item.feed?.isLocalFeed == true)
it.remove()
}
val autoDownloadableEpisodes = candidates.size
val downloadedEpisodes = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name))
val deletedEpisodes = AutoCleanups.build().makeRoomForEpisodes(context, autoDownloadableEpisodes)
val cacheIsUnlimited = episodeCacheSize == UserPreferences.EPISODE_CACHE_SIZE_UNLIMITED
val episodeCacheSize = episodeCacheSize
val episodeSpaceLeft =
if (cacheIsUnlimited || episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) autoDownloadableEpisodes
else episodeCacheSize - (downloadedEpisodes - deletedEpisodes)
val itemsToDownload: List<Episode> = candidates.subList(0, episodeSpaceLeft)
if (itemsToDownload.isNotEmpty()) {
Logd(TAG, "Enqueueing " + itemsToDownload.size + " items for download")
for (episode in itemsToDownload) DownloadServiceInterface.get()?.download(context, episode)
}
}
else Logd(TAG, "not auto downloaded networkShouldAutoDl: $networkShouldAutoDl powerShouldAutoDl $powerShouldAutoDl")
// // true if we should auto download based on network status
//// val networkShouldAutoDl = (isAutoDownloadAllowed)
// val networkShouldAutoDl = (isAutoDownloadAllowed && isEnableAutodownload)
// // true if we should auto download based on power status
// val powerShouldAutoDl = (deviceCharging(context) || isEnableAutodownloadOnBattery)
// Logd(TAG, "prepare autoDownloadUndownloadedItems $networkShouldAutoDl $powerShouldAutoDl")
// // we should only auto download if both network AND power are happy
// if (networkShouldAutoDl && powerShouldAutoDl) {
// Logd(TAG, "Performing auto-dl of undownloaded episodes")
// val queueItems = curQueue.episodes
// val newItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.new.name), EpisodeSortOrder.DATE_NEW_OLD)
// Logd(TAG, "newItems: ${newItems.size}")
// val candidates: MutableList<Episode> = ArrayList(queueItems.size + newItems.size)
// candidates.addAll(queueItems)
// for (newItem in newItems) {
// val feedPrefs = newItem.feed!!.preferences
// if (feedPrefs!!.autoDownload && !candidates.contains(newItem) && feedPrefs.autoDownloadFilter!!.shouldAutoDownload(newItem)) candidates.add(newItem)
// }
// // filter items that are not auto downloadable
// val it = candidates.iterator()
// while (it.hasNext()) {
// val item = it.next()
// if (!item.isAutoDownloadEnabled || item.isDownloaded || item.media == null || isCurMedia(item.media) || item.feed?.isLocalFeed == true)
// it.remove()
// }
// val autoDownloadableEpisodes = candidates.size
// val downloadedEpisodes = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name))
// val deletedEpisodes = AutoCleanups.build().makeRoomForEpisodes(context, autoDownloadableEpisodes)
// val cacheIsUnlimited = episodeCacheSize == UserPreferences.EPISODE_CACHE_SIZE_UNLIMITED
// val episodeCacheSize = episodeCacheSize
// val episodeSpaceLeft =
// if (cacheIsUnlimited || episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) autoDownloadableEpisodes
// else episodeCacheSize - (downloadedEpisodes - deletedEpisodes)
// val itemsToDownload: List<Episode> = candidates.subList(0, episodeSpaceLeft)
// if (itemsToDownload.isNotEmpty()) {
// Logd(TAG, "Enqueueing " + itemsToDownload.size + " items for download")
// for (episode in itemsToDownload) DownloadServiceInterface.get()?.download(context, episode)
// }
// }
// else Logd(TAG, "not auto downloaded networkShouldAutoDl: $networkShouldAutoDl powerShouldAutoDl $powerShouldAutoDl")
}
}

Expand Down Expand Up @@ -150,7 +149,10 @@ object AutoDownloads {
feeds.forEach { f ->
if (f.preferences?.autoDownload == true && !f.isLocalFeed) {
var episodes = mutableListOf<Episode>()
val downloadedCount = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name), f.id)
val dlFilter =
if (f.preferences?.countingPlayed == true) EpisodeFilter(EpisodeFilter.States.downloaded.name)
else EpisodeFilter(EpisodeFilter.States.downloaded.name, EpisodeFilter.States.unplayed.name)
val downloadedCount = getEpisodesCount(dlFilter, f.id)
val allowedDLCount = (f.preferences?.autoDLMaxEpisodes?:0) - downloadedCount
Logd(TAG, "autoDownloadEpisodeMedia ${f.preferences?.autoDLMaxEpisodes} downloadedCount: $downloadedCount allowedDLCount: $allowedDLCount")
if (allowedDLCount > 0) {
Expand Down Expand Up @@ -198,7 +200,7 @@ object AutoDownloads {
}
}
}
// TODO: need to send an event
// TODO: probably need to send an event
}
}
if (candidates.isNotEmpty()) {
Expand Down
41 changes: 7 additions & 34 deletions app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,12 @@ import kotlin.math.abs

object Feeds {
private val TAG: String = Feeds::class.simpleName ?: "Anonymous"
private val feedMap: MutableMap<Long, Feed> = mutableMapOf()
private val tags: MutableList<String> = mutableListOf()

@Synchronized
fun getFeedList(queryString: String = "", fromDB: Boolean = true): List<Feed> {
if (fromDB) {
return if (queryString.isEmpty()) realm.query(Feed::class).find()
else realm.query(Feed::class, queryString).find()
}
return feedMap.values.toList()
fun getFeedList(queryString: String = ""): List<Feed> {
return if (queryString.isEmpty()) realm.query(Feed::class).find()
else realm.query(Feed::class, queryString).find()
}

fun getFeedCount(): Int {
Expand All @@ -56,26 +52,6 @@ object Feeds {
return tags
}

@Synchronized
fun updateFeedMap(feeds: List<Feed> = listOf(), wipe: Boolean = false) {
Logd(TAG, "updateFeedMap called feeds: ${feeds.size} wipe: $wipe")
when {
feeds.isEmpty() -> {
val feeds_ = realm.query(Feed::class).find()
feedMap.clear()
feedMap.putAll(feeds_.associateBy { it.id })
}
wipe -> {
feedMap.clear()
feedMap.putAll(feeds.associateBy { it.id })
}
else -> {
for (f in feeds) feedMap[f.id] = f
}
}
buildTags()
}

fun buildTags() {
val tagsSet = mutableSetOf<String>()
val feedsCopy = getFeedList()
Expand Down Expand Up @@ -174,13 +150,13 @@ object Feeds {
return result
}

fun getFeed(feedId: Long, copy: Boolean = false, fromDB: Boolean = true): Feed? {
fun getFeed(feedId: Long, copy: Boolean = false): Feed? {
if (BuildConfig.DEBUG) {
val stackTrace = Thread.currentThread().stackTrace
val caller = if (stackTrace.size > 3) stackTrace[3] else null
Logd(TAG, "${caller?.className}.${caller?.methodName} getFeed called fromDB: $fromDB")
Logd(TAG, "${caller?.className}.${caller?.methodName} getFeed called")
}
val f = if (fromDB) realm.query(Feed::class, "id == $feedId").first().find() else feedMap[feedId]
val f = realm.query(Feed::class, "id == $feedId").first().find()
return if (f != null) {
if (copy) realm.copyFromRealm(f)
else f
Expand Down Expand Up @@ -247,7 +223,6 @@ object Feeds {
// Look for new or updated Items
for (idx in newFeed.episodes.indices) {
val episode = newFeed.episodes[idx]

val possibleDuplicate = EpisodeAssistant.searchEpisodeGuessDuplicate(newFeed.episodes, episode)
if (!newFeed.isLocalFeed && possibleDuplicate != null && episode !== possibleDuplicate) {
// Canonical episode is the first one returned (usually oldest)
Expand All @@ -263,7 +238,6 @@ object Feeds {
""".trimIndent()))
continue
}

var oldItem = EpisodeAssistant.searchEpisodeByIdentifyingValue(savedFeed.episodes, episode)
if (!newFeed.isLocalFeed && oldItem == null) {
oldItem = EpisodeAssistant.searchEpisodeGuessDuplicate(savedFeed.episodes, episode)
Expand Down Expand Up @@ -394,7 +368,6 @@ object Feeds {
}
copyToRealm(feed)
}
// updateFeedMap(feeds.toList())
}
for (feed in feeds) {
if (!feed.isLocalFeed && feed.downloadUrl != null) SynchronizationQueueSink.enqueueFeedAddedIfSyncActive(context, feed.downloadUrl!!)
Expand Down Expand Up @@ -451,7 +424,7 @@ object Feeds {
val feedToDelete = findLatest(feed_)
if (feedToDelete != null) {
delete(feedToDelete)
feedMap.remove(feedId)
// feedMap.remove(feedId)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ object Queues {
queue.episodeIds.add(insertPosition, episode.id)
queue.episodes.add(insertPosition, episode)
insertPosition++
queue.update()
if (queue.id == curQueue.id) queue.update()
upsert(queue) {}

if (markAsUnplayed && episode.isNew) setPlayState(Episode.UNPLAYED, false, episode)

if (queue_?.id == curQueue.id) EventFlow.postEvent(FlowEvent.QueueEvent.added(episode, insertPosition))
if (queue.id == curQueue.id) EventFlow.postEvent(FlowEvent.QueueEvent.added(episode, insertPosition))
// if (performAutoDownload) autodownloadEpisodeMedia(context)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import kotlin.coroutines.ContinuationInterceptor
object RealmDB {
private val TAG: String = RealmDB::class.simpleName ?: "Anonymous"

private const val SCHEMA_VERSION_NUMBER = 10L
private const val SCHEMA_VERSION_NUMBER = 11L

private val ioScope = CoroutineScope(Dispatchers.IO)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Episode : RealmObject {
@Ignore
var feed: Feed? = null
get() {
if (field == null && feedId != null) field = getFeed(feedId!!, fromDB = true)
if (field == null && feedId != null) field = getFeed(feedId!!)
return field
}

Expand Down
43 changes: 24 additions & 19 deletions app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ac.mdiq.podcini.storage.model

import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.model.FeedFunding.Companion.extractPaymentLinks
import ac.mdiq.podcini.storage.model.EpisodeSortOrder.Companion.fromCode
import io.realm.kotlin.ext.realmListOf
Expand Down Expand Up @@ -132,29 +133,30 @@ class Feed : RealmObject {
preferences?.sortOrderCode = value.code
}

@Ignore
var sortOrderAux: EpisodeSortOrder? = null
get() = fromCode(preferences?.sortOrderAuxCode ?: 0)
set(value) {
if (value == null) return
field = value
preferences?.sortOrderAuxCode = value.code
}
// @Ignore
// var sortOrderAux: EpisodeSortOrder? = null
// get() = fromCode(preferences?.sortOrderAuxCode ?: 0)
// set(value) {
// if (value == null) return
// field = value
// preferences?.sortOrderAuxCode = value.code
// }

@Ignore
val mostRecentItem: Episode?
get() {
// we could sort, but we don't need to, a simple search is fine...
var mostRecentDate = Date(0)
var mostRecentItem: Episode? = null
for (item in episodes) {
val date = item.getPubDate()
if (date != null && date.after(mostRecentDate)) {
mostRecentDate = date
mostRecentItem = item
}
}
return mostRecentItem
// // we could sort, but we don't need to, a simple search is fine...
// var mostRecentDate = Date(0)
// var mostRecentItem: Episode? = null
// for (item in episodes) {
// val date = item.getPubDate()
// if (date != null && date.after(mostRecentDate)) {
// mostRecentDate = date
// mostRecentItem = item
// }
// }
// return mostRecentItem
return realm.query(Episode::class).query("feedId == $id SORT(pubDate DESC)").first().find()
}

@Ignore
Expand All @@ -164,6 +166,9 @@ class Feed : RealmObject {
this.eigenTitle = value
}

@Ignore
var sortInfo: String = ""

/**
* This constructor is used for test purposes.
*/
Expand Down
Loading

0 comments on commit 742aa36

Please sign in to comment.