~lubuntu-dev/lxde/libfm-qt-debian-git

« back to all changes in this revision

Viewing changes to src/core/folder.cpp

  • Committer: Alf Gaida
  • Date: 2017-12-04 17:37:06 UTC
  • Revision ID: git-v1:7f75fc0761946972ce0d9199871e851bb8c8fc56
Prepared transiton to sid

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *      fm-folder.c
 
3
 *
 
4
 *      Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
 
5
 *      Copyright 2012-2016 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
 
6
 *
 
7
 *      This file is a part of the Libfm library.
 
8
 *
 
9
 *      This library is free software; you can redistribute it and/or
 
10
 *      modify it under the terms of the GNU Lesser General Public
 
11
 *      License as published by the Free Software Foundation; either
 
12
 *      version 2.1 of the License, or (at your option) any later version.
 
13
 *
 
14
 *      This library is distributed in the hope that it will be useful,
 
15
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
17
 *      Lesser General Public License for more details.
 
18
 *
 
19
 *      You should have received a copy of the GNU Lesser General Public
 
20
 *      License along with this library; if not, write to the Free Software
 
21
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
22
 */
 
23
 
 
24
#include "folder.h"
 
25
#include <string.h>
 
26
#include <cassert>
 
27
#include <QTimer>
 
28
#include <QDebug>
 
29
 
 
30
#include "dirlistjob.h"
 
31
#include "filesysteminfojob.h"
 
32
#include "fileinfojob.h"
 
33
 
 
34
namespace Fm {
 
35
 
 
36
std::unordered_map<FilePath, std::weak_ptr<Folder>, FilePathHash> Folder::cache_;
 
37
FilePath Folder::cutFilesDirPath_;
 
38
FilePath Folder::lastCutFilesDirPath_;
 
39
std::shared_ptr<const HashSet> Folder::cutFilesHashSet_;
 
40
std::mutex Folder::mutex_;
 
41
 
 
42
Folder::Folder():
 
43
    dirlist_job{nullptr},
 
44
    fsInfoJob_{nullptr},
 
45
    volumeManager_{VolumeManager::globalInstance()},
 
46
    /* for file monitor */
 
47
    has_idle_reload_handler{0},
 
48
    has_idle_update_handler{false},
 
49
    pending_change_notify{false},
 
50
    filesystem_info_pending{false},
 
51
    wants_incremental{false},
 
52
    stop_emission{false}, /* don't set it 1 bit to not lock other bits */
 
53
    /* filesystem info - set in query thread, read in main */
 
54
    fs_total_size{0},
 
55
    fs_free_size{0},
 
56
    has_fs_info{false},
 
57
    defer_content_test{false} {
 
58
 
 
59
    connect(volumeManager_.get(), &VolumeManager::mountAdded, this, &Folder::onMountAdded);
 
60
    connect(volumeManager_.get(), &VolumeManager::mountRemoved, this, &Folder::onMountRemoved);
 
61
}
 
62
 
 
63
Folder::Folder(const FilePath& path): Folder() {
 
64
    dirPath_ = path;
 
65
}
 
66
 
 
67
Folder::~Folder() {
 
68
    if(dirMonitor_) {
 
69
        g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this);
 
70
        dirMonitor_.reset();
 
71
    }
 
72
 
 
73
    if(dirlist_job) {
 
74
        dirlist_job->cancel();
 
75
    }
 
76
 
 
77
    // cancel any file info job in progress.
 
78
    for(auto job: fileinfoJobs_) {
 
79
        job->cancel();
 
80
    }
 
81
 
 
82
    if(fsInfoJob_) {
 
83
        fsInfoJob_->cancel();
 
84
    }
 
85
 
 
86
    // We store a weak_ptr instead of shared_ptr in the hash table, so the hash table
 
87
    // does not own a reference to the folder. When the last reference to Folder is
 
88
    // freed, we need to remove its hash table entry.
 
89
    std::lock_guard<std::mutex> lock{mutex_};
 
90
    auto it = cache_.find(dirPath_);
 
91
    if(it != cache_.end()) {
 
92
        cache_.erase(it);
 
93
    }
 
94
}
 
95
 
 
96
// static
 
97
std::shared_ptr<Folder> Folder::fromPath(const FilePath& path) {
 
98
    std::lock_guard<std::mutex> lock{mutex_};
 
99
    auto it = cache_.find(path);
 
100
    if(it != cache_.end()) {
 
101
        auto folder = it->second.lock();
 
102
        if(folder) {
 
103
            return folder;
 
104
        }
 
105
        else { // FIXME: is this possible?
 
106
            cache_.erase(it);
 
107
        }
 
108
    }
 
109
    auto folder = std::make_shared<Folder>(path);
 
110
    folder->reload();
 
111
    cache_.emplace(path, folder);
 
112
    return folder;
 
113
}
 
