Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions app/src/main/java/animiru/feature/mpvfiles/MpvConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package animiru.feature.mpvfiles

import android.content.Context
import android.content.res.AssetManager
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.ui.player.settings.AdvancedPlayerPreferences
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
import logcat.LogPriority
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.custombutton.interactor.GetCustomButtons
import tachiyomi.domain.custombutton.model.CustomButton
import tachiyomi.domain.storage.service.StorageManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream

class MpvConfig(
private val context: Context,
private val storageManager: StorageManager = Injekt.get(),
private val advancedPlayerPreferences: AdvancedPlayerPreferences = Injekt.get(),
private val getCustomButtons: GetCustomButtons = Injekt.get(),
) {
private val scope = CoroutineScope(Dispatchers.IO)
private var copyJob: Job? = null

fun copyFiles() {
if (copyJob?.isActive == true) return

copyJob = scope.launchIO {
val mpvDir = getMpvDir()
copyUserFiles(mpvDir)
copyFontsDirectory(mpvDir)
copyAssets(mpvDir)
}
}

private fun getMpvDir(): UniFile {
return UniFile.fromFile(context.filesDir)!!.createDirectory(MPV_DIR)!!
}

private suspend fun copyUserFiles(mpvDir: UniFile) {
// First, delete all present scripts
val scriptsDir = deleteAndGet(mpvDir, MPV_SCRIPTS_DIR)
val scriptOptsDir = deleteAndGet(mpvDir, MPV_SCRIPTS_OPTS_DIR)
val shadersDir = deleteAndGet(mpvDir, MPV_SHADERS_DIR)

// Then, copy the user files from the Aniyomi directory
if (advancedPlayerPreferences.mpvUserFiles.get()) {
copyDirectoryContents(storageManager.getScriptsDirectory(), scriptsDir)
copyDirectoryContents(storageManager.getScriptOptsDirectory(), scriptOptsDir)
copyDirectoryContents(storageManager.getShadersDirectory(), shadersDir)
}

val buttons = getCustomButtons.getAll()
setupCustomButtons(buttons)

// Copy over the bridge file
val luaFile = scriptsDir.createFile("aniyomi.lua") ?: return
context.assets.open("aniyomi.lua").use { inputStream ->
luaFile.openOutputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
}
}

fun setupCustomButtons(buttons: List<CustomButton>) {
val scriptsDir = getMpvDir().createDirectory(MPV_SCRIPTS_DIR)!!
val primaryButtonId = buttons.firstOrNull { it.isFavorite }?.id ?: 0L

val customButtonsContent = buildString {
appendLine(
"""
local lua_modules = mp.find_config_file('scripts')
if lua_modules then
package.path = package.path .. ';' .. lua_modules .. '/?.lua;' .. lua_modules .. '/?/init.lua;' .. '${scriptsDir.filePath}' .. '/?.lua'
end
local aniyomi = require 'aniyomi'
""".trimIndent(),
)

buttons.forEach { button ->
appendLine(
"""
${button.getButtonOnStartup(primaryButtonId)}
function button${button.id}()
${button.getButtonContent(primaryButtonId)}
end
mp.register_script_message('call_button_${button.id}', button${button.id})
function button${button.id}long()
${button.getButtonLongPressContent(primaryButtonId)}
end
mp.register_script_message('call_button_${button.id}_long', button${button.id}long)
""".trimIndent(),
)
}
}

val file = scriptsDir.createFile("custombuttons.lua")
file?.openOutputStream()?.bufferedWriter()?.use {
it.write(customButtonsContent)
}
}

private suspend fun copyFontsDirectory(mpvDir: UniFile) {
// TODO: I think this is a bad hack.
// We need to find a way to let MPV directly access our fonts directory.
val fontsDirectory = deleteAndGet(mpvDir, MPV_FONTS_DIR)
copyDirectoryContents(storageManager.getFontsDirectory(), fontsDirectory)
}

private fun copyAssets(mpvDir: UniFile) {
val assetManager = context.assets
val files = arrayOf("subfont.ttf", "cacert.pem")
for (filename in files) {
var ins: InputStream? = null
var out: OutputStream? = null
try {
ins = assetManager.open(filename, AssetManager.ACCESS_STREAMING)
val outFile = mpvDir.createFile(filename)!!
// Note that .available() officially returns an *estimated* number of bytes available
// this is only true for generic streams, asset streams return the full file size
if (outFile.length() == ins.available().toLong()) {
logcat(LogPriority.VERBOSE) { "Skipping copy of asset file (exists same size): $filename" }
continue
}
out = outFile.openOutputStream()
ins.copyTo(out)
logcat(LogPriority.WARN) { "Copied asset file: $filename" }
} catch (e: IOException) {
logcat(LogPriority.ERROR, e) { "Failed to copy asset file: $filename" }
} finally {
ins?.close()
out?.close()
}
}
}

private fun deleteAndGet(parent: UniFile, name: String): UniFile {
parent.createDirectory(name)?.delete()
return parent.createDirectory(name)!!
}

private suspend fun copyDirectoryContents(sourceDir: UniFile?, destDir: UniFile) {
sourceDir?.listFiles()?.forEach { file ->
if (!currentCoroutineContext().isActive) {
throw CancellationException()
}

val outFile = destDir.createFile(file.name) ?: return@forEach
file.openInputStream().use { input ->
outFile.openOutputStream().use { output ->
input.copyTo(output)
}
}
}
}

companion object {
const val MPV_DIR = "mpv"
const val MPV_FONTS_DIR = "fonts"
const val MPV_SCRIPTS_DIR = "scripts"
const val MPV_SCRIPTS_OPTS_DIR = "script-opts"
const val MPV_SHADERS_DIR = "shaders"
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package eu.kanade.presentation.more.settings.screen.player.custombutton

import androidx.compose.runtime.Immutable
import animiru.feature.mpvfiles.MpvConfig
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import dev.icerock.moko.resources.StringResource
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.custombutton.interactor.CreateCustomButton
import tachiyomi.domain.custombutton.interactor.DeleteCustomButton
import tachiyomi.domain.custombutton.interactor.GetCustomButtons
Expand All @@ -30,6 +33,9 @@ class PlayerSettingsCustomButtonScreenModel(
private val updateCustomButton: UpdateCustomButton = Injekt.get(),
private val reorderCustomButton: ReorderCustomButton = Injekt.get(),
private val toggleFavoriteCustomButton: ToggleFavoriteCustomButton = Injekt.get(),
// AM -->
private val mpvConfig: MpvConfig = Injekt.get(),
// <-- AM
) : StateScreenModel<CustomButtonScreenState>(CustomButtonScreenState.Loading) {

private val _events: Channel<CustomButtonEvent> = Channel()
Expand All @@ -46,6 +52,16 @@ class PlayerSettingsCustomButtonScreenModel(
}
}
}

// AM -->
screenModelScope.launchIO {
getCustomButtons.subscribeAll()
.distinctUntilChanged()
.collectLatest { customButtons ->
mpvConfig.setupCustomButtons(customButtons)
}
}
// <-- AM
}

