From 4583b5e12a1573ffb06c27026b1eb26fcdbc4f85 Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 14 Nov 2023 14:09:47 +0100 Subject: [PATCH] Change the frequency of backups to 10 hours after the last change --- .../android/calculus/ReportWhistleBlower.kt | 10 +-- .../CashGameOptimalDurationCalculator.kt | 10 +-- .../model/extensions/SessionExtensions.kt | 1 - .../android/ui/fragment/StatisticsFragment.kt | 6 +- .../report/ComposableTableReportFragment.kt | 2 +- .../fragment/report/ProgressReportFragment.kt | 2 +- .../ui/modules/data/EditableDataActivity.kt | 8 +- .../ui/modules/session/SessionActivity.kt | 8 +- .../android/util/BackupOperator.kt | 78 +++++++------------ .../pokeranalytics/android/util/BackupTask.kt | 75 ++++++++++++++++++ .../csv/SessionTransactionCSVDescriptor.kt | 41 ++++++---- 11 files changed, 152 insertions(+), 89 deletions(-) create mode 100644 app/src/main/java/net/pokeranalytics/android/util/BackupTask.kt diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt b/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt index 112093d9..f7504c4f 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt @@ -62,7 +62,7 @@ class ReportWhistleBlower(var context: Context) { } fun requestReportLaunch() { - Timber.d(">>> Launch report") +// Timber.d(">>> Launch report") if (paused) { return @@ -171,7 +171,7 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co private fun launchReport(realm: Realm, report: StaticReport) { - Timber.d(">>> launch report = $report") +// Timber.d(">>> launch report = $report") when (report) { StaticReport.OptimalDuration -> launchOptimalDuration(realm, report) @@ -202,7 +202,7 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co for (stat in result.options.stats) { - Timber.d("analyse stat: $stat for report: $staticReport") +// Timber.d("analyse stat: $stat for report: $staticReport") // Get current performance var query = performancesQuery(realm, staticReport, stat) @@ -261,10 +261,10 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co } } ?: run { // if there is no max but a now irrelevant Performance, we delete it - Timber.d("NO best computed value, current perf = $currentPerf ") +// Timber.d("NO best computed value, current perf = $currentPerf ") currentPerf?.let { perf -> realm.executeTransaction { - Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}") +// Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}") perf.deleteFromRealm() } } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt index 1ec0797d..ffdc00da 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt @@ -65,7 +65,7 @@ class CashGameOptimalDurationCalculator { var validBuckets = 0 val hkeys = sessionsByDuration.keys.map { it / 3600 / 1000.0 }.sorted() - Timber.d("Stop notif > keys: $hkeys ") +// Timber.d("Stop notif > keys: $hkeys ") for (key in sessionsByDuration.keys.sorted()) { val sessionCount = sessionsByDuration[key]?.size ?: 0 if (start == null && sessionCount >= minimumValidityCount) { @@ -76,15 +76,15 @@ class CashGameOptimalDurationCalculator { validBuckets++ } } - Timber.d("Stop notif > validBuckets: $validBuckets ") +// Timber.d("Stop notif > validBuckets: $validBuckets ") if (!(start != null && end != null && (end - start) >= intervalValidity)) { - Timber.d("Stop notif > invalid setup: $start / $end ") +// Timber.d("Stop notif > invalid setup: $start / $end ") return null } // define if we have enough sessions if (sessions.size < 50) { - Timber.d("Stop notif > not enough sessions: ${sessions.size} ") +// Timber.d("Stop notif > not enough sessions: ${sessions.size} ") return null } @@ -134,7 +134,7 @@ class CashGameOptimalDurationCalculator { return bestDuration } - Timber.d("Stop notif > not found, best duration: $bestDuration") +// Timber.d("Stop notif > not found, best duration: $bestDuration") realm.close() return null } diff --git a/app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt b/app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt index 8a020e90..cd8f8214 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt @@ -120,7 +120,6 @@ fun Session.scheduleStopNotification(context: Context, optimalDuration: Long) { .addTag(this.id) .build() -// WorkManager.getInstance(context).enqueue(work) WorkManager.getInstance(context).enqueueUniqueWork(this.id, ExistingWorkPolicy.REPLACE, work) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt index b32e041e..cf3fc188 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt @@ -162,7 +162,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener { val async = GlobalScope.async { val s = Date() - Timber.d(">>> start...") +// Timber.d(">>> start...") val realm = Realm.getDefaultInstance() realm.refresh() @@ -174,7 +174,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener { val e = Date() val duration = (e.time - s.time) / 1000.0 - Timber.d(">>> ended in $duration seconds") +// Timber.d(">>> ended in $duration seconds") } async.await() @@ -241,7 +241,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener { val tSessionGroup = ComputableGroup(Query(QueryCondition.IsTournament).merge(query), tStats) - Timber.d(">>>>> Start computations...") +// Timber.d(">>>>> Start computations...") val options = Calculator.Options() val computedStats = mutableListOf() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt index 4fddc017..0d375aac 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt @@ -208,7 +208,7 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab var report: Report? = null val test = GlobalScope.async { val s = Date() - Timber.d(">>> start...") +// Timber.d(">>> start...") val realm = Realm.getDefaultInstance() realm.refresh() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt index 63601a7f..ee0f9219 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt @@ -166,7 +166,7 @@ class ProgressReportFragment : AbstractReportFragment() { GlobalScope.launch { val s = Date() - Timber.d(">>> start...") +// Timber.d(">>> start...") val realm = Realm.getDefaultInstance() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataActivity.kt index a29412ad..08958662 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataActivity.kt @@ -51,10 +51,10 @@ class EditableDataActivity : MediaActivity() { initUI() } - override fun onPause() { - super.onPause() - this.paApplication.backupOperator?.backupIfNecessary() - } +// override fun onPause() { +// super.onPause() +// this.paApplication.backupOperator?.backupIfNecessary() +// } /** * Init UI diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionActivity.kt index 12cc93b3..7ec95fe3 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionActivity.kt @@ -68,10 +68,10 @@ class SessionActivity: BaseActivity() { initUI() } - override fun onPause() { - super.onPause() - this.paApplication.backupOperator?.backupIfNecessary() - } +// override fun onPause() { +// super.onPause() +// this.paApplication.backupOperator?.backupIfNecessary() +// } override fun onBackPressed() { setResult(Activity.RESULT_OK) diff --git a/app/src/main/java/net/pokeranalytics/android/util/BackupOperator.kt b/app/src/main/java/net/pokeranalytics/android/util/BackupOperator.kt index 37e76d47..6233e11a 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/BackupOperator.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/BackupOperator.kt @@ -1,26 +1,25 @@ package net.pokeranalytics.android.util import android.content.Context +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager import io.realm.Realm import io.realm.RealmResults -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import net.pokeranalytics.android.api.BackupApi import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Transaction -import net.pokeranalytics.android.util.csv.ProductCSVDescriptors -import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted +import net.pokeranalytics.android.util.csv.DataType import timber.log.Timber -import java.util.* +import java.util.concurrent.TimeUnit class BackupOperator(var context: Context) { private var sessions: RealmResults? = null private var transactions: RealmResults? = null - private var sessionsChanged = false - private var transactionsChanged = false + private var sessionsInitialized = false + private var transactionsInitialized = false private val realm = Realm.getDefaultInstance() @@ -28,59 +27,40 @@ class BackupOperator(var context: Context) { this.sessions = this.realm.where(Session::class.java).findAllAsync() this.sessions?.addChangeListener { _ -> - sessionsChanged = true + if (this.sessionsInitialized) { + Preferences.getBackupEmail(context)?.let { + backupDataType(DataType.SESSION) + } + } + this.sessionsInitialized = true } this.transactions = this.realm.where(Transaction::class.java).findAllAsync() this.transactions?.addChangeListener { _ -> - transactionsChanged = true - } - - } - - fun backupIfNecessary() { - - Timber.d(">>> backupIfNecessary") - - Preferences.getBackupEmail(context)?.let { email -> - this.backupSessionsIfNecessary(email) - this.backupTransactionsIfNecessary(email) - } - - } - - private fun backupSessionsIfNecessary(email: String) { - - if (this.sessionsChanged) { - Timber.d(">>>> backup sessions") - val sessions = this.realm.where(Session::class.java).findAll().sort("startDate") - val csv = ProductCSVDescriptors.pokerAnalyticsAndroid6Sessions.toCSV(sessions) - val fileName = "sessions_${Date().dateTimeFileFormatted}.csv" - - CoroutineScope(context = Dispatchers.IO).launch { - if (BackupApi.backupFile(context, email, fileName, csv)) { - sessionsChanged = false + if (this.transactionsInitialized) { + Preferences.getBackupEmail(context)?.let { + backupDataType(DataType.TRANSACTION) } } + this.transactionsInitialized = true } } - private fun backupTransactionsIfNecessary(email: String) { + private fun backupDataType(dataType: DataType) { + val data = Data.Builder() + .putInt(BackupTask.ParamKeys.DATA.value, dataType.ordinal) - if (this.transactionsChanged) { - Timber.d(">>>> backup transactions") - val transactions = this.realm.where(Transaction::class.java).findAll().sort("date") - val csv = ProductCSVDescriptors.pokerAnalyticsAndroidTransactions.toCSV(transactions) - val fileName = "transactions_${Date().dateTimeFileFormatted}.csv" + val backupTask = OneTimeWorkRequestBuilder() + .setInitialDelay(10, TimeUnit.HOURS) +// .setInitialDelay(10, TimeUnit.SECONDS) + .setInputData(data.build()) + .addTag(dataType.workId) + .build() - CoroutineScope(context = Dispatchers.IO).launch { - if (BackupApi.backupFile(context, email, fileName, csv)) { - transactionsChanged = false - } - } - } + Timber.d(">>> create backupTask") + WorkManager.getInstance(context).enqueueUniqueWork(dataType.workId, ExistingWorkPolicy.REPLACE, backupTask) } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/BackupTask.kt b/app/src/main/java/net/pokeranalytics/android/util/BackupTask.kt new file mode 100644 index 00000000..5ac6150b --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/BackupTask.kt @@ -0,0 +1,75 @@ +package net.pokeranalytics.android.util + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import io.realm.Realm +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.pokeranalytics.android.api.BackupApi +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.util.csv.DataType +import net.pokeranalytics.android.util.csv.ProductCSVDescriptors +import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted +import timber.log.Timber +import java.util.* + + +class BackupTask(var context: Context, var params: WorkerParameters) : Worker(context, params) { + + enum class ParamKeys(val value: String) { + DATA("title"), + } + + override fun doWork(): Result { + + val data = params.inputData + + val dataTypeInt = data.getInt(ParamKeys.DATA.value, 0) + val dataType = DataType.values()[dataTypeInt] + + Preferences.getBackupEmail(context)?.let { email -> + when(dataType) { + DataType.SESSION -> { + backupSessions(email) + } + DataType.TRANSACTION -> { + backupTransactions(email) + } + } + } + + return Result.success() + } + + private fun backupSessions(email: String) { + + Timber.d(">>>> backup sessions") + val realm = Realm.getDefaultInstance() + val sessions = realm.where(Session::class.java).findAll().sort("startDate") + val csv = ProductCSVDescriptors.pokerAnalyticsAndroid6Sessions.toCSV(sessions) + val fileName = "sessions_${Date().dateTimeFileFormatted}.csv" + + CoroutineScope(context = Dispatchers.IO).launch { + BackupApi.backupFile(context, email, fileName, csv) + } + realm.close() + } + + private fun backupTransactions(email: String) { + + Timber.d(">>>> backup transactions") + val realm = Realm.getDefaultInstance() + val transactions = realm.where(Transaction::class.java).findAll().sort("date") + val csv = ProductCSVDescriptors.pokerAnalyticsAndroidTransactions.toCSV(transactions) + val fileName = "transactions_${Date().dateTimeFileFormatted}.csv" + + CoroutineScope(context = Dispatchers.IO).launch { + BackupApi.backupFile(context, email, fileName, csv) + } + realm.close() + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt index 0a2a4bf8..cf242036 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt @@ -11,28 +11,37 @@ import org.apache.commons.csv.CSVRecord import timber.log.Timber import java.util.* -/** - * A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects - */ -class SessionTransactionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg elements: CSVField) : - PACSVDescriptor(source, isTournament, *elements) { +enum class DataType { + TRANSACTION, + SESSION; - private enum class DataType { - TRANSACTION, - SESSION; + companion object { - companion object { + fun valueForString(type: String): DataType? { + return when (type) { + "Deposit/Payout" -> TRANSACTION + "Cash Game", "Tournament" -> SESSION + else -> null + } + } + } - fun valueForString(type: String): DataType? { - return when (type) { - "Deposit/Payout" -> TRANSACTION - "Cash Game", "Tournament" -> SESSION - else -> null - } + val workId: String + get() { + return when (this) { + TRANSACTION -> "transaction.work" + SESSION -> "session.work" } } - } +} + +/** + * A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects + */ +class SessionTransactionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg elements: CSVField) : + PACSVDescriptor(source, isTournament, *elements) { + /** * Parses a [record] and return an optional Session