114
 
 
115
bool Folder::makeDirectory(const char* /*name*/, GError** /*error*/) {
 
116
    // TODO:
 
117
    // FIXME: what the API is used for in the original libfm C API?
 
118
    return false;
 
119
}
 
120
 
 
121
bool Folder::isIncremental() const {
 
122
    return wants_incremental;
 
123
}
 
124
 
 
125
bool Folder::isValid() const {
 
126
    return dirInfo_ != nullptr;
 
127
}
 
128
 
 
129
bool Folder::isLoaded() const {
 
130
    return (dirlist_job == nullptr);
 
131
}
 
132
 
 
133
std::shared_ptr<const FileInfo> Folder::fileByName(const char* name) const {
 
134
    auto it = files_.find(name);
 
135
    if(it != files_.end()) {
 
136
        return it->second;
 
137
    }
 
138
    return nullptr;
 
139
}
 
140
 
 
141
bool Folder::isEmpty() const {
 
142
    return files_.empty();
 
143
}
 
144
 
 
145
FileInfoList Folder::files() const {
 
146
    FileInfoList ret;
 
147
    ret.reserve(files_.size());
 
148
    for(const auto& item : files_) {
 
149
        ret.push_back(item.second);
 
150
    }
 
151
    return ret;
 
152
}
 
153
 
 
154
 
 
155
const FilePath& Folder::path() const {
 
156
    auto pathStr = dirPath_.toString();
 
157
    // qDebug() << this << "FOLDER_PATH:" << pathStr.get() << dirPath_.gfile().get();
 
158
    //assert(!g_str_has_prefix(pathStr.get(), "file:"));
 
159
    return dirPath_;
 
160
}
 
161
 
 
162
const std::shared_ptr<const FileInfo>& Folder::info() const {
 
163
    return dirInfo_;
 
164
}
 
165
 
 
166
#if 0
 
167
void Folder::init(FmFolder* folder) {
 
168
    files = fm_file_info_list_new();
 
169
    G_LOCK(hash);
 
170
    if(G_UNLIKELY(hash_uses == 0)) {
 
171
        hash = g_hash_table_new((GHashFunc)fm_path_hash, (GEqualFunc)fm_path_equal);
 
172
        volume_monitor = g_volume_monitor_get();
 
173
        if(G_LIKELY(volume_monitor)) {
 
174
            g_signal_connect(volume_monitor, "mount-added", G_CALLBACK(on_mount_added), nullptr);
 
175
            g_signal_connect(volume_monitor, "mount-removed", G_CALLBACK(on_mount_removed), nullptr);
 
176
        }
 
177
    }
 
178
    hash_uses++;
 
179
    G_UNLOCK(hash);
 
180
}
 
181
#endif
 
182
 
 
183
void Folder::onIdleReload() {
 
184
    /* check if folder still exists */
 
185
    reload();
 
186
    // G_LOCK(query);
 
187
    has_idle_reload_handler = false;
 
188
    // G_UNLOCK(query);
 
189
}
 
190
 
 
191
void Folder::queueReload() {
 
192
    // G_LOCK(query);
 
193
    if(!has_idle_reload_handler) {
 
194
        has_idle_reload_handler = true;
 
195
        QTimer::singleShot(0, this, &Folder::onIdleReload);
 
196
    }
 
197
    // G_UNLOCK(query);
 
198
}
 
199
 
 
200
void Folder::onFileInfoFinished() {
 
201
    FileInfoJob* job = static_cast<FileInfoJob*>(sender());
 
202
    fileinfoJobs_.erase(std::find(fileinfoJobs_.cbegin(), fileinfoJobs_.cend(), job));
 
203
 
 
204
    if(job->isCancelled())
 
205
        return;
 
206
 
 
207
    FileInfoList files_to_add;
 
208
    std::vector<FileInfoPair> files_to_update;
 
209
 
 
210
    const auto& paths = job->paths();
 
211
    const auto& infos = job->files();
 
212
    auto path_it = paths.cbegin();
 
213
    auto info_it = infos.cbegin();
 
214
    for(; path_it != paths.cend() && info_it != infos.cend(); ++path_it, ++info_it) {
 
215
        const auto& path = *path_it;
 
216
        const auto& info = *info_it;
 
217
 
 
218
        if(path == dirPath_) { // got the info for the folder itself.
 
219
            dirInfo_ = info;
 
220
        }
 
221
        else {
 
222
            auto it = files_.find(info->name());
 
223
            if(it != files_.end()) { // the file already exists, update
 
224
                files_to_update.push_back(std::make_pair(it->second, info));
 
225
            }
 
226
            else { // newly added
 
227
                files_to_add.push_back(info);
 
228
            }
 
229
            files_[info->name()] = info;
 
230
        }
 
231
    }
 
232
    if(!files_to_add.empty()) {
 
233
        Q_EMIT filesAdded(files_to_add);
 
234
    }
 
235
    if(!files_to_update.empty()) {
 
236
        Q_EMIT filesChanged(files_to_update);
 
237
    }
 
238
    Q_EMIT contentChanged();
 
239
}
 
