diff options
-rw-r--r-- | android-mobile/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | core/file.c | 2 | ||||
-rw-r--r-- | core/git-access.c | 19 | ||||
-rw-r--r-- | core/git-access.h | 1 | ||||
-rw-r--r-- | core/qt-init.cpp | 11 | ||||
-rw-r--r-- | core/subsurface-qt/SettingsObjectWrapper.cpp | 7 | ||||
-rw-r--r-- | core/subsurfacestartup.c | 12 | ||||
-rw-r--r-- | core/subsurfacestartup.h | 2 | ||||
-rw-r--r-- | mobile-widgets/qml/CloudCredentials.qml | 11 | ||||
-rw-r--r-- | mobile-widgets/qml/DiveDetails.qml | 2 | ||||
-rw-r--r-- | mobile-widgets/qml/DiveList.qml | 28 | ||||
-rw-r--r-- | mobile-widgets/qml/StartPage.qml | 6 | ||||
-rw-r--r-- | mobile-widgets/qml/icons/nocloud.svg | 71 | ||||
-rw-r--r-- | mobile-widgets/qml/main.qml | 31 | ||||
-rw-r--r-- | mobile-widgets/qml/mobile-resources.qrc | 2 | ||||
-rw-r--r-- | mobile-widgets/qmlmanager.cpp | 162 | ||||
-rw-r--r-- | mobile-widgets/qmlmanager.h | 7 | ||||
-rw-r--r-- | subsurface-mobile-main.cpp | 29 |
18 files changed, 327 insertions, 77 deletions
diff --git a/android-mobile/AndroidManifest.xml b/android-mobile/AndroidManifest.xml index 8fa0c5e83..87ef9d997 100644 --- a/android-mobile/AndroidManifest.xml +++ b/android-mobile/AndroidManifest.xml @@ -16,6 +16,7 @@ android:label="@string/app_name" android:theme="@style/AppTheme" android:launchMode="singleTop" + android:windowSoftInputMode="adjustResize" android:screenOrientation="unspecified" > <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/core/file.c b/core/file.c index 82eb684b0..4c0ae8fd0 100644 --- a/core/file.c +++ b/core/file.c @@ -485,7 +485,7 @@ int parse_file(const char *filename) /* opening the cloud storage repository failed for some reason * give up here and don't send errors about git repositories */ free(current_sha); - return 0; + return -1; } /* if this is a git repository, do we already have this exact state loaded ? * get the SHA and compare with what we currently have */ diff --git a/core/git-access.c b/core/git-access.c index dba9cbec5..fe3a918ac 100644 --- a/core/git-access.c +++ b/core/git-access.c @@ -450,6 +450,11 @@ static int try_to_update(git_repository *repo, git_remote *origin, git_reference return report_error("Unable to get local or remote SHA1"); } if (git_merge_base(&base, repo, local_id, remote_id)) { + // TODO: + // if they have no merge base, they actually are different repos + // so instead merge this as merging a commit into a repo - git_merge() appears to do that + // but needs testing and cleanup afterwards + // if (is_subsurface_cloud) goto cloud_data_error; else @@ -940,3 +945,17 @@ struct git_repository *is_git_repository(const char *filename, const char **bran *branchp = branch; return repo; } + +int git_create_local_repo(const char *filename) +{ + git_repository *repo; + char *path = strdup(filename); + char *branch = strchr(path, '['); + if (branch) + *branch = '\0'; + int ret = git_repository_init(&repo, path, false); + free(path); + if (ret != 0) + (void)report_error("Create local repo failed with error code %d", ret); + return ret; +} diff --git a/core/git-access.h b/core/git-access.h index f098f1e8d..b8b8181fe 100644 --- a/core/git-access.h +++ b/core/git-access.h @@ -27,6 +27,7 @@ extern void set_git_id(const struct git_oid *); void set_git_update_cb(int(*)(bool, const char *)); int git_storage_update_progress(bool reset, const char *text); char *get_local_dir(const char *remote, const char *branch); +int git_create_local_repo(const char *filename); extern int last_git_storage_update_val; diff --git a/core/qt-init.cpp b/core/qt-init.cpp index b52dfd970..5dc25f9fc 100644 --- a/core/qt-init.cpp +++ b/core/qt-init.cpp @@ -4,6 +4,8 @@ #include <QTextCodec> #include "helpers.h" +char *settings_suffix = NULL; + void init_qt_late() { QApplication *application = qApp; @@ -19,7 +21,14 @@ void init_qt_late() QCoreApplication::setOrganizationName("Subsurface"); QCoreApplication::setOrganizationDomain("subsurface.hohndel.org"); - QCoreApplication::setApplicationName("Subsurface"); + // enable user specific settings (based on command line argument) + if (settings_suffix) { + if (verbose) + qDebug() << "using custom config for" << QString("Subsurface-%1").arg(settings_suffix); + QCoreApplication::setApplicationName(QString("Subsurface-%1").arg(settings_suffix)); + } else { + QCoreApplication::setApplicationName("Subsurface"); + } // find plugins installed in the application directory (without this SVGs don't work on Windows) QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); QLocale loc; diff --git a/core/subsurface-qt/SettingsObjectWrapper.cpp b/core/subsurface-qt/SettingsObjectWrapper.cpp index e43be1a9b..ed305ef28 100644 --- a/core/subsurface-qt/SettingsObjectWrapper.cpp +++ b/core/subsurface-qt/SettingsObjectWrapper.cpp @@ -1315,6 +1315,7 @@ int GeneralSettingsObjectWrapper::pscrRatio() const void GeneralSettingsObjectWrapper::setDefaultFilename(const QString& value) { QSettings s; + s.beginGroup(group); s.setValue("default_filename", value); prefs.default_filename = copy_string(qPrintable(value)); emit defaultFilenameChanged(value); @@ -1323,6 +1324,7 @@ void GeneralSettingsObjectWrapper::setDefaultFilename(const QString& value) void GeneralSettingsObjectWrapper::setDefaultCylinder(const QString& value) { QSettings s; + s.beginGroup(group); s.setValue("default_cylinder", value); prefs.default_cylinder = copy_string(qPrintable(value)); emit defaultCylinderChanged(value); @@ -1331,6 +1333,7 @@ void GeneralSettingsObjectWrapper::setDefaultCylinder(const QString& value) void GeneralSettingsObjectWrapper::setDefaultFileBehavior(short value) { QSettings s; + s.beginGroup(group); s.setValue("default_file_behavior", value); prefs.default_file_behavior = value; if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) { @@ -1347,6 +1350,7 @@ void GeneralSettingsObjectWrapper::setDefaultFileBehavior(short value) void GeneralSettingsObjectWrapper::setUseDefaultFile(bool value) { QSettings s; + s.beginGroup(group); s.setValue("use_default_file", value); prefs.use_default_file = value; emit useDefaultFileChanged(value); @@ -1355,6 +1359,7 @@ void GeneralSettingsObjectWrapper::setUseDefaultFile(bool value) void GeneralSettingsObjectWrapper::setDefaultSetPoint(int value) { QSettings s; + s.beginGroup(group); s.setValue("defaultsetpoint", value); prefs.defaultsetpoint = value; emit defaultSetPointChanged(value); @@ -1363,6 +1368,7 @@ void GeneralSettingsObjectWrapper::setDefaultSetPoint(int value) void GeneralSettingsObjectWrapper::setO2Consumption(int value) { QSettings s; + s.beginGroup(group); s.setValue("o2consumption", value); prefs.o2consumption = value; emit o2ConsumptionChanged(value); @@ -1371,6 +1377,7 @@ void GeneralSettingsObjectWrapper::setO2Consumption(int value) void GeneralSettingsObjectWrapper::setPscrRatio(int value) { QSettings s; + s.beginGroup(group); s.setValue("pscr_ratio", value); prefs.pscr_ratio = value; emit pscrRatioChanged(value); diff --git a/core/subsurfacestartup.c b/core/subsurfacestartup.c index 6e0dede1c..c2881a17f 100644 --- a/core/subsurfacestartup.c +++ b/core/subsurfacestartup.c @@ -10,6 +10,9 @@ struct preferences prefs, informational_prefs; struct preferences default_prefs = { .cloud_base_url = "https://cloud.subsurface-divelog.org/", +#if defined(SUBSURFACE_MOBILE) + .git_local_only = true, +#endif .units = SI_UNITS, .unit_system = METRIC, .coordinates_traditional = true, @@ -197,6 +200,11 @@ void parse_argument(const char *arg) continue; case '-': /* long options with -- */ + /* first test for --user=bla which allows the use of user specific settings */ + if (strncmp(arg, "--user=", sizeof("--user=") - 1) == 0) { + settings_suffix = strdup(arg + sizeof("--user=") - 1); + return; + } if (strcmp(arg, "--help") == 0) { print_help(); exit(0); @@ -254,8 +262,10 @@ void setup_system_prefs(void) subsurface_OS_pref_setup(); default_prefs.divelist_font = strdup(system_divelist_default_font); default_prefs.font_size = system_divelist_default_font_size; - default_prefs.default_filename = system_default_filename(); +#if !defined(SUBSURFACE_MOBILE) + default_prefs.default_filename = system_default_filename(); +#endif env = getenv("LC_MEASUREMENT"); if (!env) env = getenv("LC_ALL"); diff --git a/core/subsurfacestartup.h b/core/subsurfacestartup.h index 3ccc24aa4..53d44f28d 100644 --- a/core/subsurfacestartup.h +++ b/core/subsurfacestartup.h @@ -19,6 +19,8 @@ void free_prefs(void); void copy_prefs(struct preferences *src, struct preferences *dest); void print_files(void); +extern char *settings_suffix; + #ifdef __cplusplus } #endif diff --git a/mobile-widgets/qml/CloudCredentials.qml b/mobile-widgets/qml/CloudCredentials.qml index d63227619..2bb42a6ed 100644 --- a/mobile-widgets/qml/CloudCredentials.qml +++ b/mobile-widgets/qml/CloudCredentials.qml @@ -23,6 +23,17 @@ Item { id: outerLayout width: loginWindow.width - loginWindow.leftPadding - loginWindow.rightPadding - 2 * Kirigami.Units.gridUnit + function goToNext() { + for (var i = 0; i < children.length; ++i) + if (children[i].focus) { + children[i].nextItemInFocusChain().forceActiveFocus() + break + } + } + + Keys.onReturnPressed: goToNext() + Keys.onTabPressed: goToNext() + onVisibleChanged: { if (visible && manager.accessingCloud < 0) { manager.appendTextToLog("Credential scrn: show kbd was: " + (Qt.inputMethod.isVisible ? "visible" : "invisible")) diff --git a/mobile-widgets/qml/DiveDetails.qml b/mobile-widgets/qml/DiveDetails.qml index 3f42a1ed5..5735266f6 100644 --- a/mobile-widgets/qml/DiveDetails.qml +++ b/mobile-widgets/qml/DiveDetails.qml @@ -30,8 +30,6 @@ Kirigami.Page { property alias gpsCheckbox: detailsEdit.gpsCheckbox property int updateCurrentIdx: manager.updateSelectedDive - property bool contentItem: true // HACK to work around Kirigami issue - remove once that's addressed upstream - title: diveDetailsListView.currentItem ? diveDetailsListView.currentItem.modelData.dive.location : "Dive details" state: "view" leftPadding: 0 diff --git a/mobile-widgets/qml/DiveList.qml b/mobile-widgets/qml/DiveList.qml index 6ad3161e1..9ffb2ee88 100644 --- a/mobile-widgets/qml/DiveList.qml +++ b/mobile-widgets/qml/DiveList.qml @@ -211,18 +211,24 @@ Kirigami.ScrollablePage { ScrollView { id: startPageWrapper anchors.fill: parent - opacity: (diveListView.count > 0 && (credentialStatus == QMLManager.VALID || credentialStatus == QMLManager.VALID_EMAIL)) ? 0 : 1 + opacity: credentialStatus === QMLManager.NOCLOUD || (diveListView.count > 0 && (credentialStatus === QMLManager.VALID || credentialStatus === QMLManager.VALID_EMAIL)) ? 0 : 1 visible: opacity > 0 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } } onVisibleChanged: { + print("startPageWrapper onVisibleChanged credentialStatus " + credentialStatus + " diveListView.count " + diveListView.count) if (visible) { page.actions.main = page.saveAction + page.actions.right = page.offlineAction title = "Cloud credentials" - } else if(manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL) { + } else if(manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL || manager.credentialStatus === QMLManager.NOCLOUD) { page.actions.main = page.addDiveAction + page.actions.right = null title = "Dive list" + if (diveListView.count === 0) + showPassiveNotification(qsTr("Please tap the '+' button to add a dive"), 3000) } else { page.actions.main = null + page.actions.right = null title = "Dive list" } } @@ -279,15 +285,25 @@ Kirigami.ScrollablePage { } } + property QtObject offlineAction: Action { + iconName: "qrc:/qml/nocloud.svg" + onTriggered: { + manager.syncToCloud = false + manager.credentialStatus = QMLManager.NOCLOUD + } + } + onBackRequested: { - if (startPageWrapper.visible && diveListView.count > 0 && manager.credentialStatus != QMLManager.INVALID) { + if (startPageWrapper.visible && diveListView.count > 0 && manager.credentialStatus !== QMLManager.INVALID) { manager.credentialStatus = oldStatus event.accepted = true; } if (!startPageWrapper.visible) { - manager.quit() - // we shouldn't come back from there, but just in case - event.accepted = true + if (Qt.platform.os != "ios") { + manager.quit() + // we shouldn't come back from there, but just in case + event.accepted = true + } } } } diff --git a/mobile-widgets/qml/StartPage.qml b/mobile-widgets/qml/StartPage.qml index 41fe469c2..e9c785672 100644 --- a/mobile-widgets/qml/StartPage.qml +++ b/mobile-widgets/qml/StartPage.qml @@ -16,8 +16,10 @@ ColumnLayout { Layout.fillWidth: true Layout.margins: Kirigami.Units.gridUnit Layout.topMargin: Kirigami.Units.gridUnit * 3 - text: "In order to use Subsurface-mobile you need to have a Subsurface cloud storage account " + - "(which can be created with the Subsurface desktop application)." + text: "To use Subsurface-mobile with Subsurface cloud storage, please enter " + + "your cloud credentials.\n\n" + + "To use Subsurface-mobile only with local data on this device, tap " + + "on the no cloud icon below." wrapMode: Text.WordWrap } Kirigami.Label { diff --git a/mobile-widgets/qml/icons/nocloud.svg b/mobile-widgets/qml/icons/nocloud.svg new file mode 100644 index 000000000..0661d32e6 --- /dev/null +++ b/mobile-widgets/qml/icons/nocloud.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + viewBox="0 0 22 22" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="nocloud.svg"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1022" + inkscape:window-height="740" + id="namedview8" + showgrid="false" + inkscape:zoom="10.727273" + inkscape:cx="-2.8898305" + inkscape:cy="11" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <defs + id="defs3051"> + <style + type="text/css" + id="current-color-scheme"> + .ColorScheme-Text { + color:#4d4d4d; + } + </style> + </defs> + <path + style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 11 4 A 6 6 0 0 0 5 10 A 6 6 0 0 0 5.0039062 10.128906 A 4 4 0 0 0 2 14 A 4 4 0 0 0 6 18 L 15 18 A 5 5 0 0 0 20 13 A 5 5 0 0 0 16.757812 8.3242188 A 6 6 0 0 0 11 4 z M 11 5 A 5 5 0 0 1 15.919922 9.1113281 A 4.0000019 4.0000019 0 0 1 19 13 A 4.0000019 4.0000019 0 0 1 15 17 L 6 17 A 2.9999979 2.9999979 0 0 1 3 14 A 2.9999979 2.9999979 0 0 1 6 11 A 2.9999979 2.9999979 0 0 1 6.1074219 11.005859 A 5 5 0 0 1 6 10 A 5 5 0 0 1 11 5 z " + class="ColorScheme-Text" + id="path6" /> + <path + style="opacity:1;fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.20000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 4.0550844,20.042373 18.877119,4.0084751" + id="path3338-1" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#4d4d4d;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1" + d="M 3.8220339,19.855932 18.644068,3.8220339" + id="path3338" + inkscape:connector-curvature="0" /> +</svg> diff --git a/mobile-widgets/qml/main.qml b/mobile-widgets/qml/main.qml index e1a140635..b4ab897e9 100644 --- a/mobile-widgets/qml/main.qml +++ b/mobile-widgets/qml/main.qml @@ -124,7 +124,6 @@ Kirigami.ApplicationWindow { }, Kirigami.Action { text: "Manage dives" - enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL /* * disable for the beta to avoid confusion Action { @@ -137,6 +136,7 @@ Kirigami.ApplicationWindow { */ Kirigami.Action { text: "Add dive manually" + enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL || manager.credentialStatus === QMLManager.NOCLOUD onTriggered: { returnTopPage() // otherwise odd things happen with the page stack startAddDive() @@ -144,24 +144,34 @@ Kirigami.ApplicationWindow { } Kirigami.Action { text: "Manual sync with cloud" + enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL || manager.credentialStatus === QMLManager.NOCLOUD onTriggered: { - globalDrawer.close() - detailsWindow.endEditMode() - manager.saveChangesCloud(true); - globalDrawer.close() + if (manager.credentialStatus === QMLManager.NOCLOUD) { + returnTopPage() + oldStatus = manager.credentialStatus + manager.startPageText = "Enter valid cloud storage credentials" + manager.credentialStatus = QMLManager.UNKNOWN + globalDrawer.close() + } else { + globalDrawer.close() + detailsWindow.endEditMode() + manager.saveChangesCloud(true); + globalDrawer.close() + } } } Kirigami.Action { - text: syncToCloud ? "Disable auto cloud sync" : "Enable auto cloud sync" + text: syncToCloud ? "Offline mode" : "Enable auto cloud sync" + enabled: manager.credentialStatus !== QMLManager.NOCLOUD onTriggered: { syncToCloud = !syncToCloud if (!syncToCloud) { var alertText = "Turning off automatic sync to cloud causes all data\n" - alertText +=" to only be stored locally.\n" + alertText +="to only be stored locally.\n" alertText += "This can be very useful in situations with\n" - alertText += " limited or no network access.\n" + alertText += "limited or no network access.\n" alertText += "Please chose 'Manual sync with cloud' if you have network\n" - alertText += " connectivity and want to sync your data to cloud storage." + alertText += "connectivity and want to sync your data to cloud storage." showPassiveNotification(alertText, 10000) } } @@ -326,8 +336,7 @@ Kirigami.ApplicationWindow { DiveDetails { id: detailsWindow visible: false - width: parent.width - height: parent.height + anchors.fill: parent } DownloadFromDiveComputer { diff --git a/mobile-widgets/qml/mobile-resources.qrc b/mobile-widgets/qml/mobile-resources.qrc index 6157e3bf6..ab556789b 100644 --- a/mobile-widgets/qml/mobile-resources.qrc +++ b/mobile-widgets/qml/mobile-resources.qrc @@ -21,6 +21,7 @@ <file alias="context-menu.png">icons/context-menu.png</file> <file alias="menu-edit.png">icons/menu-edit.png</file> <file alias="menu-back.png">icons/menu-back.png</file> + <file alias="nocloud.svg">icons/nocloud.svg</file> </qresource> <qresource prefix="/imports"> <file alias="org/kde/kirigami/qmldir">kirigami/qmldir</file> @@ -48,6 +49,7 @@ <file alias="org/kde/kirigami/private/ActionButton.qml">kirigami/private/ActionButton.qml</file> <file alias="org/kde/kirigami/private/BackButton.qml">kirigami/private/BackButton.qml</file> <file alias="org/kde/kirigami/private/ContextIcon.qml">kirigami/private/ContextIcon.qml</file> + <file alias="org/kde/kirigami/private/DefaultListItemBackground.qml">kirigami/private/DefaultListItemBackground.qml</file> <file alias="org/kde/kirigami/private/EdgeShadow.qml">kirigami/private/EdgeShadow.qml</file> <file alias="org/kde/kirigami/private/MenuIcon.qml">kirigami/private/MenuIcon.qml</file> <file alias="org/kde/kirigami/private/DefaultListItemBackground.qml">kirigami/private/DefaultListItemBackground.qml</file> diff --git a/mobile-widgets/qmlmanager.cpp b/mobile-widgets/qmlmanager.cpp index c2c60ce5c..82ea319bb 100644 --- a/mobile-widgets/qmlmanager.cpp +++ b/mobile-widgets/qmlmanager.cpp @@ -19,12 +19,16 @@ #include "core/qt-gui.h" #include "core/git-access.h" #include "core/cloudstorage.h" +#include "core/subsurface-qt/SettingsObjectWrapper.h" +#include "core/membuffer.h" QMLManager *QMLManager::m_instance = NULL; #define RED_FONT QLatin1Literal("<font color=\"red\">") #define END_FONT QLatin1Literal("</font>") +#define NOCLOUD_LOCALSTORAGE format_string("%s/cloudstorage/localrepo[master]", system_default_directory()) + static void appendTextToLogStandalone(const char *text) { QMLManager *self = QMLManager::instance(); @@ -153,6 +157,11 @@ void QMLManager::openLocalThenRemote(QString url) DiveListModel::instance()->addAllDives(); appendTextToLog(QStringLiteral("%1 dives loaded from cache").arg(dive_table.nr)); } + if (oldStatus() == NOCLOUD) { + // if we switch to credentials from NOCLOUD, we take things online temporarily + prefs.git_local_only = false; + appendTextToLog(QStringLiteral("taking things online to be able to switch to cloud account")); + } set_filename(fileNamePrt.data(), true); if (prefs.git_local_only) { appendTextToLog(QStringLiteral("have cloud credentials, but user asked not to connect to network")); @@ -163,6 +172,13 @@ void QMLManager::openLocalThenRemote(QString url) } } +void QMLManager::mergeLocalRepo() +{ + char *filename = NOCLOUD_LOCALSTORAGE; + parse_file(filename); + process_dives(true, false); +} + void QMLManager::finishSetup() { // Initialize cloud credentials. @@ -178,9 +194,23 @@ void QMLManager::finishSetup() // but we need to make sure we stay the only ones accessing git storage alreadySaving = true; openLocalThenRemote(url); + } else if (!same_string(existing_filename, "")) { + setCredentialStatus(NOCLOUD); + appendTextToLog(tr("working in no-cloud mode")); + int error = parse_file(existing_filename); + if (error) { + // we got an error loading the local file + appendTextToLog(QString("got error %2 when parsing file %1").arg(existing_filename, get_error_string())); + set_filename(NULL, ""); + } else { + // successfully opened the local file, now add thigs to the dive list + consumeFinishedLoad(0); + setAccessingCloud(-1); + appendTextToLog(QString("working in no-cloud mode, finished loading %1 dives from %2").arg(dive_table.nr).arg(existing_filename)); + } } else { setCredentialStatus(INCOMPLETE); - appendTextToLog(QStringLiteral("no cloud credentials")); + appendTextToLog(tr("no cloud credentials")); setStartPageText(RED_FONT + tr("Please enter valid cloud credentials.") + END_FONT); } setDistanceThreshold(prefs.distance_threshold); @@ -231,6 +261,7 @@ void QMLManager::saveCloudCredentials() // just go back to the dive list setCredentialStatus(oldStatus()); } + if (!same_string(prefs.cloud_storage_password, qPrintable(cloudPassword()))) { free(prefs.cloud_storage_password); prefs.cloud_storage_password = strdup(qPrintable(cloudPassword())); @@ -238,6 +269,9 @@ void QMLManager::saveCloudCredentials() if (cloudUserName().isEmpty() || cloudPassword().isEmpty()) { setStartPageText(RED_FONT + tr("Please enter valid cloud credentials.") + END_FONT); } else if (cloudCredentialsChanged) { + // let's make sure there are no unsaved changes + saveChangesLocal(); + free(prefs.userid); prefs.userid = NULL; syncLoadFromCloud(); @@ -252,6 +286,10 @@ void QMLManager::saveCloudCredentials() // we therefore know that no one else is already accessing THIS git repo; // let's make sure we stay the only ones doing so alreadySaving = true; + // since we changed credentials, we need to try to connect to the cloud, regardless + // of whether we're in offline mode or not, to make sure the repository is synced + currentGitLocalOnly = prefs.git_local_only; + prefs.git_local_only = false; openLocalThenRemote(url); } } @@ -268,7 +306,6 @@ void QMLManager::checkCredentialsAndExecute(execute_function_type execute) CloudStorageAuthenticate *csa = new CloudStorageAuthenticate(this); csa->backend(prefs.cloud_storage_email, prefs.cloud_storage_password); connect(manager(), &QNetworkAccessManager::authenticationRequired, this, &QMLManager::provideAuth, Qt::UniqueConnection); - connect(manager(), &QNetworkAccessManager::finished, this, execute, Qt::UniqueConnection); QUrl url(CLOUDREDIRECTURL); request = QNetworkRequest(url); request.setRawHeader("User-Agent", getUserAgent().toUtf8()); @@ -276,6 +313,7 @@ void QMLManager::checkCredentialsAndExecute(execute_function_type execute) reply = manager()->get(request); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleError(QNetworkReply::NetworkError))); connect(reply, &QNetworkReply::sslErrors, this, &QMLManager::handleSslErrors); + connect(reply, &QNetworkReply::finished, this, execute, Qt::UniqueConnection); } } @@ -325,10 +363,11 @@ void QMLManager::handleError(QNetworkReply::NetworkError nError) void QMLManager::retrieveUserid() { if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 302) { - appendTextToLog(QStringLiteral("Cloud storage connection not working correctly: %1").arg(QString(reply->readAll()))); + appendTextToLog(QStringLiteral("Cloud storage connection not working correctly: (%1) %2") + .arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()) + .arg(QString(reply->readAll()))); setStartPageText(RED_FONT + tr("Cannot connect to cloud storage") + END_FONT); - setAccessingCloud(-1); - alreadySaving = false; + revertToNoCloudIfNeeded(); return; } setCredentialStatus(VALID); @@ -336,8 +375,7 @@ void QMLManager::retrieveUserid() if (userid.isEmpty()) { if (same_string(prefs.cloud_storage_email, "") || same_string(prefs.cloud_storage_password, "")) { appendTextToLog("cloud user name or password are empty, can't retrieve web user id"); - setAccessingCloud(-1); - alreadySaving = false; + revertToNoCloudIfNeeded(); return; } appendTextToLog(QStringLiteral("calling getUserid with user %1").arg(prefs.cloud_storage_email)); @@ -366,12 +404,12 @@ void QMLManager::loadDiveProgress(int percent) void QMLManager::loadDivesWithValidCredentials() { QString url; + timestamp_t currentDiveTimestamp = selectedDiveTimestamp(); if (getCloudURL(url)) { QString errorString(get_error_string()); appendTextToLog(errorString); setStartPageText(RED_FONT + tr("Cloud storage error: %1").arg(errorString) + END_FONT); - setAccessingCloud(-1); - alreadySaving = false; + revertToNoCloudIfNeeded(); return; } QByteArray fileNamePrt = QFile::encodeName(url); @@ -381,13 +419,9 @@ void QMLManager::loadDivesWithValidCredentials() if (check_git_sha(fileNamePrt.data(), &git, &branch) == 0) { qDebug() << "local cache was current, no need to modify dive list"; appendTextToLog("Cloud sync shows local cache was current"); - setLoadFromCloud(true); - setAccessingCloud(-1); - alreadySaving = false; - return; + goto successful_exit; } appendTextToLog("Cloud sync brought newer data, reloading the dive list"); - timestamp_t currentDiveTimestamp = selectedDiveTimestamp(); clear_dive_file_data(); if (git != dummy_git_repository) { @@ -407,10 +441,71 @@ void QMLManager::loadDivesWithValidCredentials() report_error("failed to open file %s", fileNamePrt.data()); QString errorString(get_error_string()); appendTextToLog(errorString); - setStartPageText(RED_FONT + tr("Cloud storage error: %1").arg(errorString) + END_FONT); - alreadySaving = false; + revertToNoCloudIfNeeded(); return; } + consumeFinishedLoad(currentDiveTimestamp); + +successful_exit: + alreadySaving = false; + setLoadFromCloud(true); + // if we came from local storage mode, let's merge the local data into the local cache + // for the remote data - which then later gets merged with the remote data if necessary + if (oldStatus() == NOCLOUD) { + git_storage_update_progress(false, "import dives from nocloud local storage"); + dive_table.preexisting = dive_table.nr; + mergeLocalRepo(); + DiveListModel::instance()->clear(); + DiveListModel::instance()->addAllDives(); + appendTextToLog(QStringLiteral("%1 dives loaded after importing nocloud local storage").arg(dive_table.nr)); + saveChangesLocal(); + if (syncToCloud() == false) { + appendTextToLog(QStringLiteral("taking things back offline now that storage is synced")); + prefs.git_local_only = syncToCloud(); + } + } + setAccessingCloud(-1); + // if we got here just for an initial connection to the cloud, reset to offline + if (currentGitLocalOnly) { + currentGitLocalOnly = false; + prefs.git_local_only = true; + } + return; +} + +void QMLManager::revertToNoCloudIfNeeded() +{ + if (currentGitLocalOnly) { + // we tried to connect to the cloud for the first time and that failed + currentGitLocalOnly = false; + prefs.git_local_only = true; + } + if (oldStatus() == NOCLOUD) { + // we tried to switch to a cloud account and had previously used local data, + // but connecting to the cloud account (and subsequently merging the local + // and cloud data) failed - so let's delete the cloud credentials and go + // back to NOCLOUD mode in order to prevent us from losing the locally stored + // dives + if (syncToCloud() == false) { + appendTextToLog(QStringLiteral("taking things back offline since sync with cloud failed")); + prefs.git_local_only = syncToCloud(); + } + free(prefs.cloud_storage_email); + prefs.cloud_storage_email = NULL; + free(prefs.cloud_storage_password); + prefs.cloud_storage_password = NULL; + setCloudUserName(""); + setCloudPassword(""); + setCredentialStatus(INCOMPLETE); + set_filename(NOCLOUD_LOCALSTORAGE, true); + setStartPageText(RED_FONT + tr("Failed to connect to cloud server, reverting to no cloud status") + END_FONT); + } + setAccessingCloud(-1); + alreadySaving = false; +} + +void QMLManager::consumeFinishedLoad(timestamp_t currentDiveTimestamp) +{ prefs.unit_system = informational_prefs.unit_system; if (informational_prefs.unit_system == IMPERIAL) informational_prefs.units = IMPERIAL_units; @@ -420,12 +515,9 @@ void QMLManager::loadDivesWithValidCredentials() DiveListModel::instance()->addAllDives(); if (currentDiveTimestamp) setUpdateSelectedDive(dlSortModel->getIdxForId(get_dive_id_closest_to(currentDiveTimestamp))); - else - setUpdateSelectedDive(0); appendTextToLog(QStringLiteral("%1 dives loaded").arg(dive_table.nr)); if (dive_table.nr == 0) setStartPageText(tr("Cloud storage open successfully. No dives in dive list.")); - setLoadFromCloud(true); alreadySaving = false; } @@ -782,11 +874,10 @@ void QMLManager::changesNeedSaving() // to be reasonably fast), but don't save at all (and only remember that we need to save things // on iOS // on all other platforms we just save the changes and be done with it -#if defined(Q_OS_IOS) mark_divelist_changed(true); -#elif defined(Q_OS_ANDROID) +#if defined(Q_OS_ANDROID) saveChangesLocal(); -#else +#elif !defined(Q_OS_IOS) saveChangesCloud(false); #endif } @@ -794,7 +885,18 @@ void QMLManager::saveChangesLocal() { if (unsaved_changes()) { git_storage_update_progress(true, "saving dives locally"); // reset the timers - if (!loadFromCloud()) { + if (credentialStatus() == NOCLOUD) { + if (same_string(existing_filename, "")) { + char *filename = NOCLOUD_LOCALSTORAGE; + if (git_create_local_repo(filename)) + appendTextToLog(get_error_string()); + set_filename(filename, true); + GeneralSettingsObjectWrapper s(this); + s.setDefaultFilename(filename); + s.setDefaultFileBehavior(LOCAL_DEFAULT_FILE); + qDebug() << "setting default file to" << filename; + } + } else if (!loadFromCloud()) { // this seems silly, but you need a common ancestor in the repository in // order to be able to merge che changes later appendTextToLog("Don't save dives without loading from the cloud, first."); @@ -809,6 +911,7 @@ void QMLManager::saveChangesLocal() prefs.git_local_only = true; if (save_dives(existing_filename)) { appendTextToLog(get_error_string()); + set_filename(NULL, true); setAccessingCloud(-1); prefs.git_local_only = glo; alreadySaving = false; @@ -825,14 +928,10 @@ void QMLManager::saveChangesLocal() void QMLManager::saveChangesCloud(bool forceRemoteSync) { - if (!unsaved_changes()) { + if (!unsaved_changes() && !forceRemoteSync) { appendTextToLog("asked to save changes but no unsaved changes"); return; } - if (!loadFromCloud()) { - appendTextToLog("Don't save dives without loading from the cloud, first."); - return; - } if (alreadySaving) { appendTextToLog("save operation in progress already"); return; @@ -844,6 +943,11 @@ void QMLManager::saveChangesCloud(bool forceRemoteSync) if (prefs.git_local_only && !forceRemoteSync) return; + if (!loadFromCloud()) { + appendTextToLog("Don't save dives without loading from the cloud, first."); + return; + } + bool glo = prefs.git_local_only; git_storage_update_progress(false, "start save change to cloud"); prefs.git_local_only = false; @@ -1119,6 +1223,8 @@ QMLManager::credentialStatus_t QMLManager::credentialStatus() const void QMLManager::setCredentialStatus(const credentialStatus_t value) { if (m_credentialStatus != value) { + if (value == NOCLOUD) + appendTextToLog("Switching to no cloud mode"); m_credentialStatus = value; emit credentialStatusChanged(); } diff --git a/mobile-widgets/qmlmanager.h b/mobile-widgets/qmlmanager.h index ece2c7b59..906e7b0b2 100644 --- a/mobile-widgets/qmlmanager.h +++ b/mobile-widgets/qmlmanager.h @@ -39,7 +39,8 @@ public: UNKNOWN, INVALID, VALID_EMAIL, - VALID + VALID, + NOCLOUD }; static QMLManager *instance(); @@ -127,11 +128,14 @@ public slots: void clearGpsData(); void finishSetup(); void openLocalThenRemote(QString url); + void mergeLocalRepo(); QString getNumber(const QString& diveId); QString getDate(const QString& diveId); QString getCurrentPosition(); QString getVersion() const; void deleteGpsFix(quint64 when); + void revertToNoCloudIfNeeded(); + void consumeFinishedLoad(timestamp_t currentDiveTimestamp); void refreshDiveList(); void screenChanged(QScreen *screen); qreal lastDevicePixelRatio(); @@ -170,6 +174,7 @@ private: bool checkLocation(DiveObjectHelper *myDive, struct dive *d, QString location, QString gps); bool checkDuration(DiveObjectHelper *myDive, struct dive *d, QString duration); bool checkDepth(DiveObjectHelper *myDive, struct dive *d, QString depth); + bool currentGitLocalOnly; signals: void cloudUserNameChanged(); diff --git a/subsurface-mobile-main.cpp b/subsurface-mobile-main.cpp index e5965090b..b7259c44a 100644 --- a/subsurface-mobile-main.cpp +++ b/subsurface-mobile-main.cpp @@ -22,12 +22,9 @@ QTranslator *qtTranslator, *ssrfTranslator; int main(int argc, char **argv) { int i; - bool no_filenames = true; QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); QApplication *application = new QApplication(argc, argv); (void)application; - QStringList files; - QStringList importedFiles; QStringList arguments = QCoreApplication::arguments(); bool dedicated_console = arguments.length() > 1 && @@ -40,18 +37,8 @@ int main(int argc, char **argv) parse_argument(a.toLocal8Bit().data()); continue; } - if (imported) { - importedFiles.push_back(a); - } else { - no_filenames = false; - files.push_back(a); - } } -#if !LIBGIT2_VER_MAJOR && LIBGIT2_VER_MINOR < 22 - git_threads_init(); -#else git_libgit2_init(); -#endif setup_system_prefs(); if (uiLanguage(0).contains("-US")) default_prefs.units = IMPERIAL_units; @@ -61,6 +48,11 @@ int main(int argc, char **argv) taglist_init_global(); init_ui(); loadPreferences(); + if (prefs.default_file_behavior == LOCAL_DEFAULT_FILE) + set_filename(prefs.default_filename, true); + else + set_filename(NULL, true); + // some hard coded settings prefs.animation_speed = 0; // we render the profile to pixmap, no animations @@ -69,17 +61,6 @@ int main(int argc, char **argv) prefs.redceiling = 1; init_proxy(); - if (no_filenames) { - if (prefs.default_file_behavior == LOCAL_DEFAULT_FILE) { - QString defaultFile(prefs.default_filename); - if (!defaultFile.isEmpty()) - files.push_back(QString(prefs.default_filename)); - } else if (prefs.default_file_behavior == CLOUD_DEFAULT_FILE) { - QString cloudURL; - if (getCloudURL(cloudURL) == 0) - files.push_back(cloudURL); - } - } if (!quit) run_ui(); |