Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement board persistence for example application "faketrello" #3

Merged
merged 1 commit into from
Aug 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 70 additions & 2 deletions examples/faketrello/appdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
5 changes: 5 additions & 0 deletions examples/faketrello/appdelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
54 changes: 52 additions & 2 deletions examples/faketrello/board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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<QVariant> lists = map.value("lists").toList();
int listsCount = lists.size();
for (int listsIdx = 0 ; listsIdx < listsCount ; listsIdx++) {
QMap<QString,QVariant> listsMap = lists[listsIdx].toMap();
QString listName = listsMap.value("title").toString();
QList<QVariant> 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;
Expand Down
7 changes: 5 additions & 2 deletions examples/faketrello/board.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
16 changes: 14 additions & 2 deletions examples/faketrello/main.qml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
}
}