diff --git a/__tests__/LoadedApp.snapshot.tsx b/__tests__/LoadedApp.snapshot.tsx index b9a2c929a..e2a8d93e2 100644 --- a/__tests__/LoadedApp.snapshot.tsx +++ b/__tests__/LoadedApp.snapshot.tsx @@ -13,7 +13,7 @@ import { ThemeType } from '../app/types'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { I18n } from 'i18n-js'; import { StackScreenProps } from '@react-navigation/stack'; -import { ServerType } from '../app/AppState'; +import { BackgroundType, ServerType } from '../app/AppState'; // Crea un mock para el constructor de I18n jest.mock('i18n-js', () => ({ @@ -121,10 +121,11 @@ describe('Component LoadedApp - test', () => { const sendAll = false; const privacy = false; const mode = 'basic'; - const background = { + const background: BackgroundType = { batches: 0, message: '', date: 0, + dateEnd: 0, }; const readOnly = false; const receive = render( diff --git a/__tests__/LoadingApp.snapshot.tsx b/__tests__/LoadingApp.snapshot.tsx index 55c628222..36a9ba9e0 100644 --- a/__tests__/LoadingApp.snapshot.tsx +++ b/__tests__/LoadingApp.snapshot.tsx @@ -13,7 +13,7 @@ import { ThemeType } from '../app/types'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { I18n } from 'i18n-js'; import { StackScreenProps } from '@react-navigation/stack'; -import { ServerType } from '../app/AppState'; +import { BackgroundType, ServerType } from '../app/AppState'; // Crea un mock para el constructor de I18n jest.mock('i18n-js', () => ({ @@ -121,10 +121,11 @@ describe('Component LoadingApp - test', () => { const sendAll = false; const privacy = false; const mode = 'basic'; - const background = { + const background: BackgroundType = { batches: 0, message: '', date: 0, + dateEnd: 0, }; const firstLaunchingMessage = false; const receive = render( diff --git a/android/app/build.gradle b/android/app/build.gradle index c53d08a18..986cf0229 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -144,9 +144,9 @@ android { //applicationId 'com.ZingoMobile' // @Test minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 131 // Real + versionCode 145 // Real //versionCode 117 // @Test - versionName "zingo-1.3.3" // Real + versionName "zingo-1.3.4" // Real missingDimensionStrategy 'react-native-camera', 'general' testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' @@ -297,6 +297,7 @@ dependencies { implementation jscFlavor } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.5.0" def work_version = "2.7.1" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a0b43f231..de88364e4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -38,7 +38,6 @@ - diff --git a/android/app/src/main/java/org/ZingoLabs/Zingo/BackgroundSync.kt b/android/app/src/main/java/org/ZingoLabs/Zingo/BackgroundSync.kt deleted file mode 100644 index 940058f97..000000000 --- a/android/app/src/main/java/org/ZingoLabs/Zingo/BackgroundSync.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.ZingoLabs.Zingo - -import android.content.Intent -import com.facebook.react.HeadlessJsTaskService -import com.facebook.react.bridge.Arguments -import com.facebook.react.jstasks.HeadlessJsTaskConfig - - class BackgroundSync : HeadlessJsTaskService() { - override fun getTaskConfig(intent: Intent): HeadlessJsTaskConfig? { - return intent.extras?.let { - HeadlessJsTaskConfig( - "BackgroundSync", - Arguments.fromBundle(it), - 0, // timeout for the task - true // optional: defines whether or not the task is allowed in foreground. - // Default is false - ) - } - } -} - diff --git a/android/app/src/main/java/org/ZingoLabs/Zingo/BackgroundSyncWorker.kt b/android/app/src/main/java/org/ZingoLabs/Zingo/BackgroundSyncWorker.kt new file mode 100644 index 000000000..26caf9213 --- /dev/null +++ b/android/app/src/main/java/org/ZingoLabs/Zingo/BackgroundSyncWorker.kt @@ -0,0 +1,254 @@ +package org.ZingoLabs.Zingo + +import android.content.Context +import android.os.Build +import androidx.work.Worker +import androidx.work.WorkerParameters +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.work.Constraints +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkManager +import java.io.File +import java.util.* +import org.json.JSONObject +import java.nio.charset.StandardCharsets +import com.facebook.react.bridge.ReactApplicationContext +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atTime +import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime +import kotlinx.datetime.until +import kotlin.random.Random +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes +import kotlin.time.DurationUnit +import kotlin.time.toDuration +import kotlin.time.toJavaDuration + +class BackgroundSyncWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + + @RequiresApi(Build.VERSION_CODES.O) + override fun doWork(): Result { + val reactContext = ReactApplicationContext(MainApplication.getAppContext()) + val rpcModule = RPCModule(reactContext) + + Log.i("SCHEDULED_TASK_RUN", "Task running") + + // save the background JSON file + val timeStampStart = Date().time / 1000 + val timeStampStrStart = timeStampStart.toString() + val jsonBackgroundStart = "{\"batches\": \"0\", \"message\": \"Starting OK.\", \"date\": \"$timeStampStrStart\", \"dateEnd\": \"0\"}" + rpcModule.saveBackgroundFile(jsonBackgroundStart) + Log.i("SCHEDULED_TASK_RUN", "background json file SAVED $jsonBackgroundStart") + + // checking if the wallet file exists + val exists: Boolean = walletExists() + + if (exists) { + RustFFI.initlogging() + + // check the Server, because the task can run without the App. + val balance = RustFFI.execute("balance", "") + Log.i("SCHEDULED_TASK_RUN", "Testing if server is active: $balance") + if (balance.lowercase().startsWith("error")) { + // this means this task is running with the App closed + loadWalletFile(rpcModule) + } else { + // this means the App is open, + // stop syncing first, just in case. + stopSyncingProcess() + } + + // interrupt sync to false, just in case it is true. + val noInterrupting = RustFFI.execute("interrupt_sync_after_batch", "false") + Log.i("SCHEDULED_TASK_RUN", "Not interrupting sync: $noInterrupting") + + // the task is running here blocking this execution until this process finished: + // 1. finished the syncing. + + Log.i("SCHEDULED_TASK_RUN", "sync BEGIN") + val syncing = RustFFI.execute("sync", "") + Log.i("SCHEDULED_TASK_RUN", "sync END: $syncing") + + } else { + Log.i("SCHEDULED_TASK_RUN", "No exists wallet file END") + // save the background JSON file + val timeStampError = Date().time / 1000 + val timeStampStrError = timeStampError.toString() + val jsonBackgroundError = "{\"batches\": \"0\", \"message\": \"No active wallet KO.\", \"date\": \"$timeStampStrStart\", \"dateEnd\": \"$timeStampStrError\"}" + rpcModule.saveBackgroundFile(jsonBackgroundError) + Log.i("SCHEDULED_TASK_RUN", "background json file SAVED $jsonBackgroundError") + return Result.failure() + + } + + // save the wallet file with the new data from the sync process + rpcModule.saveWallet() + Log.i("SCHEDULED_TASK_RUN", "wallet file SAVED") + + // save the background JSON file + val timeStampEnd = Date().time / 1000 + val timeStampStrEnd = timeStampEnd.toString() + val jsonBackgroundEnd = "{\"batches\": \"0\", \"message\": \"Finished OK.\", \"date\": \"$timeStampStrStart\", \"dateEnd\": \"$timeStampStrEnd\"}" + rpcModule.saveBackgroundFile(jsonBackgroundEnd) + Log.i("SCHEDULED_TASK_RUN", "background json file SAVED $jsonBackgroundEnd") + + return Result.success() + } + + private fun loadWalletFile(rpcModule: RPCModule) { + // I have to init from wallet file in order to do the sync + // and I need to read the settings.json to find the server & chain type + MainApplication.getAppContext()?.openFileInput("settings.json")?.use { file -> + val settingsBytes = file.readBytes() + file.close() + val settingsString = settingsBytes.toString(Charsets.UTF_8) + val jsonObject = JSONObject(settingsString) + val server = jsonObject.getJSONObject("server").getString("uri") + val chainhint = jsonObject.getJSONObject("server").getString("chain_name") + Log.i( + "SCHEDULED_TASK_RUN", + "Opening the wallet file - No App active - server: $server chain: $chainhint" + ) + rpcModule.loadExistingWalletNative(server, chainhint) + } + } + + private fun stopSyncingProcess() { + var status = RustFFI.execute("syncstatus", "") + Log.i("SCHEDULED_TASK_RUN", "status response $status") + + var data: ByteArray = status.toByteArray(StandardCharsets.UTF_8) + var jsonResp = JSONObject(String(data, StandardCharsets.UTF_8)) + var inProgressStr: String = jsonResp.optString("in_progress") + var inProgress: Boolean = inProgressStr.toBoolean() + + Log.i("SCHEDULED_TASK_RUN", "in progress value $inProgress") + + while (inProgress) { + // interrupt + val interrupting = RustFFI.execute("interrupt_sync_after_batch", "true") + Log.i("SCHEDULED_TASK_RUN", "Interrupting sync: $interrupting") + + // blocking the thread for 0.5 seconds. + Thread.sleep(500) + + status = RustFFI.execute("syncstatus", "") + Log.i("SCHEDULED_TASK_RUN", "status response $status") + + data = status.toByteArray(StandardCharsets.UTF_8) + jsonResp = JSONObject(String(data, StandardCharsets.UTF_8)) + inProgressStr = jsonResp.optString("in_progress") + inProgress = inProgressStr.toBoolean() + + Log.i("SCHEDULED_TASK_RUN", "in progress value $inProgress") + } + + Log.i("SCHEDULED_TASK_RUN", "sync process STOPPED") + + } + + private fun walletExists(): Boolean { + // Check if a wallet already exists + val file = File(MainApplication.getAppContext()?.filesDir, "wallet.dat") + return if (file.exists()) { + Log.i("SCHEDULED_TASK_RUN", "Wallet exists") + true + } else { + Log.i("SCHEDULED_TASK_RUN", "Wallet DOES NOT exist") + false + } + } +} + +class BSCompanion { + companion object { + private const val taskID = "Zingo_Processing_Task_ID" + private val SYNC_PERIOD = 24.hours + private val SYNC_DAY_SHIFT = 1.days // Move to tomorrow + private val SYNC_START_TIME_HOURS = 3.hours // Start around 3 a.m. at night + private val SYNC_START_TIME_MINUTES = 60.minutes // Randomize with minutes until 4 a.m. + @RequiresApi(Build.VERSION_CODES.O) + fun scheduleBackgroundTask() { + val reactContext = ReactApplicationContext(MainApplication.getAppContext()) + + // zancas requeriment, not plug-in, reverted. + val constraints = Constraints.Builder() + .setRequiresStorageNotLow(false) // less restricted + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresCharging(true) + .build() + + // PRODUCTION - next day between 3:00 and 4:00 am. + val targetTimeDiff = calculateTargetTimeDifference() + + Log.i("SCHEDULING_TASK", "calculated target time DIFF $targetTimeDiff") + + val workRequest = PeriodicWorkRequest.Builder(BackgroundSyncWorker::class.java, SYNC_PERIOD.toJavaDuration()) + .setConstraints(constraints) + .setInitialDelay(targetTimeDiff.toJavaDuration()) + .build() + + Log.i("SCHEDULING_TASK", "Enqueuing the background task - Background") + WorkManager.getInstance(reactContext) + .enqueueUniquePeriodicWork( + taskID, + ExistingPeriodicWorkPolicy.REPLACE, + workRequest + ) + + Log.i("SCHEDULING_TASK", "Task info ${WorkManager.getInstance(reactContext).getWorkInfosForUniqueWork( + taskID).get()}") + } + + private fun calculateTargetTimeDifference(): Duration { + val currentTimeZone: TimeZone = TimeZone.currentSystemDefault() + + val now: Instant = Clock.System.now() + + val targetTime = + now + .plus(SYNC_DAY_SHIFT) + .toLocalDateTime(currentTimeZone) + .date + .atTime( + hour = SYNC_START_TIME_HOURS.inWholeHours.toInt(), + // Even though the WorkManager will trigger the work approximately at the set time, it's + // better to randomize time in 3-4 a.m. This generates a number between 0 (inclusive) and 60 + // (exclusive) + minute = Random.nextInt(0, SYNC_START_TIME_MINUTES.inWholeMinutes.toInt()) + ) + + val targetTimeTime = targetTime.time + val targetTimeDate = targetTime.date + Log.i("SCHEDULING_TASK", "calculated target time $targetTimeTime and date $targetTimeDate") + + return now.until( + other = targetTime.toInstant(currentTimeZone), + unit = DateTimeUnit.MILLISECOND, + timeZone = currentTimeZone + ).toDuration(DurationUnit.MILLISECONDS) + } + + fun cancelExecutingTask() { + val reactContext = ReactApplicationContext(MainApplication.getAppContext()) + + // run interrupt sync, just in case. + val interrupting = RustFFI.execute("interrupt_sync_after_batch", "true") + Log.i("SCHEDULED_TASK_RUN", "Interrupting sync: $interrupting") + + Log.i("SCHEDULING_TASK", "Cancel background Task") + WorkManager.getInstance(reactContext) + .cancelUniqueWork(taskID) + } + + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/ZingoLabs/Zingo/MainActivity.kt b/android/app/src/main/java/org/ZingoLabs/Zingo/MainActivity.kt index 9633ecded..0342e1aeb 100644 --- a/android/app/src/main/java/org/ZingoLabs/Zingo/MainActivity.kt +++ b/android/app/src/main/java/org/ZingoLabs/Zingo/MainActivity.kt @@ -1,44 +1,46 @@ package org.ZingoLabs.Zingo -import android.content.Intent +import android.os.Build import android.os.Bundle import android.util.Log +import androidx.annotation.RequiresApi +import androidx.work.* import com.facebook.react.ReactActivity -import java.util.concurrent.TimeUnit + class MainActivity : ReactActivity() { /** * Returns the name of the main component registered from JavaScript. This is used to schedule * rendering of the component. */ - override fun getMainComponentName(): String? { + + private var isStarting = true; + override fun getMainComponentName(): String { return "Zingo!" } override fun onCreate(savedInstanceState: Bundle?) { - Log.w("", "Starting main activity") - val service = Intent(applicationContext, BackgroundSync::class.java) - applicationContext.stopService(service) + Log.i("ON_CREATE", "Starting main activity") super.onCreate(null) } + @RequiresApi(Build.VERSION_CODES.O) override fun onPause() { - Log.w("", "Pausing main activity") - val service = Intent(applicationContext, BackgroundSync::class.java) - val bundle = Bundle() - - bundle.putString("BS: start syncing", "Native") - - service.putExtras(bundle) - - applicationContext.startService(service) + Log.i("ON_PAUSE", "Pausing main activity - Background") + BSCompanion.scheduleBackgroundTask() super.onPause() - //val backgroundRequest = PeriodicWorkRequest.Builder(BackgroundWorker::class.java, 15, TimeUnit.MINUTES).build() - //WorkManager.getInstance(application).enqueue(backgroundRequest) } + @RequiresApi(Build.VERSION_CODES.O) override fun onResume() { - val service = Intent(applicationContext, BackgroundSync::class.java) - applicationContext.stopService(service) + Log.i("ON_RESUME", "Resuming main activity - Foreground") + // cancel the task if it is in execution now + if (isStarting) { + // this is the time the App is launching. + isStarting = false + } else { + BSCompanion.cancelExecutingTask() + } super.onResume() } + } \ No newline at end of file diff --git a/android/app/src/main/java/org/ZingoLabs/Zingo/RPCModule.kt b/android/app/src/main/java/org/ZingoLabs/Zingo/RPCModule.kt index e47dd66e6..bb130285e 100644 --- a/android/app/src/main/java/org/ZingoLabs/Zingo/RPCModule.kt +++ b/android/app/src/main/java/org/ZingoLabs/Zingo/RPCModule.kt @@ -3,7 +3,6 @@ package org.ZingoLabs.Zingo import android.content.Context import android.util.Log import android.util.Base64 -import androidx.work.PeriodicWorkRequest import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod @@ -12,15 +11,10 @@ import com.facebook.react.bridge.Promise //import android.util.Log import java.io.File import java.io.InputStream -import java.util.concurrent.TimeUnit import kotlin.concurrent.thread class RPCModule internal constructor(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - //companion object { - // const val TAG = "RPCModule" - //} - override fun getName(): String { return "RPCModule" } @@ -30,10 +24,10 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC // Check if a wallet already exists val file = File(MainApplication.getAppContext()?.filesDir, "wallet.dat") if (file.exists()) { - // Log.w("MAIN", "Wallet exists") + // Log.i("MAIN", "Wallet exists") promise.resolve(true) } else { - // Log.w("MAIN", "Wallet DOES NOT exist") + // Log.i("MAIN", "Wallet DOES NOT exist") promise.resolve(false) } } @@ -43,25 +37,25 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC // Check if a wallet backup already exists val file = File(MainApplication.getAppContext()?.filesDir, "wallet.backup.dat") if (file.exists()) { - // Log.w("MAIN", "Wallet backup exists") + // Log.i("MAIN", "Wallet backup exists") promise.resolve(true) } else { - // Log.w("MAIN", "Wallet backup DOES NOT exist") + // Log.i("MAIN", "Wallet backup DOES NOT exist") promise.resolve(false) } } @ReactMethod fun createNewWallet(server: String, chainhint: String, promise: Promise) { - // Log.w("MAIN", "Creating new wallet") + // Log.i("MAIN", "Creating new wallet") RustFFI.initlogging() // Create a seed val seed = RustFFI.initnew(server, reactContext.applicationContext.filesDir.absolutePath, chainhint, "true") - // Log.w("MAIN-Seed", seed) + // Log.i("MAIN-Seed", seed) - if (!seed.startsWith("Error")) { + if (!seed.lowercase().startsWith("error")) { saveWallet() } @@ -70,14 +64,14 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC @ReactMethod fun restoreWalletFromSeed(seed: String, birthday: String, server: String, chainhint: String, promise: Promise) { - // Log.w("MAIN", "Restoring wallet with seed $seed") + // Log.i("MAIN", "Restoring wallet with seed $seed") RustFFI.initlogging() val rseed = RustFFI.initfromseed(server, seed, birthday, reactContext.applicationContext.filesDir.absolutePath, chainhint, "true") - // Log.w("MAIN", rseed) + // Log.i("MAIN", rseed) - if (!rseed.startsWith("Error")) { + if (!rseed.lowercase().startsWith("Error")) { saveWallet() } @@ -86,14 +80,14 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC @ReactMethod fun restoreWalletFromUfvk(ufvk: String, birthday: String, server: String, chainhint: String, promise: Promise) { - // Log.w("MAIN", "Restoring wallet with ufvk $ufvk") + // Log.i("MAIN", "Restoring wallet with ufvk $ufvk") RustFFI.initlogging() val rufvk = RustFFI.initfromufvk(server, ufvk, birthday, reactContext.applicationContext.filesDir.absolutePath, chainhint, "true") - // Log.w("MAIN", rufvk) + // Log.i("MAIN", rufvk) - if (!rufvk.startsWith("Error")) { + if (!rufvk.lowercase().startsWith("Error")) { saveWallet() } @@ -102,13 +96,17 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC @ReactMethod fun loadExistingWallet(server: String, chainhint: String, promise: Promise) { + promise.resolve(loadExistingWalletNative(server, chainhint)) + } + + fun loadExistingWalletNative(server: String, chainhint: String): String { // Read the file val file: InputStream = MainApplication.getAppContext()?.openFileInput("wallet.dat")!! var fileBytes = file.readBytes() file.close() - val middle0w = 0 - val middle1w = 6000000 // 6_000_000 - 8 pieces + val middle0w = 0 + val middle1w = 6000000 // 6_000_000 - 8 pieces val middle2w = 12000000 val middle3w = 18000000 val middle4w = 24000000 @@ -119,34 +117,139 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC var fileb64 = StringBuilder("") if (middle8w <= middle1w) { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle0w, middle8w - middle0w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle0w, + middle8w - middle0w, + Base64.NO_WRAP + ) + ) } else { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle0w, middle1w - middle0w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle0w, + middle1w - middle0w, + Base64.NO_WRAP + ) + ) if (middle8w <= middle2w) { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle1w, middle8w - middle1w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle1w, + middle8w - middle1w, + Base64.NO_WRAP + ) + ) } else { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle1w, middle2w - middle1w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle1w, + middle2w - middle1w, + Base64.NO_WRAP + ) + ) if (middle8w <= middle3w) { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle2w, middle8w - middle2w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle2w, + middle8w - middle2w, + Base64.NO_WRAP + ) + ) } else { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle2w, middle3w - middle2w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle2w, + middle3w - middle2w, + Base64.NO_WRAP + ) + ) if (middle8w <= middle4w) { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle3w, middle8w - middle3w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle3w, + middle8w - middle3w, + Base64.NO_WRAP + ) + ) } else { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle3w, middle4w - middle3w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle3w, + middle4w - middle3w, + Base64.NO_WRAP + ) + ) if (middle8w <= middle5w) { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle4w, middle8w - middle4w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle4w, + middle8w - middle4w, + Base64.NO_WRAP + ) + ) } else { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle4w, middle5w - middle4w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle4w, + middle5w - middle4w, + Base64.NO_WRAP + ) + ) if (middle8w <= middle6w) { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle5w, middle8w - middle5w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle5w, + middle8w - middle5w, + Base64.NO_WRAP + ) + ) } else { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle5w, middle6w - middle5w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle5w, + middle6w - middle5w, + Base64.NO_WRAP + ) + ) if (middle8w <= middle7w) { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle6w, middle8w - middle6w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle6w, + middle8w - middle6w, + Base64.NO_WRAP + ) + ) } else { - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle6w, middle7w - middle6w, Base64.NO_WRAP)) - fileb64 = fileb64.append(Base64.encodeToString(fileBytes, middle7w, middle8w - middle7w, Base64.NO_WRAP)) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle6w, + middle7w - middle6w, + Base64.NO_WRAP + ) + ) + fileb64 = fileb64.append( + Base64.encodeToString( + fileBytes, + middle7w, + middle8w - middle7w, + Base64.NO_WRAP + ) + ) } } } @@ -157,13 +260,14 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC RustFFI.initlogging() - val wseed = RustFFI.initfromb64(server, + // Log.i("MAIN", wseed) + + return RustFFI.initfromb64( + server, fileb64.toString(), reactContext.applicationContext.filesDir.absolutePath, - chainhint, "true") - // Log.w("MAIN", wseed) - - promise.resolve(wseed) + chainhint, "true" + ) } @ReactMethod @@ -225,9 +329,9 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC RustFFI.initlogging() - // Log.w("send", "Trying to send $sendJSON") + // Log.i("send", "Trying to send $sendJSON") val result = RustFFI.execute("send", sendJSON) - // Log.w("send", "Send Result: $result") + // Log.i("send", "Send Result: $result") promise.resolve(result) } @@ -239,12 +343,12 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC RustFFI.initlogging() - // Log.w("execute", "Executing $cmd with $args") + // Log.i("execute", "Executing $cmd with $args") val resp = RustFFI.execute(cmd, args) - // Log.w("execute", "Response to $cmd : $resp") + // Log.i("execute", "Response to $cmd : $resp") // And save it if it was a sync - if (cmd == "sync" && !resp.startsWith("Error")) { + if (cmd == "sync" && !resp.lowercase().startsWith("Error")) { saveWallet() } @@ -266,14 +370,14 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC promise.resolve(true) } - private fun saveWallet() { + fun saveWallet() { // Get the encoded wallet file val b64encoded = RustFFI.save() - // Log.w("MAIN", b64encoded) + // Log.i("MAIN", b64encoded) try { val fileBytes = Base64.decode(b64encoded, Base64.NO_WRAP) - Log.w("MAIN", "file size: ${fileBytes.size} bytes") + Log.i("MAIN", "file size: ${fileBytes.size} bytes") // Save file to disk val file = MainApplication.getAppContext()?.openFileOutput("wallet.dat", Context.MODE_PRIVATE) @@ -291,11 +395,11 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC val fileRead = MainApplication.getAppContext()!!.openFileInput("wallet.dat") val fileBytes = fileRead.readBytes() // val fileb64 = Base64.encodeToString(fileBytes, Base64.NO_WRAP) - // Log.w("MAIN", b64encoded) + // Log.i("MAIN", b64encoded) try { // val fileBytes = Base64.decode(b64encoded, Base64.NO_WRAP) - // Log.w("MAIN", "file size${fileBytes.size}") + // Log.i("MAIN", "file size${fileBytes.size}") // Save file to disk val file = MainApplication.getAppContext()?.openFileOutput("wallet.backup.dat", Context.MODE_PRIVATE) @@ -306,9 +410,25 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC } } + fun saveBackgroundFile(json: String) { + // Log.i("MAIN", b64encoded) + + try { + val fileBytes: ByteArray = json.toByteArray() + Log.i("MAIN", "file background size: ${fileBytes.size} bytes") + + // Save file to disk + val file = MainApplication.getAppContext()?.openFileOutput("background.json", Context.MODE_PRIVATE) + file?.write(fileBytes) + file?.close() + } catch (e: IllegalArgumentException) { + Log.e("MAIN", "Couldn't save the background file") + } + } + @ReactMethod fun getLatestBlock(server: String, promise: Promise) { - // Log.w("MAIN", "Initialize Light Client") + // Log.i("MAIN", "Initialize Light Client") RustFFI.initlogging() diff --git a/android/build.gradle b/android/build.gradle index 6cae882ac..12b8eed52 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { compileSdkVersion = 33 targetSdkVersion = 33 ndkVersion = "23.2.8568313" - kotlinVersion = '1.6.21' + kotlinVersion = '1.9.0' } repositories { google() diff --git a/app/AppState/types/BackgroundType.ts b/app/AppState/types/BackgroundType.ts index 901a27fe0..4ed0c3acf 100644 --- a/app/AppState/types/BackgroundType.ts +++ b/app/AppState/types/BackgroundType.ts @@ -2,5 +2,6 @@ export default interface BackgroundType { batches: number; message: string; date: number; + dateEnd: number; // eslint-disable-next-line semi } diff --git a/app/BackgroundSync.ts b/app/BackgroundSync.ts deleted file mode 100644 index e051f7be9..000000000 --- a/app/BackgroundSync.ts +++ /dev/null @@ -1,129 +0,0 @@ -import RPCModule from './RPCModule'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import NetInfo, { NetInfoStateType } from '@react-native-community/netinfo'; -import { RPCSyncStatusType } from './rpc/types/RPCSyncStatusType'; - -const BackgroundSync = async (task_data: any) => { - // this not interact with the server. - const exists = await RPCModule.walletExists(); - - // only if exists the wallet file make sense to do the sync. - if (exists && exists !== 'false') { - // only if we have connection make sense to call RPCModule. - const networkState = await NetInfo.fetch(); - if ( - !networkState.isConnected || - networkState.type === NetInfoStateType.cellular || - (networkState.details !== null && networkState.details.isConnectionExpensive) - ) { - //console.log( - // 'BS: Not started (connected: ' + networkState.isConnected, - // +', type: ' + - // networkState.type + - // +', expensive connection: ' + - // networkState.details?.isConnectionExpensive + - // ')', - //); - return; - } - // if the App goes to Foreground kill the interval - const background = await AsyncStorage.getItem('@background'); - if (background === 'no') { - //console.log('BS: Not started (going to foreground)'); - return; - } - - let batch_num = -1; - console.log('BS:', task_data); - - // finishEarly has two fields: wait, and done. - // wait() returns a promise, which is resolved when - // done() is called - let finishEarly = manuallyResolve(); - - let saver = setInterval(async () => { - const networkStateSaver = await NetInfo.fetch(); - if ( - !networkStateSaver.isConnected || - networkStateSaver.type === NetInfoStateType.cellular || - (networkStateSaver.details !== null && networkStateSaver.details.isConnectionExpensive) - ) { - //console.log( - // 'BS: Interrupted (connected: ' + networkStateSaver.isConnected, - // +', type: ' + - // networkStateSaver.type + - // +', expensive connection: ' + - // networkStateSaver.details?.isConnectionExpensive + - // ')', - //); - clearInterval(saver); - finishEarly.done(); - return; - } - // if the App goes to Foreground kill the interval - const backgroundSaver = await AsyncStorage.getItem('@background'); - if (backgroundSaver === 'no') { - clearInterval(saver); - //console.log('BS: Finished (going to foreground)'); - finishEarly.done(); - return; - } - - const syncStatusStr: string = await RPCModule.execute('syncstatus', ''); - if (syncStatusStr) { - if (syncStatusStr.toLowerCase().startsWith('error')) { - //console.log(`BS: Error sync status ${syncStatusStr}`); - return; - } - } else { - //console.log('BS: Internal Error sync status'); - return; - } - - let ss = {} as RPCSyncStatusType; - try { - ss = await JSON.parse(syncStatusStr); - } catch (e) { - //console.log('BS: Error parsing syncstatus JSON', e); - return; - } - - //console.log('BS:', ss); - if (ss.batch_num && ss.batch_num > -1 && batch_num !== ss.batch_num) { - await RPCModule.doSave(); - //console.log('BS: saving...'); - // update batch_num with the new value, otherwise never change - batch_num = ss.batch_num; - } - }, 5000); - - await Promise.race([RPCModule.execute('sync', ''), finishEarly.wait()]); - clearInterval(saver); - } else { - console.log('BS: wallet file does not exist'); - } - //console.log('BS: Finished (end of syncing)'); -}; - -export default BackgroundSync; - -function manuallyResolve() { - let resolve: Function; - // new Promise takes a function as an argument. When that function is called - // the promise resolves with the value output by that function. - // By passing the function out of the promise, we can call it later - // in order to resolve the promise at will - const promise = new Promise(fun => { - resolve = fun; - }); - - function done() { - resolve(); - } - - function wait() { - return promise; - } - - return { wait, done }; -} diff --git a/app/LoadedApp/LoadedApp.tsx b/app/LoadedApp/LoadedApp.tsx index 42a7bd70f..79d4dfae3 100644 --- a/app/LoadedApp/LoadedApp.tsx +++ b/app/LoadedApp/LoadedApp.tsx @@ -9,7 +9,6 @@ import { EmitterSubscription, AppState, NativeEventSubscription, - Platform, Linking, SafeAreaView, } from 'react-native'; @@ -98,7 +97,7 @@ export default function LoadedApp(props: LoadedAppProps) { const [sendAll, setSendAll] = useState(false); const [privacy, setPrivacy] = useState(false); const [mode, setMode] = useState<'basic' | 'advanced'>('basic'); - const [background, setBackground] = useState({ batches: 0, message: '', date: 0 }); + const [background, setBackground] = useState({ batches: 0, message: '', date: 0, dateEnd: 0 }); const [loading, setLoading] = useState(true); const file = useMemo( () => ({ @@ -178,13 +177,10 @@ export default function LoadedApp(props: LoadedAppProps) { } // reading background task info - if (Platform.OS === 'ios') { - // this file only exists in IOS BS. - const backgroundJson = await BackgroundFileImpl.readBackground(); - //console.log('background', backgroundJson); - if (backgroundJson) { - setBackground(backgroundJson); - } + const backgroundJson = await BackgroundFileImpl.readBackground(); + //console.log('background', backgroundJson); + if (backgroundJson) { + setBackground(backgroundJson); } setLoading(false); })(); @@ -298,13 +294,10 @@ export class LoadedAppClass extends Component { if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') { //console.log('App has come to the foreground!'); - // deactivate the interruption sync flag - await RPC.rpc_setInterruptSyncAfterBatch('false'); + // deactivate the interruption sync flag. No needed. + //await RPC.rpc_setInterruptSyncAfterBatch('false'); // reading background task info - if (Platform.OS === 'ios') { - // this file only exists in IOS BS. - await this.fetchBackgroundSyncing(); - } + await this.fetchBackgroundSyncing(); // setting value for background task Android await AsyncStorage.setItem('@background', 'no'); //console.log('background no in storage'); @@ -317,6 +310,8 @@ export class LoadedAppClass extends Component(false); const [privacy, setPrivacy] = useState(false); const [mode, setMode] = useState<'basic' | 'advanced'>('advanced'); // by default advanced - const [background, setBackground] = useState({ batches: 0, message: '', date: 0 }); + const [background, setBackground] = useState({ batches: 0, message: '', date: 0, dateEnd: 0 }); const [firstLaunchingMessage, setFirstLaunchingMessage] = useState(false); const [loading, setLoading] = useState(true); const file = useMemo( @@ -168,12 +167,9 @@ export default function LoadingApp(props: LoadingAppProps) { //await delay(5000); // reading background task info - if (Platform.OS === 'ios') { - // this file only exists in IOS BS. - const backgroundJson = await BackgroundFileImpl.readBackground(); - if (backgroundJson) { - setBackground(backgroundJson); - } + const backgroundJson = await BackgroundFileImpl.readBackground(); + if (backgroundJson) { + setBackground(backgroundJson); } setLoading(false); })(); @@ -357,10 +353,7 @@ export class LoadingAppClass extends Component { - const backgroundJson = await BackgroundFileImpl.readBackground(); + const backgroundJson: BackgroundType = await BackgroundFileImpl.readBackground(); if (backgroundJson) { - this.setState({ - background: backgroundJson, - }); + this.setState({ background: backgroundJson }); } }; diff --git a/app/context/contextAppLoaded.tsx b/app/context/contextAppLoaded.tsx index 08d84dfda..0331e3e40 100644 --- a/app/context/contextAppLoaded.tsx +++ b/app/context/contextAppLoaded.tsx @@ -75,7 +75,9 @@ export const defaultAppStateLoaded: AppStateLoaded = { sendAll: false, background: { batches: 0, + message: '', date: 0, + dateEnd: 0, } as BackgroundType, translate: () => '', diff --git a/app/context/contextAppLoading.tsx b/app/context/contextAppLoading.tsx index cd6fcc9e6..f08648374 100644 --- a/app/context/contextAppLoading.tsx +++ b/app/context/contextAppLoading.tsx @@ -38,7 +38,9 @@ export const defaultAppStateLoading: AppStateLoading = { sendAll: false, background: { batches: 0, + message: '', date: 0, + dateEnd: 0, } as BackgroundType, translate: () => '', diff --git a/app/rpc/RPC.ts b/app/rpc/RPC.ts index f989c25e0..e9b12cbce 100644 --- a/app/rpc/RPC.ts +++ b/app/rpc/RPC.ts @@ -517,8 +517,17 @@ export default class RPC { return reducedDetailedTxns; } - // this is only for the first time when the App is booting. + // this is only for the first time when the App is booting, but + // there are more cases: + // - LoadedApp mounting component. + // - App go to Foreground. + // - Internet from Not Connected to Connected. + // - Cambio de Servidor. async configure(): Promise { + // First things first, I need to stop an existing sync process (if any) + // clean start. + await this.stopSyncProcess(); + // every 30 seconds the App try to Sync the new blocks. if (!this.refreshTimerID) { this.refreshTimerID = setInterval(() => { @@ -564,6 +573,40 @@ export default class RPC { }, 1000); } + sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); + + async stopSyncProcess(): Promise { + let returnStatus = await this.doSyncStatus(); + if (returnStatus.toLowerCase().startsWith('error')) { + return; + } + let ss = {} as RPCSyncStatusType; + try { + ss = await JSON.parse(returnStatus); + } catch (e) { + return; + } + + console.log('stop sync process. in progress', ss.in_progress); + + while (ss.in_progress) { + // interrupting sync process + await RPC.rpc_setInterruptSyncAfterBatch('true'); + + // sleep for half second + await this.sleep(500); + + returnStatus = await this.doSyncStatus(); + ss = await JSON.parse(returnStatus); + + console.log('stop sync process. in progress', ss.in_progress); + } + console.log('stop sync process. STOPPED'); + + // NOT interrupting sync process + await RPC.rpc_setInterruptSyncAfterBatch('false'); + } + async clearTimers(): Promise { if (this.refreshTimerID) { clearInterval(this.refreshTimerID); diff --git a/app/translations/en.json b/app/translations/en.json index 1ee7c99ef..4afda6409 100644 --- a/app/translations/en.json +++ b/app/translations/en.json @@ -1,6 +1,6 @@ { "zingo": "Zingo!", - "version": "zingo-1.3.4 (137)", + "version": "zingo-1.3.4 (145)", "loading": "loading...", "connectingserver": "Connecting to the server...", "wait": "Please wait...", diff --git a/app/translations/es.json b/app/translations/es.json index 1b238ee15..71a5c1b21 100644 --- a/app/translations/es.json +++ b/app/translations/es.json @@ -1,6 +1,6 @@ { "zingo": "Zingo!", - "version": "zingo-1.3.4 (137)", + "version": "zingo-1.3.4 (145)", "loading": "cargando...", "connectingserver": "Conectando con el servidor...", "wait": "Por favor espere...", diff --git a/components/Background/BackgroundFileImpl.ts b/components/Background/BackgroundFileImpl.ts index 4dcf38b1e..c6e10c952 100644 --- a/components/Background/BackgroundFileImpl.ts +++ b/components/Background/BackgroundFileImpl.ts @@ -10,7 +10,7 @@ export default class BackgroundFileImpl { // Write the server background static async reset() { const fileName = await this.getFileName(); - const newBackground: BackgroundType = { batches: 0, message: '', date: 0 }; + const newBackground: BackgroundType = { batches: 0, message: '', date: 0, dateEnd: 0 }; RNFS.writeFile(fileName, JSON.stringify(newBackground), 'utf8') .then(() => { @@ -34,7 +34,7 @@ export default class BackgroundFileImpl { } catch (err) { // File probably doesn't exist, so return nothing console.log('background json Error', err); - return { batches: 0, date: 0 } as BackgroundType; + return { batches: 0, message: '', date: 0, dateEnd: 0 } as BackgroundType; } } } diff --git a/components/SyncReport/SyncReport.tsx b/components/SyncReport/SyncReport.tsx index c2fa3ebf5..423e7affb 100644 --- a/components/SyncReport/SyncReport.tsx +++ b/components/SyncReport/SyncReport.tsx @@ -219,25 +219,29 @@ const SyncReport: React.FunctionComponent = ({ closeModal }) => /> )} - {Number(background.date) > 0 && showBackgroundLegend && ( - - - {!!background.message && {background.message}} - - )} + {(Number(background.date) > 0 || Number(background.dateEnd) > 0 || !!background.message) && + showBackgroundLegend && ( + + 0 + ? ' - ' + moment(Number(Number(background.dateEnd).toFixed(0)) * 1000).format('YYYY MMM D h:mm a') + : '') + } + /> + {!!background.message && {background.message}} + + )} {maxBlocks && netInfo.isConnected ? ( <> diff --git a/index.js b/index.js index 32168fcb8..9b7393291 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,5 @@ import { AppRegistry } from 'react-native'; import App from './App'; import { name as appName } from './app.json'; -import BackgroundSync from './app/BackgroundSync'; -AppRegistry.registerHeadlessTask('BackgroundSync', () => BackgroundSync); AppRegistry.registerComponent(appName, () => App); diff --git a/ios/ZingoMobile.xcodeproj/project.pbxproj b/ios/ZingoMobile.xcodeproj/project.pbxproj index 4b53f90ed..494835773 100644 --- a/ios/ZingoMobile.xcodeproj/project.pbxproj +++ b/ios/ZingoMobile.xcodeproj/project.pbxproj @@ -533,7 +533,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ZingoMobile/ZingoMobile.entitlements; - CURRENT_PROJECT_VERSION = 137; + CURRENT_PROJECT_VERSION = 145; DEVELOPMENT_TEAM = 788KRST4S8; ENABLE_BITCODE = NO; EXCLUDED_ARCHS = ""; @@ -573,7 +573,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ZingoMobile/ZingoMobile.entitlements; - CURRENT_PROJECT_VERSION = 137; + CURRENT_PROJECT_VERSION = 145; DEVELOPMENT_TEAM = 788KRST4S8; ENABLE_BITCODE = NO; EXCLUDED_ARCHS = ""; diff --git a/ios/ZingoMobile/AppDelegate.m b/ios/ZingoMobile/AppDelegate.m index e6f72f75c..023d4cba0 100644 --- a/ios/ZingoMobile/AppDelegate.m +++ b/ios/ZingoMobile/AppDelegate.m @@ -31,11 +31,10 @@ static void InitializeFlipper(UIApplication *application) { @implementation AppDelegate -static NSString* syncTask = @"Zingo_Processing_Task_ID"; -static NSString* syncSchedulerTask = @"Zingo_Processing_Scheduler_Task_ID"; -static BOOL isConnectedToWifi = false; -static BOOL isCharging = false; -static BGProcessingTask *bgTask = nil; +NSString* syncTask = @"Zingo_Processing_Task_ID"; +NSString* syncSchedulerTask = @"Zingo_Processing_Scheduler_Task_ID"; +BGProcessingTask *bgTask = nil; +NSString* timeStampStrStart = nil; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { @@ -57,8 +56,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [self.window makeKeyAndVisible]; if (@available(iOS 13.0, *)) { - NSLog(@"BGTask handleBackgroundTask"); - [self handleBackgroundTask]; + NSLog(@"BGTask registerTasks"); + [self registerTasks]; } return YES; @@ -134,36 +133,36 @@ -(void)stopSyncingProcess:(NSString *)noValue { @autoreleasepool { NSLog(@"BGTask stopSyncingProcess"); - char *resp = execute("syncstatus", ""); - NSString* respStr = [NSString stringWithUTF8String:resp]; - rust_free(resp); - NSLog(@"BGTask stopSyncingProcess - status response %@", respStr); + char *status = execute("syncstatus", ""); + NSString* statusStr = [NSString stringWithUTF8String:status]; + rust_free(status); + NSLog(@"BGTask stopSyncingProcess - status response %@", statusStr); - if ([respStr hasPrefix:@"Error"]) { + if ([statusStr hasPrefix:@"Error"]) { NSLog(@"BGTask stopSyncingProcess - no lightwalled likely"); return; } - NSData *data = [respStr dataUsingEncoding:NSUTF8StringEncoding]; + NSData *data = [statusStr dataUsingEncoding:NSUTF8StringEncoding]; id jsonResp = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; NSString *inProgressStr = [jsonResp valueForKey:@"in_progress"]; BOOL inProgress = [inProgressStr boolValue]; while(inProgress) { - char *resp2 = execute("interrupt_sync_after_batch", "true"); - NSString* respStr2 = [NSString stringWithUTF8String:resp2]; - NSLog(@"BGTask stopSyncingProcess - interrupt syncing %@", respStr2); + char *interrupt = execute("interrupt_sync_after_batch", "true"); + NSString* interruptStr = [NSString stringWithUTF8String:interrupt]; + NSLog(@"BGTask stopSyncingProcess - interrupt syncing %@", interruptStr); [NSThread sleepForTimeInterval: 0.5]; - char *resp = execute("syncstatus", ""); - NSString* respStr = [NSString stringWithUTF8String:resp]; - rust_free(resp); - NSLog(@"BGTask stopSyncingProcess - status response %@", respStr); + status = execute("syncstatus", ""); + statusStr = [NSString stringWithUTF8String:status]; + rust_free(status); + NSLog(@"BGTask stopSyncingProcess - status response %@", statusStr); - NSData *data = [respStr dataUsingEncoding:NSUTF8StringEncoding]; - id jsonResp = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - NSString *inProgressStr = [jsonResp valueForKey:@"in_progress"]; + data = [statusStr dataUsingEncoding:NSUTF8StringEncoding]; + jsonResp = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + inProgressStr = [jsonResp valueForKey:@"in_progress"]; inProgress = [inProgressStr boolValue]; } @@ -177,16 +176,27 @@ -(void)syncingProcessBackgroundTask:(NSString *)noValue { //do things with task @autoreleasepool { + RPCModule *rpcmodule = [RPCModule new]; + + // save info in background json + NSTimeInterval timeStampStart = [[NSDate date] timeIntervalSince1970]; + // NSTimeInterval is defined as double + NSNumber *timeStampObjStart = [NSNumber numberWithDouble: timeStampStart]; + timeStampStrStart = [timeStampObjStart stringValue]; + NSString *jsonBackgroudStart = [NSString stringWithFormat: @"%@%@%@%@%@%@%@%@%@", @"{\"batches\": \"", @"0", @"\", \"message\": \"", @"Starting OK.", @"\", \"date\": \"", timeStampStrStart, @"\", \"dateEnd\": \"", @"0", @"\"}"]; + [rpcmodule saveBackgroundFile:jsonBackgroudStart]; + NSLog(@"BGTask syncingProcessBackgroundTask - Save background JSON %@", jsonBackgroudStart); + NSLog(@"BGTask syncingProcessBackgroundTask"); BOOL exists = [self wallet__exists]; if (exists) { // check the Server, because the task can run without the App. - char *bal = execute("balance", ""); - NSString* balStr = [NSString stringWithUTF8String:bal]; - NSLog(@"BGTask syncingProcessBackgroundTask - testing if server is active %@", balStr); - rust_free(bal); - if ([balStr hasPrefix:@"Error"]) { + char *balance = execute("balance", ""); + NSString* balanceStr = [NSString stringWithUTF8String:balance]; + NSLog(@"BGTask syncingProcessBackgroundTask - testing if server is active %@", balanceStr); + rust_free(balance); + if ([balanceStr hasPrefix:@"Error"]) { // this means this task is running with the App closed [self loadWalletFile:nil]; } else { @@ -196,10 +206,10 @@ -(void)syncingProcessBackgroundTask:(NSString *)noValue { } // we need to sync without interruption, I run this just in case - char *resp = execute("interrupt_sync_after_batch", "false"); - NSString* respStr = [NSString stringWithUTF8String:resp]; - NSLog(@"BGTask syncingProcessBackgroundTask - no interrupt syncing %@", respStr); - rust_free(resp); + char *noInterrupt = execute("interrupt_sync_after_batch", "false"); + NSString* noInterruptStr = [NSString stringWithUTF8String:noInterrupt]; + NSLog(@"BGTask syncingProcessBackgroundTask - no interrupt syncing %@", noInterruptStr); + rust_free(noInterrupt); // the task is running here blocking this execution until this process finished: // 1. finished the syncing. @@ -207,15 +217,27 @@ -(void)syncingProcessBackgroundTask:(NSString *)noValue { NSLog(@"BGTask syncingProcessBackgroundTask - sync BEGIN"); - char *resp2 = execute("sync", ""); - NSString* respStr2 = [NSString stringWithUTF8String:resp2]; - rust_free(resp2); + char *syncing = execute("sync", ""); + NSString* syncingStr = [NSString stringWithUTF8String:syncing]; + rust_free(syncing); - NSLog(@"BGTask syncingProcessBackgroundTask - sync END %@", respStr2); + NSLog(@"BGTask syncingProcessBackgroundTask - sync END %@", syncingStr); } else { NSLog(@"BGTask syncingProcessBackgroundTask - No exists wallet file END"); + // save info in background json + NSTimeInterval timeStampError = [[NSDate date] timeIntervalSince1970]; + // NSTimeInterval is defined as double + NSNumber *timeStampObjError = [NSNumber numberWithDouble: timeStampError]; + NSString *timeStampStrError = [timeStampObjError stringValue]; + NSString *jsonBackgroudError = [NSString stringWithFormat: @"%@%@%@%@%@%@%@%@%@", @"{\"batches\": \"", @"0", @"\", \"message\": \"", @"No active wallet KO.", @"\", \"date\": \"", timeStampStrStart, @"\", \"dateEnd\": \"", timeStampStrError, @"\"}"]; + [rpcmodule saveBackgroundFile:jsonBackgroudError]; + NSLog(@"BGTask syncingProcessBackgroundTask - Save background JSON %@", jsonBackgroudError); + + [bgTask setTaskCompletedWithSuccess:NO]; + bgTask = nil; + return; } @@ -225,18 +247,17 @@ -(void)syncingProcessBackgroundTask:(NSString *)noValue { // I'm gessing NO. // save the wallet - RPCModule *rpcmodule = [RPCModule new]; [rpcmodule saveWalletInternal]; NSLog(@"BGTask syncingProcessBackgroundTask - Save Wallet"); // save info in background json - NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970]; + NSTimeInterval timeStampEnd = [[NSDate date] timeIntervalSince1970]; // NSTimeInterval is defined as double - NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp]; - NSString *timeStampStr = [timeStampObj stringValue]; - NSString *jsonBackgroud = [NSString stringWithFormat: @"%@%@%@%@%@%@%@", @"{\"batches\": \"", @"0", @"\", \"message\": \"", @"Finished OK.", @"\", \"date\": \"", timeStampStr, @"\"}"]; - [rpcmodule saveBackgroundFile:jsonBackgroud]; - NSLog(@"BGTask syncingProcessBackgroundTask - Save background JSON"); + NSNumber *timeStampObjEnd = [NSNumber numberWithDouble: timeStampEnd]; + NSString *timeStampStrEnd = [timeStampObjEnd stringValue]; + NSString *jsonBackgroudEnd = [NSString stringWithFormat: @"%@%@%@%@%@%@%@%@%@", @"{\"batches\": \"", @"0", @"\", \"message\": \"", @"Finished OK.", @"\", \"date\": \"", timeStampStrStart, @"\", \"dateEnd\": \"", timeStampStrEnd, @"\"}"]; + [rpcmodule saveBackgroundFile:jsonBackgroudEnd]; + NSLog(@"BGTask syncingProcessBackgroundTask - Save background JSON %@", jsonBackgroudEnd); [bgTask setTaskCompletedWithSuccess:YES]; bgTask = nil; @@ -289,32 +310,6 @@ -(BOOL)wallet__exists { // NEW BACKGROUND SCHEDULING TASKS -- (void)handleBackgroundTask { - // We require the background task to run when connected to the power and wifi - Reachability *reachability = [Reachability reachabilityForInternetConnection]; - NetworkStatus networkStatus = [reachability currentReachabilityStatus]; - - if (networkStatus == ReachableViaWiFi) { - // the device have Wifi. - isConnectedToWifi = true; - } else { - isConnectedToWifi = false; - } - - UIDeviceBatteryState currentState = [[UIDevice currentDevice] batteryState]; - - if (currentState == UIDeviceBatteryStateCharging) { - // The battery is either charging, or connected to a charger. - isCharging = true; - } else { - isCharging = false; - } - - NSLog(@"BGTask isConnectedToWifi %@ isCharging %@", isConnectedToWifi ? @"true" : @"false", isCharging ? @"true" : @"false"); - - [self registerTasks]; -} - - (void)registerTasks { BOOL bcgSyncTaskResult; bcgSyncTaskResult = [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:syncTask usingQueue:dispatch_get_main_queue() @@ -346,42 +341,11 @@ - (void)registerTasks { - (void)startBackgroundTask:(NSString *)noValue { NSLog(@"BGTask startBackgroundTask called"); - RPCModule *rpcmodule = [RPCModule new]; // Schedule tasks for the next time [self scheduleBackgroundTask]; [self scheduleSchedulerBackgroundTask]; - if (!isConnectedToWifi) { - // save info in background json - NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970]; - // NSTimeInterval is defined as double - NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp]; - NSString *timeStampStr = [timeStampObj stringValue]; - NSString *jsonBackgroud = [NSString stringWithFormat: @"%@%@%@%@%@%@%@", @"{\"batches\": \"", @"0", @"\", \"message\": \"", @"No wifi KO.", @"\", \"date\": \"", timeStampStr, @"\"}"]; - [rpcmodule saveBackgroundFile:jsonBackgroud]; - - NSLog(@"BGTask startBackgroundTask: not connected to the wifi"); - [bgTask setTaskCompletedWithSuccess:NO]; - bgTask = nil; - return; - } - - if (!isCharging) { - // save info in background json - NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970]; - // NSTimeInterval is defined as double - NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp]; - NSString *timeStampStr = [timeStampObj stringValue]; - NSString *jsonBackgroud = [NSString stringWithFormat: @"%@%@%@%@%@%@%@", @"{\"batches\": \"", @"0", @"\", \"message\": \"", @"No plug-in KO.", @"\", \"date\": \"", timeStampStr, @"\"}"]; - [rpcmodule saveBackgroundFile:jsonBackgroud]; - - NSLog(@"BGTask startBackgroundTask: not plug in to power"); - [bgTask setTaskCompletedWithSuccess:NO]; - bgTask = nil; - return; - } - // Start the syncing NSLog(@"BGTask startBackgroundTask run sync task"); // in order to run only one task @@ -393,12 +357,13 @@ - (void)startBackgroundTask:(NSString *)noValue { NSLog(@"BGTask startBackgroundTask - expirationHandler called"); // interrupting the sync process, I can't wait to see if the process is over // because I have no time enough to run all I need in this task. - char *resp2 = execute("interrupt_sync_after_batch", "true"); - NSString* respStr2 = [NSString stringWithUTF8String:resp2]; - NSLog(@"BGTask startBackgroundTask - expirationHandler interrupt syncing %@", respStr2); + char *interrupt = execute("interrupt_sync_after_batch", "true"); + NSString* interruptStr = [NSString stringWithUTF8String:interrupt]; + NSLog(@"BGTask startBackgroundTask - expirationHandler interrupt syncing %@", interruptStr); + + RPCModule *rpcmodule = [RPCModule new]; // save the wallet - RPCModule *rpcmodule = [RPCModule new]; [rpcmodule saveWalletInternal]; NSLog(@"BGTask startBackgroundTask - expirationHandler Save Wallet"); @@ -407,9 +372,9 @@ - (void)startBackgroundTask:(NSString *)noValue { // NSTimeInterval is defined as double NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp]; NSString *timeStampStr = [timeStampObj stringValue]; - NSString *jsonBackgroud = [NSString stringWithFormat: @"%@%@%@%@%@%@%@", @"{\"batches\": \"", @"0", @"\", \"message\": \"", @"Expiration fired. Finished OK.", @"\", \"date\": \"", timeStampStr, @"\"}"]; + NSString *jsonBackgroud = [NSString stringWithFormat: @"%@%@%@%@%@%@%@%@%@", @"{\"batches\": \"", @"0", @"\", \"message\": \"", @"Expiration fired. Finished OK.", @"\", \"date\": \"", timeStampStrStart, @"\", \"dateEnd\": \"", timeStampStr, @"\"}"]; [rpcmodule saveBackgroundFile:jsonBackgroud]; - NSLog(@"BGTask startBackgroundTask - expirationHandler Save background JSON"); + NSLog(@"BGTask startBackgroundTask - expirationHandler Save background JSON %@", jsonBackgroud); [bgTask setTaskCompletedWithSuccess:NO]; bgTask = nil; @@ -432,16 +397,11 @@ - (void)scheduleBackgroundTask { earlyMorningComponent.hour = 3; earlyMorningComponent.minute = arc4random_uniform(61); NSDate *earlyMorning = [[NSCalendar currentCalendar] dateByAddingComponents:earlyMorningComponent toDate:tomorrow options:0]; - - // DEVELOPMENT - //NSDate *now = [NSDate date]; - - //NSDate *twoMinutesLater = [now dateByAddingTimeInterval:120]; // 2 minutes = 120 seconds NSLog(@"BGTask scheduleBackgroundTask date calculated: %@", earlyMorning); request.earliestBeginDate = earlyMorning; - //request.earliestBeginDate = twoMinutesLater; + // zancas requeriment, not plug-in, reverted. request.requiresExternalPower = YES; request.requiresNetworkConnectivity = YES; @@ -469,14 +429,8 @@ - (void)scheduleSchedulerBackgroundTask { afternoonComponent.hour = 14; afternoonComponent.minute = arc4random_uniform(61); NSDate *afternoon = [[NSCalendar currentCalendar] dateByAddingComponents:afternoonComponent toDate:tomorrow options:0]; - - // DEVELOPMENT - //NSDate *now = [NSDate date]; - - //NSDate *fiveMinutesLater = [now dateByAddingTimeInterval:300]; // 5 minutes = 300 seconds request.earliestBeginDate = afternoon; - //request.earliestBeginDate = fiveMinutesLater; request.requiresExternalPower = NO; request.requiresNetworkConnectivity = NO; diff --git a/package.json b/package.json index d1004c2da..a064eec1c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "lint": "eslint . --fix --ext .js,.jsx,.ts,.tsx", "coverage": "jest --coverage", "postinstall": "patch-package", - "build:bundle": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res && rimraf --glob android/app/src/main/res/drawable* && cd android && ./gradlew assembleRelease -PsplitApk=true && cd .." + "build:bundle": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res && rimraf --glob android/app/src/main/res/drawable* && cd android && ./gradlew assembleRelease -PsplitApk=true && cd ..", + "bundle": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res && rimraf --glob android/app/src/main/res/drawable*" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.2.0",