Skip to content
Open
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
6 changes: 3 additions & 3 deletions app/src/main/java/org/stratoemu/strato/AppDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class AppDialog : BottomSheetDialogFragment() {
* Used to manage save files
*/
private lateinit var documentPicker : ActivityResultLauncher<Array<String>>
private lateinit var startForResultExportSave : ActivityResultLauncher<Intent>
private lateinit var startForResultExportSave : ActivityResultLauncher<String>

override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -63,7 +63,7 @@ class AppDialog : BottomSheetDialogFragment() {
binding.deleteSave.isEnabled = isSaveFileOfThisGame
binding.exportSave.isEnabled = isSaveFileOfThisGame
}
startForResultExportSave = SaveManagementUtils.registerStartForResultExportSave(requireActivity())
startForResultExportSave = SaveManagementUtils.registerStartForResultExportSave(requireActivity(), item.titleId!!)
}

/**
Expand Down Expand Up @@ -141,7 +141,7 @@ class AppDialog : BottomSheetDialogFragment() {

binding.exportSave.isEnabled = saveExists
binding.exportSave.setOnClickListener {
SaveManagementUtils.exportSave(requireContext(), startForResultExportSave, item.titleId, "${item.title} (v${binding.gameVersion.text}) [${item.titleId}]")
SaveManagementUtils.exportSave(startForResultExportSave, "${item.title} (v${binding.gameVersion.text}) [${item.titleId}]")
}

binding.gameTitleId.setOnLongClickListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import org.stratoemu.strato.utils.SaveManagementUtils

class ImportExportSavesPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = androidx.preference.R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) {
private val documentPicker = SaveManagementUtils.registerDocumentPicker(context)
private val startForResultExportSave = SaveManagementUtils.registerStartForResultExportSave(context)
private val startForResultExportSave = SaveManagementUtils.registerStartForResultExportSave(context, "")

override fun onClick() {
val saveDataExists = SaveManagementUtils.savesFolderRootExists()
Expand All @@ -29,7 +29,7 @@ class ImportExportSavesPreference @JvmOverloads constructor(context : Context, a
if (saveDataExists) {
dialog.setMessage(R.string.save_data_found)
.setNegativeButton(R.string.export_save) { _, _ ->
SaveManagementUtils.exportSave(context, startForResultExportSave, "", context.getString(R.string.global_save_data_zip_name))
SaveManagementUtils.exportSave(startForResultExportSave, context.getString(R.string.global_save_data_zip_name))
}
} else {
dialog.setMessage(R.string.save_data_not_found)
Expand Down
50 changes: 36 additions & 14 deletions app/src/main/java/org/stratoemu/strato/utils/SaveManagementUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import java.io.FileInputStream

interface SaveManagementUtils {

companion object {
private val savesFolderRoot = "${StratoApplication.instance.getPublicFilesDir().canonicalPath}/switch/nand/user/save/0000000000000000/00000000000000000000000000000001"
private var exportZipName: String = "export"

fun registerDocumentPicker(context : Context) : ActivityResultLauncher<Array<String>> {
return (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.OpenDocument()) {
Expand All @@ -54,18 +56,25 @@ interface SaveManagementUtils {
}
}

fun registerStartForResultExportSave(context : Context) : ActivityResultLauncher<Intent> {
return (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
fun registerStartForResultExportSave(context : Context, titleId: String) : ActivityResultLauncher<String> {
File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
return (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { uri ->
uri?.let {
exportSave(context, it, titleId)
}
}
}

fun registerStartForResultExportSave(fragmentAct : FragmentActivity) : ActivityResultLauncher<Intent> {
fun registerStartForResultExportSave(fragmentAct : FragmentActivity, titleId: String) : ActivityResultLauncher<String> {
val activity = fragmentAct as AppCompatActivity
val activityResultRegistry = fragmentAct.activityResultRegistry

return activityResultRegistry.register("startForResultExportSaveKey", ActivityResultContracts.StartActivityForResult()) {
File(activity.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()

return activityResultRegistry.register("saveExportFolderPickerKey", ActivityResultContracts.CreateDocument("application/zip")) { uri ->
uri?.let {
exportSave(fragmentAct as Context, it, titleId)
}
}
}

Expand Down Expand Up @@ -103,7 +112,7 @@ interface SaveManagementUtils {
tempFolder.mkdirs()

val saveFolder = File(saveFolderPath)
val outputZipFile = File(tempFolder, "$outputZipName - ${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))}.zip")
val outputZipFile = File(tempFolder, "$outputZipName.zip")
outputZipFile.createNewFile()
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
saveFolder.walkTopDown().forEach { file ->
Expand All @@ -125,26 +134,39 @@ interface SaveManagementUtils {
* @param titleId The title ID of the game to export the save file of. If empty, export all save files.
* @param outputZipName The initial part of the name of the zip file to create.
*/
fun exportSave(context : Context, startForResultExportSave : ActivityResultLauncher<Intent>, titleId : String?, outputZipName : String) {
fun exportSave(context : Context, uri: Uri, titleId : String) {
if (titleId == null) return
CoroutineScope(Dispatchers.IO).launch {
val saveFolderPath = "$savesFolderRoot/$titleId"
val zipCreated = zipSave(saveFolderPath, outputZipName)
val zipCreated = zipSave(saveFolderPath, exportZipName)
if (zipCreated == null) {
withContext(Dispatchers.Main) {
Toast.makeText(context, R.string.error, Toast.LENGTH_LONG).show()
}
return@launch
}

try {
context.contentResolver.openOutputStream(uri)?.use { output ->
FileInputStream(zipCreated).use { it.copyTo(output) }
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show()
}
return@launch
}
withContext(Dispatchers.Main) {
val file = DocumentFile.fromSingleUri(context, DocumentsContract.buildDocumentUri(DocumentsProvider.AUTHORITY, "${DocumentsProvider.ROOT_ID}/temp/${zipCreated.name}"))!!
val intent = Intent(Intent.ACTION_SEND).setDataAndType(file.uri, "application/zip").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION).putExtra(Intent.EXTRA_STREAM, file.uri)
startForResultExportSave.launch(Intent.createChooser(intent, context.getString(R.string.save_file_share)))
Toast.makeText(context, R.string.save_exported_successfully, Toast.LENGTH_LONG).show()
}
}
}

fun exportSave(startForResultExportSave : ActivityResultLauncher<String>, outputZipName : String) {
exportZipName = "$outputZipName - ${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))}"
startForResultExportSave.launch("$exportZipName.zip")
}

/**
* Launches the document picker to import a save file.
*/
Expand Down Expand Up @@ -207,4 +229,4 @@ interface SaveManagementUtils {
return true
}
}
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<string name="delete_save">Delete save</string>
<string name="import_save">Import</string>
<string name="export_save">Export</string>
<string name="save_exported_successfully">Successfully exported save file</string>
<string name="searching_roms">Searching for ROMs</string>
<string name="invalid_file">Invalid file</string>
<string name="missing_title_key">Missing title key</string>
Expand Down