Skip to content

Commit 27f5f5e

Browse files
committed
6.0.5 commit
1 parent a00376b commit 27f5f5e

File tree

70 files changed

+1643
-1912
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1643
-1912
lines changed

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ android {
125125
buildConfig true
126126
}
127127
defaultConfig {
128-
versionCode 3020204
129-
versionName "6.0.4"
128+
versionCode 3020205
129+
versionName "6.0.5"
130130

131131
applicationId "ac.mdiq.podcini.R"
132132
def commit = ""

app/src/androidTest/kotlin/ac/test/podcini/service/download/HttpDownloaderTest.kt

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import ac.mdiq.podcini.net.download.service.HttpDownloader
66
import ac.mdiq.podcini.net.download.serviceinterface.DownloadRequest
77
import ac.mdiq.podcini.preferences.UserPreferences.init
88
import ac.mdiq.podcini.util.Logd
9-
import ac.test.podcini.service.download.FeedComponent
109
import androidx.test.filters.LargeTest
1110
import androidx.test.platform.app.InstrumentationRegistry
1211
import de.test.podcini.util.service.download.HTTPBin
@@ -245,3 +244,48 @@ abstract class FeedFile(@JvmField var file_url: String? = null,
245244
this.downloaded = downloaded
246245
}
247246
}
247+
248+
/**
249+
* Represents every possible component of a feed
250+
*
251+
* @author daniel
252+
*/
253+
// only used in test
254+
abstract class FeedComponent internal constructor() {
255+
open var id: Long = 0
256+
257+
/**
258+
* Update this FeedComponent's attributes with the attributes from another
259+
* FeedComponent. This method should only update attributes which where read from
260+
* the feed.
261+
*/
262+
fun updateFromOther(other: FeedComponent?) {}
263+
264+
/**
265+
* Compare's this FeedComponent's attribute values with another FeedComponent's
266+
* attribute values. This method will only compare attributes which were
267+
* read from the feed.
268+
*
269+
* @return true if attribute values are different, false otherwise
270+
*/
271+
fun compareWithOther(other: FeedComponent?): Boolean {
272+
return false
273+
}
274+
275+
/**
276+
* Should return a non-null, human-readable String so that the item can be
277+
* identified by the user. Can be title, download-url, etc.
278+
*/
279+
abstract fun getHumanReadableIdentifier(): String?
280+
281+
override fun equals(o: Any?): Boolean {
282+
if (this === o) return true
283+
if (o !is FeedComponent) return false
284+
285+
return id == o.id
286+
}
287+
288+
override fun hashCode(): Int {
289+
return (id xor (id ushr 32)).toInt()
290+
}
291+
}

