mirror of
https://github.com/rebelonion/Dantotsu.git
synced 2026-01-20 22:23:56 +00:00
Addons (#368)
* feat: (wip) torrent credit to kuukiyomi * fix: extensions -> addons * fix: unified loader * feat: (wip) modularity * fix: addon ui * feat: addon install/uninstall --------- Co-authored-by: aayush262 <aayushthakur262006@gmail.com>
This commit is contained in:
@@ -39,6 +39,12 @@ object Notifications {
|
||||
const val GROUP_NEW_CHAPTERS = "eu.kanade.tachiyomi.NEW_CHAPTERS"
|
||||
const val GROUP_NEW_EPISODES = "eu.kanade.tachiyomi.NEW_EPISODES"
|
||||
|
||||
/**
|
||||
* Notification channel and ids used by the torrent server.
|
||||
*/
|
||||
const val ID_TORRENT_SERVER = -1100
|
||||
const val CHANNEL_TORRENT_SERVER = "dantotsu_torrent_server"
|
||||
|
||||
/**
|
||||
* Notification channel used for Incognito Mode
|
||||
*/
|
||||
@@ -154,6 +160,9 @@ object Notifications {
|
||||
buildNotificationChannel(CHANNEL_INCOGNITO_MODE, IMPORTANCE_LOW) {
|
||||
setName("Incognito Mode")
|
||||
},
|
||||
buildNotificationChannel(CHANNEL_TORRENT_SERVER, IMPORTANCE_LOW) {
|
||||
setName("Torrent Server")
|
||||
},
|
||||
buildNotificationChannel(CHANNEL_COMMENTS, IMPORTANCE_HIGH) {
|
||||
setName("Comments")
|
||||
setGroup(GROUP_COMMENTS)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package eu.kanade.tachiyomi.data.torrentServer.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Torrent(
|
||||
var title: String,
|
||||
var poster: String? = null,
|
||||
var data: String? = null,
|
||||
var timestamp: Long? = null,
|
||||
var name: String? = null,
|
||||
var hash: String? = null,
|
||||
var stat: Int? = null,
|
||||
var stat_string: String? = null,
|
||||
var loaded_size: Long? = null,
|
||||
var torrent_size: Long? = null,
|
||||
var preloaded_bytes: Long? = null,
|
||||
var preload_size: Long? = null,
|
||||
var download_speed: Double? = null,
|
||||
var upload_speed: Double? = null,
|
||||
var total_peers: Int? = null,
|
||||
var pending_peers: Int? = null,
|
||||
var active_peers: Int? = null,
|
||||
var connected_seeders: Int? = null,
|
||||
var half_open_peers: Int? = null,
|
||||
var bytes_written: Long? = null,
|
||||
var bytes_written_data: Long? = null,
|
||||
var bytes_read: Long? = null,
|
||||
var bytes_read_data: Long? = null,
|
||||
var bytes_read_useful_data: Long? = null,
|
||||
var chunks_written: Long? = null,
|
||||
var chunks_read: Long? = null,
|
||||
var chunks_read_useful: Long? = null,
|
||||
var chunks_read_wasted: Long? = null,
|
||||
var pieces_dirtied_good: Long? = null,
|
||||
var pieces_dirtied_bad: Long? = null,
|
||||
var duration_seconds: Double? = null,
|
||||
var bit_rate: String? = null,
|
||||
var file_stats: List<FileStat>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FileStat(
|
||||
var id: Int? = null,
|
||||
var path: String,
|
||||
var length: Long,
|
||||
)
|
||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.extension.anime
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
@@ -206,7 +207,8 @@ class AnimeExtensionManager(
|
||||
* @param extension The anime extension to be installed.
|
||||
*/
|
||||
fun installExtension(extension: AnimeExtension.Available): Observable<InstallStep> {
|
||||
return installer.downloadAndInstall(api.getAnimeApkUrl(extension), extension)
|
||||
return installer.downloadAndInstall(api.getAnimeApkUrl(extension), extension.pkgName,
|
||||
extension.name, MediaType.ANIME)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,11 @@ import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import ani.dantotsu.addons.download.DownloadAddonManager
|
||||
import ani.dantotsu.addons.torrent.TorrentAddonManager
|
||||
import ani.dantotsu.media.AddonType
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.Type
|
||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.InstallStep
|
||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||
@@ -25,6 +29,8 @@ abstract class Installer(private val service: Service) {
|
||||
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
||||
private val mangaExtensionManager: MangaExtensionManager by injectLazy()
|
||||
private val novelExtensionManager: NovelExtensionManager by injectLazy()
|
||||
private val torrentAddonManager: TorrentAddonManager by injectLazy()
|
||||
private val downloadAddonManager: DownloadAddonManager by injectLazy()
|
||||
|
||||
private var waitingInstall = AtomicReference<Entry>(null)
|
||||
private val queue = Collections.synchronizedList(mutableListOf<Entry>())
|
||||
@@ -49,7 +55,7 @@ abstract class Installer(private val service: Service) {
|
||||
* @param downloadId Download ID as known by [ExtensionManager]
|
||||
* @param uri Uri of APK to install
|
||||
*/
|
||||
fun addToQueue(type: MediaType, downloadId: Long, uri: Uri) {
|
||||
fun addToQueue(type: Type, downloadId: Long, uri: Uri) {
|
||||
queue.add(Entry(type, downloadId, uri))
|
||||
checkQueue()
|
||||
}
|
||||
@@ -63,10 +69,17 @@ abstract class Installer(private val service: Service) {
|
||||
*/
|
||||
@CallSuper
|
||||
open fun processEntry(entry: Entry) {
|
||||
when (entry.type) {
|
||||
MediaType.ANIME -> animeExtensionManager.setInstalling(entry.downloadId)
|
||||
MediaType.MANGA -> mangaExtensionManager.setInstalling(entry.downloadId)
|
||||
MediaType.NOVEL -> novelExtensionManager.setInstalling(entry.downloadId)
|
||||
if (entry.type is MediaType) {
|
||||
when (entry.type) {
|
||||
MediaType.ANIME -> animeExtensionManager.setInstalling(entry.downloadId)
|
||||
MediaType.MANGA -> mangaExtensionManager.setInstalling(entry.downloadId)
|
||||
MediaType.NOVEL -> novelExtensionManager.setInstalling(entry.downloadId)
|
||||
}
|
||||
} else {
|
||||
when (entry.type) {
|
||||
AddonType.TORRENT -> torrentAddonManager.setInstalling(entry.downloadId)
|
||||
AddonType.DOWNLOAD -> downloadAddonManager.setInstalling(entry.downloadId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,17 +103,34 @@ abstract class Installer(private val service: Service) {
|
||||
fun continueQueue(resultStep: InstallStep) {
|
||||
val completedEntry = waitingInstall.getAndSet(null)
|
||||
if (completedEntry != null) {
|
||||
when (completedEntry.type) {
|
||||
MediaType.ANIME -> {
|
||||
animeExtensionManager.updateInstallStep(completedEntry.downloadId, resultStep)
|
||||
}
|
||||
if (completedEntry.type is MediaType) {
|
||||
when (completedEntry.type) {
|
||||
MediaType.ANIME -> animeExtensionManager.updateInstallStep(
|
||||
completedEntry.downloadId,
|
||||
resultStep
|
||||
)
|
||||
|
||||
MediaType.MANGA -> {
|
||||
mangaExtensionManager.updateInstallStep(completedEntry.downloadId, resultStep)
|
||||
}
|
||||
MediaType.MANGA -> mangaExtensionManager.updateInstallStep(
|
||||
completedEntry.downloadId,
|
||||
resultStep
|
||||
)
|
||||
|
||||
MediaType.NOVEL -> {
|
||||
novelExtensionManager.updateInstallStep(completedEntry.downloadId, resultStep)
|
||||
MediaType.NOVEL -> novelExtensionManager.updateInstallStep(
|
||||
completedEntry.downloadId,
|
||||
resultStep
|
||||
)
|
||||
}
|
||||
} else {
|
||||
when (completedEntry.type) {
|
||||
AddonType.TORRENT -> torrentAddonManager.updateInstallStep(
|
||||
completedEntry.downloadId,
|
||||
resultStep
|
||||
)
|
||||
|
||||
AddonType.DOWNLOAD -> downloadAddonManager.updateInstallStep(
|
||||
completedEntry.downloadId,
|
||||
resultStep
|
||||
)
|
||||
}
|
||||
}
|
||||
checkQueue()
|
||||
@@ -113,7 +143,7 @@ abstract class Installer(private val service: Service) {
|
||||
*
|
||||
* @see ready
|
||||
*/
|
||||
fun checkQueue() {
|
||||
private fun checkQueue() {
|
||||
if (!ready) {
|
||||
return
|
||||
}
|
||||
@@ -135,15 +165,35 @@ abstract class Installer(private val service: Service) {
|
||||
open fun onDestroy() {
|
||||
LocalBroadcastManager.getInstance(service).unregisterReceiver(cancelReceiver)
|
||||
queue.forEach {
|
||||
when (it.type) {
|
||||
MediaType.ANIME -> {
|
||||
animeExtensionManager.updateInstallStep(it.downloadId, InstallStep.Error)
|
||||
|
||||
if (it.type is MediaType) {
|
||||
when (it.type) {
|
||||
MediaType.ANIME -> animeExtensionManager.updateInstallStep(
|
||||
it.downloadId,
|
||||
InstallStep.Error
|
||||
)
|
||||
|
||||
MediaType.MANGA -> mangaExtensionManager.updateInstallStep(
|
||||
it.downloadId,
|
||||
InstallStep.Error
|
||||
)
|
||||
|
||||
MediaType.NOVEL -> novelExtensionManager.updateInstallStep(
|
||||
it.downloadId,
|
||||
InstallStep.Error
|
||||
)
|
||||
}
|
||||
MediaType.MANGA -> {
|
||||
mangaExtensionManager.updateInstallStep(it.downloadId, InstallStep.Error)
|
||||
}
|
||||
MediaType.NOVEL -> {
|
||||
novelExtensionManager.updateInstallStep(it.downloadId, InstallStep.Error)
|
||||
} else {
|
||||
when (it.type) {
|
||||
AddonType.TORRENT -> torrentAddonManager.updateInstallStep(
|
||||
it.downloadId,
|
||||
InstallStep.Error
|
||||
)
|
||||
|
||||
AddonType.DOWNLOAD -> downloadAddonManager.updateInstallStep(
|
||||
it.downloadId,
|
||||
InstallStep.Error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,15 +218,34 @@ abstract class Installer(private val service: Service) {
|
||||
this.waitingInstall.set(null)
|
||||
checkQueue()
|
||||
}
|
||||
when (toCancel.type) {
|
||||
MediaType.ANIME -> {
|
||||
animeExtensionManager.updateInstallStep(downloadId, InstallStep.Idle)
|
||||
if (toCancel.type is MediaType) {
|
||||
when (toCancel.type) {
|
||||
MediaType.ANIME -> animeExtensionManager.updateInstallStep(
|
||||
downloadId,
|
||||
InstallStep.Idle
|
||||
)
|
||||
|
||||
MediaType.MANGA -> mangaExtensionManager.updateInstallStep(
|
||||
downloadId,
|
||||
InstallStep.Idle
|
||||
)
|
||||
|
||||
MediaType.NOVEL -> novelExtensionManager.updateInstallStep(
|
||||
downloadId,
|
||||
InstallStep.Idle
|
||||
)
|
||||
}
|
||||
MediaType.MANGA -> {
|
||||
mangaExtensionManager.updateInstallStep(downloadId, InstallStep.Idle)
|
||||
}
|
||||
MediaType.NOVEL -> {
|
||||
novelExtensionManager.updateInstallStep(downloadId, InstallStep.Idle)
|
||||
} else {
|
||||
when (toCancel.type) {
|
||||
AddonType.TORRENT -> torrentAddonManager.updateInstallStep(
|
||||
downloadId,
|
||||
InstallStep.Idle
|
||||
)
|
||||
|
||||
AddonType.DOWNLOAD -> downloadAddonManager.updateInstallStep(
|
||||
downloadId,
|
||||
InstallStep.Idle
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +257,7 @@ abstract class Installer(private val service: Service) {
|
||||
* @param downloadId Download ID as known by [ExtensionManager]
|
||||
* @param uri Uri of APK to install
|
||||
*/
|
||||
data class Entry(val type: MediaType, val downloadId: Long, val uri: Uri)
|
||||
data class Entry(val type: Type, val downloadId: Long, val uri: Uri)
|
||||
|
||||
init {
|
||||
val filter = IntentFilter(ACTION_CANCEL_QUEUE)
|
||||
|
||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.extension.manga
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
@@ -203,7 +204,8 @@ class MangaExtensionManager(
|
||||
* @param extension The extension to be installed.
|
||||
*/
|
||||
fun installExtension(extension: MangaExtension.Available): Observable<InstallStep> {
|
||||
return installer.downloadAndInstall(api.getMangaApkUrl(extension), extension)
|
||||
return installer.downloadAndInstall(api.getMangaApkUrl(extension), extension.pkgName,
|
||||
extension.name, MediaType.MANGA)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,9 @@ import android.os.Bundle
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import ani.dantotsu.addons.download.DownloadAddonManager
|
||||
import ani.dantotsu.addons.torrent.TorrentAddonManager
|
||||
import ani.dantotsu.media.AddonType
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||
import ani.dantotsu.themes.ThemeManager
|
||||
@@ -29,7 +32,8 @@ class ExtensionInstallActivity : AppCompatActivity() {
|
||||
private var ignoreResult = false
|
||||
private var hasIgnoredResult = false
|
||||
|
||||
private var type: MediaType? = null
|
||||
private var mediaType: MediaType? = null
|
||||
private var addonType: AddonType? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -37,7 +41,9 @@ class ExtensionInstallActivity : AppCompatActivity() {
|
||||
ThemeManager(this).applyTheme()
|
||||
|
||||
if (intent.hasExtra(ExtensionInstaller.EXTRA_EXTENSION_TYPE))
|
||||
type = intent.getSerializableExtraCompat<MediaType>(ExtensionInstaller.EXTRA_EXTENSION_TYPE)
|
||||
mediaType = intent.getSerializableExtraCompat<MediaType>(ExtensionInstaller.EXTRA_EXTENSION_TYPE)
|
||||
if (intent.hasExtra(ExtensionInstaller.EXTRA_ADDON_TYPE))
|
||||
addonType = intent.getSerializableExtraCompat<AddonType>(ExtensionInstaller.EXTRA_ADDON_TYPE)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
@@ -85,17 +91,34 @@ class ExtensionInstallActivity : AppCompatActivity() {
|
||||
RESULT_CANCELED -> InstallStep.Idle
|
||||
else -> InstallStep.Error
|
||||
}
|
||||
when (type) {
|
||||
MediaType.ANIME -> {
|
||||
Injekt.get<AnimeExtensionManager>().updateInstallStep(downloadId, newStep)
|
||||
if (mediaType != null) {
|
||||
when (mediaType) {
|
||||
MediaType.ANIME -> {
|
||||
Injekt.get<AnimeExtensionManager>().updateInstallStep(downloadId, newStep)
|
||||
}
|
||||
|
||||
MediaType.MANGA -> {
|
||||
Injekt.get<MangaExtensionManager>().updateInstallStep(downloadId, newStep)
|
||||
}
|
||||
|
||||
MediaType.NOVEL -> {
|
||||
Injekt.get<NovelExtensionManager>().updateInstallStep(downloadId, newStep)
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
MediaType.MANGA -> {
|
||||
Injekt.get<MangaExtensionManager>().updateInstallStep(downloadId, newStep)
|
||||
} else {
|
||||
when (addonType) {
|
||||
AddonType.TORRENT -> {
|
||||
Injekt.get<TorrentAddonManager>().updateInstallStep(downloadId, newStep)
|
||||
}
|
||||
|
||||
AddonType.DOWNLOAD -> {
|
||||
Injekt.get<DownloadAddonManager>().updateInstallStep(downloadId, newStep)
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
MediaType.NOVEL -> {
|
||||
Injekt.get<NovelExtensionManager>().updateInstallStep(downloadId, newStep)
|
||||
}
|
||||
null -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,18 +50,6 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the intent filter this receiver should subscribe to.
|
||||
*/
|
||||
private val filter
|
||||
get() = IntentFilter().apply {
|
||||
priority = 100
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REPLACED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addDataScheme("package")
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the events of the [filter] is received. When the package is an extension,
|
||||
* it's loaded in background and it notifies the [listener] when finished.
|
||||
@@ -136,21 +124,13 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this package is performing an update.
|
||||
*
|
||||
* @param intent The intent that triggered the event.
|
||||
*/
|
||||
private fun isReplacing(intent: Intent): Boolean {
|
||||
return intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension triggered by the given intent.
|
||||
*
|
||||
* @param context The application context.
|
||||
* @param intent The intent containing the package name of the extension.
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
private suspend fun getAnimeExtensionFromIntent(context: Context, intent: Intent?): AnimeLoadResult {
|
||||
val pkgName = getPackageNameFromIntent(intent)
|
||||
if (pkgName == null) {
|
||||
@@ -180,12 +160,6 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
|
||||
}.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name of the installed, updated or removed application.
|
||||
*/
|
||||
private fun getPackageNameFromIntent(intent: Intent?): String? {
|
||||
return intent?.data?.encodedSchemeSpecificPart ?: return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener that receives extension installation events.
|
||||
@@ -203,4 +177,36 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
|
||||
fun onExtensionUntrusted(extension: MangaExtension.Untrusted)
|
||||
fun onPackageUninstalled(pkgName: String)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Returns the intent filter this receiver should subscribe to.
|
||||
*/
|
||||
val filter
|
||||
get() = IntentFilter().apply {
|
||||
priority = 100
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REPLACED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addDataScheme("package")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this package is performing an update.
|
||||
*
|
||||
* @param intent The intent that triggered the event.
|
||||
*/
|
||||
fun isReplacing(intent: Intent): Boolean {
|
||||
return intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the package name of the installed, updated or removed application.
|
||||
*/
|
||||
fun getPackageNameFromIntent(intent: Intent?): String? {
|
||||
return intent?.data?.encodedSchemeSpecificPart ?: return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.media.AddonType
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.Type
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
@@ -45,12 +47,13 @@ class ExtensionInstallService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val uri = intent?.data
|
||||
val type = intent?.getSerializableExtraCompat<MediaType>(EXTRA_EXTENSION_TYPE)
|
||||
val mediaType = intent?.getSerializableExtraCompat<MediaType>(EXTRA_EXTENSION_TYPE)
|
||||
val addonType = intent?.getSerializableExtraCompat<AddonType>(ExtensionInstaller.EXTRA_ADDON_TYPE)
|
||||
val id = intent?.getLongExtra(EXTRA_DOWNLOAD_ID, -1)?.takeIf { it != -1L }
|
||||
val installerUsed = intent?.getSerializableExtraCompat<BasePreferences.ExtensionInstaller>(
|
||||
EXTRA_INSTALLER
|
||||
)
|
||||
if (uri == null || type == null || id == null || installerUsed == null) {
|
||||
if (uri == null || (mediaType == null && addonType == null) || id == null || installerUsed == null) {
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
@@ -68,7 +71,7 @@ class ExtensionInstallService : Service() {
|
||||
}
|
||||
}
|
||||
}
|
||||
installer!!.addToQueue(type, id, uri)
|
||||
installer!!.addToQueue(mediaType ?: addonType!!, id, uri)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
@@ -84,16 +87,21 @@ class ExtensionInstallService : Service() {
|
||||
|
||||
fun getIntent(
|
||||
context: Context,
|
||||
type: MediaType,
|
||||
type: Type,
|
||||
downloadId: Long,
|
||||
uri: Uri,
|
||||
installer: BasePreferences.ExtensionInstaller,
|
||||
): Intent {
|
||||
return Intent(context, ExtensionInstallService::class.java)
|
||||
val intent = Intent(context, ExtensionInstallService::class.java)
|
||||
.setDataAndType(uri, ExtensionInstaller.APK_MIME)
|
||||
.putExtra(EXTRA_DOWNLOAD_ID, downloadId)
|
||||
.putExtra(EXTRA_EXTENSION_TYPE, type)
|
||||
.putExtra(EXTRA_INSTALLER, installer)
|
||||
if (type is MediaType) {
|
||||
intent.putExtra(EXTRA_EXTENSION_TYPE, type)
|
||||
} else if (type is AddonType) {
|
||||
intent.putExtra(ExtensionInstaller.EXTRA_ADDON_TYPE, type)
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import android.os.Environment
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
import ani.dantotsu.media.AddonType
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.Type
|
||||
import ani.dantotsu.parsers.novel.NovelExtension
|
||||
import ani.dantotsu.util.Logger
|
||||
import com.jakewharton.rxrelay.PublishRelay
|
||||
@@ -33,7 +35,7 @@ import java.util.concurrent.TimeUnit
|
||||
*
|
||||
* @param context The application context.
|
||||
*/
|
||||
internal class ExtensionInstaller(private val context: Context) {
|
||||
class ExtensionInstaller(private val context: Context) {
|
||||
|
||||
/**
|
||||
* The system's download manager
|
||||
@@ -65,27 +67,24 @@ internal class ExtensionInstaller(private val context: Context) {
|
||||
* @param url The url of the apk.
|
||||
* @param extension The extension to install.
|
||||
*/
|
||||
fun downloadAndInstall(url: String, extension: AnimeExtension): Observable<InstallStep> = Observable.defer {
|
||||
val pkgName = extension.pkgName
|
||||
|
||||
fun <T : Type> downloadAndInstall(url: String, pkgName: String, name: String, type: T): Observable<InstallStep> = Observable.defer {
|
||||
val oldDownload = activeDownloads[pkgName]
|
||||
if (oldDownload != null) {
|
||||
deleteDownload(pkgName)
|
||||
}
|
||||
|
||||
// Register the receiver after removing (and unregistering) the previous download
|
||||
downloadReceiver.register()
|
||||
|
||||
val downloadUri = url.toUri()
|
||||
val request = DownloadManager.Request(downloadUri)
|
||||
.setTitle(extension.name)
|
||||
.setTitle(name)
|
||||
.setMimeType(APK_MIME)
|
||||
.setDestinationInExternalFilesDir(
|
||||
context,
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
downloadUri.lastPathSegment
|
||||
)
|
||||
.setDescription(MediaType.ANIME.asText())
|
||||
.setDescription(type.asText())
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
|
||||
val id = downloadManager.enqueue(request)
|
||||
@@ -93,91 +92,12 @@ internal class ExtensionInstaller(private val context: Context) {
|
||||
|
||||
downloadsRelay.filter { it.first == id }
|
||||
.map { it.second }
|
||||
// Poll download status
|
||||
.mergeWith(pollStatus(id))
|
||||
// Stop when the application is installed or errors
|
||||
.takeUntil { it.isCompleted() }
|
||||
// Always notify on main thread
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Always remove the download when unsubscribed
|
||||
.doOnUnsubscribe { deleteDownload(pkgName) }
|
||||
}
|
||||
|
||||
fun downloadAndInstall(url: String, extension: MangaExtension): Observable<InstallStep> = Observable.defer {
|
||||
val pkgName = extension.pkgName
|
||||
|
||||
val oldDownload = activeDownloads[pkgName]
|
||||
if (oldDownload != null) {
|
||||
deleteDownload(pkgName)
|
||||
}
|
||||
|
||||
// Register the receiver after removing (and unregistering) the previous download
|
||||
downloadReceiver.register()
|
||||
|
||||
val downloadUri = url.toUri()
|
||||
val request = DownloadManager.Request(downloadUri)
|
||||
.setTitle(extension.name)
|
||||
.setMimeType(APK_MIME)
|
||||
.setDestinationInExternalFilesDir(
|
||||
context,
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
downloadUri.lastPathSegment
|
||||
)
|
||||
.setDescription(MediaType.MANGA.asText())
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
|
||||
val id = downloadManager.enqueue(request)
|
||||
activeDownloads[pkgName] = id
|
||||
|
||||
downloadsRelay.filter { it.first == id }
|
||||
.map { it.second }
|
||||
// Poll download status
|
||||
.mergeWith(pollStatus(id))
|
||||
// Stop when the application is installed or errors
|
||||
.takeUntil { it.isCompleted() }
|
||||
// Always notify on main thread
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Always remove the download when unsubscribed
|
||||
.doOnUnsubscribe { deleteDownload(pkgName) }
|
||||
}
|
||||
|
||||
fun downloadAndInstall(url: String, extension: NovelExtension) = Observable.defer {
|
||||
val pkgName = extension.pkgName
|
||||
|
||||
val oldDownload = activeDownloads[pkgName]
|
||||
if (oldDownload != null) {
|
||||
deleteDownload(pkgName)
|
||||
}
|
||||
|
||||
// Register the receiver after removing (and unregistering) the previous download
|
||||
downloadReceiver.register()
|
||||
|
||||
val downloadUri = url.toUri()
|
||||
val request = DownloadManager.Request(downloadUri)
|
||||
.setTitle(extension.name)
|
||||
.setMimeType(APK_MIME)
|
||||
.setDestinationInExternalFilesDir(
|
||||
context,
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
downloadUri.lastPathSegment
|
||||
)
|
||||
.setDescription(MediaType.MANGA.asText())
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
|
||||
val id = downloadManager.enqueue(request)
|
||||
activeDownloads[pkgName] = id
|
||||
|
||||
downloadsRelay.filter { it.first == id }
|
||||
.map { it.second }
|
||||
// Poll download status
|
||||
.mergeWith(pollStatus(id))
|
||||
// Stop when the application is installed or errors
|
||||
.takeUntil { it.isCompleted() }
|
||||
// Always notify on main thread
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Always remove the download when unsubscribed
|
||||
.doOnUnsubscribe { deleteDownload(pkgName) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable that polls the given download id for its status every second, as the
|
||||
@@ -215,14 +135,18 @@ internal class ExtensionInstaller(private val context: Context) {
|
||||
*
|
||||
* @param uri The uri of the extension to install.
|
||||
*/
|
||||
fun installApk(type: MediaType, downloadId: Long, uri: Uri) {
|
||||
fun installApk(type: Type, downloadId: Long, uri: Uri) {
|
||||
when (val installer = extensionInstaller.get()) {
|
||||
BasePreferences.ExtensionInstaller.LEGACY -> {
|
||||
val intent = Intent(context, ExtensionInstallActivity::class.java)
|
||||
.setDataAndType(uri, APK_MIME)
|
||||
.putExtra(EXTRA_EXTENSION_TYPE, type)
|
||||
.putExtra(EXTRA_DOWNLOAD_ID, downloadId)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
if (type is MediaType) {
|
||||
intent.putExtra(EXTRA_EXTENSION_TYPE, type)
|
||||
} else if (type is AddonType) {
|
||||
intent.putExtra(EXTRA_ADDON_TYPE, type)
|
||||
}
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
@@ -342,7 +266,9 @@ internal class ExtensionInstaller(private val context: Context) {
|
||||
).removePrefix(FILE_SCHEME)
|
||||
val type = MediaType.fromText(cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_DESCRIPTION),
|
||||
))
|
||||
)) ?: AddonType.fromText(cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_DESCRIPTION),
|
||||
)) ?: return
|
||||
|
||||
installApk(type, id, File(localUri).getUriCompat(context))
|
||||
}
|
||||
@@ -354,6 +280,7 @@ internal class ExtensionInstaller(private val context: Context) {
|
||||
const val APK_MIME = "application/vnd.android.package-archive"
|
||||
const val EXTRA_DOWNLOAD_ID = "ExtensionInstaller.extra.DOWNLOAD_ID"
|
||||
const val EXTRA_EXTENSION_TYPE = "ExtensionInstaller.extra.EXTENSION_TYPE"
|
||||
const val EXTRA_ADDON_TYPE = "ExtensionInstaller.extra.ADDON_TYPE"
|
||||
const val FILE_SCHEME = "file://"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ internal object ExtensionLoader {
|
||||
const val MANGA_LIB_VERSION_MIN = 1.2
|
||||
const val MANGA_LIB_VERSION_MAX = 1.5
|
||||
|
||||
private val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or
|
||||
val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or
|
||||
PackageManager.GET_META_DATA or
|
||||
@Suppress ("DEPRECATION") PackageManager.GET_SIGNATURES or
|
||||
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||
|
||||
Reference in New Issue
Block a user