singlefileresourcebase.cpp 9.45 KB
Newer Older
1
/*
2
    Copyright (c) 2008 Bertjan Broeksema <broeksema@kde.org>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    Copyright (c) 2008 Volker Krause <vkrause@kde.org>

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library 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 Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include "singlefileresourcebase.h"

23
24
25
#include <changerecorder.h>
#include <entitydisplayattribute.h>
#include <itemfetchscope.h>
26

27
28
#include <kio/job.h>
#include <kio/jobuidelegate.h>
Laurent Montel's avatar
Laurent Montel committed
29
#include <QDebug>
30
#include <KDirWatch>
Laurent Montel's avatar
Laurent Montel committed
31
#include <KLocalizedString>
Laurent Montel's avatar
Laurent Montel committed
32

Laurent Montel's avatar
Laurent Montel committed
33
#include <KConfigGroup>
34

Laurent Montel's avatar
Laurent Montel committed
35
36
#include <QDir>
#include <QCryptographicHash>
Laurent Montel's avatar
Laurent Montel committed
37
#include <QStandardPaths>
Laurent Montel's avatar
Laurent Montel committed
38
#include <QTimer>
39

Laurent Montel's avatar
Laurent Montel committed
40
Q_DECLARE_METATYPE(QEventLoopLocker *)
41

42
43
using namespace Akonadi;

Laurent Montel's avatar
Laurent Montel committed
44
SingleFileResourceBase::SingleFileResourceBase(const QString &id)
Laurent Montel's avatar
Laurent Montel committed
45
    : ResourceBase(id)
46
{
47
48
    connect(this, &SingleFileResourceBase::reloadConfiguration,
            this, [this]() {
Laurent Montel's avatar
Laurent Montel committed
49
50
51
52
        applyConfigurationChanges();
        reloadFile();
        synchronizeCollectionTree();
    });
Laurent Montel's avatar
Laurent Montel committed
53
54
55
    QTimer::singleShot(0, this, [this]() {
        readFile();
    });
56

Laurent Montel's avatar
Laurent Montel committed
57
58
    changeRecorder()->itemFetchScope().fetchFullPayload();
    changeRecorder()->fetchCollection(true);
59

Sergio Martins's avatar
Sergio Martins committed
60
    connect(changeRecorder(), &ChangeRecorder::changesAdded, this, &SingleFileResourceBase::scheduleWrite);
61

Laurent Montel's avatar
Laurent Montel committed
62
63
    connect(KDirWatch::self(), &KDirWatch::dirty, this, &SingleFileResourceBase::fileChanged);
    connect(KDirWatch::self(), &KDirWatch::created, this, &SingleFileResourceBase::fileChanged);
64
65
}

66
67
68
69
void SingleFileResourceBase::applyConfigurationChanges()
{
}

70
71
KSharedConfig::Ptr SingleFileResourceBase::runtimeConfig() const
{
Laurent Montel's avatar
Laurent Montel committed
72
    return KSharedConfig::openConfig(name() + QLatin1String("rc"), KConfig::SimpleConfig, QStandardPaths::CacheLocation);
73
74
}

Laurent Montel's avatar
Laurent Montel committed
75
bool SingleFileResourceBase::readLocalFile(const QString &fileName)
76
{
Laurent Montel's avatar
Laurent Montel committed
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
    const QByteArray newHash = calculateHash(fileName);
    if (mCurrentHash != newHash) {
        if (!mCurrentHash.isEmpty()) {
            // There was a hash stored in the config file or a chached one from
            // a previous read and it is different from the hash we just read.
            handleHashChange();
        }

        if (!readFromFile(fileName)) {
            mCurrentHash.clear();
            mCurrentUrl = QUrl(); // reset so we don't accidentally overwrite the file
            return false;
        }

        if (mCurrentHash.isEmpty()) {
            // This is the very first time we read the file so make sure to store
            // the hash as writeFile() might not be called at all (e.g in case of
            // read only resources).
            saveHash(newHash);
        }

        // Only synchronize when the contents of the file have changed wrt to
        // the last time this file was read. Before we synchronize first
        // clearCache is called to make sure that the cached items get the
        // actual values as present in the file.
        invalidateCache(rootCollection());
        synchronize();
    } else {
        // The hash didn't change, notify implementing resources about the
        // actual file name that should be used when reading the file is
        // necessary.
        setLocalFileName(fileName);
109
110
    }

Laurent Montel's avatar
Laurent Montel committed
111
112
    mCurrentHash = newHash;
    return true;
113
114
}

Laurent Montel's avatar
Laurent Montel committed
115
void SingleFileResourceBase::setLocalFileName(const QString &fileName)
116
{
Laurent Montel's avatar
Laurent Montel committed
117
118
119
120
121
122
    // Default implementation.
    if (!readFromFile(fileName)) {
        mCurrentHash.clear();
        mCurrentUrl = QUrl(); // reset so we don't accidentally overwrite the file
        return;
    }
123
124
}

Tobias Koenig's avatar
Tobias Koenig committed
125
QString SingleFileResourceBase::cacheFile() const
126
{
127
128
    const QString currentDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
    QDir().mkpath(currentDir);
129
    return currentDir + QLatin1Char('/') + identifier();
130
131
}

Laurent Montel's avatar
Laurent Montel committed
132
QByteArray SingleFileResourceBase::calculateHash(const QString &fileName) const
133
{
Laurent Montel's avatar
Laurent Montel committed
134
135
136
137
    QFile file(fileName);
    if (!file.exists()) {
        return QByteArray();
    }
138

Laurent Montel's avatar
Laurent Montel committed
139
140
141
    if (!file.open(QIODevice::ReadOnly)) {
        return QByteArray();
    }
142

Laurent Montel's avatar
Laurent Montel committed
143
144
    QCryptographicHash hash(QCryptographicHash::Md5);
    qint64 blockSize = 512 * 1024; // Read blocks of 512K
145

Laurent Montel's avatar
Laurent Montel committed
146
147
148
    while (!file.atEnd()) {
        hash.addData(file.read(blockSize));
    }
149

Laurent Montel's avatar
Laurent Montel committed
150
    file.close();
151

Laurent Montel's avatar
Laurent Montel committed
152
    return hash.result();
153
154
}

155
156
void SingleFileResourceBase::handleHashChange()
{
Laurent Montel's avatar
Laurent Montel committed
157
158
    // Default implementation does nothing.
    qDebug() << "The hash has changed.";
159
160
161
162
}

QByteArray SingleFileResourceBase::loadHash() const
{
Laurent Montel's avatar
Laurent Montel committed
163
164
    KConfigGroup generalGroup(runtimeConfig(), "General");
    return QByteArray::fromHex(generalGroup.readEntry<QByteArray>("hash", QByteArray()));
165
166
}

Laurent Montel's avatar
Laurent Montel committed
167
void SingleFileResourceBase::saveHash(const QByteArray &hash) const
168
{
Laurent Montel's avatar
Laurent Montel committed
169
170
171
172
    KSharedConfig::Ptr config = runtimeConfig();
    KConfigGroup generalGroup(config, "General");
    generalGroup.writeEntry("hash", hash.toHex());
    config->sync();
173
174
}

Laurent Montel's avatar
Laurent Montel committed
175
void SingleFileResourceBase::setSupportedMimetypes(const QStringList &mimeTypes, const QString &icon)
Volker Krause's avatar
Volker Krause committed
176
{
Laurent Montel's avatar
Laurent Montel committed
177
178
    mSupportedMimetypes = mimeTypes;
    mCollectionIcon = icon;
179
180
}

Laurent Montel's avatar
Laurent Montel committed
181
void SingleFileResourceBase::collectionChanged(const Akonadi::Collection &collection)
182
{
Laurent Montel's avatar
Laurent Montel committed
183
184
    const QString newName = collection.displayName();
    if (collection.hasAttribute<EntityDisplayAttribute>()) {
185
        const EntityDisplayAttribute *attr = collection.attribute<EntityDisplayAttribute>();
Laurent Montel's avatar
Laurent Montel committed
186
187
188
189
        if (!attr->iconName().isEmpty()) {
            mCollectionIcon = attr->iconName();
        }
    }
190

Laurent Montel's avatar
Laurent Montel committed
191
192
193
    if (newName != name()) {
        setName(newName);
    }
194

Laurent Montel's avatar
Laurent Montel committed
195
    changeCommitted(collection);
Volker Krause's avatar
Volker Krause committed
196
197
}

198
199
void SingleFileResourceBase::reloadFile()
{
Laurent Montel's avatar
Laurent Montel committed
200
201
    // Update the network setting.
    setNeedsNetwork(!mCurrentUrl.isEmpty() && !mCurrentUrl.isLocalFile());
202

Laurent Montel's avatar
Laurent Montel committed
203
204
205
206
207
    // if we have something loaded already, make sure we write that back in case
    // the settings changed
    if (!mCurrentUrl.isEmpty() && !readOnly()) {
        writeFile();
    }
Tobias Koenig's avatar
Tobias Koenig committed
208

Laurent Montel's avatar
Laurent Montel committed
209
    readFile();
210

Laurent Montel's avatar
Laurent Montel committed
211
212
    // name or rights could have changed
    synchronizeCollectionTree();
213
214
}

Laurent Montel's avatar
Laurent Montel committed
215
void SingleFileResourceBase::handleProgress(KJob *, unsigned long pct)
Bertjan Broeksema's avatar
Bertjan Broeksema committed
216
{
Laurent Montel's avatar
Laurent Montel committed
217
    Q_EMIT percent(pct);
Bertjan Broeksema's avatar
Bertjan Broeksema committed
218
219
}

Laurent Montel's avatar
Laurent Montel committed
220
void SingleFileResourceBase::fileChanged(const QString &fileName)
221
{
Laurent Montel's avatar
Laurent Montel committed
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    if (fileName != mCurrentUrl.toLocalFile()) {
        return;
    }

    const QByteArray newHash = calculateHash(fileName);

    // There is only a need to synchronize when the file was changed by another
    // process. At this point we're sure that it is the file that the resource
    // was configured for because of the check at the beginning of this function.
    if (newHash == mCurrentHash) {
        return;
    }

    if (!mCurrentUrl.isEmpty()) {
        QString lostFoundFileName;
        const QUrl prevUrl = mCurrentUrl;
        int i = 0;
        do {
240
            lostFoundFileName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + identifier() + QLatin1Char('/') + prevUrl.fileName() + QLatin1Char('-')
Laurent Montel's avatar
Laurent Montel committed
241
                                + QString::number(++i);
242
        } while (QFileInfo::exists(lostFoundFileName));
Laurent Montel's avatar
Laurent Montel committed
243
244
245
246
247
248
249
250
251
252
253
254
255
256

        // create the directory if it doesn't exist yet
        QDir dir = QFileInfo(lostFoundFileName).dir();
        if (!dir.exists()) {
            dir.mkpath(dir.path());
        }

        mCurrentUrl = QUrl::fromLocalFile(lostFoundFileName);
        writeFile();
        mCurrentUrl = prevUrl;

        const QString message = i18n("The file '%1' was changed on disk. "
                                     "As a precaution, a backup of its previous contents has been created at '%2'.",
                                     prevUrl.toDisplayString(), QUrl::fromLocalFile(lostFoundFileName).toDisplayString());
Laurent Montel's avatar
Laurent Montel committed
257
        Q_EMIT warning(message);
Laurent Montel's avatar
Laurent Montel committed
258
259
260
261
262
263
264
265
266
    }

    readFile();

    // Notify resources, so that information bound to the file like indexes etc.
    // can be updated.
    handleHashChange();
    invalidateCache(rootCollection());
    synchronize();
267
268
}

269
void SingleFileResourceBase::scheduleWrite()
270
{
Laurent Montel's avatar
Laurent Montel committed
271
    scheduleCustomTask(this, "writeFile", QVariant(true), ResourceBase::AfterChangeReplay);
272
273
}

Laurent Montel's avatar
Laurent Montel committed
274
void SingleFileResourceBase::slotDownloadJobResult(KJob *job)
275
{
Laurent Montel's avatar
Laurent Montel committed
276
277
278
    if (job->error() && job->error() != KIO::ERR_DOES_NOT_EXIST) {
        const QString message = i18n("Could not load file '%1'.", mCurrentUrl.toDisplayString());
        qWarning() << message;
Laurent Montel's avatar
Laurent Montel committed
279
        Q_EMIT status(Broken, message);
Laurent Montel's avatar
Laurent Montel committed
280
281
282
283
    } else {
        readLocalFile(QUrl::fromLocalFile(cacheFile()).toLocalFile());
    }

Laurent Montel's avatar
Laurent Montel committed
284
    mDownloadJob = nullptr;
Laurent Montel's avatar
Laurent Montel committed
285
    auto ref = job->property("QEventLoopLocker").value<QEventLoopLocker *>();
286
287
288
    if (ref) {
        delete ref;
    }
Laurent Montel's avatar
Laurent Montel committed
289

Laurent Montel's avatar
Laurent Montel committed
290
    Q_EMIT status(Idle, i18nc("@info:status", "Ready"));
291
292
}

Laurent Montel's avatar
Laurent Montel committed
293
void SingleFileResourceBase::slotUploadJobResult(KJob *job)
294
{
Laurent Montel's avatar
Laurent Montel committed
295
296
297
    if (job->error()) {
        const QString message = i18n("Could not save file '%1'.", mCurrentUrl.toDisplayString());
        qWarning() << message;
Laurent Montel's avatar
Laurent Montel committed
298
        Q_EMIT status(Broken, message);
Laurent Montel's avatar
Laurent Montel committed
299
    }
300

Laurent Montel's avatar
Laurent Montel committed
301
    mUploadJob = nullptr;
Laurent Montel's avatar
Laurent Montel committed
302
    auto ref = job->property("QEventLoopLocker").value<QEventLoopLocker *>();
303
304
305
    if (ref) {
        delete ref;
    }
Bertjan Broeksema's avatar
Bertjan Broeksema committed
306

Laurent Montel's avatar
Laurent Montel committed
307
    Q_EMIT status(Idle, i18nc("@info:status", "Ready"));
308
}