Skip to content

Commit

Permalink
7.2.0 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed Jan 2, 2025
1 parent 6c03710 commit 37de762
Show file tree
Hide file tree
Showing 87 changed files with 1,649 additions and 2,252 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ Note, if you already have subscriptions in Podcini, importing the OPML file or t

* Auto download algorithm is changed to individual feed based.
* When auto download is enabled in the Settings, feeds to be auto-downloaded need to be separately enabled in the feed settings.
* Each feed also has its own download policy (only new episodes, newest episodes, oldest episodes or episodes marked as Soon. "newest episodes" meaning most recent episodes, new or old)
* Each feed also has its own download policy (Only new (with or without Replace), Newest, Oldest or Marked as Soon.
* Newest meaning most recent episodes, With Replace, new episodes will be downloaded and older downloaded episodes deleted
* Each feed has its own limit (Episode cache) for number of episodes downloaded, this limit rules in combination of the overall limit for the app.
* Auto downloads run after feed updates, scheduled or manual
* Auto download always includes any undownloaded episodes (regardless of feeds) added in the Default queue
Expand Down Expand Up @@ -239,6 +240,11 @@ Note, if you already have subscriptions in Podcini, importing the OPML file or t
* then Preferences and DB are backed up in sub-folder named "Podcini-AudoBackups-(date)"
* backup time is on the next resume of Podcini after interval hours from last backup time
* to restore, use Combo restore
* Folder for downloaded media can be customized
* the use of customized folder can be changed or reset
* folder in SD card should also work (Someone try it and let me know as I can't test it)
* upon change, downloaded media files are moved from the previous folder to the new folder
* export and reconcile should also work with customized folder
* Play history/progress can be separately exported/imported as Json files
* Reconsile feature (accessed from Downloads view) is added to ensure downloaded media files are in sync with specs in DB
* Podcasts can be selectively exported from Subscriptions view
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ android {
vectorDrawables.useSupportLibrary false
vectorDrawables.generatedDensities = []

versionCode 3020332
versionName "7.1.1"
versionCode 3020333
versionName "7.2.0"

ndkVersion "27.0.12077973"

Expand Down
9 changes: 6 additions & 3 deletions app/src/main/kotlin/ac/mdiq/podcini/PodciniApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ac.mdiq.podcini
import ac.mdiq.podcini.net.download.VistaDownloaderImpl
import ac.mdiq.podcini.preferences.PreferenceUpgrader
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.preferences.UserPreferences.getPref
import ac.mdiq.podcini.receiver.SPAReceiver
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.ui.activity.SplashActivity
Expand Down Expand Up @@ -81,20 +82,18 @@ class PodciniApp : Application() {
* The receiving single purpose apps will then send their feeds back to Podcini via an
* ACTION_SP_APPS_QUERY_FEEDS_RESPONSE intent.
* This intent will only be sent once.
*
* @return True if an intent was sent, false otherwise (for example if the intent has already been
* sent before.
*/
@Synchronized
fun sendSPAppsQueryFeedsIntent(context: Context): Boolean {
// assert(context != null) { "context = null" }
val appContext = context.applicationContext
if (appContext == null) {
Log.wtf("App", "Unable to get application context")
return false
}
// val prefs = PreferenceManager.getDefaultSharedPreferences(appContext)
if (!appPrefs.getBoolean(PREF_HAS_QUERIED_SP_APPS, false)) {
if (!getPref(PREF_HAS_QUERIED_SP_APPS, false)) {
appContext.sendBroadcast(Intent(SPAReceiver.ACTION_SP_APPS_QUERY_FEEDS))
Logd("App", "Sending SP_APPS_QUERY_FEEDS intent")
val editor = appPrefs.edit()
Expand All @@ -118,6 +117,10 @@ class PodciniApp : Application() {
return singleton
}

fun getAppContext(): Context {
return singleton.applicationContext
}

@JvmStatic
fun forceRestart() {
val intent = Intent(getInstance(), SplashActivity::class.java)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ac.mdiq.podcini.net.download.service

import ac.mdiq.podcini.net.utils.UrlChecker.prepareUrl
import ac.mdiq.podcini.net.utils.NetworkUtils.prepareUrl
import ac.mdiq.podcini.storage.model.Episode

import ac.mdiq.podcini.storage.model.Episode.Companion.FEEDFILETYPE_FEEDMEDIA
Expand Down Expand Up @@ -28,7 +28,8 @@ class DownloadRequest private constructor(
var size: Long = 0
private var statusMsg = 0

private constructor(builder: Builder) : this(builder.destination,
private constructor(builder: Builder) : this(
builder.destination,
builder.source,
builder.title,
builder.feedfileId,
Expand All @@ -40,7 +41,8 @@ class DownloadRequest private constructor(
builder.arguments,
builder.initiatedByUser)

private constructor(inVal: Parcel) : this(inVal.readString(),
private constructor(inVal: Parcel) : this(
inVal.readString(),
inVal.readString(),
inVal.readString(),
inVal.readLong(),
Expand All @@ -52,9 +54,7 @@ class DownloadRequest private constructor(
inVal.readBundle(), // TODO: this may have problem
inVal.readByte() > 0)

override fun describeContents(): Int {
return 0
}
override fun describeContents(): Int = 0

override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(destination)
Expand Down Expand Up @@ -172,17 +172,13 @@ class DownloadRequest private constructor(
this.password = password
return this
}
fun build(): DownloadRequest {
return DownloadRequest(this)
}
fun build(): DownloadRequest = DownloadRequest(this)
}

companion object {
const val REQUEST_ARG_PAGE_NR: String = "page"

private fun nullIfEmpty(str: String?): String? {
return if (str.isNullOrEmpty()) null else str
}
private fun nullIfEmpty(str: String?): String? = if (str.isNullOrEmpty()) null else str

@JvmField
val CREATOR: Parcelable.Creator<DownloadRequest> = object : Parcelable.Creator<DownloadRequest> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ package ac.mdiq.podcini.net.download.service

import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.FilesUtils.feedfilePath
import ac.mdiq.podcini.storage.utils.FilesUtils.findUnusedFile
import ac.mdiq.podcini.storage.utils.FilesUtils.getFeedfileName
import ac.mdiq.podcini.storage.utils.FilesUtils.getMediafilePath
import ac.mdiq.podcini.storage.utils.FilesUtils.getMediafilename
import ac.mdiq.podcini.storage.utils.StorageUtils.feedfilePath
import ac.mdiq.podcini.util.Logd
import android.util.Log
import java.io.File

/**
Expand All @@ -19,7 +16,7 @@ object DownloadRequestCreator {

@JvmStatic
fun create(feed: Feed): DownloadRequest.Builder {
val dest = File(feedfilePath, getFeedfileName(feed))
val dest = File(feedfilePath, feed.getFeedfileName())
if (dest.exists()) dest.delete()
Logd(TAG, "Requesting download feed from url " + feed.downloadUrl)
val username = feed.username
Expand All @@ -30,15 +27,14 @@ object DownloadRequestCreator {
@JvmStatic
fun create(media: Episode): DownloadRequest.Builder {
Logd(TAG, "create: ${media.fileUrl} ${media.title}")
val pdFile = if (media.fileUrl != null) File(media.fileUrl!!) else null
val partiallyDownloadedFileExists = pdFile?.exists() ?: false
var dest: File
dest = if (partiallyDownloadedFileExists) pdFile!! else File(getMediafilePath(media), getMediafilename(media))
if (dest.exists() && !partiallyDownloadedFileExists) dest = findUnusedFile(dest)!!
Logd(TAG, "Requesting download media from url " + media.downloadUrl)
val destUriString = media.getMediaFileUriString() ?: ""
Logd(TAG, "destUriString: $destUriString")
if (destUriString.isEmpty()) {
Log.e(TAG, "destUriString is empty")
}
val feed = media.feed
val username = feed?.username
val password = feed?.password
return DownloadRequest.Builder(dest.toString(), media).withAuthentication(username, password)
return DownloadRequest.Builder(destUriString, media).withAuthentication(username, password)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileEpisodeDownload
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.preferences.UserPreferences.getPref
import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.LogsAndStats
import ac.mdiq.podcini.storage.database.Queues
Expand All @@ -21,6 +22,7 @@ import ac.mdiq.podcini.storage.model.Episode

import ac.mdiq.podcini.storage.model.Episode.MediaMetadataRetrieverCompat
import ac.mdiq.podcini.storage.utils.ChapterUtils
import ac.mdiq.podcini.storage.utils.StorageUtils.ensureMediaFileExists
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
import ac.mdiq.podcini.ui.utils.NotificationUtils
import ac.mdiq.podcini.util.EventFlow
Expand All @@ -32,6 +34,7 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.work.*
Expand Down Expand Up @@ -159,24 +162,29 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
return Futures.immediateFuture(ForegroundInfo(R.id.notification_downloading, generateProgressNotification()))
}
private fun performDownload(media: Episode, request: DownloadRequest): Result {
Logd(TAG, "starting performDownload")
Logd(TAG, "starting performDownload: ${request.destination}")
if (request.destination == null) {
Log.e(TAG, "performDownload request.destination is null")
return Result.failure()
}
val dest = File(request.destination)
if (!dest.exists()) {
try { dest.createNewFile() } catch (e: IOException) { Log.e(TAG, "performDownload Unable to create file") }
}
if (dest.exists()) {
try {
var episode = realm.query(Episode::class).query("id == ${media.id}").first().find()
if (episode != null) {
episode = upsertBlk(episode) { it.setfileUrlOrNull(request.destination) }
EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.updated(episode))
} else Log.e(TAG, "performDownload media.episode is null")
} catch (e: Exception) { Log.e(TAG, "performDownload Exception in writeFileUrl: " + e.message) }
}

// TODO: need to save file name in episode
ensureMediaFileExists(Uri.parse(request.destination))

// val dest = File(request.destination)
// if (!dest.exists()) {
// try { dest.createNewFile() } catch (e: IOException) { Log.e(TAG, "performDownload Unable to create file") }
// }
// if (dest.exists()) {
// try {
// var episode = realm.query(Episode::class).query("id == ${media.id}").first().find()
// if (episode != null) {
// episode = upsertBlk(episode) { it.setfileUrlOrNull(request.destination) }
// EventFlow.postEvent(FlowEvent.EpisodeMediaEvent.updated(episode))
// } else Log.e(TAG, "performDownload media.episode is null")
// } catch (e: Exception) { Log.e(TAG, "performDownload Exception in writeFileUrl: " + e.message) }
// }

downloader = DefaultDownloaderFactory().create(request)
if (downloader == null) {
Log.e(TAG, "performDownload Unable to create downloader")
Expand Down Expand Up @@ -285,7 +293,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
}

class MediaDownloadedHandler(private val context: Context, var updatedStatus: DownloadResult, private val request: DownloadRequest) : Runnable {
override fun run() {
override fun run() {
var item = realm.query(Episode::class).query("id == ${request.feedfileId}").first().find()
if (item == null) {
Log.e(TAG, "Could not find downloaded episode object in database")
Expand All @@ -295,14 +303,18 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
item = upsertBlk(item) {
it.setIsDownloaded()
it.setfileUrlOrNull(request.destination)
Logd(TAG, "run() set request.destination: ${request.destination}")
if (request.destination != null) it.size = File(request.destination).length()
Logd(TAG, "run() set size: ${it.size}")
it.checkEmbeddedPicture(false) // enforce check
if (it.chapters.isEmpty()) it.setChapters(it.loadChaptersFromMediaFile(context))
Logd(TAG, "run() set setChapters: ${it.chapters.size}")
if (it.podcastIndexChapterUrl != null) ChapterUtils.loadChaptersFromUrl(it.podcastIndexChapterUrl!!, false)
Logd(TAG, "run() loaded chapters: ${it.chapters.size}")
var durationStr: String? = null
try {
MediaMetadataRetrieverCompat().use { mmr ->
mmr.setDataSource(it.fileUrl)
mmr.setDataSource(context, Uri.parse(it.fileUrl))
durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
if (durationStr != null) it.duration = (durationStr.toInt())
}
Expand All @@ -311,6 +323,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
Log.e(TAG, "Get duration failed", e)
it.duration = (30000)
}
Logd(TAG, "run() set duration: ${it.duration}")
it.disableAutoDownload()
}
EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(item))
Expand Down Expand Up @@ -347,7 +360,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
.setInitialDelay(0L, TimeUnit.MILLISECONDS)
.addTag(WORK_TAG)
.addTag(WORK_TAG_EPISODE_URL + item.downloadUrl)
if (appPrefs.getBoolean(UserPreferences.Prefs.prefEnqueueDownloaded.name, true)) {
if (getPref(UserPreferences.Prefs.prefEnqueueDownloaded, true)) {
if (item.feed?.queue != null) runBlocking { Queues.addToQueueSync(item, item.feed?.queue) }
workRequest.addTag(WORK_DATA_WAS_QUEUED)
}
Expand Down
Loading

0 comments on commit 37de762

Please sign in to comment.