240
 
 
241
void Folder::processPendingChanges() {
 
242
    has_idle_update_handler = false;
 
243
    // FmFileInfoJob* job = nullptr;
 
244
    std::lock_guard<std::mutex> lock{mutex_};
 
245
 
 
246
    // idle_handler = 0;
 
247
    /* if we were asked to block updates let delay it for now */
 
248
    if(stop_emission) {
 
249
        return;
 
250
    }
 
251
 
 
252
    FileInfoJob* info_job = nullptr;
 
253
    if(!paths_to_update.empty() || !paths_to_add.empty()) {
 
254
        FilePathList paths;
 
255
        paths.insert(paths.end(), paths_to_add.cbegin(), paths_to_add.cend());
 
256
        paths.insert(paths.end(), paths_to_update.cbegin(), paths_to_update.cend());
 
257
        info_job = new FileInfoJob{paths, dirPath_,
 
258
                hasCutFiles() ? cutFilesHashSet_ : nullptr};
 
259
        paths_to_update.clear();
 
260
        paths_to_add.clear();
 
261
    }
 
262
 
 
263
    if(info_job) {
 
264
        fileinfoJobs_.push_back(info_job);
 
265
        info_job->setAutoDelete(true);
 
266
        connect(info_job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished, Qt::BlockingQueuedConnection);
 
267
        info_job->runAsync();
 
268
#if 0
 
269
        pending_jobs = g_slist_prepend(pending_jobs, job);
 
270
        if(!fm_job_run_async(FM_JOB(job))) {
 
271
            pending_jobs = g_slist_remove(pending_jobs, job);
 
272
            g_object_unref(job);
 
273
            g_critical("failed to start folder update job");
 
274
        }
 
275
#endif
 
276
    }
 
277
 
 
278
    if(!paths_to_del.empty()) {
 
279
        FileInfoList deleted_files;
 
280
        for(const auto &path: paths_to_del) {
 
281
            auto name = path.baseName();
 
282
            auto it = files_.find(name.get());
 
283
            if(it != files_.end()) {
 
284
                deleted_files.push_back(it->second);
 
285
                files_.erase(it);
 
286
            }
 
287
        }
 
288
        Q_EMIT filesRemoved(deleted_files);
 
289
        Q_EMIT contentChanged();
 
290
        paths_to_del.clear();
 
291
    }
 
292
 
 
293
    if(pending_change_notify) {
 
294
        Q_EMIT changed();
 
295
        /* update volume info */
 
296
        queryFilesystemInfo();
 
297
        pending_change_notify = false;
 
298
    }
 
299
 
 
300
    if(filesystem_info_pending) {
 
301
        Q_EMIT fileSystemChanged();
 
302
        filesystem_info_pending = false;
 
303
    }
 
304
}
 
305
 
 
306
/* should be called only with G_LOCK(lists) on! */
 
307
void Folder::queueUpdate() {
 
308
    // qDebug() << "queue_update:" << !has_idle_handler << paths_to_add.size() << paths_to_update.size() << paths_to_del.size();
 
309
    if(!has_idle_update_handler) {
 
310
        QTimer::singleShot(0, this, &Folder::processPendingChanges);
 
311
        has_idle_update_handler = true;
 
312
    }
 
313
}
 
314
 
 
315
 
 
316
/* returns true if reference was taken from path */
 
317
bool Folder::eventFileAdded(const FilePath &path) {
 
318
    bool added = true;
 
319
    // G_LOCK(lists);
 
320
    /* make sure that the file is not already queued for addition. */
 
321
    if(std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) {
 
322
        if(files_.find(path.baseName().get()) != files_.end()) { // the file already exists, update instead
 
323
            if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), path) == paths_to_update.cend()) {
 
324
                paths_to_update.push_back(path);
 
325
            }
 
326
        }
 
327
        else { // newly added file
 
328
            paths_to_add.push_back(path);
 
329
        }
 
330
        /* bug #3591771: 'ln -fns . test' leave no file visible in folder.
 
331
           If it is queued for deletion then cancel that operation */
 
332
        paths_to_del.erase(std::remove(paths_to_del.begin(), paths_to_del.end(), path), paths_to_del.cend());
 
333
    }
 
334
    else
 
335
        /* file already queued for adding, don't duplicate */
 
336
    {
 
337
        added = false;
 
338
    }
 
339
    if(added) {
 
340
        queueUpdate();
 
341
    }
 
342
    // G_UNLOCK(lists);
 
343
    return added;
 
344
}
 
345
 
 
346
bool Folder::eventFileChanged(const FilePath &path) {
 
347
    bool added;
 
348
    // G_LOCK(lists);
 
349
    /* make sure that the file is not already queued for changes or
 
350
     * it's already queued for addition. */
 
351
    if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), path) == paths_to_update.cend()
 
