You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libfm-qt-packaging/src/core/folder.cpp

909 lines
29 KiB

/*
* fm-folder.c
*
* Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
* Copyright 2012-2016 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
*
* This file is a part of the Libfm library.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "folder.h"
#include <string.h>
#include <cassert>
#include <QTimer>
#include <QDebug>
#include "dirlistjob.h"
#include "filesysteminfojob.h"
#include "fileinfojob.h"
namespace Fm {
std::unordered_map<FilePath, std::weak_ptr<Folder>, FilePathHash> Folder::cache_;
FilePath Folder::cutFilesDirPath_;
FilePath Folder::lastCutFilesDirPath_;
std::shared_ptr<const HashSet> Folder::cutFilesHashSet_;
std::mutex Folder::mutex_;
Folder::Folder():
dirlist_job{nullptr},
fsInfoJob_{nullptr},
volumeManager_{VolumeManager::globalInstance()},
/* for file monitor */
has_idle_reload_handler{0},
has_idle_update_handler{false},
pending_change_notify{false},
filesystem_info_pending{false},
wants_incremental{false},
stop_emission{false}, /* don't set it 1 bit to not lock other bits */
/* filesystem info - set in query thread, read in main */
fs_total_size{0},
fs_free_size{0},
has_fs_info{false},
defer_content_test{false} {
connect(volumeManager_.get(), &VolumeManager::mountAdded, this, &Folder::onMountAdded);
connect(volumeManager_.get(), &VolumeManager::mountRemoved, this, &Folder::onMountRemoved);
}
Folder::Folder(const FilePath& path): Folder() {
dirPath_ = path;
}
Folder::~Folder() {
if(dirMonitor_) {
g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this);
dirMonitor_.reset();
}
if(dirlist_job) {
dirlist_job->cancel();
}
// cancel any file info job in progress.
for(auto job: fileinfoJobs_) {
job->cancel();
}
if(fsInfoJob_) {
fsInfoJob_->cancel();
}
// We store a weak_ptr instead of shared_ptr in the hash table, so the hash table
// does not own a reference to the folder. When the last reference to Folder is
// freed, we need to remove its hash table entry.
std::lock_guard<std::mutex> lock{mutex_};
auto it = cache_.find(dirPath_);
if(it != cache_.end()) {
cache_.erase(it);
}
}
// static
std::shared_ptr<Folder> Folder::fromPath(const FilePath& path) {
std::lock_guard<std::mutex> lock{mutex_};
auto it = cache_.find(path);
if(it != cache_.end()) {
auto folder = it->second.lock();
if(folder) {
return folder;
}
else { // FIXME: is this possible?
cache_.erase(it);
}
}
auto folder = std::make_shared<Folder>(path);
folder->reload();
cache_.emplace(path, folder);
return folder;
}
bool Folder::makeDirectory(const char* /*name*/, GError** /*error*/) {
// TODO:
// FIXME: what the API is used for in the original libfm C API?
return false;
}
bool Folder::isIncremental() const {
return wants_incremental;
}
bool Folder::isValid() const {
return dirInfo_ != nullptr;
}
bool Folder::isLoaded() const {
return (dirlist_job == nullptr);
}
std::shared_ptr<const FileInfo> Folder::fileByName(const char* name) const {
auto it = files_.find(name);
if(it != files_.end()) {
return it->second;
}
return nullptr;
}
bool Folder::isEmpty() const {
return files_.empty();
}
FileInfoList Folder::files() const {
FileInfoList ret;
ret.reserve(files_.size());
for(const auto& item : files_) {
ret.push_back(item.second);
}
return ret;
}
const FilePath& Folder::path() const {
auto pathStr = dirPath_.toString();
// qDebug() << this << "FOLDER_PATH:" << pathStr.get() << dirPath_.gfile().get();
//assert(!g_str_has_prefix(pathStr.get(), "file:"));
return dirPath_;
}
const std::shared_ptr<const FileInfo>& Folder::info() const {
return dirInfo_;
}
#if 0
void Folder::init(FmFolder* folder) {
files = fm_file_info_list_new();
G_LOCK(hash);
if(G_UNLIKELY(hash_uses == 0)) {
hash = g_hash_table_new((GHashFunc)fm_path_hash, (GEqualFunc)fm_path_equal);
volume_monitor = g_volume_monitor_get();
if(G_LIKELY(volume_monitor)) {
g_signal_connect(volume_monitor, "mount-added", G_CALLBACK(on_mount_added), nullptr);
g_signal_connect(volume_monitor, "mount-removed", G_CALLBACK(on_mount_removed), nullptr);
}
}
hash_uses++;
G_UNLOCK(hash);
}
#endif
void Folder::onIdleReload() {
/* check if folder still exists */
reload();
// G_LOCK(query);
has_idle_reload_handler = false;
// G_UNLOCK(query);
}
void Folder::queueReload() {
// G_LOCK(query);
if(!has_idle_reload_handler) {
has_idle_reload_handler = true;
QTimer::singleShot(0, this, &Folder::onIdleReload);
}
// G_UNLOCK(query);
}
void Folder::onFileInfoFinished() {
FileInfoJob* job = static_cast<FileInfoJob*>(sender());
fileinfoJobs_.erase(std::find(fileinfoJobs_.cbegin(), fileinfoJobs_.cend(), job));
if(job->isCancelled())
return;
FileInfoList files_to_add;
std::vector<FileInfoPair> files_to_update;
const auto& paths = job->paths();
const auto& infos = job->files();
auto path_it = paths.cbegin();
auto info_it = infos.cbegin();
for(; path_it != paths.cend() && info_it != infos.cend(); ++path_it, ++info_it) {
const auto& path = *path_it;
const auto& info = *info_it;
if(path == dirPath_) { // got the info for the folder itself.
dirInfo_ = info;
}
else {
auto it = files_.find(info->name());
if(it != files_.end()) { // the file already exists, update
files_to_update.push_back(std::make_pair(it->second, info));
}
else { // newly added
files_to_add.push_back(info);
}
files_[info->name()] = info;
}
}
if(!files_to_add.empty()) {
Q_EMIT filesAdded(files_to_add);
}
if(!files_to_update.empty()) {
Q_EMIT filesChanged(files_to_update);
}
Q_EMIT contentChanged();
}
void Folder::processPendingChanges() {
has_idle_update_handler = false;
// FmFileInfoJob* job = nullptr;
std::lock_guard<std::mutex> lock{mutex_};
// idle_handler = 0;
/* if we were asked to block updates let delay it for now */
if(stop_emission) {
return;
}
FileInfoJob* info_job = nullptr;
if(!paths_to_update.empty() || !paths_to_add.empty()) {
FilePathList paths;
paths.insert(paths.end(), paths_to_add.cbegin(), paths_to_add.cend());
paths.insert(paths.end(), paths_to_update.cbegin(), paths_to_update.cend());
info_job = new FileInfoJob{paths, dirPath_,
hasCutFiles() ? cutFilesHashSet_ : nullptr};
paths_to_update.clear();
paths_to_add.clear();
}
if(info_job) {
fileinfoJobs_.push_back(info_job);
info_job->setAutoDelete(true);
connect(info_job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished, Qt::BlockingQueuedConnection);
info_job->runAsync();
#if 0
pending_jobs = g_slist_prepend(pending_jobs, job);
if(!fm_job_run_async(FM_JOB(job))) {
pending_jobs = g_slist_remove(pending_jobs, job);
g_object_unref(job);
g_critical("failed to start folder update job");
}
#endif
}
if(!paths_to_del.empty()) {
FileInfoList deleted_files;
for(const auto &path: paths_to_del) {
auto name = path.baseName();
auto it = files_.find(name.get());
if(it != files_.end()) {
deleted_files.push_back(it->second);
files_.erase(it);
}
}
Q_EMIT filesRemoved(deleted_files);
Q_EMIT contentChanged();
paths_to_del.clear();
}
if(pending_change_notify) {
Q_EMIT changed();
/* update volume info */
queryFilesystemInfo();
pending_change_notify = false;
}
if(filesystem_info_pending) {
Q_EMIT fileSystemChanged();
filesystem_info_pending = false;
}
}
/* should be called only with G_LOCK(lists) on! */
void Folder::queueUpdate() {
// qDebug() << "queue_update:" << !has_idle_handler << paths_to_add.size() << paths_to_update.size() << paths_to_del.size();
if(!has_idle_update_handler) {
QTimer::singleShot(0, this, &Folder::processPendingChanges);
has_idle_update_handler = true;
}
}
/* returns true if reference was taken from path */
bool Folder::eventFileAdded(const FilePath &path) {
bool added = true;
// G_LOCK(lists);
/* make sure that the file is not already queued for addition. */
if(std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) {
if(files_.find(path.baseName().get()) != files_.end()) { // the file already exists, update instead
if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), path) == paths_to_update.cend()) {
paths_to_update.push_back(path);
}
}
else { // newly added file
paths_to_add.push_back(path);
}
/* bug #3591771: 'ln -fns . test' leave no file visible in folder.
If it is queued for deletion then cancel that operation */
paths_to_del.erase(std::remove(paths_to_del.begin(), paths_to_del.end(), path), paths_to_del.cend());
}
else
/* file already queued for adding, don't duplicate */
{
added = false;
}
if(added) {
queueUpdate();
}
// G_UNLOCK(lists);
return added;
}
bool Folder::eventFileChanged(const FilePath &path) {
bool added;
// G_LOCK(lists);
/* make sure that the file is not already queued for changes or
* it's already queued for addition. */
if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), path) == paths_to_update.cend()
&& std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) {
/* Since this function is called only when a file already exists, even if that file
isn't included in "files_" yet, it will be soon due to a previous call to queueUpdate().
So, here, we should queue it for changes regardless of what "files_" may contain. */
paths_to_update.push_back(path);
added = true;
queueUpdate();
}
else {
added = false;
}
// G_UNLOCK(lists);
return added;
}
void Folder::eventFileDeleted(const FilePath& path) {
// qDebug() << "delete " << path.baseName().get();
// G_LOCK(lists);
if(files_.find(path.baseName().get()) != files_.cend()) {
if(std::find(paths_to_del.cbegin(), paths_to_del.cend(), path) == paths_to_del.cend()) {
paths_to_del.push_back(path);
}
}
/* if the file is already queued for addition or update, that operation
will be just a waste, therefore cancel it right now */
paths_to_add.erase(std::remove(paths_to_add.begin(), paths_to_add.end(), path), paths_to_add.cend());
paths_to_update.erase(std::remove(paths_to_update.begin(), paths_to_update.end(), path), paths_to_update.cend());
queueUpdate();
// G_UNLOCK(lists);
}
void Folder::onDirChanged(GFileMonitorEvent evt) {
switch(evt) {
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
/* g_debug("folder is going to be unmounted"); */
break;
case G_FILE_MONITOR_EVENT_UNMOUNTED:
Q_EMIT unmount();
/* g_debug("folder is unmounted"); */
queueReload();
break;
case G_FILE_MONITOR_EVENT_DELETED:
Q_EMIT removed();
/* g_debug("folder is deleted"); */
break;
case G_FILE_MONITOR_EVENT_CREATED:
queueReload();
break;
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
case G_FILE_MONITOR_EVENT_CHANGED: {
std::lock_guard<std::mutex> lock{mutex_};
pending_change_notify = true;
if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), dirPath_) != paths_to_update.cend()) {
paths_to_update.push_back(dirPath_);
queueUpdate();
}
/* g_debug("folder is changed"); */
break;
}
#if GLIB_CHECK_VERSION(2,24,0)
case G_FILE_MONITOR_EVENT_MOVED:
#endif
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
;
default:
break;
}
}
void Folder::onFileChangeEvents(GFileMonitor* /*monitor*/, GFile* gf, GFile* /*other_file*/, GFileMonitorEvent evt) {
/* const char* names[]={
"G_FILE_MONITOR_EVENT_CHANGED",
"G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT",
"G_FILE_MONITOR_EVENT_DELETED",
"G_FILE_MONITOR_EVENT_CREATED",
"G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED",
"G_FILE_MONITOR_EVENT_PRE_UNMOUNT",
"G_FILE_MONITOR_EVENT_UNMOUNTED"
}; */
if(dirPath_ == gf) {
onDirChanged(evt);
return;
}
else {
std::lock_guard<std::mutex> lock{mutex_};
auto path = FilePath{gf, true};
/* NOTE: sometimes, for unknown reasons, GFileMonitor gives us the
* same event of the same file for multiple times. So we need to
* check for duplications ourselves here. */
switch(evt) {
case G_FILE_MONITOR_EVENT_CREATED:
eventFileAdded(path);
break;
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
case G_FILE_MONITOR_EVENT_CHANGED:
eventFileChanged(path);
break;
case G_FILE_MONITOR_EVENT_DELETED:
eventFileDeleted(path);
break;
default:
return;
}
queueUpdate();
}
}
// checks whether there were cut files here
// and if there were, invalidates this last cut path
bool Folder::hadCutFilesUnset() {
if(lastCutFilesDirPath_ == dirPath_) {
lastCutFilesDirPath_ = FilePath();
return true;
}
return false;
}
bool Folder::hasCutFiles() {
return cutFilesHashSet_
&& !cutFilesHashSet_->empty()
&& cutFilesDirPath_ == dirPath_;
}
void Folder::setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) {
if(cutFilesHashSet_ && !cutFilesHashSet_->empty()) {
lastCutFilesDirPath_ = cutFilesDirPath_;
}
cutFilesDirPath_ = dirPath_;
cutFilesHashSet_ = cutFilesHashSet;
}
void Folder::onDirListFinished() {
DirListJob* job = static_cast<DirListJob*>(sender());
if(job->isCancelled()) { // this is a cancelled job, ignore!
if(job == dirlist_job) {
dirlist_job = nullptr;
}
Q_EMIT finishLoading();
return;
}
dirInfo_ = job->dirInfo();
FileInfoList files_to_add;
std::vector<FileInfoPair> files_to_update;
const auto& infos = job->files();
// with "search://", there is no update for infos and all of them should be added
if(strcmp(dirPath_.uriScheme().get(), "search") == 0) {
files_to_add = infos;
for(auto& file: files_to_add) {
files_[file->name()] = file;
}
}
else {
auto info_it = infos.cbegin();
for(; info_it != infos.cend(); ++info_it) {
const auto& info = *info_it;
auto it = files_.find(info->name());
if(it != files_.end()) {
files_to_update.push_back(std::make_pair(it->second, info));
}
else {
files_to_add.push_back(info);
}
files_[info->name()] = info;
}
}
if(!files_to_add.empty()) {
Q_EMIT filesAdded(files_to_add);
}
if(!files_to_update.empty()) {
Q_EMIT filesChanged(files_to_update);
}
#if 0
if(dirlist_job->isCancelled() && !wants_incremental) {
GList* l;
for(l = fm_file_info_list_peek_head_link(job->files); l; l = l->next) {
FmFileInfo* inf = (FmFileInfo*)l->data;
files = g_slist_prepend(files, inf);
fm_file_info_list_push_tail(files, inf);
}
if(G_LIKELY(files)) {
GSList* l;
G_LOCK(lists);
if(defer_content_test && fm_path_is_native(dir_path))
/* we got only basic info on content, schedule update it now */
for(l = files; l; l = l->next)
files_to_update = g_slist_prepend(files_to_update,
fm_path_ref(fm_file_info_get_path(l->data)));
G_UNLOCK(lists);
g_signal_emit(folder, signals[FILES_ADDED], 0, files);
g_slist_free(files);
}
if(job->dir_fi) {
dir_fi = fm_file_info_ref(job->dir_fi);
}
/* Some new files are created while FmDirListJob is loading the folder. */
G_LOCK(lists);
if(G_UNLIKELY(files_to_add)) {
/* This should be a very rare case. Could this happen? */
GSList* l;
for(l = files_to_add; l;) {
FmPath* path = l->data;
GSList* next = l->next;
if(_Folder::get_file_by_path(folder, path)) {
/* we already have the file. remove it from files_to_add,
* and put it in files_to_update instead.
* No strdup for name is needed here. We steal
* the string from files_to_add.*/
files_to_update = g_slist_prepend(files_to_update, path);
files_to_add = g_slist_delete_link(files_to_add, l);
}
l = next;
}
}
G_UNLOCK(lists);
}
else if(!dir_fi && job->dir_fi)
/* we may need dir_fi for incremental folders too */
{
dir_fi = fm_file_info_ref(job->dir_fi);
}
g_object_unref(dirlist_job);
#endif
dirlist_job = nullptr;
Q_EMIT finishLoading();
}
#if 0
void on_dirlist_job_files_found(FmDirListJob* job, GSList* files, gpointer user_data) {
FmFolder* folder = FM_FOLDER(user_data);
GSList* l;
for(l = files; l; l = l->next) {
FmFileInfo* file = FM_FILE_INFO(l->data);
fm_file_info_list_push_tail(files, file);
}
if(G_UNLIKELY(!dir_fi && job->dir_fi))
/* we may want info while folder is still loading */
{
dir_fi = fm_file_info_ref(job->dir_fi);
}
g_signal_emit(folder, signals[FILES_ADDED], 0, files);
}
ErrorAction on_dirlist_job_error(FmDirListJob* job, GError* err, FmJobErrorSeverity severity, FmFolder* folder) {
guint ret;
/* it's possible that some signal handlers tries to free the folder
* when errors occurs, so let's g_object_ref here. */
g_object_ref(folder);
g_signal_emit(folder, signals[ERROR], 0, err, (guint)severity, &ret);
g_object_unref(folder);
return ret;
}
void free_dirlist_job(FmFolder* folder) {
if(wants_incremental) {
g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_files_found, folder);
}
g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_finished, folder);
g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_error, folder);
fm_job_cancel(FM_JOB(dirlist_job));
g_object_unref(dirlist_job);
dirlist_job = nullptr;
}
#endif
void Folder::reload() {
// cancel in-progress jobs if there are any
GError* err = nullptr;
// cancel directory monitoring
if(dirMonitor_) {
g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this);
dirMonitor_.reset();
}
/* clear all update-lists now, see SF bug #919 - if update comes before
listing job is finished, a duplicate may be created in the folder */
if(has_idle_update_handler) {
// FIXME: cancel the idle handler
paths_to_add.clear();
paths_to_update.clear();
paths_to_del.clear();
// cancel any file info job in progress.
for(auto job: fileinfoJobs_) {
job->cancel();
disconnect(job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished);
}
fileinfoJobs_.clear();
}
/* remove all existing files */
if(!files_.empty()) {
// FIXME: this is not very efficient :(
auto tmp = files();
files_.clear();
Q_EMIT filesRemoved(tmp);
}
/* Tell the world that we're about to reload the folder.
* It might be a good idea for users of the folder to disconnect
* from the folder temporarily and reconnect to it again after
* the folder complete the loading. This might reduce some
* unnecessary signal handling and UI updates. */
Q_EMIT startLoading();
dirInfo_.reset(); // clear dir info
/* also re-create a new file monitor */
// mon = GFileMonitorPtr{fm_monitor_directory(dir_path.gfile().get(), &err), false};
// FIXME: should we make this cancellable?
dirMonitor_ = GFileMonitorPtr{
g_file_monitor_directory(dirPath_.gfile().get(), G_FILE_MONITOR_WATCH_MOUNTS, nullptr, &err),
false
};
if(dirMonitor_) {
g_signal_connect(dirMonitor_.get(), "changed", G_CALLBACK(_onFileChangeEvents), this);
}
else {
qDebug("file monitor cannot be created: %s", err->message);
g_error_free(err);
}
Q_EMIT contentChanged();
/* run a new dir listing job */
// FIXME:
// defer_content_test = fm_config->defer_content_test;
dirlist_job = new DirListJob(dirPath_, defer_content_test ? DirListJob::FAST : DirListJob::DETAILED,
hasCutFiles() ? cutFilesHashSet_ : nullptr);
dirlist_job->setAutoDelete(true);
connect(dirlist_job, &DirListJob::error, this, &Folder::error, Qt::BlockingQueuedConnection);
connect(dirlist_job, &DirListJob::finished, this, &Folder::onDirListFinished, Qt::BlockingQueuedConnection);
#if 0
if(wants_incremental) {
g_signal_connect(dirlist_job, "files-found", G_CALLBACK(on_dirlist_job_files_found), folder);
}
fm_dir_list_job_set_incremental(dirlist_job, wants_incremental);
#endif
dirlist_job->runAsync();
/* also reload filesystem info.
* FIXME: is this needed? */
queryFilesystemInfo();
}
#if 0
/**
* Folder::is_incremental
* @folder: folder to test
*
* Checks if a folder is incrementally loaded.
* After an FmFolder object is obtained from calling Folder::from_path(),
* if it's not yet loaded, it begins loading the content of the folder
* and emits "start-loading" signal. Most of the time, the info of the
* files in the folder becomes available only after the folder is fully
* loaded. That means, after the "finish-loading" signal is emitted.
* Before the loading is finished, Folder::get_files() returns nothing.
* You can tell if a folder is still being loaded with Folder::is_loaded().
*
* However, for some special FmFolder types, such as the ones handling
* search:// URIs, we want to access the file infos while the folder is
* still being loaded (the search is still ongoing).
* The content of the folder grows incrementally and Folder::get_files()
* returns files currently being loaded even when the folder is not
* fully loaded. This is what we called incremental.
* Folder::is_incremental() tells you if the FmFolder has this feature.
*
* Returns: %true if @folder is incrementally loaded
*
* Since: 1.0.2
*/
bool Folder::is_incremental(FmFolder* folder) {
return wants_incremental;
}
#endif
bool Folder::getFilesystemInfo(uint64_t* total_size, uint64_t* free_size) const {
if(has_fs_info) {
*total_size = fs_total_size;
*free_size = fs_free_size;
return true;
}
return false;
}
void Folder::onFileSystemInfoFinished() {
FileSystemInfoJob* job = static_cast<FileSystemInfoJob*>(sender());
if(job->isCancelled() || job != fsInfoJob_) { // this is a cancelled job, ignore!
fsInfoJob_ = nullptr;
has_fs_info = false;
return;
}
has_fs_info = job->isAvailable();
fs_total_size = job->size();
fs_free_size = job->freeSize();
filesystem_info_pending = true;
fsInfoJob_ = nullptr;
queueUpdate();
}
void Folder::queryFilesystemInfo() {
// G_LOCK(query);
if(fsInfoJob_)
return;
fsInfoJob_ = new FileSystemInfoJob{dirPath_};
fsInfoJob_->setAutoDelete(true);
connect(fsInfoJob_, &FileSystemInfoJob::finished, this, &Folder::onFileSystemInfoFinished, Qt::BlockingQueuedConnection);
fsInfoJob_->runAsync();
// G_UNLOCK(query);
}
#if 0
/**
* Folder::block_updates
* @folder: folder to apply
*
* Blocks emitting signals for changes in folder, i.e. if some file was
* added, changed, or removed in folder after this API, no signal will be
* sent until next call to Folder::unblock_updates().
*
* Since: 1.2.0
*/
void Folder::block_updates(FmFolder* folder) {
/* g_debug("Folder::block_updates %p", folder); */
G_LOCK(lists);
/* just set the flag */
stop_emission = true;
G_UNLOCK(lists);
}
/**
* Folder::unblock_updates
* @folder: folder to apply
*
* Unblocks emitting signals for changes in folder. If some changes were
* in folder after previous call to Folder::block_updates() then these
* changes will be sent after this call.
*
* Since: 1.2.0
*/
void Folder::unblock_updates(FmFolder* folder) {
/* g_debug("Folder::unblock_updates %p", folder); */
G_LOCK(lists);
stop_emission = false;
/* query update now */
queue_update(folder);
G_UNLOCK(lists);
/* g_debug("Folder::unblock_updates OK"); */
}
/**
* Folder::make_directory
* @folder: folder to apply
* @name: display name for new directory
* @error: (allow-none) (out): location to save error
*
* Creates new directory in given @folder.
*
* Returns: %true in case of success.
*
* Since: 1.2.0
*/
bool Folder::make_directory(FmFolder* folder, const char* name, GError** error) {
GFile* dir, *gf;
FmPath* path;
bool ok;
dir = fm_path_to_gfile(dir_path);
gf = g_file_get_child_for_display_name(dir, name, error);
g_object_unref(dir);
if(gf == nullptr) {
return false;
}
ok = g_file_make_directory(gf, nullptr, error);
if(ok) {
path = fm_path_new_for_gfile(gf);
if(!_Folder::event_file_added(folder, path)) {
fm_path_unref(path);
}
}
g_object_unref(gf);
return ok;
}
void Folder::content_changed(FmFolder* folder) {
if(has_fs_info && !fs_info_not_avail) {
Folder::query_filesystem_info(folder);
}
}
#endif
/* NOTE:
* GFileMonitor has some significant limitations:
* 1. Currently it can correctly emit unmounted event for a directory.
* 2. After a directory is unmounted, its content changes.
* Inotify does not fire events for this so a forced reload is needed.
* 3. If a folder is empty, and later a filesystem is mounted to the
* folder, its content should reflect the content of the newly mounted
* filesystem. However, GFileMonitor and inotify do not emit events
* for this case. A forced reload might be needed for this case as well.
* 4. Some limitations come from Linux/inotify. If FAM/gamin is used,
* the condition may be different. More testing is needed.
*/
void Folder::onMountAdded(const Mount& mnt) {
/* If a filesystem is mounted over an existing folder,
* we need to refresh the content of the folder to reflect
* the changes. Besides, we need to create a new GFileMonitor
* for the newly-mounted filesystem as the inode already changed.
* GFileMonitor cannot detect this kind of changes caused by mounting.
* So let's do it ourselves. */
auto mountRoot = mnt.root();
if(mountRoot.isPrefixOf(dirPath_)) {
queueReload();
}
/* g_debug("FmFolder::mount_added"); */
}
void Folder::onMountRemoved(const Mount& mnt) {
/* g_debug("FmFolder::mount_removed"); */
/* NOTE: gvfs does not emit unmount signals for remote folders since
* GFileMonitor does not support remote filesystems at all.
* So here is the side effect, no unmount notifications.
* We need to generate the signal ourselves. */
if(!dirMonitor_) {
// this is only needed when we don't have a GFileMonitor
auto mountRoot = mnt.root();
if(mountRoot.isPrefixOf(dirPath_)) {
// if the current folder is under the unmounted path, generate the event ourselves
onDirChanged(G_FILE_MONITOR_EVENT_UNMOUNTED);
}
}
}
} // namespace Fm