QML UI: don't immediately save data after we make changes

Much as this felt like the prudent thing to do, it makes the UI painful
to use.  In bad network conditions, with a large dive log, on a phone,
the save operation can take more than a minute - which is just completely
ludicrous.

So instead we mark the dive list changed when we make changes and wait
for the app to not be in the foreground. Once the OS tells us that we are
hidden (on the desktop that generally means we don't have focus, on a
mobile device it usually does mean that the app is not on the screen), we
check if there are data to be saved and do so.

There is of course a major problem with this logic. If the user switches
away from Subsurface-mobile but comes back fairly quickly (just reacting
to a notification or briefly checking something, changing a song,
something... then the app may still be non-responsive for quite a while.

So we need to do something about the time it takes us to save the git
tree locally, and then figure out if we can move at least some of the
network traffic to another thread.

And we need to make sure the user immediately notices that the app is not
crashed but is actually saving their data. But that's for another commit.

tl;dr: CAREFUL, don't kill Subsurface-mobile before it had time to save
your data or your changes may be gone. In typical use that shouldn't be
an issue, but it is something that we need to tell the user about.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Dirk Hohndel 2016-04-03 19:37:17 -05:00
parent 5821c56da2
commit 4af9ee5dea
2 changed files with 53 additions and 23 deletions

View file

@ -61,9 +61,11 @@ QMLManager::QMLManager() : m_locationServiceEnabled(false),
deletedDive(0),
deletedTrip(0),
m_credentialStatus(UNKNOWN),
m_lastDevicePixelRatio(1.0)
m_lastDevicePixelRatio(1.0),
alreadySaving(false)
{
m_instance = this;
connect(qobject_cast<QApplication *>(QApplication::instance()), &QApplication::applicationStateChanged, this, &QMLManager::applicationStateChanged);
appendTextToLog(getUserAgent());
appendTextToLog(QStringLiteral("build with Qt Version %1, runtime from Qt Version %2").arg(QT_VERSION_STR).arg(qVersion()));
qDebug() << "Starting" << getUserAgent();
@ -78,6 +80,42 @@ QMLManager::QMLManager() : m_locationServiceEnabled(false),
syncLoadFromCloud();
}
void QMLManager::applicationStateChanged(Qt::ApplicationState state)
{
if (!timer.isValid())
timer.start();
QString stateText;
switch (state) {
case Qt::ApplicationActive: stateText = "active"; break;
case Qt::ApplicationHidden: stateText = "hidden"; break;
case Qt::ApplicationSuspended: stateText = "suspended"; break;
case Qt::ApplicationInactive: stateText = "inactive"; break;
default: stateText = QString("none of the four: 0x") + QString::number(state, 16);
}
stateText.prepend(QString::number(timer.elapsed() / 1000.0,'f', 3) + ": AppState changed to ");
stateText.append(" with ");
stateText.append((alreadySaving ? QLatin1Literal("") : QLatin1Literal("no ")) + QLatin1Literal("save ongoing"));
appendTextToLog(stateText);
qDebug() << stateText;
if (!alreadySaving && state == Qt::ApplicationInactive && unsaved_changes()) {
// FIXME
// make sure the user sees that we are saving data if they come back
// while this is running
alreadySaving = true;
bool cbs = prefs.cloud_background_sync;
bool glo = prefs.git_local_only;
prefs.cloud_background_sync = true;
prefs.git_local_only = false;
saveChanges();
prefs.cloud_background_sync = cbs;
prefs.git_local_only = glo;
alreadySaving = false;
appendTextToLog(QString::number(timer.elapsed() / 1000.0,'f', 3) + ": done saving to git local / remote");
mark_divelist_changed(false);
}
}
void QMLManager::openLocalThenRemote(QString url)
{
clear_dive_file_data();
@ -682,18 +720,11 @@ parsed:
}
DiveListModel::instance()->updateDive(oldModelIdx, d);
}
if (diveChanged || needResort) {
if (diveChanged || needResort)
// we no longer save right away, but only the next time the app is not
// in the foreground (or when explicitly requested)
mark_divelist_changed(true);
// this is called "commit" for a reason - when the user saves an
// edit they have a reasonable expectation that their data is actually
// stored - so we need to store this to the local cache
qDebug() << "save dives to local cache";
prefs.cloud_background_sync = false;
prefs.git_local_only = true;
saveChanges();
prefs.cloud_background_sync = true;
prefs.git_local_only = false;
}
}
void QMLManager::saveChanges()
@ -743,11 +774,9 @@ void QMLManager::undoDelete(int id)
}
record_dive(deletedDive);
DiveListModel::instance()->addDive(deletedDive);
prefs.cloud_background_sync = false;
prefs.git_local_only = true;
saveChanges();
prefs.cloud_background_sync = true;
prefs.git_local_only = false;
// make sure the changes get saved if the app is no longer in the foreground
// or if the user requests a save
mark_divelist_changed(true);
deletedDive = NULL;
deletedTrip = NULL;
}
@ -774,7 +803,6 @@ void QMLManager::deleteDive(int id)
}
// if this is the last dive in that trip, remember the trip as well
if (d->divetrip && d->divetrip->nrdives == 1) {
deletedTrip = (struct dive_trip *)calloc(1, sizeof(struct dive_trip));
*deletedTrip = *d->divetrip;
deletedTrip->location = copy_string(d->divetrip->location);
deletedTrip->notes = copy_string(d->divetrip->notes);
@ -783,11 +811,9 @@ void QMLManager::deleteDive(int id)
}
DiveListModel::instance()->removeDiveById(id);
delete_single_dive(get_idx_by_uniq_id(id));
prefs.cloud_background_sync = false;
prefs.git_local_only = true;
saveChanges();
prefs.cloud_background_sync = true;
prefs.git_local_only = false;
// make sure the changes get saved if the app is no longer in the foreground
// or if the user requests a save
mark_divelist_changed(true);
}
QString QMLManager::addDive()

View file

@ -5,6 +5,7 @@
#include <QString>
#include <QNetworkAccessManager>
#include <QScreen>
#include <QElapsedTimer>
#include "gpslocation.h"
@ -74,6 +75,7 @@ public:
typedef void (QMLManager::*execute_function_type)();
public slots:
void applicationStateChanged(Qt::ApplicationState state);
void savePreferences();
void saveCloudCredentials();
void checkCredentialsAndExecute(execute_function_type execute);
@ -134,6 +136,8 @@ private:
int m_accessingCloud;
credentialStatus_t m_credentialStatus;
qreal m_lastDevicePixelRatio;
QElapsedTimer timer;
bool alreadySaving;
signals:
void cloudUserNameChanged();