diff --git a/doomsday/engine/engine.pro b/doomsday/engine/engine.pro index 90a5e99f45..735ea09cf6 100644 --- a/doomsday/engine/engine.pro +++ b/doomsday/engine/engine.pro @@ -242,6 +242,7 @@ DENG_HEADERS += \ portable/include/hedge.h \ portable/include/image.h \ portable/include/joystick.h \ + portable/include/json.h \ portable/include/kdtree.h \ portable/include/keycode.h \ portable/include/library.h \ @@ -358,6 +359,7 @@ DENG_HEADERS += \ portable/include/ui_main.h \ portable/include/ui_mpi.h \ portable/include/ui_panel.h \ + portable/include/updater.h \ portable/include/vertex.h \ portable/include/wadfile.h \ portable/include/window.h \ @@ -519,6 +521,7 @@ SOURCES += \ portable/src/gridmap.c \ portable/src/hedge.cpp \ portable/src/image.c \ + portable/src/json.cpp \ portable/src/kdtree.c \ portable/src/keycode.cpp \ portable/src/library.c \ @@ -636,6 +639,7 @@ SOURCES += \ portable/src/ui_main.c \ portable/src/ui_mpi.c \ portable/src/ui_panel.c \ + portable/src/updater.cpp \ portable/src/uri.c \ portable/src/vertex.cpp \ portable/src/wadfile.c \ @@ -715,7 +719,7 @@ macx { doPostLink("cp -fRp $$OUT_PWD/../libdeng2/libdeng2*dylib $$FW_DIR") # Fix the dynamic linker paths so they point to ../Frameworks/ inside the bundle. - fixInstallName("Doomsday.app/Contents/MacOS/Doomsday", "libdeng2.2.dylib", "..") + fixInstallName(Doomsday.app/Contents/MacOS/Doomsday, libdeng2.2.dylib, ..) # Clean up previous deployment. doPostLink("rm -rf Doomsday.app/Contents/PlugIns/") diff --git a/doomsday/engine/portable/include/json.h b/doomsday/engine/portable/include/json.h new file mode 100644 index 0000000000..d88272a689 --- /dev/null +++ b/doomsday/engine/portable/include/json.h @@ -0,0 +1,45 @@ +/** + * @file json.cpp + * JSON parser. @ingroup data + * + * Parses JSON and outputs a QVariant with the data. + * + * @todo Move this to libdeng2. + * + * @authors Copyright © 2012 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef LIBDENG_JSON_H +#define LIBDENG_JSON_H + +#ifdef __cplusplus + +#include +#include + +/** + * Parses text as JSON and returns the data structured in a QVariant. + * + * @param jsonText Text to parse. + * + * @return Parsed data, or an invalid variant if an error occurred. + */ +QVariant parseJSON(const de::String& jsonText); + +#endif // __cplusplus + +#endif // LIBDENG_JSON_H diff --git a/doomsday/engine/portable/include/updater.h b/doomsday/engine/portable/include/updater.h new file mode 100644 index 0000000000..d9bcd2108f --- /dev/null +++ b/doomsday/engine/portable/include/updater.h @@ -0,0 +1,71 @@ +/** + * @file updater.h + * Automatic updater that works with dengine.net. @ingroup base + * + * @authors Copyright © 2012 Jaakko Keränen + * @authors Copyright © 2012 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef LIBDENG_UPDATER_H +#define LIBDENG_UPDATER_H + +#ifdef __cplusplus + +#include +#include + +/** + * Automatic updater. Communicates with dengine.net and coordinates the + * download and reinstall procedure. + */ +class Updater : public QObject +{ +public: + explicit Updater(QObject* parent = 0); + ~Updater(); + +public slots: + void gotReply(QNetworkReply*); + +private: + struct Instance; + Instance* d; +}; + +#endif // __cplusplus + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initializes the automatic updater. If it is time to check for an update, + * queries the latest version from http://dengine.net/ and determines the need + * to update. + */ +void Updater_Init(void); + +/** + * Shuts down the automatic updater. Must be called at engine shutdown. + */ +void Updater_Shutdown(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LIBDENG_UPDATER_H diff --git a/doomsday/engine/portable/src/json.cpp b/doomsday/engine/portable/src/json.cpp new file mode 100644 index 0000000000..f071eb1c30 --- /dev/null +++ b/doomsday/engine/portable/src/json.cpp @@ -0,0 +1,248 @@ +/** + * @file json.cpp + * JSON parser. @ingroup data + * + * Parses JSON and outputs a QVariant with the data. + * + * @authors Copyright © 2012 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "json.h" +#include +#include +#include + +/** + * JSON parser. Not exposed outside this source file; use parseJSON() instead. + */ +class JSONParser { + const QString& source; + int pos; + +public: + JSONParser(const QString& s) : source(s), pos(0) { + skipWhite(); + } + void advance() { + pos++; + skipWhite(); + } + void skipWhite() { + while(!atEnd() && source[pos].isSpace()) pos++; + } + bool atEnd() const { + return pos >= source.size(); + } + QChar peek() const { + if(atEnd()) return 0; + return source[pos]; + } + QChar next() { + if(atEnd()) return 0; + QChar c = source[pos]; + advance(); + return c; + } + QChar nextNoSkip() { + if(atEnd()) return 0; + return source[pos++]; + } + void error(const QString& message) { + throw de::Error("JSONParser", de::String("Error at position %1: %2").arg(pos).arg(message)); + } + QVariant parse() { + LOG_AS("JSONParser"); + if(atEnd()) return QVariant(); + QChar c = peek(); + if(c == '{') { + return parseObject(); + } + else if(c == '[') { + return parseArray(); + } + else if(c == '\"') { + return parseString(); + } + else if(c == '-' || c.isDigit()) { + return parseNumber(); + } + else { + return parseKeyword(); + } + } + QVariant parseObject() { + QVariantMap result; + QChar c = next(); + DENG2_ASSERT(c == '{'); + forever { + QString name = parseString().toString(); + c = next(); + if(c != ':') error("object keys and values must be separated by a colon"); + QVariant value = parse(); + // Add to the result. + result.insert(name, value); + // Move forward. + c = next(); + if(c == '}') { + // End of object. + break; + } + else if(c != ',') { + LOG_DEBUG(de::String("got %1 instead of ,").arg(c)); + error("key/value pairs must be separated by comma"); + } + } + return result; + } + QVariant parseArray() { + QVariantList result; + QChar c = next(); + DENG2_ASSERT(c == '['); + if(peek() == ']') + { + // Empty list. + next(); + return result; + } + forever { + result << parse(); + c = next(); + if(c == ']') { + // End of array. + break; + } + else if(c != ',') { + // What? + error("array items must be separated by comma"); + } + } + return result; + } + QVariant parseString() { + QVarLengthArray result; + QChar c = next(); + DENG2_ASSERT(c == '\"'); + forever { + c = nextNoSkip(); + if(c == '\\') { + // Escape. + c = nextNoSkip(); + if(c == '\"' || c == '\\' || c == '/') + result.append(c); + else if(c == 'b') + result.append('\b'); + else if(c == 'f') + result.append('\f'); + else if(c == 'n') + result.append('\n'); + else if(c == 'r') + result.append('\r'); + else if(c == 't') + result.append('\t'); + else if(c == 'u') { + QString code = source.mid(pos, 4); + pos += 4; + result.append(QChar(ushort(code.toLong(0, 16)))); + } + else error("unknown escape sequence in string"); + } + else if(c == '\"') { + // End of string. + break; + } + else + { + result.append(c); + } + } + result.append(0); + return QString(result.constData()); + } + QVariant parseNumber() { + QVarLengthArray str; + QChar c = next(); + if(c == '-') { + str.append(c); + c = next(); + } + for(; c.isDigit(); c = next()) { + str.append(c); + } + bool hasDecimal = false; + if(c == '.') { + str.append(c); + hasDecimal = true; + c = next(); + for(; c.isDigit(); c = next()) { + str.append(c); + } + } + if(c == 'e' || c == 'E') { + // Exponent. + str.append(c); + c = next(); + if(c == '+' || c == '-') { + str.append(c); + c = next(); + } + for(; c.isDigit(); c = next()) { + str.append(c); + } + } + // Rewind one char (the loop was broken when a non-digit was read). + pos--; + str.append(0); + double value = QString(str.constData()).toDouble(); + if(hasDecimal) { + return QVariant(value); + } + else { + return QVariant(int(value)); + } + } + QVariant parseKeyword() { + if(source.mid(pos, 4) == "true") { + pos += 4; + return QVariant(true); + } + else if(source.mid(pos, 5) == "false") { + pos += 5; + return QVariant(false); + } + else if(source.mid(pos, 4) == "null") { + pos += 4; + return QVariant(0); + } + else { + error("unknown keyword"); + } + return QVariant(); + } +}; + +QVariant parseJSON(const de::String& jsonText) +{ + try + { + return JSONParser(jsonText).parse(); + } + catch(const de::Error& er) + { + LOG_WARNING(er.asText()); + return QVariant(); // invalid + } +} diff --git a/doomsday/engine/portable/src/updater.cpp b/doomsday/engine/portable/src/updater.cpp new file mode 100644 index 0000000000..fe45b99595 --- /dev/null +++ b/doomsday/engine/portable/src/updater.cpp @@ -0,0 +1,97 @@ +/** + * @file updater.cpp + * Automatic updater that works with dengine.net. @ingroup base + * + * @authors Copyright © 2012 Jaakko Keränen + * @authors Copyright © 2012 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "updater.h" +#include +#include +#include +#include + +static Updater* updater = 0; + +#define WEBSITE_URL "http://dengine.net/" + +struct Updater::Instance +{ + enum Frequency + { + Daily, + Biweekly, // 3.5 days + Weekly, // 7 days + Monthly // 30 days + }; + enum Channel + { + Stable, + Unstable + }; + + Updater* self; + QNetworkAccessManager* network; + Frequency checkFrequency; + Channel channel; ///< What kind of updates to check for. + QDateTime lastCheckTime;///< Time of last check (automatic or manual). + bool onlyCheckManually; ///< Should only check when manually requested. + bool trashAfterUpdate; ///< Downloaded file is moved to trash afterwards. + QString downloadPath; ///< Path where the downloaded file is saved. + + Instance(Updater* up) : self(up) + { + network = new QNetworkAccessManager(self); + } + + ~Instance() + {} + + void handleReply(QNetworkReply* reply) + { + reply->deleteLater(); // make sure it gets deleted + + + } +}; + +Updater::Updater(QObject *parent) : QObject(parent) +{ + d = new Instance(this); + connect(d->network, SIGNAL(finished(QNetworkReply*)), this, SLOT(gotReply(QNetworkReply*))); +} + +Updater::~Updater() +{ + delete d; +} + +void Updater::gotReply(QNetworkReply* reply) +{ + d->handleReply(reply); +} + +void Updater_Init(void) +{ + updater = new Updater; +} + +void Updater_Shutdown(void) +{ + delete updater; +}