352
       && std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) {
 
353
        /* Since this function is called only when a file already exists, even if that file
 
354
           isn't included in "files_" yet, it will be soon due to a previous call to queueUpdate().
 
355
           So, here, we should queue it for changes regardless of what "files_" may contain. */
 
356
        paths_to_update.push_back(path);
 
357
        added = true;
 
358
        queueUpdate();
 
359
    }
 
360
    else {
 
361
        added = false;
 
362
    }
 
363
    // G_UNLOCK(lists);
 
364
    return added;
 
365
}
 
366
 
 
367
void Folder::eventFileDeleted(const FilePath& path) {
 
368
    // qDebug() << "delete " << path.baseName().get();
 
369
    // G_LOCK(lists);
 
370
    if(files_.find(path.baseName().get()) != files_.cend()) {
 
371
        if(std::find(paths_to_del.cbegin(), paths_to_del.cend(), path) == paths_to_del.cend()) {
 
372
            paths_to_del.push_back(path);
 
373
        }
 
374
    }
 
375
    /* if the file is already queued for addition or update, that operation
 
376
       will be just a waste, therefore cancel it right now */
 
377
    paths_to_add.erase(std::remove(paths_to_add.begin(), paths_to_add.end(), path), paths_to_add.cend());
 
378
    paths_to_update.erase(std::remove(paths_to_update.begin(), paths_to_update.end(), path), paths_to_update.cend());
 
379
    queueUpdate();
 
380
    // G_UNLOCK(lists);
 
381
}
 
382
 
 
383
 
 
384
void Folder::onDirChanged(GFileMonitorEvent evt) {
 
385
    switch(evt) {
 
386
    case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
 
387
        /* g_debug("folder is going to be unmounted"); */
 
388
        break;
 
389
    case G_FILE_MONITOR_EVENT_UNMOUNTED:
 
390
        Q_EMIT unmount();
 
391
        /* g_debug("folder is unmounted"); */
 
392
        queueReload();
 
393
        break;
 
394
    case G_FILE_MONITOR_EVENT_DELETED:
 
395
        Q_EMIT removed();
 
396
        /* g_debug("folder is deleted"); */
 
397
        break;
 
398
    case G_FILE_MONITOR_EVENT_CREATED:
 
399
        queueReload();
 
400
        break;
 
401
    case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
 
402
    case G_FILE_MONITOR_EVENT_CHANGED: {
 
403
        std::lock_guard<std::mutex> lock{mutex_};
 
404
        pending_change_notify = true;
 
405
        if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), dirPath_) != paths_to_update.cend()) {
 
406
            paths_to_update.push_back(dirPath_);
 
407
            queueUpdate();
 
408
        }
 
409
        /* g_debug("folder is changed"); */
 
410
        break;
 
411
    }
 
412
#if GLIB_CHECK_VERSION(2,24,0)
 
413
    case G_FILE_MONITOR_EVENT_MOVED:
 
414
#endif
 
415
    case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
 
416
        ;
 
417
    default:
 
418
        break;
 
419
    }
 
420
}
 
421
 
 
422
void Folder::onFileChangeEvents(GFileMonitor* /*monitor*/, GFile* gf, GFile* /*other_file*/, GFileMonitorEvent evt) {
 
423
    /* const char* names[]={
 
424
        "G_FILE_MONITOR_EVENT_CHANGED",
 
425
        "G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT",
 
426
        "G_FILE_MONITOR_EVENT_DELETED",
 
427
        "G_FILE_MONITOR_EVENT_CREATED",
 
428
        "G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED",
 
429
        "G_FILE_MONITOR_EVENT_PRE_UNMOUNT",
 
430
        "G_FILE_MONITOR_EVENT_UNMOUNTED"
 
431
    }; */
 
432
    if(dirPath_ == gf) {
 
433
        onDirChanged(evt);
 
434
        return;
 
435
    }
 
436
    else {
 
437
        std::lock_guard<std::mutex> lock{mutex_};
 
438
        auto path = FilePath{gf, true};
 
439
        /* NOTE: sometimes, for unknown reasons, GFileMonitor gives us the
 
440
         * same event of the same file for multiple times. So we need to
 
441
         * check for duplications ourselves here. */
 
442
        switch(evt) {
 
443
        case G_FILE_MONITOR_EVENT_CREATED:
 
444
            eventFileAdded(path);
 
445
            break;
 
446
        case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
 
447
        case G_FILE_MONITOR_EVENT_CHANGED:
 
448
            eventFileChanged(path);
 
449
            break;
 
450
        case G_FILE_MONITOR_EVENT_DELETED:
 
451
            eventFileDeleted(path);
 
452
            break;
 
453
        default:
 
454
            return;
 
455
        }
 
456
        queueUpdate();
 
457
    }
 
458
}
 
459
 
 
460
// checks whether there were cut files here
 
461
// and if there were, invalidates this last cut path
 
