keepassxc

Форк
0
/
ShareObserver.cpp 
352 строки · 11.4 Кб
1
/*
2
 *  Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
3
 *
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU General Public License as published by
6
 *  the Free Software Foundation, either version 2 or (at your option)
7
 *  version 3 of the License.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17

18
#include "ShareObserver.h"
19
#include "core/FileWatcher.h"
20
#include "core/Group.h"
21
#include "keeshare/KeeShare.h"
22
#include "keeshare/ShareExport.h"
23
#include "keeshare/ShareImport.h"
24

25
#include <QDir>
26

27
namespace
28
{
29
    QString resolvePath(const QString& path, QSharedPointer<Database> database)
30
    {
31
        const QFileInfo info(database->filePath());
32
        return info.absoluteDir().absoluteFilePath(path);
33
    }
34

35
    constexpr int FileWatchPeriod = 30;
36
    constexpr int FileWatchSize = 5;
37
} // End Namespace
38

39
ShareObserver::ShareObserver(QSharedPointer<Database> db, QObject* parent)
40
    : QObject(parent)
41
    , m_db(std::move(db))
42
{
43
    connect(KeeShare::instance(), &KeeShare::activeChanged, this, &ShareObserver::handleDatabaseChanged);
44

45
    connect(m_db.data(), &Database::groupDataChanged, this, &ShareObserver::handleDatabaseChanged);
46
    connect(m_db.data(), &Database::groupAdded, this, &ShareObserver::handleDatabaseChanged);
47
    connect(m_db.data(), &Database::groupRemoved, this, &ShareObserver::handleDatabaseChanged);
48

49
    connect(m_db.data(), &Database::modified, this, &ShareObserver::handleDatabaseChanged);
50
    connect(m_db.data(), &Database::databaseSaved, this, &ShareObserver::handleDatabaseSaved);
51

52
    handleDatabaseChanged();
53
}
54

55
ShareObserver::~ShareObserver()
56
{
57
    m_db->disconnect(this);
58
}
59

60
void ShareObserver::deinitialize()
61
{
62
    m_groupToReference.clear();
63
    m_shareToGroup.clear();
64
    m_fileWatchers.clear();
65
}
66

67
void ShareObserver::reinitialize()
68
{
69
    QList<QPair<QPointer<Group>, KeeShareSettings::Reference>> shares;
70
    for (Group* group : m_db->rootGroup()->groupsRecursive(true)) {
71
        auto oldReference = m_groupToReference.value(group);
72
        auto newReference = KeeShare::referenceOf(group);
73
        if (oldReference == newReference) {
74
            continue;
75
        }
76

77
        const auto oldResolvedPath = resolvePath(oldReference.path, m_db);
78
        m_groupToReference.remove(group);
79
        m_shareToGroup.remove(oldResolvedPath);
80
        m_fileWatchers.remove(oldResolvedPath);
81

82
        if (newReference.isValid()) {
83
            m_groupToReference[group] = newReference;
84
            const auto newResolvedPath = resolvePath(newReference.path, m_db);
85
            m_shareToGroup[newResolvedPath] = group;
86
        }
87

88
        shares.append({group, newReference});
89
    }
90

91
    QStringList success;
92
    QStringList warning;
93
    QStringList error;
94
    QMap<QString, QStringList> imported;
95
    QMap<QString, QStringList> exported;
96

97
    for (const auto& share : shares) {
98
        auto group = share.first;
99
        auto& reference = share.second;
100
        // Check group validity, it may have been deleted by a merge action
101
        if (!group) {
102
            continue;
103
        }
104

105
        if (!reference.path.isEmpty() && reference.type != KeeShareSettings::Inactive) {
106
            const auto newResolvedPath = resolvePath(reference.path, m_db);
107
            auto fileWatcher = QSharedPointer<FileWatcher>::create(this);
108
            connect(fileWatcher.data(), &FileWatcher::fileChanged, this, &ShareObserver::handleFileUpdated);
109
            fileWatcher->start(newResolvedPath, FileWatchPeriod, FileWatchSize);
110
            m_fileWatchers.insert(newResolvedPath, fileWatcher);
111
        }
112
        if (reference.isExporting()) {
113
            exported[reference.path] << group->name();
114
            // export is only on save
115
        }
116

117
        if (reference.isImporting()) {
118
            imported[reference.path] << group->name();
119
            // import has to occur immediately
120
            const auto result = this->importShare(reference.path);
121
            if (!result.isValid()) {
122
                // tolerable result - blocked import or missing source
123
                continue;
124
            }
125

126
            if (result.isError()) {
127
                error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
128
            } else if (result.isWarning()) {
129
                warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
130
            } else if (result.isInfo()) {
131
                success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message);
132
            } else {
133
                success << tr("Imported from %1").arg(result.path);
134
            }
135
        }
136
    }
137

138
    for (auto it = imported.cbegin(); it != imported.cend(); ++it) {
139
        if (it.value().count() > 1) {
140
            warning << tr("Multiple import source path to %1 in %2").arg(it.key(), it.value().join(", "));
141
        }
142
    }
143

144
    for (auto it = exported.cbegin(); it != exported.cend(); ++it) {
145
        if (it.value().count() > 1) {
146
            error << tr("Conflicting export target path %1 in %2").arg(it.key(), it.value().join(", "));
147
        }
148
    }
149

150
    notifyAbout(success, warning, error);
151
}
152

153
void ShareObserver::notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error)
154
{
155
    QStringList messages;
156
    MessageWidget::MessageType type = MessageWidget::Positive;
157
    if (!(success.isEmpty() || config()->get(Config::KeeShare_QuietSuccess).toBool())) {
158
        messages += success;
159
    }
160
    if (!warning.isEmpty()) {
161
        type = MessageWidget::Warning;
162
        messages += warning;
163
    }
164
    if (!error.isEmpty()) {
165
        type = MessageWidget::Error;
166
        messages += error;
167
    }
168
    if (!messages.isEmpty()) {
169
        emit sharingMessage(messages.join("\n"), type);
170
    }
171
}
172

173
void ShareObserver::handleDatabaseChanged()
174
{
175
    if (!m_db) {
176
        Q_ASSERT(m_db);
177
        return;
178
    }
179
    const auto active = KeeShare::active();
180
    if (!active.out && !active.in) {
181
        deinitialize();
182
    } else {
183
        reinitialize();
184
    }
185
}
186

187
void ShareObserver::handleFileUpdated(const QString& path)
188
{
189
    if (!m_inFileUpdate) {
190
        QTimer::singleShot(100, this, [this, path] {
191
            const Result result = importShare(path);
192
            m_inFileUpdate = false;
193
            if (!result.isValid()) {
194
                return;
195
            }
196
            QStringList success;
197
            QStringList warning;
198
            QStringList error;
199
            if (result.isError()) {
200
                error << tr("Import from %1 failed (%2)").arg(result.path, result.message);
201
            } else if (result.isWarning()) {
202
                warning << tr("Import from %1 failed (%2)").arg(result.path, result.message);
203
            } else if (result.isInfo()) {
204
                success << tr("Import from %1 successful (%2)").arg(result.path, result.message);
205
            } else {
206
                success << tr("Imported from %1").arg(result.path);
207
            }
208
            notifyAbout(success, warning, error);
209
        });
210
        m_inFileUpdate = true;
211
    }
212
}
213

214
ShareObserver::Result ShareObserver::importShare(const QString& path)
215
{
216
    if (!KeeShare::active().in) {
217
        return {};
218
    }
219
    const auto changePath = resolvePath(path, m_db);
220
    auto shareGroup = m_shareToGroup.value(changePath);
221
    if (!shareGroup) {
222
        qWarning("Group for %s does not exist", qPrintable(path));
223
        return {};
224
    }
225
    const auto reference = KeeShare::referenceOf(shareGroup);
226
    if (reference.type == KeeShareSettings::Inactive) {
227
        // changes of inactive references are ignored
228
        return {};
229
    }
230
    if (reference.type == KeeShareSettings::ExportTo) {
231
        // changes of export only references are ignored
232
        return {};
233
    }
234

235
    Q_ASSERT(shareGroup->database() == m_db);
236
    Q_ASSERT(shareGroup == m_db->rootGroup()->findGroupByUuid(shareGroup->uuid()));
237
    const auto resolvedPath = resolvePath(reference.path, m_db);
238
    return ShareImport::containerInto(resolvedPath, reference, shareGroup);
239
}
240

241
QSharedPointer<Database> ShareObserver::database()
242
{
243
    return m_db;
244
}
245

246
QList<ShareObserver::Result> ShareObserver::exportShares()
247
{
248
    QList<Result> results;
249
    struct Reference
250
    {
251
        KeeShareSettings::Reference config;
252
        const Group* group;
253
    };
254

255
    QMap<QString, QList<Reference>> references;
256
    const auto groups = m_db->rootGroup()->groupsRecursive(true);
257
    for (const auto* group : groups) {
258
        const auto reference = KeeShare::referenceOf(group);
259
        if (!reference.isExporting()) {
260
            continue;
261
        }
262
        references[reference.path] << Reference{reference, group};
263
    }
264

265
    for (auto it = references.cbegin(); it != references.cend(); ++it) {
266
        if (it.value().count() != 1) {
267
            const auto path = it.value().first().config.path;
268
            QStringList groupnames;
269
            for (const auto& reference : it.value()) {
270
                groupnames << reference.group->name();
271
            }
272
            results << Result{
273
                path, Result::Error, tr("Conflicting export target path %1 in %2").arg(path, groupnames.join(", "))};
274
        }
275
    }
276
    if (!results.isEmpty()) {
277
        // We need to block export due to config
278
        return results;
279
    }
280

281
    for (auto it = references.cbegin(); it != references.cend(); ++it) {
282
        auto reference = it.value().first();
283
        const QString resolvedPath = resolvePath(reference.config.path, m_db);
284
        auto watcher = m_fileWatchers.value(resolvedPath);
285
        if (watcher) {
286
            watcher->stop();
287
        }
288

289
        // TODO: save new path into group settings if not saving to signed container anymore
290
        results << ShareExport::intoContainer(resolvedPath, reference.config, reference.group);
291

292
        if (watcher) {
293
            watcher->start(resolvedPath, FileWatchPeriod, FileWatchSize);
294
        }
295
    }
296
    return results;
297
}
298

299
void ShareObserver::handleDatabaseSaved()
300
{
301
    if (!KeeShare::active().out) {
302
        return;
303
    }
304
    QStringList error;
305
    QStringList warning;
306
    QStringList success;
307

308
    const auto results = exportShares();
309
    for (const Result& result : results) {
310
        if (!result.isValid()) {
311
            Q_ASSERT(result.isValid());
312
            continue;
313
        }
314
        if (result.isError()) {
315
            error << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message);
316
        } else if (result.isWarning()) {
317
            warning << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message);
318
        } else if (result.isInfo()) {
319
            success << tr("Export to %1 successful (%2)").arg(result.path).arg(result.message);
320
        } else {
321
            success << tr("Export to %1").arg(result.path);
322
        }
323
    }
324
    notifyAbout(success, warning, error);
325
}
326

327
ShareObserver::Result::Result(const QString& path, ShareObserver::Result::Type type, const QString& message)
328
    : path(path)
329
    , type(type)
330
    , message(message)
331
{
332
}
333

334
bool ShareObserver::Result::isValid() const
335
{
336
    return !path.isEmpty() || !message.isEmpty();
337
}
338

339
bool ShareObserver::Result::isError() const
340
{
341
    return !message.isEmpty() && type == Error;
342
}
343

344
bool ShareObserver::Result::isInfo() const
345
{
346
    return !message.isEmpty() && type == Info;
347
}
348

349
bool ShareObserver::Result::isWarning() const
350
{
351
    return !message.isEmpty() && type == Warning;
352
}
353

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.