diskusage.cpp 7.59 KB
Newer Older
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
/*
 * Copyright (C) 2015 Jolla Ltd.
 * Contact: Thomas Perl <thomas.perl@jolla.com>
 *
 * You may use this file under the terms of the BSD license as follows:
 *
 * "Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Nemo Mobile nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 */

#include "diskusage.h"
#include "diskusage_p.h"

#include <QThread>
#include <QDebug>
#include <QJSEngine>
39
#include <QDir>
40

41

42 43 44 45 46 47 48 49 50 51
DiskUsageWorker::DiskUsageWorker(QObject *parent)
    : QObject(parent)
    , m_quit(false)
{
}

DiskUsageWorker::~DiskUsageWorker()
{
}

52
void DiskUsageWorker::submit(QStringList paths, QJSValue *callback)
53
{
54
    emit finished(calculate(paths), callback);
55 56
}

57
QVariantMap DiskUsageWorker::calculate(QStringList paths)
58 59
{
    QVariantMap usage;
60
    // expanded Path places the object in the tree so parents can have it subtracted from its total
61
    QMap<QString, QString> expandedPaths; // input path -> expanded path
62
    QMap<QString, QString> originalPaths; // expanded path -> input path
63

64 65 66 67
    // Older adaptations (e.g. Jolla 1) don't have /home/.android/. Android home is in the root.
    QString androidHome = QString("/home/.android");
    bool androidHomeExists = QDir(androidHome).exists();

68
    foreach (const QString &path, paths) {
69
        QString expandedPath;
70 71 72 73 74 75 76
        // Pseudo-path for querying RPM database for file sizes
        // ----------------------------------------------------
        // Example path with package name: ":rpm:python3-base"
        // Example path with glob: ":rpm:harbour-*" (will sum up all matching package sizes)
        if (path.startsWith(":rpm:")) {
            QString glob = path.mid(5);
            usage[path] = calculateRpmSize(glob);
77
            expandedPath = "/usr/" + path;
78 79 80 81
        } else if (path.startsWith(":apkd:")) {
            // Pseudo-path for querying Android apps' data usage
            QString rest = path.mid(6);
            usage[path] = calculateApkdSize(rest);
82
            expandedPath = (androidHomeExists ? androidHome : "") + "/data/data";
83
        } else {
84 85 86 87
            quint64 size = calculateSize(path, &expandedPath, androidHomeExists);
            if (expandedPath.startsWith(androidHome) && !androidHomeExists) {
                expandedPath = expandedPath.mid(androidHome.length());
            }
88
            usage[path] = size;
89
        }
90

91 92
        expandedPaths[path] = expandedPath;
        originalPaths[expandedPath] = path;
93 94 95 96 97
        if (m_quit) {
            break;
        }
    }

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    // Sort keys in reverse order (so child directories come before their
    // parents, and the calculation is done correctly, no child directory
    // subtracted once too often), for example:
    //  1. a0 = size(/home/nemo/foo/)
    //  2. b0 = size(/home/nemo/)
    //  3. c0 = size(/)
    //
    // This will calculate the following changes in the nested for loop below:
    //  1. b1 = b0 - a0
    //  2. c1 = c0 - a0
    //  3. c2 = c1 - b1
    //
    // Combined and simplified, this will give us the output values:
    //  1. a' = a0
    //  2. b' = b1 = b0 - a0
    //  3. c' = c2 = c1 - b1 = (c0 - a0) - (b0 - a0) = c0 - a0 - b0 + a0 = c0 - b0
    //
    // Or with paths:
    //  1. output(/home/nemo/foo/) = size(/home/nemo/foo/)
    //  2. output(/home/nemo/)     = size(/home/nemo/)     - size(/home/nemo/foo/)
    //  3. output(/)               = size(/)               - size(/home/nemo/)
    QStringList keys;
    foreach (const QString &key, usage.uniqueKeys()) {
        keys << expandedPaths.value(key, key);
    }
    qStableSort(keys.begin(), keys.end(), qGreater<QString>());
    for (int i=0; i<keys.length(); i++) {
        for (int j=i+1; j<keys.length(); j++) {
            QString subpath = keys[i];
            QString path = keys[j];
128

129
            if ((subpath.length() > path.length() && subpath.indexOf(path) == 0) || (path == "/")) {
130 131 132
                qlonglong subbytes = usage[originalPaths.value(subpath, subpath)].toLongLong();
                qlonglong bytes = usage[originalPaths.value(path, path)].toLongLong();

133
                bytes -= subbytes;
134
                usage[originalPaths.value(path, path)] = bytes;
135 136 137 138
            }
        }
    }

139
    return usage;
140 141 142 143 144 145 146 147 148 149 150 151 152 153
}

class DiskUsagePrivate
{
    Q_DISABLE_COPY(DiskUsagePrivate)
    Q_DECLARE_PUBLIC(DiskUsage)

    DiskUsage * const q_ptr;

public:
    DiskUsagePrivate(DiskUsage *usage);
    ~DiskUsagePrivate();

private:
154
    QThread *m_thread;
155 156 157 158 159
    DiskUsageWorker *m_worker;
};

DiskUsagePrivate::DiskUsagePrivate(DiskUsage *usage)
    : q_ptr(usage)
160
    , m_thread(new QThread())
161 162
    , m_worker(new DiskUsageWorker())
{
163
    m_worker->moveToThread(m_thread);
164 165 166 167 168 169 170

    QObject::connect(usage, SIGNAL(submit(QStringList, QJSValue *)),
                     m_worker, SLOT(submit(QStringList, QJSValue *)));

    QObject::connect(m_worker, SIGNAL(finished(QVariantMap, QJSValue *)),
                     usage, SLOT(finished(QVariantMap, QJSValue *)));

171 172 173 174 175 176 177
    QObject::connect(m_thread, SIGNAL(finished()),
                     m_worker, SLOT(deleteLater()));

    QObject::connect(m_thread, SIGNAL(finished()),
                     m_thread, SLOT(deleteLater()));

    m_thread->start();
178 179 180 181 182 183 184
}

DiskUsagePrivate::~DiskUsagePrivate()
{
    // Make sure the worker quits as soon as possible
    m_worker->scheduleQuit();

185 186
    // Tell thread to shut down as early as possible
    m_thread->quit();
187 188 189 190 191 192 193 194
}


DiskUsage::DiskUsage(QObject *parent)
    : QObject(parent)
    , d_ptr(new DiskUsagePrivate(this))
    , m_working(false)
{
195
    qWarning() << Q_FUNC_INFO << "DiskUsage is deprecated in org.nemomobile.systemsettings package 0.5.22 (Sept 2019), use DiskUsage from Nemo.FileManager instead.";
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
}

DiskUsage::~DiskUsage()
{
}

void DiskUsage::calculate(const QStringList &paths, QJSValue callback)
{
    QJSValue *cb = 0;

    if (!callback.isNull() && !callback.isUndefined() && callback.isCallable()) {
        cb = new QJSValue(callback);
    }

    setWorking(true);
    emit submit(paths, cb);
}

void DiskUsage::finished(QVariantMap usage, QJSValue *callback)
{
    if (callback) {
        callback->call(QJSValueList() << callback->engine()->toScriptValue(usage));
        delete callback;
    }

221 222 223 224
    // the result has been set, so emit resultChanged() even if result was not valid
    m_result = usage;
    emit resultChanged();

225 226
    setWorking(false);
}
227 228 229 230 231

QVariantMap DiskUsage::result() const
{
    return m_result;
}