Merge branch 'dev' of gitlab.com:stax-river/poker-analytics into dev

dev
Aurelien Hubert 7 years ago
commit ed9da94302
  1. 40
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  2. 1
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  3. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  4. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  5. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  6. 2
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  7. 2
      app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
  8. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt
  9. 1
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt
  10. 2
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt
  11. 2
      app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt
  12. 2
      app/src/main/java/net/pokeranalytics/android/ui/view/MultiLineLegendView.kt
  13. 2
      app/src/main/java/net/pokeranalytics/android/util/TextFormat.kt
  14. 38
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt
  15. 95
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt
  16. 71
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt
  17. 14
      app/src/main/java/net/pokeranalytics/android/util/csv/TypedCSVField.kt

@ -17,6 +17,7 @@ import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.view.DefaultLegendValues
import net.pokeranalytics.android.ui.view.LegendContent
import net.pokeranalytics.android.util.ColorUtils
import net.pokeranalytics.android.util.TextFormat
import kotlin.math.abs
/**
@ -95,21 +96,17 @@ class Report(var options: Calculator.Options) {
/**
* A sessionGroup of computable items identified by a name
*/
class ComputableGroup(query: Query, stats: List<Stat>? = null) {
class ComputableGroup(var query: Query, var stats: List<Stat>? = null) {
// constructor(query: Query, statIds: List<Stat>? = null) : this(query.name, query.conditions)
//
// private constructor(name: String = "", conditions: List<QueryCondition> = listOf(), statIds: List<Stat>? = null)
var query: Query = query
/**
* A subgroup used to compute stat variation
*/
var comparedGroup: ComputableGroup? = null
/**
* The display name of the group
* The computed statIds of the comparable sessionGroup
*/
// val name: String
// get() {
// return this.query.name
// }
var comparedComputedResults: ComputedResults? = null
/**
* A list of _conditions to get
@ -164,21 +161,6 @@ class ComputableGroup(query: Query, stats: List<Stat>? = null) {
return sets
}
/**
* The list of statIds to display
*/
var stats: List<Stat>? = stats
/**
* A subgroup used to compute stat variation
*/
var comparedGroup: ComputableGroup? = null
/**
* The computed statIds of the comparable sessionGroup
*/
var comparedComputedResults: ComputedResults? = null
fun cleanup() {
this._computables = null
this._sessionSets = null
@ -220,10 +202,10 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
} else {
Point(value, data = data.objectIdentifier)
}
this._addEvolutionValue(point, stat = stat)
this.addEvolutionValue(point, stat = stat)
}
private fun _addEvolutionValue(point: Point, stat: Stat) {
private fun addEvolutionValue(point: Point, stat: Stat) {
val evolutionValues = this._evolutionValues[stat]
if (evolutionValues != null) {
evolutionValues.add(point)
@ -364,9 +346,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
}
fun finalize() {
this.consolidateProgressStats()
}
// MPAndroidChart

@ -6,6 +6,7 @@ import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable
import net.pokeranalytics.android.util.extensions.formatted

@ -15,7 +15,7 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize

@ -8,7 +8,7 @@ import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed

@ -8,7 +8,7 @@ import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.*

@ -1,7 +1,7 @@
package net.pokeranalytics.android.ui.adapter
import android.content.Context
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor

@ -16,7 +16,7 @@ import androidx.core.content.FileProvider
import androidx.core.view.isVisible
import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.util.DeviceUtils

@ -30,7 +30,7 @@ import java.util.*
*/
class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDataSource {
// Return the item as a Custom TypedField object
// Return the item as a Custom TypedCSVField object
private val customField: CustomField
get() {
return this.item as CustomField

@ -22,6 +22,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.StatRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext

@ -3,7 +3,7 @@ package net.pokeranalytics.android.ui.graph
import android.content.Context
import com.github.mikephil.charting.data.Entry
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.view.DefaultLegendValues

@ -9,7 +9,7 @@ import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.layout_legend_default.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.ui.extensions.setTextFormat
import net.pokeranalytics.android.ui.fragment.GraphFragment

@ -9,7 +9,7 @@ import kotlinx.android.synthetic.main.layout_legend_default.view.stat2Name
import kotlinx.android.synthetic.main.layout_legend_default.view.stat2Value
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.ui.extensions.setTextFormat
import net.pokeranalytics.android.ui.fragment.GraphFragment

@ -1,4 +1,4 @@
package net.pokeranalytics.android.calculus
package net.pokeranalytics.android.util
import android.content.Context
import android.graphics.Color

@ -4,14 +4,19 @@ import io.realm.Realm
import io.realm.RealmModel
import org.apache.commons.csv.CSVRecord
/**
* The various sources of CSV
*/
enum class DataSource {
POKER_INCOME,
POKER_BANKROLL_TRACKER,
RUNGOOD
}
abstract class DataCSVDescriptor<T : RealmModel>(source: DataSource, vararg elements: Field) : CSVDescriptor(source, *elements) {
/**
* A DataCSVDescriptor produces RealmModel instances for each row
*/
abstract class DataCSVDescriptor<T : RealmModel>(source: DataSource, vararg elements: CSVField) : CSVDescriptor(source, *elements) {
val realmModels = mutableListOf<RealmModel>()
@ -28,10 +33,19 @@ abstract class DataCSVDescriptor<T : RealmModel>(source: DataSource, vararg elem
}
open class CSVDescriptor(var source: DataSource, vararg elements: Field) {
/**
* A CSVDescriptor describes a CSV format by a source and a list of Fields
*/
abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) {
protected var fields: List<Field> = listOf()
protected var fieldMapping: MutableMap<Field, Int> = mutableMapOf()
/**
* The CSVField list describing the CSV header format
*/
protected var fields: List<CSVField> = listOf()
/**
* The mapping of CSVField with their index in the CSV file
*/
protected var fieldMapping: MutableMap<CSVField, Int> = mutableMapOf()
init {
if (elements.size > 0) {
@ -40,10 +54,16 @@ open class CSVDescriptor(var source: DataSource, vararg elements: Field) {
}
companion object {
/**
* The list of all managed CSVDescriptors
*/
val all: List<CSVDescriptor> =
listOf(SessionCSVDescriptor.pokerIncomeCash, SessionCSVDescriptor.pokerBankrollTracker, SessionCSVDescriptor.runGoodCashGames, SessionCSVDescriptor.runGoodTournaments)
}
/**
* Returns whether the [record] matches the CSVDescriptor
*/
fun matches(record: CSVRecord): Boolean {
var count = 0
@ -63,9 +83,9 @@ open class CSVDescriptor(var source: DataSource, vararg elements: Field) {
return count == this.fields.size
}
open fun parse(realm: Realm, record: CSVRecord) {
}
/**
* Method called when iterating on a CSVRecord
*/
abstract fun parse(realm: Realm, record: CSVRecord)
}

@ -9,24 +9,56 @@ import java.io.InputStream
import java.io.InputStreamReader
import java.io.Reader
open class CSVImporter {
class ImportException(message: String) : Exception(message)
/**
* A CSVImporter is a class in charge of parsing a CSV file and processing it
* When starting the parsing of a file, the instance will search for a CSVDescriptor, which describes
* the format of a CSV file.
* When finding a descriptor, the CSVImporter then continue to parse the file and delegates the parsing of each row
* to the CSVDescriptor
*/
open class CSVImporter(istream: InputStream) {
/**
* Number of commits required to commit a Realm transaction
*/
private val COMMIT_FREQUENCY = 100
var path: String? = null
var inputStream: InputStream? = null
constructor(istream: InputStream) {
inputStream = istream
}
constructor(filePath: String) {
path = filePath
}
/**
* The number of column indicating a valid record
*/
private val VALID_RECORD_COLUMNS = 4
/**
* The number of valid record to test for descriptor before throwing a File Format Exception
*/
private val VALID_RECORD_ATTEMPTS_BEFORE_THROWING_EXCEPTION = 5
/**
* The path of the CSV file
*/
private var path: String? = null
/**
* The InputStream containing a file content
*/
private var inputStream: InputStream? = istream
/**
* The current number of attempts at finding a valid CSVDescriptor
*/
private var descriptorFindingAttempts = 0
/**
* Stores the descriptors found
*/
private var usedDescriptors: MutableList<CSVDescriptor> = mutableListOf()
/**
* The currently used CSVDescriptor for parsing
*/
private var currentDescriptor: CSVDescriptor? = null
/**
* Constructs a CSVParser object and starts parsing the CSV
*/
fun start() {
val realm = Realm.getDefaultInstance()
@ -49,21 +81,31 @@ open class CSVImporter {
Timber.d("line $index")
if (currentDescriptor == null) { // find descriptor
this.findDescriptor(record)
} else {
if (this.currentDescriptor == null) { // find descriptor
this.currentDescriptor = this.findDescriptor(record)
if (this.currentDescriptor == null) {
this.descriptorFindingAttempts++
if ((index + 1) % COMMIT_FREQUENCY == 0) {
Timber.d("****** committing at ${index} sessions...")
if (record.size() > VALID_RECORD_COLUMNS && this.descriptorFindingAttempts > VALID_RECORD_ATTEMPTS_BEFORE_THROWING_EXCEPTION) {
throw ImportException("This type of file is not supported")
}
}
} else { // parse
val parsingIndex = index + 1
if (parsingIndex % COMMIT_FREQUENCY == 0) {
Timber.d("****** committing at $parsingIndex sessions...")
realm.commitTransaction()
realm.beginTransaction()
}
currentDescriptor?.let {
this.currentDescriptor?.let {
if (record.size() == 0) {
this.usedDescriptors.add(it)
this.currentDescriptor =
null // reset descriptor when encountering an empty line (multiple descriptors can be found in a single file)
this.currentDescriptor = null // reset descriptor when encountering an empty line (multiple descriptors can be found in a single file)
this.descriptorFindingAttempts = 0
} else {
it.parse(realm, record)
}
@ -81,16 +123,19 @@ open class CSVImporter {
realm.close()
}
private fun findDescriptor(record: CSVRecord) {
/**
* Search for a descriptor in the list of managed formats
*/
private fun findDescriptor(record: CSVRecord) : CSVDescriptor? {
CSVDescriptor.all.forEach { descriptor ->
if (descriptor.matches(record)) {
this.currentDescriptor = descriptor
Timber.d("Identified source: ${descriptor.source}")
return
return descriptor
}
}
return null
}
fun save(realm: Realm) {

@ -12,107 +12,113 @@ import net.pokeranalytics.android.util.extensions.setHourMinutes
import org.apache.commons.csv.CSVRecord
import java.util.*
/**
* The enumeration of Session fields
*/
sealed class SessionField {
data class Start(
override var header: String,
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateField
) : DateCSVField
data class StartTime(
override var header: String,
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateField
) : DateCSVField
data class End(
override var header: String,
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateField
) : DateCSVField
data class EndTime(
override var header: String,
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateField
) : DateCSVField
data class Buyin(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
data class NetResult(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
data class CashedOut(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
data class Break(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
data class Tips(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
data class SmallBlind(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
data class BigBlind(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class Blind(override var header: String, override var callback: ((String) -> Pair<Double, Double>?)? = null) : BlindField
data class Game(override var header: String) : Field
data class Location(override var header: String) : Field
data class LocationType(override var header: String) : Field
data class Bankroll(override var header: String) : Field
data class LimitType(override var header: String) : Field
data class Comment(override var header: String) : Field
data class SessionType(override var header: String) : Field
data class TableSize(override var header: String) : Field
data class CurrencyCode(override var header: String) : Field
data class TournamentName(override var header: String) : Field
data class TournamentType(override var header: String) : Field
) : NumberCSVField
data class Blind(override var header: String, override var callback: ((String) -> Pair<Double, Double>?)? = null) : BlindCSVField
data class Game(override var header: String) : CSVField
data class Location(override var header: String) : CSVField
data class LocationType(override var header: String) : CSVField
data class Bankroll(override var header: String) : CSVField
data class LimitType(override var header: String) : CSVField
data class Comment(override var header: String) : CSVField
data class SessionType(override var header: String) : CSVField
data class TableSize(override var header: String) : CSVField
data class CurrencyCode(override var header: String) : CSVField
data class TournamentName(override var header: String) : CSVField
data class TournamentType(override var header: String) : CSVField
data class CurrencyRate(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
data class TournamentPosition(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
data class TournamentNumberOfPlayers(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
) : NumberCSVField
}
class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: Field) : DataCSVDescriptor<Session>(source, *elements) {
/**
* A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects
*/
class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: CSVField) : DataCSVDescriptor<Session>(source, *elements) {
companion object {
val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor(
@ -216,6 +222,9 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean
}
/**
* Parses a [record] and return an optional Session
*/
override fun parseData(realm: Realm, record: CSVRecord): Session? {
val session = Session.newInstance(realm, this.isTournament)
@ -283,11 +292,11 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean
val endDate = session.endDate
val net = session.result?.net
if (startDate != null && endDate != null && net != null) { // valid session
return if (startDate != null && endDate != null && net != null) { // valid session
val unique = SessionUtils.unicityCheck(realm, startDate, endDate, net)
return if (unique) session else null
if (unique) session else null
} else { // invalid session
return null
null
}
}

@ -7,8 +7,10 @@ import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
interface NumberField: TypedField<Double> {
/**
* An interface used to provide a Number flavour to a CSV field
*/
interface NumberCSVField: TypedCSVField<Double> {
val numberFormat: String?
@ -29,7 +31,7 @@ interface NumberField: TypedField<Double> {
}
}
interface DateField : TypedField<Date> {
interface DateCSVField : TypedCSVField<Date> {
val dateFormat: String?
@ -46,7 +48,7 @@ interface DateField : TypedField<Date> {
}
interface BlindField : TypedField<Pair<Double, Double>> {
interface BlindCSVField : TypedCSVField<Pair<Double, Double>> {
override fun parse(value: String) : Pair<Double, Double>? {
@ -68,11 +70,11 @@ interface BlindField : TypedField<Pair<Double, Double>> {
}
interface TypedField<T> : Field {
interface TypedCSVField<T> : CSVField {
fun parse(value: String) : T?
var callback: ((String) -> T?)?
}
interface Field {
interface CSVField {
val header: String
}
Loading…
Cancel
Save