diff options
| author | Tomasz Sterna <tomek@xiaoka.com> | 2015-01-07 00:37:15 +0100 |
|---|---|---|
| committer | Tomasz Sterna <tomek@xiaoka.com> | 2015-01-07 00:37:15 +0100 |
| commit | 16ddb4d6ca5742aa36112f187e36a039a7357460 (patch) | |
| tree | fc8b0acdf1883f5d34863a8c063b2d642d1fe100 /daemon/appinfo.cpp | |
| parent | a55f8f218ea9b52e97b9b7de1ac43502ce8c9994 (diff) | |
Refactored whole app handling into AppInfo class
Diffstat (limited to 'daemon/appinfo.cpp')
| -rw-r--r-- | daemon/appinfo.cpp | 309 |
1 files changed, 249 insertions, 60 deletions
diff --git a/daemon/appinfo.cpp b/daemon/appinfo.cpp index 587ed8b..91467b0 100644 --- a/daemon/appinfo.cpp +++ b/daemon/appinfo.cpp @@ -1,9 +1,23 @@ #include <QSharedData> #include <QBuffer> +#include <QDir> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> #include "appinfo.h" +#include "unpacker.h" +#include "stm32crc.h" + +namespace { +struct ResourceEntry { + int index; + quint32 offset; + quint32 length; + quint32 crc; +}; +} struct AppInfoData : public QSharedData { - bool local; QUuid uuid; QString shortName; QString longName; @@ -15,17 +29,21 @@ struct AppInfoData : public QSharedData { AppInfo::Capabilities capabilities; QHash<QString, int> keyInts; QHash<int, QString> keyNames; - QImage menuIcon; + bool menuIcon; + int menuIconResource; QString path; }; +QLoggingCategory AppInfo::l("AppInfo"); + AppInfo::AppInfo() : d(new AppInfoData) { - d->local = false; d->versionCode = 0; d->watchface = false; d->jskit = false; d->capabilities = 0; + d->menuIcon = false; + d->menuIconResource = -1; } AppInfo::AppInfo(const AppInfo &rhs) : d(rhs.d) @@ -45,22 +63,22 @@ AppInfo::~AppInfo() bool AppInfo::isLocal() const { - return d->local; + return ! d->path.isEmpty(); } -void AppInfo::setLocal(const bool local) +bool AppInfo::isValid() const { - d->local = local; + return ! d->uuid.isNull(); } -QUuid AppInfo::uuid() const +void AppInfo::setInvalid() { - return d->uuid; + d->uuid = QUuid(); // Clear the uuid to force invalid app } -void AppInfo::setUuid(const QUuid &uuid) +QUuid AppInfo::uuid() const { - d->uuid = uuid; + return d->uuid; } QString AppInfo::shortName() const @@ -68,81 +86,41 @@ QString AppInfo::shortName() const return d->shortName; } -void AppInfo::setShortName(const QString &string) -{ - d->shortName = string; -} - QString AppInfo::longName() const { return d->longName; } -void AppInfo::setLongName(const QString &string) -{ - d->longName = string; -} - QString AppInfo::companyName() const { return d->companyName; } -void AppInfo::setCompanyName(const QString &string) -{ - d->companyName = string; -} - int AppInfo::versionCode() const { return d->versionCode; } -void AppInfo::setVersionCode(int code) -{ - d->versionCode = code; -} - QString AppInfo::versionLabel() const { return d->versionLabel; } -void AppInfo::setVersionLabel(const QString &string) -{ - d->versionLabel = string; -} - bool AppInfo::isWatchface() const { return d->watchface; } -void AppInfo::setWatchface(bool b) -{ - d->watchface = b; -} - bool AppInfo::isJSKit() const { return d->jskit; } -void AppInfo::setJSKit(bool b) -{ - d->jskit = b; -} - AppInfo::Capabilities AppInfo::capabilities() const { return d->capabilities; } -void AppInfo::setCapabilities(Capabilities caps) -{ - d->capabilities = caps; -} - void AppInfo::addAppKey(const QString &key, int value) { d->keyInts.insert(key, value); @@ -169,32 +147,243 @@ int AppInfo::valueForAppKey(const QString &key) const return d->keyInts.value(key, -1); } -QImage AppInfo::menuIcon() const +bool AppInfo::hasMenuIcon() const +{ + return d->menuIcon && d->menuIconResource >= 0; +} + +QImage AppInfo::getMenuIconImage() const { - return d->menuIcon; + if (hasMenuIcon()) { + QByteArray data = extractFromResourcePack( + QDir(d->path).filePath("app_resources.pbpack"), d->menuIconResource); + if (!data.isEmpty()) { + return decodeResourceImage(data); + } + } + + return QImage(); } -QByteArray AppInfo::menuIconAsPng() const +QByteArray AppInfo::getMenuIconPng() const { QByteArray data; QBuffer buf(&data); buf.open(QIODevice::WriteOnly); - d->menuIcon.save(&buf, "PNG"); + getMenuIconImage().save(&buf, "PNG"); buf.close(); return data; } -void AppInfo::setMenuIcon(const QImage &img) +QString AppInfo::getJSApp() const +{ + if (!isValid() || !isLocal()) return QString(); + + QFile file(d->path + "/pebble-js-app.js"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCWarning(l) << "Failed to load JS file:" << file.fileName(); + return QString(); + } + + return QString::fromUtf8(file.readAll()); + +} + +AppInfo AppInfo::fromPath(const QString &path) +{ + AppInfo info; + + QDir appDir(path); + if (!appDir.isReadable()) { + qCWarning(l) << "app" << appDir.absolutePath() << "is not readable"; + return info; + } + + QFile appInfoFile(path + "/appinfo.json"); + if (!appInfoFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCWarning(l) << "cannot open app info file" << appInfoFile.fileName() << ":" + << appInfoFile.errorString(); + return info; + } + + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(appInfoFile.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qCWarning(l) << "cannot parse app info file" << appInfoFile.fileName() << ":" + << parseError.errorString(); + return info; + } + + const QJsonObject root = doc.object(); + info.d->uuid = QUuid(root["uuid"].toString()); + info.d->shortName = root["shortName"].toString(); + info.d->longName = root["longName"].toString(); + info.d->companyName = root["companyName"].toString(); + info.d->versionCode = root["versionCode"].toInt(); + info.d->versionLabel = root["versionLabel"].toString(); + + const QJsonObject watchapp = root["watchapp"].toObject(); + info.d->watchface = watchapp["watchface"].toBool(); + info.d->jskit = appDir.exists("pebble-js-app.js"); + + if (root.contains("capabilities")) { + const QJsonArray capabilities = root["capabilities"].toArray(); + AppInfo::Capabilities caps = 0; + for (auto it = capabilities.constBegin(); it != capabilities.constEnd(); ++it) { + QString cap = (*it).toString(); + if (cap == "location") caps |= AppInfo::Location; + if (cap == "configurable") caps |= AppInfo::Configurable; + } + info.d->capabilities = caps; + } + + if (root.contains("appKeys")) { + const QJsonObject appkeys = root["appKeys"].toObject(); + for (auto it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) { + info.addAppKey(it.key(), it.value().toInt()); + } + } + + if (root.contains("resources")) { + const QJsonObject resources = root["resources"].toObject(); + const QJsonArray media = resources["media"].toArray(); + int index = 0; + + for (auto it = media.constBegin(); it != media.constEnd(); ++it) { + const QJsonObject res = (*it).toObject(); + const QJsonValue menuIcon = res["menuIcon"]; + + switch (menuIcon.type()) { + case QJsonValue::Bool: + info.d->menuIcon = menuIcon.toBool(); + info.d->menuIconResource = index; + break; + case QJsonValue::String: + info.d->menuIcon = !menuIcon.toString().isEmpty(); + info.d->menuIconResource = index; + break; + default: + break; + } + + index++; + } + } + + info.d->path = path; + + if (info.uuid().isNull() || info.shortName().isEmpty()) { + qCWarning(l) << "invalid or empty uuid/name in" << appInfoFile.fileName(); + return AppInfo(); + } + + return info; +} + +AppInfo AppInfo::fromSlot(const BankManager::SlotInfo &slot) { - d->menuIcon = img; + AppInfo info; + + info.d->uuid = QUuid::createUuid(); + info.d->shortName = slot.name; + info.d->companyName = slot.company; + info.d->versionCode = slot.version; + info.d->capabilities = AppInfo::Capabilities(slot.flags); + + return info; +} + +QByteArray AppInfo::extractFromResourcePack(const QString &file, int wanted_id) const +{ + QFile f(file); + if (!f.open(QIODevice::ReadOnly)) { + qCWarning(l) << "cannot open resource file" << f.fileName(); + return QByteArray(); + } + + QByteArray data = f.readAll(); + Unpacker u(data); + + int num_files = u.readLE<quint32>(); + u.readLE<quint32>(); // crc for entire file + u.readLE<quint32>(); // timestamp + + qCDebug(l) << "reading" << num_files << "resources from" << file; + + QList<ResourceEntry> table; + + for (int i = 0; i < num_files; i++) { + ResourceEntry e; + e.index = u.readLE<quint32>(); + e.offset = u.readLE<quint32>(); + e.length = u.readLE<quint32>(); + e.crc = u.readLE<quint32>(); + + if (u.bad()) { + qCWarning(l) << "short read on resource file"; + return QByteArray(); + } + + table.append(e); + } + + if (wanted_id >= table.size()) { + qCWarning(l) << "specified resource does not exist"; + return QByteArray(); + } + + const ResourceEntry &e = table[wanted_id]; + + int offset = 12 + 256 * 16 + e.offset; + + QByteArray res = data.mid(offset, e.length); + + Stm32Crc crc; + crc.addData(res); + + if (crc.result() != e.crc) { + qCWarning(l) << "CRC failure in resource" << e.index << "on file" << file; + return QByteArray(); + } + + return res; } -QString AppInfo::path() const +QImage AppInfo::decodeResourceImage(const QByteArray &data) const { - return d->path; + Unpacker u(data); + int scanline = u.readLE<quint16>(); + u.skip(sizeof(quint16) + sizeof(quint32)); + int width = u.readLE<quint16>(); + int height = u.readLE<quint16>(); + + QImage img(width, height, QImage::Format_MonoLSB); + const uchar *src = reinterpret_cast<const uchar *>(&data.constData()[12]); + for (int line = 0; line < height; ++line) { + memcpy(img.scanLine(line), src, qMin(scanline, img.bytesPerLine())); + src += scanline; + } + + return img; } -void AppInfo::setPath(const QString &string) +// TODO: abstract to QIOReader +QString AppInfo::filePath(enum AppInfo::File file) const { - d->path = string; + QString fileName; + switch (file) { + case AppInfo::BINARY: + fileName = "pebble-app.bin"; + break; + case AppInfo::RESOURCES: + fileName = "app_resources.pbpack"; + break; + } + + QDir appDir(d->path); + if (appDir.exists(fileName)) { + return appDir.absoluteFilePath(fileName); + } + + return QString(); } |
