diff --git a/examples/faketrello/appdelegate.cpp b/examples/faketrello/appdelegate.cpp index 9f13e0a..aac2091 100644 --- a/examples/faketrello/appdelegate.cpp +++ b/examples/faketrello/appdelegate.cpp @@ -11,11 +11,31 @@ AppDelegate::AppDelegate(QObject *parent) : QObject(parent) int AppDelegate::run() { - // Pretend to load from file, but now it just load dummy daa. - m_board.load(); + // NPM: Find filesystem location for persistent storage + QString persistDir = +#ifdef Q_OS_ANDROID /* for android, store data in a location that doesn't get erased each app update */ + QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/" //"/storage/emulated/0/" + + "faketrello.example.qtproject.org"; +#else + QStandardPaths::writableLocation(QStandardPaths::DataLocation); +#endif /* Q_OS_ANDROID */ + + // NPM: Create application-specific directory for persistent storage if it doesn't exist + QDir path; + path.setPath(persistDir); + if (!path.exists()) { + path.mkpath(persistDir); + } QCoreApplication* app = QCoreApplication::instance(); + // NPM: load board layout from persistence file, + // or load dummy data if no persistence file, or parse error. + QString persistFilePath = persistDir + "/persist.json"; + m_board.load(persistFilePath); + + m_engine.rootContext()->setContextProperty("PersistFilePath", persistFilePath); + m_engine.rootContext()->setContextProperty("App", this); m_engine.rootContext()->setContextProperty("CardListStore", cardListStore); @@ -71,3 +91,51 @@ void AppDelegate::sync() runner.patch(cardListStore, patches); } +// NPM -- see https://github.com/benlau/qsyncable/issues/2 +QByteArray AppDelegate::stringifyModel() +{ + QVariantList lists; + int count = cardListStore->storage().size(); + for (int i = 0 ; i < count ; i++) { + lists << cardListStore->get(i); + } + + QVariantMap map; + map["lists"] = lists; + QJsonObject object = QJsonObject::fromVariantMap(map); + + QJsonDocument doc; + doc.setObject(object); + QByteArray bytes = doc.toJson(QJsonDocument::Indented); + + return bytes; +} + +// NPM -- see https://github.com/benlau/qsyncable/issues/2 +void AppDelegate::persistModel(const QString& filePath) { + QFile file(filePath); + file.open(QIODevice::WriteOnly); + file.write(AppDelegate::stringifyModel()); + file.close(); +} + +// NPM -- see https://github.com/benlau/qsyncable/issues/2 +QByteArray AppDelegate::stringifyBoard() +{ + QVariantMap map = m_board.toMap(); + QJsonObject object = QJsonObject::fromVariantMap(map); + + QJsonDocument doc; + doc.setObject(object); + QByteArray bytes = doc.toJson(QJsonDocument::Indented); + + return bytes; +} + +// NPM -- see https://github.com/benlau/qsyncable/issues/2 +void AppDelegate::persistBoard(const QString& filePath) { + QFile file(filePath); + file.open(QIODevice::WriteOnly); + file.write(AppDelegate::stringifyBoard()); + file.close(); +} diff --git a/examples/faketrello/appdelegate.h b/examples/faketrello/appdelegate.h index fee5938..0d2d003 100644 --- a/examples/faketrello/appdelegate.h +++ b/examples/faketrello/appdelegate.h @@ -18,6 +18,11 @@ class AppDelegate : public QObject public slots: + QByteArray stringifyModel(); //NPM + void persistModel(const QString& filePath); //NPM + QByteArray stringifyBoard(); //NPM + void persistBoard(const QString& filePath); //NPM + void addList(); void addCard(const QString& listUuid); diff --git a/examples/faketrello/board.cpp b/examples/faketrello/board.cpp index d6896fe..92756a3 100644 --- a/examples/faketrello/board.cpp +++ b/examples/faketrello/board.cpp @@ -109,9 +109,10 @@ void Board::removeCard(const QString& listUuid, const QString& cardUuid) } } -void Board::load() -{ +// NPM: original version of load(), now invoked from load() if persistence file missing or malformed +void Board::failsafeLoad() { List list1 = list(); + list1 << card() << card() << card(); @@ -122,6 +123,55 @@ void Board::load() d->lists << list1 << list2; } +// NPM: load persisted JSON board, or call failsafeload() above if first run or malformed persistence file. +// see https://github.com/benlau/qsyncable/issues/2 +void Board::load(const QString& persistFilePath) +{ + QFile file(persistFilePath); + if(!file.open(QIODevice::ReadOnly)) { + qWarning() << "NOTE: using default board because no persistence file found: "<< persistFilePath; + Board::failsafeLoad(); + } + else { + QTextStream stream (&file); + QString text = stream.readAll(); + file.close(); + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(text.toUtf8(), &error); + + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON::parse() error: "<< error.errorString(); + qWarning() << "NOTE: using default board because persistence file is malformed: "<< persistFilePath; + Board::failsafeLoad(); + } + else { + QVariantMap map = doc.object().toVariantMap(); + QList lists = map.value("lists").toList(); + int listsCount = lists.size(); + for (int listsIdx = 0 ; listsIdx < listsCount ; listsIdx++) { + QMap listsMap = lists[listsIdx].toMap(); + QString listName = listsMap.value("title").toString(); + QList listCards = listsMap.value("cards").toList(); + int listNum = listName.mid(5).toInt(); //"List %1" --> "%1" + if (d->nextListId <= listNum) + d->nextListId = listNum + 1; + List cardsList(listName); + int cardsCount = listCards.size(); + for (int cardIdx = 0 ; cardIdx < cardsCount ; cardIdx++) { + QString cardName = listCards[cardIdx].toMap().value("text").toString(); + int cardNum = cardName.mid(5).toInt(); //"Card %1" --> "%1" + if (d->nextCardId <= cardNum) + d->nextCardId = cardNum + 1; + Card card(cardName); + cardsList << card; + } + d->lists << cardsList; + } + } + } +} + QVariantMap Board::toMap() const { QVariantMap res; diff --git a/examples/faketrello/board.h b/examples/faketrello/board.h index e7868ce..b317509 100644 --- a/examples/faketrello/board.h +++ b/examples/faketrello/board.h @@ -30,8 +30,11 @@ class Board int indexOfList(const QString& listUuid); - // Load from file, but now it just load dummy data for demo - void load(); + // NPM: Load persisted board from file. + void load(const QString &persistFilePath); + + // NPM: Load initial board if persistence file missing or malformed. + void failsafeLoad(); QVariantMap toMap() const; diff --git a/examples/faketrello/main.qml b/examples/faketrello/main.qml index 74e98d0..dcc49be 100644 --- a/examples/faketrello/main.qml +++ b/examples/faketrello/main.qml @@ -1,9 +1,10 @@ import QtQuick 2.3 import QtQuick.Window 2.2 import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.1 import "./views" -Window { +ApplicationWindow { id: window width: 640 height: 480 @@ -23,5 +24,16 @@ Window { } + // NPM: Persist the model to file on application close. + // see https://github.com/benlau/qsyncable/issues/2 + // BUG: when running on Android, putting application in background and closing + // (via swipe or close window button) doesn't call this -- you just get + // '"org.qtproject.example.faketrello" died.' message before anything can be done. + // However, closing android app with back button works and persists the board. + onClosing: { + //console.log("Model=" + App.stringifyModel()); + console.log("Exiting application... Persisting model to file " + PersistFilePath); + App.persistModel(PersistFilePath); + close.accepted = true; + } } -