Created CSV data importer + cleanup

dev
Laurent 7 years ago
parent 35a3a2429e
commit 2f6a7a7717
  1. 3
      app/build.gradle
  2. 1
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  3. 7
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  4. 21
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt
  5. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt
  6. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
  7. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  8. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt
  9. 3
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt
  10. 60
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt
  11. 55
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt
  12. 38
      app/src/main/java/net/pokeranalytics/android/util/csv/Field.kt
  13. 68
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt
  14. 2
      app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt

@ -102,6 +102,9 @@ dependencies {
// MPAndroidChart // MPAndroidChart
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' 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 // Instrumented Tests
androidTestImplementation 'androidx.test:core:1.1.0' androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.1.1'

@ -38,7 +38,6 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.* import net.pokeranalytics.android.util.extensions.*
import net.pokeranalytics.android.util.sorted
import java.text.DateFormat import java.text.DateFormat
import java.util.* import java.util.*
import java.util.Currency import java.util.Currency

@ -11,7 +11,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
* Returns all significant parameters concatenated in a String * Returns all significant parameters concatenated in a String
* Not suitable for display * Not suitable for display
*/ */
fun Session.parameterRepresentation(context: Context): String { private fun Session.parameterRepresentation(context: Context): String {
var representation = "" var representation = ""
this.significantFields().forEach { this.significantFields().forEach {
@ -56,9 +56,8 @@ class FavoriteSessionFinder {
/** /**
* A counter convenience class * A counter convenience class
*/ */
class Counter(session: Session) { class Counter(var session: Session) {
var session: Session = session
var counter: Int = 1 var counter: Int = 1
fun increment() { fun increment() {
@ -77,7 +76,7 @@ class FavoriteSessionFinder {
fun copyParametersFromFavoriteSession(newSession: Session, location: Location?, context: Context) { fun copyParametersFromFavoriteSession(newSession: Session, location: Location?, context: Context) {
val favoriteSession = val favoriteSession =
FavoriteSessionFinder.favoriteSession(newSession.type, location, newSession.realm, context) favoriteSession(newSession.type, location, newSession.realm, context)
favoriteSession?.let { fav -> favoriteSession?.let { fav ->

@ -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()
}
}
}

@ -28,7 +28,6 @@ import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity
import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.GraphActivity 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.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow
import net.pokeranalytics.android.util.sorted import net.pokeranalytics.android.util.extensions.sorted
import net.pokeranalytics.android.util.sorted
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList

@ -23,7 +23,7 @@ import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType 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 { class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {

@ -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
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.sorted import net.pokeranalytics.android.util.extensions.sorted
import timber.log.Timber import timber.log.Timber

@ -18,7 +18,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionRow
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.round import net.pokeranalytics.android.util.extensions.round
import net.pokeranalytics.android.util.extensions.shortDate import net.pokeranalytics.android.util.extensions.shortDate
import net.pokeranalytics.android.util.sorted import net.pokeranalytics.android.util.extensions.sorted
import java.util.* import java.util.*
/** /**

@ -2,13 +2,12 @@ package net.pokeranalytics.android.ui.view.rowrepresentable
import io.realm.Realm import io.realm.Realm
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.model.realm.CustomField
import net.pokeranalytics.android.ui.interfaces.FilterableType import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow.* 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 { enum class FilterCategoryRow(override val resId: Int?, override val viewType: Int = RowViewType.TITLE_VALUE_ARROW.ordinal) : RowRepresentable {
GENERAL(R.string.general), GENERAL(R.string.general),

@ -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<T : RealmModel>(vararg elements: Field<*>) : CSVDescriptor(*elements) {
val realmModels = mutableListOf<RealmModel>()
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<Field<*>> = listOf()
init {
if (elements.size > 0) {
this.fields = elements.toList()
}
}
companion object {
val all: List<CSVDescriptor> = listOf(SessionCSVDescriptor.pokerIncomeCash)
}
fun matches(headerMap: Map<String, Int>) : 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) {
}
}

@ -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<String, Int>) : 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)
}
}
}
}

@ -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<Double> {
val numberFormat: String?
}
interface DateField : Field<Date> {
val dateFormat: String?
override fun parse(value: String) : Date? {
return null
}
}
interface BlindField : Field<Double> {
override fun parse(value: String) : Double? {
return null
}
}
interface Field<T> {
val header: String
var callback: (() -> Unit)?
fun parse(value: String) : T?
}

@ -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<Session>(*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
}
}
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.util package net.pokeranalytics.android.util.extensions
import io.realm.Realm import io.realm.Realm
import io.realm.RealmModel import io.realm.RealmModel
Loading…
Cancel
Save