app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import com.google.common.util.concurrent.ListenableFuture
3838
import kotlinx.coroutines.CoroutineScope
3939
import kotlinx.coroutines.Dispatchers
4040
import kotlinx.coroutines.launch
41+
import kotlinx.coroutines.runBlocking
4142
import org.apache.commons.io.FileUtils
4243
import java.io.File
4344
import java.io.IOException
@@ -114,7 +115,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
114115
.addTag(WORK_TAG)
115116
.addTag(WORK_TAG_EPISODE_URL + item.media!!.downloadUrl)
116117
if (UserPreferences.enqueueDownloadedEpisodes()) {
117-
Queues.addToQueue(false, item)
118+
runBlocking { Queues.addToQueueSync(false, item) }
118119
workRequest.addTag(WORK_DATA_WAS_QUEUED)
119120
}
120121
workRequest.setInputData(Data.Builder().putLong(WORK_DATA_MEDIA_ID, item.media!!.id).build())
@@ -388,10 +389,11 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
388389
if (item != null) {
389390
item.disableAutoDownload()
390391
Logd(TAG, "persisting episode downloaded ${item.title} ${item.media?.fileUrl} ${item.media?.downloaded}")
391-
// setFeedItem() signals (via EventBus) that the item has been updated,
392+
// setFeedItem() signals that the item has been updated,
392393
// so we do it after the enclosing media has been updated above,
393394
// to ensure subscribers will get the updated EpisodeMedia as well
394395
Episodes.persistEpisode(item)
396+
// TODO: should use different event?
395397
if (broadcastUnreadStateUpdate) EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(item))
396398
}
397399
} catch (e: InterruptedException) {

app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedUpdateManager.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ import ac.mdiq.podcini.net.utils.NetworkUtils.isNetworkRestricted
1515
import ac.mdiq.podcini.net.utils.NetworkUtils.isVpnOverWifi
1616
import ac.mdiq.podcini.net.utils.NetworkUtils.networkAvailable
1717
import ac.mdiq.podcini.storage.algorithms.AutoDownloads.autodownloadEpisodeMedia
18-
import ac.mdiq.podcini.storage.database.Episodes
1918
import ac.mdiq.podcini.storage.database.Feeds
2019
import ac.mdiq.podcini.storage.database.LogsAndStats
21-
import ac.mdiq.podcini.storage.database.RealmDB.unmanagedCopy
20+
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
2221
import ac.mdiq.podcini.storage.model.DownloadResult
2322
import ac.mdiq.podcini.storage.model.FeedPreferences
2423
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
@@ -206,7 +205,7 @@ object FeedUpdateManager {
206205
while (toUpdate.isNotEmpty()) {
207206
if (isStopped) return
208207
notificationManager.notify(R.id.notification_updating_feeds, createNotification(toUpdate))
209-
val feed = unmanagedCopy(toUpdate[0])
208+
val feed = unmanaged(toUpdate[0])
210209
try {
211210
Logd(TAG, "updating local feed? ${feed.isLocalFeed} ${feed.title}")
212211
if (feed.isLocalFeed) LocalFeedUpdater.updateFeed(feed, applicationContext, null)

app/src/main/kotlin/ac/mdiq/podcini/net/sync/SyncService.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.isAllowMobileSync
2121
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
2222
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
2323
import ac.mdiq.podcini.storage.database.Episodes.persistEpisodes
24-
import ac.mdiq.podcini.storage.database.Feeds.deleteFeed
24+
import ac.mdiq.podcini.storage.database.Feeds.deleteFeedSync
2525
import ac.mdiq.podcini.storage.database.Feeds.getFeedList
2626
import ac.mdiq.podcini.storage.database.Feeds.getFeedListDownloadUrls
2727
import ac.mdiq.podcini.storage.database.Feeds.updateFeed
@@ -44,11 +44,8 @@ import androidx.core.app.NotificationCompat
4444
import androidx.media3.common.util.UnstableApi
4545
import androidx.work.*
4646
import androidx.work.Constraints.Builder
47-
import kotlinx.coroutines.CoroutineScope
48-
import kotlinx.coroutines.Dispatchers
49-
import kotlinx.coroutines.cancel
47+
import kotlinx.coroutines.*
5048
import kotlinx.coroutines.flow.collectLatest
51-
import kotlinx.coroutines.launch
5249
import org.apache.commons.lang3.StringUtils
5350
import java.util.concurrent.ExecutionException
5451
import java.util.concurrent.TimeUnit
@@ -168,7 +165,10 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
168165
}
169166
if (feedID != null) {
170167
try {
171-
deleteFeed(context, feedID)
168+
runBlocking {
169+
deleteFeedSync(context, feedID)
170+
EventFlow.postEvent(FlowEvent.FeedListEvent(FlowEvent.FeedListEvent.Action.REMOVED, feedID))
171+
}
172172
} catch (e: InterruptedException) {
173173
e.printStackTrace()
174174
} catch (e: ExecutionException) {
@@ -185,7 +185,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
185185
EventFlow.stickyEvents.collectLatest { event ->
186186
Logd(TAG, "Received sticky event: ${event.TAG}")
187187
when (event) {
188-
is FlowEvent.FeedUpdateRunningEvent -> if (!event.isFeedUpdateRunning) return@collectLatest
188+
is FlowEvent.FeedUpdatingEvent -> if (!event.isRunning) return@collectLatest
189189
else -> {}
190190
}
191191
}

app/src/main/kotlin/ac/mdiq/podcini/playback/PlaybackServiceStarter.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import ac.mdiq.podcini.storage.model.EpisodeMedia
99
import ac.mdiq.podcini.playback.base.InTheatre.curEpisode
1010
import ac.mdiq.podcini.playback.base.InTheatre.curMedia
1111
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.EXTRA_ALLOW_STREAM_THIS_TIME
12+
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
1213
import ac.mdiq.podcini.storage.model.Playable
1314
import ac.mdiq.podcini.util.Logd
1415
import ac.mdiq.podcini.util.event.EventFlow
@@ -44,8 +45,12 @@ class PlaybackServiceStarter(private val context: Context, private val media: Pl
4445
fun start() {
4546
Logd("PlaybackServiceStarter", "starting PlaybackService")
4647
if (curEpisode != null) EventFlow.postEvent(FlowEvent.PlayEvent(curEpisode!!, FlowEvent.PlayEvent.Action.END))
47-
curMedia = media
48-
if (media is EpisodeMedia) curEpisode = media.episode
48+
if (media is EpisodeMedia) {
49+
curMedia = media
50+
// curEpisode = if (media.episode != null) unmanaged(media.episode!!) else null
51+
curEpisode = media.episode
52+
// curMedia = curEpisode?.media
53+
} else curMedia = media
4954

5055
if (PlaybackService.isRunning && !callEvenIfRunning) return
5156
ContextCompat.startForegroundService(context, intent)

app/src/main/kotlin/ac/mdiq/podcini/playback/base/InTheatre.kt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ import kotlinx.coroutines.*
1818
object InTheatre {
1919
val TAG: String = InTheatre::class.simpleName ?: "Anonymous"
2020

21-
var curQueue: PlayQueue
21+
var curQueue: PlayQueue // unmanaged
2222

23-
var curEpisode: Episode? = null
23+
var curEpisode: Episode? = null // unmanged
2424
set(value) {
2525
field = value
2626
if (curMedia != field?.media) curMedia = field?.media
2727
}
2828

29-
var curMedia: Playable? = null
29+
var curMedia: Playable? = null // unmanged if EpisodeMedia
3030
set(value) {
3131
field = value
3232
if (field is EpisodeMedia) {
@@ -35,7 +35,7 @@ object InTheatre {
3535
}
3636
}
3737

38-
var curState: CurrentState
38+
var curState: CurrentState // unmanaged
3939

4040
init {
4141
curQueue = PlayQueue()
@@ -60,9 +60,7 @@ object InTheatre {
6060
curQueue_.id = i.toLong()
6161
curQueue_.name = "Queue $i"
6262
}
63-
realm.write {
64-
copyToRealm(curQueue_)
65-
}
63+
upsert(curQueue_) {}
6664
}
6765
curQueue.update()
6866
upsert(curQueue) {}
@@ -75,9 +73,7 @@ object InTheatre {
7573
Logd(TAG, "creating new curState")
7674
curState_ = CurrentState()
7775
curState = curState_
78-
realm.write {
79-
copyToRealm(curState_)
80-
}
76+
upsert(curState_) {}
8177
}
8278
loadPlayableFromPreferences()
8379
}

app/src/main/kotlin/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
238238
}
239239
// stop playback of this episode
240240
if (status == PlayerStatus.PAUSED || status == PlayerStatus.PLAYING || status == PlayerStatus.PREPARED) exoPlayer?.stop()
241-
if (prevMedia != null && curMedia!!.getIdentifier() != prevMedia?.getIdentifier())
242-
callback.onPostPlayback(prevMedia, ended = false, skipped = false, true)
241+
// if (prevMedia != null && curMedia!!.getIdentifier() != prevMedia?.getIdentifier())
242+
// callback.onPostPlayback(prevMedia, ended = false, skipped = false, true)
243243
prevMedia = curMedia
244244
setPlayerStatus(PlayerStatus.INDETERMINATE, null)
245245
}

app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,20 @@ import ac.mdiq.podcini.preferences.UserPreferences.isPersistNotify
3232
import ac.mdiq.podcini.preferences.UserPreferences.isUnpauseOnBluetoothReconnect
3333
import ac.mdiq.podcini.preferences.UserPreferences.isUnpauseOnHeadsetReconnect
3434
import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
35+
import ac.mdiq.podcini.preferences.UserPreferences.shouldAutoDeleteItem
36+
import ac.mdiq.podcini.preferences.UserPreferences.shouldDeleteRemoveFromQueue
3537
import ac.mdiq.podcini.preferences.UserPreferences.shouldFavoriteKeepEpisode
3638
import ac.mdiq.podcini.preferences.UserPreferences.shouldSkipKeepEpisode
3739
import ac.mdiq.podcini.receiver.MediaButtonReceiver
3840
import ac.mdiq.podcini.storage.database.Episodes.addToHistory
39-
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaOfEpisode
41+
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync
4042
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
41-
import ac.mdiq.podcini.storage.database.Episodes.markPlayed
43+
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
4244
import ac.mdiq.podcini.storage.database.Episodes.persistEpisode
43-
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItemsOnFeed
4445
import ac.mdiq.podcini.storage.database.Queues.addToQueue
46+
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
4547
import ac.mdiq.podcini.storage.database.RealmDB.realm
46-
import ac.mdiq.podcini.storage.database.RealmDB.unmanagedCopy
48+
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
4749
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
4850
import ac.mdiq.podcini.storage.model.CurrentState.Companion.NO_MEDIA_PLAYING
4951
import ac.mdiq.podcini.storage.model.CurrentState.Companion.PLAYER_STATUS_OTHER
@@ -292,7 +294,7 @@ class PlaybackService : MediaSessionService() {
292294
// TODO: test
293295
// return
294296
}
295-
val item = (playable as? EpisodeMedia)?.episode ?: currentitem
297+
var item = (playable as? EpisodeMedia)?.episode ?: currentitem
296298
val smartMarkAsPlayed = hasAlmostEnded(playable)
297299
if (!ended && smartMarkAsPlayed) Logd(TAG, "smart mark as played")
298300

@@ -311,22 +313,21 @@ class PlaybackService : MediaSessionService() {
311313
}
312314
}
313315
if (item != null) {
314-
if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode())) {
315-
Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped")
316-
// only mark the item as played if we're not keeping it anyways
317-
markPlayed(Episode.PLAYED, ended || (skipped && smartMarkAsPlayed), item)
318-
// don't know if it actually matters to not autodownload when smart mark as played is triggered
319-
// removeFromQueue(this@PlaybackService, ended, item)
320-
// Delete episode if enabled
321-
val action = item.feed?.preferences?.currentAutoDelete
322-
val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS
323-
|| (action == AutoDeleteAction.GLOBAL && item.feed != null && shouldAutoDeleteItemsOnFeed(item.feed!!)))
324-
if (playable is EpisodeMedia && shouldAutoDelete && (!item.isFavorite || !shouldFavoriteKeepEpisode())) {
325-
deleteMediaOfEpisode(this@PlaybackService, item)
326-
Logd(TAG, "Episode Deleted")
316+
runOnIOScope {
317+
if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode())) {
318+
Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped")
319+
// only mark the item as played if we're not keeping it anyways
320+
item = setPlayStateSync(Episode.PLAYED, ended || (skipped && smartMarkAsPlayed), item!!)
321+
val action = item?.feed?.preferences?.currentAutoDelete
322+
val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS ||
323+
(action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!)))
324+
if (playable is EpisodeMedia && shouldAutoDelete && (item?.isFavorite != true || !shouldFavoriteKeepEpisode())) {
325+
item = deleteMediaSync(this@PlaybackService, item!!)
326+
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, null, item!!)
327+
}
327328
}
329+
if (playable is EpisodeMedia && (ended || skipped || playingNext)) addToHistory(item!!)
328330
}
329-
if (playable is EpisodeMedia && (ended || skipped || playingNext)) addToHistory(item)
330331
}
331332
}
332333

