From b5c15d19d3d2c1f85126cfe9750480464a285e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 9 Apr 2023 19:28:56 +0200 Subject: [PATCH 01/22] implement SAF as SaveSyncManagerImpl target --- lemuroid-app-ext-free/build.gradle.kts | 3 + .../src/main/AndroidManifest.xml | 9 ++- .../feature/savesync/ActivateSAFActivity.kt | 80 +++++++++++++++++++ .../feature/savesync/SaveSyncManagerImpl.kt | 70 ++++++++++++++-- .../layout/activity_activate_safactivity.xml | 9 +++ .../src/main/res/values/strings.xml | 1 + .../src/main/res/values/keys.xml | 1 + 7 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt create mode 100644 lemuroid-app-ext-free/src/main/res/layout/activity_activate_safactivity.xml diff --git a/lemuroid-app-ext-free/build.gradle.kts b/lemuroid-app-ext-free/build.gradle.kts index 8f99cc5e74..ba9c63489e 100644 --- a/lemuroid-app-ext-free/build.gradle.kts +++ b/lemuroid-app-ext-free/build.gradle.kts @@ -16,4 +16,7 @@ dependencies { implementation(deps.libs.retrofit) implementation(deps.libs.kotlinxCoroutinesAndroid) + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.4.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") } diff --git a/lemuroid-app-ext-free/src/main/AndroidManifest.xml b/lemuroid-app-ext-free/src/main/AndroidManifest.xml index 0d5f04dc1b..e33f11a8da 100644 --- a/lemuroid-app-ext-free/src/main/AndroidManifest.xml +++ b/lemuroid-app-ext-free/src/main/AndroidManifest.xml @@ -1,4 +1,9 @@ - + package="com.swordfish.lemuroid.ext" + tools:ignore="MissingLeanbackLauncher,ImpliedTouchscreenHardware,MissingLeanbackSupport"> + + + + diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt new file mode 100644 index 0000000000..00caa06c17 --- /dev/null +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt @@ -0,0 +1,80 @@ +package com.swordfish.lemuroid.ext.feature.savesync + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import com.swordfish.lemuroid.ext.R +import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper + +class ActivateSAFActivity : AppCompatActivity() { + + companion object { + const val PREF_KEY_STORAGE_URI_NONE = "" + private const val REQUEST_CODE_PICK_SAVEGAMEFOLDER = 2 + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_activate_safactivity) + + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { + this.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + this.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + this.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + this.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) + this.putExtra(Intent.EXTRA_LOCAL_ONLY, true) + } + try { + startActivityForResult(intent, REQUEST_CODE_PICK_SAVEGAMEFOLDER) + } catch (e: Exception) { + e.printStackTrace() + } + } + + + private fun getStorageUri(): String { + val sharedPreferences = SharedPreferencesHelper.getSharedPreferences(this) + val preferenceKey = getString(R.string.pref_key_saf_uri) + val uri = sharedPreferences.getString(preferenceKey, PREF_KEY_STORAGE_URI_NONE) + return uri ?: PREF_KEY_STORAGE_URI_NONE + } + + private fun setStorageUri(uri: String) { + val sharedPreferences = SharedPreferencesHelper.getSharedPreferences(this).edit() + val preferenceKey = getString(R.string.pref_key_saf_uri) + sharedPreferences.putString(preferenceKey, uri) + sharedPreferences.apply() + } + + + override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { + super.onActivityResult(requestCode, resultCode, resultData) + + if (requestCode == REQUEST_CODE_PICK_SAVEGAMEFOLDER && resultCode == Activity.RESULT_OK) { + + val currentValue: String = getStorageUri() + val newValue = resultData?.data + + if (newValue != null && newValue.toString() != currentValue) { + updatePersistableUrisRW(newValue) + setStorageUri(newValue.toString()) + } + } + finish() + } + + private fun updatePersistableUrisRW(uri: Uri) { + + grantUriPermission( + packageName, + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + contentResolver.takePersistableUriPermission( + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + } +} diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index bf27525e1a..8236027460 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -2,29 +2,87 @@ package com.swordfish.lemuroid.ext.feature.savesync import android.app.Activity import android.content.Context +import com.swordfish.lemuroid.common.kotlin.SharedPreferencesDelegates +import com.swordfish.lemuroid.ext.R import com.swordfish.lemuroid.lib.library.CoreID +import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper import com.swordfish.lemuroid.lib.savesync.SaveSyncManager import com.swordfish.lemuroid.lib.storage.DirectoriesManager +import timber.log.Timber +import java.io.File +import java.text.SimpleDateFormat class SaveSyncManagerImpl( private val appContext: Context, private val directoriesManager: DirectoriesManager ) : SaveSyncManager { - override fun getProvider(): String = "" - override fun getSettingsActivity(): Class? = null + private var lastSyncTimestamp: Long by SharedPreferencesDelegates.LongDelegate( + SharedPreferencesHelper.getSharedPreferences(appContext), + appContext.getString(R.string.pref_key_last_save_sync), + 0L + ) - override fun isSupported(): Boolean = false + private var storageUri: String by SharedPreferencesDelegates.StringDelegate( + SharedPreferencesHelper.getSharedPreferences(appContext), + appContext.getString(R.string.pref_key_saf_uri), + ActivateSAFActivity.PREF_KEY_STORAGE_URI_NONE + ) - override fun isConfigured(): Boolean = false - override fun getLastSyncInfo(): String = "" + // done + override fun getProvider(): String = "SAF" + // done + override fun getSettingsActivity(): Class? = ActivateSAFActivity::class.java + + // done + override fun isSupported(): Boolean = true + + + // todo, check if pref has been set + override fun isConfigured(): Boolean { + return storageUri != ActivateSAFActivity.PREF_KEY_STORAGE_URI_NONE + } + + // done + override fun getLastSyncInfo(): String { + val dateString = if (lastSyncTimestamp > 0) { + SimpleDateFormat.getDateTimeInstance().format(lastSyncTimestamp) + } else { + "-" + } + return appContext.getString(R.string.saf_last_sync_completed, dateString) + } + + // not needed override fun getConfigInfo(): String = "" - override suspend fun sync(cores: Set) {} + override suspend fun sync(cores: Set) { + synchronized(SYNC_LOCK) { + val saveSyncResult = runCatching { + performSaveSyncForCores(cores) + } + + saveSyncResult.onFailure { + Timber.e(it, "Error while performing save sync.") + } + } + } + + + private fun performSaveSyncForCores(cores: Set) { + + lastSyncTimestamp = System.currentTimeMillis() + } override fun computeSavesSpace() = "" override fun computeStatesSpace(coreID: CoreID) = "" + + + companion object { + const val GDRIVE_PROPERTY_LOCAL_PATH = "localPath" + private val SYNC_LOCK = Object() + } } diff --git a/lemuroid-app-ext-free/src/main/res/layout/activity_activate_safactivity.xml b/lemuroid-app-ext-free/src/main/res/layout/activity_activate_safactivity.xml new file mode 100644 index 0000000000..c1230f1814 --- /dev/null +++ b/lemuroid-app-ext-free/src/main/res/layout/activity_activate_safactivity.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/lemuroid-app-ext-free/src/main/res/values/strings.xml b/lemuroid-app-ext-free/src/main/res/values/strings.xml index de5ff2507e..eac07f7925 100644 --- a/lemuroid-app-ext-free/src/main/res/values/strings.xml +++ b/lemuroid-app-ext-free/src/main/res/values/strings.xml @@ -6,4 +6,5 @@ This can take up to a few minutes The Libretro core was not loaded. Make sure your device is connected to internet and try to rescan your library. If the issue persist this core might not be supported on your device. + Last sync: %s diff --git a/retrograde-app-shared/src/main/res/values/keys.xml b/retrograde-app-shared/src/main/res/values/keys.xml index 8dc363795d..7a208c4701 100644 --- a/retrograde-app-shared/src/main/res/values/keys.xml +++ b/retrograde-app-shared/src/main/res/values/keys.xml @@ -5,4 +5,5 @@ last_save_sync external_folder legacy_external_folder + pref_key_saf_uri From 419b954f33d52770673ec8447e57aa4b0e02515a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 9 Apr 2023 20:46:03 +0200 Subject: [PATCH 02/22] copy non existent files --- .../feature/savesync/SaveSyncManagerImpl.kt | 130 +++++++++++++++++- 1 file changed, 125 insertions(+), 5 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 8236027460..d56aeca8fc 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -2,6 +2,9 @@ package com.swordfish.lemuroid.ext.feature.savesync import android.app.Activity import android.content.Context +import android.net.Uri +import android.util.Log +import androidx.documentfile.provider.DocumentFile import com.swordfish.lemuroid.common.kotlin.SharedPreferencesDelegates import com.swordfish.lemuroid.ext.R import com.swordfish.lemuroid.lib.library.CoreID @@ -10,8 +13,13 @@ import com.swordfish.lemuroid.lib.savesync.SaveSyncManager import com.swordfish.lemuroid.lib.storage.DirectoriesManager import timber.log.Timber import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.InputStream +import java.io.OutputStream import java.text.SimpleDateFormat + class SaveSyncManagerImpl( private val appContext: Context, private val directoriesManager: DirectoriesManager @@ -55,13 +63,79 @@ class SaveSyncManagerImpl( return appContext.getString(R.string.saf_last_sync_completed, dateString) } - // not needed - override fun getConfigInfo(): String = "" + override fun getConfigInfo(): String { + return storageUri + } override suspend fun sync(cores: Set) { + + Log.e("TAG", "start") synchronized(SYNC_LOCK) { val saveSyncResult = runCatching { - performSaveSyncForCores(cores) + val safProviderUri = Uri.parse(storageUri) + val safDirectory = DocumentFile.fromTreeUri(appContext, safProviderUri) + + + if (safDirectory != null) { + val safSaves = safDirectory.findFile("saves") + val saveFiles = safSaves?.listFiles() + + if (saveFiles != null) { + + // copy from saf to internal + + for(saveFile in saveFiles) { + if(!saveFile.name.isNullOrEmpty()) { + + val internalTarget = getInternalSaveFile(saveFile.name!!) + if(internalTarget != null) { + Timber.tag("SAF to Internal") + .e("SAF: ${saveFile.name} - ${saveFile.lastModified()}; Internal: ${internalTarget.lastModified()}") + if (internalTarget.lastModified() < saveFile.lastModified()) { + Timber.tag("SAF to Internal").e("SAF is newer") + } else { + Timber.tag("SAF to Internal").e("Internal is newer") + } + } else { + Log.e("SAF to Internal", "test: ${saveFile.name} does not exist in internal storage") + val internalDir = File(appContext.getExternalFilesDir(null), "saves"); + val newTarget = File(internalDir, saveFile.name) + if (newTarget.createNewFile()) { + copyFromSafToInternal(saveFile, newTarget) + } else { + Timber.e("Could not create new file in internal storage") + } + } + + } + } + + // now copy from internal to saf + + val savesInternal = File(appContext.getExternalFilesDir(null), "saves") + + for(i in savesInternal.listFiles()){ + val safTarget = safSaves.findFile(i.name) + if(safTarget != null) { + Timber.tag("Internal to SAF") + .e("Internal: ${i.name} - ${i.lastModified()}; SAF: ${safTarget.lastModified()}") + if (safTarget.lastModified() < i.lastModified()) { + Timber.tag("Internal to SAF").e("Internal is newer") + } else { + Timber.tag("Internal to SAF").e("SAF is newer") + } + } else { + val newTarget = safSaves.createFile("application/octet-stream", i.name) + if (newTarget != null) { + copyFromInternalToSaf(newTarget, i) + } + } + + } + + } + } + lastSyncTimestamp = System.currentTimeMillis() } saveSyncResult.onFailure { @@ -69,13 +143,58 @@ class SaveSyncManagerImpl( } } } + private fun getInternalSaveFile(filename: String): File? { + val saves = File(appContext.getExternalFilesDir(null), "saves") + if(saves != null) { + for(i in saves.listFiles()!!){ + if(i.name.equals(filename)) { + return File(saves, i.name) + } + } + } + return null + } + + private fun copyFromSafToInternal(saf: DocumentFile, internal: File) { + val output: OutputStream = FileOutputStream(internal) + val input: InputStream? = appContext.contentResolver.openInputStream(saf.uri) + copyFile(input, output) + + } + private fun copyFromInternalToSaf(saf: DocumentFile, internal: File) { + val output: OutputStream? = appContext.contentResolver.openOutputStream(saf.uri) + val input: InputStream = FileInputStream(internal) + copyFile(input, output) + } - private fun performSaveSyncForCores(cores: Set) { + private fun copyFile(input: InputStream?, output: OutputStream?) { + if(input == null) { + Timber.e("Could not read source file!") + return + } + if(output == null) { + Timber.e("Could not read target file!") + return + } - lastSyncTimestamp = System.currentTimeMillis() + try { + try { + // Transfer bytes from in to out + val buf = ByteArray(1024) + var len: Int + while (input.read(buf).also { len = it } > 0) { + output.write(buf, 0, len) + } + } finally { + output.close() + } + } finally { + input.close() + } } + override fun computeSavesSpace() = "" override fun computeStatesSpace(coreID: CoreID) = "" @@ -85,4 +204,5 @@ class SaveSyncManagerImpl( const val GDRIVE_PROPERTY_LOCAL_PATH = "localPath" private val SYNC_LOCK = Object() } + } From c43379ed72630714c499662d930a63e3b99da393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 9 Apr 2023 20:48:22 +0200 Subject: [PATCH 03/22] copy outdated saves --- .../lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index d56aeca8fc..2529225cc8 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -93,6 +93,7 @@ class SaveSyncManagerImpl( .e("SAF: ${saveFile.name} - ${saveFile.lastModified()}; Internal: ${internalTarget.lastModified()}") if (internalTarget.lastModified() < saveFile.lastModified()) { Timber.tag("SAF to Internal").e("SAF is newer") + copyFromSafToInternal(saveFile, internalTarget) } else { Timber.tag("SAF to Internal").e("Internal is newer") } @@ -121,6 +122,7 @@ class SaveSyncManagerImpl( .e("Internal: ${i.name} - ${i.lastModified()}; SAF: ${safTarget.lastModified()}") if (safTarget.lastModified() < i.lastModified()) { Timber.tag("Internal to SAF").e("Internal is newer") + copyFromInternalToSaf(safTarget, i) } else { Timber.tag("Internal to SAF").e("SAF is newer") } From c66d94c5bb2007ac5fce375de25c69f8b7a3a70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 9 Apr 2023 22:23:42 +0200 Subject: [PATCH 04/22] overhaul permissions request --- .../feature/savesync/ActivateSAFActivity.kt | 71 ++++++------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt index 00caa06c17..c143353fa0 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt @@ -2,9 +2,9 @@ package com.swordfish.lemuroid.ext.feature.savesync import android.app.Activity import android.content.Intent -import android.net.Uri import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.activity.result.contract.ActivityResultContracts import com.swordfish.lemuroid.ext.R import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper @@ -12,33 +12,36 @@ class ActivateSAFActivity : AppCompatActivity() { companion object { const val PREF_KEY_STORAGE_URI_NONE = "" - private const val REQUEST_CODE_PICK_SAVEGAMEFOLDER = 2 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_activate_safactivity) - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { - this.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - this.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - this.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - this.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) - this.putExtra(Intent.EXTRA_LOCAL_ONLY, true) - } - try { - startActivityForResult(intent, REQUEST_CODE_PICK_SAVEGAMEFOLDER) - } catch (e: Exception) { - e.printStackTrace() - } + openPicker() } + private fun openPicker() { + + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + when (result.resultCode) { + Activity.RESULT_OK -> { + val targetUri = result?.data?.data + + if (targetUri != null ) { + contentResolver.takePersistableUriPermission( + targetUri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + setStorageUri(targetUri.toString()) + } + finish() + } + } + } - private fun getStorageUri(): String { - val sharedPreferences = SharedPreferencesHelper.getSharedPreferences(this) - val preferenceKey = getString(R.string.pref_key_saf_uri) - val uri = sharedPreferences.getString(preferenceKey, PREF_KEY_STORAGE_URI_NONE) - return uri ?: PREF_KEY_STORAGE_URI_NONE + resultLauncher.launch(intent) } private fun setStorageUri(uri: String) { @@ -47,34 +50,4 @@ class ActivateSAFActivity : AppCompatActivity() { sharedPreferences.putString(preferenceKey, uri) sharedPreferences.apply() } - - - override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { - super.onActivityResult(requestCode, resultCode, resultData) - - if (requestCode == REQUEST_CODE_PICK_SAVEGAMEFOLDER && resultCode == Activity.RESULT_OK) { - - val currentValue: String = getStorageUri() - val newValue = resultData?.data - - if (newValue != null && newValue.toString() != currentValue) { - updatePersistableUrisRW(newValue) - setStorageUri(newValue.toString()) - } - } - finish() - } - - private fun updatePersistableUrisRW(uri: Uri) { - - grantUriPermission( - packageName, - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - contentResolver.takePersistableUriPermission( - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - } } From 388be20e7337460e436ef4059b0ff9cd1ec6aadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 9 Apr 2023 22:28:31 +0200 Subject: [PATCH 05/22] directly use provided folder --- .../feature/savesync/SaveSyncManagerImpl.kt | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 2529225cc8..0313ff5099 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -73,15 +73,12 @@ class SaveSyncManagerImpl( synchronized(SYNC_LOCK) { val saveSyncResult = runCatching { val safProviderUri = Uri.parse(storageUri) - val safDirectory = DocumentFile.fromTreeUri(appContext, safProviderUri) - + val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) if (safDirectory != null) { - val safSaves = safDirectory.findFile("saves") - val saveFiles = safSaves?.listFiles() + val saveFiles = safDirectory.listFiles() if (saveFiles != null) { - // copy from saf to internal for(saveFile in saveFiles) { @@ -95,7 +92,7 @@ class SaveSyncManagerImpl( Timber.tag("SAF to Internal").e("SAF is newer") copyFromSafToInternal(saveFile, internalTarget) } else { - Timber.tag("SAF to Internal").e("Internal is newer") + //Timber.tag("SAF to Internal").e("Internal is newer") } } else { Log.e("SAF to Internal", "test: ${saveFile.name} does not exist in internal storage") @@ -113,28 +110,29 @@ class SaveSyncManagerImpl( // now copy from internal to saf - val savesInternal = File(appContext.getExternalFilesDir(null), "saves") - - for(i in savesInternal.listFiles()){ - val safTarget = safSaves.findFile(i.name) - if(safTarget != null) { - Timber.tag("Internal to SAF") - .e("Internal: ${i.name} - ${i.lastModified()}; SAF: ${safTarget.lastModified()}") - if (safTarget.lastModified() < i.lastModified()) { - Timber.tag("Internal to SAF").e("Internal is newer") - copyFromInternalToSaf(safTarget, i) + val savesInternal = File(appContext.getExternalFilesDir(null), "saves").listFiles() + + if(savesInternal != null) { + for(i in savesInternal){ + val safTarget = safDirectory.findFile(i.name) + if(safTarget != null) { + Timber.tag("Internal to SAF") + .e("Internal: ${i.name} - ${i.lastModified()}; SAF: ${safTarget.lastModified()}") + if (safTarget.lastModified() < i.lastModified()) { + Timber.tag("Internal to SAF").e("Internal is newer") + copyFromInternalToSaf(safTarget, i) + } else { + //Timber.tag("Internal to SAF").e("SAF is newer") + } } else { - Timber.tag("Internal to SAF").e("SAF is newer") - } - } else { - val newTarget = safSaves.createFile("application/octet-stream", i.name) - if (newTarget != null) { - copyFromInternalToSaf(newTarget, i) + val newTarget = safDirectory.createFile("application/octet-stream", i.name) + if (newTarget != null) { + copyFromInternalToSaf(newTarget, i) + } } - } + } } - } } lastSyncTimestamp = System.currentTimeMillis() From 9089494b6c00dcc63831781b699045f062ebf127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 9 Apr 2023 22:28:49 +0200 Subject: [PATCH 06/22] create missing internal folder if required --- .../lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 0313ff5099..17b6d99066 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -145,6 +145,7 @@ class SaveSyncManagerImpl( } private fun getInternalSaveFile(filename: String): File? { val saves = File(appContext.getExternalFilesDir(null), "saves") + saves.mkdirs() if(saves != null) { for(i in saves.listFiles()!!){ if(i.name.equals(filename)) { From 3f8e3e48e14faa514636c0d7c95bccc1c0b769f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 9 Apr 2023 22:29:19 +0200 Subject: [PATCH 07/22] fix timestamps of copied files to prevent unnessessary copying --- .../lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 17b6d99066..224a25e247 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -160,13 +160,17 @@ class SaveSyncManagerImpl( val output: OutputStream = FileOutputStream(internal) val input: InputStream? = appContext.contentResolver.openInputStream(saf.uri) copyFile(input, output) - + // update last modified timestamp to match (this one backdates the file) + internal.setLastModified(saf.lastModified()) } private fun copyFromInternalToSaf(saf: DocumentFile, internal: File) { val output: OutputStream? = appContext.contentResolver.openOutputStream(saf.uri) val input: InputStream = FileInputStream(internal) copyFile(input, output) + + // update last modified timestamp to match + internal.setLastModified(saf.lastModified()) } private fun copyFile(input: InputStream?, output: OutputStream?) { From 8cf3c392883b71ca9de15c738a6c10483bbff362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Mon, 10 Apr 2023 15:14:23 +0200 Subject: [PATCH 08/22] update error handling for selecting path --- .../lemuroid/ext/feature/savesync/ActivateSAFActivity.kt | 6 ++++++ .../lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt | 3 +-- lemuroid-app-ext-free/src/main/res/values/strings.xml | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt index c143353fa0..f5b147ef0d 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import com.swordfish.lemuroid.ext.R import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper @@ -38,6 +39,11 @@ class ActivateSAFActivity : AppCompatActivity() { } finish() } + else -> { + Toast.makeText(this, getString(R.string.saf_save_sync_no_uri_selected), Toast.LENGTH_LONG).show() + setStorageUri(PREF_KEY_STORAGE_URI_NONE) + finish() + } } } diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 224a25e247..5110a48cfc 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -38,8 +38,7 @@ class SaveSyncManagerImpl( ) - // done - override fun getProvider(): String = "SAF" + override fun getProvider(): String = appContext.getString(R.string.saf_save_sync_providername) // done override fun getSettingsActivity(): Class? = ActivateSAFActivity::class.java diff --git a/lemuroid-app-ext-free/src/main/res/values/strings.xml b/lemuroid-app-ext-free/src/main/res/values/strings.xml index eac07f7925..c7fb9e2a34 100644 --- a/lemuroid-app-ext-free/src/main/res/values/strings.xml +++ b/lemuroid-app-ext-free/src/main/res/values/strings.xml @@ -7,4 +7,6 @@ The Libretro core was not loaded. Make sure your device is connected to internet and try to rescan your library. If the issue persist this core might not be supported on your device. Last sync: %s + SAF + You have not selected a folder. Sync unavailable! From 2ce2df986c726b99c1a4ee37a055ef7676c11fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Mon, 10 Apr 2023 15:18:54 +0200 Subject: [PATCH 09/22] Split checks into functions --- .../feature/savesync/SaveSyncManagerImpl.kt | 128 +++++++++--------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 5110a48cfc..c1da6276ca 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -40,19 +40,14 @@ class SaveSyncManagerImpl( override fun getProvider(): String = appContext.getString(R.string.saf_save_sync_providername) - // done override fun getSettingsActivity(): Class? = ActivateSAFActivity::class.java - // done override fun isSupported(): Boolean = true - - // todo, check if pref has been set override fun isConfigured(): Boolean { return storageUri != ActivateSAFActivity.PREF_KEY_STORAGE_URI_NONE } - // done override fun getLastSyncInfo(): String { val dateString = if (lastSyncTimestamp > 0) { SimpleDateFormat.getDateTimeInstance().format(lastSyncTimestamp) @@ -75,64 +70,13 @@ class SaveSyncManagerImpl( val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) if (safDirectory != null) { - val saveFiles = safDirectory.listFiles() - - if (saveFiles != null) { - // copy from saf to internal - - for(saveFile in saveFiles) { - if(!saveFile.name.isNullOrEmpty()) { - - val internalTarget = getInternalSaveFile(saveFile.name!!) - if(internalTarget != null) { - Timber.tag("SAF to Internal") - .e("SAF: ${saveFile.name} - ${saveFile.lastModified()}; Internal: ${internalTarget.lastModified()}") - if (internalTarget.lastModified() < saveFile.lastModified()) { - Timber.tag("SAF to Internal").e("SAF is newer") - copyFromSafToInternal(saveFile, internalTarget) - } else { - //Timber.tag("SAF to Internal").e("Internal is newer") - } - } else { - Log.e("SAF to Internal", "test: ${saveFile.name} does not exist in internal storage") - val internalDir = File(appContext.getExternalFilesDir(null), "saves"); - val newTarget = File(internalDir, saveFile.name) - if (newTarget.createNewFile()) { - copyFromSafToInternal(saveFile, newTarget) - } else { - Timber.e("Could not create new file in internal storage") - } - } - - } - } - - // now copy from internal to saf - - val savesInternal = File(appContext.getExternalFilesDir(null), "saves").listFiles() - - if(savesInternal != null) { - for(i in savesInternal){ - val safTarget = safDirectory.findFile(i.name) - if(safTarget != null) { - Timber.tag("Internal to SAF") - .e("Internal: ${i.name} - ${i.lastModified()}; SAF: ${safTarget.lastModified()}") - if (safTarget.lastModified() < i.lastModified()) { - Timber.tag("Internal to SAF").e("Internal is newer") - copyFromInternalToSaf(safTarget, i) - } else { - //Timber.tag("Internal to SAF").e("SAF is newer") - } - } else { - val newTarget = safDirectory.createFile("application/octet-stream", i.name) - if (newTarget != null) { - copyFromInternalToSaf(newTarget, i) - } - } - - } - } - } + + // copy from saf to internal + checkRemoteStorage(safDirectory) + + // now copy from internal to saf + checkLocalStorage(safDirectory) + } lastSyncTimestamp = System.currentTimeMillis() } @@ -142,6 +86,7 @@ class SaveSyncManagerImpl( } } } + private fun getInternalSaveFile(filename: String): File? { val saves = File(appContext.getExternalFilesDir(null), "saves") saves.mkdirs() @@ -155,6 +100,63 @@ class SaveSyncManagerImpl( return null } + + private fun checkRemoteStorage(safDirectory: DocumentFile) { + val saveFiles = safDirectory.listFiles() + + for(saveFile in saveFiles) { + if(!saveFile.name.isNullOrEmpty()) { + val internalTarget = getInternalSaveFile(saveFile.name!!) + if(internalTarget != null) { + Timber.tag("SAF to Internal") + .e("SAF: ${saveFile.name} - ${saveFile.lastModified()}; Internal: ${internalTarget.lastModified()}") + if (internalTarget.lastModified() < saveFile.lastModified()) { + Timber.tag("SAF to Internal").e("SAF is newer") + copyFromSafToInternal(saveFile, internalTarget) + } else { + //Timber.tag("SAF to Internal").e("Internal is newer") + } + } else { + Log.e("SAF to Internal", "test: ${saveFile.name} does not exist in internal storage") + val internalDir = File(appContext.getExternalFilesDir(null), "saves"); + val newTarget = File(internalDir, saveFile.name) + if (newTarget.createNewFile()) { + copyFromSafToInternal(saveFile, newTarget) + } else { + Timber.e("Could not create new file in internal storage") + } + } + } + } + } + + private fun checkLocalStorage(safDirectory: DocumentFile) { + // todo: check if there is a "saves"-constant + val internalSavefiles = File(appContext.getExternalFilesDir(null), "saves").listFiles() + + if(internalSavefiles != null) { + for(internalFile in internalSavefiles){ + val safTarget = safDirectory.findFile(internalFile.name) + if(safTarget != null) { + Timber.tag("Internal to SAF") + .e("Internal: ${internalFile.name} - ${internalFile.lastModified()}; SAF: ${safTarget.lastModified()}") + if (safTarget.lastModified() < internalFile.lastModified()) { + Timber.tag("Internal to SAF").e("Internal is newer") + copyFromInternalToSaf(safTarget, internalFile) + } else { + //Timber.tag("Internal to SAF").e("SAF is newer") + } + } else { + val newTarget = safDirectory.createFile("application/octet-stream", internalFile.name) + if (newTarget != null) { + copyFromInternalToSaf(newTarget, internalFile) + } + } + + } + } + } + private fun copyFromSafToInternal(saf: DocumentFile, internal: File) { val output: OutputStream = FileOutputStream(internal) val input: InputStream? = appContext.contentResolver.openInputStream(saf.uri) From 80a05db9ce704c1e6010d42d6f6bb61b41a51199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Mon, 10 Apr 2023 15:26:36 +0200 Subject: [PATCH 10/22] Refactor and document code --- .../feature/savesync/SaveSyncManagerImpl.kt | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index c1da6276ca..3b5c592790 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -76,7 +76,6 @@ class SaveSyncManagerImpl( // now copy from internal to saf checkLocalStorage(safDirectory) - } lastSyncTimestamp = System.currentTimeMillis() } @@ -87,20 +86,6 @@ class SaveSyncManagerImpl( } } - private fun getInternalSaveFile(filename: String): File? { - val saves = File(appContext.getExternalFilesDir(null), "saves") - saves.mkdirs() - if(saves != null) { - for(i in saves.listFiles()!!){ - if(i.name.equals(filename)) { - return File(saves, i.name) - } - } - } - return null - } - - private fun checkRemoteStorage(safDirectory: DocumentFile) { val saveFiles = safDirectory.listFiles() @@ -157,6 +142,23 @@ class SaveSyncManagerImpl( } } + private fun getInternalSaveFile(filename: String): File? { + val saves = File(appContext.getExternalFilesDir(null), "saves") + saves.mkdirs() + for(i in saves.listFiles()!!){ + if(i.name.equals(filename)) { + return File(saves, i.name) + } + } + return null + } + + /** + * Copy a file from the provided DocumentFile to File via copyFile(). + * File will get an updated timestamp, matching the older DocumentFile. + * File will have a backdated timestamp. This is so that the sync-client will + * not "backsync" the "newer" file in Internal Storage back to SAF even if both are identical. + */ private fun copyFromSafToInternal(saf: DocumentFile, internal: File) { val output: OutputStream = FileOutputStream(internal) val input: InputStream? = appContext.contentResolver.openInputStream(saf.uri) @@ -165,6 +167,11 @@ class SaveSyncManagerImpl( internal.setLastModified(saf.lastModified()) } + /** + * Copy a file from the provided File to DocumentFile via copyFile(). + * File will get an updated timestamp, matching the (new) DocumentFile. + * For the reasoning, see copyFromSafToInternal() + */ private fun copyFromInternalToSaf(saf: DocumentFile, internal: File) { val output: OutputStream? = appContext.contentResolver.openOutputStream(saf.uri) val input: InputStream = FileInputStream(internal) @@ -174,29 +181,30 @@ class SaveSyncManagerImpl( internal.setLastModified(saf.lastModified()) } - private fun copyFile(input: InputStream?, output: OutputStream?) { - if(input == null) { - Timber.e("Could not read source file!") + + /** + * This function writes from input to output. + * Null-checks are performed and caught. + */ + private fun copyFile(inputStream: InputStream?, outputStream: OutputStream?) { + if(inputStream == null) { + Timber.d("SaveSyncManagerImpl: copyFile: Could not read source file!") return } - if(output == null) { - Timber.e("Could not read target file!") + if(outputStream == null) { + Timber.d("SaveSyncManagerImpl: copyFile: Could not read target file!") return } - try { - try { - // Transfer bytes from in to out - val buf = ByteArray(1024) + inputStream.use { input -> + outputStream.use { output -> + // use 8k buffer for better performance + val buf = ByteArray(8192) var len: Int while (input.read(buf).also { len = it } > 0) { output.write(buf, 0, len) } - } finally { - output.close() } - } finally { - input.close() } } From 40aead7f880e645a9f883edf3bebf1b6fb1f9e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Mon, 10 Apr 2023 15:33:25 +0200 Subject: [PATCH 11/22] refactor and improve logging --- .../feature/savesync/SaveSyncManagerImpl.kt | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 3b5c592790..2b21a9ea8c 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -61,9 +61,12 @@ class SaveSyncManagerImpl( return storageUri } + /** + * Sync savegames. + * + * Todo: Sync states! + */ override suspend fun sync(cores: Set) { - - Log.e("TAG", "start") synchronized(SYNC_LOCK) { val saveSyncResult = runCatching { val safProviderUri = Uri.parse(storageUri) @@ -72,10 +75,10 @@ class SaveSyncManagerImpl( if (safDirectory != null) { // copy from saf to internal - checkRemoteStorage(safDirectory) + updateInternalStorage(safDirectory) // now copy from internal to saf - checkLocalStorage(safDirectory) + updateRemoteStorage(safDirectory) } lastSyncTimestamp = System.currentTimeMillis() } @@ -86,23 +89,17 @@ class SaveSyncManagerImpl( } } - private fun checkRemoteStorage(safDirectory: DocumentFile) { + private fun updateInternalStorage(safDirectory: DocumentFile) { val saveFiles = safDirectory.listFiles() - for(saveFile in saveFiles) { - if(!saveFile.name.isNullOrEmpty()) { + for (saveFile in saveFiles) { + if (!saveFile.name.isNullOrEmpty()) { val internalTarget = getInternalSaveFile(saveFile.name!!) - if(internalTarget != null) { - Timber.tag("SAF to Internal") - .e("SAF: ${saveFile.name} - ${saveFile.lastModified()}; Internal: ${internalTarget.lastModified()}") + if (internalTarget != null) { if (internalTarget.lastModified() < saveFile.lastModified()) { - Timber.tag("SAF to Internal").e("SAF is newer") copyFromSafToInternal(saveFile, internalTarget) - } else { - //Timber.tag("SAF to Internal").e("Internal is newer") } } else { - Log.e("SAF to Internal", "test: ${saveFile.name} does not exist in internal storage") val internalDir = File(appContext.getExternalFilesDir(null), "saves"); val newTarget = File(internalDir, saveFile.name) if (newTarget.createNewFile()) { @@ -111,25 +108,22 @@ class SaveSyncManagerImpl( Timber.e("Could not create new file in internal storage") } } + } else { + Timber.tag("SAF to Internal").d("Error: Remote file does not have a name!") } } } - private fun checkLocalStorage(safDirectory: DocumentFile) { + private fun updateRemoteStorage(safDirectory: DocumentFile) { // todo: check if there is a "saves"-constant val internalSavefiles = File(appContext.getExternalFilesDir(null), "saves").listFiles() - if(internalSavefiles != null) { - for(internalFile in internalSavefiles){ + if (internalSavefiles != null) { + for (internalFile in internalSavefiles) { val safTarget = safDirectory.findFile(internalFile.name) - if(safTarget != null) { - Timber.tag("Internal to SAF") - .e("Internal: ${internalFile.name} - ${internalFile.lastModified()}; SAF: ${safTarget.lastModified()}") + if (safTarget != null) { if (safTarget.lastModified() < internalFile.lastModified()) { - Timber.tag("Internal to SAF").e("Internal is newer") copyFromInternalToSaf(safTarget, internalFile) - } else { - //Timber.tag("Internal to SAF").e("SAF is newer") } } else { val newTarget = safDirectory.createFile("application/octet-stream", internalFile.name) @@ -137,7 +131,6 @@ class SaveSyncManagerImpl( copyFromInternalToSaf(newTarget, internalFile) } } - } } } @@ -145,8 +138,8 @@ class SaveSyncManagerImpl( private fun getInternalSaveFile(filename: String): File? { val saves = File(appContext.getExternalFilesDir(null), "saves") saves.mkdirs() - for(i in saves.listFiles()!!){ - if(i.name.equals(filename)) { + for (i in saves.listFiles()!!) { + if (i.name.equals(filename)) { return File(saves, i.name) } } @@ -187,11 +180,11 @@ class SaveSyncManagerImpl( * Null-checks are performed and caught. */ private fun copyFile(inputStream: InputStream?, outputStream: OutputStream?) { - if(inputStream == null) { + if (inputStream == null) { Timber.d("SaveSyncManagerImpl: copyFile: Could not read source file!") return } - if(outputStream == null) { + if (outputStream == null) { Timber.d("SaveSyncManagerImpl: copyFile: Could not read target file!") return } From 614fd672764ef69c5d6a2374d5f305e779d481d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Mon, 10 Apr 2023 15:44:49 +0200 Subject: [PATCH 12/22] compute remote space usage --- .../feature/savesync/SaveSyncManagerImpl.kt | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 2b21a9ea8c..c8c645a319 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -3,7 +3,7 @@ package com.swordfish.lemuroid.ext.feature.savesync import android.app.Activity import android.content.Context import android.net.Uri -import android.util.Log +import android.text.format.Formatter import androidx.documentfile.provider.DocumentFile import com.swordfish.lemuroid.common.kotlin.SharedPreferencesDelegates import com.swordfish.lemuroid.ext.R @@ -12,11 +12,7 @@ import com.swordfish.lemuroid.lib.preferences.SharedPreferencesHelper import com.swordfish.lemuroid.lib.savesync.SaveSyncManager import com.swordfish.lemuroid.lib.storage.DirectoriesManager import timber.log.Timber -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.InputStream -import java.io.OutputStream +import java.io.* import java.text.SimpleDateFormat @@ -201,15 +197,29 @@ class SaveSyncManagerImpl( } } + override fun computeSavesSpace(): String { + var size = 0L + val safProviderUri = Uri.parse(storageUri) + val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) - override fun computeSavesSpace() = "" + if (safDirectory != null) { - override fun computeStatesSpace(coreID: CoreID) = "" + val saveFiles = safDirectory.listFiles() + + for (saveFile in saveFiles) { + size += saveFile.length() + } + } + return Formatter.formatShortFileSize(appContext, size) + } + + override fun computeStatesSpace(coreID: CoreID): String { + return "0" + } companion object { const val GDRIVE_PROPERTY_LOCAL_PATH = "localPath" private val SYNC_LOCK = Object() } - } From 0009111cf6c8522fba783349f5c2a61eb7f498ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Thu, 13 Apr 2023 09:29:50 +0200 Subject: [PATCH 13/22] add newline to satisfy lint --- .../src/main/res/layout/activity_activate_safactivity.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemuroid-app-ext-free/src/main/res/layout/activity_activate_safactivity.xml b/lemuroid-app-ext-free/src/main/res/layout/activity_activate_safactivity.xml index c1230f1814..49cc207652 100644 --- a/lemuroid-app-ext-free/src/main/res/layout/activity_activate_safactivity.xml +++ b/lemuroid-app-ext-free/src/main/res/layout/activity_activate_safactivity.xml @@ -6,4 +6,4 @@ android:layout_height="match_parent" tools:context=".feature.savesync.ActivateSAFActivity"> - \ No newline at end of file + From 95ae1ee69dcb22e41f4117b66dde343516031bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Thu, 20 Apr 2023 15:23:56 +0200 Subject: [PATCH 14/22] migrate dependencies out of build.gradle.kts --- lemuroid-app-ext-free/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lemuroid-app-ext-free/build.gradle.kts b/lemuroid-app-ext-free/build.gradle.kts index ba9c63489e..2e2453506f 100644 --- a/lemuroid-app-ext-free/build.gradle.kts +++ b/lemuroid-app-ext-free/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { implementation(deps.libs.retrofit) implementation(deps.libs.kotlinxCoroutinesAndroid) - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.4.0") - implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation(deps.libs.androidx.appcompat.appcompat) + implementation(deps.libs.androidx.appcompat.constraintLayout) + implementation(deps.libs.material) } From d8a456f7ebfc3bb5daf6e6268df8636427186df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Thu, 20 Apr 2023 15:27:24 +0200 Subject: [PATCH 15/22] improve naming --- lemuroid-app-ext-free/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemuroid-app-ext-free/src/main/res/values/strings.xml b/lemuroid-app-ext-free/src/main/res/values/strings.xml index c7fb9e2a34..5d89e8f98b 100644 --- a/lemuroid-app-ext-free/src/main/res/values/strings.xml +++ b/lemuroid-app-ext-free/src/main/res/values/strings.xml @@ -7,6 +7,6 @@ The Libretro core was not loaded. Make sure your device is connected to internet and try to rescan your library. If the issue persist this core might not be supported on your device. Last sync: %s - SAF + External Storage You have not selected a folder. Sync unavailable! From 01e7a41ae20aefaa1718fd3c841bd037abb8979d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 7 May 2023 20:32:04 +0200 Subject: [PATCH 16/22] allow folders to be synced recursive to SAF --- .../feature/savesync/ActivateSAFActivity.kt | 2 +- .../feature/savesync/SaveSyncManagerImpl.kt | 49 ++++++++++++++----- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt index f5b147ef0d..158c224b15 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt @@ -33,7 +33,7 @@ class ActivateSAFActivity : AppCompatActivity() { if (targetUri != null ) { contentResolver.takePersistableUriPermission( targetUri, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION + Intent.FLAG_GRANT_WRITE_URI_PERMISSION and Intent.FLAG_GRANT_READ_URI_PERMISSION ) setStorageUri(targetUri.toString()) } diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index c8c645a319..01fed7c4a6 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.net.Uri import android.text.format.Formatter +import android.util.Log import androidx.documentfile.provider.DocumentFile import com.swordfish.lemuroid.common.kotlin.SharedPreferencesDelegates import com.swordfish.lemuroid.ext.R @@ -71,7 +72,7 @@ class SaveSyncManagerImpl( if (safDirectory != null) { // copy from saf to internal - updateInternalStorage(safDirectory) + //updateInternalStorage(safDirectory) // now copy from internal to saf updateRemoteStorage(safDirectory) @@ -112,21 +113,38 @@ class SaveSyncManagerImpl( private fun updateRemoteStorage(safDirectory: DocumentFile) { // todo: check if there is a "saves"-constant - val internalSavefiles = File(appContext.getExternalFilesDir(null), "saves").listFiles() + val internalSavefiles = File(appContext.getExternalFilesDir(null), "saves") + updateRemoteStorageFolder(internalSavefiles, safDirectory) + } + + private fun updateRemoteStorageFolder(currentInternalFolder: File, currentSafTarget: DocumentFile) { + val recursiveFiles = currentInternalFolder.listFiles() - if (internalSavefiles != null) { - for (internalFile in internalSavefiles) { - val safTarget = safDirectory.findFile(internalFile.name) + for (internalFile in recursiveFiles) { + if(internalFile.isFile) { + val safTarget = currentSafTarget.findFile(internalFile.name) if (safTarget != null) { if (safTarget.lastModified() < internalFile.lastModified()) { copyFromInternalToSaf(safTarget, internalFile) } } else { - val newTarget = safDirectory.createFile("application/octet-stream", internalFile.name) + val newTarget = currentSafTarget.createFile("application/octet-stream", internalFile.name) if (newTarget != null) { copyFromInternalToSaf(newTarget, internalFile) } } + } else { + + var targetFolder = currentSafTarget.findFile(internalFile.name) + if(targetFolder == null || !targetFolder.exists()){ + targetFolder = currentSafTarget.createDirectory(internalFile.name) + } + + if (targetFolder == null) { + Log.e("SaveSync", "Target is null. Skipping.") + return + } + updateRemoteStorageFolder( internalFile, targetFolder) } } } @@ -162,8 +180,11 @@ class SaveSyncManagerImpl( * For the reasoning, see copyFromSafToInternal() */ private fun copyFromInternalToSaf(saf: DocumentFile, internal: File) { + Log.e("SaveSync", "Copy to: "+saf.name) val output: OutputStream? = appContext.contentResolver.openOutputStream(saf.uri) val input: InputStream = FileInputStream(internal) + + copyFile(input, output) // update last modified timestamp to match @@ -199,16 +220,20 @@ class SaveSyncManagerImpl( override fun computeSavesSpace(): String { var size = 0L - val safProviderUri = Uri.parse(storageUri) - val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) + try { + val safProviderUri = Uri.parse(storageUri) + val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) - if (safDirectory != null) { + if (safDirectory != null) { - val saveFiles = safDirectory.listFiles() + val saveFiles = safDirectory.listFiles() - for (saveFile in saveFiles) { - size += saveFile.length() + for (saveFile in saveFiles) { + size += saveFile.length() + } } + } catch (e: Exception) { + e.printStackTrace() } return Formatter.formatShortFileSize(appContext, size) } From 5c9977558002c439755a1fd24f6702b3a0f67545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 7 May 2023 20:56:26 +0200 Subject: [PATCH 17/22] allow folders to be synced recursive from SAF --- lemuroid-app-ext-free/build.gradle.kts | 1 + .../feature/savesync/SaveSyncManagerImpl.kt | 64 ++++++++++++------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/lemuroid-app-ext-free/build.gradle.kts b/lemuroid-app-ext-free/build.gradle.kts index 2e2453506f..2ec08286f9 100644 --- a/lemuroid-app-ext-free/build.gradle.kts +++ b/lemuroid-app-ext-free/build.gradle.kts @@ -19,4 +19,5 @@ dependencies { implementation(deps.libs.androidx.appcompat.appcompat) implementation(deps.libs.androidx.appcompat.constraintLayout) implementation(deps.libs.material) + implementation("androidx.documentfile:documentfile:1.0.1") } diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 01fed7c4a6..214f2c96c8 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -72,7 +72,7 @@ class SaveSyncManagerImpl( if (safDirectory != null) { // copy from saf to internal - //updateInternalStorage(safDirectory) + updateInternalStorage(safDirectory) // now copy from internal to saf updateRemoteStorage(safDirectory) @@ -87,26 +87,38 @@ class SaveSyncManagerImpl( } private fun updateInternalStorage(safDirectory: DocumentFile) { - val saveFiles = safDirectory.listFiles() - - for (saveFile in saveFiles) { - if (!saveFile.name.isNullOrEmpty()) { - val internalTarget = getInternalSaveFile(saveFile.name!!) - if (internalTarget != null) { - if (internalTarget.lastModified() < saveFile.lastModified()) { - copyFromSafToInternal(saveFile, internalTarget) - } - } else { - val internalDir = File(appContext.getExternalFilesDir(null), "saves"); - val newTarget = File(internalDir, saveFile.name) - if (newTarget.createNewFile()) { - copyFromSafToInternal(saveFile, newTarget) + val internalSaves = File(appContext.getExternalFilesDir(null), "saves") + updateInternalStorageFolder(safDirectory, internalSaves) + } + + private fun updateInternalStorageFolder(currentSafTarget: DocumentFile, currentInternalFolder: File) { + for (saveFile in currentSafTarget.listFiles()) { + if(saveFile.isFile) { + if (!saveFile.name.isNullOrEmpty()) { + val internalTarget = getFileFromFolder(currentInternalFolder, saveFile.name.toString()) + if(internalTarget != null){ + if (internalTarget.lastModified() < saveFile.lastModified()) { + copyFromSafToInternal(saveFile, internalTarget) + } } else { - Timber.e("Could not create new file in internal storage") + val newTarget = File(currentInternalFolder, saveFile.name) + if (newTarget.createNewFile()) { + copyFromSafToInternal(saveFile, newTarget) + } else { + Timber.e("Could not create new file in internal storage") + } } + } else { + Timber.tag("SAF to Internal").d("Error: Remote file does not have a name!") } } else { - Timber.tag("SAF to Internal").d("Error: Remote file does not have a name!") + var internalFolder = getFileFromFolder(currentInternalFolder, saveFile.name.toString()) + if(internalFolder == null) { + internalFolder = File(currentInternalFolder.absolutePath+"/"+saveFile.name) + internalFolder.mkdirs() + } + + updateInternalStorageFolder(saveFile, internalFolder) } } } @@ -134,14 +146,13 @@ class SaveSyncManagerImpl( } } } else { - var targetFolder = currentSafTarget.findFile(internalFile.name) if(targetFolder == null || !targetFolder.exists()){ targetFolder = currentSafTarget.createDirectory(internalFile.name) } if (targetFolder == null) { - Log.e("SaveSync", "Target is null. Skipping.") + Timber.e("SaveSync", "Target is null. Skipping.") return } updateRemoteStorageFolder( internalFile, targetFolder) @@ -149,12 +160,14 @@ class SaveSyncManagerImpl( } } - private fun getInternalSaveFile(filename: String): File? { - val saves = File(appContext.getExternalFilesDir(null), "saves") - saves.mkdirs() - for (i in saves.listFiles()!!) { + private fun getFileFromFolder(folder: File, filename: String): File? { + if(!folder.isDirectory){ + return null + } + + for (i in folder.listFiles()) { if (i.name.equals(filename)) { - return File(saves, i.name) + return i } } return null @@ -167,6 +180,9 @@ class SaveSyncManagerImpl( * not "backsync" the "newer" file in Internal Storage back to SAF even if both are identical. */ private fun copyFromSafToInternal(saf: DocumentFile, internal: File) { + Log.e("SaveSync", "To: "+internal.name) + Log.e("SaveSync", "From: "+saf.name) + val output: OutputStream = FileOutputStream(internal) val input: InputStream? = appContext.contentResolver.openInputStream(saf.uri) copyFile(input, output) From 8e6cf87848bfb13fe4ce0302203b853cf9c18dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 7 May 2023 21:04:57 +0200 Subject: [PATCH 18/22] also support states --- .../feature/savesync/SaveSyncManagerImpl.kt | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index 214f2c96c8..f5bb390e71 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -72,10 +72,14 @@ class SaveSyncManagerImpl( if (safDirectory != null) { // copy from saf to internal - updateInternalStorage(safDirectory) + updateInternalStorage(safDirectory, "saves") // now copy from internal to saf - updateRemoteStorage(safDirectory) + updateRemoteStorage(safDirectory, "saves") + + //repeat for states + updateInternalStorage(safDirectory, "states") + updateRemoteStorage(safDirectory, "states") } lastSyncTimestamp = System.currentTimeMillis() } @@ -86,9 +90,16 @@ class SaveSyncManagerImpl( } } - private fun updateInternalStorage(safDirectory: DocumentFile) { - val internalSaves = File(appContext.getExternalFilesDir(null), "saves") - updateInternalStorageFolder(safDirectory, internalSaves) + private fun updateInternalStorage(safDirectory: DocumentFile, subdir: String) { + val internalSaves = File(appContext.getExternalFilesDir(null), subdir) + var safSubdir = safDirectory.findFile(subdir) + if(safSubdir == null) { + safSubdir = safDirectory.createDirectory(subdir) + } + + if (safSubdir != null) { + updateInternalStorageFolder(safSubdir, internalSaves) + } } private fun updateInternalStorageFolder(currentSafTarget: DocumentFile, currentInternalFolder: File) { @@ -123,10 +134,18 @@ class SaveSyncManagerImpl( } } - private fun updateRemoteStorage(safDirectory: DocumentFile) { + private fun updateRemoteStorage(safDirectory: DocumentFile, subdir: String) { // todo: check if there is a "saves"-constant - val internalSavefiles = File(appContext.getExternalFilesDir(null), "saves") - updateRemoteStorageFolder(internalSavefiles, safDirectory) + val internalSavefiles = File(appContext.getExternalFilesDir(null), subdir) + + var safSubdir = safDirectory.findFile(subdir) + if(safSubdir == null) { + safSubdir = safDirectory.createDirectory(subdir) + } + + if (safSubdir != null) { + updateRemoteStorageFolder(internalSavefiles, safSubdir) + } } private fun updateRemoteStorageFolder(currentInternalFolder: File, currentSafTarget: DocumentFile) { From 4d4e6dbb91fd2ca80cb77772c4f84218103c2e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Tue, 16 May 2023 21:55:21 +0200 Subject: [PATCH 19/22] add intents to persist uris --- .../lemuroid/ext/feature/savesync/ActivateSAFActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt index 158c224b15..4364051fa5 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt @@ -25,6 +25,7 @@ class ActivateSAFActivity : AppCompatActivity() { private fun openPicker() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION and Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> when (result.resultCode) { Activity.RESULT_OK -> { From 0eb24e1c751f31b2b050f0d9b8580e611fbfdbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Tue, 16 May 2023 22:17:47 +0200 Subject: [PATCH 20/22] calculate saved space for cores properly --- .../feature/savesync/SaveSyncManagerImpl.kt | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index f5bb390e71..b2ed1384d2 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -255,31 +255,53 @@ class SaveSyncManagerImpl( override fun computeSavesSpace(): String { var size = 0L - try { - val safProviderUri = Uri.parse(storageUri) - val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) + val safProviderUri = Uri.parse(storageUri) + val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) + val saves = safDirectory?.findFile("saves") - if (safDirectory != null) { + if (safDirectory != null) { + size = saves?.let { getSpaceForDirectory(it) } ?: 0L + } - val saveFiles = safDirectory.listFiles() + return Formatter.formatShortFileSize(appContext, size) + } - for (saveFile in saveFiles) { - size += saveFile.length() - } - } - } catch (e: Exception) { - e.printStackTrace() + override fun computeStatesSpace(coreID: CoreID): String { + var size = 0L + + val safProviderUri = Uri.parse(storageUri) + val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) + val states = safDirectory?.findFile("states") + val core = states?.findFile(coreID.coreName) + + if (safDirectory != null) { + size = core?.let { getSpaceForDirectory(it) } ?: 0L } + return Formatter.formatShortFileSize(appContext, size) } - override fun computeStatesSpace(coreID: CoreID): String { - return "0" + /** + * Calculate the size for a given folder, and its subdirectories. + * Only respects files, folders are not counted. + */ + private fun getSpaceForDirectory(safDirectory: DocumentFile): Long { + val files = safDirectory.listFiles() + var size = 0L + + for (file in files) { + if(file.isFile) { + size += file.length() + } else { + size += getSpaceForDirectory(file) + } + } + + return size } companion object { - const val GDRIVE_PROPERTY_LOCAL_PATH = "localPath" private val SYNC_LOCK = Object() } } From 95c508df0dd2609c7fdc67b0f2302ffa55f6f3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sat, 10 Jun 2023 16:27:18 +0200 Subject: [PATCH 21/22] catch exception for size calculation when sync is not set up --- .../feature/savesync/SaveSyncManagerImpl.kt | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt index b2ed1384d2..47680dc6a5 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/SaveSyncManagerImpl.kt @@ -255,13 +255,15 @@ class SaveSyncManagerImpl( override fun computeSavesSpace(): String { var size = 0L - val safProviderUri = Uri.parse(storageUri) - val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) - val saves = safDirectory?.findFile("saves") + try { + val safProviderUri = Uri.parse(storageUri) + val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) + val saves = safDirectory?.findFile("saves") - if (safDirectory != null) { - size = saves?.let { getSpaceForDirectory(it) } ?: 0L - } + if (safDirectory != null) { + size = saves?.let { getSpaceForDirectory(it) } ?: 0L + } + } catch (e: Exception){} return Formatter.formatShortFileSize(appContext, size) } @@ -269,14 +271,16 @@ class SaveSyncManagerImpl( override fun computeStatesSpace(coreID: CoreID): String { var size = 0L - val safProviderUri = Uri.parse(storageUri) - val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) - val states = safDirectory?.findFile("states") - val core = states?.findFile(coreID.coreName) + try { + val safProviderUri = Uri.parse(storageUri) + val safDirectory = DocumentFile.fromTreeUri(appContext.applicationContext, safProviderUri) + val states = safDirectory?.findFile("states") + val core = states?.findFile(coreID.coreName) - if (safDirectory != null) { - size = core?.let { getSpaceForDirectory(it) } ?: 0L - } + if (safDirectory != null) { + size = core?.let { getSpaceForDirectory(it) } ?: 0L + } + } catch (e: Exception){} return Formatter.formatShortFileSize(appContext, size) } From c3b47bf7f35f67a4f9477d9f061a5bfd27ea19da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20N=C3=BCsse?= Date: Sun, 11 Jun 2023 00:32:03 +0200 Subject: [PATCH 22/22] properly persist uri --- .../feature/savesync/ActivateSAFActivity.kt | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt index 4364051fa5..a273a4a65e 100644 --- a/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt +++ b/lemuroid-app-ext-free/src/main/java/com/swordfish/lemuroid/ext/feature/savesync/ActivateSAFActivity.kt @@ -2,6 +2,7 @@ package com.swordfish.lemuroid.ext.feature.savesync import android.app.Activity import android.content.Intent +import android.net.Uri import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Toast @@ -24,19 +25,22 @@ class ActivateSAFActivity : AppCompatActivity() { private fun openPicker() { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION and Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { + this.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + this.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + this.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + this.putExtra(Intent.EXTRA_LOCAL_ONLY, false) + } + val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> when (result.resultCode) { Activity.RESULT_OK -> { val targetUri = result?.data?.data if (targetUri != null ) { - contentResolver.takePersistableUriPermission( - targetUri, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION and Intent.FLAG_GRANT_READ_URI_PERMISSION - ) setStorageUri(targetUri.toString()) + updatePersistableUris(targetUri) } finish() } @@ -57,4 +61,16 @@ class ActivateSAFActivity : AppCompatActivity() { sharedPreferences.putString(preferenceKey, uri) sharedPreferences.apply() } + + private fun updatePersistableUris(uri: Uri) { + val contentResolver = applicationContext.contentResolver + + val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + + contentResolver.takePersistableUriPermission(uri, takeFlags) + } + } + +