462
bool Folder::hadCutFilesUnset() {
 
463
    if(lastCutFilesDirPath_ == dirPath_) {
 
464
        lastCutFilesDirPath_ = FilePath();
 
465
        return true;
 
466
    }
 
467
    return false;
 
468
}
 
469
 
 
470
bool Folder::hasCutFiles() {
 
471
    return cutFilesHashSet_
 
472
            && !cutFilesHashSet_->empty()
 
473
            && cutFilesDirPath_ == dirPath_;
 
474
}
 
475
 
 
476
void Folder::setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) {
 
477
    if(cutFilesHashSet_ && !cutFilesHashSet_->empty()) {
 
478
        lastCutFilesDirPath_ = cutFilesDirPath_;
 
479
    }
 
480
    cutFilesDirPath_ = dirPath_;
 
481
    cutFilesHashSet_ = cutFilesHashSet;
 
482
}
 
483
 
 
484
void Folder::onDirListFinished() {
 
485
    DirListJob* job = static_cast<DirListJob*>(sender());
 
486
    if(job->isCancelled()) { // this is a cancelled job, ignore!
 
487
        if(job == dirlist_job) {
 
488
            dirlist_job = nullptr;
 
489
        }
 
490
        Q_EMIT finishLoading();
 
491
        return;
 
492
    }
 
493
    dirInfo_ = job->dirInfo();
 
494
 
 
495
    FileInfoList files_to_add;
 
496
    std::vector<FileInfoPair> files_to_update;
 
497
    const auto& infos = job->files();
 
498
 
 
499
    // with "search://", there is no update for infos and all of them should be added
 
500
    if(strcmp(dirPath_.uriScheme().get(), "search") == 0) {
 
501
        files_to_add = infos;
 
502
        for(auto& file: files_to_add) {
 
503
            files_[file->name()] = file;
 
504
        }
 
505
    }
 
506
    else {
 
507
        auto info_it = infos.cbegin();
 
508
        for(; info_it != infos.cend(); ++info_it) {
 
509
            const auto& info = *info_it;
 
510
            auto it = files_.find(info->name());
 
511
            if(it != files_.end()) {
 
512
                files_to_update.push_back(std::make_pair(it->second, info));
 
513
            }
 
514
            else {
 
515
                files_to_add.push_back(info);
 
516
            }
 
517
            files_[info->name()] = info;
 
518
        }
 
519
    }
 
520
 
 
521
    if(!files_to_add.empty()) {
 
522
        Q_EMIT filesAdded(files_to_add);
 
523
    }
 
524
    if(!files_to_update.empty()) {
 
525
        Q_EMIT filesChanged(files_to_update);
 
526
    }
 
527
 
 
528
#if 0
 
529
    if(dirlist_job->isCancelled() && !wants_incremental) {
 
530
        GList* l;
 
531
        for(l = fm_file_info_list_peek_head_link(job->files); l; l = l->next) {
 
532
            FmFileInfo* inf = (FmFileInfo*)l->data;
 
533
            files = g_slist_prepend(files, inf);
 
534
            fm_file_info_list_push_tail(files, inf);
 
535
        }
 
536
        if(G_LIKELY(files)) {
 
537
            GSList* l;
 
538
 
 
539
            G_LOCK(lists);
 
540
            if(defer_content_test && fm_path_is_native(dir_path))
 
541
                /* we got only basic info on content, schedule update it now */
 
542
                for(l = files; l; l = l->next)
 
543
                    files_to_update = g_slist_prepend(files_to_update,
 
544
                                              fm_path_ref(fm_file_info_get_path(l->data)));
 
545
            G_UNLOCK(lists);
 
546
            g_signal_emit(folder, signals[FILES_ADDED], 0, files);
 
547
            g_slist_free(files);
 
548
        }
 
549
 
 
550
        if(job->dir_fi) {
 
551
            dir_fi = fm_file_info_ref(job->dir_fi);
 
552
        }
 
553
 
 
554
        /* Some new files are created while FmDirListJob is loading the folder. */
 
555
        G_LOCK(lists);
 
556
        if(G_UNLIKELY(files_to_add)) {
 
557
            /* This should be a very rare case. Could this happen? */
 
558
            GSList* l;
 
559
            for(l = files_to_add; l;) {
 
560
                FmPath* path = l->data;
 
561
                GSList* next = l->next;
 
562
                if(_Folder::get_file_by_path(folder, path)) {
 
563
                    /* we already have the file. remove it from files_to_add,
 
564
                     * and put it in files_to_update instead.
 
565
                     * No strdup for name is needed here. We steal
 
566
                     * the string from files_to_add.*/
 
567
                    files_to_update = g_slist_prepend(files_to_update, path);
 
568
                    files_to_add = g_slist_delete_link(files_to_add, l);
 
569
                }
 
570
                l = next;
 
571
            }
 
572
        }
 
573
        G_UNLOCK(lists);
 
574
    }
 
575
    else if(!dir_fi && job->dir_fi)
 
576
        /* we may need dir_fi for incremental folders too */
 
577
    {
 
578
        dir_fi = fm_file_info_ref(job->dir_fi);
 
579
    }
 
580
    g_object_unref(dirlist_job);
 
581
#endif
 
582
 
 
583
    dirlist_job = nullptr;
 
584
    Q_EMIT finishLoading();
 
585
}
 
