2017-01-20 21:24:31 +01:00
|
|
|
package eu.kanade.tachiyomi.source
|
|
|
|
|
|
|
|
import android.Manifest.permission.READ_EXTERNAL_STORAGE
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.pm.ApplicationInfo
|
|
|
|
import android.content.pm.PackageManager
|
|
|
|
import android.os.Environment
|
|
|
|
import dalvik.system.PathClassLoader
|
|
|
|
import eu.kanade.tachiyomi.R
|
2017-01-20 21:27:53 +01:00
|
|
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
|
|
import eu.kanade.tachiyomi.source.online.YamlHttpSource
|
2017-01-20 21:24:31 +01:00
|
|
|
import eu.kanade.tachiyomi.source.online.english.*
|
|
|
|
import eu.kanade.tachiyomi.source.online.german.WieManga
|
|
|
|
import eu.kanade.tachiyomi.source.online.russian.Mangachan
|
|
|
|
import eu.kanade.tachiyomi.source.online.russian.Mintmanga
|
|
|
|
import eu.kanade.tachiyomi.source.online.russian.Readmanga
|
|
|
|
import eu.kanade.tachiyomi.util.hasPermission
|
|
|
|
import org.yaml.snakeyaml.Yaml
|
|
|
|
import timber.log.Timber
|
|
|
|
import java.io.File
|
|
|
|
|
|
|
|
open class SourceManager(private val context: Context) {
|
|
|
|
|
|
|
|
private val sourcesMap = mutableMapOf<Long, Source>()
|
|
|
|
|
|
|
|
init {
|
|
|
|
createSources()
|
|
|
|
}
|
|
|
|
|
|
|
|
open fun get(sourceKey: Long): Source? {
|
|
|
|
return sourcesMap[sourceKey]
|
|
|
|
}
|
|
|
|
|
2017-01-20 21:27:53 +01:00
|
|
|
fun getOnlineSources() = sourcesMap.values.filterIsInstance<HttpSource>()
|
2017-01-20 21:24:31 +01:00
|
|
|
|
|
|
|
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>()
|
|
|
|
|
|
|
|
private fun createSources() {
|
|
|
|
createExtensionSources().forEach { registerSource(it) }
|
|
|
|
createYamlSources().forEach { registerSource(it) }
|
|
|
|
createInternalSources().forEach { registerSource(it) }
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun registerSource(source: Source, overwrite: Boolean = false) {
|
|
|
|
if (overwrite || !sourcesMap.containsKey(source.id)) {
|
|
|
|
sourcesMap.put(source.id, source)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun createInternalSources(): List<Source> = listOf(
|
|
|
|
Batoto(),
|
|
|
|
Mangahere(),
|
|
|
|
Mangafox(),
|
|
|
|
Kissmanga(),
|
|
|
|
Readmanga(),
|
|
|
|
Mintmanga(),
|
|
|
|
Mangachan(),
|
|
|
|
Readmangatoday(),
|
|
|
|
Mangasee(),
|
|
|
|
WieManga()
|
|
|
|
)
|
|
|
|
|
|
|
|
private fun createYamlSources(): List<Source> {
|
|
|
|
val sources = mutableListOf<Source>()
|
|
|
|
|
|
|
|
val parsersDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
|
|
|
File.separator + context.getString(R.string.app_name), "parsers")
|
|
|
|
|
|
|
|
if (parsersDir.exists() && context.hasPermission(READ_EXTERNAL_STORAGE)) {
|
|
|
|
val yaml = Yaml()
|
|
|
|
for (file in parsersDir.listFiles().filter { it.extension == "yml" }) {
|
|
|
|
try {
|
|
|
|
val map = file.inputStream().use { yaml.loadAs(it, Map::class.java) }
|
2017-01-20 21:27:53 +01:00
|
|
|
sources.add(YamlHttpSource(map))
|
2017-01-20 21:24:31 +01:00
|
|
|
} catch (e: Exception) {
|
|
|
|
Timber.e("Error loading source from file. Bad format?")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sources
|
|
|
|
}
|
|
|
|
|
2017-01-21 17:49:34 +01:00
|
|
|
private fun createExtensionSources(): List<Source> {
|
2017-01-20 21:24:31 +01:00
|
|
|
val pkgManager = context.packageManager
|
|
|
|
val flags = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
|
|
|
val installedPkgs = pkgManager.getInstalledPackages(flags)
|
2017-01-21 17:49:34 +01:00
|
|
|
val extPkgs = installedPkgs.filter { it.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE } }
|
2017-01-20 21:24:31 +01:00
|
|
|
|
2017-01-21 17:49:34 +01:00
|
|
|
val sources = mutableListOf<Source>()
|
2017-01-20 21:24:31 +01:00
|
|
|
for (pkgInfo in extPkgs) {
|
|
|
|
val appInfo = pkgManager.getApplicationInfo(pkgInfo.packageName,
|
|
|
|
PackageManager.GET_META_DATA) ?: continue
|
|
|
|
|
2017-01-21 17:49:34 +01:00
|
|
|
val extName = pkgManager.getApplicationLabel(appInfo).toString()
|
|
|
|
.substringAfter("Tachiyomi: ")
|
|
|
|
val version = pkgInfo.versionName
|
|
|
|
var sourceClass = appInfo.metaData.getString(METADATA_SOURCE_CLASS)
|
|
|
|
if (sourceClass.startsWith(".")) {
|
|
|
|
sourceClass = pkgInfo.packageName + sourceClass
|
2017-01-20 21:24:31 +01:00
|
|
|
}
|
|
|
|
|
2017-01-21 17:49:34 +01:00
|
|
|
val extension = Extension(extName, appInfo, version, sourceClass)
|
|
|
|
try {
|
|
|
|
val instance = loadExtension(extension)
|
|
|
|
sources.add(instance)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
Timber.e("Extension load error: $extName. Reason: ${e.message}")
|
|
|
|
} catch (e: LinkageError) {
|
|
|
|
Timber.e("Extension load error: $extName. Reason: ${e.message}")
|
2017-01-20 21:24:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return sources
|
|
|
|
}
|
|
|
|
|
2017-01-21 17:49:34 +01:00
|
|
|
private fun loadExtension(ext: Extension): Source {
|
|
|
|
// Validate lib version
|
|
|
|
val majorLibVersion = ext.version.substringBefore('.').toInt()
|
|
|
|
if (majorLibVersion < LIB_VERSION_MIN || majorLibVersion > LIB_VERSION_MAX) {
|
|
|
|
throw Exception("Lib version is $majorLibVersion, while only versions "
|
2017-01-20 21:24:31 +01:00
|
|
|
+ "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed")
|
|
|
|
}
|
|
|
|
|
2017-01-21 17:49:34 +01:00
|
|
|
val classLoader = PathClassLoader(ext.appInfo.sourceDir, null, context.classLoader)
|
|
|
|
return Class.forName(ext.sourceClass, false, classLoader).newInstance() as Source
|
2017-01-20 21:24:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class Extension(val name: String,
|
|
|
|
val appInfo: ApplicationInfo,
|
2017-01-21 17:49:34 +01:00
|
|
|
val version: String,
|
2017-01-20 21:24:31 +01:00
|
|
|
val sourceClass: String)
|
|
|
|
|
|
|
|
private companion object {
|
2017-01-21 17:49:34 +01:00
|
|
|
const val EXTENSION_FEATURE = "tachiyomi.extension"
|
|
|
|
const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
2017-01-20 21:24:31 +01:00
|
|
|
const val LIB_VERSION_MIN = 1
|
|
|
|
const val LIB_VERSION_MAX = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|