1 /***************************************************************************
2  *   (C) 2006 Ejner Borgbjerg <ejner@users.sourceforge.net>                *
3  *   (C) 2006-2009 Michal Rudolf <mrudolf@kdewebdev.org>                   *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  ***************************************************************************/
10 
11 
12 /* Documentation for managing QDataset version, see
13 http://doc.trolltech.com/4.0/qdatastream.html
14 */
15 #include "playerdatabase.h"
16 
17 #if defined(_MSC_VER) && defined(_DEBUG)
18 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
19 #define new DEBUG_NEW
20 #endif // _MSC_VER
21 
22 static quint32 Magic = (quint32)0xB0D0A0D0; // 'magic' number
23 static quint32 Version = (quint32)100; // file format version
24 static QString Mapfile_suffix = ".cpm";
25 static QString Datafile_suffix = ".cpd";
26 
create(const QString & fname)27 bool PlayerDatabase::create(const QString& fname)
28 {
29     m_dirty = false;
30     m_mapfile.setFileName(fname + Mapfile_suffix);
31     m_datafile.setFileName(fname + Datafile_suffix);
32     if(m_mapfile.exists() || m_datafile.exists())
33     {
34         return false;
35     }
36 
37 // set QDataset format version to use
38     if(Version == (quint32)100)
39     {
40         m_mapds.setVersion(6);
41         m_datads.setVersion(6);
42     }
43     else    //default
44     {
45         m_mapds.setVersion(6);
46         m_datads.setVersion(6);
47     }
48 
49     m_mapfile.open(QIODevice::ReadWrite);
50     m_mapds.setDevice(&m_mapfile);
51     m_mapds << Magic;
52     m_mapds << Version;
53     m_nplayers = 0;
54     m_nplayers_offset = m_mapfile.pos();
55     m_mapds << m_nplayers;
56 
57     m_datafile.open(QIODevice::ReadWrite);
58     m_datads.setDevice(&m_datafile);
59     m_datads << Magic;
60     m_datads << Version;//use current version for new db's
61 
62     m_mapfile.flush();
63     m_datafile.flush();
64 
65     m_dataFileCurrentPosition = m_datafile.pos();
66     m_npending_adds = 0;
67 
68     return true;
69 }
70 
open(const QString & fname)71 bool PlayerDatabase::open(const QString& fname)
72 {
73     m_dirty = false;
74     m_mapfile.setFileName(fname + Mapfile_suffix);
75     if(!m_mapfile.open(QIODevice::ReadWrite))
76     {
77         return false;
78     }
79     m_mapds.setDevice(&m_mapfile);
80     quint32 map_magic;
81     quint32 map_version;
82     m_mapds >> map_magic;
83     m_mapds >> map_version;
84 
85     if(map_magic != Magic)
86     {
87         m_mapds.setDevice(nullptr);
88         m_mapfile.close();
89         return false;
90     }
91 // set QDataset format version to use
92     if(map_version == (quint32)100)
93     {
94         m_mapds.setVersion(6);
95     }
96     else    //unknown version
97     {
98         m_mapds.setDevice(nullptr);
99         m_mapfile.close();
100         return false;
101     }
102 
103     quint32 data_magic;
104     quint32 data_version;
105     m_datafile.setFileName(fname + Datafile_suffix);
106     if(!m_datafile.open(QIODevice::ReadWrite))
107     {
108         m_mapds.setDevice(nullptr);
109         m_mapfile.close();
110         return false;
111     }
112     m_datads.setDevice(&m_datafile);
113     m_datads >> data_magic;
114     m_datads >> data_version;
115 
116     if(data_magic != Magic)
117     {
118         m_mapds.setDevice(nullptr);
119         m_mapfile.close();
120         m_datads.setDevice(nullptr);
121         m_datafile.close();
122         return false;
123     }
124 // set QDataset format version to use
125     if(data_version == (quint32)100)
126     {
127         m_datads.setVersion(6);
128     }
129     else    //unknown version
130     {
131         m_mapds.setDevice(nullptr);
132         m_mapfile.close();
133         m_datads.setDevice(nullptr);
134         m_datafile.close();
135         return false;
136     }
137 
138     if(map_version != data_version)
139     {
140         m_mapds.setDevice(nullptr);
141         m_mapfile.close();
142         m_datads.setDevice(nullptr);
143         m_datafile.close();
144         return false;
145     }
146 
147     m_dataFileCurrentPosition = m_datafile.pos();
148     m_nplayers_offset = m_mapfile.pos();
149     m_mapds >> m_nplayers;
150     if(m_nplayers > 0)
151     {
152         m_mapds >> m_mapping;
153     }
154     m_npending_adds = 0;
155     return true;
156 
157 }
158 
removeDatabase(const QString & fname)159 bool PlayerDatabase::removeDatabase(const QString& fname)
160 {
161     m_mapfile.setFileName(fname + Mapfile_suffix);
162     m_datafile.setFileName(fname + Datafile_suffix);
163     return m_mapfile.remove() && m_datafile.remove();
164 }
165 
close()166 void PlayerDatabase::close()
167 {
168     commit();
169     m_mapds.setDevice(nullptr);
170     m_mapfile.flush();
171     m_mapfile.close();
172     m_datads.setDevice(nullptr);
173     m_datafile.flush();
174     m_datafile.close();
175 }
176 
rollback()177 void PlayerDatabase::rollback()
178 {
179     m_pendingUpdates.clear();
180     m_dirty = false;
181     m_npending_adds = 0;
182 }
183 
commit()184 void PlayerDatabase::commit()
185 {
186 
187     if(m_dirty) //current player was changed
188     {
189         m_pendingUpdates.insert(m_currentPlayerName, m_currentPlayer);
190     }
191 
192     m_mapfile.seek(m_nplayers_offset);
193     m_mapds << m_nplayers;
194 
195     // write non-committed changes
196     m_dataFileCurrentPosition = m_datafile.size();
197     m_datafile.seek(m_dataFileCurrentPosition);
198 
199     QMap<QString, PlayerData>::Iterator it;
200     for(it = m_pendingUpdates.begin(); it != m_pendingUpdates.end(); ++it)
201     {
202         m_mapping.insert(it.key(), m_datafile.pos());
203         m_datads << it.value().dateOfBirth().asString();
204         m_datads << it.value().dateOfDeath().asString();
205         m_datads << it.value().country();
206         m_datads << it.value().title();
207         m_datads << it.value().eloListData();
208         m_datads << (qint32)(it.value().firstEloListIndex());
209         m_datads << (qint32)(it.value().lastEloListIndex());
210         m_datads << (qint32)(it.value().estimatedElo());
211         m_datads << (qint32)(it.value().peakElo());
212         m_datads << it.value().photo();
213         m_datads << it.value().biography();
214     }
215     m_mapds << m_mapping;
216     m_mapfile.flush();
217     m_datafile.flush();
218     m_pendingUpdates.clear();
219     m_dirty = false;
220     m_nplayers += m_npending_adds;
221     m_npending_adds = 0;
222 }
223 
224 
readPlayerData(const QString & playername)225 PlayerData PlayerDatabase::readPlayerData(const QString& playername)
226 {
227     PlayerData pd;
228     QMap<QString, qint32>::Iterator it;
229     it = m_mapping.find(playername);
230     if(it == m_mapping.end())
231     {
232 //not in committed data, look in non-committed updates
233         QMap<QString, PlayerData>::Iterator it2;
234         it2 = m_pendingUpdates.find(playername);
235         if(it2 != m_pendingUpdates.end())
236         {
237             return it2.value();
238         }
239         else    //give up
240         {
241             return pd;
242         }
243     }
244 
245     qint32 pos = it.value();
246     m_datafile.seek(pos);//pointing to the player data
247 
248     QString birthDate;
249     QString deathDate;
250     QString country;
251     QString title;
252 
253     m_datads >> birthDate;
254     m_datads >> deathDate;
255     m_datads >> country;
256     m_datads >> title;
257 
258     if(birthDate.contains('.'))
259     {
260         pd.setDateOfBirth(PartialDate(birthDate));
261     }
262     if(deathDate.contains('.'))
263     {
264         pd.setDateOfDeath(PartialDate(deathDate));
265     }
266 
267     pd.setCountry(country);
268     pd.setTitle(title);
269 
270     QList<qint32> eloList;
271     m_datads >> eloList;
272     pd.eloFromListData(eloList);
273 
274     qint32 firstEloListIndex;
275     qint32 lastEloListIndex;
276     qint32 estimatedElo;
277     qint32 peakElo;
278     QImage photo;
279     QString biography;
280 
281     m_datads >> firstEloListIndex;
282     m_datads >> lastEloListIndex;
283     m_datads >> estimatedElo;
284     m_datads >> peakElo;
285     m_datads >> photo;
286     m_datads >> biography;
287 
288     pd.setFirstEloListIndex((int)firstEloListIndex);
289     pd.setLastEloListIndex((int)lastEloListIndex);
290     pd.setEstimatedElo((int)estimatedElo);
291     pd.setPeakElo((int)peakElo);
292     pd.setPhoto(photo);
293     pd.setBiography(biography);
294 
295     return pd;
296 }
297 
count() const298 unsigned int PlayerDatabase::count() const
299 {
300     return m_nplayers + m_npending_adds;
301 }
302 
add(const QString & playername)303 bool PlayerDatabase::add(const QString& playername)
304 {
305     if(m_mapping.contains(playername) || m_pendingUpdates.contains(playername))
306     {
307         return false;
308     }
309     if(m_dirty) //previous current player was changed
310     {
311         m_pendingUpdates.insert(m_currentPlayerName, m_currentPlayer);
312     }
313     PlayerData pd;
314     m_currentPlayerName = playername;
315     m_currentPlayer = pd;
316     m_dirty = true;
317     ++m_npending_adds;
318     return true;
319 }
320 
current() const321 QString PlayerDatabase::current() const
322 {
323     return m_currentPlayerName;
324 }
setCurrent(const QString & playername)325 void PlayerDatabase::setCurrent(const QString& playername)
326 {
327     if(m_currentPlayerName.compare(playername) == 0)
328     {
329         return;
330     }
331     if(m_dirty) //previous current player was changed
332     {
333         m_pendingUpdates.insert(m_currentPlayerName, m_currentPlayer);
334     }
335     m_currentPlayerName = playername;
336     m_currentPlayer = readPlayerData(playername);
337     m_dirty = false;
338 }
339 
exists(const QString & playername) const340 bool PlayerDatabase::exists(const QString& playername) const
341 {
342     if(m_mapping.contains(playername))
343     {
344         return true;
345     }
346     if(m_pendingUpdates.contains(playername))
347     {
348         return true;
349     }
350     return false;
351 }
352 
dateOfBirth() const353 PartialDate PlayerDatabase::dateOfBirth() const
354 {
355     return m_currentPlayer.dateOfBirth();
356 }
setDateOfBirth(const PartialDate & date)357 void PlayerDatabase::setDateOfBirth(const PartialDate& date)
358 {
359     m_currentPlayer.setDateOfBirth(date);
360     if(!m_dirty)
361     {
362         m_dirty = true;
363     }
364 }
365 
dateOfDeath() const366 PartialDate PlayerDatabase::dateOfDeath() const
367 {
368     return m_currentPlayer.dateOfDeath();
369 }
setDateOfDeath(const PartialDate & date)370 void PlayerDatabase::setDateOfDeath(const PartialDate & date)
371 {
372     m_currentPlayer.setDateOfDeath(date);
373     if(!m_dirty)
374     {
375         m_dirty = true;
376     }
377 }
378 
country() const379 QString PlayerDatabase::country() const
380 {
381     return m_currentPlayer.country();
382 }
setCountry(const QString & country)383 void PlayerDatabase::setCountry(const QString& country)
384 {
385     m_currentPlayer.setCountry(country);
386     if(!m_dirty)
387     {
388         m_dirty = true;
389     }
390 }
391 
title() const392 QString PlayerDatabase::title() const
393 {
394     return m_currentPlayer.title();
395 }
setTitle(const QString & title)396 void PlayerDatabase::setTitle(const QString& title)
397 {
398     m_currentPlayer.setTitle(title);
399     if(!m_dirty)
400     {
401         m_dirty = true;
402     }
403 }
404 
firstEloListIndex()405 int PlayerDatabase::firstEloListIndex()
406 {
407     return m_currentPlayer.firstEloListIndex();
408 }
lastEloListIndex()409 int PlayerDatabase::lastEloListIndex()
410 {
411     return m_currentPlayer.lastEloListIndex();
412 }
413 
414 
elo(const PartialDate & date) const415 int PlayerDatabase::elo(const PartialDate& date) const
416 {
417     return m_currentPlayer.elo(eloList(date));
418 }
elo(const int eloList) const419 int PlayerDatabase::elo(const int eloList) const
420 {
421     return m_currentPlayer.elo(eloList);
422 }
423 
estimatedElo(const PartialDate & date)424 int PlayerDatabase::estimatedElo(const PartialDate& date)
425 {
426     return m_currentPlayer.estimatedElo(eloList(date));
427 }
estimatedEloNoCache(const PartialDate & date) const428 int PlayerDatabase::estimatedEloNoCache(const PartialDate& date) const
429 {
430     return m_currentPlayer.estimatedEloNoCache(eloList(date));
431 }
432 
estimatedElo() const433 int PlayerDatabase::estimatedElo() const
434 {
435     return m_currentPlayer.estimatedElo();
436 }
highestElo() const437 int PlayerDatabase::highestElo() const
438 {
439     return m_currentPlayer.peakElo();
440 }
441 
setElo(const int year,const int listIndex,const int elo)442 void PlayerDatabase::setElo(const int year, const int listIndex, const int elo)
443 {
444     m_currentPlayer.setElo(eloList(year, listIndex), elo);
445     if(!m_dirty)
446     {
447         m_dirty = true;
448     }
449 }
450 
451 
setEstimatedElo(const int elo)452 void PlayerDatabase::setEstimatedElo(const int elo)
453 {
454     m_currentPlayer.setEstimatedElo(elo);
455     if(!m_dirty)
456     {
457         m_dirty = true;
458     }
459 }
460 
hasPhoto() const461 bool PlayerDatabase::hasPhoto() const
462 {
463     return !m_currentPlayer.photo().isNull();
464 }
photo() const465 QImage PlayerDatabase::photo() const
466 {
467     return m_currentPlayer.photo();
468 }
setPhoto(const QImage & img)469 void PlayerDatabase::setPhoto(const QImage& img)
470 {
471     m_currentPlayer.setPhoto(img);
472     if(!m_dirty)
473     {
474         m_dirty = true;
475     }
476 }
477 
hasBiography() const478 bool PlayerDatabase::hasBiography() const
479 {
480     return !m_currentPlayer.biography().isNull();
481 }
biography() const482 QString PlayerDatabase::biography() const
483 {
484     return m_currentPlayer.biography();
485 }
setBiography(const QString & s)486 void PlayerDatabase::setBiography(const QString& s)
487 {
488     m_currentPlayer.setBiography(s);
489     if(!m_dirty)
490     {
491         m_dirty = true;
492     }
493 }
appendToBiography(const QString & s)494 void PlayerDatabase::appendToBiography(const QString& s)
495 {
496     m_currentPlayer.appendToBiography(s);
497     if(!m_dirty)
498     {
499         m_dirty = true;
500     }
501 }
502 
503 
playerNames()504 QStringList PlayerDatabase::playerNames()
505 {
506     QStringList result;
507     QMap<QString, qint32>::Iterator it;
508     for(it = m_mapping.begin(); it != m_mapping.end(); ++it)
509     {
510         result.push_back(it.key());
511     }
512     return result;
513 }
514 
findPlayers(const QString & prefix,const int maxCount,const Qt::CaseSensitivity cs)515 QStringList PlayerDatabase::findPlayers(const QString& prefix, const int maxCount, const Qt::CaseSensitivity cs)
516 {
517     QStringList result;
518     QMap<QString, qint32>::Iterator it;
519     int i = 0;
520     for(it = m_mapping.begin(); it != m_mapping.end(); ++it)
521     {
522         if(it.key().startsWith(prefix, cs))
523         {
524             if(i >= maxCount)
525             {
526                 break;
527             }
528             result.push_back(it.key());
529             ++i;
530         }
531     }
532     return result;
533 }
534 
535 
eloList(const PartialDate date) const536 int PlayerDatabase::eloList(const PartialDate date) const
537 {
538     const int year = date.year();
539     if(year < 1971)
540     {
541         return 0;
542     }
543     if(year < 2001) //2 lists in the year
544     {
545         return ((year - 1971) * 2) + 1 + (date.month() / 7);
546     }
547     return 60 + ((year - 2001) * 4) + 1 + (date.month() / 4); //4 lists in the year
548 }
549 
eloList(const int year,const int index) const550 int PlayerDatabase::eloList(const int year, const int index) const
551 {
552     if(year < 1971)
553     {
554         return 0;
555     }
556     if(year < 2001) //2 lists in the year
557     {
558         return ((year - 1971) * 2) + index;
559     }
560     return 60 + ((year - 2001) * 4) + index; //4 lists in the year
561 }
562 
eloListToDate(const int index)563 PartialDate PlayerDatabase::eloListToDate(const int index)
564 {
565     if(index < 1)
566     {
567         return PDInvalidDate;
568     }
569     int year;
570     int rem;
571     int month;
572     if(index < 61)
573     {
574         year = 1970 + ((index + 1) / 2);
575         rem = index % 2;
576         if(rem == 0)
577         {
578             month = 7;
579         }
580         else
581         {
582             month = 1;
583         }
584     }
585     else
586     {
587         year = 2000 + ((index - 57) / 4);
588         rem = index % 4;
589         if(rem == 0)
590         {
591             month = 10;
592         }
593         else if(rem == 1)
594         {
595             month = 1;
596         }
597         else if(rem == 2)
598         {
599             month = 4;
600         }
601         else
602         {
603             month = 7;
604         }
605     }
606     return PartialDate(year, month, 1);
607 }
608 
609