Files
commandergenius/project/jni/application/enigma/src/lev/PersistentIndex.cpp
T
2010-10-15 14:23:30 +03:00

1103 lines
48 KiB
C++

/*
* Copyright (C) 2006, 2007 Ronald Lamprecht
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "lev/PersistentIndex.hh"
#include "lev/Proxy.hh"
#include "lev/RatingManager.hh"
#include "errors.hh"
#include "gui/ErrorMenu.hh"
#include "main.hh"
#include "nls.hh"
#include "file.hh"
#include "options.hh"
#include "oxyd.hh"
#include "LocalToXML.hh"
#include "utilXML.hh"
#include "Utf8ToXML.hh"
#include "XMLtoUtf8.hh"
#include "ecl_system.hh"
#include <fstream>
#include <iostream>
#include <sstream>
#include <set>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/util/XMLException.hpp>
#include <xercesc/util/XMLDouble.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/util/XMLUniDefs.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/framework/Wrapper4InputSource.hpp>
#include <xercesc/util/XercesVersion.hpp>
#if _XERCES_VERSION < 30000
#include <xercesc/framework/LocalFileFormatTarget.hpp>
#endif
using namespace std;
XERCES_CPP_NAMESPACE_USE
namespace enigma { namespace lev {
Variation::Variation(controlType ctrlValue, scoreUnitType unitValue,
std::string targetValue) : ctrl (ctrlValue),
unit (unitValue), target (targetValue) {
}
bool Variation::operator == (const Variation& otherVar) {
return ctrl == otherVar.ctrl && unit == otherVar.unit &&
target == otherVar.target && extensions == otherVar.extensions;
}
PersistentIndex * PersistentIndex::historyIndex = NULL;
std::vector<PersistentIndex *> PersistentIndex::indexCandidates;
void PersistentIndex::checkCandidate(PersistentIndex * candidate) {
if (candidate->getName().empty() ||
candidate->getCompatibility() > ENIGMACOMPATIBITLITY) {
delete candidate;
} else {
// check if new Index is an update of another
for (int i = 0; i < indexCandidates.size(); i++) {
if (indexCandidates[i]->getName() != candidate->getName()) {
continue;
} else if (indexCandidates[i]->getRelease() != candidate->getRelease() ||
indexCandidates[i]->getRevision() >= candidate->getRevision()) {
delete candidate;
return;
} else {
// it is an update
delete indexCandidates[i];
indexCandidates[i] = candidate;
return;
}
}
indexCandidates.push_back(candidate);
}
}
void PersistentIndex::registerPersistentIndices(bool onlySystemIndices) {
DirIter * dirIter;
DirEntry dirEntry;
// SysemPath: register dirs and zips with xml-indices
std::vector<std::string> sysPaths = app.systemFS->getPaths();
std::set<std::string> candidates;
std::set<std::string> candidates2;
for (int i = 0; i < sysPaths.size(); i++) {
dirIter = DirIter::instance(sysPaths[i] + "/levels");
while (dirIter->get_next(dirEntry)) {
if (dirEntry.is_dir && dirEntry.name != "." && dirEntry.name != ".." &&
dirEntry.name != ".svn" && dirEntry.name != "enigma_cross") {
candidates.insert(dirEntry.name);
}
else {
std::string::size_type zipPos = dirEntry.name.rfind(".zip");
if (zipPos != std::string::npos && zipPos == dirEntry.name.size() - 4) {
candidates.insert(dirEntry.name.substr(0, dirEntry.name.size() - 4));
}
}
}
delete dirIter;
}
for (std::set<std::string>::iterator i = candidates.begin();
i != candidates.end(); i++) {
// register the index just on the system path even if the
// user has an update on his path. We need to know the release to
// decide if a user copy is an update
PersistentIndex * anIndex = new PersistentIndex(*i, true);
anIndex->isUserOwned = false;
Log << "precheck: " << *i << "\n";
checkCandidate(anIndex);
}
//add system cross indices
for (int i = 0; i < sysPaths.size(); i++) {
dirIter = DirIter::instance(sysPaths[i] + "/levels/enigma_cross");
while (dirIter->get_next(dirEntry)) {
if (!dirEntry.is_dir && dirEntry.name.size() > 4 &&
(dirEntry.name.rfind(".xml") == dirEntry.name.size() - 4)) {
PersistentIndex * anIndex = new PersistentIndex("enigma_cross", true,
INDEX_DEFAULT_PACK_LOCATION, "", dirEntry.name);
anIndex->isUserOwned = false;
checkCandidate(anIndex);
}
}
}
delete dirIter;
if (onlySystemIndices)
return;
// UserPath: register dirs and zips with xml-indices excl auto
dirIter = DirIter::instance(app.userPath + "/levels");
while (dirIter->get_next(dirEntry)) {
if (dirEntry.is_dir && dirEntry.name != "." && dirEntry.name != ".." &&
dirEntry.name != ".svn" && dirEntry.name != "auto" &&
dirEntry.name != "cross" && dirEntry.name != "enigma_cross" &&
dirEntry.name != "legacy_dat") {
candidates2.insert(dirEntry.name);
}
else {
std::string::size_type zipPos = dirEntry.name.rfind(".zip");
if (zipPos != std::string::npos && zipPos == dirEntry.name.size() - 4) {
candidates2.insert(dirEntry.name.substr(0, dirEntry.name.size() - 4));
}
}
}
delete dirIter;
candidates2.insert("");
#ifdef __MINGW32__
// eliminate logical duplicates as Windows does not distinguish
// upper and lower case filenames but we can have e.g. an uppercase zip
// and a lower case dir
candidates2 = ecl::UniqueFilenameSet(candidates2);
#endif
for (std::set<std::string>::iterator i = candidates2.begin();
i != candidates2.end(); i++) {
PersistentIndex * anIndex = new PersistentIndex(*i, false);
checkCandidate(anIndex);
}
//add user cross indices
dirIter = DirIter::instance(app.userPath + "/levels/cross");
while (dirIter->get_next(dirEntry)) {
if (!dirEntry.is_dir && dirEntry.name.size() > 4 &&
(dirEntry.name.rfind(".xml") == dirEntry.name.size() - 4)) {
PersistentIndex * anIndex = new PersistentIndex("cross", false,
INDEX_DEFAULT_PACK_LOCATION, "", dirEntry.name);
checkCandidate(anIndex);
}
}
delete dirIter;
for (int i = 0; i < indexCandidates.size(); i++) {
Index::registerIndex(indexCandidates[i]);
}
// register auto not yet registered new files
PersistentIndex * autoIndex = new PersistentIndex("auto", false,
INDEX_AUTO_PACK_LOCATION, INDEX_AUTO_PACK_NAME);
autoIndex->isEditable = false;
dirIter = DirIter::instance(app.userPath + "/levels/auto");
while (dirIter->get_next(dirEntry)) {
if( !dirEntry.is_dir) {
if (dirEntry.name.size() > 4 && (
(dirEntry.name.rfind(".xml") == dirEntry.name.size() - 4) ||
(dirEntry.name.rfind(".lua") == dirEntry.name.size() - 4))) {
Proxy * newProxy = Proxy::autoRegisterLevel("auto",
dirEntry.name.substr(0, dirEntry.name.size() - 4));
if (newProxy != NULL) {
// first check that the proxy is not in the index
// - may occur if the level is stored as .xml and .lua in the folder
if (!autoIndex->containsProxy(newProxy)) {
// it is new, add it
autoIndex->appendProxy(newProxy);
}
}
}
}
}
delete dirIter;
Index::registerIndex(autoIndex);
// check if history is available - else generate a new index
Index * foundHistory = Index::findIndex("History");
if ( foundHistory != NULL) {
historyIndex = dynamic_cast<PersistentIndex *>(foundHistory);
} else {
historyIndex = new PersistentIndex("cross", false, INDEX_HISTORY_PACK_LOCATION,
INDEX_HISTORY_PACK_NAME, "history.xml");
Index::registerIndex(historyIndex);
}
historyIndex->isEditable = false;
}
void PersistentIndex::addCurrentToHistory() {
Variation var;
Proxy * curProxy = Index::getCurrentProxy();
// remember all but commandline absolute and relative paths
if (curProxy->getNormPathType() != Proxy::pt_absolute) {
PersistentIndex * curIndex = dynamic_cast<PersistentIndex *>(Index::getCurrentIndex());
if (curIndex != NULL)
var = curIndex->getVariation(curIndex->getCurrentPosition());
historyIndex->insertProxy(0, curProxy, false, var.ctrl, var.unit,
var.target, var.extensions);
if (historyIndex->size() > 100)
historyIndex->erase(historyIndex->size() - 1);
historyIndex->setCurrentPosition(0); // last played is always current in history
}
}
PersistentIndex::PersistentIndex(std::string thePackPath, bool systemOnly,
double defaultLocation, std::string anIndexName,
std::string theIndexFilename, std::string aGroupName) :
Index(anIndexName, aGroupName, defaultLocation), packPath (thePackPath),
indexFilename(theIndexFilename), isModified (false),
isUserOwned (true), isEditable (true), release (1), revision (1),
compatibility (1.00), doc(NULL) {
// Log << "PersistentIndex AddLevelPack " << thePackPath << " - " << anIndexName << " - " << indexDefaultLocation <<"\n";
load(systemOnly);
}
void PersistentIndex::load(bool systemOnly, bool update) {
if (doc != NULL) {
doc->release();
doc = NULL;
}
// auto and new levelpacks are not loadable
if (packPath == " " || packPath == "auto")
return; // as long as Auto is not editable
std::auto_ptr<std::istream> isptr;
ByteVec indexCode;
std::string errMessage;
absIndexPath = "";
std::string relIndexPath = "levels/" + packPath + "/" + indexFilename;
if ((!systemOnly && app.resourceFS->findFile(relIndexPath, absIndexPath, isptr)) ||
(systemOnly && app.systemFS->findFile(relIndexPath, absIndexPath, isptr))) {
// preload index file or zipped index
if (isptr.get() != NULL) {
// zipped file
Readfile (*isptr, indexCode);
} else {
// plain file
std::basic_ifstream<char> ifs(absIndexPath.c_str(), ios::binary | ios::in);
Readfile(ifs, indexCode);
}
try {
std::ostringstream errStream;
app.domParserErrorHandler->resetErrors();
app.domParserErrorHandler->reportToOstream(&errStream);
app.domParserSchemaResolver->resetResolver();
app.domParserSchemaResolver->addSchemaId("index.xsd","index.xsd");
if (update) {
// local xml file or URL
doc = app.domParser->parseURI(indexUrl.c_str());
} else {
// preloaded xml or zipped xml
#if _XERCES_VERSION >= 30000
std::auto_ptr<DOMLSInput> domInputIndexSource ( new Wrapper4InputSource(
new MemBufInputSource(reinterpret_cast<const XMLByte *>(&(indexCode[0])),
indexCode.size(), absIndexPath.c_str(), false)));
doc = app.domParser->parse(domInputIndexSource.get());
#else
std::auto_ptr<Wrapper4InputSource> domInputIndexSource ( new Wrapper4InputSource(
new MemBufInputSource(reinterpret_cast<const XMLByte *>(&(indexCode[0])),
indexCode.size(), absIndexPath.c_str(), false)));
doc = app.domParser->parse(*domInputIndexSource);
#endif
}
if (doc != NULL && !app.domParserErrorHandler->getSawErrors()) {
infoElem = reinterpret_cast<DOMElement *>(doc->getElementsByTagName(
Utf8ToXML("info").x_str())->item(0));
updateElem = reinterpret_cast<DOMElement *>(doc->getElementsByTagName(
Utf8ToXML("update").x_str())->item(0));
levelsElem = reinterpret_cast<DOMElement *>(doc->getElementsByTagName(
Utf8ToXML("levels").x_str())->item(0));
}
if(app.domParserErrorHandler->getSawErrors()) {
errMessage = errStream.str();
}
app.domParserErrorHandler->reportToNull(); // do not report to errStream any more
}
catch (...) {
errMessage = "Unexpected XML Exception on load of index\n";
}
if (!errMessage.empty()) {
Log << errMessage; // make long error messages readable
std::string message;
if (update) {
message = _("Error on update of levelpack index: \n");
message += absIndexPath + "\n\n";
message += _("Note: the current version will be reloaded!\n\n");
message += errMessage;
} else {
if (doc != NULL) {
doc->release(); // empty or errornous doc
doc = NULL;
}
message = _("Error on registration of levelpack index: \n");
message += absIndexPath + "\n\n";
message += _("Note: the levelpack will not show up!\n\n");
message += errMessage;
}
gui::ErrorMenu m(message, N_("Continue"));
m.manage();
if (update) {
load(systemOnly, false); // reload local version
}
return;
} else if (doc != NULL) {
//TODO check if an updated index exists for system packs
loadDoc();
}
}
}
void PersistentIndex::loadDoc() {
if (doc != NULL) {
clear(); // allow a reload of an index
indexName = XMLtoUtf8(infoElem->getAttribute(
Utf8ToXML("title").x_str())).c_str();
indexGroup = XMLtoUtf8(infoElem->getAttribute(
Utf8ToXML("group").x_str())).c_str();
defaultGroup = indexGroup;
owner = XMLtoUtf8(infoElem->getAttribute(
Utf8ToXML("owner").x_str())).c_str();
release = XMLString::parseInt(infoElem->getAttribute(
Utf8ToXML("release").x_str()));
revision = XMLString::parseInt(infoElem->getAttribute(
Utf8ToXML("revision").x_str()));
XMLDouble * result = new XMLDouble(infoElem->getAttribute(
Utf8ToXML("enigma").x_str()));
compatibility = result->getValue();
delete result;
result = new XMLDouble(infoElem->getAttribute(
Utf8ToXML("location").x_str()));
indexDefaultLocation = result->getValue();
indexLocation = indexDefaultLocation;
delete result;
if (updateElem != NULL) {
indexUrl = XMLtoUtf8(updateElem->getAttribute(
Utf8ToXML("indexurl").x_str())).c_str();
}
DOMNodeList *levelList = levelsElem->getElementsByTagName(
Utf8ToXML("level").x_str());
std::set<std::string> knownAttributes;
knownAttributes.insert("_seq");
knownAttributes.insert("_title");
knownAttributes.insert("_xpath");
knownAttributes.insert("id");
knownAttributes.insert("author");
knownAttributes.insert("score");
knownAttributes.insert("rel");
knownAttributes.insert("rev");
knownAttributes.insert("easy");
knownAttributes.insert("ctrl");
knownAttributes.insert("unit");
knownAttributes.insert("target");
for (int i = 0, l = levelList->getLength(); i < l; i++) {
DOMElement *levelElem = reinterpret_cast<DOMElement *>(levelList->item(i));
std::string path = XMLtoUtf8(levelElem->getAttribute(
Utf8ToXML("_xpath").x_str())).c_str();
std::string id = XMLtoUtf8(levelElem->getAttribute(
Utf8ToXML("id").x_str())).c_str();
std::string title = XMLtoUtf8(levelElem->getAttribute(
Utf8ToXML("_title").x_str())).c_str();
std::string author = XMLtoUtf8(levelElem->getAttribute(
Utf8ToXML("author").x_str())).c_str();
int scoreVersion = XMLString::parseInt(levelElem->getAttribute(
Utf8ToXML("score").x_str()));
int releaseVersion = XMLString::parseInt(levelElem->getAttribute(
Utf8ToXML("rel").x_str()));
int revisionVersion = XMLString::parseInt(levelElem->getAttribute(
Utf8ToXML("rev").x_str()));
bool hasEasymodeFlag = boolValue(levelElem->getAttribute(
Utf8ToXML("easy").x_str()));
Proxy * newProxy = Proxy::registerLevel(path, packPath, id, title,
author, scoreVersion, releaseVersion, hasEasymodeFlag,
GAMET_ENIGMA, STATUS_RELEASED, revisionVersion);
Variation var;
std::string controlString = XMLtoUtf8(levelElem->getAttribute(
Utf8ToXML("ctrl").x_str())).c_str();
if (controlString == "balance")
var.ctrl = balance;
else if (controlString == "key")
var.ctrl = key;
else if (controlString == "other")
var.ctrl = other;
std::string txt = XMLtoUtf8(levelElem->getAttribute(
Utf8ToXML("unit").x_str())).c_str();
if (txt == "number")
var.unit = number;
else
// default
var.unit = duration;
var.target = XMLtoUtf8(levelElem->getAttribute(
Utf8ToXML("target").x_str())).c_str();
DOMNamedNodeMap * attrMap = levelElem->getAttributes();
for (int j = 0, k = attrMap->getLength(); j < k; j++) {
DOMAttr * levelAttr = reinterpret_cast<DOMAttr *>(attrMap->item(j));
std::string attrName = XMLtoUtf8(levelAttr->getName()).c_str();
if (knownAttributes.find(attrName) == knownAttributes.end()) {
Log << "PersistentIndex Load unknown Attribut: " << attrName << "\n";
var.extensions[attrName]= XMLtoUtf8(levelAttr->getValue()).c_str();
}
}
appendProxy(newProxy, var.ctrl, var.unit, var.target, var.extensions);
}
}
}
PersistentIndex::~PersistentIndex() {
if (doc != NULL)
doc->release();
}
std::string PersistentIndex::getPackPath() {
return packPath;
}
bool PersistentIndex::setName(std::string newName) {
if (findIndex(newName) != NULL)
return false; // do not allow duplicate names
// substitute spaces by underscores
std::string fileName = newName;
std::string::size_type pos = fileName.find_first_of(' ', 0);
while (pos != std::string::npos) {
fileName.replace(pos, 1,"_");
pos = fileName.find_first_of(' ', pos+1);
}
if (packPath == " " || (packPath == "cross" && indexFilename == INDEX_STD_FILENAME)) {
// generate usabale path name and check it it usable
if (fileName == "cross" || fileName == "enigma_cross" ||
fileName == "legacy_dat" || fileName == "auto" ||
fileName == "history") {
return false;
}
// check if the name would conflict with existing files
std::auto_ptr<std::istream> isptr; // dummy
absIndexPath = "";
std::string relIndexPath1 = "levels/" + fileName + "/" + INDEX_STD_FILENAME;
std::string relIndexPath2 = "levels/cross/" + fileName + ".xml";
if (app.resourceFS->findFile(relIndexPath1, absIndexPath, isptr) ||
app.resourceFS->findFile(relIndexPath2, absIndexPath, isptr)) {
return false;
}
if (packPath == " ") {
packPath = fileName;
indexFilename = INDEX_STD_FILENAME;
} else
indexFilename = fileName + ".xml";
}
if (!getName().empty())
renameIndex(newName); // Index maps
else
// a new unregistered levelpack is named the first time - just set the name
indexName = newName;
return true;
}
bool PersistentIndex::isUpdatable() {
return (updateElem != NULL) && !indexUrl.empty();
}
bool PersistentIndex::isCross() {
return packPath == "cross" || packPath == "enigma_cross";
}
void PersistentIndex::markNewAsCross(){
if (packPath == " ")
packPath = "cross";
}
std::string PersistentIndex::getOwner() {
return owner;
}
void PersistentIndex::setOwner(std::string newOwner) {
owner = newOwner;
}
int PersistentIndex::getRelease() {
return release;
}
void PersistentIndex::setRelease(int newRelease) {
release = newRelease;
}
int PersistentIndex::getRevision() {
return revision;
}
void PersistentIndex::setRevision(int newRevision) {
revision = newRevision;
}
double PersistentIndex::getCompatibility() {
return compatibility;
}
void PersistentIndex::setCompatibility(double newCompatibility) {
compatibility = newCompatibility;
}
bool PersistentIndex::isUserEditable() {
return isEditable && (isUserOwned || WizardMode);
}
void PersistentIndex::clear() {
proxies.clear();
variations.clear();
currentPosition = 0;
}
void PersistentIndex::appendProxy(Proxy * newLevel, controlType varCtrl,
scoreUnitType varUnit, std::string varTarget,
std::map<std::string, std::string> varExtensions) {
proxies.push_back(newLevel);
Variation var(varCtrl, varUnit, varTarget);
var.extensions = varExtensions;
variations.push_back(var);
}
void PersistentIndex::insertProxy(int pos, Proxy * newLevel, bool allowDuplicates,
controlType varCtrl, scoreUnitType varUnit, std::string varTarget,
std::map<std::string, std::string> varExtensions) {
// TODO Assert pos >= size
Variation var(varCtrl, varUnit, varTarget);
var.extensions = varExtensions;
std::vector<Proxy *>::iterator itProxy = proxies.begin();
std::vector<Variation>::iterator itVar = variations.begin();
if (!allowDuplicates) {
// delete duplicates
while (itProxy != proxies.end()) {
if (*itProxy == newLevel && *itVar == var) {
// Log << "History Duplicat found\n";
itProxy = proxies.erase(itProxy);
itVar = variations.erase(itVar);
}
if (itProxy != proxies.end())
itProxy++; itVar++;
}
}
itProxy = proxies.begin();
itVar = variations.begin();
for (int i = 0; i < pos; i++) {
itProxy++; itVar++;
}
proxies.insert(itProxy, newLevel);
variations.insert(itVar, var);
}
Variation PersistentIndex::getVariation(int pos) {
// TODO Assert pos
return variations[pos];
}
void PersistentIndex::erase(int pos) {
std::vector<Proxy *>::iterator itProxy = proxies.begin();
std::vector<Variation>::iterator itVar = variations.begin();
for (int i = 0; i < pos; i++) {
itProxy++; itVar++;
}
proxies.erase(itProxy);
variations.erase(itVar);
}
void PersistentIndex::exchange(int pos1, int pos2) {
Proxy * proxy = proxies[pos1];
proxies[pos1] = proxies[pos2];
proxies[pos2] = proxy;
Variation var = variations[pos1];
variations[pos1] = variations[pos2];
variations[pos2] = var;
if (getCurrentPosition() == pos1)
setCurrentPosition(pos2);
else if (getCurrentPosition() == pos2)
setCurrentPosition(pos1);
}
bool PersistentIndex::isSource(Proxy * aProxy) {
std::string proxyPath = aProxy->getNormLevelPath();
if (proxyPath[0] == '#')
// Oxyd reference
return false;
else if (packPath.empty()) {
// old levelpack on levels directory
if (proxyPath.find('/') == std::string::npos)
return true;
else
return false;
} else {
if (proxyPath.find(packPath + "/") == 0)
return true;
else
return false;
}
}
bool PersistentIndex::save(bool allowOverwrite) {
bool result = true;
if (doc == NULL) {
std::string errMessage;
std::string indexTemplatePath;
if (app.systemFS->findFile( "schemas/index.xml" , indexTemplatePath)) {
try {
std::ostringstream errStream;
app.domParserErrorHandler->resetErrors();
app.domParserErrorHandler->reportToOstream(&errStream);
app.domParserSchemaResolver->resetResolver();
app.domParserSchemaResolver->addSchemaId("index.xsd","index.xsd");
doc = app.domParser->parseURI(indexTemplatePath.c_str());
if (doc != NULL && !app.domParserErrorHandler->getSawErrors()) {
infoElem = reinterpret_cast<DOMElement *>(doc->getElementsByTagName(
Utf8ToXML("info").x_str())->item(0));
levelsElem = reinterpret_cast<DOMElement *>(doc->getElementsByTagName(
Utf8ToXML("levels").x_str())->item(0));
}
if(app.domParserErrorHandler->getSawErrors()) {
errMessage = errStream.str();
}
app.domParserErrorHandler->reportToNull(); // do not report to errStream any more
}
catch (...) {
errMessage = "Unexpected XML Exception on load of index\n";
}
if (!errMessage.empty()) {
if (doc != NULL) {
doc->release(); // empty or errornous doc
doc = NULL;
}
Log << errMessage; // make long error messages readable
return false;
}
} else
// TODO add error handling
return false;
}
//
infoElem->setAttribute( Utf8ToXML("title").x_str(),
Utf8ToXML(&indexName).x_str());
infoElem->setAttribute( Utf8ToXML("group").x_str(),
Utf8ToXML(&defaultGroup).x_str());
infoElem->setAttribute( Utf8ToXML("owner").x_str(),
Utf8ToXML(&owner).x_str());
infoElem->setAttribute( Utf8ToXML("release").x_str(),
Utf8ToXML(ecl::strf("%d",release)).x_str());
infoElem->setAttribute( Utf8ToXML("revision").x_str(),
Utf8ToXML(ecl::strf("%d",revision)).x_str());
infoElem->setAttribute( Utf8ToXML("enigma").x_str(),
Utf8ToXML(ecl::strf("%.2f",compatibility)).x_str());
infoElem->setAttribute( Utf8ToXML("location").x_str(),
Utf8ToXML(ecl::strf("%g",indexDefaultLocation)).x_str());
DOMNodeList *levelsChildList = levelsElem->getChildNodes();
int levelsChildCount = levelsChildList->getLength();
// delete no more used level elements
for (int i = 0; i < levelsChildCount; i++) {
// delete all elements of list, as the list is dynamically updated -
// we can not recycle level elements as they may be reordered and
// their attributes are not identical
levelsElem->removeChild(levelsChildList->item(0));
}
DOMElement * levelElem;
// add level elements
for (int i = 0; i < size(); i++) {
// add a new element
levelElem = doc->createElement(Utf8ToXML("level").x_str());
levelsElem->appendChild(levelElem); // insert it at the end
Proxy * level = getProxy(i);
// convert Proxy normLevelPath to pack local path ./* if possible
std::string xpath = level->getNormLevelPath();
if (xpath.find(packPath + "/") == 0)
xpath = "." + xpath.substr(packPath.size());
else if (packPath.empty() && xpath.find("/") == std::string::npos)
xpath = "./" + xpath;
levelElem->setAttribute( Utf8ToXML("_xpath").x_str(),
Utf8ToXML(xpath).x_str());
levelElem->setAttribute( Utf8ToXML("id").x_str(),
Utf8ToXML(level->getId()).x_str());
levelElem->setAttribute( Utf8ToXML("_title").x_str(),
Utf8ToXML(level->getTitle()).x_str());
levelElem->setAttribute( Utf8ToXML("author").x_str(),
Utf8ToXML(level->getAuthor()).x_str());
levelElem->setAttribute( Utf8ToXML("score").x_str(),
Utf8ToXML(ecl::strf("%d",level->getScoreVersion())).x_str());
levelElem->setAttribute( Utf8ToXML("rel").x_str(),
Utf8ToXML(ecl::strf("%d",level->getReleaseVersion())).x_str());
levelElem->setAttribute( Utf8ToXML("rev").x_str(),
Utf8ToXML(ecl::strf("%d",level->getRevisionNumber())).x_str());
levelElem->setAttribute( Utf8ToXML("easy").x_str(),
Utf8ToXML(level->hasEasymode() ? "true" : "false").x_str());
std::string control;
switch (variations[i].ctrl) {
case lev::force:
control = "force"; break;
case lev::balance:
control = "balance"; break;
case lev::key:
control = "key"; break;
default:
control = "other"; break;
}
levelElem->setAttribute( Utf8ToXML("ctrl").x_str(),
Utf8ToXML(control).x_str());
std::string unit;
switch (variations[i].unit) {
case lev::duration:
unit = "duration"; break;
case lev::number:
unit = "number"; break;
}
levelElem->setAttribute( Utf8ToXML("unit").x_str(),
Utf8ToXML(unit).x_str());
levelElem->setAttribute( Utf8ToXML("target").x_str(),
Utf8ToXML(variations[i].target).x_str());
for (std::map<std::string, std::string>::iterator j= variations[i].extensions.begin();
j != variations[i].extensions.end(); j++) {
// Log << "Persistent save extension: " << (*j).first << " - " << (*j).second << "\n";
levelElem->setAttribute( Utf8ToXML((*j).first).x_str(),
Utf8ToXML((*j).second).x_str());
}
}
// update the sequence number of the levels
DOMNodeList *levList = levelsElem->getElementsByTagName(
Utf8ToXML("level").x_str());
for (int i = 0, l = levList-> getLength(); i < l; i++) {
DOMElement *levElem = reinterpret_cast<DOMElement *>(levList->item(i));
levElem->setAttribute( Utf8ToXML("_seq").x_str(),
Utf8ToXML(ecl::strf("%d",i+1)).x_str());
}
stripIgnorableWhitespace(doc->getDocumentElement());
std::string path = app.userPath + "/levels/" + packPath + "/" + indexFilename;
if (allowOverwrite || !ecl::FileExists(path)) {
try {
// auto-create the directory if necessary
std::string directory;
if (ecl::split_path (path, &directory, 0) && !ecl::FolderExists(directory)) {
ecl::FolderCreate (directory);
}
#if _XERCES_VERSION >= 30000
result = app.domSer->writeToURI(doc, LocalToXML(& path).x_str());
#else
XMLFormatTarget *myFormTarget = new LocalFileFormatTarget(path.c_str());
result = app.domSer->writeNode(myFormTarget, *doc);
delete myFormTarget; // flush
#endif
}
catch (const XMLException& toCatch) {
char* message = XMLString::transcode(toCatch.getMessage());
cerr << "Exception on save of index: "
<< message << "\n";
XMLString::release(&message);
result = false;
}
catch (const DOMException& toCatch) {
char* message = XMLString::transcode(toCatch.msg);
cerr << "Exception on save of index: "
<< message << "\n";
XMLString::release(&message);
result = false;
}
catch (...) {
cerr << "Unexpected exception on save of index\n" ;
result = false;
}
if (!result)
Log << "Index save fault on " << path << " \n";
else
Log << "Index save " << path << " o.k.\n";
} else
result = false;
return result;
}
PersistentIndex::PersistentIndex(std::istream *legacyIndexStream,
std::string thePackPath, bool isZip, std::string anIndexName,
std::string theIndexFilename) :
Index(anIndexName, INDEX_DEFAULT_GROUP, Index::getNextUserLocation()),
indexFilename(theIndexFilename), isModified (false),
isUserOwned (true), isEditable (true), release (1), revision (1),
compatibility (1.00), doc(NULL) {
Log << "PersistentIndex convert 0.92 index " << thePackPath << " - " << anIndexName <<"\n";
lev::RatingManager *theRatingMgr = lev::RatingManager::instance();
// prepare Proxy coding of pack path -
if (thePackPath == "levels")
packPath = "";
else {
// cut off leading "levels/"
packPath = thePackPath.substr(7);
}
int linenumber = 0;
try {
std::string line;
while (std::getline(*legacyIndexStream, line)) {
using namespace std;
using namespace ecl;
string filename = ""; //< Filename of the level (exl. extension)
string indexname = ""; //< The name used in the options file,
// empty string -> use filename entry
string name = ""; //< Complete name of the level
string author = ""; //< Author of the level
int revision = 1; //< Revision # of this level
bool has_easymode = false; //< whether level has an easymode
int par_time_easy = -1 ; //< Best time in seconds (for easy mode)
int par_time_normal = -1; //< Best time in seconds (for normal mode)
string par_time_easy_by = ""; //< player name(s) for 'best_time_easy'
string par_time_normal_by = ""; //< same for 'best_time_normal'
int par_moves = -1; //< Minimum moves to solve level
int intelligence = 0;
int dexterity = 0;
int patience = 0;
int knowledge = 0 ;
int speed = 0;
++linenumber;
const char *wspace = " \t";
size_t p = line.find_first_not_of(wspace);
if (p == string::npos || line.at(p) != '{')
continue;
// found a level description
int par_time = -1;
string par_time_by;
string par_moves_by;
++p;
while (true) {
string tag;
string content;
// ugly parser code starts here.
// - it parses 'tag = content' or 'tag = "content"'
// - '\"' is allowed in '"content"'
// - whitespace is ignored
// - sets message and breaks while loop in case of error
size_t tag_start = line.find_first_not_of(wspace, p);
Assert <XLevelPackInit> (tag_start != string::npos, "Expected tag or '}'");
if (line.at(tag_start) == '}') break; // line done
size_t equal = line.find('=', tag_start);
Assert <XLevelPackInit> (equal != string::npos, "'=' expected");
Assert <XLevelPackInit> (equal != tag_start, "empty tag");
size_t tag_end = line.find_last_not_of(wspace, equal-1);
tag = line.substr(tag_start, tag_end-tag_start+1);
size_t content_start = line.find_first_not_of(wspace, equal+1);
if (line.at(content_start) == '\"') { // content in ""
size_t oquote = line.find('\"', content_start+1);
bool have_escapes = false;
while (oquote != string::npos && line.at(oquote-1) == '\\') { // step over \"
oquote = line.find('\"', oquote+1);
have_escapes = true;
}
Assert <XLevelPackInit> (oquote != string::npos, "unmatched quote");
content = line.substr(content_start+1, oquote-content_start-1);
if (have_escapes) {
size_t esc;
while ((esc = content.find("\\\"")) != string::npos)
content.replace(esc, 2, "\"");
}
p = oquote+1;
}
else { // content w/o ""
size_t content_end = line.find_first_of(" \t}", content_start);
Assert <XLevelPackInit> (content_end != string::npos, "expected space or } behind content");
content = line.substr(content_start, content_end-content_start);
p = content_end;
}
if (tag == "file") filename = content;
else if (tag == "indexname")indexname = content;
else if (tag == "name") name = content;
else if (tag == "author") author = content;
else if (tag == "revision") revision = atoi(content.c_str());
else if (tag == "easymode") has_easymode = (content == "1");
else if (tag == "int") intelligence = atoi(content.c_str());
else if (tag == "dex") dexterity = atoi(content.c_str());
else if (tag == "pat") patience = atoi(content.c_str());
else if (tag == "kno") knowledge = atoi(content.c_str());
else if (tag == "spe") speed = atoi(content.c_str());
else if (tag == "par_time") parsePar(content, par_time, par_time_by);
else if (tag == "par_time_easy") parsePar(content, par_time_easy, par_time_easy_by);
else if (tag == "par_time_normal") parsePar(content, par_time_normal, par_time_normal_by);
else if (tag == "par_moves") parsePar(content, par_moves, par_moves_by);
// else if (tag == "hint1") hint1 = content;
// else if (tag == "hint2") hint2 = content;
else
throw XLevelPackInit(strf("unknown tag '%s'", tag.c_str()));
}
Assert <XLevelPackInit> (filename.length() != 0,
"mandatory tag 'file' missing");
if (has_easymode) {
Assert <XLevelPackInit> (par_time == -1,
"'par_time' not allowed when easymode=1 "
"(use 'par_time_easy' and 'par_time_normal')");
}
else {
Assert <XLevelPackInit> (par_time_normal==-1 && par_time_easy==-1,
"'par_time_easy' and 'par_time_normal' are not allowed when easymode=0 (use 'par_time')");
par_time_easy = par_time_normal = par_time;
par_time_easy_by = par_time_normal_by = par_time_by;
}
// correct filename for zip
if (isZip) {
Log << "Zip " << thePackPath << " - " <<packPath << " - " << indexname << "\n";
if (filename.find('/') == std::string::npos) {
// it is an old zip file with plain levelnames as filename
if (indexname.empty()) {
// keep id
indexname = filename;
}
// add the zip basename to get the complete path to the level
filename = packPath + "/" + filename;
}
} else if (linenumber == 1 && packPath.empty()) {
// a std index.txt on levels dir - we need to guess location of pack
// we take the dir of the first level
std::string::size_type n = filename.rfind('/');
if (n != std::string::npos) {
packPath = filename.substr(0, n);
// Log << "Index convert pack '" << anIndexName << "' - guess pack directory is '"
// << packPath << "'\n";
}
}
// register Proxy
appendProxy(Proxy::registerLevel(filename, packPath,
(indexname.empty() ? filename : indexname), name, author,
revision, revision, has_easymode, GAMET_ENIGMA));
theRatingMgr->registerRating((indexname.empty() ? filename : indexname),
revision, intelligence, dexterity, patience, knowledge, speed,
par_time_easy, par_time_easy_by, par_time_normal, par_time_normal_by);
}
} catch (const XLevelPackInit &e) {
std::string xerror = ecl::strf("in line %i: %s", linenumber, e.what());
throw XLevelPackInit (xerror);
}
// convert to XML
updateFromProxies();
// save but do not overwrite existing index.xml - would be a second conversion,
// but the user may already have modified the index.xml
save(false);
}
void PersistentIndex::parsePar(const string& par, int& par_value, std::string& par_text)
{
// 'par' is in format "value,text"
// The value is stored in 'par_value' and the text in 'par_text'.
using namespace std;
using namespace ecl;
size_t comma = par.find(',');
Assert<XLevelPackInit> (comma!=string::npos, "Comma expected in par");
string value = par.substr(0, comma);
par_text = par.substr(comma+1);
par_value = atoi(value.c_str());
}
void AddLevelPack (const char *init_file, const char *indexName) {
// Log << "Index AddLevelPack " << init_file << "\n";
if (Index::findIndex(indexName) == NULL) {
std::string absPath;
if (app.resourceFS->findFile(init_file, absPath)) {
try {
std::string path = init_file;
std::string dir = "";
std::string filename = "";
ecl::split_path(path, &dir, &filename);
std::ifstream is(absPath.c_str());
if (!is)
throw XLevelPackInit ("Cannot open index file");
Index::registerIndex(new PersistentIndex(&is, dir, false, indexName));
} catch (const XLevelPackInit &e) {
Log << e.get_string() << "\n";
}
} else {
Log << "Could not find level index file `" << init_file << "'\n";
}
} else {
// Log << "Ignored index file `" << init_file << "' - already converted to XML\n";
}
}
void AddZippedLevelPack (const char *zipfile) {
// Log << "Index AddZippedLevelPack " << zipfile << "\n";
using namespace std;
using namespace ecl;
string absPath;
if (app.resourceFS->findFile (zipfile, absPath)) {
// the index file as it would be for a unpacked zip
std::string zf = zipfile;
std::string dir = zf.substr(0, zf.rfind('.'));
std::string indexfile = dir + "/index.txt";
try {
auto_ptr<istream> isptr;
std::string dummy;
if(!app.resourceFS->findFile(indexfile, dummy, isptr))
throw XLevelPackInit ("No index in level pack: ");
istream &is = *isptr;
string line;
std::string indexName;
if (getline(is, line)) {
// we read the index in binary mode and have to strip of the \n for
// windows
if (line[line.size()-1] = '\n') {
line.resize(line.size()-1);
}
indexName = line;
// check if already loaded
Index::registerIndex(new PersistentIndex(isptr.get(), dir, true, indexName));
}
else {
throw XLevelPackInit ("Invalid level pack: " + indexName);
}
}
catch (std::exception &) {
throw XLevelPackInit ("Error reading from .zip file: " + indexfile);
}
} else {
enigma::Log << "Could not find zip file `" << zipfile << "'\n";
}
}
}} // namespace enigma::lev