kexthighscore_internal.cpp

00001 /*
00002     This file is part of the KDE games library
00003     Copyright (C) 2001-2004 Nicolas Hadacek (hadacek@kde.org)
00004 
00005     This library is free software; you can redistribute it and/or
00006     modify it under the terms of the GNU Library General Public
00007     License version 2 as published by the Free Software Foundation.
00008 
00009     This library is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     Library General Public License for more details.
00013 
00014     You should have received a copy of the GNU Library General Public License
00015     along with this library; see the file COPYING.LIB.  If not, write to
00016     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017     Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "kexthighscore_internal.h"
00021 
00022 #include <pwd.h>
00023 #include <sys/types.h>
00024 #include <unistd.h>
00025 
00026 #include <qfile.h>
00027 #include <qlayout.h>
00028 #include <qdom.h>
00029 
00030 #include <kglobal.h>
00031 #include <kio/netaccess.h>
00032 #include <kio/job.h>
00033 #include <kmessagebox.h>
00034 #include <kmdcodec.h>
00035 #include <kdebug.h>
00036 
00037 #include "config.h"
00038 #include "kexthighscore.h"
00039 #include "kexthighscore_gui.h"
00040 #include "kemailsettings.h"
00041 
00042 
00043 namespace KExtHighscore
00044 {
00045 
00046 //-----------------------------------------------------------------------------
00047 const char ItemContainer::ANONYMOUS[] = "_";
00048 const char ItemContainer::ANONYMOUS_LABEL[] = I18N_NOOP("anonymous");
00049 
00050 ItemContainer::ItemContainer()
00051     : _item(0)
00052 {}
00053 
00054 ItemContainer::~ItemContainer()
00055 {
00056     delete _item;
00057 }
00058 
00059 void ItemContainer::setItem(Item *item)
00060 {
00061     delete _item;
00062     _item = item;
00063 }
00064 
00065 QString ItemContainer::entryName() const
00066 {
00067     if ( _subGroup.isEmpty() ) return _name;
00068     return _name + "_" + _subGroup;
00069 }
00070 
00071 QVariant ItemContainer::read(uint i) const
00072 {
00073     Q_ASSERT(_item);
00074 
00075     QVariant v = _item->defaultValue();
00076     if ( isStored() ) {
00077         internal->hsConfig().setHighscoreGroup(_group);
00078         v = internal->hsConfig().readPropertyEntry(i+1, entryName(), v);
00079     }
00080     return _item->read(i, v);
00081 }
00082 
00083 QString ItemContainer::pretty(uint i) const
00084 {
00085     Q_ASSERT(_item);
00086     return _item->pretty(i, read(i));
00087 }
00088 
00089 void ItemContainer::write(uint i, const QVariant &value) const
00090 {
00091     Q_ASSERT( isStored() );
00092     Q_ASSERT( internal->hsConfig().isLocked() );
00093     internal->hsConfig().setHighscoreGroup(_group);
00094     internal->hsConfig().writeEntry(i+1, entryName(), value);
00095 }
00096 
00097 uint ItemContainer::increment(uint i) const
00098 {
00099     uint v = read(i).toUInt() + 1;
00100     write(i, v);
00101     return v;
00102 }
00103 
00104 //-----------------------------------------------------------------------------
00105 ItemArray::ItemArray()
00106     : _group(""), _subGroup("") // no null groups
00107 {}
00108 
00109 ItemArray::~ItemArray()
00110 {
00111     for (uint i=0; i<size(); i++) delete at(i);
00112 }
00113 
00114 int ItemArray::findIndex(const QString &name) const
00115 {
00116     for (uint i=0; i<size(); i++)
00117         if ( at(i)->name()==name ) return i;
00118     return -1;
00119 }
00120 
00121 const ItemContainer *ItemArray::item(const QString &name) const
00122 {
00123     int i = findIndex(name);
00124     if ( i==-1 ) kdError(11002) << k_funcinfo << "no item named \"" << name
00125                                 << "\"" << endl;
00126     return at(i);
00127 }
00128 
00129 ItemContainer *ItemArray::item(const QString &name)
00130 {
00131     int i = findIndex(name);
00132     if ( i==-1 ) kdError(11002) << k_funcinfo << "no item named \"" << name
00133                                 << "\"" << endl;
00134     return at(i);
00135 }
00136 
00137 void ItemArray::setItem(const QString &name, Item *item)
00138 {
00139     int i = findIndex(name);
00140     if ( i==-1 ) kdError(11002) << k_funcinfo << "no item named \"" << name
00141                                 << "\"" << endl;
00142     bool stored = at(i)->isStored();
00143     bool canHaveSubGroup = at(i)->canHaveSubGroup();
00144     _setItem(i, name, item, stored, canHaveSubGroup);
00145 }
00146 
00147 void ItemArray::addItem(const QString &name, Item *item,
00148                         bool stored, bool canHaveSubGroup)
00149 {
00150     if ( findIndex(name)!=-1 )
00151         kdError(11002) << "item already exists \"" << name << "\"" << endl;
00152     uint i = size();
00153     resize(i+1);
00154     at(i) = new ItemContainer;
00155     _setItem(i, name, item, stored, canHaveSubGroup);
00156 }
00157 
00158 void ItemArray::_setItem(uint i, const QString &name, Item *item,
00159                          bool stored, bool canHaveSubGroup)
00160 {
00161     at(i)->setItem(item);
00162     at(i)->setName(name);
00163     at(i)->setGroup(stored ? _group : QString::null);
00164     at(i)->setSubGroup(canHaveSubGroup ? _subGroup : QString::null);
00165 }
00166 
00167 void ItemArray::setGroup(const QString &group)
00168 {
00169     Q_ASSERT( !group.isNull() );
00170     _group = group;
00171     for (uint i=0; i<size(); i++)
00172         if ( at(i)->isStored() ) at(i)->setGroup(group);
00173 }
00174 
00175 void ItemArray::setSubGroup(const QString &subGroup)
00176 {
00177     Q_ASSERT( !subGroup.isNull() );
00178     _subGroup = subGroup;
00179     for (uint i=0; i<size(); i++)
00180         if ( at(i)->canHaveSubGroup() ) at(i)->setSubGroup(subGroup);
00181 }
00182 
00183 void ItemArray::read(uint k, Score &data) const
00184 {
00185     for (uint i=0; i<size(); i++) {
00186         if ( !at(i)->isStored() ) continue;
00187         data.setData(at(i)->name(), at(i)->read(k));
00188     }
00189 }
00190 
00191 void ItemArray::write(uint k, const Score &data, uint nb) const
00192 {
00193     for (uint i=0; i<size(); i++) {
00194         if ( !at(i)->isStored() ) continue;
00195         for (uint j=nb-1; j>k; j--)  at(i)->write(j, at(i)->read(j-1));
00196         at(i)->write(k, data.data(at(i)->name()));
00197     }
00198 }
00199 
00200 void ItemArray::exportToText(QTextStream &s) const
00201 {
00202     for (uint k=0; k<nbEntries()+1; k++) {
00203         for (uint i=0; i<size(); i++) {
00204             const Item *item = at(i)->item();
00205             if ( item->isVisible() ) {
00206                 if ( i!=0 ) s << '\t';
00207                 if ( k==0 ) s << item->label();
00208                 else s << at(i)->pretty(k-1);
00209             }
00210         }
00211         s << endl;
00212     }
00213 }
00214 
00215 //-----------------------------------------------------------------------------
00216 class ScoreNameItem : public NameItem
00217 {
00218  public:
00219     ScoreNameItem(const ScoreInfos &score, const PlayerInfos &infos)
00220         : _score(score), _infos(infos) {}
00221 
00222     QString pretty(uint i, const QVariant &v) const {
00223         uint id = _score.item("id")->read(i).toUInt();
00224         if ( id==0 ) return NameItem::pretty(i, v);
00225         return _infos.prettyName(id-1);
00226     }
00227 
00228  private:
00229     const ScoreInfos  &_score;
00230     const PlayerInfos &_infos;
00231 };
00232 
00233 //-----------------------------------------------------------------------------
00234 ScoreInfos::ScoreInfos(uint maxNbEntries, const PlayerInfos &infos)
00235     : _maxNbEntries(maxNbEntries)
00236 {
00237     addItem("id", new Item((uint)0));
00238     addItem("rank", new RankItem, false);
00239     addItem("name", new ScoreNameItem(*this, infos));
00240     addItem("score", Manager::createItem(Manager::ScoreDefault));
00241     addItem("date", new DateItem);
00242 }
00243 
00244 uint ScoreInfos::nbEntries() const
00245 {
00246     uint i = 0;
00247     for (; i<_maxNbEntries; i++)
00248         if ( item("score")->read(i)==item("score")->item()->defaultValue() )
00249             break;
00250     return i;
00251 }
00252 
00253 //-----------------------------------------------------------------------------
00254 const char *HS_ID              = "player id";
00255 const char *HS_REGISTERED_NAME = "registered name";
00256 const char *HS_KEY             = "player key";
00257 const char *HS_WW_ENABLED      = "ww hs enabled";
00258 
00259 PlayerInfos::PlayerInfos()
00260 {
00261     setGroup("players");
00262 
00263     // standard items
00264     addItem("name", new NameItem);
00265     Item *it = new Item((uint)0, i18n("Games Count"),Qt::AlignRight);
00266     addItem("nb games", it, true, true);
00267     it = Manager::createItem(Manager::MeanScoreDefault);
00268     addItem("mean score", it, true, true);
00269     it = Manager::createItem(Manager::BestScoreDefault);
00270     addItem("best score", it, true, true);
00271     addItem("date", new DateItem, true, true);
00272     it = new Item(QString::null, i18n("Comment"), Qt::AlignLeft);
00273     addItem("comment", it);
00274 
00275     // statistics items
00276     addItem("nb black marks", new Item((uint)0), true, true); // legacy
00277     addItem("nb lost games", new Item((uint)0), true, true);
00278     addItem("nb draw games", new Item((uint)0), true, true);
00279     addItem("current trend", new Item((int)0), true, true);
00280     addItem("max lost trend", new Item((uint)0), true, true);
00281     addItem("max won trend", new Item((uint)0), true, true);
00282 
00283     struct passwd *pwd = getpwuid(getuid());
00284     QString username = pwd->pw_name;
00285 #ifdef HIGHSCORE_DIRECTORY
00286     internal->hsConfig().setHighscoreGroup("players");
00287     for (uint i=0; ;i++) {
00288         if ( !internal->hsConfig().hasEntry(i+1, "username") ) {
00289             _newPlayer = true;
00290             _id = i;
00291             break;
00292         }
00293         if ( internal->hsConfig().readEntry(i+1, "username")==username ) {
00294             _newPlayer = false;
00295             _id = i;
00296             return;
00297         }
00298     }
00299 #endif
00300     internal->hsConfig().lockForWriting();
00301     KEMailSettings emailConfig;
00302     emailConfig.setProfile(emailConfig.defaultProfileName());
00303     QString name = emailConfig.getSetting(KEMailSettings::RealName);
00304     if ( name.isEmpty() || isNameUsed(name) ) name = username;
00305     if ( isNameUsed(name) ) name= QString(ItemContainer::ANONYMOUS);
00306 #ifdef HIGHSCORE_DIRECTORY
00307     internal->hsConfig().writeEntry(_id+1, "username", username);
00308     item("name")->write(_id, name);
00309 #endif
00310 
00311     ConfigGroup cg;
00312     _oldLocalPlayer = cg.config()->hasKey(HS_ID);
00313     _oldLocalId = cg.config()->readUnsignedNumEntry(HS_ID);
00314 #ifdef HIGHSCORE_DIRECTORY
00315     if (_oldLocalPlayer) { // player already exists in local config file
00316         // copy player data
00317         QString prefix = QString("%1_").arg(_oldLocalId+1);
00318         QMap<QString, QString> entries =
00319             cg.config()->entryMap("KHighscore_players");
00320         QMap<QString, QString>::const_iterator it;
00321         for (it=entries.begin(); it!=entries.end(); ++it) {
00322             QString key = it.key();
00323             if ( key.find(prefix)==0 ) {
00324                 QString name = key.right(key.length()-prefix.length());
00325                 if ( name!="name" || !isNameUsed(it.data()) )
00326                     internal->hsConfig().writeEntry(_id+1, name, it.data());
00327             }
00328         }
00329     }
00330 #else
00331     _newPlayer = !_oldLocalPlayer;
00332     if (_oldLocalPlayer) _id = _oldLocalId;
00333     else {
00334         _id = nbEntries();
00335         cg.config()->writeEntry(HS_ID, _id);
00336         item("name")->write(_id, name);
00337     }
00338 #endif
00339     internal->hsConfig().writeAndUnlock();
00340 }
00341 
00342 void PlayerInfos::createHistoItems(const QMemArray<uint> &scores, bool bound)
00343 {
00344     Q_ASSERT( _histogram.size()==0 );
00345     _bound = bound;
00346     _histogram = scores;
00347     for (uint i=1; i<histoSize(); i++)
00348         addItem(histoName(i), new Item((uint)0), true, true);
00349 }
00350 
00351 bool PlayerInfos::isAnonymous() const
00352 {
00353     return ( name()==ItemContainer::ANONYMOUS );
00354 }
00355 
00356 uint PlayerInfos::nbEntries() const
00357 {
00358     internal->hsConfig().setHighscoreGroup("players");
00359     QStringList list = internal->hsConfig().readList("name", -1);
00360     return list.count();
00361 }
00362 
00363 QString PlayerInfos::key() const
00364 {
00365     ConfigGroup cg;
00366     return cg.config()->readEntry(HS_KEY, QString::null);
00367 }
00368 
00369 bool PlayerInfos::isWWEnabled() const
00370 {
00371     ConfigGroup cg;
00372     return cg.config()->readBoolEntry(HS_WW_ENABLED, false);
00373 }
00374 
00375 QString PlayerInfos::histoName(uint i) const
00376 {
00377     const QMemArray<uint> &sh = _histogram;
00378     Q_ASSERT( i<sh.size() || (_bound || i==sh.size()) );
00379     if ( i==sh.size() )
00380         return QString("nb scores greater than %1").arg(sh[sh.size()-1]);
00381     return QString("nb scores less than %1").arg(sh[i]);
00382 }
00383 
00384 uint PlayerInfos::histoSize() const
00385 {
00386      return _histogram.size() + (_bound ? 0 : 1);
00387 }
00388 
00389 void PlayerInfos::submitScore(const Score &score) const
00390 {
00391     // update counts
00392     uint nbGames = item("nb games")->increment(_id);
00393     switch (score.type()) {
00394     case Lost:
00395         item("nb lost games")->increment(_id);
00396         break;
00397     case Won: break;
00398     case Draw:
00399         item("nb draw games")->increment(_id);
00400         break;
00401     };
00402 
00403     // update mean
00404     if ( score.type()==Won ) {
00405         uint nbWonGames = nbGames - item("nb lost games")->read(_id).toUInt()
00406                         - item("nb draw games")->read(_id).toUInt()
00407                         - item("nb black marks")->read(_id).toUInt(); // legacy
00408         double mean = (nbWonGames==1 ? 0.0
00409                        : item("mean score")->read(_id).toDouble());
00410         mean += (double(score.score()) - mean) / nbWonGames;
00411         item("mean score")->write(_id, mean);
00412     }
00413 
00414     // update best score
00415     Score best = score; // copy optionnal fields (there are not taken into account here)
00416     best.setScore( item("best score")->read(_id).toUInt() );
00417     if ( best<score ) {
00418         item("best score")->write(_id, score.score());
00419         item("date")->write(_id, score.data("date").toDateTime());
00420     }
00421 
00422     // update trends
00423     int current = item("current trend")->read(_id).toInt();
00424     switch (score.type()) {
00425     case Won: {
00426         if ( current<0 ) current = 0;
00427         current++;
00428         uint won = item("max won trend")->read(_id).toUInt();
00429         if ( (uint)current>won ) item("max won trend")->write(_id, current);
00430         break;
00431     }
00432     case Lost: {
00433         if ( current>0 ) current = 0;
00434         current--;
00435         uint lost = item("max lost trend")->read(_id).toUInt();
00436         uint clost = -current;
00437         if ( clost>lost ) item("max lost trend")->write(_id, clost);
00438         break;
00439     }
00440     case Draw:
00441         current = 0;
00442         break;
00443     }
00444     item("current trend")->write(_id, current);
00445 
00446     // update histogram
00447     if ( score.type()==Won ) {
00448         const QMemArray<uint> &sh = _histogram;
00449         for (uint i=1; i<histoSize(); i++)
00450             if ( i==sh.size() || score.score()<sh[i] ) {
00451                 item(histoName(i))->increment(_id);
00452                 break;
00453             }
00454     }
00455 }
00456 
00457 bool PlayerInfos::isNameUsed(const QString &newName) const
00458 {
00459     if ( newName==name() ) return false; // own name...
00460     for (uint i=0; i<nbEntries(); i++)
00461         if ( newName.lower()==item("name")->read(i).toString().lower() ) return true;
00462     if ( newName==i18n(ItemContainer::ANONYMOUS_LABEL) ) return true;
00463     return false;
00464 }
00465 
00466 void PlayerInfos::modifyName(const QString &newName) const
00467 {
00468     item("name")->write(_id, newName);
00469 }
00470 
00471 void PlayerInfos::modifySettings(const QString &newName,
00472                                  const QString &comment, bool WWEnabled,
00473                                  const QString &newKey) const
00474 {
00475     modifyName(newName);
00476     item("comment")->write(_id, comment);
00477     ConfigGroup cg;
00478     cg.config()->writeEntry(HS_WW_ENABLED, WWEnabled);
00479     if ( !newKey.isEmpty() ) cg.config()->writeEntry(HS_KEY, newKey);
00480     if (WWEnabled) cg.config()->writeEntry(HS_REGISTERED_NAME, newName);
00481 }
00482 
00483 QString PlayerInfos::registeredName() const
00484 {
00485     ConfigGroup cg;
00486     return cg.config()->readEntry(HS_REGISTERED_NAME, QString::null);
00487 }
00488 
00489 void PlayerInfos::removeKey()
00490 {
00491     ConfigGroup cg;
00492 
00493     // save old key/nickname
00494     uint i = 0;
00495     QString str = "%1 old #%2";
00496     QString sk;
00497     do {
00498         i++;
00499         sk = str.arg(HS_KEY).arg(i);
00500     } while ( !cg.config()->readEntry(sk, QString::null).isEmpty() );
00501     cg.config()->writeEntry(sk, key());
00502     cg.config()->writeEntry(str.arg(HS_REGISTERED_NAME).arg(i),
00503                             registeredName());
00504 
00505     // clear current key/nickname
00506     cg.config()->deleteEntry(HS_KEY);
00507     cg.config()->deleteEntry(HS_REGISTERED_NAME);
00508     cg.config()->writeEntry(HS_WW_ENABLED, false);
00509 }
00510 
00511 //-----------------------------------------------------------------------------
00512 ManagerPrivate::ManagerPrivate(uint nbGameTypes, Manager &m)
00513     : manager(m), showStatistics(false), showDrawGames(false),
00514       trackLostGames(false), trackDrawGames(false), 
00515       showMode(Manager::ShowForHigherScore),
00516       _first(true), _nbGameTypes(nbGameTypes), _gameType(0)
00517 {}
00518 
00519 void ManagerPrivate::init(uint maxNbEntries)
00520 {
00521     _hsConfig = new KHighscore(false, 0);
00522     _playerInfos = new PlayerInfos;
00523     _scoreInfos = new ScoreInfos(maxNbEntries, *_playerInfos);
00524 }
00525 
00526 ManagerPrivate::~ManagerPrivate()
00527 {
00528     delete _scoreInfos;
00529     delete _playerInfos;
00530     delete _hsConfig;
00531 }
00532 
00533 KURL ManagerPrivate::queryURL(QueryType type, const QString &newName) const
00534 {
00535     KURL url = serverURL;
00536     QString nameItem = "nickname";
00537     QString name = _playerInfos->registeredName();
00538     bool withVersion = true;
00539     bool key = false;
00540     bool level = false;
00541 
00542     switch (type) {
00543         case Submit:
00544             url.addPath("submit.php");
00545             level = true;
00546             key = true;
00547             break;
00548         case Register:
00549             url.addPath("register.php");
00550             name = newName;
00551             break;
00552         case Change:
00553             url.addPath("change.php");
00554             key = true;
00555             if ( newName!=name )
00556                 Manager::addToQueryURL(url, "new_nickname", newName);
00557             break;
00558         case Players:
00559             url.addPath("players.php");
00560             nameItem = "highlight";
00561             withVersion = false;
00562             break;
00563         case Scores:
00564             url.addPath("highscores.php");
00565             withVersion = false;
00566             if ( _nbGameTypes>1 ) level = true;
00567             break;
00568     }
00569 
00570     if (withVersion) Manager::addToQueryURL(url, "version", version);
00571     if ( !name.isEmpty() ) Manager::addToQueryURL(url, nameItem, name);
00572     if (key) Manager::addToQueryURL(url, "key", _playerInfos->key());
00573     if (level) {
00574         QString label = manager.gameTypeLabel(_gameType, Manager::WW);
00575         if ( !label.isEmpty() ) Manager::addToQueryURL(url, "level", label);
00576     }
00577 
00578     return url;
00579 }
00580 
00581 // strings that needs to be translated (coming from the highscores server)
00582 const char *DUMMY_STRINGS[] = {
00583     I18N_NOOP("Undefined error."),
00584     I18N_NOOP("Missing argument(s)."),
00585     I18N_NOOP("Invalid argument(s)."),
00586 
00587     I18N_NOOP("Unable to connect to MySQL server."),
00588     I18N_NOOP("Unable to select database."),
00589     I18N_NOOP("Error on database query."),
00590     I18N_NOOP("Error on database insert."),
00591 
00592     I18N_NOOP("Nickname already registered."),
00593     I18N_NOOP("Nickname not registered."),
00594     I18N_NOOP("Invalid key."),
00595     I18N_NOOP("Invalid submit key."),
00596 
00597     I18N_NOOP("Invalid level."),
00598     I18N_NOOP("Invalid score.")
00599 };
00600 
00601 const char *UNABLE_TO_CONTACT =
00602     I18N_NOOP("Unable to contact world-wide highscore server");
00603 
00604 bool ManagerPrivate::doQuery(const KURL &url, QWidget *parent,
00605                                 QDomNamedNodeMap *map)
00606 {
00607     KIO::http_update_cache(url, true, 0); // remove cache !
00608 
00609     QString tmpFile;
00610     if ( !KIO::NetAccess::download(url, tmpFile, parent) ) {
00611         QString details = i18n("Server URL: %1").arg(url.host());
00612         KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
00613         return false;
00614     }
00615 
00616     QFile file(tmpFile);
00617     if ( !file.open(IO_ReadOnly) ) {
00618         KIO::NetAccess::removeTempFile(tmpFile);
00619         QString details = i18n("Unable to open temporary file.");
00620         KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
00621         return false;
00622     }
00623 
00624     QTextStream t(&file);
00625     QString content = t.read().stripWhiteSpace();
00626     file.close();
00627     KIO::NetAccess::removeTempFile(tmpFile);
00628 
00629     QDomDocument doc;
00630     if ( doc.setContent(content) ) {
00631         QDomElement root = doc.documentElement();
00632         QDomElement element = root.firstChild().toElement();
00633         if ( element.tagName()=="success" ) {
00634             if (map) *map = element.attributes();
00635             return true;
00636         }
00637         if ( element.tagName()=="error" ) {
00638             QDomAttr attr = element.attributes().namedItem("label").toAttr();
00639             if ( !attr.isNull() ) {
00640                 QString msg = i18n(attr.value().latin1());
00641                 QString caption = i18n("Message from world-wide highscores "
00642                                        "server");
00643                 KMessageBox::sorry(parent, msg, caption);
00644                 return false;
00645             }
00646         }
00647     }
00648     QString msg = i18n("Invalid answer from world-wide highscores server.");
00649     QString details = i18n("Raw message: %1").arg(content);
00650     KMessageBox::detailedSorry(parent, msg, details);
00651     return false;
00652 }
00653 
00654 bool ManagerPrivate::getFromQuery(const QDomNamedNodeMap &map,
00655                                   const QString &name, QString &value,
00656                                   QWidget *parent)
00657 {
00658     QDomAttr attr = map.namedItem(name).toAttr();
00659     if ( attr.isNull() ) {
00660         KMessageBox::sorry(parent,
00661                i18n("Invalid answer from world-wide "
00662                     "highscores server (missing item: %1).").arg(name));
00663         return false;
00664     }
00665     value = attr.value();
00666     return true;
00667 }
00668 
00669 Score ManagerPrivate::readScore(uint i) const
00670 {
00671     Score score(Won);
00672     _scoreInfos->read(i, score);
00673     return score;
00674 }
00675 
00676 int ManagerPrivate::rank(const Score &score) const
00677 {
00678     uint nb = _scoreInfos->nbEntries();
00679     uint i = 0;
00680     for (; i<nb; i++)
00681         if ( readScore(i)<score ) break;
00682     return (i<_scoreInfos->maxNbEntries() ? (int)i : -1);
00683 }
00684 
00685 bool ManagerPrivate::modifySettings(const QString &newName,
00686                                     const QString &comment, bool WWEnabled,
00687                                     QWidget *widget)
00688 {
00689     QString newKey;
00690     bool newPlayer = false;
00691 
00692     if (WWEnabled) {
00693         newPlayer = _playerInfos->key().isEmpty()
00694                     || _playerInfos->registeredName().isEmpty();
00695         KURL url = queryURL((newPlayer ? Register : Change), newName);
00696         Manager::addToQueryURL(url, "comment", comment);
00697 
00698         QDomNamedNodeMap map;
00699         bool ok = doQuery(url, widget, &map);
00700         if ( !ok || (newPlayer && !getFromQuery(map, "key", newKey, widget)) )
00701             return false;
00702     }
00703 
00704     bool ok = _hsConfig->lockForWriting(widget); // no GUI when locking
00705     if (ok) {
00706         // check again name in case the config file has been changed...
00707         // if it has, it is unfortunate because the WWW name is already
00708         // committed but should be very rare and not really problematic
00709         ok = ( !_playerInfos->isNameUsed(newName) );
00710         if (ok)
00711             _playerInfos->modifySettings(newName, comment, WWEnabled, newKey);
00712         _hsConfig->writeAndUnlock();
00713     }
00714     return ok;
00715 }
00716 
00717 void ManagerPrivate::convertToGlobal()
00718 {
00719     // read old highscores
00720     KHighscore *tmp = _hsConfig;
00721     _hsConfig = new KHighscore(true, 0);
00722     QValueVector<Score> scores(_scoreInfos->nbEntries());
00723     for (uint i=0; i<scores.count(); i++)
00724         scores[i] = readScore(i);
00725 
00726     // commit them
00727     delete _hsConfig;
00728     _hsConfig = tmp;
00729     _hsConfig->lockForWriting();
00730     for (uint i=0; i<scores.count(); i++)
00731         if ( scores[i].data("id").toUInt()==_playerInfos->oldLocalId()+1 )
00732             submitLocal(scores[i]);
00733     _hsConfig->writeAndUnlock();
00734 }
00735 
00736 void ManagerPrivate::setGameType(uint type)
00737 {
00738     if (_first) {
00739         _first = false;
00740         if ( _playerInfos->isNewPlayer() ) {
00741             // convert legacy highscores
00742             for (uint i=0; i<_nbGameTypes; i++) {
00743                 setGameType(i);
00744                 manager.convertLegacy(i);
00745             }
00746 
00747 #ifdef HIGHSCORE_DIRECTORY
00748             if ( _playerInfos->isOldLocalPlayer() ) {
00749                 // convert local to global highscores
00750                 for (uint i=0; i<_nbGameTypes; i++) {
00751                     setGameType(i);
00752                     convertToGlobal();
00753                 }
00754             }
00755 #endif
00756         }
00757     }
00758 
00759     Q_ASSERT( type<_nbGameTypes );
00760     _gameType = kMin(type, _nbGameTypes-1);
00761     QString str = "scores";
00762     QString lab = manager.gameTypeLabel(_gameType, Manager::Standard);
00763     if ( !lab.isEmpty() ) {
00764         _playerInfos->setSubGroup(lab);
00765         str += "_" + lab;
00766     }
00767     _scoreInfos->setGroup(str);
00768 }
00769 
00770 void ManagerPrivate::checkFirst()
00771 {
00772     if (_first) setGameType(0);
00773 }
00774 
00775 int ManagerPrivate::submitScore(const Score &ascore,
00776                                 QWidget *widget, bool askIfAnonymous)
00777 {
00778     checkFirst();
00779 
00780     Score score = ascore;
00781     score.setData("id", _playerInfos->id() + 1);
00782     score.setData("date", QDateTime::currentDateTime());
00783 
00784     // ask new name if anonymous and winner
00785     const char *dontAskAgainName = "highscore_ask_name_dialog";
00786     QString newName;
00787     KMessageBox::ButtonCode dummy;
00788     if ( score.type()==Won && askIfAnonymous && _playerInfos->isAnonymous()
00789      && KMessageBox::shouldBeShownYesNo(dontAskAgainName, dummy) ) {
00790          AskNameDialog d(widget);
00791          if ( d.exec()==QDialog::Accepted ) newName = d.name();
00792          if ( d.dontAskAgain() )
00793              KMessageBox::saveDontShowAgainYesNo(dontAskAgainName,
00794                                                  KMessageBox::No);
00795     }
00796 
00797     int rank = -1;
00798     if ( _hsConfig->lockForWriting(widget) ) { // no GUI when locking
00799         // check again new name in case the config file has been changed...
00800         if ( !newName.isEmpty() && !_playerInfos->isNameUsed(newName) )
00801              _playerInfos->modifyName(newName);
00802 
00803         // commit locally
00804         _playerInfos->submitScore(score);
00805         if ( score.type()==Won ) rank = submitLocal(score);
00806         _hsConfig->writeAndUnlock();
00807     }
00808 
00809     if ( _playerInfos->isWWEnabled() )
00810         submitWorldWide(score, widget);
00811 
00812     return rank;
00813 }
00814 
00815 int ManagerPrivate::submitLocal(const Score &score)
00816 {
00817     int r = rank(score);
00818     if ( r!=-1 ) {
00819         uint nb = _scoreInfos->nbEntries();
00820         if ( nb<_scoreInfos->maxNbEntries() ) nb++;
00821         _scoreInfos->write(r, score, nb);
00822     }
00823     return r;
00824 }
00825 
00826 bool ManagerPrivate::submitWorldWide(const Score &score,
00827                                      QWidget *widget) const
00828 {
00829     if ( score.type()==Lost && !trackLostGames ) return true;
00830     if ( score.type()==Draw && !trackDrawGames ) return true;
00831 
00832     KURL url = queryURL(Submit);
00833     manager.additionalQueryItems(url, score);
00834     int s = (score.type()==Won ? score.score() : (int)score.type());
00835     QString str =  QString::number(s);
00836     Manager::addToQueryURL(url, "score", str);
00837     KMD5 context(QString(_playerInfos->registeredName() + str).latin1());
00838     Manager::addToQueryURL(url, "check", context.hexDigest());
00839 
00840     return doQuery(url, widget);
00841 }
00842 
00843 void ManagerPrivate::exportHighscores(QTextStream &s)
00844 {
00845     uint tmp = _gameType;
00846 
00847     for (uint i=0; i<_nbGameTypes; i++) {
00848         setGameType(i);
00849         if ( _nbGameTypes>1 ) {
00850             if ( i!=0 ) s << endl;
00851             s << "--------------------------------" << endl;
00852             s << "Game type: "
00853               << manager.gameTypeLabel(_gameType, Manager::I18N)
00854               << endl;
00855             s << endl;
00856         }
00857         s << "Players list:" << endl;
00858         _playerInfos->exportToText(s);
00859         s << endl;
00860         s << "Highscores list:" << endl;
00861         _scoreInfos->exportToText(s);
00862     }
00863 
00864     setGameType(tmp);
00865 }
00866 
00867 } // namespace
KDE Home | KDE Accessibility Home | Description of Access Keys