Commit a2276d31 authored by Volker Krause's avatar Volker Krause
Browse files

Add support for updating pkpass files from their corresponding web service

parent ed26ac8f
......@@ -10,6 +10,7 @@ ecm_qt_declare_logging_category(itinerary_srcs
add_library(itinerary STATIC ${itinerary_srcs})
target_link_libraries(itinerary PUBLIC
KPkPass
Qt5::Network
)
add_executable(itinerary-app
......
......@@ -49,6 +49,13 @@ Kirigami.ApplicationWindow {
onTriggered: {
fileDialog.visible = true;
}
},
Kirigami.Action {
text: qsTr("Check for Updates")
iconName: "view-refresh"
onTriggered: {
_pkpassManager.updatePasses();
}
}
]
}
......
......@@ -24,12 +24,16 @@
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QStandardPaths>
#include <QTemporaryFile>
#include <QUrl>
#include <QVector>
PkPassManager::PkPassManager(QObject* parent)
: QObject(parent)
, m_nam(new QNetworkAccessManager(this))
{
}
......@@ -89,25 +93,37 @@ void PkPassManager::doImportPass(const QUrl& url, PkPassManager::ImportMode mode
if (file->passTypeIdentifier().isEmpty() || file->serialNumber().isEmpty())
return; // TODO error handling
// TODO check for already existing version
QDir dir(basePath);
dir.mkdir(file->passTypeIdentifier());
dir.cd(file->passTypeIdentifier());
// serialNumber() can contain percent-encoding or slashes,
// ie stuff we don't want to have in file names
const auto passId = QString::fromUtf8(file->serialNumber().toUtf8().toBase64(QByteArray::Base64UrlEncoding));
const auto serNum = QString::fromUtf8(file->serialNumber().toUtf8().toBase64(QByteArray::Base64UrlEncoding));
const QString passId = dir.dirName() + QLatin1Char('/') + serNum;
auto oldPass = pass(passId);
if (oldPass) {
QFile::remove(dir.absoluteFilePath(serNum + QLatin1String(".pkpass")));
m_passes.remove(passId);
}
switch (mode) {
case Move:
QFile::rename(url.toLocalFile(), dir.absoluteFilePath(passId + QLatin1String(".pkpass")));
QFile::rename(url.toLocalFile(), dir.absoluteFilePath(serNum + QLatin1String(".pkpass")));
break;
case Copy:
QFile::copy(url.toLocalFile(), dir.absoluteFilePath(passId + QLatin1String(".pkpass")));
QFile::copy(url.toLocalFile(), dir.absoluteFilePath(serNum + QLatin1String(".pkpass")));
break;
}
emit passAdded(dir.dirName() + QLatin1Char('/') + passId);
if (oldPass) {
// TODO check for changes and generate change message
emit passUpdated(passId);
oldPass->deleteLater();
} else {
emit passAdded(passId);
}
}
void PkPassManager::removePass(const QString& passId)
......@@ -116,3 +132,38 @@ void PkPassManager::removePass(const QString& passId)
QFile::remove(basePath + QLatin1Char('/') + passId + QLatin1String(".pkpass"));
// TODO change signal
}
void PkPassManager::updatePass(const QString& passId)
{
auto p = pass(passId);
if (!p || p->webServiceUrl().isEmpty() || p->authenticationToken().isEmpty())
return;
if (p->relevantDate() < QDateTime::currentDateTimeUtc()) // TODO check expiration date and voided property
return;
QUrl url(p->webServiceUrl());
url.setPath(url.path() + QLatin1String("/v1/passes/") + p->passTypeIdentifier() + QLatin1Char('/') + p->serialNumber());
qCDebug(Log) << "GET" << url;
QNetworkRequest req(url);
req.setRawHeader("Authorization", "ApplePass " + p->authenticationToken().toUtf8());
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
auto reply = m_nam->get(req);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
if (reply->error() != QNetworkReply::NoError) {
qCWarning(Log) << "Failed to download pass:" << reply->errorString();
return;
}
QTemporaryFile tmp;
tmp.open();
tmp.write(reply->readAll());
tmp.close();
importPassFromTempFile(tmp.fileName());
});
}
void PkPassManager::updatePasses()
{
for (const auto &passId : passes())
updatePass(passId);
}
......@@ -21,6 +21,8 @@
#include <QHash>
#include <QObject>
class QNetworkAccessManager;
namespace KPkPass {
class Pass;
}
......@@ -40,14 +42,19 @@ public:
void importPassFromTempFile(const QString &tmpFile);
Q_INVOKABLE void removePass(const QString &passId);
void updatePass(const QString &passId);
Q_INVOKABLE void updatePasses();
signals:
void passAdded(const QString &passId);
void passUpdated(const QString &passId);
private:
enum ImportMode { Copy, Move };
void doImportPass(const QUrl &url, ImportMode mode);
QHash<QString, KPkPass::Pass*> m_passes;
QNetworkAccessManager *m_nam;
};
#endif // PKPASSMANAGER_H
......@@ -39,6 +39,7 @@ void TimelineModel::setPkPassManager(PkPassManager* mgr)
return m_mgr->pass(lhs)->relevantDate() > m_mgr->pass(rhs)->relevantDate();
});
connect(mgr, &PkPassManager::passAdded, this, &TimelineModel::passAdded);
connect(mgr, &PkPassManager::passUpdated, this, &TimelineModel::passUpdated);
endResetModel();
}
......@@ -74,7 +75,7 @@ QHash<int, QByteArray> TimelineModel::roleNames() const
return names;
}
void TimelineModel::passAdded(const QString& passId)
void TimelineModel::passAdded(const QString &passId)
{
auto it = std::lower_bound(m_passes.begin(), m_passes.end(), passId, [this](const QString &lhs, const QString &rhs) {
return m_mgr->pass(lhs)->relevantDate() > m_mgr->pass(rhs)->relevantDate();
......@@ -84,3 +85,12 @@ void TimelineModel::passAdded(const QString& passId)
m_passes.insert(it, passId);
endInsertRows();
}
void TimelineModel::passUpdated(const QString &passId)
{
auto it = std::lower_bound(m_passes.begin(), m_passes.end(), passId, [this](const QString &lhs, const QString &rhs) {
return m_mgr->pass(lhs)->relevantDate() > m_mgr->pass(rhs)->relevantDate();
});
auto row = std::distance(m_passes.begin(), it);
emit dataChanged(index(row, 0), index(row, 0));
}
......@@ -42,6 +42,7 @@ public:
private:
void passAdded(const QString &passId);
void passUpdated(const QString &passId);
PkPassManager *m_mgr = nullptr;
QVector<QString> m_passes;
......
......@@ -314,6 +314,16 @@ QDateTime Pass::relevantDate() const
return QDateTime::fromString(d->passObj.value(QLatin1String("relevantDate")).toString(), Qt::ISODate);
}
QString Pass::authenticationToken() const
{
return d->passObj.value(QLatin1String("authenticationToken")).toString();
}
QString Pass::webServiceUrl() const
{
return d->passObj.value(QLatin1String("webServiceURL")).toString();
}
QVector<Barcode> Pass::barcodes() const
{
QVector<Barcode> codes;
......
......@@ -58,6 +58,9 @@ class KPKPASS_EXPORT Pass : public QObject
Q_PROPERTY(QString logoText READ logoText CONSTANT)
Q_PROPERTY(QDateTime relevantDate READ relevantDate CONSTANT)
Q_PROPERTY(QString authenticationToken READ authenticationToken CONSTANT)
Q_PROPERTY(QString webServiceUrl READ webServiceUrl CONSTANT)
// needs to be QVariantList just for QML (Grantlee would also work with QVector<Field>
Q_PROPERTY(QVariantList auxiliaryFields READ auxiliaryFieldsVariant CONSTANT)
Q_PROPERTY(QVariantList backFields READ backFieldsVariant CONSTANT)
......@@ -91,6 +94,9 @@ public:
QImage logo(unsigned int devicePixelRatio = 1) const;
QDateTime relevantDate() const;
QString authenticationToken() const;
QString webServiceUrl() const;
QVector<Field> auxiliaryFields() const;
QVector<Field> backFields() const;
QVector<Field> headerFields() const;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment