diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt index 26ebd99a..aa9ea5f2 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt @@ -46,6 +46,8 @@ interface NameManageable : Manageable { } +class Identificator(var id: String, var clazz: Class) + /** * An interface associate a unique uniqueIdentifier to an object */ @@ -56,6 +58,11 @@ interface Identifiable : RealmModel { */ var id: String + val identificator: Identificator + get() { + return Identificator(this.id, this::class.java) + } + } /** diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt index 010e2340..3a506826 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt @@ -42,11 +42,6 @@ open class Filter : RealmObject(), RowRepresentable, Editable, Deletable, Counta //return realm.copyToRealm(filter) } - // Get a queryWith by its id - fun getFilterBydId(realm: Realm, filterId: String): Filter? { - return realm.where().equalTo("id", filterId).findFirst() - } - inline fun queryOn(realm: Realm, query: Query, sortField: String? = null): RealmResults { val rootQuery = realm.where() var realmQuery = query.queryWith(rootQuery) 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 975da7d5..25651110 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 @@ -67,6 +67,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } companion object { + fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session { val session = Session() session.result = Result() @@ -76,6 +77,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat session.bankroll = realm.where().findFirst() } session.type = if (isTournament) Session.Type.TOURNAMENT.ordinal else Session.Type.CASH_GAME.ordinal + return realm.copyToRealm(session) } diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/DataUtils.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/DataUtils.kt new file mode 100644 index 00000000..2e0bd3a6 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/DataUtils.kt @@ -0,0 +1,39 @@ +package net.pokeranalytics.android.model.utils + +import io.realm.Realm +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType +import java.util.* + +class DataUtils { + + companion object { + + /** + * Returns true if the provided parameters doesn't correspond to an existing session + */ + fun sessionCount(realm: Realm, startDate: Date, endDate: Date, net: Double): Int { + val sessions = realm.where(Session::class.java) + .equalTo("startDate", startDate) + .equalTo("endDate", endDate) + .equalTo("result.net", net) + .findAll() + return sessions.size + } + + /** + * Returns true if the provided parameters doesn't correspond to an existing transaction + */ + fun transactionUnicityCheck(realm: Realm, date: Date, amount: Double, type: TransactionType): Boolean { + val transactions = realm.where(Transaction::class.java) + .equalTo("date", date) + .equalTo("amount", amount) + .equalTo("type.id", type.id) + .findAll() + return transactions.isEmpty() + } + + } + +} 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 deleted file mode 100644 index d8d93f7e..00000000 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt +++ /dev/null @@ -1,21 +0,0 @@ -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/FiltersFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt index 0a1b696e..564fb7ac 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 @@ -12,6 +12,7 @@ import kotlinx.android.synthetic.main.fragment_editable_data.recyclerView import kotlinx.android.synthetic.main.fragment_filters.* import kotlinx.android.synthetic.main.fragment_filters.view.toolbar import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.ui.activity.FilterDetailsActivity @@ -27,6 +28,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.extensions.findById import net.pokeranalytics.android.util.extensions.sorted import timber.log.Timber @@ -173,7 +175,9 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, val realm = getRealm() primaryKey?.let { - currentFilter = realm.copyFromRealm(Filter.getFilterBydId(realm, it)) + + val filter = realm.findById(it) ?: throw PAIllegalStateException("Can't find filter with id=$it") + currentFilter = realm.copyFromRealm(filter) isUpdating = true } ?: run { currentFilter = Filter.newInstance(this.filterableType.uniqueIdentifier) //realm.copyFromRealm(Filter.newInstanceForResult(realm, this.filterableType.ordinal)) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt index c0d0775c..e8025a10 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt @@ -4,18 +4,19 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import kotlinx.android.synthetic.main.fragment_import.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async import kotlinx.coroutines.launch import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.activity.components.ResultCode import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.util.csv.CSVImporter import net.pokeranalytics.android.util.csv.ImportDelegate import net.pokeranalytics.android.util.csv.ImportException import timber.log.Timber import java.io.InputStream +import java.text.NumberFormat import java.util.* import kotlin.coroutines.CoroutineContext @@ -26,6 +27,7 @@ class ImportFragment : RealmFragment(), ImportDelegate { private lateinit var filePath: String private lateinit var inputStream: InputStream + private lateinit var importer: CSVImporter fun setData(path: String) { this.filePath = path @@ -43,12 +45,34 @@ class ImportFragment : RealmFragment(), ImportDelegate { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + this.initUI() + this.startImport() } - fun startImport() { + private fun initUI() { + + this.imported.text = requireContext().getString(R.string.imported) + this.total.text = requireContext().getString(R.string.total) + + this.save.isEnabled = false + this.save.setOnClickListener { + this.end() + } - var shouldDismissActivity = false + this.cancel.setOnClickListener { + this.cancel() + this.end() + } + + } + + private fun startImport() { + +// var shouldDismissActivity = false + + this.importer = CSVImporter(inputStream) + this.importer.delegate = this GlobalScope.launch(coroutineContext) { @@ -57,10 +81,9 @@ class ImportFragment : RealmFragment(), ImportDelegate { Timber.d(">>> Start Import...") try { - val csv = CSVImporter(inputStream) - csv.start() + importer.start() } catch (e: ImportException) { - shouldDismissActivity = true +// shouldDismissActivity = true } val e = Date() val duration = (e.time - s.time) / 1000.0 @@ -69,23 +92,43 @@ class ImportFragment : RealmFragment(), ImportDelegate { } test.await() - if (shouldDismissActivity) { +// if (shouldDismissActivity) { +// +// activity?.let { +// it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) +// it.finish() +// } +// +// } else { +// } + importDidFinish() - activity?.let { - it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) - it.finish() - } + } - } + } - } + private fun cancel() { + this.importer.cancel(getRealm()) + } + + private fun importDidFinish() { + this.save.isEnabled = true + + } + + private fun end() { + activity?.finish() } + val numberFormatter = NumberFormat.getNumberInstance() + // ImportDelegate - override fun parsingCountUpdate(count: Int) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + override fun parsingCountUpdate(importedCount: Int, totalCount: Int) { + this.counter.text = this.numberFormatter.format(importedCount) + this.totalCounter.text = this.numberFormatter.format(totalCount) } + } \ No newline at end of file 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 index bf517d5e..847a775c 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -1,7 +1,10 @@ package net.pokeranalytics.android.util.csv import io.realm.Realm -import io.realm.RealmModel +import io.realm.kotlin.deleteFromRealm +import net.pokeranalytics.android.model.interfaces.Identifiable +import net.pokeranalytics.android.model.interfaces.Identificator +import net.pokeranalytics.android.util.extensions.findById import org.apache.commons.csv.CSVRecord import timber.log.Timber @@ -17,19 +20,30 @@ enum class DataSource { /** * A DataCSVDescriptor produces RealmModel instances for each row */ -abstract class DataCSVDescriptor(source: DataSource, vararg elements: CSVField) : CSVDescriptor(source, *elements) { +abstract class DataCSVDescriptor(source: DataSource, vararg elements: CSVField) : CSVDescriptor(source, *elements) { - val realmModels = mutableListOf() + /** + * List of Realm object identificators + */ + val realmModelIds = mutableListOf() abstract fun parseData(realm: Realm, record: CSVRecord): T? - override fun parse(realm: Realm, record: CSVRecord) { + override fun parse(realm: Realm, record: CSVRecord): Int { val data = this.parseData(realm, record) data?.let { - this.realmModels.add(it) + this.realmModelIds.add(it.identificator) } + return if (data != null) 1 else 0 + } + override fun cancel(realm: Realm) { + realm.executeTransaction { + this.realmModelIds.forEach { identificator -> + realm.findById(identificator.clazz, identificator.id)?.deleteFromRealm() + } + } } } @@ -59,7 +73,19 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) * The list of all managed CSVDescriptors */ val all: List = - listOf(SessionCSVDescriptor.pokerIncomeCash, SessionCSVDescriptor.pokerBankrollTracker, SessionCSVDescriptor.runGoodCashGames, SessionCSVDescriptor.runGoodTournaments) + listOf(ProductCSVDescriptors.pokerIncomeCash, + ProductCSVDescriptors.pokerBankrollTracker, + ProductCSVDescriptors.runGoodCashGames, + ProductCSVDescriptors.runGoodTournaments) + } + + /** + * Method called when iterating on a CSVRecord + */ + abstract fun parse(realm: Realm, record: CSVRecord): Int + + open fun cancel(realm: Realm) { + } /** @@ -83,9 +109,4 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) return count == this.fields.size } - /** - * Method called when iterating on a CSVRecord - */ - abstract 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 index a2772236..17476360 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt @@ -1,7 +1,10 @@ package net.pokeranalytics.android.util.csv +import android.os.Handler +import android.os.Looper import io.realm.Realm import org.apache.commons.csv.CSVFormat +import org.apache.commons.csv.CSVParser import org.apache.commons.csv.CSVRecord import timber.log.Timber import java.io.FileReader @@ -12,7 +15,7 @@ import java.io.Reader class ImportException(message: String) : Exception(message) interface ImportDelegate { - fun parsingCountUpdate(count: Int) + fun parsingCountUpdate(importedCount: Int, totalCount: Int) } /** @@ -60,11 +63,27 @@ open class CSVImporter(istream: InputStream) { * Stores the descriptors found */ private var usedDescriptors: MutableList = mutableListOf() + /** * The currently used CSVDescriptor for parsing */ private var currentDescriptor: CSVDescriptor? = null + /** + * The number of valid record parsed + */ + private var totalParsedRecords = 0 + + /** + * The number of successfully imported records + */ + private var importedRecords = 0 + + /** + * The CSV parser + */ + private lateinit var parser: CSVParser + /** * Constructs a CSVParser object and starts parsing the CSV */ @@ -80,7 +99,7 @@ open class CSVImporter(istream: InputStream) { reader = InputStreamReader(this.inputStream) } - val parser = CSVFormat.DEFAULT.withAllowMissingColumnNames().parse(reader) + this.parser = CSVFormat.DEFAULT.withAllowMissingColumnNames().parse(reader) Timber.d("Starting import...") @@ -89,6 +108,7 @@ open class CSVImporter(istream: InputStream) { parser.forEachIndexed { index, record -> Timber.d("line $index") + this.notifyDelegate() if (this.currentDescriptor == null) { // find descriptor this.currentDescriptor = this.findDescriptor(record) @@ -106,10 +126,10 @@ open class CSVImporter(istream: InputStream) { } else { // parse + // batch commit val parsingIndex = index + 1 if (parsingIndex % COMMIT_FREQUENCY == 0) { Timber.d("****** committing at $parsingIndex sessions...") - this.delegate?.parsingCountUpdate(parsingIndex) realm.commitTransaction() realm.beginTransaction() } @@ -117,19 +137,27 @@ open class CSVImporter(istream: InputStream) { 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) + val count = it.parse(realm, record) + + this.importedRecords += count + this.totalParsedRecords++ + + this.notifyDelegate() } } ?: run { realm.cancelTransaction() - throw IllegalStateException("CSVDescriptor should never be null here") + throw ImportException("CSVDescriptor should never be null here") } } } + this.notifyDelegate() + realm.commitTransaction() Timber.d("Ending import...") @@ -137,10 +165,16 @@ open class CSVImporter(istream: InputStream) { realm.close() } + private fun notifyDelegate() { + Handler(Looper.getMainLooper()).post { + this.delegate?.parsingCountUpdate(this.importedRecords, this.totalParsedRecords) + } + } + /** * Search for a descriptor in the list of managed formats */ - private fun findDescriptor(record: CSVRecord) : CSVDescriptor? { + private fun findDescriptor(record: CSVRecord): CSVDescriptor? { CSVDescriptor.all.forEach { descriptor -> if (descriptor.matches(record)) { @@ -152,17 +186,26 @@ open class CSVImporter(istream: InputStream) { return null } - fun save(realm: Realm) { - +// fun save(realm: Realm) { +// +// this.usedDescriptors.forEach { descriptor -> +// +// if (descriptor is DataCSVDescriptor<*>) { +// realm.executeTransaction { +// realm.copyToRealm(descriptor.realmModels) +// } +// } +// } +// } + + fun cancel(realm: Realm) { + this.parser.close() + realm.refresh() + + this.currentDescriptor?.cancel(realm) this.usedDescriptors.forEach { descriptor -> - - if (descriptor is DataCSVDescriptor<*>) { - realm.executeTransaction { - realm.copyToRealm(descriptor.realmModels) - } - } + descriptor.cancel(realm) } - } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt new file mode 100644 index 00000000..a475972b --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt @@ -0,0 +1,112 @@ +package net.pokeranalytics.android.util.csv + +class ProductCSVDescriptors { + + companion object { + + val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor( + DataSource.POKER_INCOME, + false, + SessionField.Start("Start Time"), + SessionField.End("End Time"), + SessionField.Buyin("Buy In"), + SessionField.CashedOut("Cashed Out"), + SessionField.Break("Break Minutes"), + SessionField.LimitType("Limit Type"), + SessionField.Game("Game"), + SessionField.Bankroll("Bankroll"), + SessionField.Location("Location"), + SessionField.Location("Location Type"), + SessionField.Comment("Note"), + SessionField.Tips("Tips"), + SessionField.Blind("Stake") + ) + + val pokerBankrollTracker: CSVDescriptor = SessionCSVDescriptor( + DataSource.POKER_BANKROLL_TRACKER, + true, + SessionField.Start("starttime", dateFormat = "MM/dd/yy HH:mm"), + SessionField.End("endtime", dateFormat = "MM/dd/yy HH:mm"), + SessionField.SessionType("variant"), + SessionField.Buyin("buyin"), + SessionField.CashedOut("cashout"), + SessionField.Rebuy("rebuycosts"), + SessionField.Addon("addoncosts"), + SessionField.Break("breakminutes"), + SessionField.LimitType("limit"), + SessionField.Game("game"), + SessionField.Bankroll("currency"), // same as currency code + SessionField.Location("type"), +// SessionField.Comment("sessionnote"), + SessionField.Tips("expensesfromstack"), + SessionField.SmallBlind("smallblind"), + SessionField.BigBlind("bigblind"), + SessionField.TournamentNumberOfPlayers("player"), + SessionField.TournamentPosition("place"), + SessionField.TournamentName("mttname"), + SessionField.CurrencyCode("currency"), + SessionField.CurrencyRate("exchangerate"), + SessionField.TableSize("tablesize") + ) + + val runGoodTournaments: CSVDescriptor = SessionCSVDescriptor( + DataSource.RUNGOOD, + true, + SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), + SessionField.StartTime("Start Time"), + SessionField.End("End Date"), + SessionField.EndTime("End Time"), + SessionField.Buyin("Total Buy-In"), + SessionField.CashedOut("Winnings"), + SessionField.NetResult("Profit"), + SessionField.Break("Break"), + SessionField.LimitType("Limit Type"), + SessionField.Game("Game"), + SessionField.Bankroll("Bankroll"), + SessionField.TableSize("Table Type"), + SessionField.Location("Location"), + SessionField.LocationType("Location Type"), + SessionField.Comment("Notes"), + SessionField.CurrencyCode("Currency"), + SessionField.TournamentName("Event Name"), + SessionField.TournamentNumberOfPlayers("Total Players"), + SessionField.TournamentPosition("Finished Place"), + SessionField.TournamentType("Single-Table/Multi-Table") + + ) + + val runGoodCashGames: CSVDescriptor = SessionCSVDescriptor( + DataSource.RUNGOOD, + false, + SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), + SessionField.StartTime("Start Time", dateFormat = "HH:mm"), + SessionField.End("End Date", dateFormat = "dd/MM/yyyy"), + SessionField.EndTime("End Time", dateFormat = "HH:mm"), + SessionField.Buyin("Total Buy-In"), + SessionField.CashedOut("Cashed Out"), + SessionField.NetResult("Profit"), + SessionField.Break("Break"), + SessionField.LimitType("Limit Type"), + SessionField.Game("Game"), + SessionField.Bankroll("Bankroll"), + SessionField.TableSize("Table Type"), + SessionField.Location("Location"), + SessionField.LocationType("Location Type"), + SessionField.Comment("Notes"), + SessionField.CurrencyCode("Currency"), + SessionField.Blind("Stakes", callback = { value -> + // $10/20 + value.drop(1) + val blinds = value.split("/") + if (blinds.size == 2) { + return@Blind Pair(blinds.first().toDouble(), blinds.last().toDouble()) + } else { + return@Blind null + } + }) + ) + + + } + +} \ No newline at end of file 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 index c70d15bb..54dda264 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -1,248 +1,26 @@ package net.pokeranalytics.android.util.csv import io.realm.Realm -import io.realm.RealmModel import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.TournamentType +import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.model.realm.TransactionType -import net.pokeranalytics.android.model.utils.SessionUtils +import net.pokeranalytics.android.model.utils.DataUtils import net.pokeranalytics.android.util.extensions.getOrCreate import net.pokeranalytics.android.util.extensions.setHourMinutes import org.apache.commons.csv.CSVRecord +import timber.log.Timber 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 - ) : DateCSVField - - data class StartTime( - override var header: String, - override var callback: ((String) -> Date?)? = null, - override val dateFormat: String? = null - ) : DateCSVField - - data class End( - override var header: String, - override var callback: ((String) -> Date?)? = null, - override val dateFormat: String? = null - ) : DateCSVField - - data class EndTime( - override var header: String, - override var callback: ((String) -> Date?)? = null, - override val dateFormat: String? = null - ) : DateCSVField - - data class Buyin( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class NetResult( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class CashedOut( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class Break( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class Tips( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class SmallBlind( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class BigBlind( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class Rebuy( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class Addon( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - - data class Blind(override var header: String, override var callback: ((String) -> Pair?)? = 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 - ) : NumberCSVField - - data class TournamentPosition( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class TournamentNumberOfPlayers( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField -} - /** * A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects */ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: CSVField) : - DataCSVDescriptor(source, *elements) { - - companion object { - val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor( - DataSource.POKER_INCOME, - false, - SessionField.Start("Start Time"), - SessionField.End("End Time"), - SessionField.Buyin("Buy In"), - SessionField.CashedOut("Cashed Out"), - SessionField.Break("Break Minutes"), - SessionField.LimitType("Limit Type"), - SessionField.Game("Game"), - SessionField.Bankroll("Bankroll"), - SessionField.Location("Location"), - SessionField.Location("Location Type"), - SessionField.Comment("Note"), - SessionField.Tips("Tips"), - SessionField.Blind("Stake") - ) - - val pokerBankrollTracker: CSVDescriptor = SessionCSVDescriptor( - DataSource.POKER_BANKROLL_TRACKER, - true, - SessionField.Start("starttime", dateFormat = "MM/dd/yy HH:mm"), - SessionField.End("endtime", dateFormat = "MM/dd/yy HH:mm"), - SessionField.SessionType("variant"), - SessionField.Buyin("buyin"), - SessionField.CashedOut("cashout"), - SessionField.Rebuy("rebuycosts"), - SessionField.Addon("addoncosts"), - SessionField.Break("breakminutes"), - SessionField.LimitType("limit"), - SessionField.Game("game"), - SessionField.Bankroll("currency"), // same as currency code - SessionField.Location("type"), -// SessionField.Comment("sessionnote"), - SessionField.Tips("expensesfromstack"), - SessionField.SmallBlind("smallblind"), - SessionField.BigBlind("bigblind"), - SessionField.TournamentNumberOfPlayers("player"), - SessionField.TournamentPosition("place"), - SessionField.TournamentName("mttname"), - SessionField.CurrencyCode("currency"), - SessionField.CurrencyRate("exchangerate"), - SessionField.TableSize("tablesize") - ) - - val runGoodTournaments: CSVDescriptor = SessionCSVDescriptor( - DataSource.RUNGOOD, - true, - SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), - SessionField.StartTime("Start Time"), - SessionField.End("End Date"), - SessionField.EndTime("End Time"), - SessionField.Buyin("Total Buy-In"), - SessionField.CashedOut("Winnings"), - SessionField.NetResult("Profit"), - SessionField.Break("Break"), - SessionField.LimitType("Limit Type"), - SessionField.Game("Game"), - SessionField.Bankroll("Bankroll"), - SessionField.TableSize("Table Type"), - SessionField.Location("Location"), - SessionField.LocationType("Location Type"), - SessionField.Comment("Notes"), - SessionField.CurrencyCode("Currency"), - SessionField.TournamentName("Event Name"), - SessionField.TournamentNumberOfPlayers("Total Players"), - SessionField.TournamentPosition("Finished Place"), - SessionField.TournamentType("Single-Table/Multi-Table") - - ) - - val runGoodCashGames: CSVDescriptor = SessionCSVDescriptor( - DataSource.RUNGOOD, - false, - SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), - SessionField.StartTime("Start Time", dateFormat = "HH:mm"), - SessionField.End("End Date", dateFormat = "dd/MM/yyyy"), - SessionField.EndTime("End Time", dateFormat = "HH:mm"), - SessionField.Buyin("Total Buy-In"), - SessionField.CashedOut("Cashed Out"), - SessionField.NetResult("Profit"), - SessionField.Break("Break"), - SessionField.LimitType("Limit Type"), - SessionField.Game("Game"), - SessionField.Bankroll("Bankroll"), - SessionField.TableSize("Table Type"), - SessionField.Location("Location"), - SessionField.LocationType("Location Type"), - SessionField.Comment("Notes"), - SessionField.CurrencyCode("Currency"), - SessionField.Blind("Stakes", callback = { value -> - // $10/20 - value.drop(1) - val blinds = value.split("/") - if (blinds.size == 2) { - return@Blind Pair(blinds.first().toDouble(), blinds.last().toDouble()) - } else { - return@Blind null - } - }) - ) - - } + DataCSVDescriptor(source, *elements) { private enum class DataType { TRANSACTION, @@ -264,7 +42,7 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean /** * Parses a [record] and return an optional Session */ - override fun parseData(realm: Realm, record: CSVRecord): RealmModel? { + override fun parseData(realm: Realm, record: CSVRecord): Identifiable? { var dataType: DataType? = null val typeField = fields.firstOrNull { it is SessionField.SessionType } @@ -282,7 +60,7 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean } - fun parseTransaction(realm: Realm, record: CSVRecord): Transaction? { + private fun parseTransaction(realm: Realm, record: CSVRecord): Transaction? { var date: Date? = null var amount: Double? = null @@ -313,24 +91,32 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean if (date != null && amount != null && type != null && currencyCode != null) { - val transaction = realm.copyToRealm(Transaction()) - transaction.date = date!! - transaction.amount = amount!! - transaction.type = type + if (DataUtils.transactionUnicityCheck(realm, date!!, amount!!, type!!)) { - val bankroll = Bankroll.getOrCreate(realm, currencyCode!!, currencyRate = currencyRate) - transaction.bankroll = bankroll + val transaction = realm.copyToRealm(Transaction()) + transaction.date = date!! + transaction.amount = amount!! + transaction.type = type - return transaction + val bankroll = Bankroll.getOrCreate(realm, currencyCode!!, currencyRate = currencyRate) + transaction.bankroll = bankroll + return transaction + } else { + Timber.d("Transaction already exists") + } + } else { + Timber.d("Can't import transaction: date=$date, amount=$amount, type=${type?.name}") } return null } - fun parseSession(realm: Realm, record: CSVRecord): Session? { + private fun parseSession(realm: Realm, record: CSVRecord): Session? { val session = Session.newInstance(realm, this.isTournament) + + var isLive = true var bankrollName: String? = null var currencyCode: String? = null @@ -423,14 +209,17 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean } if (startDate != null && endDate != null && net != null) { // valid session - if (SessionUtils.unicityCheck(realm, startDate, endDate, net)) { + if (DataUtils.sessionCount(realm, startDate, endDate, net) == 1) { // session already in realm, we'd love not put it in Realm before doing the check return session + } else { + Timber.d("Session already exists") } + } else { + Timber.d("Can't import session: sd=$startDate, ed=$endDate, net=$net") } session.deleteFromRealm() return null - } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt new file mode 100644 index 00000000..6f0876af --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt @@ -0,0 +1,122 @@ +package net.pokeranalytics.android.util.csv + +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 + ) : DateCSVField + + data class StartTime( + override var header: String, + override var callback: ((String) -> Date?)? = null, + override val dateFormat: String? = null + ) : DateCSVField + + data class End( + override var header: String, + override var callback: ((String) -> Date?)? = null, + override val dateFormat: String? = null + ) : DateCSVField + + data class EndTime( + override var header: String, + override var callback: ((String) -> Date?)? = null, + override val dateFormat: String? = null + ) : DateCSVField + + data class Buyin( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class NetResult( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class CashedOut( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Break( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Tips( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class SmallBlind( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class BigBlind( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Rebuy( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Addon( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + + data class Blind(override var header: String, override var callback: ((String) -> Pair?)? = 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 + ) : NumberCSVField + + data class TournamentPosition( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class TournamentNumberOfPlayers( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField +} diff --git a/app/src/main/res/layout/fragment_import.xml b/app/src/main/res/layout/fragment_import.xml index 3d2e9ce5..67018bcd 100644 --- a/app/src/main/res/layout/fragment_import.xml +++ b/app/src/main/res/layout/fragment_import.xml @@ -5,9 +5,21 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e0726ec..d9c9355e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -37,6 +37,7 @@ The filter cannot be deleted because it is currently selected. Custom field The item is used in one or more transactions…Please delete the linked transactions first + Imported Address Naming suggestions diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 94f1afb6..23e128e8 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -353,7 +353,16 @@ - + + + +