From 2f6a7a77179c50ad9e127e3a798194a324083c90 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 20 May 2019 19:52:39 +0200 Subject: [PATCH] Created CSV data importer + cleanup --- app/build.gradle | 3 + .../android/model/realm/Session.kt | 1 - .../model/utils/FavoriteSessionFinder.kt | 7 +- .../android/model/utils/SessionUtils.kt | 21 ++++++ .../android/ui/fragment/BankrollFragment.kt | 4 +- .../android/ui/fragment/DataListFragment.kt | 2 +- .../android/ui/fragment/FiltersFragment.kt | 2 +- .../fragment/data/TransactionDataFragment.kt | 2 +- .../rowrepresentable/FilterCategoryRow.kt | 3 +- .../android/util/csv/CSVDescriptor.kt | 60 ++++++++++++++++ .../android/util/csv/CSVImporter.kt | 55 +++++++++++++++ .../pokeranalytics/android/util/csv/Field.kt | 38 +++++++++++ .../android/util/csv/SessionCSVDescriptor.kt | 68 +++++++++++++++++++ .../util/{ => extensions}/RealmExtensions.kt | 2 +- 14 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt create mode 100644 app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt create mode 100644 app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt create mode 100644 app/src/main/java/net/pokeranalytics/android/util/csv/Field.kt create mode 100644 app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt rename app/src/main/java/net/pokeranalytics/android/util/{ => extensions}/RealmExtensions.kt (97%) diff --git a/app/build.gradle b/app/build.gradle index 5f4fb5c3..a60e3a05 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -102,6 +102,9 @@ dependencies { // MPAndroidChart implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' + // https://mvnrepository.com/artifact/org.apache.commons/commons-csv + implementation 'org.apache.commons:commons-csv:1.6' + // Instrumented Tests androidTestImplementation 'androidx.test:core:1.1.0' androidTestImplementation 'androidx.test:runner:1.1.1' diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index feadc7a3..11f87c4f 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -38,7 +38,6 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.extensions.* -import net.pokeranalytics.android.util.sorted import java.text.DateFormat import java.util.* import java.util.Currency diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt index f4b3d9bc..57a8e17b 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt @@ -11,7 +11,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow * Returns all significant parameters concatenated in a String * Not suitable for display */ -fun Session.parameterRepresentation(context: Context): String { +private fun Session.parameterRepresentation(context: Context): String { var representation = "" this.significantFields().forEach { @@ -56,9 +56,8 @@ class FavoriteSessionFinder { /** * A counter convenience class */ - class Counter(session: Session) { + class Counter(var session: Session) { - var session: Session = session var counter: Int = 1 fun increment() { @@ -77,7 +76,7 @@ class FavoriteSessionFinder { fun copyParametersFromFavoriteSession(newSession: Session, location: Location?, context: Context) { val favoriteSession = - FavoriteSessionFinder.favoriteSession(newSession.type, location, newSession.realm, context) + favoriteSession(newSession.type, location, newSession.realm, context) favoriteSession?.let { fav -> diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt new file mode 100644 index 00000000..d8d93f7e --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt @@ -0,0 +1,21 @@ +package net.pokeranalytics.android.model.utils + +import io.realm.Realm +import net.pokeranalytics.android.model.realm.Session +import java.util.* + +class SessionUtils { + + companion object { + + /** + * Returns true if the provided parameters doesn't correspond to an existing session + */ + fun unicityCheck(realm: Realm, startDate: Date, endDate: Date, net: Double) : Boolean { + val sessions = realm.where(Session::class.java).equalTo("startDate", startDate).equalTo("endDate", endDate).equalTo("result.net", net).findAll() + return sessions.isEmpty() + } + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt index 902eb9f5..3af69812 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt @@ -28,7 +28,6 @@ import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.EditableDataActivity import net.pokeranalytics.android.ui.activity.GraphActivity -import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource @@ -37,8 +36,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow -import net.pokeranalytics.android.util.sorted -import net.pokeranalytics.android.util.sorted +import net.pokeranalytics.android.util.extensions.sorted import timber.log.Timber import java.util.* import kotlin.collections.ArrayList diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt index ce942b54..19d24c7e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt @@ -23,7 +23,7 @@ import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -import net.pokeranalytics.android.util.sorted +import net.pokeranalytics.android.util.extensions.sorted class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt index f8e1bdd0..5a4bd9c4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt @@ -24,7 +24,7 @@ import net.pokeranalytics.android.ui.interfaces.FilterableType import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow import net.pokeranalytics.android.util.Preferences -import net.pokeranalytics.android.util.sorted +import net.pokeranalytics.android.util.extensions.sorted import timber.log.Timber diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt index 20ffcd89..0cd0520b 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt @@ -18,7 +18,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionRow import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.extensions.round import net.pokeranalytics.android.util.extensions.shortDate -import net.pokeranalytics.android.util.sorted +import net.pokeranalytics.android.util.extensions.sorted import java.util.* /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt index 009e2ec9..29096d5e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt @@ -2,13 +2,12 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import io.realm.Realm import net.pokeranalytics.android.R -import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.ui.interfaces.FilterableType import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow.* -import net.pokeranalytics.android.util.sorted +import net.pokeranalytics.android.util.extensions.sorted enum class FilterCategoryRow(override val resId: Int?, override val viewType: Int = RowViewType.TITLE_VALUE_ARROW.ordinal) : RowRepresentable { GENERAL(R.string.general), diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt new file mode 100644 index 00000000..27207e37 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -0,0 +1,60 @@ +package net.pokeranalytics.android.util.csv + +import io.realm.Realm +import io.realm.RealmModel +import org.apache.commons.csv.CSVRecord + + +abstract class DataCSVDescriptor(vararg elements: Field<*>) : CSVDescriptor(*elements) { + + val realmModels = mutableListOf() + + abstract fun parseData(realm: Realm, record: CSVRecord) : T? + + override fun parse(realm: Realm, record: CSVRecord) { + + val data = this.parseData(realm, record) + data?.let { + this.realmModels.add(it) + } + + } + +} + +open class CSVDescriptor(vararg elements: Field<*>) { + + protected var fields: List> = listOf() + + init { + if (elements.size > 0) { + this.fields = elements.toList() + } + } + + companion object { + val all: List = listOf(SessionCSVDescriptor.pokerIncomeCash) + } + + fun matches(headerMap: Map) : Boolean { + + var count = 0 + val headers = headerMap.keys + + this.fields.forEach { + + val index = headers.indexOf(it.header) + if (index >= 0) { + count++ + } + } + + return count == this.fields.size + } + + open fun parse(realm: Realm, record: CSVRecord) { + + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt new file mode 100644 index 00000000..851cfdcf --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt @@ -0,0 +1,55 @@ +package net.pokeranalytics.android.util.csv + +import io.realm.Realm +import org.apache.commons.csv.CSVFormat +import org.apache.commons.csv.CSVParser +import timber.log.Timber +import java.io.FileReader + +open class CSVImporter(var path: String) { + + private lateinit var descriptor: CSVDescriptor + + private fun start(realm: Realm) { + + val reader = FileReader(this.path) + + val parser = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(reader) + + val descriptor = this.findDescriptor(parser.headerMap) + + if (descriptor != null) { + this.descriptor = descriptor + this.parse(realm, descriptor, parser) + } else { + Timber.d("No CSV descriptor found") + } + + } + + private fun findDescriptor(header: Map) : CSVDescriptor? { + CSVDescriptor.all.forEach { + if (it.matches(header)) { + return it + } + } + return null + } + + protected open fun parse(realm: Realm, descriptor: CSVDescriptor, parser : CSVParser) { + parser.records.forEach { record -> + descriptor.parse(realm, record) + } + } + + fun save(realm: Realm) { + + if (descriptor is DataCSVDescriptor<*>) { + realm.executeTransaction { + realm.copyToRealm((descriptor as DataCSVDescriptor<*>).realmModels) + } + } + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/Field.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/Field.kt new file mode 100644 index 00000000..33ec3347 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/Field.kt @@ -0,0 +1,38 @@ +package net.pokeranalytics.android.util.csv + +import java.util.* + + +interface AmountField: NumberField { + + override fun parse(value: String) : Double? { + return null + } + +} + +interface NumberField: Field { + val numberFormat: String? +} + +interface DateField : Field { + val dateFormat: String? + + override fun parse(value: String) : Date? { + return null + } +} + +interface BlindField : Field { + override fun parse(value: String) : Double? { + return null + } + +} + +interface Field { + val header: String + var callback: (() -> Unit)? + + fun parse(value: String) : T? +} diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt new file mode 100644 index 00000000..603c9b1e --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -0,0 +1,68 @@ +package net.pokeranalytics.android.util.csv + +import io.realm.Realm +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.model.utils.SessionUtils +import org.apache.commons.csv.CSVRecord + +sealed class SessionField { + + data class Start(override var header: String, override var callback: (() -> Unit)? = null, override val dateFormat: String? = null) : DateField + data class End(override var header: String, override var callback: (() -> Unit)? = null, override val dateFormat: String? = null) : DateField + + data class Buyin(override var header: String, override var callback: (() -> Unit)? = null, override val numberFormat: String? = null) : AmountField + data class CashedOut(override var header: String, override var callback: (() -> Unit)? = null, override val numberFormat: String? = null) : AmountField + + data class Blind(override var header: String, override var callback: (() -> Unit)? = null) : BlindField + +} + +class SessionCSVDescriptor(vararg elements: Field<*>) : DataCSVDescriptor(*elements) { + + companion object { + val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor(SessionField.Start("Start Time"), + SessionField.End("End Time"), + SessionField.Buyin("Buy In"), + SessionField.CashedOut("Cashed Out")) + + } + + override fun parseData(realm: Realm, record: CSVRecord): Session? { + + val session = Session() + + fields.forEach { + + val value = record.get(it.header) + when (it) { + is SessionField.Start -> { + session.startDate = it.parse(value) + } + is SessionField.End -> { + session.endDate = it.parse(value) + } + is SessionField.Buyin -> session.result?.buyin = it.parse(value) + is SessionField.CashedOut -> session.result?.cashout = it.parse(value) + is SessionField.Blind -> { +// session.cgSmallBlind = + } + else -> {} + } + + + } + + val startDate = session.startDate + val endDate = session.endDate + val net = session.result?.net + + if (startDate != null && endDate != null && net != null) { // valid session + val unique = SessionUtils.unicityCheck(realm, startDate, endDate, net) + return if (unique) session else null + } else { // invalid session + return null + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/RealmExtensions.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt similarity index 97% rename from app/src/main/java/net/pokeranalytics/android/util/RealmExtensions.kt rename to app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt index 5c458e7b..50d158ef 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/RealmExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.util +package net.pokeranalytics.android.util.extensions import io.realm.Realm import io.realm.RealmModel