@@ -425,9 +426,8 @@ class PlaybackService : MediaSessionService() {
425426

426427
fun writeMediaPlaying(playable: Playable?, playerStatus: PlayerStatus) {
427428
Logd(InTheatre.TAG, "Writing playback preferences ${playable?.getIdentifier()}")
428-
if (playable == null) {
429-
writeNoMediaPlaying()
430-
} else {
429+
if (playable == null) writeNoMediaPlaying()
430+
else {
431431
curState.curMediaType = playable.getPlayableType().toLong()
432432
curState.curIsVideo = playable.getMediaType() == MediaType.VIDEO
433433
if (playable is EpisodeMedia) {

app/src/main/kotlin/ac/mdiq/podcini/preferences/ThemeSwitcher.kt

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,9 @@ object ThemeSwitcher {
2424
val dynamic = UserPreferences.isThemeColorTinted
2525
return when (readThemeValue(context)) {
2626
UserPreferences.ThemePreference.DARK -> if (dynamic) R.style.Theme_Podcini_Dynamic_Dark_NoTitle else R.style.Theme_Podcini_Dark_NoTitle
27-
UserPreferences.ThemePreference.BLACK -> if (dynamic) R.style.Theme_Podcini_Dynamic_TrueBlack_NoTitle
28-
else R.style.Theme_Podcini_TrueBlack_NoTitle
29-
UserPreferences.ThemePreference.LIGHT -> if (dynamic) R.style.Theme_Podcini_Dynamic_Light_NoTitle
30-
else R.style.Theme_Podcini_Light_NoTitle
31-
else -> if (dynamic) R.style.Theme_Podcini_Dynamic_Light_NoTitle
32-
else R.style.Theme_Podcini_Light_NoTitle
27+
UserPreferences.ThemePreference.BLACK -> if (dynamic) R.style.Theme_Podcini_Dynamic_TrueBlack_NoTitle else R.style.Theme_Podcini_TrueBlack_NoTitle
28+
UserPreferences.ThemePreference.LIGHT -> if (dynamic) R.style.Theme_Podcini_Dynamic_Light_NoTitle else R.style.Theme_Podcini_Light_NoTitle
29+
else -> if (dynamic) R.style.Theme_Podcini_Dynamic_Light_NoTitle else R.style.Theme_Podcini_Light_NoTitle
3330
}
3431
}
3532

@@ -38,14 +35,10 @@ object ThemeSwitcher {
3835
fun getTranslucentTheme(context: Context): Int {
3936
val dynamic = UserPreferences.isThemeColorTinted
4037
return when (readThemeValue(context)) {
41-
UserPreferences.ThemePreference.DARK -> if (dynamic) R.style.Theme_Podcini_Dynamic_Dark_Translucent
42-
else R.style.Theme_Podcini_Dark_Translucent
43-
UserPreferences.ThemePreference.BLACK -> if (dynamic) R.style.Theme_Podcini_Dynamic_TrueBlack_Translucent
44-
else R.style.Theme_Podcini_TrueBlack_Translucent
45-
UserPreferences.ThemePreference.LIGHT -> if (dynamic) R.style.Theme_Podcini_Dynamic_Light_Translucent
46-
else R.style.Theme_Podcini_Light_Translucent
47-
else -> if (dynamic) R.style.Theme_Podcini_Dynamic_Light_Translucent
48-
else R.style.Theme_Podcini_Light_Translucent
38+
UserPreferences.ThemePreference.DARK -> if (dynamic) R.style.Theme_Podcini_Dynamic_Dark_Translucent else R.style.Theme_Podcini_Dark_Translucent
39+
UserPreferences.ThemePreference.BLACK -> if (dynamic) R.style.Theme_Podcini_Dynamic_TrueBlack_Translucent else R.style.Theme_Podcini_TrueBlack_Translucent
40+
UserPreferences.ThemePreference.LIGHT -> if (dynamic) R.style.Theme_Podcini_Dynamic_Light_Translucent else R.style.Theme_Podcini_Light_Translucent
41+
else -> if (dynamic) R.style.Theme_Podcini_Dynamic_Light_Translucent else R.style.Theme_Podcini_Light_Translucent
4942
}
5043
}
5144

0 commit comments

Comments
 (0)