4
* Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5
* Copyright 2012-2016 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
7
* This file is a part of the Libfm library.
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.
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.
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
30
#include "dirlistjob.h"
31
#include "filesysteminfojob.h"
32
#include "fileinfojob.h"
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_;
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 */
57
defer_content_test{false} {
59
connect(volumeManager_.get(), &VolumeManager::mountAdded, this, &Folder::onMountAdded);
60
connect(volumeManager_.get(), &VolumeManager::mountRemoved, this, &Folder::onMountRemoved);
63
Folder::Folder(const FilePath& path): Folder() {
69
g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this);
74
dirlist_job->cancel();
77
// cancel any file info job in progress.
78
for(auto job: fileinfoJobs_) {
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()) {
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();
105
else { // FIXME: is this possible?
109
auto folder = std::make_shared<Folder>(path);
111
cache_.emplace(path, folder);
115
bool Folder::makeDirectory(const char* /*name*/, GError** /*error*/) {
117
// FIXME: what the API is used for in the original libfm C API?
121
bool Folder::isIncremental() const {
122
return wants_incremental;
125
bool Folder::isValid() const {
126
return dirInfo_ != nullptr;
129
bool Folder::isLoaded() const {
130
return (dirlist_job == nullptr);
133
std::shared_ptr<const FileInfo> Folder::fileByName(const char* name) const {
134
auto it = files_.find(name);
135
if(it != files_.end()) {
141
bool Folder::isEmpty() const {
142
return files_.empty();
145
FileInfoList Folder::files() const {
147
ret.reserve(files_.size());
148
for(const auto& item : files_) {
149
ret.push_back(item.second);
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:"));
162
const std::shared_ptr<const FileInfo>& Folder::info() const {
167
void Folder::init(FmFolder* folder) {
168
files = fm_file_info_list_new();
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);
183
void Folder::onIdleReload() {
184
/* check if folder still exists */
187
has_idle_reload_handler = false;
191
void Folder::queueReload() {
193
if(!has_idle_reload_handler) {
194
has_idle_reload_handler = true;
195
QTimer::singleShot(0, this, &Folder::onIdleReload);
200
void Folder::onFileInfoFinished() {
201
FileInfoJob* job = static_cast<FileInfoJob*>(sender());
202
fileinfoJobs_.erase(std::find(fileinfoJobs_.cbegin(), fileinfoJobs_.cend(), job));
204
if(job->isCancelled())
207
FileInfoList files_to_add;
208
std::vector<FileInfoPair> files_to_update;
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;
218
if(path == dirPath_) { // got the info for the folder itself.
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));
226
else { // newly added
227
files_to_add.push_back(info);
229
files_[info->name()] = info;
232
if(!files_to_add.empty()) {
233
Q_EMIT filesAdded(files_to_add);
235
if(!files_to_update.empty()) {
236
Q_EMIT filesChanged(files_to_update);
238
Q_EMIT contentChanged();
241
void Folder::processPendingChanges() {
242
has_idle_update_handler = false;
243
// FmFileInfoJob* job = nullptr;
244
std::lock_guard<std::mutex> lock{mutex_};
247
/* if we were asked to block updates let delay it for now */
252
FileInfoJob* info_job = nullptr;
253
if(!paths_to_update.empty() || !paths_to_add.empty()) {
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();
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();
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);
273
g_critical("failed to start folder update job");
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);
288
Q_EMIT filesRemoved(deleted_files);
289
Q_EMIT contentChanged();
290
paths_to_del.clear();
293
if(pending_change_notify) {
295
/* update volume info */
296
queryFilesystemInfo();
297
pending_change_notify = false;
300
if(filesystem_info_pending) {
301
Q_EMIT fileSystemChanged();
302
filesystem_info_pending = false;
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;
316
/* returns true if reference was taken from path */
317
bool Folder::eventFileAdded(const FilePath &path) {
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);
327
else { // newly added file
328
paths_to_add.push_back(path);
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());
335
/* file already queued for adding, don't duplicate */
346
bool Folder::eventFileChanged(const FilePath &path) {
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);
367
void Folder::eventFileDeleted(const FilePath& path) {
368
// qDebug() << "delete " << path.baseName().get();
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);
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());
384
void Folder::onDirChanged(GFileMonitorEvent evt) {
386
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
387
/* g_debug("folder is going to be unmounted"); */
389
case G_FILE_MONITOR_EVENT_UNMOUNTED:
391
/* g_debug("folder is unmounted"); */
394
case G_FILE_MONITOR_EVENT_DELETED:
396
/* g_debug("folder is deleted"); */
398
case G_FILE_MONITOR_EVENT_CREATED:
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_);
409
/* g_debug("folder is changed"); */
412
#if GLIB_CHECK_VERSION(2,24,0)
413
case G_FILE_MONITOR_EVENT_MOVED:
415
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
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"
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. */
443
case G_FILE_MONITOR_EVENT_CREATED:
444
eventFileAdded(path);
446
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
447
case G_FILE_MONITOR_EVENT_CHANGED:
448
eventFileChanged(path);
450
case G_FILE_MONITOR_EVENT_DELETED:
451
eventFileDeleted(path);
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();
470
bool Folder::hasCutFiles() {
471
return cutFilesHashSet_
472
&& !cutFilesHashSet_->empty()
473
&& cutFilesDirPath_ == dirPath_;
476
void Folder::setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) {
477
if(cutFilesHashSet_ && !cutFilesHashSet_->empty()) {
478
lastCutFilesDirPath_ = cutFilesDirPath_;
480
cutFilesDirPath_ = dirPath_;
481
cutFilesHashSet_ = cutFilesHashSet;
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;
490
Q_EMIT finishLoading();
493
dirInfo_ = job->dirInfo();
495
FileInfoList files_to_add;
496
std::vector<FileInfoPair> files_to_update;
497
const auto& infos = job->files();
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;
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));
515
files_to_add.push_back(info);
517
files_[info->name()] = info;
521
if(!files_to_add.empty()) {
522
Q_EMIT filesAdded(files_to_add);
524
if(!files_to_update.empty()) {
525
Q_EMIT filesChanged(files_to_update);
529
if(dirlist_job->isCancelled() && !wants_incremental) {
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);
536
if(G_LIKELY(files)) {
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)));
546
g_signal_emit(folder, signals[FILES_ADDED], 0, files);
551
dir_fi = fm_file_info_ref(job->dir_fi);
554
/* Some new files are created while FmDirListJob is loading the folder. */
556
if(G_UNLIKELY(files_to_add)) {
557
/* This should be a very rare case. Could this happen? */
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);
575
else if(!dir_fi && job->dir_fi)
576
/* we may need dir_fi for incremental folders too */
578
dir_fi = fm_file_info_ref(job->dir_fi);
580
g_object_unref(dirlist_job);
583
dirlist_job = nullptr;
584
Q_EMIT finishLoading();
590
void on_dirlist_job_files_found(FmDirListJob* job, GSList* files, gpointer user_data) {
591
FmFolder* folder = FM_FOLDER(user_data);
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);
597
if(G_UNLIKELY(!dir_fi && job->dir_fi))
598
/* we may want info while folder is still loading */
600
dir_fi = fm_file_info_ref(job->dir_fi);
602
g_signal_emit(folder, signals[FILES_ADDED], 0, files);
605
ErrorAction on_dirlist_job_error(FmDirListJob* job, GError* err, FmJobErrorSeverity severity, FmFolder* folder) {
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);
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);
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;
629
void Folder::reload() {
630
// cancel in-progress jobs if there are any
631
GError* err = nullptr;
632
// cancel directory monitoring
634
g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this);
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();
646
// cancel any file info job in progress.
647
for(auto job: fileinfoJobs_) {
649
disconnect(job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished);
651
fileinfoJobs_.clear();
654
/* remove all existing files */
655
if(!files_.empty()) {
656
// FIXME: this is not very efficient :(
659
Q_EMIT filesRemoved(tmp);
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();
669
dirInfo_.reset(); // clear dir info
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),
680
g_signal_connect(dirMonitor_.get(), "changed", G_CALLBACK(_onFileChangeEvents), this);
683
qDebug("file monitor cannot be created: %s", err->message);
687
Q_EMIT contentChanged();
689
/* run a new dir listing job */
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);
699
if(wants_incremental) {
700
g_signal_connect(dirlist_job, "files-found", G_CALLBACK(on_dirlist_job_files_found), folder);
702
fm_dir_list_job_set_incremental(dirlist_job, wants_incremental);
705
dirlist_job->runAsync();
707
/* also reload filesystem info.
708
* FIXME: is this needed? */
709
queryFilesystemInfo();
715
* Folder::is_incremental
716
* @folder: folder to test
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().
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.
735
* Returns: %true if @folder is incrementally loaded
739
bool Folder::is_incremental(FmFolder* folder) {
740
return wants_incremental;
745
bool Folder::getFilesystemInfo(uint64_t* total_size, uint64_t* free_size) const {
747
*total_size = fs_total_size;
748
*free_size = fs_free_size;
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;
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;
771
void Folder::queryFilesystemInfo() {
775
fsInfoJob_ = new FileSystemInfoJob{dirPath_};
776
fsInfoJob_->setAutoDelete(true);
777
connect(fsInfoJob_, &FileSystemInfoJob::finished, this, &Folder::onFileSystemInfoFinished, Qt::BlockingQueuedConnection);
779
fsInfoJob_->runAsync();
786
* Folder::block_updates
787
* @folder: folder to apply
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().
795
void Folder::block_updates(FmFolder* folder) {
796
/* g_debug("Folder::block_updates %p", folder); */
798
/* just set the flag */
799
stop_emission = true;
804
* Folder::unblock_updates
805
* @folder: folder to apply
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.
813
void Folder::unblock_updates(FmFolder* folder) {
814
/* g_debug("Folder::unblock_updates %p", folder); */
816
stop_emission = false;
817
/* query update now */
818
queue_update(folder);
820
/* g_debug("Folder::unblock_updates OK"); */
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
829
* Creates new directory in given @folder.
831
* Returns: %true in case of success.
835
bool Folder::make_directory(FmFolder* folder, const char* name, GError** error) {
840
dir = fm_path_to_gfile(dir_path);
841
gf = g_file_get_child_for_display_name(dir, name, error);
846
ok = g_file_make_directory(gf, nullptr, error);
848
path = fm_path_new_for_gfile(gf);
849
if(!_Folder::event_file_added(folder, path)) {
857
void Folder::content_changed(FmFolder* folder) {
858
if(has_fs_info && !fs_info_not_avail) {
859
Folder::query_filesystem_info(folder);
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.
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_)) {
888
/* g_debug("FmFolder::mount_added"); */
891
void Folder::onMountRemoved(const Mount& mnt) {
892
/* g_debug("FmFolder::mount_removed"); */
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. */
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);