meos-2024/code/oFreePunch.cpp
2018-03-05 22:07:20 +01:00

721 lines
21 KiB
C++

/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2018 Melin Software HB
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Melin Software HB - software@melin.nu - www.melin.nu
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
************************************************************************/
#include "stdafx.h"
#include <algorithm>
#include <cassert>
#include "oFreePunch.h"
#include "oEvent.h"
#include "Table.h"
#include "meos_util.h"
#include "Localizer.h"
#include "intkeymapimpl.hpp"
#include "socket.h"
#include "meosdb/sqltypes.h"
#include "gdioutput.h"
bool oFreePunch::disableHashing = false;
oFreePunch::oFreePunch(oEvent *poe, int card, int time, int type): oPunch(poe)
{
Id=oe->getFreePunchId();
CardNo = card;
Time = time;
Type = type;
iHashType = 0;
tRunnerId = 0;
}
oFreePunch::oFreePunch(oEvent *poe, int id): oPunch(poe)
{
Id=id;
oe->qFreePunchId = max(id, oe->qFreePunchId);
iHashType = 0;
tRunnerId = 0;
}
oFreePunch::~oFreePunch(void)
{
}
bool oFreePunch::Write(xmlparser &xml)
{
if (Removed) return true;
xml.startTag("Punch");
xml.write("CardNo", CardNo);
xml.write("Time", Time);
xml.write("Type", Type);
xml.write("Id", Id);
xml.write("Updated", Modified.getStamp());
xml.endTag();
return true;
}
void oFreePunch::Set(const xmlobject *xo)
{
xmlList xl;
xo->getObjects(xl);
xmlList::const_iterator it;
for(it=xl.begin(); it != xl.end(); ++it){
if (it->is("CardNo")){
CardNo=it->getInt();
}
if (it->is("Type")){
Type=it->getInt();
}
if (it->is("Time")){
Time=it->getInt();
}
else if (it->is("Id")){
Id=it->getInt();
}
else if (it->is("Updated")){
Modified.setStamp(it->getRaw());
}
}
}
bool oFreePunch::setCardNo(int cno, bool databaseUpdate) {
if (cno != CardNo) {
pRunner r1 = oe->getRunner(tRunnerId, 0);
int oldControlId = tMatchControlId;
//oe->punchIndex[itype].remove(CardNo); // Remove from index
// Remove ourself from index
oEvent::PunchIndexType &pi = oe->punchIndex[iHashType];
oEvent::PunchConstIterator it = pi.find(CardNo);
while (it != pi.end() && it->first == CardNo) {
if (it->second == this) {
pi.erase(it);
break;
}
++it;
}
oe->removeFromPunchHash(CardNo, Type, Time);
rehashPunches(*oe, CardNo, 0);
CardNo = cno;
oe->insertIntoPunchHash(CardNo, Type, Time);
rehashPunches(*oe, CardNo, this);
pRunner r2 = oe->getRunner(tRunnerId, 0);
if (r1 && oldControlId > 0)
r1->markClassChanged(oldControlId);
if (r2 && iHashType > 0)
r2->markClassChanged(tMatchControlId);
if (!databaseUpdate)
updateChanged();
return true;
}
return false;
}
void oFreePunch::remove()
{
if (oe)
oe->removeFreePunch(Id);
}
bool oFreePunch::canRemove() const
{
return true;
}
Table *oEvent::getPunchesTB()//Table mode
{
if (tables.count("punch") == 0) {
Table *table=new Table(this, 20, L"Stämplingar", "punches");
table->addColumn("Id", 70, true, true);
table->addColumn("Ändrad", 150, false);
table->addColumn("Bricka", 70, true);
table->addColumn("Kontroll", 70, true);
table->addColumn("Tid", 70, false);
table->addColumn("Löpare", 170, false);
table->addColumn("Lag", 170, false);
tables["punch"] = table;
table->addOwnership();
}
tables["punch"]->update();
return tables["punch"];
}
void oEvent::generatePunchTableData(Table &table, oFreePunch *addPunch)
{
if (addPunch) {
addPunch->addTableRow(table);
return;
}
synchronizeList(oLPunchId);
oFreePunchList::iterator it;
oe->setupCardHash(false);
table.reserve(punches.size());
for (it = punches.begin(); it != punches.end(); ++it){
if (!it->isRemoved()){
it->addTableRow(table);
}
}
oe->setupCardHash(true);
}
void oFreePunch::addTableRow(Table &table) const {
oFreePunch &it = *pFreePunch(this);
table.addRow(getId(), &it);
int row = 0;
table.set(row++, it, TID_ID, itow(getId()), false, cellEdit);
table.set(row++, it, TID_MODIFIED, getTimeStamp(), false, cellEdit);
table.set(row++, it, TID_CARD, itow(getCardNo()), true, cellEdit);
table.set(row++, it, TID_CONTROL, getType(), true, cellEdit);
table.set(row++, it, TID_TIME, getTime(), true, cellEdit);
pRunner r = 0;
if (CardNo > 0)
r = oe->getRunnerByCardNo(CardNo, Time, false);
table.set(row++, it, TID_RUNNER, r ? r->getName() : L"?", false, cellEdit);
if (r && r->getTeam())
table.set(row++, it, TID_TEAM, r->getTeam()->getName(), false, cellEdit);
else
table.set(row++, it, TID_TEAM, L"", false, cellEdit);
}
bool oFreePunch::inputData(int id, const wstring &input,
int inputId, wstring &output, bool noUpdate)
{
synchronize(false);
switch(id) {
case TID_CARD:
setCardNo(_wtoi(input.c_str()));
synchronize(true);
output = itow(CardNo);
break;
case TID_TIME:
setTime(input);
synchronize(true);
output = getTime();
break;
case TID_CONTROL:
setType(input);
synchronize(true);
output = getType();
}
return false;
}
void oFreePunch::fillInput(int id, vector< pair<wstring, size_t> > &out, size_t &selected)
{
}
void oFreePunch::setTimeInt(int t, bool databaseUpdate) {
if (t != Time) {
oe->removeFromPunchHash(CardNo, Type, Time);
Time = t;
oe->insertIntoPunchHash(CardNo, Type, Time);
rehashPunches(*oe, CardNo, 0);
if (!databaseUpdate)
updateChanged();
}
}
bool oFreePunch::setType(const wstring &t, bool databaseUpdate) {
int type = _wtoi(t.c_str());
int ttype = 0;
if (type>0 && type<10000)
ttype = type;
else {
if (t == lang.tl("Check"))
ttype = oPunch::PunchCheck;
else if (t == lang.tl("Mål"))
ttype = oPunch::PunchFinish;
if (t == lang.tl("Start"))
ttype = oPunch::PunchStart;
}
if (ttype > 0 && ttype != Type) {
oe->removeFromPunchHash(CardNo, Type, Time);
Type = ttype;
oe->insertIntoPunchHash(CardNo, Type, Time);
int oldControlId = tMatchControlId;
rehashPunches(*oe, CardNo, 0);
pRunner r = oe->getRunner(tRunnerId, 0);
if (r) {
r->markClassChanged(tMatchControlId);
if (oldControlId > 0)
r->markClassChanged(oldControlId);
}
if (!databaseUpdate)
updateChanged();
return true;
}
return false;
}
void oFreePunch::rehashPunches(oEvent &oe, int cardNo, pFreePunch newPunch) {
if (disableHashing || (cardNo == 0 && !oe.punchIndex.empty()) || oe.punches.empty())
return;
vector<pFreePunch> fp;
if (oe.punchIndex.empty()) {
// Rehash all punches. Ignore cardNo and newPunch (will be included automatically)
fp.reserve(oe.punches.size());
for (oFreePunchList::iterator pit = oe.punches.begin(); pit != oe.punches.end(); ++pit) {
if (pit->isRemoved())
continue;
fp.push_back(&(*pit));
}
sort(fp.begin(), fp.end(), FreePunchComp());
disableHashing = true;
try {
for (size_t j = 0; j < fp.size(); j++) {
pFreePunch punch = fp[j];
punch->iHashType = oe.getControlIdFromPunch(punch->Time, punch->Type, punch->CardNo, true,
*punch);
oEvent::PunchIndexType &card2Punch = oe.punchIndex[punch->iHashType];
card2Punch.insert(make_pair(punch->CardNo, punch));
}
}
catch(...) {
disableHashing = false;
throw;
}
disableHashing = false;
return;
}
map<int, oEvent::PunchIndexType>::iterator it;
fp.reserve(oe.punchIndex.size() + 1);
// Get all punches for the specified card.
for(it = oe.punchIndex.begin(); it != oe.punchIndex.end(); ++it) {
pair<oEvent::PunchConstIterator, oEvent::PunchConstIterator> res = it->second.equal_range(cardNo);
oEvent::PunchConstIterator pIter = res.first;
while(pIter != res.second) {
pFreePunch punch = pIter->second;
assert(punch && punch->CardNo == cardNo);
if (!punch->isRemoved()) {
fp.push_back(punch);
}
++pIter;
}
it->second.erase(res.first, res.second);
}
if (newPunch)
fp.push_back(newPunch);
sort(fp.begin(), fp.end(), FreePunchComp());
for (size_t j = 0; j < fp.size(); j++) {
if (j>0 && fp[j-1] == fp[j])
continue; //Skip duplicates
pFreePunch punch = fp[j];
punch->iHashType = oe.getControlIdFromPunch(punch->Time, punch->Type, cardNo, true, *punch);
oEvent::PunchIndexType &card2Punch = oe.punchIndex[punch->iHashType];
card2Punch.insert(make_pair(punch->CardNo, punch));
}
}
//const int legHashConstant = 100000;
int oFreePunch::getControlHash(int courseControlId, int race) {
int newId = courseControlId + race*100000000;
return newId;
}
int oFreePunch::getControlIdFromHash(int hash, bool courseControlId) {
int r = (hash%100000000);
if (courseControlId)
return r;
else
return oControl::getIdIndexFromCourseControlId(r).first;
}
int oEvent::getControlIdFromPunch(int time, int type, int card,
bool markClassChanged, oFreePunch &punch) {
pRunner r = getRunnerByCardNo(card, time);
punch.tRunnerId = -1;
punch.tMatchControlId = type;
if (r!=0) {
punch.tRunnerId = r->Id;
}
int race = 0;
if (type!=oPunch::PunchFinish) {
pCourse c = r ? r->getCourse(false): 0;
if (c!=0) {
race = r->getRaceNo();
for (int k=0; k<c->nControls; k++) {
pControl ctrl=c->getControl(k);
if (ctrl && ctrl->hasNumber(type)) {
int courseControlId = c->getCourseControlId(k);
pFreePunch p = getPunch(r->getId(), courseControlId, card);
if (!p || (p && abs(p->Time-time)<60)) {
ctrl->tHasFreePunchLabel = true;
punch.tMatchControlId = ctrl->getId();
punch.tIndex = k;
int newId = oFreePunch::getControlHash(courseControlId, race);
if (newId != punch.iHashType && markClassChanged && r) {
r->markClassChanged(ctrl->getId());
if (punch.iHashType > 0)
r->markClassChanged(oFreePunch::getControlIdFromHash(punch.iHashType, false));
}
//Code controlId and runner race number into code
return newId;
}
}
}
}
}
int newId = oFreePunch::getControlHash(type, 0);
if (newId != punch.iHashType && markClassChanged && r) {
r->markClassChanged(type);
if (punch.iHashType > 0)
r->markClassChanged(oFreePunch::getControlIdFromHash(punch.iHashType, false));
}
return newId;
}
void oEvent::getFreeControls(set<int> &controlId) const
{
controlId.clear();
for (map<int, PunchIndexType >::const_iterator it = punchIndex.begin(); it != punchIndex.end(); ++it) {
int id = oFreePunch::getControlIdFromHash(it->first, false);
controlId.insert(id);
}
}
//set< pair<int,int> > readPunchHash;
void oEvent::insertIntoPunchHash(int card, int code, int time) {
if (time > 0) {
int p1 = time * 4096 + code;
int p2 = card;
readPunchHash.insert(make_pair(p1, p2));
}
}
void oEvent::removeFromPunchHash(int card, int code, int time) {
int p1 = time * 4096 + code;
int p2 = card;
readPunchHash.erase(make_pair(p1, p2));
}
bool oEvent::isInPunchHash(int card, int code, int time) {
int p1 = time * 4096 + code;
int p2 = card;
return readPunchHash.count(make_pair(p1, p2)) > 0;
}
pFreePunch oEvent::addFreePunch(int time, int type, int card, bool updateStartFinish) {
if (time > 0 && isInPunchHash(card, type, time))
return 0;
oFreePunch ofp(this, card, time, type);
punches.push_back(ofp);
pFreePunch fp=&punches.back();
oFreePunch::rehashPunches(*this, card, fp);
insertIntoPunchHash(card, type, time);
if (fp->getTiedRunner() && oe->isClient() && oe->getPropertyInt("UseDirectSocket", true)!=0) {
SocketPunchInfo pi;
pi.runnerId = fp->getTiedRunner()->getId();
pi.time = fp->getAdjustedTime();
pi.status = fp->getTiedRunner()->getStatus();
if (fp->getTypeCode() > 10)
pi.iHashType = fp->getIHashType();
else
pi.iHashType = fp->getTypeCode();
getDirectSocket().sendPunch(pi);
}
fp->updateChanged();
fp->synchronize();
// Update start/finish time
if (updateStartFinish && type == oPunch::PunchStart || type == oPunch::PunchFinish) {
pRunner tr = fp->getTiedRunner();
if (tr && tr->getStatus() == StatusUnknown && time > 0) {
tr->synchronize();
if (type == oPunch::PunchStart) {
if (tr->getClassRef(false) && !tr->getClassRef(true)->ignoreStartPunch())
tr->setStartTime(time, true, false);
}
else
tr->setFinishTime(time);
// Direct result
if (type == oPunch::PunchFinish && tr->getClassRef(false) && tr->getClassRef(true)->hasDirectResult()) {
if (tr->getCourse(false) == 0 && tr->getCard() == 0) {
tr->setStatus(StatusOK, true, false, true);
}
else if (tr->getCourse(false) != 0 && tr->getCard() == 0) {
pCard card = allocateCard(tr);
card->setupFromRadioPunches(*tr);
vector<int> mp;
card->synchronize();
tr->addPunches(card, mp);
}
}
tr->synchronize(true);
}
}
if (fp->getTiedRunner())
pushDirectChange();
return fp;
}
pFreePunch oEvent::addFreePunch(oFreePunch &fp) {
insertIntoPunchHash(fp.CardNo, fp.Type, fp.Time);
punches.push_back(fp);
pFreePunch fpz=&punches.back();
oFreePunch::rehashPunches(*this, fp.CardNo, fpz);
if (!fpz->existInDB() && HasDBConnection) {
fpz->changed = true;
fpz->synchronize();
}
return fpz;
}
void oEvent::removeFreePunch(int Id) {
oFreePunchList::iterator it;
for (it=punches.begin(); it != punches.end(); ++it) {
if (it->Id==Id) {
pRunner r = getRunner(it->tRunnerId, 0);
if (r && r->Class) {
r->markClassChanged(it->tMatchControlId);
classChanged(r->Class, true);
}
pFreePunch fp = &*it;
if (HasDBConnection)
msRemove(fp);
//punchIndex[it->itype].remove(it->CardNo);
PunchIndexType &ix = punchIndex[it->iHashType];
pair<PunchConstIterator, PunchConstIterator> res = ix.equal_range(it->CardNo);
while (res.first != res.second) {
if (res.first->second == fp) {
PunchConstIterator rm = res.first;
++res.first;
ix.erase(rm);
}
else
++res.first;
}
int cardNo = fp->CardNo;
removeFromPunchHash(cardNo, fp->Type, fp->Time);
punches.erase(it);
oFreePunch::rehashPunches(*this, cardNo, 0);
dataRevision++;
return;
}
}
}
pFreePunch oEvent::getPunch(int Id) const
{
oFreePunchList::const_iterator it;
for (it=punches.begin(); it != punches.end(); ++it) {
if (it->Id==Id) {
if (it->isRemoved())
return 0;
return const_cast<pFreePunch>(&*it);
}
}
return 0;
}
pFreePunch oEvent::getPunch(int runnerId, int courseControlId, int card) const
{
//Lazy setup
oFreePunch::rehashPunches(*oe, 0, 0);
pRunner r = oe->getRunner(runnerId, 0);
int runnerRace = r ? r->getRaceNo() : 0;
map<int, oEvent::PunchIndexType>::const_iterator it1;
int itype = oFreePunch::getControlHash(courseControlId, runnerRace);
it1=punchIndex.find(itype);
if (it1!=punchIndex.end()) {
const oEvent::PunchIndexType &cIndex = it1->second;
pair<oEvent::PunchConstIterator, oEvent::PunchConstIterator> res = cIndex.equal_range(card);
oEvent::PunchConstIterator pIter = res.first;
while(pIter != res.second) {
pFreePunch punch = pIter->second;
if (!punch->isRemoved()) {
assert(punch && punch->CardNo == card);
if (punch->tRunnerId == runnerId || runnerId == 0)
return punch;
++pIter;
}
}
}
map<pair<int, int>, oFreePunch>::const_iterator res = advanceInformationPunches.find(make_pair(itype, card));
if (res != advanceInformationPunches.end())
return (pFreePunch)&res->second;
return 0;
}
void oEvent::getPunchesForRunner(int runnerId, vector<pFreePunch> &runnerPunches) const {
runnerPunches.clear();
pRunner r = getRunner(runnerId, 0);
if (r == 0)
return;
// Get times for when other runners used this card
vector< pair<int, int> > times;
for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) {
if (it->Id == runnerId)
continue;
if (it->Card && it->CardNo == r->CardNo) {
pair<int, int> t = it->Card->getTimeRange();
if (it->getStartTime() > 0)
t.first = min(it->getStartTime(), t.first);
if (it->getFinishTime() > 0)
t.second = max(it->getFinishTime(), t.second);
times.push_back(t);
}
}
for (oFreePunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) {
if (it->CardNo == r->CardNo) {
bool other = false;
int t = it->Time;
for (size_t k = 0; k<times.size(); k++) {
if (t >= times[k].first && t <= times[k].second)
other = true;
}
if (!other)
runnerPunches.push_back(pFreePunch(&*it));
}
}
// XXX Advance punches...
}
bool oEvent::advancePunchInformation(const vector<gdioutput *> &gdi, vector<SocketPunchInfo> &pi,
bool fetchPunch, bool fetchFinish) {
if (pi.empty())
return false;
bool m = false;
for (size_t k = 0; k < pi.size(); k++) {
pRunner r = getRunner(pi[k].runnerId, 0);
if (!r)
continue;
if (pi[k].iHashType == oPunch::PunchFinish && fetchFinish) {
if (r->getStatus() == StatusUnknown && r->getFinishTime() <= 0 && !r->isChanged()) {
r->FinishTime = pi[k].time;
r->tStatus = RunnerStatus(pi[k].status);
r->status = RunnerStatus(pi[k].status); // Will be overwritten (do not set isChanged flag)
if (r->Class) {
r->markClassChanged(oPunch::PunchFinish);
classChanged(r->Class, false);
}
m = true;
}
}
else if (fetchPunch) {
// controlId is already the encoded format including index and race
if (getPunch(pi[k].runnerId, pi[k].iHashType, r->getCardNo()) == 0) {
oFreePunch fp(this, 0, pi[k].time, pi[k].iHashType);
fp.tRunnerId = pi[k].runnerId;
fp.iHashType = pi[k].iHashType;
fp.tIndex = 0;
fp.tMatchControlId = oFreePunch::getControlIdFromHash(fp.iHashType, false);
fp.changed = false;
pair<int, int> hc(pi[k].iHashType, r->getCardNo());
advanceInformationPunches.insert(make_pair(hc,fp));
if (r->Class) {
r->markClassChanged(oFreePunch::getControlIdFromHash(pi[k].iHashType, false));
classChanged(r->Class, true);
}
m = true;
}
}
}
if (m) {
dataRevision++;
for (size_t k = 0; k<gdi.size(); k++) {
if (gdi[k])
gdi[k]->makeEvent("DataUpdate", "autosync", 0, 0, false);
}
}
return m;
}
void oEvent::getLatestPunches(int firstTime, vector<const oFreePunch *> &punchesOut) const {
for (map< pair<int, int>, oFreePunch>::const_iterator it = advanceInformationPunches.begin();
it != advanceInformationPunches.end(); ++it) {
int time = it->second.getModificationTime();
if (time >= firstTime)
punchesOut.push_back(&it->second);
}
for (oFreePunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) {
int time = it->getModificationTime();
if (time >= firstTime)
punchesOut.push_back(&*it);
}
}
pRunner oFreePunch::getTiedRunner() const {
return oe->getRunner(tRunnerId, 0);
}
void oFreePunch::changedObject() {
pRunner r = getTiedRunner();
if (r && tMatchControlId>0)
r->markClassChanged(tMatchControlId);
}