* Switched to experimental * Bumped Standards to 4.1.1 * Fixed Symbols * Added build dependency libexif-dev * Added dependencies libexif-dev and libmenu-cache-dev to libfm-qt-dev * Added override for dh_missing * Bumped years in copyright
909 lines
29 KiB
C++
909 lines
29 KiB
C++
/*
|
|
* 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
|