File indexing completed on 2024-04-21 04:03:59

0001 /*
0002     SPDX-FileCopyrightText: 2001-2004 Nicolas Hadacek <hadacek@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "kexthighscore_internal.h"
0008 
0009 #include <sys/types.h>
0010 
0011 #include <QCryptographicHash>
0012 #include <QDomDocument>
0013 #include <QTemporaryFile>
0014 
0015 #include <KIO/FileCopyJob>
0016 #include <KIO/SimpleJob>
0017 #include <KJobWidgets>
0018 #include <KMessageBox>
0019 #include <KUser>
0020 
0021 #include "kexthighscore_gui.h"
0022 #include <KEMailSettings>
0023 
0024 // TODO Decide if want to support
0025 // a build time HIGHSCORE_DIRECTORY or not
0026 // #include <config-highscore.h>
0027 
0028 namespace KExtHighscore
0029 {
0030 
0031 //-----------------------------------------------------------------------------
0032 const char ItemContainer::ANONYMOUS[] = "_";
0033 const KLazyLocalizedString ItemContainer::ANONYMOUS_LABEL = kli18n("anonymous");
0034 
0035 ItemContainer::ItemContainer()
0036     : _item(nullptr)
0037 {}
0038 
0039 ItemContainer::~ItemContainer()
0040 {
0041     delete _item;
0042 }
0043 
0044 void ItemContainer::setItem(Item *item)
0045 {
0046     delete _item;
0047     _item = item;
0048 }
0049 
0050 QString ItemContainer::entryName() const
0051 {
0052     if ( _subGroup.isEmpty() ) return _name;
0053     return _name + QLatin1Char( '_' ) + _subGroup;
0054 }
0055 
0056 QVariant ItemContainer::read(uint i) const
0057 {
0058     Q_ASSERT(_item);
0059 
0060     QVariant v = _item->defaultValue();
0061     if ( isStored() ) {
0062         internal->hsConfig().setHighscoreGroup(_group);
0063         v = internal->hsConfig().readPropertyEntry(i+1, entryName(), v);
0064     }
0065     return _item->read(i, v);
0066 }
0067 
0068 QString ItemContainer::pretty(uint i) const
0069 {
0070     Q_ASSERT(_item);
0071     return _item->pretty(i, read(i));
0072 }
0073 
0074 void ItemContainer::write(uint i, const QVariant &value) const
0075 {
0076     Q_ASSERT( isStored() );
0077     Q_ASSERT( internal->hsConfig().isLocked() );
0078     internal->hsConfig().setHighscoreGroup(_group);
0079     internal->hsConfig().writeEntry(i+1, entryName(), value);
0080 }
0081 
0082 uint ItemContainer::increment(uint i) const
0083 {
0084     uint v = read(i).toUInt() + 1;
0085     write(i, v);
0086     return v;
0087 }
0088 
0089 //-----------------------------------------------------------------------------
0090 ItemArray::ItemArray()
0091     : _group(QLatin1String( "" )), _subGroup(QLatin1String( "" )) // no null groups
0092 {}
0093 
0094 ItemArray::~ItemArray()
0095 {
0096     for (int i=0; i<size(); i++) delete at(i);
0097 }
0098 
0099 int ItemArray::findIndex(const QString &name) const
0100 {
0101     for (int i=0; i<size(); i++)
0102         if ( at(i)->name()==name ) return i;
0103     return -1;
0104 }
0105 
0106 const ItemContainer *ItemArray::item(const QString &name) const
0107 {
0108     QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true"));
0109     
0110     int i = findIndex(name);
0111     if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name
0112                                 << "\"";
0113     return at(i);
0114 }
0115 
0116 ItemContainer *ItemArray::item(const QString &name)
0117 {
0118     QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true"));
0119   
0120     int i = findIndex(name);
0121     if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name
0122                                 << "\"";
0123     return at(i);
0124 }
0125 
0126 void ItemArray::setItem(const QString &name, Item *item)
0127 {
0128     QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true"));
0129   
0130     int i = findIndex(name);
0131     if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name
0132                                 << "\"";
0133     bool stored = at(i)->isStored();
0134     bool canHaveSubGroup = at(i)->canHaveSubGroup();
0135     _setItem(i, name, item, stored, canHaveSubGroup);
0136 }
0137 
0138 void ItemArray::addItem(const QString &name, Item *item,
0139                         bool stored, bool canHaveSubGroup)
0140 {
0141     QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true"));
0142     
0143     if ( findIndex(name)!=-1 )
0144         qCCritical(GAMES_EXTHIGHSCORE) << "item already exists \"" << name << "\"";
0145 
0146     append(new ItemContainer);
0147     //at(i) = new ItemContainer;
0148     _setItem(size()-1, name, item, stored, canHaveSubGroup);
0149 }
0150 
0151 void ItemArray::_setItem(uint i, const QString &name, Item *item,
0152                          bool stored, bool canHaveSubGroup)
0153 {
0154     at(i)->setItem(item);
0155     at(i)->setName(name);
0156     at(i)->setGroup(stored ? _group : QString());
0157     at(i)->setSubGroup(canHaveSubGroup ? _subGroup : QString());
0158 }
0159 
0160 void ItemArray::setGroup(const QString &group)
0161 {
0162     Q_ASSERT( !group.isNull() );
0163     _group = group;
0164     for (int i=0; i<size(); i++)
0165         if ( at(i)->isStored() ) at(i)->setGroup(group);
0166 }
0167 
0168 void ItemArray::setSubGroup(const QString &subGroup)
0169 {
0170     Q_ASSERT( !subGroup.isNull() );
0171     _subGroup = subGroup;
0172     for (int i=0; i<size(); i++)
0173         if ( at(i)->canHaveSubGroup() ) at(i)->setSubGroup(subGroup);
0174 }
0175 
0176 void ItemArray::read(uint k, Score &data) const
0177 {
0178     for (int i=0; i<size(); i++) {
0179         if ( !at(i)->isStored() ) continue;
0180         data.setData(at(i)->name(), at(i)->read(k));
0181     }
0182 }
0183 
0184 void ItemArray::write(uint k, const Score &data, uint nb) const
0185 {
0186     for (int i=0; i<size(); i++) {
0187         if ( !at(i)->isStored() ) continue;
0188         for (uint j=nb-1; j>k; j--)  at(i)->write(j, at(i)->read(j-1));
0189         at(i)->write(k, data.data(at(i)->name()));
0190     }
0191 }
0192 
0193 void ItemArray::exportToText(QTextStream &s) const
0194 {
0195     for (uint k=0; k<nbEntries()+1; k++) {
0196         for (int i=0; i<size(); i++) {
0197             const Item *item = at(i)->item();
0198             if ( item->isVisible() ) {
0199                 if ( i!=0 ) s << '\t';
0200                 if ( k==0 ) s << item->label();
0201                 else s << at(i)->pretty(k-1);
0202             }
0203         }
0204         s << Qt::endl;
0205     }
0206 }
0207 
0208 //-----------------------------------------------------------------------------
0209 class ScoreNameItem : public NameItem
0210 {
0211  public:
0212     ScoreNameItem(const ScoreInfos &score, const PlayerInfos &infos)
0213         : _score(score), _infos(infos) {}
0214 
0215     QString pretty(uint i, const QVariant &v) const override {
0216         uint id = _score.item(QStringLiteral( "id" ))->read(i).toUInt();
0217         if ( id==0 ) return NameItem::pretty(i, v);
0218         return _infos.prettyName(id-1);
0219     }
0220 
0221  private:
0222     const ScoreInfos  &_score;
0223     const PlayerInfos &_infos;
0224 };
0225 
0226 //-----------------------------------------------------------------------------
0227 ScoreInfos::ScoreInfos(uint maxNbEntries, const PlayerInfos &infos)
0228     : _maxNbEntries(maxNbEntries)
0229 {
0230     addItem(QStringLiteral( "id" ), new Item((uint)0));
0231     addItem(QStringLiteral( "rank" ), new RankItem, false);
0232     addItem(QStringLiteral( "name" ), new ScoreNameItem(*this, infos));
0233     addItem(QStringLiteral( "score" ), Manager::createItem(Manager::ScoreDefault));
0234     addItem(QStringLiteral( "date" ), new DateItem);
0235 }
0236 
0237 uint ScoreInfos::nbEntries() const
0238 {
0239     uint i = 0;
0240     for (; i<_maxNbEntries; i++)
0241         if ( item(QStringLiteral( "score" ))->read(i)==item(QStringLiteral( "score" ))->item()->defaultValue() )
0242             break;
0243     return i;
0244 }
0245 
0246 //-----------------------------------------------------------------------------
0247 const char *HS_ID              = "player id";
0248 const char *HS_REGISTERED_NAME = "registered name";
0249 const char *HS_KEY             = "player key";
0250 const char *HS_WW_ENABLED      = "ww hs enabled";
0251 
0252 PlayerInfos::PlayerInfos()
0253 {
0254     setGroup(QStringLiteral( "players" ));
0255 
0256     // standard items
0257     addItem(QStringLiteral( "name" ), new NameItem);
0258     Item *it = new Item((uint)0, i18n("Games Count"),Qt::AlignRight);
0259     addItem(QStringLiteral( "nb games" ), it, true, true);
0260     it = Manager::createItem(Manager::MeanScoreDefault);
0261     addItem(QStringLiteral( "mean score" ), it, true, true);
0262     it = Manager::createItem(Manager::BestScoreDefault);
0263     addItem(QStringLiteral( "best score" ), it, true, true);
0264     addItem(QStringLiteral( "date" ), new DateItem, true, true);
0265     it = new Item(QString(), i18n("Comment"), Qt::AlignLeft);
0266     addItem(QStringLiteral( "comment" ), it);
0267 
0268     // statistics items
0269     addItem(QStringLiteral( "nb black marks" ), new Item((uint)0), true, true); // legacy
0270     addItem(QStringLiteral( "nb lost games" ), new Item((uint)0), true, true);
0271     addItem(QStringLiteral( "nb draw games" ), new Item((uint)0), true, true);
0272     addItem(QStringLiteral( "current trend" ), new Item((int)0), true, true);
0273     addItem(QStringLiteral( "max lost trend" ), new Item((uint)0), true, true);
0274     addItem(QStringLiteral( "max won trend" ), new Item((uint)0), true, true);
0275 
0276     QString username = KUser().loginName();
0277 
0278 #ifdef HIGHSCORE_DIRECTORY
0279     internal->hsConfig().setHighscoreGroup("players");
0280     for (uint i=0; ;i++) {
0281         if ( !internal->hsConfig().hasEntry(i+1, "username") ) {
0282             _newPlayer = true;
0283             _id = i;
0284             break;
0285         }
0286         if ( internal->hsConfig().readEntry(i+1, "username")==username ) {
0287             _newPlayer = false;
0288             _id = i;
0289             return;
0290         }
0291     }
0292 #endif
0293     internal->hsConfig().lockForWriting();
0294     KEMailSettings emailConfig;
0295     emailConfig.setProfile(emailConfig.defaultProfileName());
0296     QString name = emailConfig.getSetting(KEMailSettings::RealName);
0297     if ( name.isEmpty() || isNameUsed(name) ) name = username;
0298     if ( isNameUsed(name) ) name= QLatin1String(ItemContainer::ANONYMOUS);
0299 #ifdef HIGHSCORE_DIRECTORY
0300     internal->hsConfig().writeEntry(_id+1, "username", username);
0301     item("name")->write(_id, name);
0302 #endif
0303 
0304     ConfigGroup cg;
0305     _oldLocalPlayer = cg.hasKey(HS_ID);
0306     _oldLocalId = cg.readEntry(HS_ID).toUInt();
0307 #ifdef HIGHSCORE_DIRECTORY
0308     if (_oldLocalPlayer) { // player already exists in local config file
0309         // copy player data
0310         QString prefix = QString::fromLatin1( "%1_").arg(_oldLocalId+1);
0311 #ifdef __GNUC__
0312 #warning "kde4 port g.config()->entryMap";
0313 #endif
0314 #if 0
0315         QMap<QString, QString> entries =
0316             cg.config()->entryMap("KHighscore_players");
0317         QMap<QString, QString>::const_iterator it;
0318         for (it=entries.begin(); it!=entries.end(); ++it) {
0319             QString key = it.key();
0320             if ( key.find(prefix)==0 ) {
0321                 QString name = key.right(key.length()-prefix.length());
0322                 if ( name!="name" || !isNameUsed(it.data()) )
0323                     internal->hsConfig().writeEntry(_id+1, name, it.data());
0324             }
0325         }
0326 #endif
0327     }
0328 #else
0329     _newPlayer = !_oldLocalPlayer;
0330     if (_oldLocalPlayer) _id = _oldLocalId;
0331     else {
0332         _id = nbEntries();
0333         cg.writeEntry(HS_ID, _id);
0334         item(QStringLiteral( "name" ))->write(_id, name);
0335     }
0336 #endif
0337     _bound = true;
0338     internal->hsConfig().writeAndUnlock();
0339 }
0340 
0341 void PlayerInfos::createHistoItems(const QList<uint> &scores, bool bound)
0342 {
0343     Q_ASSERT( _histogram.size()==0 );
0344     _bound = bound;
0345     _histogram = scores;
0346     for (int i=1; i<histoSize(); i++)
0347         addItem(histoName(i), new Item((uint)0), true, true);
0348 }
0349 
0350 bool PlayerInfos::isAnonymous() const
0351 {
0352     return ( name()==QLatin1String( ItemContainer::ANONYMOUS ) );
0353 }
0354 
0355 uint PlayerInfos::nbEntries() const
0356 {
0357     internal->hsConfig().setHighscoreGroup(QStringLiteral( "players" ));
0358     const QStringList list = internal->hsConfig().readList(QStringLiteral( "name" ), -1);
0359     return list.count();
0360 }
0361 
0362 QString PlayerInfos::key() const
0363 {
0364     ConfigGroup cg;
0365     return cg.readEntry(HS_KEY, QString());
0366 }
0367 
0368 bool PlayerInfos::isWWEnabled() const
0369 {
0370     ConfigGroup cg;
0371     return cg.readEntry(HS_WW_ENABLED, false);
0372 }
0373 
0374 QString PlayerInfos::histoName(int i) const
0375 {
0376     const QList<uint> &sh = _histogram;
0377     Q_ASSERT( i<sh.size() || (_bound || i==sh.size()) );
0378     if ( i==sh.size() )
0379         return QStringLiteral( "nb scores greater than %1").arg(sh[sh.size()-1]);
0380     return QStringLiteral( "nb scores less than %1").arg(sh[i]);
0381 }
0382 
0383 int PlayerInfos::histoSize() const
0384 {
0385      return _histogram.size() + (_bound ? 0 : 1);
0386 }
0387 
0388 void PlayerInfos::submitScore(const Score &score) const
0389 {
0390     // update counts
0391     uint nbGames = item(QStringLiteral( "nb games" ))->increment(_id);
0392     switch (score.type()) {
0393     case Lost:
0394         item(QStringLiteral( "nb lost games" ))->increment(_id);
0395         break;
0396     case Won: break;
0397     case Draw:
0398         item(QStringLiteral( "nb draw games" ))->increment(_id);
0399         break;
0400     };
0401 
0402     // update mean
0403     if ( score.type()==Won ) {
0404         uint nbWonGames = nbGames - item(QStringLiteral( "nb lost games" ))->read(_id).toUInt()
0405                         - item(QStringLiteral( "nb draw games" ))->read(_id).toUInt()
0406                         - item(QStringLiteral( "nb black marks" ))->read(_id).toUInt(); // legacy
0407         double mean = (nbWonGames==1 ? 0.0
0408                        : item(QStringLiteral( "mean score" ))->read(_id).toDouble());
0409         mean += (double(score.score()) - mean) / nbWonGames;
0410         item(QStringLiteral( "mean score" ))->write(_id, mean);
0411     }
0412 
0413     // update best score
0414     Score best = score; // copy optional fields (there are not taken into account here)
0415     best.setScore( item(QStringLiteral( "best score" ))->read(_id).toUInt() );
0416     if ( best<score ) {
0417         item(QStringLiteral( "best score" ))->write(_id, score.score());
0418         item(QStringLiteral( "date" ))->write(_id, score.data(QStringLiteral( "date" )).toDateTime());
0419     }
0420 
0421     // update trends
0422     int current = item(QStringLiteral( "current trend" ))->read(_id).toInt();
0423     switch (score.type()) {
0424     case Won: {
0425         if ( current<0 ) current = 0;
0426         current++;
0427         uint won = item(QStringLiteral( "max won trend" ))->read(_id).toUInt();
0428         if ( (uint)current>won ) item(QStringLiteral( "max won trend" ))->write(_id, current);
0429         break;
0430     }
0431     case Lost: {
0432         if ( current>0 ) current = 0;
0433         current--;
0434         uint lost = item(QStringLiteral( "max lost trend" ))->read(_id).toUInt();
0435         uint clost = -current;
0436         if ( clost>lost ) item(QStringLiteral( "max lost trend" ))->write(_id, clost);
0437         break;
0438     }
0439     case Draw:
0440         current = 0;
0441         break;
0442     }
0443     item(QStringLiteral( "current trend" ))->write(_id, current);
0444 
0445     // update histogram
0446     if ( score.type()==Won ) {
0447         const QList<uint> &sh = _histogram;
0448         for (int i=1; i<histoSize(); i++)
0449             if ( i==sh.size() || score.score()<sh[i] ) {
0450                 item(histoName(i))->increment(_id);
0451                 break;
0452             }
0453     }
0454 }
0455 
0456 bool PlayerInfos::isNameUsed(const QString &newName) const
0457 {
0458     if ( newName==name() ) return false; // own name...
0459     for (uint i=0; i<nbEntries(); i++)
0460         if ( newName.toLower()==item(QStringLiteral( "name" ))->read(i).toString().toLower() ) return true;
0461     if ( newName==ItemContainer::ANONYMOUS_LABEL.toString() ) return true;
0462     return false;
0463 }
0464 
0465 void PlayerInfos::modifyName(const QString &newName) const
0466 {
0467     item(QStringLiteral( "name" ))->write(_id, newName);
0468 }
0469 
0470 void PlayerInfos::modifySettings(const QString &newName,
0471                                  const QString &comment, bool WWEnabled,
0472                                  const QString &newKey) const
0473 {
0474     modifyName(newName);
0475     item(QStringLiteral( "comment" ))->write(_id, comment);
0476     ConfigGroup cg;
0477     cg.writeEntry(HS_WW_ENABLED, WWEnabled);
0478     if ( !newKey.isEmpty() ) cg.writeEntry(HS_KEY, newKey);
0479     if (WWEnabled) cg.writeEntry(HS_REGISTERED_NAME, newName);
0480 }
0481 
0482 QString PlayerInfos::registeredName() const
0483 {
0484     ConfigGroup cg;
0485     return cg.readEntry(HS_REGISTERED_NAME, QString());
0486 }
0487 
0488 void PlayerInfos::removeKey()
0489 {
0490     ConfigGroup cg;
0491 
0492     // save old key/nickname
0493     uint i = 0;
0494     QString str = QStringLiteral( "%1 old #%2" );
0495     QString sk;
0496     do {
0497         i++;
0498         sk = str.arg(QLatin1String( HS_KEY )).arg(i);
0499     } while ( !cg.readEntry(sk, QString()).isEmpty() );
0500     cg.writeEntry(sk, key());
0501     cg.writeEntry(str.arg(QLatin1String( HS_REGISTERED_NAME )).arg(i),
0502                             registeredName());
0503 
0504     // clear current key/nickname
0505     cg.deleteEntry(HS_KEY);
0506     cg.deleteEntry(HS_REGISTERED_NAME);
0507     cg.writeEntry(HS_WW_ENABLED, false);
0508 }
0509 
0510 //-----------------------------------------------------------------------------
0511 ManagerPrivate::ManagerPrivate(uint nbGameTypes, Manager &m)
0512     : manager(m), showStatistics(false), showDrawGames(false),
0513       trackLostGames(false), trackDrawGames(false),
0514       showMode(Manager::ShowForHigherScore),
0515       _first(true), _nbGameTypes(nbGameTypes), _gameType(0)
0516 {}
0517 
0518 void ManagerPrivate::init(uint maxNbEntries)
0519 {
0520     _hsConfig = new KGameHighscore(false, nullptr);
0521     _playerInfos = new PlayerInfos;
0522     _scoreInfos = new ScoreInfos(maxNbEntries, *_playerInfos);
0523 }
0524 
0525 ManagerPrivate::~ManagerPrivate()
0526 {
0527     delete _scoreInfos;
0528     delete _playerInfos;
0529     delete _hsConfig;
0530 }
0531 
0532 QUrl ManagerPrivate::queryUrl(QueryType type, const QString &newName) const
0533 {
0534     QUrl url = serverURL;
0535     QString nameItem = QStringLiteral( "nickname" );
0536     QString name = _playerInfos->registeredName();
0537     bool withVersion = true;
0538     bool key = false;
0539     bool level = false;
0540 
0541     switch (type) {
0542         case Submit:
0543             url = url.adjusted(QUrl::StripTrailingSlash);
0544         url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "submit.php" ));
0545         level = true;
0546             key = true;
0547             break;
0548         case Register:
0549             url = url.adjusted(QUrl::StripTrailingSlash);
0550         url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "register.php" ));
0551         name = newName;
0552             break;
0553         case Change:
0554             url = url.adjusted(QUrl::StripTrailingSlash);
0555         url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "change.php" ));
0556         key = true;
0557             if ( newName!=name )
0558                 Manager::addToQueryURL(url, QStringLiteral( "new_nickname" ), newName);
0559             break;
0560         case Players:
0561         url = url.adjusted(QUrl::StripTrailingSlash);
0562         url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "players.php" ));
0563             nameItem = QStringLiteral( "highlight" );
0564             withVersion = false;
0565             break;
0566         case Scores:
0567             url = url.adjusted(QUrl::StripTrailingSlash);
0568         url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "highscores.php" ));
0569         withVersion = false;
0570             if ( _nbGameTypes>1 ) level = true;
0571             break;
0572     }
0573 
0574     if (withVersion) Manager::addToQueryURL(url, QStringLiteral( "version" ), version);
0575     if ( !name.isEmpty() ) Manager::addToQueryURL(url, nameItem, name);
0576     if (key) Manager::addToQueryURL(url, QStringLiteral( "key" ), _playerInfos->key());
0577     if (level) {
0578         QString label = manager.gameTypeLabel(_gameType, Manager::WW);
0579         if ( !label.isEmpty() ) Manager::addToQueryURL(url, QStringLiteral( "level" ), label);
0580     }
0581 
0582     return url;
0583 }
0584 
0585 // strings that needs to be translated (coming from the highscores server)
0586 const KLazyLocalizedString DUMMY_STRINGS[] = {
0587     kli18n("Undefined error."),
0588     kli18n("Missing argument(s)."),
0589     kli18n("Invalid argument(s)."),
0590 
0591     kli18n("Unable to connect to MySQL server."),
0592     kli18n("Unable to select database."),
0593     kli18n("Error on database query."),
0594     kli18n("Error on database insert."),
0595 
0596     kli18n("Nickname already registered."),
0597     kli18n("Nickname not registered."),
0598     kli18n("Invalid key."),
0599     kli18n("Invalid submit key."),
0600 
0601     kli18n("Invalid level."),
0602     kli18n("Invalid score.")
0603 };
0604 
0605 const KLazyLocalizedString UNABLE_TO_CONTACT =
0606     kli18n("Unable to contact world-wide highscore server");
0607 
0608 bool ManagerPrivate::doQuery(const QUrl &url, QWidget *parent,
0609                                 QDomNamedNodeMap *map)
0610 {
0611     KIO::http_update_cache(url, true, QDateTime::fromSecsSinceEpoch(0)); // remove cache !
0612 
0613     QTemporaryFile tmpFile;
0614     if ( !tmpFile.open() ) {
0615         QString details = i18n("Unable to open temporary file.");
0616         KMessageBox::detailedError(parent, UNABLE_TO_CONTACT.toString(), details);
0617         return false;
0618     }
0619 
0620     auto copyJob = KIO::file_copy(url, QUrl::fromLocalFile(tmpFile.fileName()));
0621     KJobWidgets::setWindow(copyJob, parent);
0622     copyJob->exec();
0623     if( copyJob->error() ) {
0624         QString details = i18n("Server URL: %1", url.host());
0625         KMessageBox::detailedError(parent, UNABLE_TO_CONTACT.toString(), details);
0626         return false;
0627     }
0628 
0629     QTextStream t(&tmpFile);
0630     QString content = t.readAll().trimmed();
0631     tmpFile.close();
0632 
0633     QDomDocument doc;
0634     if ( doc.setContent(content) ) {
0635         QDomElement root = doc.documentElement();
0636         QDomElement element = root.firstChild().toElement();
0637         if ( element.tagName()==QLatin1String( "success" ) ) {
0638             if (map) *map = element.attributes();
0639             return true;
0640         }
0641         if ( element.tagName()==QLatin1String( "error" ) ) {
0642             QDomAttr attr = element.attributes().namedItem(QStringLiteral( "label" )).toAttr();
0643             if ( !attr.isNull() ) {
0644                 // see DUMMY_STRINGS
0645                 QString msg = i18n(attr.value().toLatin1().constData());
0646                 QString caption = i18n("Message from world-wide highscores "
0647                                        "server");
0648                 KMessageBox::error(parent, msg, caption);
0649                 return false;
0650             }
0651         }
0652     }
0653     QString msg = i18n("Invalid answer from world-wide highscores server.");
0654     QString details = i18n("Raw message: %1", content);
0655     KMessageBox::detailedError(parent, msg, details);
0656     return false;
0657 }
0658 
0659 bool ManagerPrivate::getFromQuery(const QDomNamedNodeMap &map,
0660                                   const QString &name, QString &value,
0661                                   QWidget *parent)
0662 {
0663     QDomAttr attr = map.namedItem(name).toAttr();
0664     if ( attr.isNull() ) {
0665         KMessageBox::error(parent,
0666                i18n("Invalid answer from world-wide "
0667                     "highscores server (missing item: %1).", name));
0668         return false;
0669     }
0670     value = attr.value();
0671     return true;
0672 }
0673 
0674 Score ManagerPrivate::readScore(uint i) const
0675 {
0676     Score score(Won);
0677     _scoreInfos->read(i, score);
0678     return score;
0679 }
0680 
0681 int ManagerPrivate::rank(const Score &score) const
0682 {
0683     uint nb = _scoreInfos->nbEntries();
0684     uint i = 0;
0685     for (; i<nb; i++)
0686         if ( readScore(i)<score ) break;
0687     return (i<_scoreInfos->maxNbEntries() ? (int)i : -1);
0688 }
0689 
0690 bool ManagerPrivate::modifySettings(const QString &newName,
0691                                     const QString &comment, bool WWEnabled,
0692                                     QWidget *widget)
0693 {
0694     QString newKey;
0695     bool newPlayer = false;
0696 
0697     if (WWEnabled) {
0698         newPlayer = _playerInfos->key().isEmpty()
0699                     || _playerInfos->registeredName().isEmpty();
0700         QUrl url = queryUrl((newPlayer ? Register : Change), newName);
0701         Manager::addToQueryURL(url, QStringLiteral( "comment" ), comment);
0702 
0703         QDomNamedNodeMap map;
0704         bool ok = doQuery(url, widget, &map);
0705         if ( !ok || (newPlayer && !getFromQuery(map, QStringLiteral( "key" ), newKey, widget)) )
0706             return false;
0707     }
0708 
0709     bool ok = _hsConfig->lockForWriting(widget); // no GUI when locking
0710     if (ok) {
0711         // check again name in case the config file has been changed...
0712         // if it has, it is unfortunate because the WWW name is already
0713         // committed but should be very rare and not really problematic
0714         ok = ( !_playerInfos->isNameUsed(newName) );
0715         if (ok)
0716             _playerInfos->modifySettings(newName, comment, WWEnabled, newKey);
0717         _hsConfig->writeAndUnlock();
0718     }
0719     return ok;
0720 }
0721 
0722 void ManagerPrivate::convertToGlobal()
0723 {
0724     // read old highscores
0725     KGameHighscore *tmp = _hsConfig;
0726     _hsConfig = new KGameHighscore(true, nullptr);
0727     QList<Score> scores(_scoreInfos->nbEntries());
0728     for (int i=0; i<scores.count(); i++)
0729         scores[i] = readScore(i);
0730 
0731     // commit them
0732     delete _hsConfig;
0733     _hsConfig = tmp;
0734     _hsConfig->lockForWriting();
0735     for (int i=0; i<scores.count(); i++)
0736         if ( scores[i].data(QStringLiteral( "id" )).toUInt()==_playerInfos->oldLocalId()+1 )
0737             submitLocal(scores[i]);
0738     _hsConfig->writeAndUnlock();
0739 }
0740 
0741 void ManagerPrivate::setGameType(uint type)
0742 {
0743     if (_first) {
0744         _first = false;
0745         if ( _playerInfos->isNewPlayer() ) {
0746             // convert legacy highscores
0747             for (uint i=0; i<_nbGameTypes; i++) {
0748                 setGameType(i);
0749                 manager.convertLegacy(i);
0750             }
0751 
0752 #ifdef HIGHSCORE_DIRECTORY
0753             if ( _playerInfos->isOldLocalPlayer() ) {
0754                 // convert local to global highscores
0755                 for (uint i=0; i<_nbGameTypes; i++) {
0756                     setGameType(i);
0757                     convertToGlobal();
0758                 }
0759             }
0760 #endif
0761         }
0762     }
0763 
0764     Q_ASSERT( type<_nbGameTypes );
0765     _gameType = qMin(type, _nbGameTypes-1);
0766     QString str = QStringLiteral( "scores" );
0767     QString lab = manager.gameTypeLabel(_gameType, Manager::Standard);
0768     if ( !lab.isEmpty() ) {
0769         _playerInfos->setSubGroup(lab);
0770         str += QLatin1Char( '_' ) + lab;
0771     }
0772     _scoreInfos->setGroup(str);
0773 }
0774 
0775 void ManagerPrivate::checkFirst()
0776 {
0777     if (_first) setGameType(0);
0778 }
0779 
0780 int ManagerPrivate::submitScore(const Score &ascore,
0781                                 QWidget *widget, bool askIfAnonymous)
0782 {
0783     checkFirst();
0784 
0785     Score score = ascore;
0786     score.setData(QStringLiteral( "id" ), _playerInfos->id() + 1);
0787     score.setData(QStringLiteral( "date" ), QDateTime::currentDateTime());
0788 
0789     // ask new name if anonymous and winner
0790     const QLatin1String dontAskAgainName = QLatin1String( "highscore_ask_name_dialog" );
0791     QString newName;
0792     KMessageBox::ButtonCode dummy;
0793     if ( score.type()==Won && askIfAnonymous && _playerInfos->isAnonymous()
0794      && KMessageBox::shouldBeShownTwoActions(dontAskAgainName, dummy) ) {
0795          AskNameDialog d(widget);
0796          if ( d.exec()==QDialog::Accepted ) newName = d.name();
0797          if ( d.dontAskAgain() )
0798              KMessageBox::saveDontShowAgainTwoActions(dontAskAgainName,
0799                                                       KMessageBox::SecondaryAction);
0800     }
0801 
0802     int rank = -1;
0803     if ( _hsConfig->lockForWriting(widget) ) { // no GUI when locking
0804         // check again new name in case the config file has been changed...
0805         if ( !newName.isEmpty() && !_playerInfos->isNameUsed(newName) )
0806              _playerInfos->modifyName(newName);
0807 
0808         // commit locally
0809         _playerInfos->submitScore(score);
0810         if ( score.type()==Won ) rank = submitLocal(score);
0811         _hsConfig->writeAndUnlock();
0812     }
0813 
0814     if ( _playerInfos->isWWEnabled() )
0815         submitWorldWide(score, widget);
0816 
0817     return rank;
0818 }
0819 
0820 int ManagerPrivate::submitLocal(const Score &score)
0821 {
0822     int r = rank(score);
0823     if ( r!=-1 ) {
0824         uint nb = _scoreInfos->nbEntries();
0825         if ( nb<_scoreInfos->maxNbEntries() ) nb++;
0826         _scoreInfos->write(r, score, nb);
0827     }
0828     return r;
0829 }
0830 
0831 bool ManagerPrivate::submitWorldWide(const Score &score,
0832                                      QWidget *widget) const
0833 {
0834     if ( score.type()==Lost && !trackLostGames ) return true;
0835     if ( score.type()==Draw && !trackDrawGames ) return true;
0836 
0837     QUrl url = queryUrl(Submit);
0838     manager.additionalQueryItems(url, score);
0839     int s = (score.type()==Won ? score.score() : (int)score.type());
0840     QString str =  QString::number(s);
0841     Manager::addToQueryURL(url, QStringLiteral( "score" ), str);
0842     QCryptographicHash context(QCryptographicHash::Md5);
0843     context.addData(QString(_playerInfos->registeredName() + str).toLatin1());
0844     Manager::addToQueryURL(url, QStringLiteral( "check" ), QLatin1String( context.result().toHex() ));
0845 
0846     return doQuery(url, widget);
0847 }
0848 
0849 void ManagerPrivate::exportHighscores(QTextStream &s)
0850 {
0851     uint tmp = _gameType;
0852 
0853     for (uint i=0; i<_nbGameTypes; i++) {
0854         setGameType(i);
0855         if ( _nbGameTypes>1 ) {
0856             if ( i!=0 ) s << Qt::endl;
0857             s << "--------------------------------" << Qt::endl;
0858             s << "Game type: "
0859               << manager.gameTypeLabel(_gameType, Manager::I18N) << Qt::endl;
0860             s << Qt::endl;
0861         }
0862         s << "Players list:" << Qt::endl;
0863         _playerInfos->exportToText(s);
0864         s << Qt::endl;
0865         s << "Highscores list:" << Qt::endl;
0866         _scoreInfos->exportToText(s);
0867     }
0868 
0869     setGameType(tmp);
0870 }
0871 
0872 } // namespace