586
 
 
587
#if 0
 
588
 
 
589
 
 
590
void on_dirlist_job_files_found(FmDirListJob* job, GSList* files, gpointer user_data) {
 
591
    FmFolder* folder = FM_FOLDER(user_data);
 
592
    GSList* l;
 
593
    for(l = files; l; l = l->next) {
 
594
        FmFileInfo* file = FM_FILE_INFO(l->data);
 
595
        fm_file_info_list_push_tail(files, file);
 
596
    }
 
597
    if(G_UNLIKELY(!dir_fi && job->dir_fi))
 
598
        /* we may want info while folder is still loading */
 
599
    {
 
600
        dir_fi = fm_file_info_ref(job->dir_fi);
 
601
    }
 
602
    g_signal_emit(folder, signals[FILES_ADDED], 0, files);
 
603
}
 
604
 
 
605
ErrorAction on_dirlist_job_error(FmDirListJob* job, GError* err, FmJobErrorSeverity severity, FmFolder* folder) {
 
606
    guint ret;
 
607
    /* it's possible that some signal handlers tries to free the folder
 
608
     * when errors occurs, so let's g_object_ref here. */
 
609
    g_object_ref(folder);
 
610
    g_signal_emit(folder, signals[ERROR], 0, err, (guint)severity, &ret);
 
611
    g_object_unref(folder);
 
612
    return ret;
 
613
}
 
614
 
 
615
void free_dirlist_job(FmFolder* folder) {
 
616
    if(wants_incremental) {
 
617
        g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_files_found, folder);
 
618
    }
 
619
    g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_finished, folder);
 
620
    g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_error, folder);
 
621
    fm_job_cancel(FM_JOB(dirlist_job));
 
622
    g_object_unref(dirlist_job);
 
623
    dirlist_job = nullptr;
 
624
}
 
625
 
 
626
#endif
 
627
 
 
628
 
 
629
void Folder::reload() {
 
630
    // cancel in-progress jobs if there are any
 
631
    GError* err = nullptr;
 
632
    // cancel directory monitoring
 
633
    if(dirMonitor_) {
 
634
        g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this);
 
635
        dirMonitor_.reset();
 
636
    }
 
637
 
 
638
    /* clear all update-lists now, see SF bug #919 - if update comes before
 
639
       listing job is finished, a duplicate may be created in the folder */
 
640
    if(has_idle_update_handler) {
 
641
        // FIXME: cancel the idle handler
 
642
        paths_to_add.clear();
 
643
        paths_to_update.clear();
 
644
        paths_to_del.clear();
 
645
 
 
646
        // cancel any file info job in progress.
 
647
        for(auto job: fileinfoJobs_) {
 
648
            job->cancel();
 
649
            disconnect(job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished);
 
650
        }
 
651
        fileinfoJobs_.clear();
 
652
    }
 
653
 
 
654
    /* remove all existing files */
 
655
    if(!files_.empty()) {
 
656
        // FIXME: this is not very efficient :(
 
657
        auto tmp = files();
 
658
        files_.clear();
 
659
        Q_EMIT filesRemoved(tmp);
 
660
    }
 
661
 
 
662
    /* Tell the world that we're about to reload the folder.
 
663
     * It might be a good idea for users of the folder to disconnect
 
664
     * from the folder temporarily and reconnect to it again after
 
665
     * the folder complete the loading. This might reduce some
 
666
     * unnecessary signal handling and UI updates. */
 
667
    Q_EMIT startLoading();
 
668
 
 
669
    dirInfo_.reset(); // clear dir info
 
670
 
 
671
    /* also re-create a new file monitor */
 
672
    // mon = GFileMonitorPtr{fm_monitor_directory(dir_path.gfile().get(), &err), false};
 
673
    // FIXME: should we make this cancellable?
 
674
    dirMonitor_ = GFileMonitorPtr{
 
675
            g_file_monitor_directory(dirPath_.gfile().get(), G_FILE_MONITOR_WATCH_MOUNTS, nullptr, &err),
 
676
            false
 
677
    };
 
678
 
 
679
    if(dirMonitor_) {
 
680
        g_signal_connect(dirMonitor_.get(), "changed", G_CALLBACK(_onFileChangeEvents), this);
 
681
    }
 
682
    else {
 
683
        qDebug("file monitor cannot be created: %s", err->message);
 
684
        g_error_free(err);
 
685
    }
 
686
 
 
687
    Q_EMIT contentChanged();
 
688
 
 
689
    /* run a new dir listing job */
 
690
    // FIXME:
 
