20 #include "kexthighscore_internal.h"
23 #include <sys/types.h>
30 #include <tdeglobal.h>
31 #include <tdeio/netaccess.h>
32 #include <tdeio/job.h>
33 #include <tdemessagebox.h>
38 #include "kexthighscore.h"
39 #include "kexthighscore_gui.h"
40 #include "tdeemailsettings.h"
43 namespace KExtHighscore
47 const char ItemContainer::ANONYMOUS[] =
"_";
48 const char ItemContainer::ANONYMOUS_LABEL[] = I18N_NOOP(
"anonymous");
50 ItemContainer::ItemContainer()
54 ItemContainer::~ItemContainer()
59 void ItemContainer::setItem(Item *item)
65 TQString ItemContainer::entryName()
const
67 if ( _subGroup.isEmpty() )
return _name;
68 return _name +
"_" + _subGroup;
71 TQVariant ItemContainer::read(uint i)
const
75 TQVariant v = _item->defaultValue();
77 internal->hsConfig().setHighscoreGroup(_group);
78 v =
internal->hsConfig().readPropertyEntry(i+1, entryName(), v);
80 return _item->read(i, v);
83 TQString ItemContainer::pretty(uint i)
const
86 return _item->pretty(i, read(i));
89 void ItemContainer::write(uint i,
const TQVariant &value)
const
91 Q_ASSERT( isStored() );
92 Q_ASSERT( internal->hsConfig().isLocked() );
93 internal->hsConfig().setHighscoreGroup(_group);
94 internal->hsConfig().writeEntry(i+1, entryName(), value);
97 uint ItemContainer::increment(uint i)
const
99 uint v = read(i).toUInt() + 1;
105 ItemArray::ItemArray()
106 : _group(
""), _subGroup(
"")
109 ItemArray::~ItemArray()
111 for (uint i=0; i<size(); i++)
delete at(i);
114 int ItemArray::findIndex(
const TQString &name)
const
116 for (uint i=0; i<size(); i++)
117 if ( at(i)->name()==name )
return i;
121 const ItemContainer *ItemArray::item(
const TQString &name)
const
123 int i = findIndex(name);
124 if ( i==-1 ) kdError(11002) << k_funcinfo <<
"no item named \"" << name
129 ItemContainer *ItemArray::item(
const TQString &name)
131 int i = findIndex(name);
132 if ( i==-1 ) kdError(11002) << k_funcinfo <<
"no item named \"" << name
137 void ItemArray::setItem(
const TQString &name, Item *item)
139 int i = findIndex(name);
140 if ( i==-1 ) kdError(11002) << k_funcinfo <<
"no item named \"" << name
142 bool stored = at(i)->isStored();
143 bool canHaveSubGroup = at(i)->canHaveSubGroup();
144 _setItem(i, name, item, stored, canHaveSubGroup);
147 void ItemArray::addItem(
const TQString &name, Item *item,
148 bool stored,
bool canHaveSubGroup)
150 if ( findIndex(name)!=-1 )
151 kdError(11002) <<
"item already exists \"" << name <<
"\"" << endl;
154 at(i) =
new ItemContainer;
155 _setItem(i, name, item, stored, canHaveSubGroup);
158 void ItemArray::_setItem(uint i,
const TQString &name, Item *item,
159 bool stored,
bool canHaveSubGroup)
161 at(i)->setItem(item);
162 at(i)->setName(name);
163 at(i)->setGroup(stored ? _group : TQString());
164 at(i)->setSubGroup(canHaveSubGroup ? _subGroup : TQString());
167 void ItemArray::setGroup(
const TQString &group)
169 Q_ASSERT( !group.isNull() );
171 for (uint i=0; i<size(); i++)
172 if ( at(i)->isStored() ) at(i)->setGroup(group);
175 void ItemArray::setSubGroup(
const TQString &subGroup)
177 Q_ASSERT( !subGroup.isNull() );
178 _subGroup = subGroup;
179 for (uint i=0; i<size(); i++)
180 if ( at(i)->canHaveSubGroup() ) at(i)->setSubGroup(subGroup);
183 void ItemArray::read(uint k, Score &data)
const
185 for (uint i=0; i<size(); i++) {
186 if ( !at(i)->isStored() )
continue;
187 data.setData(at(i)->name(), at(i)->read(k));
191 void ItemArray::write(uint k,
const Score &data, uint nb)
const
193 for (uint i=0; i<size(); i++) {
194 if ( !at(i)->isStored() )
continue;
195 for (uint j=nb-1; j>k; j--) at(i)->write(j, at(i)->read(j-1));
196 at(i)->write(k, data.data(at(i)->name()));
200 void ItemArray::exportToText(TQTextStream &s)
const
202 for (uint k=0; k<nbEntries()+1; k++) {
203 for (uint i=0; i<size(); i++) {
204 const Item *item = at(i)->item();
205 if ( item->isVisible() ) {
206 if ( i!=0 ) s <<
'\t';
207 if ( k==0 ) s << item->label();
208 else s << at(i)->pretty(k-1);
216 class ScoreNameItem :
public NameItem
219 ScoreNameItem(
const ScoreInfos &score,
const PlayerInfos &infos)
220 : _score(score), _infos(infos) {}
222 TQString pretty(uint i,
const TQVariant &v)
const {
223 uint
id = _score.item(
"id")->read(i).toUInt();
224 if (
id==0 )
return NameItem::pretty(i, v);
225 return _infos.prettyName(
id-1);
229 const ScoreInfos &_score;
230 const PlayerInfos &_infos;
234 ScoreInfos::ScoreInfos(uint maxNbEntries,
const PlayerInfos &infos)
235 : _maxNbEntries(maxNbEntries)
237 addItem(
"id",
new Item((uint)0));
238 addItem(
"rank",
new RankItem,
false);
239 addItem(
"name",
new ScoreNameItem(*
this, infos));
240 addItem(
"score", Manager::createItem(Manager::ScoreDefault));
241 addItem(
"date",
new DateItem);
244 uint ScoreInfos::nbEntries()
const
247 for (; i<_maxNbEntries; i++)
248 if ( item(
"score")->read(i)==item(
"score")->item()->defaultValue() )
254 const char *HS_ID =
"player id";
255 const char *HS_REGISTERED_NAME =
"registered name";
256 const char *HS_KEY =
"player key";
257 const char *HS_WW_ENABLED =
"ww hs enabled";
259 PlayerInfos::PlayerInfos()
264 addItem(
"name",
new NameItem);
265 Item *it =
new Item((uint)0, i18n(
"Games Count"),TQt::AlignRight);
266 addItem(
"nb games", it,
true,
true);
267 it = Manager::createItem(Manager::MeanScoreDefault);
268 addItem(
"mean score", it,
true,
true);
269 it = Manager::createItem(Manager::BestScoreDefault);
270 addItem(
"best score", it,
true,
true);
271 addItem(
"date",
new DateItem,
true,
true);
272 it =
new Item(TQString(), i18n(
"Comment"), TQt::AlignLeft);
273 addItem(
"comment", it);
276 addItem(
"nb black marks",
new Item((uint)0),
true,
true);
277 addItem(
"nb lost games",
new Item((uint)0),
true,
true);
278 addItem(
"nb draw games",
new Item((uint)0),
true,
true);
279 addItem(
"current trend",
new Item((
int)0),
true,
true);
280 addItem(
"max lost trend",
new Item((uint)0),
true,
true);
281 addItem(
"max won trend",
new Item((uint)0),
true,
true);
283 struct passwd *pwd = getpwuid(getuid());
284 TQString username = pwd->pw_name;
285 #ifdef HIGHSCORE_DIRECTORY
286 internal->hsConfig().setHighscoreGroup(
"players");
287 for (uint i=0; ;i++) {
288 if ( !internal->hsConfig().hasEntry(i+1,
"username") ) {
293 if ( internal->hsConfig().readEntry(i+1,
"username")==username ) {
300 internal->hsConfig().lockForWriting();
301 KEMailSettings emailConfig;
302 emailConfig.setProfile(emailConfig.defaultProfileName());
303 TQString name = emailConfig.getSetting(KEMailSettings::RealName);
304 if ( name.isEmpty() || isNameUsed(name) ) name = username;
305 if ( isNameUsed(name) ) name= TQString(ItemContainer::ANONYMOUS);
306 #ifdef HIGHSCORE_DIRECTORY
307 internal->hsConfig().writeEntry(_id+1,
"username", username);
308 item(
"name")->write(_id, name);
312 _oldLocalPlayer = cg.config()->hasKey(HS_ID);
313 _oldLocalId = cg.config()->readUnsignedNumEntry(HS_ID);
314 #ifdef HIGHSCORE_DIRECTORY
315 if (_oldLocalPlayer) {
317 TQString prefix = TQString(
"%1_").arg(_oldLocalId+1);
318 TQMap<TQString, TQString> entries =
319 cg.config()->entryMap(
"KHighscore_players");
320 TQMap<TQString, TQString>::const_iterator it;
321 for (it=entries.begin(); it!=entries.end(); ++it) {
322 TQString key = it.key();
323 if ( key.find(prefix)==0 ) {
324 TQString name = key.right(key.length()-prefix.length());
325 if ( name!=
"name" || !isNameUsed(it.data()) )
326 internal->hsConfig().writeEntry(_id+1, name, it.data());
331 _newPlayer = !_oldLocalPlayer;
332 if (_oldLocalPlayer) _id = _oldLocalId;
335 cg.config()->writeEntry(HS_ID, _id);
336 item(
"name")->write(_id, name);
340 internal->hsConfig().writeAndUnlock();
343 void PlayerInfos::createHistoItems(
const TQMemArray<uint> &scores,
bool bound)
345 Q_ASSERT( _histogram.size()==0 );
348 for (uint i=1; i<histoSize(); i++)
349 addItem(histoName(i),
new Item((uint)0),
true,
true);
352 bool PlayerInfos::isAnonymous()
const
354 return ( name()==ItemContainer::ANONYMOUS );
357 uint PlayerInfos::nbEntries()
const
359 internal->hsConfig().setHighscoreGroup(
"players");
360 TQStringList list =
internal->hsConfig().readList(
"name", -1);
364 TQString PlayerInfos::key()
const
367 return cg.config()->readEntry(HS_KEY, TQString());
370 bool PlayerInfos::isWWEnabled()
const
373 return cg.config()->readBoolEntry(HS_WW_ENABLED,
false);
376 TQString PlayerInfos::histoName(uint i)
const
378 const TQMemArray<uint> &sh = _histogram;
379 Q_ASSERT( i<sh.size() || (_bound || i==sh.size()) );
381 return TQString(
"nb scores greater than %1").arg(sh[sh.size()-1]);
382 return TQString(
"nb scores less than %1").arg(sh[i]);
385 uint PlayerInfos::histoSize()
const
387 return _histogram.size() + (_bound ? 0 : 1);
390 void PlayerInfos::submitScore(
const Score &score)
const
393 uint nbGames = item(
"nb games")->increment(_id);
394 switch (score.type()) {
396 item(
"nb lost games")->increment(_id);
400 item(
"nb draw games")->increment(_id);
405 if ( score.type()==Won ) {
406 uint nbWonGames = nbGames - item(
"nb lost games")->read(_id).toUInt()
407 - item(
"nb draw games")->read(_id).toUInt()
408 - item(
"nb black marks")->read(_id).toUInt();
409 double mean = (nbWonGames==1 ? 0.0
410 : item(
"mean score")->read(_id).toDouble());
411 mean += (double(score.score()) - mean) / nbWonGames;
412 item(
"mean score")->write(_id, mean);
417 best.setScore( item(
"best score")->read(_id).toUInt() );
419 item(
"best score")->write(_id, score.score());
420 item(
"date")->write(_id, score.data(
"date").toDateTime());
424 int current = item(
"current trend")->read(_id).toInt();
425 switch (score.type()) {
427 if ( current<0 ) current = 0;
429 uint won = item(
"max won trend")->read(_id).toUInt();
430 if ( (uint)current>won ) item(
"max won trend")->write(_id, current);
434 if ( current>0 ) current = 0;
436 uint lost = item(
"max lost trend")->read(_id).toUInt();
437 uint clost = -current;
438 if ( clost>lost ) item(
"max lost trend")->write(_id, clost);
445 item(
"current trend")->write(_id, current);
448 if ( score.type()==Won ) {
449 const TQMemArray<uint> &sh = _histogram;
450 for (uint i=1; i<histoSize(); i++)
451 if ( i==sh.size() || score.score()<sh[i] ) {
452 item(histoName(i))->increment(_id);
458 bool PlayerInfos::isNameUsed(
const TQString &newName)
const
460 if ( newName==name() )
return false;
461 for (uint i=0; i<nbEntries(); i++)
462 if ( newName.lower()==item(
"name")->read(i).toString().lower() )
return true;
463 if ( newName==i18n(ItemContainer::ANONYMOUS_LABEL) )
return true;
467 void PlayerInfos::modifyName(
const TQString &newName)
const
469 item(
"name")->write(_id, newName);
472 void PlayerInfos::modifySettings(
const TQString &newName,
473 const TQString &comment,
bool WWEnabled,
474 const TQString &newKey)
const
477 item(
"comment")->write(_id, comment);
479 cg.config()->writeEntry(HS_WW_ENABLED, WWEnabled);
480 if ( !newKey.isEmpty() ) cg.config()->writeEntry(HS_KEY, newKey);
481 if (WWEnabled) cg.config()->writeEntry(HS_REGISTERED_NAME, newName);
484 TQString PlayerInfos::registeredName()
const
487 return cg.config()->readEntry(HS_REGISTERED_NAME, TQString());
490 void PlayerInfos::removeKey()
496 TQString str =
"%1 old #%2";
500 sk = str.arg(HS_KEY).arg(i);
501 }
while ( !cg.config()->readEntry(sk, TQString()).isEmpty() );
502 cg.config()->writeEntry(sk, key());
503 cg.config()->writeEntry(str.arg(HS_REGISTERED_NAME).arg(i),
507 cg.config()->deleteEntry(HS_KEY);
508 cg.config()->deleteEntry(HS_REGISTERED_NAME);
509 cg.config()->writeEntry(HS_WW_ENABLED,
false);
513 ManagerPrivate::ManagerPrivate(uint nbGameTypes, Manager &m)
514 : manager(m), showStatistics(false), showDrawGames(false),
515 trackLostGames(false), trackDrawGames(false),
516 showMode(Manager::ShowForHigherScore),
517 _first(true), _nbGameTypes(nbGameTypes), _gameType(0)
520 void ManagerPrivate::init(uint maxNbEntries)
523 _playerInfos =
new PlayerInfos;
524 _scoreInfos =
new ScoreInfos(maxNbEntries, *_playerInfos);
527 ManagerPrivate::~ManagerPrivate()
534 KURL ManagerPrivate::queryURL(QueryType type,
const TQString &newName)
const
536 KURL url = serverURL;
537 TQString nameItem =
"nickname";
538 TQString name = _playerInfos->registeredName();
539 bool withVersion =
true;
545 url.addPath(
"submit.php");
550 url.addPath(
"register.php");
554 url.addPath(
"change.php");
557 Manager::addToQueryURL(url,
"new_nickname", newName);
560 url.addPath(
"players.php");
561 nameItem =
"highlight";
565 url.addPath(
"highscores.php");
567 if ( _nbGameTypes>1 ) level =
true;
571 if (withVersion) Manager::addToQueryURL(url,
"version", version);
572 if ( !name.isEmpty() ) Manager::addToQueryURL(url, nameItem, name);
573 if (key) Manager::addToQueryURL(url,
"key", _playerInfos->key());
575 TQString label = manager.gameTypeLabel(_gameType, Manager::WW);
576 if ( !label.isEmpty() ) Manager::addToQueryURL(url,
"level", label);
583 const char *DUMMY_STRINGS[] = {
584 I18N_NOOP(
"Undefined error."),
585 I18N_NOOP(
"Missing argument(s)."),
586 I18N_NOOP(
"Invalid argument(s)."),
588 I18N_NOOP(
"Unable to connect to MySQL server."),
589 I18N_NOOP(
"Unable to select database."),
590 I18N_NOOP(
"Error on database query."),
591 I18N_NOOP(
"Error on database insert."),
593 I18N_NOOP(
"Nickname already registered."),
594 I18N_NOOP(
"Nickname not registered."),
595 I18N_NOOP(
"Invalid key."),
596 I18N_NOOP(
"Invalid submit key."),
598 I18N_NOOP(
"Invalid level."),
599 I18N_NOOP(
"Invalid score.")
602 const char *UNABLE_TO_CONTACT =
603 I18N_NOOP(
"Unable to contact world-wide highscore server");
605 bool ManagerPrivate::doQuery(
const KURL &url, TQWidget *parent,
606 TQDomNamedNodeMap *map)
608 TDEIO::http_update_cache(url,
true, 0);
611 if ( !TDEIO::NetAccess::download(url, tmpFile, parent) ) {
612 TQString details = i18n(
"Server URL: %1").arg(url.host());
613 KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
617 TQFile file(tmpFile);
618 if ( !file.open(IO_ReadOnly) ) {
619 TDEIO::NetAccess::removeTempFile(tmpFile);
620 TQString details = i18n(
"Unable to open temporary file.");
621 KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
625 TQTextStream t(&file);
626 TQString content = t.read().stripWhiteSpace();
628 TDEIO::NetAccess::removeTempFile(tmpFile);
631 if ( doc.setContent(content) ) {
632 TQDomElement root = doc.documentElement();
633 TQDomElement element = root.firstChild().toElement();
634 if ( element.tagName()==
"success" ) {
635 if (map) *map = element.attributes();
638 if ( element.tagName()==
"error" ) {
639 TQDomAttr attr = element.attributes().namedItem(
"label").toAttr();
640 if ( !attr.isNull() ) {
641 TQString msg = i18n(attr.value().latin1());
642 TQString caption = i18n(
"Message from world-wide highscores "
644 KMessageBox::sorry(parent, msg, caption);
649 TQString msg = i18n(
"Invalid answer from world-wide highscores server.");
650 TQString details = i18n(
"Raw message: %1").arg(content);
651 KMessageBox::detailedSorry(parent, msg, details);
655 bool ManagerPrivate::getFromQuery(
const TQDomNamedNodeMap &map,
656 const TQString &name, TQString &value,
659 TQDomAttr attr = map.namedItem(name).toAttr();
660 if ( attr.isNull() ) {
661 KMessageBox::sorry(parent,
662 i18n(
"Invalid answer from world-wide "
663 "highscores server (missing item: %1).").arg(name));
666 value = attr.value();
670 Score ManagerPrivate::readScore(uint i)
const
673 _scoreInfos->read(i, score);
677 int ManagerPrivate::rank(
const Score &score)
const
679 uint nb = _scoreInfos->nbEntries();
682 if ( readScore(i)<score )
break;
683 return (i<_scoreInfos->maxNbEntries() ? (
int)i : -1);
686 bool ManagerPrivate::modifySettings(
const TQString &newName,
687 const TQString &comment,
bool WWEnabled,
691 bool newPlayer =
false;
694 newPlayer = _playerInfos->key().isEmpty()
695 || _playerInfos->registeredName().isEmpty();
696 KURL url = queryURL((newPlayer ? Register : Change), newName);
697 Manager::addToQueryURL(url,
"comment", comment);
699 TQDomNamedNodeMap map;
700 bool ok = doQuery(url, widget, &map);
701 if ( !ok || (newPlayer && !getFromQuery(map,
"key", newKey, widget)) )
705 bool ok = _hsConfig->lockForWriting(widget);
710 ok = ( !_playerInfos->isNameUsed(newName) );
712 _playerInfos->modifySettings(newName, comment, WWEnabled, newKey);
713 _hsConfig->writeAndUnlock();
718 void ManagerPrivate::convertToGlobal()
723 TQValueVector<Score> scores(_scoreInfos->nbEntries());
724 for (uint i=0; i<scores.count(); i++)
725 scores[i] = readScore(i);
731 for (uint i=0; i<scores.count(); i++)
732 if ( scores[i].data(
"id").toUInt()==_playerInfos->oldLocalId()+1 )
733 submitLocal(scores[i]);
734 _hsConfig->writeAndUnlock();
737 void ManagerPrivate::setGameType(uint type)
741 if ( _playerInfos->isNewPlayer() ) {
743 for (uint i=0; i<_nbGameTypes; i++) {
745 manager.convertLegacy(i);
748 #ifdef HIGHSCORE_DIRECTORY
749 if ( _playerInfos->isOldLocalPlayer() ) {
751 for (uint i=0; i<_nbGameTypes; i++) {
760 Q_ASSERT( type<_nbGameTypes );
761 _gameType = kMin(type, _nbGameTypes-1);
762 TQString str =
"scores";
763 TQString lab = manager.gameTypeLabel(_gameType, Manager::Standard);
764 if ( !lab.isEmpty() ) {
765 _playerInfos->setSubGroup(lab);
768 _scoreInfos->setGroup(str);
771 void ManagerPrivate::checkFirst()
773 if (_first) setGameType(0);
776 int ManagerPrivate::submitScore(
const Score &ascore,
777 TQWidget *widget,
bool askIfAnonymous)
781 Score score = ascore;
782 score.setData(
"id", _playerInfos->id() + 1);
783 score.setData(
"date", TQDateTime::currentDateTime());
786 const char *dontAskAgainName =
"highscore_ask_name_dialog";
788 KMessageBox::ButtonCode dummy;
789 if ( score.type()==Won && askIfAnonymous && _playerInfos->isAnonymous()
790 && KMessageBox::shouldBeShownYesNo(dontAskAgainName, dummy) ) {
791 AskNameDialog d(widget);
792 if ( d.exec()==TQDialog::Accepted ) newName = d.name();
793 if ( d.dontAskAgain() )
794 KMessageBox::saveDontShowAgainYesNo(dontAskAgainName,
799 if ( _hsConfig->lockForWriting(widget) ) {
801 if ( !newName.isEmpty() && !_playerInfos->isNameUsed(newName) )
802 _playerInfos->modifyName(newName);
805 _playerInfos->submitScore(score);
806 if ( score.type()==Won ) rank = submitLocal(score);
807 _hsConfig->writeAndUnlock();
810 if ( _playerInfos->isWWEnabled() )
811 submitWorldWide(score, widget);
816 int ManagerPrivate::submitLocal(
const Score &score)
820 uint nb = _scoreInfos->nbEntries();
821 if ( nb<_scoreInfos->maxNbEntries() ) nb++;
822 _scoreInfos->write(r, score, nb);
827 bool ManagerPrivate::submitWorldWide(
const Score &score,
828 TQWidget *widget)
const
830 if ( score.type()==Lost && !trackLostGames )
return true;
831 if ( score.type()==Draw && !trackDrawGames )
return true;
833 KURL url = queryURL(Submit);
834 manager.additionalQueryItems(url, score);
835 int s = (score.type()==Won ? score.score() : (int)score.type());
836 TQString str = TQString::number(s);
837 Manager::addToQueryURL(url,
"score", str);
838 KMD5 context(TQString(_playerInfos->registeredName() + str).latin1());
839 Manager::addToQueryURL(url,
"check", context.hexDigest());
841 return doQuery(url, widget);
844 void ManagerPrivate::exportHighscores(TQTextStream &s)
846 uint tmp = _gameType;
848 for (uint i=0; i<_nbGameTypes; i++) {
850 if ( _nbGameTypes>1 ) {
851 if ( i!=0 ) s << endl;
852 s <<
"--------------------------------" << endl;
854 << manager.gameTypeLabel(_gameType, Manager::I18N)
858 s <<
"Players list:" << endl;
859 _playerInfos->exportToText(s);
861 s <<
"Highscores list:" << endl;
862 _scoreInfos->exportToText(s);
Class for managing highscore tables.
bool lockForWriting(TQWidget *widget=0)