mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-11-03 16:39:01 -06:00 
			
		
		
		
	android: Add support for concurrent installs
This commit is contained in:
		@@ -0,0 +1,62 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
package org.yuzu.yuzu_emu.fragments
 | 
			
		||||
 | 
			
		||||
import android.app.Dialog
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.fragment.app.DialogFragment
 | 
			
		||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
 | 
			
		||||
import org.yuzu.yuzu_emu.R
 | 
			
		||||
 | 
			
		||||
class InstallDialogFragment : DialogFragment() {
 | 
			
		||||
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
 | 
			
		||||
        val titleId = requireArguments().getInt(TITLE)
 | 
			
		||||
        val description = requireArguments().getString(DESCRIPTION)
 | 
			
		||||
        val helpLinkId = requireArguments().getInt(HELP_LINK)
 | 
			
		||||
 | 
			
		||||
        val dialog = MaterialAlertDialogBuilder(requireContext())
 | 
			
		||||
            .setPositiveButton(R.string.close, null)
 | 
			
		||||
            .setTitle(titleId)
 | 
			
		||||
            .setMessage(description)
 | 
			
		||||
 | 
			
		||||
        if (helpLinkId != 0) {
 | 
			
		||||
            dialog.setNeutralButton(R.string.learn_more) { _, _ ->
 | 
			
		||||
                openLink(getString(helpLinkId))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return dialog.show()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun openLink(link: String) {
 | 
			
		||||
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
 | 
			
		||||
        startActivity(intent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val TAG = "MessageDialogFragment"
 | 
			
		||||
 | 
			
		||||
        private const val TITLE = "Title"
 | 
			
		||||
        private const val DESCRIPTION = "Description"
 | 
			
		||||
        private const val HELP_LINK = "Link"
 | 
			
		||||
 | 
			
		||||
        fun newInstance(
 | 
			
		||||
            titleId: Int,
 | 
			
		||||
            description: String,
 | 
			
		||||
            helpLinkId: Int = 0
 | 
			
		||||
        ): InstallDialogFragment {
 | 
			
		||||
            val dialog = InstallDialogFragment()
 | 
			
		||||
            val bundle = Bundle()
 | 
			
		||||
            bundle.apply {
 | 
			
		||||
                putInt(TITLE, titleId)
 | 
			
		||||
                putString(DESCRIPTION, description)
 | 
			
		||||
                putInt(HELP_LINK, helpLinkId)
 | 
			
		||||
            }
 | 
			
		||||
            dialog.arguments = bundle
 | 
			
		||||
            return dialog
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
package org.yuzu.yuzu_emu.ui.main
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup.MarginLayoutParams
 | 
			
		||||
@@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
 | 
			
		||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 | 
			
		||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 | 
			
		||||
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
 | 
			
		||||
import org.yuzu.yuzu_emu.fragments.InstallDialogFragment
 | 
			
		||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 | 
			
		||||
import org.yuzu.yuzu_emu.model.GamesViewModel
 | 
			
		||||
import org.yuzu.yuzu_emu.model.HomeViewModel
 | 
			
		||||
@@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    val installGameUpdate =
 | 
			
		||||
        registerForActivityResult(ActivityResultContracts.OpenDocument()) {
 | 
			
		||||
            if (it == null) {
 | 
			
		||||
                return@registerForActivityResult
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    val installGameUpdate = registerForActivityResult(
 | 
			
		||||
        ActivityResultContracts.OpenMultipleDocuments()
 | 
			
		||||
    ) { documents: List<Uri> ->
 | 
			
		||||
        if (documents.isNotEmpty()) {
 | 
			
		||||
            IndeterminateProgressDialogFragment.newInstance(
 | 
			
		||||
                this@MainActivity,
 | 
			
		||||
                R.string.install_game_content
 | 
			
		||||
            ) {
 | 
			
		||||
                val result = NativeLibrary.installFileToNand(it.toString())
 | 
			
		||||
                var installSuccess = 0
 | 
			
		||||
                var installOverwrite = 0
 | 
			
		||||
                var errorBaseGame = 0
 | 
			
		||||
                var errorExtension = 0
 | 
			
		||||
                var errorOther = 0
 | 
			
		||||
                var errorTotal = 0
 | 
			
		||||
                lifecycleScope.launch {
 | 
			
		||||
                    withContext(Dispatchers.Main) {
 | 
			
		||||
                        when (result) {
 | 
			
		||||
                    documents.forEach {
 | 
			
		||||
                        when (NativeLibrary.installFileToNand(it.toString())) {
 | 
			
		||||
                            NativeLibrary.InstallFileToNandResult.Success -> {
 | 
			
		||||
                                Toast.makeText(
 | 
			
		||||
                                    applicationContext,
 | 
			
		||||
                                    R.string.install_game_content_success,
 | 
			
		||||
                                    Toast.LENGTH_SHORT
 | 
			
		||||
                                ).show()
 | 
			
		||||
                                installSuccess += 1
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
 | 
			
		||||
                                Toast.makeText(
 | 
			
		||||
                                    applicationContext,
 | 
			
		||||
                                    R.string.install_game_content_success_overwrite,
 | 
			
		||||
                                    Toast.LENGTH_SHORT
 | 
			
		||||
                                ).show()
 | 
			
		||||
                                installOverwrite += 1
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
 | 
			
		||||
                                MessageDialogFragment.newInstance(
 | 
			
		||||
                                    R.string.install_game_content_failure,
 | 
			
		||||
                                    R.string.install_game_content_failure_base
 | 
			
		||||
                                ).show(supportFragmentManager, MessageDialogFragment.TAG)
 | 
			
		||||
                                errorBaseGame += 1
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
 | 
			
		||||
                                MessageDialogFragment.newInstance(
 | 
			
		||||
                                    R.string.install_game_content_failure,
 | 
			
		||||
                                    R.string.install_game_content_failure_file_extension,
 | 
			
		||||
                                    R.string.install_game_content_help_link
 | 
			
		||||
                                ).show(supportFragmentManager, MessageDialogFragment.TAG)
 | 
			
		||||
                                errorExtension += 1
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            else -> {
 | 
			
		||||
                                MessageDialogFragment.newInstance(
 | 
			
		||||
                                    R.string.install_game_content_failure,
 | 
			
		||||
                                    R.string.install_game_content_failure_description,
 | 
			
		||||
                                    R.string.install_game_content_help_link
 | 
			
		||||
                                ).show(supportFragmentManager, MessageDialogFragment.TAG)
 | 
			
		||||
                                errorOther += 1
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    withContext(Dispatchers.Main) {
 | 
			
		||||
                        val separator = System.getProperty("line.separator") ?: "\n"
 | 
			
		||||
                        val installResult = StringBuilder()
 | 
			
		||||
                        if (installSuccess > 0) {
 | 
			
		||||
                            installResult.append(
 | 
			
		||||
                                getString(
 | 
			
		||||
                                    R.string.install_game_content_success_install,
 | 
			
		||||
                                    installSuccess
 | 
			
		||||
                                )
 | 
			
		||||
                            )
 | 
			
		||||
                            installResult.append(separator)
 | 
			
		||||
                        }
 | 
			
		||||
                        if (installOverwrite > 0) {
 | 
			
		||||
                            installResult.append(
 | 
			
		||||
                                getString(
 | 
			
		||||
                                    R.string.install_game_content_success_overwrite,
 | 
			
		||||
                                    installOverwrite
 | 
			
		||||
                                )
 | 
			
		||||
                            )
 | 
			
		||||
                            installResult.append(separator)
 | 
			
		||||
                        }
 | 
			
		||||
                        errorTotal = errorBaseGame + errorExtension + errorOther
 | 
			
		||||
                        if (errorTotal > 0) {
 | 
			
		||||
                            installResult.append(separator)
 | 
			
		||||
                            installResult.append(
 | 
			
		||||
                                getString(
 | 
			
		||||
                                    R.string.install_game_content_failed_count,
 | 
			
		||||
 | 
			
		||||
                                )
 | 
			
		||||
                            )
 | 
			
		||||
                            installResult.append(separator)
 | 
			
		||||
                            if (errorBaseGame > 0) {
 | 
			
		||||
                                installResult.append(separator)
 | 
			
		||||
                                installResult.append(
 | 
			
		||||
                                    getString(R.string.install_game_content_failure_base)
 | 
			
		||||
                                )
 | 
			
		||||
                                installResult.append(separator)
 | 
			
		||||
                            }
 | 
			
		||||
                            if (errorExtension > 0) {
 | 
			
		||||
                                installResult.append(separator)
 | 
			
		||||
                                installResult.append(
 | 
			
		||||
                                    getString(R.string.install_game_content_failure_file_extension)
 | 
			
		||||
                                )
 | 
			
		||||
                                installResult.append(separator)
 | 
			
		||||
                            }
 | 
			
		||||
                            if (errorOther > 0) {
 | 
			
		||||
                                installResult.append(
 | 
			
		||||
                                    getString(R.string.install_game_content_failure_description)
 | 
			
		||||
                                )
 | 
			
		||||
                                installResult.append(separator)
 | 
			
		||||
                            }
 | 
			
		||||
                            InstallDialogFragment.newInstance(
 | 
			
		||||
                                R.string.install_game_content_failure,
 | 
			
		||||
                                installResult.toString().trim(),
 | 
			
		||||
                                R.string.install_game_content_help_link
 | 
			
		||||
                            ).show(supportFragmentManager, MessageDialogFragment.TAG)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            InstallDialogFragment.newInstance(
 | 
			
		||||
                                R.string.install_game_content_success,
 | 
			
		||||
                                installResult.toString().trim(),
 | 
			
		||||
                            ).show(supportFragmentManager, MessageDialogFragment.TAG)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return@newInstance result
 | 
			
		||||
                return@newInstance installSuccess + installOverwrite + errorTotal
 | 
			
		||||
            }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -104,12 +104,14 @@
 | 
			
		||||
    <string name="share_log_missing">No log file found</string>
 | 
			
		||||
    <string name="install_game_content">Install game content</string>
 | 
			
		||||
    <string name="install_game_content_description">Install game updates or DLC</string>
 | 
			
		||||
    <string name="install_game_content_failure">Error installing file to NAND</string>
 | 
			
		||||
    <string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string>
 | 
			
		||||
    <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string>
 | 
			
		||||
    <string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string>
 | 
			
		||||
    <string name="install_game_content_success">Game content installed successfully</string>
 | 
			
		||||
    <string name="install_game_content_success_overwrite">Game content was overwritten successfully</string>
 | 
			
		||||
    <string name="install_game_content_failure">Error installing file(s) to NAND</string>
 | 
			
		||||
    <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
 | 
			
		||||
    <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
 | 
			
		||||
    <string name="install_game_content_failure_file_extension">Only NSP and XCI content is supported. Please verify the game content(s) are valid.</string>
 | 
			
		||||
    <string name="install_game_content_failed_count">%1$d installation error(s)</string>
 | 
			
		||||
    <string name="install_game_content_success">Game content(s) installed successfully</string>
 | 
			
		||||
    <string name="install_game_content_success_install">%1$d installed successfully</string>
 | 
			
		||||
    <string name="install_game_content_success_overwrite">%1$d overwritten successfully</string>
 | 
			
		||||
    <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
 | 
			
		||||
 | 
			
		||||
    <!-- About screen strings -->
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user