691
    // defer_content_test = fm_config->defer_content_test;
 
692
    dirlist_job = new DirListJob(dirPath_, defer_content_test ? DirListJob::FAST : DirListJob::DETAILED,
 
693
                                 hasCutFiles() ? cutFilesHashSet_ : nullptr);
 
694
    dirlist_job->setAutoDelete(true);
 
695
    connect(dirlist_job, &DirListJob::error, this, &Folder::error, Qt::BlockingQueuedConnection);
 
696
    connect(dirlist_job, &DirListJob::finished, this, &Folder::onDirListFinished, Qt::BlockingQueuedConnection);
 
697
 
 
698
#if 0
 
699
    if(wants_incremental) {
 
700
        g_signal_connect(dirlist_job, "files-found", G_CALLBACK(on_dirlist_job_files_found), folder);
 
701
    }
 
702
    fm_dir_list_job_set_incremental(dirlist_job, wants_incremental);
 
703
#endif
 
704
 
 
705
    dirlist_job->runAsync();
 
706
 
 
707
    /* also reload filesystem info.
 
708
     * FIXME: is this needed? */
 
709
    queryFilesystemInfo();
 
710
}
 
711
 
 
712
#if 0
 
713
 
 
714
/**
 
715
 * Folder::is_incremental
 
716
 * @folder: folder to test
 
717
 *
 
718
 * Checks if a folder is incrementally loaded.
 
719
 * After an FmFolder object is obtained from calling Folder::from_path(),
 
720
 * if it's not yet loaded, it begins loading the content of the folder
 
721
 * and emits "start-loading" signal. Most of the time, the info of the
 
722
 * files in the folder becomes available only after the folder is fully
 
723
 * loaded. That means, after the "finish-loading" signal is emitted.
 
724
 * Before the loading is finished, Folder::get_files() returns nothing.
 
725
 * You can tell if a folder is still being loaded with Folder::is_loaded().
 
726
 *
 
727
 * However, for some special FmFolder types, such as the ones handling
 
728
 * search:// URIs, we want to access the file infos while the folder is
 
729
 * still being loaded (the search is still ongoing).
 
730
 * The content of the folder grows incrementally and Folder::get_files()
 
731
 * returns files currently being loaded even when the folder is not
 
732
 * fully loaded. This is what we called incremental.
 
733
 * Folder::is_incremental() tells you if the FmFolder has this feature.
 
734
 *
 
735
 * Returns: %true if @folder is incrementally loaded
 
736
 *
 
737
 * Since: 1.0.2
 
738
 */
 
739
bool Folder::is_incremental(FmFolder* folder) {
 
740
    return wants_incremental;
 
741
}
 
742
 
 
743
#endif
 
744
 
 
745
bool Folder::getFilesystemInfo(uint64_t* total_size, uint64_t* free_size) const {
 
746
    if(has_fs_info) {
 
747
        *total_size = fs_total_size;
 
748
        *free_size = fs_free_size;
 
749
        return true;
 
750
    }
 
751
    return false;
 
752
}
 
753
 
 
754
 
 
755
void Folder::onFileSystemInfoFinished() {
 
756
    FileSystemInfoJob* job = static_cast<FileSystemInfoJob*>(sender());
 
757
    if(job->isCancelled() || job != fsInfoJob_) { // this is a cancelled job, ignore!
 
758
        fsInfoJob_ = nullptr;
 
759
        has_fs_info = false;
 
760
        return;
 
761
    }
 
762
    has_fs_info = job->isAvailable();
 
763
    fs_total_size = job->size();
 
764
    fs_free_size = job->freeSize();
 
765
    filesystem_info_pending = true;
 
766
    fsInfoJob_ = nullptr;
 
767
    queueUpdate();
 
768
}
 
769
 
 
770
 
 
771
void Folder::queryFilesystemInfo() {
 
772
    // G_LOCK(query);
 
773
    if(fsInfoJob_)
 
774
        return;
 
775
    fsInfoJob_ = new FileSystemInfoJob{dirPath_};
 
776
    fsInfoJob_->setAutoDelete(true);
 
777
    connect(fsInfoJob_, &FileSystemInfoJob::finished, this, &Folder::onFileSystemInfoFinished, Qt::BlockingQueuedConnection);
 
778
 
 
779
    fsInfoJob_->runAsync();
 
780
    // G_UNLOCK(query);
 
781
}
 
782
 
 
783
 
 
784
#if 0
 
785
/**
 
786
 * Folder::block_updates
 
787
 * @folder: folder to apply
 
788
 *
 
789
 * Blocks emitting signals for changes in folder, i.e. if some file was
 
790
 * added, changed, or removed in folder after this API, no signal will be
 
791
 * sent until next call to Folder::unblock_updates().
 
792
 *
 
793
 * Since: 1.2.0
 
794
 */
 
