Commit 4713eeb7 authored by Johannes Schwab's avatar Johannes Schwab

Merge branch 'packaging' into develop

parents 19b3195a 7fff4605
......@@ -4,7 +4,8 @@ android-ndk-toolchain
android-sources/AndroidManifest.xml
android-sources/assets/
androidlibs
client/Makefile
build_deb/
client/Makefile*
client/android-libopenrecipes.so-deployment-settings.json
client/android_arm
client/android_x86
......@@ -18,13 +19,14 @@ client/openrecipes
client/qrc_client.cpp
client/target_wrapper.sh
doxygen/
lib/Makefile*
lib/doxygen/
lib/libopenrecipes.a
lib/moc/
lib/obj/
lib/src/config.h
libsodium-*
server/Makefile
server/Makefile*
server/doxygen/
server/moc/
server/obj/
......@@ -32,3 +34,6 @@ server/openrecipesserver
server/protocol.aux
server/protocol.log
server/protocol.pdf
windows/*.msi
windows/*.wixobj
windows/data/
......@@ -38,6 +38,6 @@ You also need to apply the latest db schema from server/dbSchema to the database
While the server is running, he will print out some status information when receiving SIGUSR1.
To build from source you will need:
- Qt >= 5.10
- Qt >= 5.11
- libsodium >= 1.0.12
- libqrencode
#! /bin/bash
VERSION=0.2.1
REVISION=1
mkdir build_deb
git archive --prefix=openrecipes-${VERSION}/ --output build_deb/openrecipes_${VERSION}.orig.tar.gz HEAD
cd build_deb
tar -xf openrecipes_${VERSION}.orig.tar.gz
cd openrecipes-${VERSION}
dpkg-buildpackage -S
cd ..
lintian --profile debian openrecipes_${VERSION}-${REVISION}.dsc && echo "lintian .dsc -> ok"
lintian --profile debian openrecipes_${VERSION}-${REVISION}_source.changes && echo "lintian .changes -> ok"
cd lib
qmake -tp vc lib.pro "CONFIG+=windeployqt"
cd ..\client
qmake -tp vc client.pro "CONFIG+=windeployqt"
qmake
make
mkdir windows\data
copy client\release\openrecipes.exe windows\data
copy libsodium-win64\bin\libsodium-23.dll windows\data
cd windows\data
windeployqt --release --qmldir ..\..\client\qml openrecipes.exe
cd ..
Paraffin.exe -update paraffin.wxs
candle.exe paraffin.wxs
candle.exe client.wxs
light.exe -out openrecipes-0.2.1.msi -ext WixUIExtension client.wixobj paraffin.wixobj
ECHO "DON'T FORGET TO UPDATE PRODUCT AND PACKAGE guidS BEFOR RELEASEING!!!"
......@@ -40,7 +40,13 @@ RESOURCES += client.qrc
unix:!android: PKGCONFIG += libqrencode
win32 {
error("TODO: add libqrencode")
warning("TODO: add libqrencode")
HEADERS += \
src/WindowsSelfUpdate.h
SOURCES += \
src/WindowsSelfUpdate.cpp
}
android {
......@@ -79,20 +85,7 @@ INSTALLS += target
!android: INSTALLS += icon desktop
win32 {
WINRT_MANIFEST.background = "$${LITERAL_HASH}FF5722"
WINRT_MANIFEST.capabilities += internetClient
WINRT_MANIFEST.description = "Your privacy friendly cook book."
WINRT_MANIFEST.logo_store = ../android-sources/res/mipmap-xxxhdpi/ic_launcher.png
WINRT_MANIFEST.iconic_tile_small = ../android-sources/res/mipmap-xhdpi/ic_launcher.png
WINRT_MANIFEST.iconic_tile_icon = ../android-sources/res/mipmap-xxxhdpi/ic_launcher.png
WINRT_MANIFEST.identity = "42e2db86-3363-4626-9fc0-f962593183b7"
WINRT_MANIFEST.name = "OpenRecipes"
WINRT_MANIFEST.publisher = "Johannes Schwab"
WINRT_MANIFEST.minVersion = "10240"
WINRT_MANIFEST.version = "$${VERSION}.0"
CONFIG(release, debug|release): libsodium_dll.files += $$PWD/../libsodium/Win32/Release/v140/dynamic/libsodium.dll
CONFIG(debug, debug|release): libsodium_dll.files += $$PWD/../libsodium/Win32/Debug/v140/dynamic/libsodium.dll
DEPLOYMENT += libsodium_dll
RC_FILE = windows_icon.rc
CONFIG += windeployqt
}
......
This diff is collapsed.
[Desktop Entry]
Name=OpenRecipes
GenericName=Cookbook
GenericName[de]=Kochbuch
Exec=openrecipes
Icon=openrecipes
Terminal=false
Type=Application
StartupNotify=true
Categories=Utility,Qt;
Categories=Utility;Qt;
......@@ -30,6 +30,80 @@ ApplicationWindow {
visible: true
title: Qt.application.name
ModalDialog {
id: updateDialog
property string version: "0.0.0"
standardButtons: Dialog.Yes | Dialog.No
title: qsTr("Update available")
ColumnLayout {
anchors.fill: parent
Label {
Layout.maximumWidth: mainWindow.width - 30
Layout.alignment: Qt.AlignHCenter
text: qsTr("An update to version %1 is available. Do you wan't to install it and restart OpenRecipes? (Please make shure to save any unsaved changes beforhand.)").arg(updateDialog.version);
wrapMode: Text.Wrap
}
}
onAccepted: {
updateDownloadDialog.open();
backend.update(version)
}
}
ModalDialog {
id: updateDownloadDialog
standardButtons: Dialog.NoButton
title: qsTr("Downloading update…")
ColumnLayout {
anchors.fill: parent
ProgressBar {
Layout.alignment: Qt.AlignHCenter
indeterminate: true
}
Label {
Layout.maximumWidth: mainWindow.width - 30
Layout.alignment: Qt.AlignHCenter
text: qsTr("Please wait while the update is downloaded.")
wrapMode: Text.Wrap
}
}
Connections {
target: backend
onUpdateDownloadFailed: {
updateDownloadFailedDialog.open();
updateDownloadDialog.accept();
}
}
}
ModalDialog {
id: updateDownloadFailedDialog
standardButtons: Dialog.Ok
title: qsTr("Update failed")
ColumnLayout {
anchors.fill: parent
Label {
Layout.maximumWidth: mainWindow.width - 30
Layout.alignment: Qt.AlignHCenter
text: qsTr("The download of the update failed.")
wrapMode: Text.Wrap
}
}
}
StackView {
id: stackView
anchors.fill: parent
......@@ -76,5 +150,9 @@ ApplicationWindow {
mainRowLayout.selectRecipe(backend.getRecipe(id));
recipesListView.highlightRecipe(id);
}
onUpdateAvailable: function(version) {
updateDialog.version = version;
updateDialog.open();
}
}
}
......@@ -34,6 +34,11 @@
#include <QAndroidJniEnvironment>
#endif
#ifdef Q_OS_WIN
#include "WindowsSelfUpdate.h"
#include <QTimer>
#endif
Backend::Backend()
:
synchronizeAsync(nullptr),
......@@ -50,6 +55,11 @@ Backend::Backend()
emit recipeImported(id);
});
#endif
#ifdef Q_OS_WIN
WindowsSelfUpdate::cleanup();
QTimer::singleShot(0, this, Backend::checkForUpdates);
#endif
}
QQmlListProperty<Recipe> Backend::getRecipesList() {
......@@ -432,3 +442,33 @@ bool Backend::saveRecipeToFile(Recipe *recipe, const QString &path) {
return true;
}
#endif
#ifdef Q_OS_WIN
void Backend::checkForUpdates() {
qDebug() << "Backend Thread is" << QThread::currentThread();
CheckForUpdateAsync *cfu = new CheckForUpdateAsync(this);
connect(cfu, CheckForUpdateAsync::updateAvailable, [&](const QVersionNumber &version) {
emit updateAvailable(version.toString());
});
connect(cfu, CheckForUpdateAsync::noUpdateAvailable, [&](bool suc) {
qDebug() << "No Update (checking succeeded: " << suc << ")";
});
connect(cfu, QThread::finished, cfu, QObject::deleteLater);
cfu->start();
}
void Backend::update(const QString &version) {
UpdateAsync *u;
try {
u = new UpdateAsync(this, QVersionNumber::fromString(version));
} catch (const InternalError &e) {
emit updateDownloadFailed();
return;
}
connect(u, UpdateAsync::downloadFailed, [&]() {
emit updateDownloadFailed();
});
connect(u, QThread::finished, u, QObject::deleteLater);
u->start();
}
#endif
......@@ -26,6 +26,9 @@
#ifdef Q_OS_ANDROID
#include <QAndroidActivityResultReceiver>
#endif
#ifdef Q_OS_WIN
#include <QVersionNumber>
#endif
#include "Recipe.h"
#include "SqlBackend.h"
......@@ -68,6 +71,11 @@ class Backend : public QObject {
void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override;
#endif
#ifdef Q_OS_WIN
private slots:
void checkForUpdates();
#endif
public:
Backend();
QQmlListProperty<Recipe> getRecipesList();
......@@ -98,6 +106,9 @@ class Backend : public QObject {
Q_INVOKABLE void commitTransaction();
Q_INVOKABLE void rollbackTransaction();
Q_INVOKABLE void sendSyncKey();
#ifdef Q_OS_WIN
Q_INVOKABLE void update(const QString &version);
#endif
#ifdef Q_OS_ANDROID
Q_INVOKABLE void shareRecipe(Recipe *recipe);
Q_INVOKABLE void requestImage();
......@@ -121,6 +132,10 @@ class Backend : public QObject {
void syncSettingsChanged();
void filterChanged();
void recipeImported(const QByteArray &id);
/* Only used on windows. */
void updateAvailable(const QString &version);
void updateDownloadFailed();
};
#endif //BACKEND_H
......@@ -59,3 +59,11 @@ QString Globals::getMinimumQtVersionStr() {
QVersionNumber Globals::getMinimumQtVersion() {
return QVersionNumber(5, 10, 0);
}
QByteArray Globals::getUpdateSigningKey() {
return QByteArray::fromHex("5C86B2FD68F44CE7446CBFF5F4589B5985F39B725AE852DCF15E4CE6EB9A2E26");
}
QVersionNumber Globals::getVersion() {
return QVersionNumber(0, 2, 1);
}
......@@ -40,6 +40,8 @@ class Globals : public QObject {
static QString getDefaultServerHostName();
static quint16 getDefaultServerPort();
static QByteArray getDefaultServerKey();
static QVersionNumber getVersion();
static QByteArray getUpdateSigningKey();
};
#endif //GLOBALS_H
......@@ -18,7 +18,9 @@
#include "QREncoder.h"
#ifndef Q_OS_WIN
#include <qrencode.h>
#endif
#include <QImage>
#include <cassert>
......@@ -28,6 +30,7 @@ QREncoder::QREncoder()
{}
QImage QREncoder::requestImage(const QString &id, QSize *size, const QSize &requestedSize) {
#ifndef Q_OS_WIN
QRcode *qrcode = QRcode_encodeString(id.toStdString().c_str(), 0, QR_ECLEVEL_L, QR_MODE_8, 1);
if (!qrcode) return QImage();
......@@ -56,4 +59,10 @@ QImage QREncoder::requestImage(const QString &id, QSize *size, const QSize &requ
*size = image.size();
return image;
#else
Q_UNUSED(id);
Q_UNUSED(size);
Q_UNUSED(requestedSize);
return QImage();
#endif
}
/*
* OpenRecipes
* Copyright (C) 2018 Johannes Schwab
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "WindowsSelfUpdate.h"
#include "InternalError.h"
#include "Globals.h"
#include <QApplication>
#include <fstream>
#include <QProcess>
#include <QString>
#include <QList>
#include <QByteArray>
#include <QtNetwork>
#include <sodium.h>
QString WindowsSelfUpdate::getLocalMsiFileName() {
const QString dirPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
return QDir::cleanPath(dirPath + QDir::separator() + "update.msi");
}
void WindowsSelfUpdate::cleanup() {
if (QFileInfo::exists(getLocalMsiFileName())) QFile::remove(getLocalMsiFileName());
}
QByteArray WindowsSelfUpdate::getFileContent(const QString &url) {
QNetworkReply *reply = getReply(url);
reply->deleteLater();
return reply->readAll();
}
void WindowsSelfUpdate::getFile(const QString &url, const QString &saveTo) {
QNetworkReply *reply = getReply(url);
QFile file(saveTo);
if (!file.open(QIODevice::NewOnly | QIODevice::WriteOnly)) throw IERROR("Can't open temporary file");
qDebug() << "Saving to" << saveTo;
file.write(reply->readAll());
reply->deleteLater();
}
QNetworkReply* WindowsSelfUpdate::getReply(const QString &url) {
qDebug() << "Requesting" << url;
QNetworkAccessManager *networkManager = new QNetworkAccessManager;
const QUrl u(url);
const QNetworkRequest request(u);
QNetworkReply *reply = networkManager->get(request);
QEventLoop loop;
QObject::connect(reply, QNetworkReply::finished, &loop, QEventLoop::quit);
QObject::connect(reply, QNetworkReply::sslErrors, [&](){loop.quit(); throw IERROR("SSL Error");});
if (!reply->isFinished()) loop.exec();
networkManager->deleteLater();
if (reply->error()) throw IERROR(QString("Download failed: %1").arg(reply->errorString()));
return reply;
}
CheckForUpdateAsync::CheckForUpdateAsync(QObject *parent)
:
QThread(parent)
{}
void CheckForUpdateAsync::run() {
qDebug() << "Checking for updates...";
QVersionNumber curVer;
try {
curVer = QVersionNumber::fromString(QString::fromLatin1(getFileContent("https://api.openrecipes.jschwab.org/currentClientVersion.txt")));
} catch (const InternalError &e) {
emit noUpdateAvailable(false);
return;
}
if (curVer > Globals::getVersion()) {
emit updateAvailable(curVer);
} else {
emit noUpdateAvailable(true);
}
}
UpdateAsync::UpdateAsync(QObject *parent, const QVersionNumber &version)
:
QThread(parent),
version(version)
{
if (version < Globals::getVersion()) throw IERROR("Won't downgrade");
}
void UpdateAsync::run() {
qDebug() << "Starting updates...";
cleanup();
const QString msiPath = getLocalMsiFileName();
QByteArray sig;
try {
const QByteArray clientInfo = getFileContent(QString("https://api.openrecipes.jschwab.org/windowsClientInfo/%1.txt").arg(version.toString()));
const QList<QByteArray> clientInfoSplit = clientInfo.split('\n');
if (clientInfoSplit.size() < 2) throw IERROR("Invalid version info file");
const QString msiUrl = QString::fromLatin1(clientInfoSplit.at(0));
sig = QByteArray::fromHex(clientInfoSplit.at(1));
getFile(msiUrl, msiPath);
} catch (const InternalError &e) {
emit downloadFailed();
return;
}
qDebug() << "Update dowload finished, checking signature...";
std::ifstream msiFile(msiPath.toStdString(), std::ios::in | std::ios::binary);
unsigned char chunk[1024];
crypto_sign_state state;
crypto_sign_init(&state);
while (msiFile.good()) {
msiFile.read(reinterpret_cast<char*>(chunk), sizeof(chunk));
crypto_sign_update(&state, chunk, msiFile.gcount());
}
assert(static_cast<size_t>(Globals::getUpdateSigningKey().size()) >= crypto_sign_PUBLICKEYBYTES);
if (crypto_sign_final_verify(&state, reinterpret_cast<const unsigned char*>(sig.constData()), reinterpret_cast<const unsigned char*>(Globals::getUpdateSigningKey().constData())) != 0) {
qDebug() << "Signature is invalid";
emit downloadFailed();
return;
}
qDebug() << "Signature looks fine, starting update";
if (QProcess::startDetached(QString("msiexec.exe /i %1").arg(QDir::toNativeSeparators(msiPath)))) {
QApplication::quit();
return;
}
qDebug() << "Couldn't start update";
emit downloadFailed();
return;
}
/*
* OpenRecipes
* Copyright (C) 2018 Johannes Schwab
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef WINDOWS_SELF_UPDATE_H
#define WINDOWS_SELF_UPDATE_H
#include <QObject>
#include <QThread>
#include <QVersionNumber>
class QNetworkReply;
class WindowsSelfUpdate {
private:
QNetworkReply* getReply(const QString &url);
protected:
static QString getLocalMsiFileName();
void getFile(const QString &url, const QString &saveTo);
QByteArray getFileContent(const QString &url);
public:
static void cleanup();
};
class CheckForUpdateAsync : public QThread, public WindowsSelfUpdate {
Q_OBJECT
public:
CheckForUpdateAsync(QObject *parent);
void run() override;
signals:
void updateAvailable(const QVersionNumber &version);
void noUpdateAvailable(bool checkSucceeded);
};
class UpdateAsync : public QThread, public WindowsSelfUpdate {
Q_OBJECT
private:
const QVersionNumber version;
public:
UpdateAsync(QObject *parent, const QVersionNumber &version);
void run() override;
signals:
void downloadFailed();
};
#endif //WINDOWS_SELF_UPDATE_H
IDI_ICON1 ICON DISCARDABLE "img/icon.ico"
......@@ -27,10 +27,10 @@ unix:!android {
}
win32 {
CONFIG(release, debug|release): LIBS += -L$$PWD/libsodium/Win32/Release/v140/dynamic -llibsodium
CONFIG(debug, debug|release): LIBS += -L$$PWD/libsodium/Win32/Debug/v140/dynamic -llibsodium
CONFIG(debug, debug|release): CONFIG += console
INCLUDEPATH += $$PWD/libsodium/include
#TODO: use pkgconfig
LIBS += -L$$PWD/libsodium-win64/bin -lsodium-23
INCLUDEPATH += $$PWD/libsodium-win64/include
CONFIG(debug, debug|release): CONFIG += console
}
android {
......
openrecipes (0.2.1-1) unstable; urgency=low
* Initial release
-- Johannes Schwab <[email protected]> Wed, 09 Jan 2019 17:34:23 +0200
openrecipes (0.1.0-1) unstable; urgency=low
* Initial release
-- Johannes Schwab <mail@jschwab.org> Fri, 24 Aug 2018 17:34:23 +0200
-- Johannes Schwab <openrecipes@jschwab.org> Fri, 24 Aug 2018 17:34:23 +0200
Source: openrecipes
Section: misc
Priority: optional
Maintainer: Johannes Schwab <mail@jschwab.org>
Build-Depends: debhelper (>= 9), qtbase5-dev (>= 5.10), qtquickcontrols2-5-dev (>= 5.10), libqt5svg5-dev (>= 5.10), qtdeclarative5-dev (>= 5.10), qttools5-dev-tools (>= 5.10), libsodium-dev (>= 1.0.16), pkg-config
Standards-Version: 3.9.6
Maintainer: Johannes Schwab <openrecipes@jschwab.org>
Build-Depends: debhelper (>= 9), qtbase5-dev (>= 5.11), qtquickcontrols2-5-dev (>= 5.11), libqt5svg5-dev (>= 5.11), qtdeclarative5-dev (>= 5.11), qttools5-dev-tools (>= 5.11), libsodium-dev (>= 1.0.16), pkg-config, libqrencode-dev
Standards-Version: 4.2.1
Homepage: https://openrecipes.jschwab.org
Package: openrecipes
Architecture: any
Depends: ${misc:Depends}, ${shlibs:Depends}, libqt5core5a (>= 5.10), libqt5network5 (>= 5.10), libsodium23 (>= 1.0.16), qml-module-qtquick-controls2 (>= 5.10), libqt5svg5 (>= 5.10), qml-module-qtquick-layouts (>= 5.10)
Description: OpenRecipes is an personal cookbook.
Depends: ${misc:Depends}, ${shlibs:Depends}, libqt5core5a (>= 5.11), libqt5network5 (>= 5.11), libsodium23 (>= 1.0.16), qml-module-qtquick-controls2 (>= 5.11), libqt5svg5 (>= 5.11), qml-module-qtquick-layouts (>= 5.11), qml-module-qt-labs-platform (>= 5.11), libqrencode3
Description: Personal and private friendly cookbook.
This is the client.
Package: openrecipesserver
Architecture: any
Depends: ${misc:Depends}, ${shlibs:Depends}, libqt5core5a (>= 5.10), libqt5network5 (>= 5.10), libsodium23 (>= 1.0.16), mysql-server
Description: OpenRecipes is an personal cookbook.
Depends: ${misc:Depends}, ${shlibs:Depends}, libqt5core5a (>= 5.11), libqt5network5 (>= 5.11), libsodium23 (>= 1.0.16), default-mysql-server
Description: Personal and private friendly cookbook.
This is the server.
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: chordival
Source: http://github.com/ddorian1/chordival
Upstream-Name: openrecipes
Source: http://gitlab.com/ddorian/openrecipes
Files: *
Copyright: 2014 Johannes Schwab <mail@jschwab.org>
License: GPL-2+
Copyright: 2019 Johannes Schwab <openrecipes@jschwab.org>
License: GPL-3+
This package 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 2 of the License, or
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
......@@ -19,4 +19,4 @@ License: GPL-2+
along with this program. If not, see <http://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
......@@ -2,6 +2,3 @@
# Compulsory line, this is a version 3 file
version=3
#http://jschwab.org/chordival/downloads.php chordival-(.*)\.tar\.gz
#TODO