summaryrefslogtreecommitdiff
path: root/daemon/appmanager.cpp
blob: 34af3af51e5e3a9d56f98b5e05d6bb7d259bb03c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <QStandardPaths>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDir>
#include "appmanager.h"

AppManager::AppManager(QObject *parent)
    : QObject(parent),
      _watcher(new QFileSystemWatcher(this))
{
    connect(_watcher, &QFileSystemWatcher::directoryChanged,
            this, &AppManager::rescan);

    QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
    if (!dataDir.exists("apps")) {
        if (!dataDir.mkdir("apps")) {
            logger()->warn() << "could not create dir" << dataDir.absoluteFilePath("apps");
        }
    }
    logger()->debug() << "install apps in" << dataDir.absoluteFilePath("apps");

    rescan();
}

QStringList AppManager::appPaths() const
{
    return QStandardPaths::locateAll(QStandardPaths::DataLocation,
                                     QLatin1String("apps"),
                                     QStandardPaths::LocateDirectory);
}

void AppManager::rescan()
{
    QStringList watchedDirs = _watcher->directories();
    if (!watchedDirs.isEmpty()) _watcher->removePaths(watchedDirs);
    QStringList watchedFiles = _watcher->files();
    if (!watchedFiles.isEmpty()) _watcher->removePaths(watchedFiles);
    _apps.clear();
    _names.clear();

    Q_FOREACH(const QString &path, appPaths()) {
        QDir dir(path);
        _watcher->addPath(dir.absolutePath());
        logger()->debug() << "scanning dir" << dir.absolutePath();
        QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Executable);
        logger()->debug() << "scanning dir results" << entries;
        Q_FOREACH(const QString &path, entries) {
            QString appPath = dir.absoluteFilePath(path);
            _watcher->addPath(appPath);
            if (dir.exists(path + "/appinfo.json")) {
                _watcher->addPath(appPath + "/appinfo.json");
                scanApp(appPath);
            }
        }
    }

    logger()->debug() << "now watching" << _watcher->directories() << _watcher->files();
}

void AppManager::scanApp(const QString &path)
{
    logger()->debug() << "scanning app" << path;
    QDir appDir(path);
    if (!appDir.isReadable()) {
        logger()->warn() << "app" << appDir.absolutePath() << "is not readable";
        return;
    }

    QFile appInfoFile(path + "/appinfo.json");
    if (!appInfoFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        logger()->warn() << "cannot open app info file" << appInfoFile.fileName() << ":"
                         << appInfoFile.errorString();
        return;
    }

    QJsonParseError parseError;
    QJsonDocument doc = QJsonDocument::fromJson(appInfoFile.readAll(), &parseError);
    if (parseError.error != QJsonParseError::NoError) {
        logger()->warn() << "cannot parse app info file" << appInfoFile.fileName() << ":"
                         << parseError.errorString();
        return;
    }

    const QJsonObject root = doc.object();
    AppInfo info;
    info.uuid = QUuid(root["uuid"].toString());
    info.shortName = root["shortName"].toString();
    info.longName = root["longName"].toString();
    info.company = root["companyName"].toString();
    info.versionCode = root["versionCode"].toInt();
    info.versionLabel = root["versionLabel"].toString();

    const QJsonObject watchapp = root["watchapp"].toObject();
    info.isWatchface = watchapp["watchface"].toBool();
    info.isJSKit = appDir.exists("pebble-js-app.js");

    const QJsonObject appkeys = root["appKeys"].toObject();
    for (QJsonObject::const_iterator it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) {
        info.appKeys.insert(it.key(), it.value().toInt());
    }

    if (info.uuid.isNull() || info.shortName.isEmpty()) {
        logger()->warn() << "invalid or empty uuid/name in" << appInfoFile.fileName();
        return;
    }

    _apps.insert(info.uuid, info);
    _names.insert(info.shortName, info.uuid);

    const char *type = info.isWatchface ? "watchface" : "app";
    logger()->debug() << "found installed" << type << info.shortName << info.versionLabel << "with uuid" << info.uuid.toString();
}