client-qml: add initial commit

Change-Id: I32bfdd2a618aa7ac6181da2697e241667b010aab
diff --git a/src/runguard.cpp b/src/runguard.cpp
new file mode 100644
index 0000000..4d22959
--- /dev/null
+++ b/src/runguard.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019-2020 by Savoir-faire Linux
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+// Based on: https://stackoverflow.com/a/28172162
+
+#include "runguard.h"
+#include <QCryptographicHash>
+
+namespace {
+
+QString
+generateKeyHash(const QString &key, const QString &salt)
+{
+    QByteArray data;
+
+    data.append(key.toUtf8());
+    data.append(salt.toUtf8());
+    data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
+
+    return data;
+}
+
+} // namespace
+
+RunGuard::RunGuard(const QString &key)
+    : key_(key)
+    , memLockKey_(generateKeyHash(key, "_memLockKey"))
+    , sharedmemKey_(generateKeyHash(key, "_sharedmemKey"))
+    , sharedMem_(sharedmemKey_)
+    , memLock_(memLockKey_, 1)
+{}
+
+RunGuard::~RunGuard()
+{
+    release();
+}
+
+void
+RunGuard::tryRestorePrimaryInstance()
+{
+    /*
+     * TODO: relaunch application
+     */
+}
+
+bool
+RunGuard::isAnotherRunning()
+{
+    if (sharedMem_.isAttached())
+        return false;
+
+    memLock_.acquire();
+    const bool isRunning = sharedMem_.attach();
+    if (isRunning)
+        sharedMem_.detach();
+    memLock_.release();
+
+    return isRunning;
+}
+
+bool
+RunGuard::tryToRun()
+{
+#ifdef Q_OS_WIN
+    if (isAnotherRunning()) {
+        /*
+         * This is a secondary instance,
+         * connect to the primary instance to trigger a restore
+         * then fail.
+         */
+        if (socket_ == nullptr) {
+            socket_ = new QLocalSocket();
+        }
+        if (socket_->state() == QLocalSocket::UnconnectedState
+            || socket_->state() == QLocalSocket::ClosingState) {
+            socket_->connectToServer(key_);
+        }
+        if (socket_->state() == QLocalSocket::ConnectingState) {
+            socket_->waitForConnected();
+        }
+        return false;
+    }
+
+    memLock_.acquire();
+    const bool result = sharedMem_.create(sizeof(quint64));
+    memLock_.release();
+    if (!result) {
+        release();
+        return false;
+    }
+
+    /*
+     * This is the primary instance,
+     * listen for subsequent instances.
+     */
+    QLocalServer::removeServer(key_);
+    server_ = new QLocalServer();
+    server_->setSocketOptions(QLocalServer::UserAccessOption);
+    server_->listen(key_);
+    QObject::connect(server_,
+                     &QLocalServer::newConnection,
+                     this,
+                     &RunGuard::tryRestorePrimaryInstance);
+#endif
+
+    return true;
+}
+
+void
+RunGuard::release()
+{
+    memLock_.acquire();
+    if (sharedMem_.isAttached())
+        sharedMem_.detach();
+    memLock_.release();
+}
\ No newline at end of file