795
void Folder::block_updates(FmFolder* folder) {
 
796
    /* g_debug("Folder::block_updates %p", folder); */
 
797
    G_LOCK(lists);
 
798
    /* just set the flag */
 
799
    stop_emission = true;
 
800
    G_UNLOCK(lists);
 
801
}
 
802
 
 
803
/**
 
804
 * Folder::unblock_updates
 
805
 * @folder: folder to apply
 
806
 *
 
807
 * Unblocks emitting signals for changes in folder. If some changes were
 
808
 * in folder after previous call to Folder::block_updates() then these
 
809
 * changes will be sent after this call.
 
810
 *
 
811
 * Since: 1.2.0
 
812
 */
 
813
void Folder::unblock_updates(FmFolder* folder) {
 
814
    /* g_debug("Folder::unblock_updates %p", folder); */
 
815
    G_LOCK(lists);
 
816
    stop_emission = false;
 
817
    /* query update now */
 
818
    queue_update(folder);
 
819
    G_UNLOCK(lists);
 
820
    /* g_debug("Folder::unblock_updates OK"); */
 
821
}
 
822
 
 
823
/**
 
824
 * Folder::make_directory
 
825
 * @folder: folder to apply
 
826
 * @name: display name for new directory
 
827
 * @error: (allow-none) (out): location to save error
 
828
 *
 
829
 * Creates new directory in given @folder.
 
830
 *
 
831
 * Returns: %true in case of success.
 
832
 *
 
833
 * Since: 1.2.0
 
834
 */
 
835
bool Folder::make_directory(FmFolder* folder, const char* name, GError** error) {
 
836
    GFile* dir, *gf;
 
837
    FmPath* path;
 
838
    bool ok;
 
839
 
 
840
    dir = fm_path_to_gfile(dir_path);
 
841
    gf = g_file_get_child_for_display_name(dir, name, error);
 
842
    g_object_unref(dir);
 
843
    if(gf == nullptr) {
 
844
        return false;
 
845
    }
 
846
    ok = g_file_make_directory(gf, nullptr, error);
 
847
    if(ok) {
 
848
        path = fm_path_new_for_gfile(gf);
 
849
        if(!_Folder::event_file_added(folder, path)) {
 
850
            fm_path_unref(path);
 
851
        }
 
852
    }
 
853
    g_object_unref(gf);
 
854
    return ok;
 
855
}
 
856
 
 
857
void Folder::content_changed(FmFolder* folder) {
 
858
    if(has_fs_info && !fs_info_not_avail) {
 
859
        Folder::query_filesystem_info(folder);
 
860
    }
 
861
}
 
862
 
 
863
#endif
 
864
 
 
865
/* NOTE:
 
866
 * GFileMonitor has some significant limitations:
 
867
 * 1. Currently it can correctly emit unmounted event for a directory.
 
868
 * 2. After a directory is unmounted, its content changes.
 
869
 *    Inotify does not fire events for this so a forced reload is needed.
 
870
 * 3. If a folder is empty, and later a filesystem is mounted to the
 
871
 *    folder, its content should reflect the content of the newly mounted
 
872
 *    filesystem. However, GFileMonitor and inotify do not emit events
 
873
 *    for this case. A forced reload might be needed for this case as well.
 
874
 * 4. Some limitations come from Linux/inotify. If FAM/gamin is used,
 
875
 *    the condition may be different. More testing is needed.
 
876
 */
 
877
void Folder::onMountAdded(const Mount& mnt) {
 
878
    /* If a filesystem is mounted over an existing folder,
 
879
     * we need to refresh the content of the folder to reflect
 
880
     * the changes. Besides, we need to create a new GFileMonitor
 
881
     * for the newly-mounted filesystem as the inode already changed.
 
882
     * GFileMonitor cannot detect this kind of changes caused by mounting.
 
883
     * So let's do it ourselves. */
 
884
    auto mountRoot = mnt.root();
 
885
    if(mountRoot.isPrefixOf(dirPath_)) {
 
886
        queueReload();
 
887
    }
 
888
    /* g_debug("FmFolder::mount_added"); */
 
889
}
 
890
 
 
891
void Folder::onMountRemoved(const Mount& mnt) {
 
892
    /* g_debug("FmFolder::mount_removed"); */
 
893
 
 
894
    /* NOTE: gvfs does not emit unmount signals for remote folders since
 
895
     * GFileMonitor does not support remote filesystems at all.
 
896
     * So here is the side effect, no unmount notifications.
 
897
     * We need to generate the signal ourselves. */
 
898
    if(!dirMonitor_) {
 
899
        // this is only needed when we don't have a GFileMonitor
 
900
        auto mountRoot = mnt.root();
 
901
        if(mountRoot.isPrefixOf(dirPath_)) {
 
902
            // if the current folder is under the unmounted path, generate the event ourselves
 
903
            onDirChanged(G_FILE_MONITOR_EVENT_UNMOUNTED);
 
904
        }
 
905
    }
 
906
}
 
907
 
 
908
} // namespace Fm