fun createCustomButton(name: String, content: String, longPressContent: String, onStartup: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package eu.kanade.presentation.more.settings.screen.player.editor

import android.content.Context
import androidx.compose.runtime.Immutable
import animiru.feature.mpvfiles.MpvConfig.Companion.MPV_DIR
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.hippo.unifile.UniFile
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.size
Expand Down Expand Up @@ -60,6 +62,17 @@ class PlayerSettingsEditorScreenModel(
return
}

// AM -->
UniFile.fromFile(context.filesDir)
?.createDirectory(MPV_DIR)
?.createDirectory(selectedType.value.directoryName)
?.createFile(fileName)
?: run {
context.toast(context.stringResource(AYMR.strings.editor_create_error))
return
}
// <-- AM

updateItems(selectedType.value)
}

Expand All @@ -68,7 +81,14 @@ class PlayerSettingsEditorScreenModel(
?.createDirectory(selectedType.value.directoryName)
?.createFile(originalFile)

if (file?.renameTo(fileName) == true) {
// AM -->
val internalFile = UniFile.fromFile(context.filesDir)
?.createDirectory(MPV_DIR)
?.createDirectory(selectedType.value.directoryName)
?.createFile(originalFile)
// <-- AM

if (file?.renameTo(fileName) == true && internalFile?.renameTo(fileName) == true) {
updateItems(selectedType.value)
} else {
context.toast(context.stringResource(AYMR.strings.editor_rename_error))
Expand All @@ -80,7 +100,14 @@ class PlayerSettingsEditorScreenModel(
?.createDirectory(selectedType.value.directoryName)
?.findFile(name)

if (file?.delete() == true) {
// AM -->
val internalFile = UniFile.fromFile(context.filesDir)
?.createDirectory(MPV_DIR)
?.createDirectory(selectedType.value.directoryName)
?.createFile(name)
// <-- AM

if (file?.delete() == true && internalFile?.delete() == true) {
updateItems(selectedType.value)
} else {
context.toast(context.stringResource(AYMR.strings.editor_delete_error))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.TextFieldValue
import animiru.feature.mpvfiles.MpvConfig.Companion.MPV_DIR
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.hippo.unifile.UniFile
Expand Down Expand Up @@ -110,6 +111,10 @@ class CodeEditScreenModel(
context.toast(AYMR.strings.editor_save_error)
return
}
// AM -->
val internalFile = UniFile.fromFile(context.filesDir)!!.createDirectory(MPV_DIR)!!
.createFile(filePath)!!
// <-- AM

val content = (mutableState.value as? CodeEditScreenState.Success)
?.content?.annotatedString?.text ?: kotlin.run {
Expand All @@ -121,6 +126,13 @@ class CodeEditScreenModel(
file.openOutputStream()
.also { (it as? FileOutputStream)?.channel?.truncate(0) }
.use { it.write(content.toByteArray()) }

// AM -->
internalFile.openOutputStream()
.also { (it as? FileOutputStream)?.channel?.truncate(0) }
.use { it.write(content.toByteArray()) }
// <-- AM

_hasModified.update { _ -> false }
context.toast(context.stringResource(AYMR.strings.editor_save_success))
} catch (e: Exception) {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.di
import android.app.Application
import androidx.core.content.ContextCompat
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import animiru.feature.mpvfiles.MpvConfig
import app.cash.sqldelight.db.SqlDriver
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConfiguration
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDatabaseType
Expand Down Expand Up @@ -160,6 +161,7 @@ class AppModule(val app: Application) : InjektModule {
// <-- AM (SYNC_DRIVE)

// AM -->
addSingletonFactory { MpvConfig(app) }
addSingletonFactory { AudioManager(app) }
addSingletonFactory { BrightnessManager(app) }
// <-- AM
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import androidx.core.util.Consumer
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import androidx.lifecycle.lifecycleScope
import animiru.feature.mpvfiles.MpvConfig
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
Expand Down Expand Up @@ -122,6 +123,7 @@ class MainActivity : BaseActivity() {
private val preferences: BasePreferences by injectLazy()

// AM -->
private val mpvConfig: MpvConfig by injectLazy()
private val uiPreferences: UiPreferences by injectLazy()
// <-- AM

Expand Down Expand Up @@ -523,6 +525,13 @@ class MainActivity : BaseActivity() {
}
// <-- AY

// AM -->
override fun onResume() {
super.onResume()
mpvConfig.copyFiles()
}
// <-- AM

companion object {
const val INTENT_SEARCH = "eu.kanade.tachiyomi.SEARCH"
const val INTENT_SEARCH_QUERY = "query"
Expand Down
Loading
Loading