/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2017 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 .
Melin Software HB - software@melin.nu - www.melin.nu
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
************************************************************************/
#include "stdafx.h"
#include "RunnerDB.h"
#include "xmlparser.h"
#include "oRunner.h"
#include "Table.h"
#include "io.h"
#include "fcntl.h"
#include "sys/stat.h"
#include "meos_util.h"
#include "oDataContainer.h"
#include "meosException.h"
#include
#include
#include "intkeymapimpl.hpp"
#include "oEvent.h"
RunnerDB::RunnerDB(oEvent *oe_): oe(oe_)
{
loadedFromServer = false;
dataDate = 20100201;
dataTime = 222222;
runnerTable = 0;
clubTable = 0;
}
RunnerDB::~RunnerDB(void)
{
releaseTables();
}
RunnerDBEntry::RunnerDBEntry()
{
memset(this, 0, sizeof(RunnerDBEntry));
}
RunnerDBEntryV1::RunnerDBEntryV1()
{
memset(this, 0, sizeof(RunnerDBEntryV1));
}
void RunnerDBEntry::getName(string &n) const
{
n=name;
}
void RunnerDBEntry::setName(const char *n)
{
memcpy(name, n, min(strlen(n)+1, baseNameLength));
name[baseNameLength-1]=0;
}
string RunnerDBEntry::getNationality() const
{
if (national[0] < 30)
return _EmptyString;
string n(" ");
n[0] = national[0];
n[1] = national[1];
n[2] = national[2];
return n;
}
string RunnerDBEntry::getSex() const
{
if (sex == 0)
return _EmptyString;
string n("W");
n[0] = sex;
return n;
}
string RunnerDBEntry::getGivenName() const
{
return ::getGivenName(name);
}
string RunnerDBEntry::getFamilyName() const
{
return ::getFamilyName(name);
}
__int64 RunnerDBEntry::getExtId() const
{
return extId;
}
void RunnerDBEntry::setExtId(__int64 id)
{
extId = id;
}
void RunnerDBEntry::init(const RunnerDBEntryV1 &dbe)
{
memcpy(this, &dbe, sizeof(RunnerDBEntryV1));
extId = 0;
}
RunnerDBEntry *RunnerDB::addRunner(const char *name,
__int64 extId,
int club, int card)
{
rdb.push_back(RunnerDBEntry());
RunnerDBEntry &e=rdb.back();
e.cardNo = card;
e.clubNo = club;
e.setName(name);
e.extId = extId;
if (!check(e) ) {
rdb.pop_back();
return 0;
} else {
if (card>0)
rhash[card]=rdb.size()-1;
if (!idhash.empty())
idhash[extId] = rdb.size()-1;
if (!nhash.empty())
nhash.insert(pair(canonizeName(e.name), rdb.size()-1));
}
return &e;
}
int RunnerDB::addClub(oClub &c, bool createNewId) {
//map::iterator it = chash.find(c.getId());
//if (it == chash.end()) {
if (createNewId) {
oDBClubEntry ce(c, cdb.size(), this);
cdb.push_back(ce);
int b = 0;
while(++b<0xFFFF) {
int newId = 10000 + rand() & 0xFFFF;
int dummy;
if (!chash.lookup(newId, dummy)) {
cdb.back().Id = newId;
chash[c.getId()]=cdb.size()-1;
return newId;
}
}
cdb.pop_back();
throw meosException("Internal database error");
}
int value;
if (!chash.lookup(c.getId(), value)) {
oDBClubEntry ce(c, cdb.size(), this);
cdb.push_back(ce);
chash[c.getId()]=cdb.size()-1;
}
else {
oDBClubEntry ce(c, value, this);
cdb[value] = ce;
}
return c.getId();
}
void RunnerDB::importClub(oClub &club, bool matchName)
{
pClub pc = getClub(club.getId());
if (pc && !pc->sameClub(club)) {
//The new id is used by some other club.
//Remap old club first
int oldId = pc->getId();
int newId = chash.size() + 1;//chash.rbegin()->first + 1;
for (size_t k=0; kgetId();
int newId = club.getId();
for (size_t k=0; k clubmap;
for (size_t k=0;k compacted;
for (size_t j=k+1;jba) {
best = &cdb[compacted[j]];
ba=nba;
}
}
swap(ref, *best);
//Update map
for (size_t j=0;j= rdb.size())
throw meosException("Index out of bounds");
return (RunnerDBEntry *)&rdb[index];
}
RunnerDBEntry *RunnerDB::getRunnerById(__int64 extId) const
{
if (extId == 0)
return 0;
setupIdHash();
int value;
if (idhash.lookup(extId, value))
return (RunnerDBEntry *)&rdb[value];
return 0;
}
RunnerDBEntry *RunnerDB::getRunnerByName(const string &name, int clubId,
int expectedBirthYear) const
{
if (expectedBirthYear>0 && expectedBirthYear<100)
expectedBirthYear = extendYear(expectedBirthYear);
setupNameHash();
vector ix;
string cname(canonizeName(name.c_str()));
multimap::const_iterator it = nhash.find(cname);
while (it != nhash.end() && cname == it->first) {
ix.push_back(it->second);
++it;
}
if (ix.empty())
return 0;
if (clubId == 0) {
if (ix.size() == 1)
return (RunnerDBEntry *)&rdb[ix[0]];
else
return 0; // Not uniquely defined.
}
// Filter on club
vector ix2;
for (size_t k = 0;k 0) {
int bestMatch = 0;
int bestYear = 0;
for (size_t k = 0;k0)
return (RunnerDBEntry *)&rdb[bestMatch];
}
return 0;
}
void RunnerDB::setupIdHash() const
{
if (!idhash.empty())
return;
for (size_t k=0; k(canonizeName(rdb[k].name), k));
}
}
void RunnerDB::setupCNHash() const
{
if (!cnhash.empty())
return;
vector split;
for (size_t k=0; k(split[j], k));
}
}
static bool isVowel(int c) {
return c=='a' || c=='e' || c=='i' ||
c=='o' || c=='u' || c=='y' ||
c=='å' || c=='ä' || c=='ö';
}
void RunnerDB::canonizeSplitName(const string &name, vector &split)
{
split.clear();
const char *cname = name.c_str();
int k = 0;
for (k=0; cname[k]; k++)
if (cname[k] != ' ')
break;
char out[128];
int outp;
while (cname[k]) {
outp = 0;
while(cname[k] != ' ' && cname[k] && outp<(sizeof(out)-1) ) {
if (cname[k] == '-') {
k++;
break;
}
out[outp++] = toLowerStripped(cname[k]);
k++;
}
out[outp] = 0;
if (outp > 0) {
for (int j=1; j4 && out[outp-1]=='s')
out[outp-1] = 0; // Identify Linköping och Linköpings
split.push_back(out);
}
while(cname[k] == ' ')
k++;
}
}
bool RunnerDB::getClub(int clubId, string &club) const
{
//map::const_iterator it = chash.find(clubId);
int value;
if (chash.lookup(clubId, value)) {
//if (it!=chash.end()) {
// int i=it->second;
club=cdb[value].getName();
return true;
}
return false;
}
oClub *RunnerDB::getClub(int clubId) const
{
//map::const_iterator it = chash.find(clubId);
//if (it!=chash.end())
int value;
if (chash.lookup(clubId, value))
return pClub(&cdb[value]);
return 0;
}
oClub *RunnerDB::getClub(const string &name) const
{
setupCNHash();
vector names;
canonizeSplitName(name, names);
vector< vector > ix(names.size());
set iset;
for (size_t k = 0; k::const_iterator it = cnhash.find(names[k]);
while (it != cnhash.end() && names[k] == it->first) {
ix[k].push_back(it->second);
++it;
}
if (ix[k].size() == 1 && names[k].length()>3)
return pClub(&cdb[ix[k][0]]);
if (iset.empty())
iset.insert(ix[k].begin(), ix[k].end());
else {
set im;
for (size_t j = 0; j::iterator it = iset.begin(); it != iset.end(); ++it) {
pClub pc = pClub(&cdb[*it]);
if (_stricmp(pc->getName().c_str(), name.c_str())==0)
return pc;
}
string cname = canonizeName(name.c_str());
// Looser compare
for (set::iterator it = iset.begin(); it != iset.end(); ++it) {
pClub pc = pClub(&cdb[*it]);
if (strcmp(canonizeName(pc->getName().c_str()), cname.c_str()) == 0 )
return pc;
}
double best = 1;
double secondBest = 1;
int bestIndex = -1;
for (set::iterator it = iset.begin(); it != iset.end(); ++it) {
pClub pc = pClub(&cdb[*it]);
double d = stringDistance(cname.c_str(), canonizeName(pc->getName().c_str()));
if (d0.4)
return pClub(&cdb[bestIndex]);
return 0;
}
void RunnerDB::saveClubs(const char *file)
{
xmlparser xml(0);
xml.openOutputT(file, true, "meosclubs");
vector::iterator it;
xml.startTag("ClubList");
for (it=cdb.begin(); it != cdb.end(); ++it)
it->write(xml);
xml.endTag();
xml.closeOut();
}
string RunnerDB::getDataDate() const
{
char bf[128];
if (dataTime<=0 && dataDate>0)
sprintf_s(bf, "%04d-%02d-%02d", dataDate/10000,
(dataDate/100)%100,
dataDate%100);
else if (dataDate>0)
sprintf_s(bf, "%04d-%02d-%02d %02d:%02d:%02d", dataDate/10000,
(dataDate/100)%100,
dataDate%100,
(dataTime/3600)%24,
(dataTime/60)%60,
(dataTime)%60);
else
return "2011-01-01 00:00:00";
return bf;
}
void RunnerDB::setDataDate(const string &date)
{
int d = convertDateYMS(date.substr(0, 10), false);
int t = date.length()>11 ? convertAbsoluteTimeHMS(date.substr(11), -1) : 0;
if (d<=0)
throw std::exception("Felaktigt datumformat");
dataDate = d;
if (t>0)
dataTime = t;
else
dataTime = 0;
}
void RunnerDB::saveRunners(const char *file)
{
int f=-1;
_sopen_s(&f, file, _O_BINARY|_O_CREAT|_O_TRUNC|_O_WRONLY,
_SH_DENYWR, _S_IREAD|_S_IWRITE);
if (f!=-1) {
int version = 5460002;
_write(f, &version, 4);
_write(f, &dataDate, 4);
_write(f, &dataTime, 4);
if (!rdb.empty())
_write(f, &rdb[0], rdb.size()*sizeof(RunnerDBEntry));
_close(f);
}
else throw std::exception("Could not save runner database.");
}
void RunnerDB::loadClubs(const char *file)
{
xmlparser xml(0);
xml.read(file);
xmlobject xo;
//Get clubs
xo=xml.getObject("ClubList");
if (xo) {
clearClubs();
loadedFromServer = false;
xmlList xl;
xo.getObjects(xl);
xmlList::const_iterator it;
cdb.clear();
chash.clear();
freeCIx = 0;
cdb.reserve(xl.size());
for (it=xl.begin(); it != xl.end(); ++it) {
if (it->is("Club")){
oDBClubEntry c(oe, cdb.size(), this);
c.set(*it);
int value;
//if (chash.find(c.getId()) == chash.end()) {
if (!chash.lookup(c.getId(), value)) {
chash[c.getId()]=cdb.size();
cdb.push_back(c);
}
}
}
}
bool checkClubs = false;
if (checkClubs) {
vector problems;
for (size_t k=0; kgetName());
if (!pc2)
problems.push_back(pc->getName());
else if (pc != pc2)
problems.push_back(pc->getName() + "-" + pc2->getName());
}
problems.begin();
}
}
void RunnerDB::loadRunners(const char *file)
{
string ex=string("Bad runner database. ")+file;
int f=-1;
_sopen_s(&f, file, _O_BINARY|_O_RDONLY,
_SH_DENYWR, _S_IREAD|_S_IWRITE);
if (f!=-1) {
clearRunners();
loadedFromServer = false;
int len = _filelength(f);
if ( (len%sizeof(RunnerDBEntryV1) != 0) && (len % sizeof(RunnerDBEntry) != 12)) {
_close(f);
return;//Failed
}
int nentry = 0;
if (len % sizeof(RunnerDBEntry) == 12) {
nentry = (len-12) / sizeof(RunnerDBEntry);
rdb.resize(nentry);
if (rdb.empty()) {
_close(f);
return;
}
int version;
_read(f, &version, 4);
_read(f, &dataDate, 4);
_read(f, &dataTime, 4);
_read(f, &rdb[0], len-12);
_close(f);
for (int k=0;k rdbV1(nentry);
_read(f, &rdbV1[0], len);
_close(f);
for (int k=0;k0)
ncard++;
rhash.resize(ncard);
for (int k=0;k0 && !rdb[k].isRemoved()) {
rhash[rdb[k].cardNo]=k;
}
}
}
else throw std::exception(ex.c_str());
}
bool RunnerDB::check(const RunnerDBEntry &rde) const
{
if (rde.cardNo<0 || rde.cardNo>99999999
|| rde.name[baseNameLength-1]!=0 || rde.clubNo<0)
return false;
return true;
}
void RunnerDB::updateAdd(const oRunner &r, map &clubIdMap)
{
if (r.getExtIdentifier() > 0) {
RunnerDBEntry *dbe = getRunnerById(int(r.getExtIdentifier()));
if (dbe) {
dbe->cardNo = r.CardNo;
return; // Do not change too much in runner from national database
}
}
const pClub pc = r.Club;
int localClubId = r.getClubId();
if (pc) {
if (clubIdMap.count(localClubId))
localClubId = clubIdMap[localClubId];
pClub dbClub = getClub(localClubId);
bool wrongId = false;
if (dbClub) {
if (dbClub->getName() != pc->getName()) {
dbClub = 0; // Wrong club!
wrongId = true;
}
}
if (dbClub == 0) {
dbClub = getClub(r.getClub());
if (dbClub) {
localClubId = dbClub->getId();
clubIdMap[pc->getId()] = localClubId;
}
}
if (dbClub == 0) {
localClubId = addClub(*pc, wrongId);
if (wrongId)
clubIdMap[pc->getId()] = localClubId;
}
}
RunnerDBEntry *dbe = getRunnerByCard(r.getCardNo());
if (dbe == 0) {
dbe = addRunner(r.getName().c_str(), 0, localClubId, r.getCardNo());
if (dbe)
dbe->birthYear = r.getDCI().getInt("BirthYear");
}
else {
if (dbe->getExtId() == 0) { // Only update entries not in national db.
dbe->setName(r.getName().c_str());
dbe->clubNo = localClubId;
dbe->birthYear = r.getDCI().getInt("BirthYear");
}
}
}
void RunnerDB::getAllNames(vector &givenName, vector &familyName)
{
givenName.reserve(rdb.size());
familyName.reserve(rdb.size());
for (size_t k=0;kclear();
}
void RunnerDB::clearRunners()
{
nhash.clear();
idhash.clear();
rhash.clear();
rdb.clear();
if (runnerTable)
runnerTable->clear();
}
const vector &RunnerDB::getClubDB() const {
return cdb;
}
const vector &RunnerDB::getRunnerDB() const {
return rdb;
}
void RunnerDB::prepareLoadFromServer(int nrunner, int nclub) {
loadedFromServer = true;
clearClubs(); // Implicitly clears runners
cdb.reserve(nclub);
rdb.reserve(nrunner);
}
void RunnerDB::fillClubs(vector< pair > &out) const {
out.reserve(cdb.size());
for (size_t k = 0; kgetDBRunnersInEvent(runnerInEvent);
if (addEntry) {
addEntry->addTableRow(table);
return;
}
table.reserve(rdb.size());
oRDB.resize(rdb.size(), oDBRunnerEntry(oe));
for (size_t k = 0; kreloadRow(value + 1);
}
catch (const std::exception &) {
// Ignore any problems with the table.
}
}
}
}
void RunnerDB::refreshTables() {
if (runnerTable)
refreshRunnerTableData(*runnerTable);
if (clubTable)
refreshClubTableData(*clubTable);
}
void RunnerDB::releaseTables() {
if (runnerTable)
runnerTable->releaseOwnership();
runnerTable = 0;
if (clubTable)
clubTable->releaseOwnership();
clubTable = 0;
}
Table *RunnerDB::getRunnerTB()//Table mode
{
if (runnerTable == 0) {
Table *table=new Table(oe, 20, "Löpardatabasen", "runnerdb");
table->addColumn("Index", 70, true, true);
table->addColumn("Id", 70, true, true);
table->addColumn("Namn", 200, false);
table->addColumn("Klubb", 200, false);
table->addColumn("SI", 70, true, true);
table->addColumn("Nationalitet", 70, false, true);
table->addColumn("Kön", 50, false, true);
table->addColumn("Födelseår", 70, true, true);
table->addColumn("Anmäl", 70, false, true);
table->setTableProp(Table::CAN_INSERT|Table::CAN_DELETE|Table::CAN_PASTE);
table->setClearOnHide(false);
table->addOwnership();
runnerTable = table;
}
int nr = 0;
for (size_t k = 0; k < rdb.size(); k++) {
if (!rdb[k].isRemoved())
nr++;
}
if (runnerTable->getNumDataRows() != nr)
runnerTable->update();
return runnerTable;
}
void RunnerDB::generateClubTableData(Table &table, oClub *addEntry)
{
if (addEntry) {
addEntry->addTableRow(table);
return;
}
table.reserve(cdb.size());
for (size_t k = 0; ksetObject(cdb[k]);
}
}
}
void RunnerDB::refreshRunnerTableData(Table &table) {
for (size_t k = 0; ksetObject(oRDB[k]);
}
}
}
Table *RunnerDB::getClubTB()//Table mode
{
bool canEdit = !oe->isClient();
if (clubTable == 0) {
Table *table = new Table(oe, 20, "Klubbdatabasen", "clubdb");
table->addColumn("Id", 70, true, true);
table->addColumn("Ändrad", 70, false);
table->addColumn("Namn", 200, false);
oClub::buildTableCol(oe, table);
if (canEdit)
table->setTableProp(Table::CAN_DELETE|Table::CAN_INSERT|Table::CAN_PASTE);
else
table->setTableProp(0);
table->setClearOnHide(false);
table->addOwnership();
clubTable = table;
}
int nr = 0;
for (size_t k = 0; k < cdb.size(); k++) {
if (!cdb[k].isRemoved())
nr++;
}
if (clubTable->getNumDataRows() != nr)
clubTable->update();
return clubTable;
}
void oDBRunnerEntry::addTableRow(Table &table) const {
bool canEdit = !oe->isClient();
oDBRunnerEntry &it = *(oDBRunnerEntry *)(this);
table.addRow(index+1, &it);
if (!db)
throw meosException("Not initialized");
RunnerDBEntry &r = db->rdb[index];
int row = 0;
table.set(row++, it, TID_INDEX, itos(index+1), false, cellEdit);
char bf[16];
oBase::converExtIdentifierString(r.extId, bf);
table.set(row++, it, TID_ID, bf, false, cellEdit);
table.set(row++, it, TID_NAME, r.name, canEdit, cellEdit);
const pClub pc = db->getClub(r.clubNo);
if (pc)
table.set(row++, it, TID_CLUB, pc->getName(), canEdit, cellSelection);
else
table.set(row++, it, TID_CLUB, "", canEdit, cellSelection);
table.set(row++, it, TID_CARD, r.cardNo > 0 ? itos(r.cardNo) : "", canEdit, cellEdit);
char nat[4] = {r.national[0],r.national[1],r.national[2], 0};
table.set(row++, it, TID_NATIONAL, nat, canEdit, cellEdit);
char sex[2] = {r.sex, 0};
table.set(row++, it, TID_SEX, sex, canEdit, cellEdit);
table.set(row++, it, TID_YEAR, itos(r.birthYear), canEdit, cellEdit);
oClass *val = 0;
bool found = false;
if (r.extId != 0)
found = db->runnerInEvent.lookup(r.extId, val);
if (canEdit)
table.setTableProp(Table::CAN_DELETE|Table::CAN_INSERT|Table::CAN_PASTE);
else
table.setTableProp(0);
if (!found)
table.set(row++, it, TID_ENTER, "@+", false, cellAction);
else
table.set(row++, it, TID_ENTER, val ? val->getName() : "", false, cellEdit);
}
const RunnerDBEntry &oDBRunnerEntry::getRunner() const {
if (!db)
throw meosException("Not initialized");
return db->rdb[index];
}
bool oDBRunnerEntry::inputData(int id, const string &input,
int inputId, string &output, bool noUpdate)
{
if (!db)
throw meosException("Not initialized");
RunnerDBEntry &r = db->rdb[index];
switch(id) {
case TID_NAME:
r.setName(input.c_str());
r.getName(output);
db->nhash.clear();
return true;
case TID_CARD:
db->rhash.remove(r.cardNo);
r.cardNo = atoi(input.c_str());
db->rhash.insert(r.cardNo, index);
if (r.cardNo)
output = itos(r.cardNo);
else
output = "";
return true;
case TID_NATIONAL:
if (input.empty()) {
r.national[0] = 0;
r.national[1] = 0;
r.national[2] = 0;
}
else if (input.size() >= 2)
memcpy(r.national, input.c_str(), 3);
output = r.getNationality();
break;
case TID_SEX:
r.sex = input[0];
output = r.getSex();
break;
case TID_YEAR:
r.birthYear = short(atoi(input.c_str()));
output = itos(r.getBirthYear());
break;
case TID_CLUB:
r.clubNo = inputId;
output = input;
break;
}
return false;
}
void oDBRunnerEntry::fillInput(int id, vector< pair > &out, size_t &selected)
{
RunnerDBEntry &r = db->rdb[index];
if (id==TID_CLUB) {
db->fillClubs(out);
out.push_back(make_pair("-", 0));
selected = r.clubNo;
}
}
void oDBRunnerEntry::remove() {
RunnerDBEntry &r = db->rdb[index];
r.remove();
db->idhash.remove(r.extId);
string cname(canonizeName(r.name));
multimap::const_iterator it = db->nhash.find(cname);
while (it != db->nhash.end() && cname == it->first) {
if (it->second == index) {
db->nhash.erase(it);
break;
}
++it;
}
if (r.cardNo > 0) {
int ix = -1;
if (db->rhash.lookup(r.cardNo, ix) && ix == index) {
db->rhash.remove(r.cardNo);
}
}
}
bool oDBRunnerEntry::canRemove() const {
return true;
}
oDBRunnerEntry *RunnerDB::addRunner() {
rdb.push_back(RunnerDBEntry());
oRDB.push_back(oDBRunnerEntry(oe));
oRDB.back().init(this, rdb.size() - 1);
return &oRDB.back();
}
oClub *RunnerDB::addClub() {
freeCIx = max(freeCIx + 1, cdb.size());
while (chash.count(freeCIx))
freeCIx++;
cdb.push_back(oDBClubEntry(oe, freeCIx, cdb.size(), this));
chash.insert(freeCIx, cdb.size()-1);
cnhash.clear();
return &cdb.back();
}
oDataContainer &oDBRunnerEntry::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const {
throw meosException("Not implemented");
}
oDBClubEntry::oDBClubEntry(oEvent *oe, int id, int ix, RunnerDB *dbin) : oClub(oe, id) {
index = ix;
db = dbin;
}
oDBClubEntry::oDBClubEntry(const oClub &c, int ix, RunnerDB *dbin) : oClub(c) {
index = ix;
db = dbin;
}
oDBClubEntry::~oDBClubEntry() {
}
void oDBClubEntry::remove() {
Removed = true;
db->chash.remove(getId());
vector split;
db->canonizeSplitName(getName(), split);
for (size_t j = 0; j::const_iterator it = db->cnhash.find(split[j]);
while (it != db->cnhash.end() && split[j] == it->first) {
if (it->second == index) {
db->cnhash.erase(it);
break;
}
++it;
}
}
}
bool oDBClubEntry::canRemove() const {
return true;
}
int oDBClubEntry::getTableId() const {
return index + 1;
}