* 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 copyrightubuntu/cosmic debian/0.12.0-1
parent
2e496b302c
commit
ea757e4af0
@ -0,0 +1,10 @@
|
||||
install(FILES
|
||||
"archivers.list"
|
||||
"terminals.list"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/libfm-qt"
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"libfm-qt-mimetypes.xml"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages"
|
||||
)
|
@ -0,0 +1,35 @@
|
||||
[file-roller]
|
||||
create=file-roller --add %U
|
||||
extract=file-roller --extract %U
|
||||
extract_to=file-roller --extract-to %d %U
|
||||
mime_types=application/x-7z-compressed;application/x-7z-compressed-tar;application/x-ace;application/x-alz;application/x-ar;application/x-arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-bzip1;application/x-bzip1-compressed-tar;application/x-cabinet;application/x-cbr;application/x-cbz;application/x-cd-image;application/x-compress;application/x-compressed-tar;application/x-cpio;application/x-deb;application/x-ear;application/x-ms-dos-executable;application/x-gtar;application/x-gzip;application/x-gzpostscript;application/x-java-archive;application/x-lha;application/x-lhz;application/x-lzip;application/x-lzip-compressed-tar;application/x-lzma;application/x-lzma-compressed-tar;application/x-lzop;application/x-lzop-compressed-tar;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-rpm;application/x-rzip;application/x-tar;application/x-tarz;application/x-stuffit;application/x-war;application/x-xz;application/x-xz-compressed-tar;application/x-zip;application/x-zip-compressed;application/x-zoo;application/zip;multipart/x-zip;
|
||||
supports_uris=true
|
||||
|
||||
[xarchiver]
|
||||
create=xarchiver --add-to %F
|
||||
extract=xarchiver --extract %F
|
||||
extract_to=xarchiver --extract-to %d %F
|
||||
mime_types=application/x-arj;application/arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-gzip;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-tar;application/x-zip;application/x-zip-compressed;application/zip;multipart/x-zip;application/x-7z-compressed;application/x-compressed-tar;application/x-bzip2;application/x-bzip2-compressed-tar;application/x-lzma-compressed-tar;application/x-lzma;application/x-deb;application/deb;application/vnd.debian.binary-package;application/x-xz;application/x-xz-compressed-tar;application/x-rpm;application/x-source-rpm;application/x-lzop;application/x-lzop-compressed-tar;application/x-tzo;application/x-war;application/x-compress;application/x-tarz;application/x-java-archive;application/x-lha;application/x-lhz;
|
||||
|
||||
[squeeze]
|
||||
create=squeeze --new %F
|
||||
extract=squeeze --extract %F
|
||||
extract_to=squeeze --extract-to %d %F
|
||||
mime_types=application/x-bzip-compressed-tar;application/x-bzip2-compressed-tar;application/x-compressed-tar;application/x-tar;application/x-tarz;application/x-tzo;application/x-zip;application/x-zip-compressed;application/zip;application/x-rar;application/vnd.rar;application/x-gzip;application/x-bzip;application/x-lzop;application/x-compress;
|
||||
|
||||
[engrampa]
|
||||
create=engrampa --add %U
|
||||
extract=engrampa --extract %U
|
||||
extract_to=engrampa --extract-to %d %U
|
||||
mime_types=application/x-7z-compressed;application/x-7z-compressed-tar;application/x-ace;application/x-alz;application/x-ar;application/x-arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-bzip1;application/x-bzip1-compressed-tar;application/x-cabinet;application/x-cbr;application/x-cbz;application/x-cd-image;application/x-compress;application/x-compressed-tar;application/x-cpio;application/x-deb;application/x-ear;application/x-ms-dos-executable;application/x-gtar;application/x-gzip;application/x-gzpostscript;application/x-java-archive;application/x-lha;application/x-lhz;application/x-lzip;application/x-lzip-compressed-tar;application/x-lzma;application/x-lzma-compressed-tar;application/x-lzop;application/x-lzop-compressed-tar;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-rpm;application/x-rzip;application/x-tar;application/x-tarz;application/x-stuffit;application/x-war;application/x-xz;application/x-xz-compressed-tar;application/x-zip;application/x-zip-compressed;application/x-zoo;application/zip;multipart/x-zip;
|
||||
supports_uris=true
|
||||
|
||||
# The KDE archiver Ark
|
||||
# Here we use %F instead of %U since KDE programs do not know the URI provided by gvfs.
|
||||
# GIO will pass FUSE-based file paths to the KDE programs, which should still work.
|
||||
[ark]
|
||||
create=ark --add --dialog %F
|
||||
extract=ark --batch --dialog %F
|
||||
extract_to=ark --batch --destination %d %F
|
||||
mime_types=application/x-tar;application/x-compressed-tar;application/x-bzip-compressed-tar;application/x-tarz;application/x-xz-compressed-tar;application/x-lzma-compressed-tar;application/x-deb;application/x-cd-image;application/x-bcpio;application/x-cpio;application/x-cpio-compressed;application/x-sv4cpio;application/x-sv4crc;application/x-rpm;application/x-source-rpm;application/vnd.ms-cab-compressed;application/x-servicepack;application/x-rar;application/vnd.rar;application/x-7z-compressed;application/x-java-archive;application/zip;application/x-compress;application/x-gzip;application/x-bzip;application/x-bzip2;application/x-lzma;application/x-xz;application/lha;application/x-lha;application/maclha;
|
||||
supports_uris=true
|
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Additional mime-types provided by libfm, adding some
|
||||
missing but frequently seen globs for some common mime-types.
|
||||
-->
|
||||
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
|
||||
|
||||
<mime-type type="text/plain">
|
||||
<glob pattern="*.ini"/>
|
||||
<glob pattern="*.inf"/>
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-ms-dos-executable">
|
||||
<glob pattern="*.com" />
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-ms-win-installer">
|
||||
<comment>Windows installer</comment>
|
||||
<comment xml:lang="zh_TW">Windows 安裝程式</comment>
|
||||
<glob pattern="*.msi" />
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-vbscript">
|
||||
<comment>MS VBScript</comment>
|
||||
<glob pattern="*.vbs" />
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="text/x-csharp">
|
||||
<comment xml:lang="en">C# source</comment>
|
||||
<comment xml:lang="zh_TW">C# 程式碼</comment>
|
||||
<glob pattern="*.cs"/>
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-desktop">
|
||||
<comment xml:lang="zh_TW">應用程式捷徑</comment>
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-sharedlib">
|
||||
<!--
|
||||
This pattern matching is not very accurate ,but the probability that
|
||||
a file is named like this and it's not a shared lib, is very low.
|
||||
-->
|
||||
<glob pattern="*.dll"/> <!-- Windows dll are shared libs, too -->
|
||||
<glob pattern="*.so.[0-9]" />
|
||||
<glob pattern="*.so.[0-9].*" />
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-desktop">
|
||||
<glob pattern="*.directory"/>
|
||||
</mime-type>
|
||||
|
||||
</mime-info>
|
@ -0,0 +1,77 @@
|
||||
[xterm]
|
||||
open_arg=-e
|
||||
noclose_arg=-hold -e
|
||||
desktop_id=xterm.desktop
|
||||
|
||||
[uxterm]
|
||||
open_arg=-e
|
||||
noclose_arg=-hold -e
|
||||
|
||||
[lxterminal]
|
||||
open_arg=-e
|
||||
desktop_id=lxterminal.desktop
|
||||
|
||||
[konsole]
|
||||
open_arg=-e
|
||||
noclose_arg=--noclose -e
|
||||
desktop_id=konsole.desktop
|
||||
|
||||
[xfce4-terminal]
|
||||
open_arg=-x
|
||||
noclose_arg=--hold -x
|
||||
desktop_id=xfce4-terminal.desktop
|
||||
|
||||
[terminator]
|
||||
open_arg=-x
|
||||
desktop_id=terminator.desktop
|
||||
|
||||
[rxvt]
|
||||
open_arg=-e
|
||||
|
||||
[urxvt]
|
||||
open_arg=-e
|
||||
noclose_arg=-hold -e
|
||||
desktop_id=rxvt-unicode.desktop
|
||||
|
||||
[eterm]
|
||||
open_arg=-e
|
||||
noclose_arg=--pause -e
|
||||
desktop_id=eterm.desktop
|
||||
|
||||
[gnome-terminal]
|
||||
open_arg=-x
|
||||
desktop_id=gnome-terminal.desktop
|
||||
|
||||
[wterm]
|
||||
open_arg=-e
|
||||
|
||||
[roxterm]
|
||||
open_arg=-e
|
||||
desktop_id=roxterm.desktop
|
||||
|
||||
[sakura]
|
||||
open_arg=-e
|
||||
desktop_id=sakura.desktop
|
||||
|
||||
[qterminal]
|
||||
open_arg=-e
|
||||
desktop_id=qterminal.desktop
|
||||
|
||||
[lilyterm]
|
||||
open_arg=-e
|
||||
noclose_arg=--hold -e
|
||||
desktop_id=lilyterm.desktop
|
||||
|
||||
[urxvtc]
|
||||
open_arg=-e
|
||||
noclose_arg=-hold -e
|
||||
|
||||
[terminology]
|
||||
open_arg=-e
|
||||
noclose_arg=--hold -e
|
||||
desktop_id=terminology.desktop
|
||||
|
||||
[termite]
|
||||
open_arg=-e
|
||||
noclose_arg=--hold -e
|
||||
desktop_id=termite.desktop
|
@ -1 +1,4 @@
|
||||
usr/lib/*/*.so.*
|
||||
usr/share/libfm-qt/terminals.list
|
||||
usr/share/libfm-qt/archivers.list
|
||||
usr/share/mime/packages/libfm-qt-mimetypes.xml
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LIBFM_QT_FM_BOOKMARKS_H__
|
||||
#define __LIBFM_QT_FM_BOOKMARKS_H__
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include "libfmqtglobals.h"
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
|
||||
class LIBFM_QT_API Bookmarks {
|
||||
public:
|
||||
|
||||
|
||||
// default constructor
|
||||
Bookmarks() {
|
||||
dataPtr_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
Bookmarks(FmBookmarks* dataPtr){
|
||||
dataPtr_ = dataPtr != nullptr ? reinterpret_cast<GObject*>(g_object_ref(dataPtr)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// copy constructor
|
||||
Bookmarks(const Bookmarks& other) {
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<GObject*>(g_object_ref(other.dataPtr_)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// move constructor
|
||||
Bookmarks(Bookmarks&& other) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(other.takeDataPtr());
|
||||
}
|
||||
|
||||
|
||||
// destructor
|
||||
virtual ~Bookmarks() {
|
||||
if(dataPtr_ != nullptr) {
|
||||
g_object_unref(dataPtr_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create a wrapper for the data pointer without increasing the reference count
|
||||
static Bookmarks wrapPtr(FmBookmarks* dataPtr) {
|
||||
Bookmarks obj;
|
||||
obj.dataPtr_ = reinterpret_cast<GObject*>(dataPtr);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// disown the managed data pointer
|
||||
FmBookmarks* takeDataPtr() {
|
||||
FmBookmarks* data = reinterpret_cast<FmBookmarks*>(dataPtr_);
|
||||
dataPtr_ = nullptr;
|
||||
return data;
|
||||
}
|
||||
|
||||
// get the raw pointer wrapped
|
||||
FmBookmarks* dataPtr() {
|
||||
return reinterpret_cast<FmBookmarks*>(dataPtr_);
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator FmBookmarks*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator void*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
|
||||
// copy assignment
|
||||
Bookmarks& operator=(const Bookmarks& other) {
|
||||
if(dataPtr_ != nullptr) {
|
||||
g_object_unref(dataPtr_);
|
||||
}
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<GObject*>(g_object_ref(other.dataPtr_)) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// move assignment
|
||||
Bookmarks& operator=(Bookmarks&& other) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(other.takeDataPtr());
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isNull() {
|
||||
return (dataPtr_ == nullptr);
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
GList* getAll(void) {
|
||||
return fm_bookmarks_get_all(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
void rename(FmBookmarkItem* item, const char* new_name) {
|
||||
fm_bookmarks_rename(dataPtr(), item, new_name);
|
||||
}
|
||||
|
||||
|
||||
void reorder(FmBookmarkItem* item, int pos) {
|
||||
fm_bookmarks_reorder(dataPtr(), item, pos);
|
||||
}
|
||||
|
||||
|
||||
void remove(FmBookmarkItem* item) {
|
||||
fm_bookmarks_remove(dataPtr(), item);
|
||||
}
|
||||
|
||||
|
||||
FmBookmarkItem* insert(FmPath* path, const char* name, int pos) {
|
||||
return fm_bookmarks_insert(dataPtr(), path, name, pos);
|
||||
}
|
||||
|
||||
|
||||
static Bookmarks dup(void ) {
|
||||
return Bookmarks::wrapPtr(fm_bookmarks_dup());
|
||||
}
|
||||
|
||||
|
||||
// automatic type casting for GObject
|
||||
operator GObject*() {
|
||||
return reinterpret_cast<GObject*>(dataPtr_);
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
GObject* dataPtr_; // data pointer for the underlying C struct
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // __LIBFM_QT_FM_BOOKMARKS_H__
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LIBFM_QT_FM_CONFIG_H__
|
||||
#define __LIBFM_QT_FM_CONFIG_H__
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include "libfmqtglobals.h"
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
|
||||
class LIBFM_QT_API Config {
|
||||
public:
|
||||
|
||||
|
||||
Config(void ) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(fm_config_new());
|
||||
}
|
||||
|
||||
|
||||
Config(FmConfig* dataPtr){
|
||||
dataPtr_ = dataPtr != nullptr ? reinterpret_cast<GObject*>(g_object_ref(dataPtr)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// copy constructor
|
||||
Config(const Config& other) {
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<GObject*>(g_object_ref(other.dataPtr_)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// move constructor
|
||||
Config(Config&& other) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(other.takeDataPtr());
|
||||
}
|
||||
|
||||
|
||||
// destructor
|
||||
virtual ~Config() {
|
||||
if(dataPtr_ != nullptr) {
|
||||
g_object_unref(dataPtr_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create a wrapper for the data pointer without increasing the reference count
|
||||
static Config wrapPtr(FmConfig* dataPtr) {
|
||||
Config obj;
|
||||
obj.dataPtr_ = reinterpret_cast<GObject*>(dataPtr);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// disown the managed data pointer
|
||||
FmConfig* takeDataPtr() {
|
||||
FmConfig* data = reinterpret_cast<FmConfig*>(dataPtr_);
|
||||
dataPtr_ = nullptr;
|
||||
return data;
|
||||
}
|
||||
|
||||
// get the raw pointer wrapped
|
||||
FmConfig* dataPtr() {
|
||||
return reinterpret_cast<FmConfig*>(dataPtr_);
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator FmConfig*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator void*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
|
||||
// copy assignment
|
||||
Config& operator=(const Config& other) {
|
||||
if(dataPtr_ != nullptr) {
|
||||
g_object_unref(dataPtr_);
|
||||
}
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<GObject*>(g_object_ref(other.dataPtr_)) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// move assignment
|
||||
Config& operator=(Config&& other) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(other.takeDataPtr());
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isNull() {
|
||||
return (dataPtr_ == nullptr);
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
void emitChanged(const char* changed_key) {
|
||||
fm_config_emit_changed(dataPtr(), changed_key);
|
||||
}
|
||||
|
||||
|
||||
void save(const char* name) {
|
||||
fm_config_save(dataPtr(), name);
|
||||
}
|
||||
|
||||
|
||||
void loadFromKeyFile(GKeyFile* kf) {
|
||||
fm_config_load_from_key_file(dataPtr(), kf);
|
||||
}
|
||||
|
||||
|
||||
void loadFromFile(const char* name) {
|
||||
fm_config_load_from_file(dataPtr(), name);
|
||||
}
|
||||
|
||||
|
||||
// automatic type casting for GObject
|
||||
operator GObject*() {
|
||||
return reinterpret_cast<GObject*>(dataPtr_);
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
GObject* dataPtr_; // data pointer for the underlying C struct
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // __LIBFM_QT_FM_CONFIG_H__
|
@ -0,0 +1,162 @@
|
||||
#include "bookmarks.h"
|
||||
#include "cstrptr.h"
|
||||
#include <algorithm>
|
||||
#include <QTimer>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::weak_ptr<Bookmarks> Bookmarks::globalInstance_;
|
||||
|
||||
static inline CStrPtr get_legacy_bookmarks_file(void) {
|
||||
return CStrPtr{g_build_filename(g_get_home_dir(), ".gtk-bookmarks", nullptr)};
|
||||
}
|
||||
|
||||
static inline CStrPtr get_new_bookmarks_file(void) {
|
||||
return CStrPtr{g_build_filename(g_get_user_config_dir(), "gtk-3.0", "bookmarks", nullptr)};
|
||||
}
|
||||
|
||||
Bookmarks::Bookmarks(QObject* parent):
|
||||
QObject(parent),
|
||||
idle_handler{false} {
|
||||
|
||||
/* trying the gtk-3.0 first and use it if it exists */
|
||||
auto fpath = get_new_bookmarks_file();
|
||||
file = FilePath::fromLocalPath(fpath.get());
|
||||
load();
|
||||
if(items_.empty()) { /* not found, use legacy file */
|
||||
fpath = get_legacy_bookmarks_file();
|
||||
file = FilePath::fromLocalPath(fpath.get());
|
||||
load();
|
||||
}
|
||||
mon = GObjectPtr<GFileMonitor>{g_file_monitor_file(file.gfile().get(), G_FILE_MONITOR_NONE, nullptr, nullptr), false};
|
||||
if(mon) {
|
||||
g_signal_connect(mon.get(), "changed", G_CALLBACK(_onFileChanged), this);
|
||||
}
|
||||
}
|
||||
|
||||
Bookmarks::~Bookmarks() {
|
||||
if(mon) {
|
||||
g_signal_handlers_disconnect_by_data(mon.get(), this);
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<const BookmarkItem>& Bookmarks::insert(const FilePath& path, const QString& name, int pos) {
|
||||
const auto insert_pos = (pos < 0 || static_cast<size_t>(pos) > items_.size()) ? items_.cend() : items_.cbegin() + pos;
|
||||
auto it = items_.insert(insert_pos, std::make_shared<const BookmarkItem>(path, name));
|
||||
queueSave();
|
||||
return *it;
|
||||
}
|
||||
|
||||
void Bookmarks::remove(const std::shared_ptr<const BookmarkItem>& item) {
|
||||
items_.erase(std::remove(items_.begin(), items_.end(), item), items_.end());
|
||||
queueSave();
|
||||
}
|
||||
|
||||
void Bookmarks::reorder(const std::shared_ptr<const BookmarkItem>& item, int pos) {
|
||||
auto old_it = std::find(items_.cbegin(), items_.cend(), item);
|
||||
if(old_it == items_.cend())
|
||||
return;
|
||||
std::shared_ptr<const BookmarkItem> newItem = item;
|
||||
auto old_pos = old_it - items_.cbegin();
|
||||
items_.erase(old_it);
|
||||
if(old_pos < pos)
|
||||
--pos;
|
||||
auto new_it = items_.cbegin() + pos;
|
||||
if(new_it > items_.cend())
|
||||
new_it = items_.cend();
|
||||
items_.insert(new_it, std::move(newItem));
|
||||
queueSave();
|
||||
}
|
||||
|
||||
void Bookmarks::rename(const std::shared_ptr<const BookmarkItem>& item, QString new_name) {
|
||||
auto it = std::find_if(items_.cbegin(), items_.cend(), [item](const std::shared_ptr<const BookmarkItem>& elem) {
|
||||
return elem->path() == item->path();
|
||||
});
|
||||
if(it != items_.cend()) {
|
||||
// create a new item to replace the old one
|
||||
// we do not modify the old item directly since this data structure is shared with others
|
||||
it = items_.insert(it, std::make_shared<const BookmarkItem>(item->path(), new_name));
|
||||
items_.erase(it + 1); // remove the old item
|
||||
queueSave();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Bookmarks> Bookmarks::globalInstance() {
|
||||
auto bookmarks = globalInstance_.lock();
|
||||
if(!bookmarks) {
|
||||
bookmarks = std::make_shared<Bookmarks>();
|
||||
globalInstance_ = bookmarks;
|
||||
}
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
void Bookmarks::save() {
|
||||
std::string buf;
|
||||
// G_LOCK(bookmarks);
|
||||
for(auto& item: items_) {
|
||||
auto uri = item->path().uri();
|
||||
buf += uri.get();
|
||||
buf += ' ';
|
||||
buf += item->name().toUtf8().constData();
|
||||
buf += '\n';
|
||||
}
|
||||
idle_handler = false;
|
||||
// G_UNLOCK(bookmarks);
|
||||
GError* err = nullptr;
|
||||
if(!g_file_replace_contents(file.gfile().get(), buf.c_str(), buf.length(), nullptr,
|
||||
FALSE, G_FILE_CREATE_NONE, nullptr, nullptr, &err)) {
|
||||
g_critical("%s", err->message);
|
||||
g_error_free(err);
|
||||
}
|
||||
/* we changed bookmarks list, let inform who interested in that */
|
||||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
void Bookmarks::load() {
|
||||
auto fpath = file.localPath();
|
||||
FILE* f;
|
||||
char buf[1024];
|
||||
/* load the file */
|
||||
f = fopen(fpath.get(), "r");
|
||||
if(f) {
|
||||
while(fgets(buf, 1024, f)) {
|
||||
// format of each line in the bookmark file:
|
||||
// <URI> <name>\n
|
||||
char* sep;
|
||||
sep = strchr(buf, '\n');
|
||||
if(sep) {
|
||||
*sep = '\0';
|
||||
}
|
||||
|
||||
QString name;
|
||||
sep = strchr(buf, ' '); // find the separator between URI and name
|
||||
if(sep) {
|
||||
*sep = '\0';
|
||||
name = sep + 1;
|
||||
}
|
||||
auto uri = buf;
|
||||
if(uri[0] != '\0') {
|
||||
items_.push_back(std::make_shared<BookmarkItem>(FilePath::fromUri(uri), name));
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
void Bookmarks::onFileChanged(GFileMonitor* /*mon*/, GFile* /*gf*/, GFile* /*other*/, GFileMonitorEvent /*evt*/) {
|
||||
// reload the bookmarks
|
||||
items_.clear();
|
||||
load();
|
||||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
|
||||
void Bookmarks::queueSave() {
|
||||
if(!idle_handler) {
|
||||
QTimer::singleShot(0, this, &Bookmarks::save);
|
||||
idle_handler = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,96 @@
|
||||
#ifndef FM2_BOOKMARKS_H
|
||||
#define FM2_BOOKMARKS_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <QObject>
|
||||
#include "gobjectptr.h"
|
||||
#include "fileinfo.h"
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API BookmarkItem {
|
||||
public:
|
||||
friend class Bookmarks;
|
||||
|
||||
explicit BookmarkItem(const FilePath& path, const QString name): path_{path}, name_{name} {
|
||||
if(name_.isEmpty()) { // if the name is not specified, use basename of the path
|
||||
name_ = path_.baseName().get();
|
||||
}
|
||||
}
|
||||
|
||||
const QString& name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
const FilePath& path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const FmFileInfo>& info() const {
|
||||
return info_;
|
||||
}
|
||||
|
||||
private:
|
||||
void setInfo(const std::shared_ptr<const FmFileInfo>& info) {
|
||||
info_ = info;
|
||||
}
|
||||
|
||||
void setName(const QString& name) {
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
private:
|
||||
FilePath path_;
|
||||
QString name_;
|
||||
std::shared_ptr<const FmFileInfo> info_;
|
||||
};
|
||||
|
||||
|
||||
class LIBFM_QT_API Bookmarks : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Bookmarks(QObject* parent = 0);
|
||||
|
||||
~Bookmarks();
|
||||
|
||||
const std::shared_ptr<const BookmarkItem> &insert(const FilePath& path, const QString& name, int pos);
|
||||
|
||||
void remove(const std::shared_ptr<const BookmarkItem>& item);
|
||||
|
||||
void reorder(const std::shared_ptr<const BookmarkItem> &item, int pos);
|
||||
|
||||
void rename(const std::shared_ptr<const BookmarkItem>& item, QString new_name);
|
||||
|
||||
const std::vector<std::shared_ptr<const BookmarkItem>>& items() const {
|
||||
return items_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<Bookmarks> globalInstance();
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed();
|
||||
|
||||
private Q_SLOTS:
|
||||
void save();
|
||||
|
||||
private:
|
||||
void load();
|
||||
void queueSave();
|
||||
|
||||
static void _onFileChanged(GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt, Bookmarks* _this) {
|
||||
_this->onFileChanged(mon, gf, other, evt);
|
||||
}
|
||||
void onFileChanged(GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt);
|
||||
|
||||
private:
|
||||
FilePath file;
|
||||
GObjectPtr<GFileMonitor> mon;
|
||||
std::vector<std::shared_ptr<const BookmarkItem>> items_;
|
||||
static std::weak_ptr<Bookmarks> globalInstance_;
|
||||
bool idle_handler;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_BOOKMARKS_H
|
@ -0,0 +1,53 @@
|
||||
#ifndef LIBFM_QT_COMPAT_P_H
|
||||
#define LIBFM_QT_COMPAT_P_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "core/filepath.h"
|
||||
#include "core/fileinfo.h"
|
||||
#include "core/gioptrs.h"
|
||||
|
||||
// deprecated
|
||||
#include <libfm/fm.h>
|
||||
#include "path.h"
|
||||
|
||||
// compatibility functions bridging the old libfm C APIs and new C++ APIs.
|
||||
|
||||
namespace Fm {
|
||||
|
||||
inline FM_QT_DEPRECATED Fm::Path _convertPath(const Fm::FilePath& path) {
|
||||
return Fm::Path::newForGfile(path.gfile().get());
|
||||
}
|
||||
|
||||
inline FM_QT_DEPRECATED Fm::PathList _convertPathList(const Fm::FilePathList& srcFiles) {
|
||||
Fm::PathList ret;
|
||||
for(auto& file: srcFiles) {
|
||||
ret.pushTail(_convertPath(file));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline FM_QT_DEPRECATED FmFileInfo* _convertFileInfo(const std::shared_ptr<const Fm::FileInfo>& info) {
|
||||
// conver to GFileInfo first
|
||||
GFileInfoPtr ginfo{g_file_info_new(), false};
|
||||
g_file_info_set_name(ginfo.get(), info->name().c_str());
|
||||
g_file_info_set_display_name(ginfo.get(), info->displayName().toUtf8().constData());
|
||||
g_file_info_set_content_type(ginfo.get(), info->mimeType()->name());
|
||||
|
||||
auto mode = info->mode();
|
||||
g_file_info_set_attribute_uint32(ginfo.get(), G_FILE_ATTRIBUTE_UNIX_MODE, mode);
|
||||
GFileType ftype = info->isDir() ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR; // FIXME: generate more accurate type
|
||||
g_file_info_set_file_type(ginfo.get(), ftype);
|
||||
g_file_info_set_size(ginfo.get(), info->size());
|
||||
g_file_info_set_icon(ginfo.get(), info->icon()->gicon().get());
|
||||
|
||||
g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED, info->mtime());
|
||||
g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_ACCESS, info->atime());
|
||||
g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_CHANGED, info->ctime());
|
||||
|
||||
auto gf = info->path().gfile();
|
||||
return fm_file_info_new_from_g_file_data(gf.get(), ginfo.get(), nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // LIBFM_QT_COMPAT_P_H
|
@ -0,0 +1,453 @@
|
||||
#include "copyjob.h"
|
||||
#include "totalsizejob.h"
|
||||
#include "fileinfo_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
CopyJob::CopyJob(const FilePathList& paths, const FilePath& destDirPath, Mode mode):
|
||||
FileOperationJob{},
|
||||
srcPaths_{paths},
|
||||
destDirPath_{destDirPath},
|
||||
mode_{mode},
|
||||
skip_dir_content{false} {
|
||||
}
|
||||
|
||||
CopyJob::CopyJob(const FilePathList &&paths, const FilePath &&destDirPath, Mode mode):
|
||||
FileOperationJob{},
|
||||
srcPaths_{paths},
|
||||
destDirPath_{destDirPath},
|
||||
mode_{mode},
|
||||
skip_dir_content{false} {
|
||||
}
|
||||
|
||||
void CopyJob::gfileProgressCallback(goffset current_num_bytes, goffset total_num_bytes, CopyJob* _this) {
|
||||
_this->setCurrentFileProgress(total_num_bytes, current_num_bytes);
|
||||
}
|
||||
|
||||
bool CopyJob::copyRegularFile(const FilePath& srcPath, GFileInfoPtr /*srcFile*/, const FilePath& destPath) {
|
||||
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
||||
GErrorPtr err;
|
||||
_retry_copy:
|
||||
if(!g_file_copy(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
|
||||
GFileProgressCallback(gfileProgressCallback), this, &err)) {
|
||||
flags &= ~G_FILE_COPY_OVERWRITE;
|
||||
/* handle existing files or file name conflict */
|
||||
if(err.domain() == G_IO_ERROR && (err.code() == G_IO_ERROR_EXISTS ||
|
||||
err.code() == G_IO_ERROR_INVALID_FILENAME ||
|
||||
err.code() == G_IO_ERROR_FILENAME_TOO_LONG)) {
|
||||
#if 0
|
||||
GFile* dest_cp = new_dest;
|
||||
bool dest_exists = (err->code == G_IO_ERROR_EXISTS);
|
||||
FmFileOpOption opt = 0;
|
||||
g_error_free(err);
|
||||
err = nullptr;
|
||||
|
||||
new_dest = nullptr;
|
||||
opt = _fm_file_ops_job_ask_new_name(job, src, dest, &new_dest, dest_exists);
|
||||
if(!new_dest) { /* restoring status quo */
|
||||
new_dest = dest_cp;
|
||||
}
|
||||
else if(dest_cp) { /* we got new new_dest, forget old one */
|
||||
g_object_unref(dest_cp);
|
||||
}
|
||||
switch(opt) {
|
||||
case FM_FILE_OP_RENAME:
|
||||
dest = new_dest;
|
||||
goto _retry_copy;
|
||||
break;
|
||||
case FM_FILE_OP_OVERWRITE:
|
||||
flags |= G_FILE_COPY_OVERWRITE;
|
||||
goto _retry_copy;
|
||||
break;
|
||||
case FM_FILE_OP_CANCEL:
|
||||
fm_job_cancel(fmjob);
|
||||
break;
|
||||
case FM_FILE_OP_SKIP:
|
||||
ret = true;
|
||||
delete_src = false; /* don't delete source file. */
|
||||
break;
|
||||
case FM_FILE_OP_SKIP_ERROR: ; /* FIXME */
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
ErrorAction act = emitError( err, ErrorSeverity::MODERATE);
|
||||
err.reset();
|
||||
if(act == ErrorAction::RETRY) {
|
||||
// FIXME: job->current_file_finished = 0;
|
||||
goto _retry_copy;
|
||||
}
|
||||
# if 0
|
||||
const bool is_no_space = (err.domain() == G_IO_ERROR &&
|
||||
err.code() == G_IO_ERROR_NO_SPACE);
|
||||
/* FIXME: ask to leave partial content? */
|
||||
if(is_no_space) {
|
||||
g_file_delete(dest, fm_job_get_cancellable(fmjob), nullptr);
|
||||
}
|
||||
ret = false;
|
||||
delete_src = false;
|
||||
#endif
|
||||
}
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CopyJob::copySpecialFile(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& destPath) {
|
||||
bool ret = false;
|
||||
GError* err = nullptr;
|
||||
/* only handle FIFO for local files */
|
||||
if(srcPath.isNative() && destPath.isNative()) {
|
||||
auto src_path = srcPath.localPath();
|
||||
struct stat src_st;
|
||||
int r;
|
||||
r = lstat(src_path.get(), &src_st);
|
||||
if(r == 0) {
|
||||
/* Handle FIFO on native file systems. */
|
||||
if(S_ISFIFO(src_st.st_mode)) {
|
||||
auto dest_path = destPath.localPath();
|
||||
if(mkfifo(dest_path.get(), src_st.st_mode) == 0) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
/* FIXME: how about block device, char device, and socket? */
|
||||
}
|
||||
}
|
||||
if(!ret) {
|
||||
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
("Cannot copy file '%s': not supported"),
|
||||
g_file_info_get_display_name(srcFile.get()));
|
||||
// emitError( err, ErrorSeverity::MODERATE);
|
||||
g_clear_error(&err);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CopyJob::copyDir(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& destPath) {
|
||||
bool ret = false;
|
||||
if(makeDir(srcPath, srcFile, destPath)) {
|
||||
GError* err = nullptr;
|
||||
auto enu = GFileEnumeratorPtr{
|
||||
g_file_enumerate_children(srcPath.gfile().get(),
|
||||
gfile_info_query_attribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false};
|
||||
if(enu) {
|
||||
int n_children = 0;
|
||||
int n_copied = 0;
|
||||
ret = true;
|
||||
while(!isCancelled()) {
|
||||
auto inf = GFileInfoPtr{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
|
||||
if(inf) {
|
||||
++n_children;
|
||||
/* don't overwrite dir content, only calculate progress. */
|
||||
if(Q_UNLIKELY(skip_dir_content)) {
|
||||
/* FIXME: this is incorrect as we don't do the calculation recursively. */
|
||||
addFinishedAmount(g_file_info_get_size(inf.get()), 1);
|
||||
}
|
||||
else {
|
||||
const char* name = g_file_info_get_name(inf.get());
|
||||
FilePath childPath = srcPath.child(name);
|
||||
bool child_ret = copyPath(childPath, inf, destPath, name);
|
||||
if(child_ret) {
|
||||
++n_copied;
|
||||
}
|
||||
else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
// FIXME: emitError( err, ErrorSeverity::MODERATE);
|
||||
g_error_free(err);
|
||||
err = nullptr;
|
||||
/* ErrorAction::RETRY is not supported here */
|
||||
ret = false;
|
||||
}
|
||||
else { /* EOF is reached */
|
||||
/* all files are successfully copied. */
|
||||
if(isCancelled()) {
|
||||
ret = false;
|
||||
}
|
||||
else {
|
||||
/* some files are not copied */
|
||||
if(n_children != n_copied) {
|
||||
/* if the copy actions are skipped deliberately, it's ok */
|
||||
if(!skip_dir_content) {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
/* else job->skip_dir_content is true */
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_file_enumerator_close(enu.get(), nullptr, &err);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CopyJob::makeDir(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& dirPath) {
|
||||
GError* err = nullptr;
|
||||
if(isCancelled())
|
||||
return false;
|
||||
|
||||
FilePath destPath = dirPath;
|
||||
bool mkdir_done = false;
|
||||
do {
|
||||
mkdir_done = g_file_make_directory(destPath.gfile().get(), cancellable().get(), &err);
|
||||
if(err->domain == G_IO_ERROR && (err->code == G_IO_ERROR_EXISTS ||
|
||||
err->code == G_IO_ERROR_INVALID_FILENAME ||
|
||||
err->code == G_IO_ERROR_FILENAME_TOO_LONG)) {
|
||||
GFileInfoPtr destFile;
|
||||
// FIXME: query its info
|
||||
FilePath newDestPath;
|
||||
FileExistsAction opt = askRename(FileInfo{srcFile, srcPath.parent()}, FileInfo{destFile, dirPath.parent()}, newDestPath);
|
||||
g_error_free(err);
|
||||
err = nullptr;
|
||||
|
||||
switch(opt) {
|
||||
case FileOperationJob::RENAME:
|
||||
destPath = newDestPath;
|
||||
break;
|
||||
case FileOperationJob::SKIP:
|
||||
/* when a dir is skipped, we need to know its total size to calculate correct progress */
|
||||
// job->finished += size;
|
||||
// fm_file_ops_job_emit_percent(job);
|
||||
// job->skip_dir_content = skip_dir_content = true;
|
||||
mkdir_done = true; /* pretend that dir creation succeeded */
|
||||
break;
|
||||
case FileOperationJob::OVERWRITE:
|
||||
mkdir_done = true; /* pretend that dir creation succeeded */
|
||||
break;
|
||||
case FileOperationJob::CANCEL:
|
||||
cancel();
|
||||
break;
|
||||
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
|
||||
}
|
||||
}
|
||||
else {
|
||||
#if 0
|
||||
ErrorAction act = emitError( err, ErrorSeverity::MODERATE);
|
||||
g_error_free(err);
|
||||
err = nullptr;
|
||||
if(act == ErrorAction::RETRY) {
|
||||
goto _retry_mkdir;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
// job->finished += size;
|
||||
} while(!mkdir_done && !isCancelled());
|
||||
|
||||
if(mkdir_done && !isCancelled()) {
|
||||
bool chmod_done = false;
|
||||
mode_t mode = g_file_info_get_attribute_uint32(srcFile.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
|
||||
if(mode) {
|
||||
mode |= (S_IRUSR | S_IWUSR); /* ensure we have rw permission to this file. */
|
||||
do {
|
||||
/* chmod the newly created dir properly */
|
||||
// if(!fm_job_is_cancelled(fmjob) && !job->skip_dir_content)
|
||||
chmod_done = g_file_set_attribute_uint32(destPath.gfile().get(),
|
||||
G_FILE_ATTRIBUTE_UNIX_MODE,
|
||||
mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err);
|
||||
if(!chmod_done) {
|
||||
/*
|
||||
ErrorAction act = emitError( err, ErrorSeverity::MODERATE);
|
||||
g_error_free(err);
|
||||
err = nullptr;
|
||||
if(act == ErrorAction::RETRY) {
|
||||
goto _retry_chmod_for_dir;
|
||||
}
|
||||
*/
|
||||
/* FIXME: some filesystems may not support this. */
|
||||
}
|
||||
} while(!chmod_done && !isCancelled());
|
||||
// finished += size;
|
||||
// fm_file_ops_job_emit_percent(job);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CopyJob::copyPath(const FilePath& srcPath, const FilePath& destDirPath, const char* destFileName) {
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr srcInfo = GFileInfoPtr {
|
||||
g_file_query_info(srcPath.gfile().get(),
|
||||
gfile_info_query_attribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(!srcInfo || isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
return copyPath(srcPath, srcInfo, destDirPath, destFileName);
|
||||
}
|
||||
|
||||
bool CopyJob::copyPath(const FilePath& srcPath, const GFileInfoPtr& srcInfo, const FilePath& destDirPath, const char* destFileName) {
|
||||
setCurrentFile(srcPath);
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr destDirInfo = GFileInfoPtr {
|
||||
g_file_query_info(destDirPath.gfile().get(),
|
||||
"id::filesystem",
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
|
||||
if(!destDirInfo || isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto size = g_file_info_get_size(srcInfo.get());
|
||||
setCurrentFileProgress(size, 0);
|
||||
|
||||
auto destPath = destDirPath.child(destFileName);
|
||||
bool success = false;
|
||||
switch(g_file_info_get_file_type(srcInfo.get())) {
|
||||
case G_FILE_TYPE_DIRECTORY:
|
||||
success = copyDir(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
case G_FILE_TYPE_SPECIAL:
|
||||
success = copySpecialFile(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
default:
|
||||
success = copyRegularFile(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
}
|
||||
|
||||
if(success) {
|
||||
addFinishedAmount(size, 1);
|
||||
#if 0
|
||||
|
||||
if(ret && dest_folder) {
|
||||
fm_dest = fm_path_new_for_gfile(dest);
|
||||
if(!_fm_folder_event_file_added(dest_folder, fm_dest)) {
|
||||
fm_path_unref(fm_dest);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
bool _fm_file_ops_job_copy_run(FmFileOpsJob* job) {
|
||||
bool ret = true;
|
||||
GFile* dest_dir;
|
||||
GList* l;
|
||||
FmJob* fmjob = FM_JOB(job);
|
||||
/* prepare the job, count total work needed with FmDeepCountJob */
|
||||
FmDeepCountJob* dc = fm_deep_count_job_new(job->srcs, FM_DC_JOB_DEFAULT);
|
||||
FmFolder* df;
|
||||
|
||||
/* let the deep count job share the same cancellable object. */
|
||||
fm_job_set_cancellable(FM_JOB(dc), fm_job_get_cancellable(fmjob));
|
||||
fm_job_run_sync(FM_JOB(dc));
|
||||
job->total = dc->total_size;
|
||||
if(fm_job_is_cancelled(fmjob)) {
|
||||
g_object_unref(dc);
|
||||
return false;
|
||||
}
|
||||
g_object_unref(dc);
|
||||
g_debug("total size to copy: %llu", (long long unsigned int)job->total);
|
||||
|
||||
dest_dir = fm_path_to_gfile(job->dest);
|
||||
/* suspend updates for destination */
|
||||
df = fm_folder_find_by_path(job->dest);
|
||||
if(df) {
|
||||
fm_folder_block_updates(df);
|
||||
}
|
||||
|
||||
fm_file_ops_job_emit_prepared(job);
|
||||
|
||||
for(l = fm_path_list_peek_head_link(job->srcs); !fm_job_is_cancelled(fmjob) && l; l = l->next) {
|
||||
FmPath* path = FM_PATH(l->data);
|
||||
GFile* src = fm_path_to_gfile(path);
|
||||
GFile* dest;
|
||||
char* tmp_basename;
|
||||
|
||||
if(g_file_is_native(src) && g_file_is_native(dest_dir))
|
||||
/* both are native */
|
||||
{
|
||||
tmp_basename = nullptr;
|
||||
}
|
||||
else if(g_file_is_native(src)) /* copy from native to virtual */
|
||||
tmp_basename = g_filename_to_utf8(fm_path_get_basename(path),
|
||||
-1, nullptr, nullptr, nullptr);
|
||||
/* gvfs escapes it itself */
|
||||
else { /* copy from virtual to native/virtual */
|
||||
/* if we drop URI query onto native filesystem, omit query part */
|
||||
const char* basename = fm_path_get_basename(path);
|
||||
char* sub_name;
|
||||
|
||||
sub_name = strchr(basename, '?');
|
||||
if(sub_name) {
|
||||
sub_name = g_strndup(basename, sub_name - basename);
|
||||
basename = strrchr(sub_name, G_DIR_SEPARATOR);
|
||||
if(basename) {
|
||||
basename++;
|
||||
}
|
||||
else {
|
||||
basename = sub_name;
|
||||
}
|
||||
}
|
||||
tmp_basename = fm_uri_subpath_to_native_subpath(basename, nullptr);
|
||||
g_free(sub_name);
|
||||
}
|
||||
dest = g_file_get_child(dest_dir,
|
||||
tmp_basename ? tmp_basename : fm_path_get_basename(path));
|
||||
g_free(tmp_basename);
|
||||
if(!_fm_file_ops_job_copy_file(job, src, nullptr, dest, nullptr, df)) {
|
||||
ret = false;
|
||||
}
|
||||
g_object_unref(src);
|
||||
g_object_unref(dest);
|
||||
}
|
||||
|
||||
/* g_debug("finished: %llu, total: %llu", job->finished, job->total); */
|
||||
fm_file_ops_job_emit_percent(job);
|
||||
|
||||
/* restore updates for destination */
|
||||
if(df) {
|
||||
fm_folder_unblock_updates(df);
|
||||
g_object_unref(df);
|
||||
}
|
||||
g_object_unref(dest_dir);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
void CopyJob::exec() {
|
||||
TotalSizeJob totalSizeJob{srcPaths_};
|
||||
connect(&totalSizeJob, &TotalSizeJob::error, this, &CopyJob::error);
|
||||
connect(this, &CopyJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
|
||||
totalSizeJob.run();
|
||||
if(isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount());
|
||||
Q_EMIT preparedToRun();
|
||||
|
||||
for(auto& srcPath : srcPaths_) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
copyPath(srcPath, destDirPath_, srcPath.baseName().get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,46 @@
|
||||
#ifndef FM2_COPYJOB_H
|
||||
#define FM2_COPYJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API CopyJob : public Fm::FileOperationJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
enum class Mode {
|
||||
COPY,
|
||||
MOVE
|
||||
};
|
||||
|
||||
explicit CopyJob(const FilePathList& paths, const FilePath& destDirPath, Mode mode = Mode::COPY);
|
||||
|
||||
explicit CopyJob(const FilePathList&& paths, const FilePath&& destDirPath, Mode mode = Mode::COPY);
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
bool copyPath(const FilePath& srcPath, const FilePath& destPath, const char *destFileName);
|
||||
bool copyPath(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName);
|
||||
bool copyRegularFile(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath);
|
||||
bool copySpecialFile(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath);
|
||||
bool copyDir(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath);
|
||||
bool makeDir(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& dirPath);
|
||||
|
||||
static void gfileProgressCallback(goffset current_num_bytes, goffset total_num_bytes, CopyJob* _this);
|
||||
|
||||
private:
|
||||
FilePathList srcPaths_;
|
||||
FilePath destDirPath_;
|
||||
Mode mode_;
|
||||
bool skip_dir_content;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_COPYJOB_H
|
@ -0,0 +1,42 @@
|
||||
#ifndef FM2_CSTRPTR_H
|
||||
#define FM2_CSTRPTR_H
|
||||
|
||||
#include <memory>
|
||||
#include <glib.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
struct CStrDeleter {
|
||||
void operator()(char* ptr) {
|
||||
g_free(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
// smart pointer for C string (char*) which should be freed by free()
|
||||
typedef std::unique_ptr<char[], CStrDeleter> CStrPtr;
|
||||
|
||||
struct CStrHash {
|
||||
std::size_t operator()(const char* str) const {
|
||||
return g_str_hash(str);
|
||||
}
|
||||
};
|
||||
|
||||
struct CStrEqual {
|
||||
bool operator()(const char* str1, const char* str2) const {
|
||||
return g_str_equal(str1, str2);
|
||||
}
|
||||
};
|
||||
|
||||
struct CStrVDeleter {
|
||||
void operator()(char** ptr) {
|
||||
g_strfreev(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
// smart pointer for C string array (char**) which should be freed by g_strfreev() of glib
|
||||
typedef std::unique_ptr<char*[], CStrVDeleter> CStrArrayPtr;
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_CSTRPTR_H
|
@ -0,0 +1,151 @@
|
||||
#include "deletejob.h"
|
||||
#include "totalsizejob.h"
|
||||
#include "fileinfo_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
bool DeleteJob::deleteFile(const FilePath& path, GFileInfoPtr inf) {
|
||||
ErrorAction act = ErrorAction::CONTINUE;
|
||||
while(!inf) {
|
||||
GErrorPtr err;
|
||||
inf = GFileInfoPtr{
|
||||
g_file_query_info(path.gfile().get(), "standard::*",
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(err) {
|
||||
act = emitError(err, ErrorSeverity::SEVERE);
|
||||
if(act == ErrorAction::ABORT) {
|
||||
return false;
|
||||
}
|
||||
if(act != ErrorAction::RETRY) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* currently processed file. */
|
||||
setCurrentFile(path);
|
||||
|
||||
if(g_file_info_get_file_type(inf.get()) == G_FILE_TYPE_DIRECTORY) {
|
||||
// delete the content of the dir prior to deleting itself
|
||||
deleteDirContent(path, inf);
|
||||
}
|
||||
|
||||
bool hasError = false;
|
||||
while(!isCancelled()) {
|
||||
GErrorPtr err;
|
||||
// try to delete the path directly
|
||||
if(g_file_delete(path.gfile().get(), cancellable().get(), &err)) {
|
||||
break;
|
||||
}
|
||||
if(err) {
|
||||
// FIXME: error handling
|
||||
/* if it's non-empty dir then descent into it then try again */
|
||||
/* trash root gives G_IO_ERROR_PERMISSION_DENIED */
|
||||
if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_NOT_EMPTY) {
|
||||
deleteDirContent(path, inf);
|
||||
}
|
||||
else if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_PERMISSION_DENIED) {
|
||||
/* special case for trash:/// */
|
||||
/* FIXME: is there any better way to handle this? */
|
||||
auto scheme = path.uriScheme();
|
||||
if(g_strcmp0(scheme.get(), "trash") == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
act = emitError(err, ErrorSeverity::MODERATE);
|
||||
if(act != ErrorAction::RETRY) {
|
||||
hasError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addFinishedAmount(g_file_info_get_size(inf.get()), 1);
|
||||
|
||||
return !hasError;
|
||||
}
|
||||
|
||||
bool DeleteJob::deleteDirContent(const FilePath& path, GFileInfoPtr inf) {
|
||||
#if 0
|
||||
FmFolder* sub_folder;
|
||||
/* special handling for trash:/// */
|
||||
if(!g_file_is_native(gf)) {
|
||||
char* scheme = g_file_get_uri_scheme(gf);
|
||||
if(g_strcmp0(scheme, "trash") == 0) {
|
||||
/* little trick: basename of trash root is /. */
|
||||
char* basename = g_file_get_basename(gf);
|
||||
if(basename && basename[0] == G_DIR_SEPARATOR) {
|
||||
is_trash_root = true;
|
||||
}
|
||||
g_free(basename);
|
||||
}
|
||||
g_free(scheme);
|
||||
}
|
||||
#endif
|
||||
|
||||
GErrorPtr err;
|
||||
GFileEnumeratorPtr enu {
|
||||
g_file_enumerate_children(path.gfile().get(), gfile_info_query_attribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(!enu) {
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasError = false;
|
||||
while(!isCancelled()) {
|
||||
inf = GFileInfoPtr{
|
||||
g_file_enumerator_next_file(enu.get(), cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(inf) {
|
||||
auto subPath = path.child(g_file_info_get_name(inf.get()));
|
||||
if(!deleteFile(subPath, inf)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
/* ErrorAction::RETRY is not supported here */
|
||||
hasError = true;
|
||||
}
|
||||
else { /* EOF */
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_file_enumerator_close(enu.get(), nullptr, nullptr);
|
||||
return !hasError;
|
||||
}
|
||||
|
||||
|
||||
void DeleteJob::exec() {
|
||||
/* prepare the job, count total work needed with FmDeepCountJob */
|
||||
TotalSizeJob totalSizeJob{paths_, TotalSizeJob::Flags::PREPARE_DELETE};
|
||||
connect(&totalSizeJob, &TotalSizeJob::error, this, &DeleteJob::error);
|
||||
connect(this, &DeleteJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
|
||||
totalSizeJob.run();
|
||||
|
||||
if(isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount());
|
||||
Q_EMIT preparedToRun();
|
||||
|
||||
for(auto& path : paths_) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
deleteFile(path, GFileInfoPtr{nullptr});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,36 @@
|
||||
#ifndef FM2_DELETEJOB_H
|
||||
#define FM2_DELETEJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
#include "filepath.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API DeleteJob : public Fm::FileOperationJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeleteJob(const FilePathList& paths): paths_{paths} {
|
||||
}
|
||||
|
||||
explicit DeleteJob(FilePathList&& paths): paths_{paths} {
|
||||
}
|
||||
|
||||
~DeleteJob() {
|
||||
}
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
bool deleteFile(const FilePath& path, GFileInfoPtr inf);
|
||||
bool deleteDirContent(const FilePath& path, GFileInfoPtr inf);
|
||||
|
||||
private:
|
||||
FilePathList paths_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_DELETEJOB_H
|
@ -0,0 +1,178 @@
|
||||
#include "dirlistjob.h"
|
||||
#include <gio/gio.h>
|
||||
#include "fileinfo_p.h"
|
||||
#include "gioptrs.h"
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
DirListJob::DirListJob(const FilePath& path, Flags _flags, const std::shared_ptr<const HashSet>& cutFilesHashSet):
|
||||
dir_path{path}, flags{_flags}, cutFilesHashSet_{cutFilesHashSet} {
|
||||
}
|
||||
|
||||
void DirListJob::exec() {
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr dir_inf;
|
||||
GFilePtr dir_gfile = dir_path.gfile();
|
||||
// FIXME: these are hacks for search:/// URI implemented by libfm which contains some bugs
|
||||
bool isFileSearch = dir_path.hasUriScheme("search");
|
||||
if(isFileSearch) {
|
||||
// NOTE: The GFile instance changes its URI during file enumeration (bad design).
|
||||
// So we create a copy here to avoid channging the gfile stored in dir_path.
|
||||
// FIXME: later we should refactor file search and remove this dirty hack.
|
||||
dir_gfile = GFilePtr{g_file_dup(dir_gfile.get())};
|
||||
}
|
||||
_retry:
|
||||
err.reset();
|
||||
dir_inf = GFileInfoPtr{
|
||||
g_file_query_info(dir_gfile.get(), gfile_info_query_attribs,
|
||||
G_FILE_QUERY_INFO_NONE, cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(!dir_inf) {
|
||||
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
||||
if(act == ErrorAction::RETRY) {
|
||||
err.reset();
|
||||
goto _retry;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(g_file_info_get_file_type(dir_inf.get()) != G_FILE_TYPE_DIRECTORY) {
|
||||
auto path_str = dir_path.toString();
|
||||
err = GErrorPtr{
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_DIRECTORY,
|
||||
tr("The specified directory '%1' is not valid").arg(path_str.get())
|
||||
};
|
||||
emitError(err, ErrorSeverity::CRITICAL);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
dir_fi = std::make_shared<FileInfo>(dir_inf, dir_path.parent());
|
||||
}
|
||||
|
||||
FileInfoList foundFiles;
|
||||
/* check if FS is R/O and set attr. into inf */
|
||||
// FIXME: _fm_file_info_job_update_fs_readonly(gf, inf, nullptr, nullptr);
|
||||
err.reset();
|
||||
GFileEnumeratorPtr enu = GFileEnumeratorPtr{
|
||||
g_file_enumerate_children(dir_gfile.get(), gfile_info_query_attribs,
|
||||
G_FILE_QUERY_INFO_NONE, cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(enu) {
|
||||
// qDebug() << "START LISTING:" << dir_path.toString().get();
|
||||
while(!isCancelled()) {
|
||||
err.reset();
|
||||
GFileInfoPtr inf{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
|
||||
if(inf) {
|
||||
#if 0
|
||||
FmPath* dir, *sub;
|
||||
GFile* child;
|
||||
if(G_UNLIKELY(job->flags & FM_DIR_LIST_JOB_DIR_ONLY)) {
|
||||
/* FIXME: handle symlinks */
|
||||
if(g_file_info_get_file_type(inf) != G_FILE_TYPE_DIRECTORY) {
|
||||
g_object_unref(inf);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// virtual folders may return children not within them
|
||||
// For example: the search:/// URI implemented by libfm might return files from different folders during enumeration.
|
||||
// So here we call g_file_enumerator_get_container() to get the real parent path rather than simply using dir_path.
|
||||
// This is not the behaviour of gio, but the extensions by libfm might do this.
|
||||
// FIXME: after we port these vfs implementation from libfm, we can redesign this.
|
||||
FilePath realParentPath = FilePath{g_file_enumerator_get_container(enu.get()), true};
|
||||
if(isFileSearch) { // this is a file sarch job (search:/// URI)
|
||||
// FIXME: redesign file search and remove this dirty hack
|
||||
// the libfm implementation of search:/// URI returns a customized GFile implementation that does not behave normally.
|
||||
// let's get its actual URI and re-create a normal gio GFile instance from it.
|
||||
realParentPath = FilePath::fromUri(realParentPath.uri().get());
|
||||
}
|
||||
#if 0
|
||||
if(g_file_info_get_file_type(inf) == G_FILE_TYPE_DIRECTORY)
|
||||
/* for dir: check if its FS is R/O and set attr. into inf */
|
||||
{
|
||||
_fm_file_info_job_update_fs_readonly(child, inf, nullptr, nullptr);
|
||||
}
|
||||
fi = fm_file_info_new_from_g_file_data(child, inf, sub);
|
||||
#endif
|
||||
auto fileInfo = std::make_shared<FileInfo>(inf, realParentPath);
|
||||
if(emit_files_found) {
|
||||
// Q_EMIT filesFound();
|
||||
}
|
||||
|
||||
if(cutFilesHashSet_
|
||||
&& cutFilesHashSet_->count(fileInfo->path().hash()) > 0) {
|
||||
fileInfo->bindCutFiles(cutFilesHashSet_);
|
||||
}
|
||||
|
||||
foundFiles.push_back(std::move(fileInfo));
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
ErrorAction act = emitError(err, ErrorSeverity::MILD);
|
||||
/* ErrorAction::RETRY is not supported. */
|
||||
if(act == ErrorAction::ABORT) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
/* otherwise it's EOL */
|
||||
break;
|
||||
}
|
||||
}
|
||||
err.reset();
|
||||
g_file_enumerator_close(enu.get(), cancellable().get(), &err);
|
||||
}
|
||||
else {
|
||||
emitError(err, ErrorSeverity::CRITICAL);
|
||||
}
|
||||
|
||||
// qDebug() << "END LISTING:" << dir_path.toString().get();
|
||||
if(!foundFiles.empty()) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
files_.swap(foundFiles);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
//FIXME: incremental..
|
||||
|
||||
static gboolean emit_found_files(gpointer user_data) {
|
||||
/* this callback is called from the main thread */
|
||||
FmDirListJob* job = FM_DIR_LIST_JOB(user_data);
|
||||
/* g_print("emit_found_files: %d\n", g_slist_length(job->files_to_add)); */
|
||||
|
||||
if(g_source_is_destroyed(g_main_current_source())) {
|
||||
return FALSE;
|
||||
}
|
||||
g_signal_emit(job, signals[FILES_FOUND], 0, job->files_to_add);
|
||||
g_slist_free_full(job->files_to_add, (GDestroyNotify)fm_file_info_unref);
|
||||
job->files_to_add = nullptr;
|
||||
job->delay_add_files_handler = 0;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gpointer queue_add_file(FmJob* fmjob, gpointer user_data) {
|
||||
FmDirListJob* job = FM_DIR_LIST_JOB(fmjob);
|
||||
FmFileInfo* file = FM_FILE_INFO(user_data);
|
||||
/* this callback is called from the main thread */
|
||||
/* g_print("queue_add_file: %s\n", fm_file_info_get_disp_name(file)); */
|
||||
job->files_to_add = g_slist_prepend(job->files_to_add, fm_file_info_ref(file));
|
||||
if(job->delay_add_files_handler == 0)
|
||||
job->delay_add_files_handler = g_timeout_add_seconds_full(G_PRIORITY_LOW,
|
||||
1, emit_found_files, g_object_ref(job), g_object_unref);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void fm_dir_list_job_add_found_file(FmDirListJob* job, FmFileInfo* file) {
|
||||
fm_file_info_list_push_tail(job->files, file);
|
||||
if(G_UNLIKELY(job->emit_files_found)) {
|
||||
fm_job_call_main_thread(FM_JOB(job), queue_add_file, file);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,65 @@
|
||||
#ifndef FM2_DIRLISTJOB_H
|
||||
#define FM2_DIRLISTJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <mutex>
|
||||
#include "job.h"
|
||||
#include "filepath.h"
|
||||
#include "gobjectptr.h"
|
||||
#include "fileinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API DirListJob : public Job {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Flags {
|
||||
FAST = 0,
|
||||
DIR_ONLY = 1 << 0,
|
||||
DETAILED = 1 << 1
|
||||
};
|
||||
|
||||
explicit DirListJob(const FilePath& path, Flags flags, const std::shared_ptr<const HashSet>& cutFilesHashSet = nullptr);
|
||||
|
||||
FileInfoList& files() {
|
||||
return files_;
|
||||
}
|
||||
|
||||
void setIncremental(bool set);
|
||||
|
||||
bool incremental() const {
|
||||
return emit_files_found;
|
||||
}
|
||||
|
||||
FilePath dirPath() const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
return dir_path;
|
||||
}
|
||||
|
||||
std::shared_ptr<const FileInfo> dirInfo() const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
return dir_fi;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void filesFound(FileInfoList& foundFiles);
|
||||
|
||||
protected:
|
||||
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
FilePath dir_path;
|
||||
Flags flags;
|
||||
std::shared_ptr<const FileInfo> dir_fi;
|
||||
FileInfoList files_;
|
||||
const std::shared_ptr<const HashSet> cutFilesHashSet_;
|
||||
bool emit_files_found;
|
||||
// guint delay_add_files_handler;
|
||||
// GSList* files_to_add;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_DIRLISTJOB_H
|
@ -0,0 +1,9 @@
|
||||
#include "filechangeattrjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileChangeAttrJob::FileChangeAttrJob() {
|
||||
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,17 @@
|
||||
#ifndef FM2_FILECHANGEATTRJOB_H
|
||||
#define FM2_FILECHANGEATTRJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileChangeAttrJob : public Fm::FileOperationJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FileChangeAttrJob();
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_FILECHANGEATTRJOB_H
|
@ -0,0 +1,378 @@
|
||||
#include "fileinfo.h"
|
||||
#include "fileinfo_p.h"
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
const char gfile_info_query_attribs[] = "standard::*,"
|
||||
"unix::*,"
|
||||
"time::*,"
|
||||
"access::*,"
|
||||
"id::filesystem,"
|
||||
"metadata::emblems";
|
||||
|
||||
FileInfo::FileInfo() {
|
||||
// FIXME: initialize numeric data members
|
||||
}
|
||||
|
||||
FileInfo::FileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath) {
|
||||
setFromGFileInfo(inf, parentDirPath);
|
||||
}
|
||||
|
||||
FileInfo::~FileInfo() {
|
||||
}
|
||||
|
||||
void FileInfo::setFromGFileInfo(const GObjectPtr<GFileInfo>& inf, const FilePath& parentDirPath) {
|
||||
dirPath_ = parentDirPath;
|
||||
const char* tmp, *uri;
|
||||
GIcon* gicon;
|
||||
GFileType type;
|
||||
|
||||
name_ = g_file_info_get_name(inf.get());
|
||||
|
||||
dispName_ = g_file_info_get_display_name(inf.get());
|
||||
|
||||
size_ = g_file_info_get_size(inf.get());
|
||||
|
||||
tmp = g_file_info_get_content_type(inf.get());
|
||||
if(!tmp) {
|
||||
tmp = "application/octet-stream";
|
||||
}
|
||||
mimeType_ = MimeType::fromName(tmp);
|
||||
|
||||
mode_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
|
||||
|
||||
uid_ = gid_ = -1;
|
||||
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_UNIX_UID)) {
|
||||
uid_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_UID);
|
||||
}
|
||||
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_UNIX_GID)) {
|
||||
gid_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_GID);
|
||||
}
|
||||
|
||||
type = g_file_info_get_file_type(inf.get());
|
||||
if(0 == mode_) { /* if UNIX file mode is not available, compose a fake one. */
|
||||
switch(type) {
|
||||
case G_FILE_TYPE_REGULAR:
|
||||
mode_ |= S_IFREG;
|
||||
break;
|
||||
case G_FILE_TYPE_DIRECTORY:
|
||||
mode_ |= S_IFDIR;
|
||||
break;
|
||||
case G_FILE_TYPE_SYMBOLIC_LINK:
|
||||
mode_ |= S_IFLNK;
|
||||
break;
|
||||
case G_FILE_TYPE_SHORTCUT:
|
||||
break;
|
||||
case G_FILE_TYPE_MOUNTABLE:
|
||||
break;
|
||||
case G_FILE_TYPE_SPECIAL:
|
||||
if(mode_) {
|
||||
break;
|
||||
}
|
||||
/* if it's a special file but it doesn't have UNIX mode, compose a fake one. */
|
||||
if(strcmp(tmp, "inode/chardevice") == 0) {
|
||||
mode_ |= S_IFCHR;
|
||||
}
|
||||
else if(strcmp(tmp, "inode/blockdevice") == 0) {
|
||||
mode_ |= S_IFBLK;
|
||||
}
|
||||
else if(strcmp(tmp, "inode/fifo") == 0) {
|
||||
mode_ |= S_IFIFO;
|
||||
}
|
||||
#ifdef S_IFSOCK
|
||||
else if(strcmp(tmp, "inode/socket") == 0) {
|
||||
mode_ |= S_IFSOCK;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case G_FILE_TYPE_UNKNOWN:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
|
||||
isAccessible_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
|
||||
}
|
||||
else
|
||||
/* assume it's accessible */
|
||||
{
|
||||
isAccessible_ = true;
|
||||
}
|
||||
|
||||
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) {
|
||||
isWritable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
|
||||
}
|
||||
else
|
||||
/* assume it's writable */
|
||||
{
|
||||
isWritable_ = true;
|
||||
}
|
||||
|
||||
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) {
|
||||
isDeletable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE);
|
||||
}
|
||||
else
|
||||
/* assume it's deletable */
|
||||
{
|
||||
isDeletable_ = true;
|
||||
}
|
||||
|
||||
/* special handling for symlinks */
|
||||
if(g_file_info_get_is_symlink(inf.get())) {
|
||||
mode_ &= ~S_IFMT; /* reset type */
|
||||
mode_ |= S_IFLNK; /* set type to symlink */
|
||||
goto _file_is_symlink;
|
||||
}
|
||||
|
||||
isShortcut_ = false;
|
||||
|
||||
switch(type) {
|
||||
case G_FILE_TYPE_SHORTCUT:
|
||||
isShortcut_ = true;
|
||||
case G_FILE_TYPE_MOUNTABLE:
|
||||
uri = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
||||
if(uri) {
|
||||
if(g_str_has_prefix(uri, "file:///")) {
|
||||
auto filename = CStrPtr{g_filename_from_uri(uri, nullptr, nullptr)};
|
||||
target_ = filename.get();
|
||||
}
|
||||
else {
|
||||
target_ = uri;
|
||||
}
|
||||
if(!mimeType_) {
|
||||
mimeType_ = MimeType::guessFromFileName(target_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
/* if the mime-type is not determined or is unknown */
|
||||
if(G_UNLIKELY(!mimeType_ || mimeType_->isUnknownType())) {
|
||||
/* FIXME: is this appropriate? */
|
||||
if(type == G_FILE_TYPE_SHORTCUT) {
|
||||
mimeType_ = MimeType::inodeShortcut();
|
||||
}
|
||||
else {
|
||||
mimeType_ = MimeType::inodeMountPoint();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case G_FILE_TYPE_DIRECTORY:
|
||||
if(!mimeType_) {
|
||||
mimeType_ = MimeType::inodeDirectory();
|
||||
}
|
||||
isReadOnly_ = false; /* default is R/W */
|
||||
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) {
|
||||
isReadOnly_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_READONLY);
|
||||
}
|
||||
/* directories should be writable to be deleted by user */
|
||||
if(isReadOnly_ || !isWritable_) {
|
||||
isDeletable_ = false;
|
||||
}
|
||||
break;
|
||||
case G_FILE_TYPE_SYMBOLIC_LINK:
|
||||
_file_is_symlink:
|
||||
uri = g_file_info_get_symlink_target(inf.get());
|
||||
if(uri) {
|
||||
if(g_str_has_prefix(uri, "file:///")) {
|
||||
auto filename = CStrPtr{g_filename_from_uri(uri, nullptr, nullptr)};
|
||||
target_ = filename.get();
|
||||
}
|
||||
else {
|
||||
target_ = uri;
|
||||
}
|
||||
if(!mimeType_) {
|
||||
mimeType_ = MimeType::guessFromFileName(target_.c_str());
|
||||
}
|
||||
}
|
||||
/* continue with absent mime type */
|
||||
default: /* G_FILE_TYPE_UNKNOWN G_FILE_TYPE_REGULAR G_FILE_TYPE_SPECIAL */
|
||||
if(G_UNLIKELY(!mimeType_)) {
|
||||
if(!mimeType_) {
|
||||
mimeType_ = MimeType::guessFromFileName(name_.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if there is a custom folder icon, use it */
|
||||
if(isNative() && type == G_FILE_TYPE_DIRECTORY) {
|
||||
auto local_path = path().localPath();
|
||||
auto dot_dir = CStrPtr{g_build_filename(local_path.get(), ".directory", nullptr)};
|
||||
if(g_file_test(dot_dir.get(), G_FILE_TEST_IS_REGULAR)) {
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(g_key_file_load_from_file(kf, dot_dir.get(), G_KEY_FILE_NONE, nullptr)) {
|
||||
CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)};
|
||||
if(icon_name) {
|
||||
auto dot_icon = IconInfo::fromName(icon_name.get());
|
||||
if(dot_icon && dot_icon->isValid()) {
|
||||
icon_ = dot_icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
}
|
||||
|
||||
if(!icon_) {
|
||||
/* try file-specific icon first */
|
||||
gicon = g_file_info_get_icon(inf.get());
|
||||
if(gicon) {
|
||||
icon_ = IconInfo::fromGIcon(gicon);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* set "locked" icon on unaccesible folder */
|
||||
else if(!accessible && type == G_FILE_TYPE_DIRECTORY) {
|
||||
icon = g_object_ref(icon_locked_folder);
|
||||
}
|
||||
else {
|
||||
icon = g_object_ref(fm_mime_type_get_icon(mime_type));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* if the file has emblems, add them to the icon */
|
||||
auto emblem_names = g_file_info_get_attribute_stringv(inf.get(), "metadata::emblems");
|
||||
if(emblem_names) {
|
||||
auto n_emblems = g_strv_length(emblem_names);
|
||||
for(int i = n_emblems - 1; i >= 0; --i) {
|
||||
emblems_.emplace_front(Fm::IconInfo::fromName(emblem_names[i]));
|
||||
}
|
||||
}
|
||||
|
||||
tmp = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM);
|
||||
filesystemId_ = g_intern_string(tmp);
|
||||
|
||||
mtime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED);
|
||||
atime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_ACCESS);
|
||||
ctime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_CHANGED);
|
||||
isHidden_ = g_file_info_get_is_hidden(inf.get());
|
||||
isBackup_ = g_file_info_get_is_backup(inf.get());
|
||||
isNameChangeable_ = true; /* GVFS tends to ignore this attribute */
|
||||
isIconChangeable_ = isHiddenChangeable_ = false;
|
||||
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) {
|
||||
isNameChangeable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME);
|
||||
}
|
||||
|
||||
// special handling for desktop entry files (show the name and icon defined in the desktop entry instead)
|
||||
if(isNative() && G_UNLIKELY(isDesktopEntry())) {
|
||||
auto local_path = path().localPath();
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(g_key_file_load_from_file(kf, local_path.get(), G_KEY_FILE_NONE, nullptr)) {
|
||||
/* check if type is correct and supported */
|
||||
CStrPtr type{g_key_file_get_string(kf, "Desktop Entry", "Type", nullptr)};
|
||||
if(type) {
|
||||
// Type == "Link"
|
||||
if(strcmp(type.get(), G_KEY_FILE_DESKTOP_TYPE_LINK) == 0) {
|
||||
CStrPtr uri{g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, nullptr)};
|
||||
if(uri) {
|
||||
isShortcut_ = true;
|
||||
target_ = uri.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)};
|
||||
if(icon_name) {
|
||||
icon_ = IconInfo::fromName(icon_name.get());
|
||||
}
|
||||
/* Use title of the desktop entry for display */
|
||||
CStrPtr displayName{g_key_file_get_locale_string(kf, "Desktop Entry", "Name", nullptr, nullptr)};
|
||||
if(displayName) {
|
||||
dispName_ = displayName.get();
|
||||
}
|
||||
/* handle 'Hidden' key to set hidden attribute */
|
||||
if(!isHidden_) {
|
||||
isHidden_ = g_key_file_get_boolean(kf, "Desktop Entry", "Hidden", nullptr);
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
|
||||
if(!icon_ && mimeType_)
|
||||
icon_ = mimeType_->icon();
|
||||
|
||||
#if 0
|
||||
GFile* _gf = nullptr;
|
||||
GFileAttributeInfoList* list;
|
||||
auto list = g_file_query_settable_attributes(gf, nullptr, nullptr);
|
||||
if(G_LIKELY(list)) {
|
||||
if(g_file_attribute_info_list_lookup(list, G_FILE_ATTRIBUTE_STANDARD_ICON)) {
|
||||
icon_is_changeable = true;
|
||||
}
|
||||
if(g_file_attribute_info_list_lookup(list, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) {
|
||||
hidden_is_changeable = true;
|
||||
}
|
||||
g_file_attribute_info_list_unref(list);
|
||||
}
|
||||
if(G_UNLIKELY(_gf)) {
|
||||
g_object_unref(_gf);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileInfo::bindCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) {
|
||||
cutFilesHashSet_ = cutFilesHashSet;
|
||||
}
|
||||
|
||||
bool FileInfo::canThumbnail() const {
|
||||
/* We cannot use S_ISREG here as this exclude all symlinks */
|
||||
if(size_ == 0 || /* don't generate thumbnails for empty files */
|
||||
!(mode_ & S_IFREG) ||
|
||||
isDesktopEntry() ||
|
||||
isUnknownType()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* full path of the file is required by this function */
|
||||
bool FileInfo::isExecutableType() const {
|
||||
if(isText()) { /* g_content_type_can_be_executable reports text files as executables too */
|
||||
/* We don't execute remote files nor files in trash */
|
||||
if(isNative() && (mode_ & (S_IXOTH | S_IXGRP | S_IXUSR))) {
|
||||
/* it has executable bits so lets check shell-bang */
|
||||
auto pathStr = path().toString();
|
||||
int fd = open(pathStr.get(), O_RDONLY);
|
||||
if(fd >= 0) {
|
||||
char buf[2];
|
||||
ssize_t rdlen = read(fd, &buf, 2);
|
||||
close(fd);
|
||||
if(rdlen == 2 && buf[0] == '#' && buf[1] == '!') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return mimeType_->canBeExecutable();
|
||||
}
|
||||
|
||||
|
||||
bool FileInfoList::isSameType() const {
|
||||
if(!empty()) {
|
||||
auto& item = front();
|
||||
for(auto it = cbegin() + 1; it != cend(); ++it) {
|
||||
auto& item2 = *it;
|
||||
if(item->mimeType() != item2->mimeType()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileInfoList::isSameFilesystem() const {
|
||||
if(!empty()) {
|
||||
auto& item = front();
|
||||
for(auto it = cbegin() + 1; it != cend(); ++it) {
|
||||
auto& item2 = *it;
|
||||
if(item->filesystemId() != item2->filesystemId()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LIBFM_QT_FM2_FILE_INFO_H__
|
||||
#define __LIBFM_QT_FM2_FILE_INFO_H__
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include "../libfmqtglobals.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <forward_list>
|
||||
|
||||
#include "gioptrs.h"
|
||||
#include "filepath.h"
|
||||
#include "iconinfo.h"
|
||||
#include "mimetype.h"
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FileInfoList;
|
||||
typedef std::set<unsigned int> HashSet;
|
||||
|
||||
class LIBFM_QT_API FileInfo {
|
||||
public:
|
||||
|
||||
explicit FileInfo();
|
||||
|
||||
explicit FileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath);
|
||||
|
||||
virtual ~FileInfo();
|
||||
|
||||
bool canSetHidden() const {
|
||||
return isHiddenChangeable_;
|
||||
}
|
||||
|
||||
bool canSetIcon() const {
|
||||
return isIconChangeable_;
|
||||
}
|
||||
|
||||
bool canSetName() const {
|
||||
return isNameChangeable_;
|
||||
}
|
||||
|
||||
bool canThumbnail() const;
|
||||
|
||||
gid_t gid() const {
|
||||
return gid_;
|
||||
}
|
||||
|
||||
uid_t uid() const {
|
||||
return uid_;
|
||||
}
|
||||
|
||||
const char* filesystemId() const {
|
||||
return filesystemId_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const IconInfo>& icon() const {
|
||||
return icon_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const MimeType>& mimeType() const {
|
||||
return mimeType_;
|
||||
}
|
||||
|
||||
time_t ctime() const {
|
||||
return ctime_;
|
||||
}
|
||||
|
||||
|
||||
time_t atime() const {
|
||||
return atime_;
|
||||
}
|
||||
|
||||
time_t mtime() const {
|
||||
return mtime_;
|
||||
}
|
||||
|
||||
const std::string& target() const {
|
||||
return target_;
|
||||
}
|
||||
|
||||
bool isWritableDirectory() const {
|
||||
return (!isReadOnly_ && isDir());
|
||||
}
|
||||
|
||||
bool isAccessible() const {
|
||||
return isAccessible_;
|
||||
}
|
||||
|
||||
bool isWritable() const {
|
||||
return isWritable_;
|
||||
}
|
||||
|
||||
bool isDeletable() const {
|
||||
return isDeletable_;
|
||||
}
|
||||
|
||||
bool isExecutableType() const;
|
||||
|
||||
bool isBackup() const {
|
||||
return isBackup_;
|
||||
}
|
||||
|
||||
bool isHidden() const {
|
||||
// FIXME: we might treat backup files as hidden
|
||||
return isHidden_;
|
||||
}
|
||||
|
||||
bool isUnknownType() const {
|
||||
return mimeType_->isUnknownType();
|
||||
}
|
||||
|
||||
bool isDesktopEntry() const {
|
||||
return mimeType_->isDesktopEntry();
|
||||
}
|
||||
|
||||
bool isText() const {
|
||||
return mimeType_->isText();
|
||||
}
|
||||
|
||||
bool isImage() const {
|
||||
return mimeType_->isImage();
|
||||
}
|
||||
|
||||
bool isMountable() const {
|
||||
return mimeType_->isMountable();
|
||||
}
|
||||
|
||||
bool isShortcut() const {
|
||||
return isShortcut_;
|
||||
}
|
||||
|
||||
bool isSymlink() const {
|
||||
return S_ISLNK(mode_) ? true : false;
|
||||
}
|
||||
|
||||
bool isDir() const {
|
||||
return mimeType_->isDir();
|
||||
}
|
||||
|
||||
bool isNative() const {
|
||||
return dirPath_ ? dirPath_.isNative() : path().isNative();
|
||||
}
|
||||
|
||||
bool isCut() const {
|
||||
return !cutFilesHashSet_.expired();
|
||||
}
|
||||
|
||||
mode_t mode() const {
|
||||
return mode_;
|
||||
}
|
||||
|
||||
uint64_t realSize() const {
|
||||
return blksize_ *blocks_;
|
||||
}
|
||||
|
||||
uint64_t size() const {
|
||||
return size_;
|
||||
}
|
||||
|
||||
const std::string& name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
const QString& displayName() const {
|
||||
return dispName_;
|
||||
}
|
||||
|
||||
FilePath path() const {
|
||||
return dirPath_ ? dirPath_.child(name_.c_str()) : FilePath::fromPathStr(name_.c_str());
|
||||
}
|
||||
|
||||
const FilePath& dirPath() const {
|
||||
return dirPath_;
|
||||
}
|
||||
|
||||
void setFromGFileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath);
|
||||
|
||||
void bindCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet);
|
||||
|
||||
const std::forward_list<std::shared_ptr<const IconInfo>>& emblems() const {
|
||||
return emblems_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
QString dispName_;
|
||||
|
||||
FilePath dirPath_;
|
||||
|
||||
mode_t mode_;
|
||||
const char* filesystemId_;
|
||||
uid_t uid_;
|
||||
gid_t gid_;
|
||||
uint64_t size_;
|
||||
time_t mtime_;
|
||||
time_t atime_;
|
||||
time_t ctime_;
|
||||
|
||||
uint64_t blksize_;
|
||||
uint64_t blocks_;
|
||||
|
||||
std::shared_ptr<const MimeType> mimeType_;
|
||||
std::shared_ptr<const IconInfo> icon_;
|
||||
std::forward_list<std::shared_ptr<const IconInfo>> emblems_;
|
||||
|
||||
std::string target_; /* target of shortcut or mountable. */
|
||||
|
||||
bool isShortcut_ : 1; /* TRUE if file is shortcut type */
|
||||
bool isAccessible_ : 1; /* TRUE if can be read by user */
|
||||
bool isWritable_ : 1; /* TRUE if can be written to by user */
|
||||
bool isDeletable_ : 1; /* TRUE if can be deleted by user */
|
||||
bool isHidden_ : 1; /* TRUE if file is hidden */
|
||||
bool isBackup_ : 1; /* TRUE if file is backup */
|
||||
bool isNameChangeable_ : 1; /* TRUE if name can be changed */
|
||||
bool isIconChangeable_ : 1; /* TRUE if icon can be changed */
|
||||
bool isHiddenChangeable_ : 1; /* TRUE if hidden can be changed */
|
||||
bool isReadOnly_ : 1; /* TRUE if host FS is R/O */
|
||||
|
||||
std::weak_ptr<const HashSet> cutFilesHashSet_;
|
||||
// std::vector<std::tuple<int, void*, void(void*)>> extraData_;
|
||||
};
|
||||
|
||||
|
||||
class LIBFM_QT_API FileInfoList: public std::vector<std::shared_ptr<const FileInfo>> {
|
||||
public:
|
||||
|
||||
bool isSameType() const;
|
||||
|
||||
bool isSameFilesystem() const;
|
||||
|
||||
FilePathList paths() const {
|
||||
FilePathList ret;
|
||||
for(auto& file: *this) {
|
||||
ret.push_back(file->path());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
typedef std::pair<std::shared_ptr<const FileInfo>, std::shared_ptr<const FileInfo>> FileInfoPair;
|
||||
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<const Fm::FileInfo>)
|
||||
|
||||
|
||||
#endif // __LIBFM_QT_FM2_FILE_INFO_H__
|
@ -0,0 +1,10 @@
|
||||
#ifndef FILEINFO_P_H
|
||||
#define FILEINFO_P_H
|
||||
|
||||
namespace Fm {
|
||||
|
||||
extern const char gfile_info_query_attribs[];
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FILEINFO_P_H
|
@ -0,0 +1,42 @@
|
||||
#include "fileinfojob.h"
|
||||
#include "fileinfo_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileInfoJob::FileInfoJob(FilePathList paths, FilePath commonDirPath, const std::shared_ptr<const HashSet>& cutFilesHashSet):
|
||||
Job(),
|
||||
paths_{std::move(paths)},
|
||||
commonDirPath_{std::move(commonDirPath)},
|
||||
cutFilesHashSet_{cutFilesHashSet} {
|
||||
}
|
||||
|
||||
void FileInfoJob::exec() {
|
||||
for(const auto& path: paths_) {
|
||||
if(!isCancelled()) {
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr inf{
|
||||
g_file_query_info(path.gfile().get(), gfile_info_query_attribs,
|
||||
G_FILE_QUERY_INFO_NONE, cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(!inf)
|
||||
return;
|
||||
|
||||
// Reuse the same dirPath object when the path remains the same (optimize for files in the same dir)
|
||||
auto dirPath = commonDirPath_.isValid() ? commonDirPath_ : path.parent();
|
||||
FileInfo fileInfo(inf, dirPath);
|
||||
|
||||
if(cutFilesHashSet_
|
||||
&& cutFilesHashSet_->count(fileInfo.path().hash())) {
|
||||
fileInfo.bindCutFiles(cutFilesHashSet_);
|
||||
}
|
||||
|
||||
auto fileInfoPtr = std::make_shared<const FileInfo>(fileInfo);
|
||||
|
||||
results_.push_back(fileInfoPtr);
|
||||
Q_EMIT gotInfo(path, fileInfoPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,41 @@
|
||||
#ifndef FM2_FILEINFOJOB_H
|
||||
#define FM2_FILEINFOJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "job.h"
|
||||
#include "filepath.h"
|
||||
#include "fileinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
|
||||
class LIBFM_QT_API FileInfoJob : public Job {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit FileInfoJob(FilePathList paths, FilePath commonDirPath = FilePath(), const std::shared_ptr<const HashSet>& cutFilesHashSet = nullptr);
|
||||
|
||||
const FilePathList& paths() const {
|
||||
return paths_;
|
||||
}
|
||||
|
||||
const FileInfoList& files() const {
|
||||
return results_;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void gotInfo(const FilePath& path, std::shared_ptr<const FileInfo>& info);
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
FilePathList paths_;
|
||||
FileInfoList results_;
|
||||
FilePath commonDirPath_;
|
||||
const std::shared_ptr<const HashSet> cutFilesHashSet_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_FILEINFOJOB_H
|
@ -0,0 +1,9 @@
|
||||
#include "filelinkjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileLinkJob::FileLinkJob() {
|
||||
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,16 @@
|
||||
#ifndef FM2_FILELINKJOB_H
|
||||
#define FM2_FILELINKJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileLinkJob : public Fm::FileOperationJob {
|
||||
public:
|
||||
explicit FileLinkJob();
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_FILELINKJOB_H
|
@ -0,0 +1,9 @@
|
||||
#include "filemonitor.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileMonitor::FileMonitor() {
|
||||
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,26 @@
|
||||
#ifndef FM2_FILEMONITOR_H
|
||||
#define FM2_FILEMONITOR_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <QObject>
|
||||
#include "gioptrs.h"
|
||||
#include "filepath.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileMonitor: public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit FileMonitor();
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
|
||||
private:
|
||||
GFileMonitorPtr monitor_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_FILEMONITOR_H
|
@ -0,0 +1,79 @@
|
||||
#include "fileoperationjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileOperationJob::FileOperationJob():
|
||||
hasTotalAmount_{false},
|
||||
totalSize_{0},
|
||||
totalCount_{0},
|
||||
finishedSize_{0},
|
||||
finishedCount_{0},
|
||||
currentFileSize_{0},
|
||||
currentFileFinished_{0} {
|
||||
}
|
||||
|
||||
bool FileOperationJob::totalAmount(uint64_t& fileSize, uint64_t& fileCount) const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
if(hasTotalAmount_) {
|
||||
fileSize = totalSize_;
|
||||
fileCount = totalCount_;
|
||||
}
|
||||
return hasTotalAmount_;
|
||||
}
|
||||
|
||||
bool FileOperationJob::currentFileProgress(FilePath& path, uint64_t& totalSize, uint64_t& finishedSize) const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
if(currentFile_.isValid()) {
|
||||
path = currentFile_;
|
||||
totalSize = currentFileSize_;
|
||||
finishedSize = currentFileFinished_;
|
||||
}
|
||||
return currentFile_.isValid();
|
||||
}
|
||||
|
||||
FileOperationJob::FileExistsAction FileOperationJob::askRename(const FileInfo &src, const FileInfo &dest, FilePath &newDest) {
|
||||
FileExistsAction action = SKIP;
|
||||
Q_EMIT fileExists(src, dest, action, newDest);
|
||||
return action;
|
||||
}
|
||||
|
||||
bool FileOperationJob::finishedAmount(uint64_t& finishedSize, uint64_t& finishedCount) const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
if(hasTotalAmount_) {
|
||||
finishedSize = finishedSize_;
|
||||
finishedCount = finishedCount_;
|
||||
}
|
||||
return hasTotalAmount_;
|
||||
}
|
||||
|
||||
void FileOperationJob::setTotalAmount(uint64_t fileSize, uint64_t fileCount) {
|
||||
std::lock_guard<std::mutex> locl{mutex_};
|
||||
hasTotalAmount_ = true;
|
||||
totalSize_ = fileSize;
|
||||
totalCount_ = fileCount;
|
||||
}
|
||||
|
||||
void FileOperationJob::setFinishedAmount(uint64_t finishedSize, uint64_t finishedCount) {
|
||||
std::lock_guard<std::mutex> locl{mutex_};
|
||||
finishedSize_ = finishedSize;
|
||||
finishedCount_ = finishedCount;
|
||||
}
|
||||
|
||||
void FileOperationJob::addFinishedAmount(uint64_t finishedSize, uint64_t finishedCount) {
|
||||
std::lock_guard<std::mutex> locl{mutex_};
|
||||
finishedSize_ += finishedSize;
|
||||
finishedCount_ += finishedCount;
|
||||
}
|
||||
|
||||
void FileOperationJob::setCurrentFile(const FilePath& path) {
|
||||
std::lock_guard<std::mutex> locl{mutex_};
|
||||
currentFile_ = path;
|
||||
}
|
||||
|
||||
void FileOperationJob::setCurrentFileProgress(uint64_t totalSize, uint64_t finishedSize) {
|
||||
std::lock_guard<std::mutex> locl{mutex_};
|
||||
currentFileSize_ = totalSize;
|
||||
currentFileFinished_ = finishedSize;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,73 @@
|
||||
#ifndef FM2_FILEOPERATIONJOB_H
|
||||
#define FM2_FILEOPERATIONJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "job.h"
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <cstdint>
|
||||
#include "fileinfo.h"
|
||||
#include "filepath.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileOperationJob : public Fm::Job {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum FileExistsAction {
|
||||
CANCEL = 0,
|
||||
OVERWRITE = 1<<0,
|
||||
RENAME = 1<<1,
|
||||
SKIP = 1<<2,
|
||||
SKIP_ERROR = 1<<3
|
||||
};
|
||||
|
||||
explicit FileOperationJob();
|
||||
|
||||
bool totalAmount(std::uint64_t& fileSize, std::uint64_t& fileCount) const;
|
||||
|
||||
bool finishedAmount(std::uint64_t& finishedSize, std::uint64_t& finishedCount) const;
|
||||
|
||||
bool currentFileProgress(FilePath& path, std::uint64_t& totalSize, std::uint64_t& finishedSize) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
void preparedToRun();
|
||||
|
||||
// void currentFile(const char* file);
|
||||
|
||||
// void progress(uint32_t percent);
|
||||
|
||||
// to correctly handle the signal, connect with Qt::BlockingQueuedConnection so it's a sync call.
|
||||
void fileExists(const FileInfo& src, const FileInfo& dest, FileExistsAction& response, FilePath& newDest);
|
||||
|
||||
protected:
|
||||
|
||||
FileExistsAction askRename(const FileInfo& src, const FileInfo& dest, FilePath& newDest);
|
||||
|
||||
void setTotalAmount(std::uint64_t fileSize, std::uint64_t fileCount);
|
||||
|
||||
void setFinishedAmount(std::uint64_t finishedSize, std::uint64_t finishedCount);
|
||||
|
||||
void addFinishedAmount(std::uint64_t finishedSize, std::uint64_t finishedCount);
|
||||
|
||||
void setCurrentFile(const FilePath &path);
|
||||
|
||||
void setCurrentFileProgress(uint64_t totalSize, uint64_t finishedSize);
|
||||
|
||||
private:
|
||||
bool hasTotalAmount_;
|
||||
std::uint64_t totalSize_;
|
||||
std::uint64_t totalCount_;
|
||||
std::uint64_t finishedSize_;
|
||||
std::uint64_t finishedCount_;
|
||||
|
||||
FilePath currentFile_;
|
||||
std::uint64_t currentFileSize_;
|
||||
std::uint64_t currentFileFinished_;
|
||||
mutable std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_FILEOPERATIONJOB_H
|
@ -0,0 +1,21 @@
|
||||
#include "filepath.h"
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include <glib.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FilePath FilePath::homeDir_;
|
||||
|
||||
const FilePath &FilePath::homeDir() {
|
||||
if(!homeDir_) {
|
||||
const char* home = getenv("HOME");
|
||||
if(!home) {
|
||||
home = g_get_home_dir();
|
||||
}
|
||||
homeDir_ = FilePath::fromLocalPath(home);
|
||||
}
|
||||
return homeDir_;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,177 @@
|
||||
#ifndef FM2_FILEPATH_H
|
||||
#define FM2_FILEPATH_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "gobjectptr.h"
|
||||
#include "cstrptr.h"
|
||||
#include <gio/gio.h>
|
||||
#include <vector>
|
||||
#include <QMetaType>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FilePath {
|
||||
public:
|
||||
|
||||
explicit FilePath() {
|
||||
}
|
||||
|
||||
explicit FilePath(GFile* gfile, bool add_ref): gfile_{gfile, add_ref} {
|
||||
}
|
||||
|
||||
FilePath(const FilePath& other): FilePath{} {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
FilePath(FilePath&& other) noexcept: FilePath{} {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
static FilePath fromUri(const char* uri) {
|
||||
return FilePath{g_file_new_for_uri(uri), false};
|
||||
}
|
||||
|
||||
static FilePath fromLocalPath(const char* path) {
|
||||
return FilePath{g_file_new_for_path(path), false};
|
||||
}
|
||||
|
||||
static FilePath fromDisplayName(const char* path) {
|
||||
return FilePath{g_file_parse_name(path), false};
|
||||
}
|
||||
|
||||
static FilePath fromPathStr(const char* path_str) {
|
||||
return FilePath{g_file_new_for_commandline_arg(path_str), false};
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return gfile_ != nullptr;
|
||||
}
|
||||
|
||||
unsigned int hash() const {
|
||||
return g_file_hash(gfile_.get());
|
||||
}
|
||||
|
||||
CStrPtr baseName() const {
|
||||
return CStrPtr{g_file_get_basename(gfile_.get())};
|
||||
}
|
||||
|
||||
CStrPtr localPath() const {
|
||||
return CStrPtr{g_file_get_path(gfile_.get())};
|
||||
}
|
||||
|
||||
CStrPtr uri() const {
|
||||
return CStrPtr{g_file_get_uri(gfile_.get())};
|
||||
}
|
||||
|
||||
CStrPtr toString() const {
|
||||
if(isNative()) {
|
||||
return localPath();
|
||||
}
|
||||
return uri();
|
||||
}
|
||||
|
||||
// a human readable UTF-8 display name for the path
|
||||
CStrPtr displayName() const {
|
||||
return CStrPtr{g_file_get_parse_name(gfile_.get())};
|
||||
}
|
||||
|
||||
FilePath parent() const {
|
||||
return FilePath{g_file_get_parent(gfile_.get()), false};
|
||||
}
|
||||
|
||||
bool hasParent() const {
|
||||
return g_file_has_parent(gfile_.get(), nullptr);
|
||||
}
|
||||
|
||||
bool isParentOf(const FilePath& other) {
|
||||
return g_file_has_parent(other.gfile_.get(), gfile_.get());
|
||||
}
|
||||
|
||||
bool isPrefixOf(const FilePath& other) {
|
||||
return g_file_has_prefix(other.gfile_.get(), gfile_.get());
|
||||
}
|
||||
|
||||
FilePath child(const char* name) const {
|
||||
return FilePath{g_file_get_child(gfile_.get(), name), false};
|
||||
}
|
||||
|
||||
CStrPtr relativePathStr(const FilePath& descendant) const {
|
||||
return CStrPtr{g_file_get_relative_path(gfile_.get(), descendant.gfile_.get())};
|
||||
}
|
||||
|
||||
FilePath relativePath(const char* relPath) const {
|
||||
return FilePath{g_file_resolve_relative_path(gfile_.get(), relPath), false};
|
||||
}
|
||||
|
||||
bool isNative() const {
|
||||
return g_file_is_native(gfile_.get());
|
||||
}
|
||||
|
||||
bool hasUriScheme(const char* scheme) const {
|
||||
return g_file_has_uri_scheme(gfile_.get(), scheme);
|
||||
}
|
||||
|
||||
CStrPtr uriScheme() const {
|
||||
return CStrPtr{g_file_get_uri_scheme(gfile_.get())};
|
||||
}
|
||||
|
||||
const GObjectPtr<GFile>& gfile() const {
|
||||
return gfile_;
|
||||
}
|
||||
|
||||
FilePath& operator = (const FilePath& other) {
|
||||
gfile_ = other.gfile_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FilePath& operator = (const FilePath&& other) noexcept {
|
||||
gfile_ = std::move(other.gfile_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator == (const FilePath& other) const {
|
||||
return operator==(other.gfile_.get());
|
||||
}
|
||||
|
||||
bool operator == (GFile* other_gfile) const {
|
||||
if(gfile_ == other_gfile) {
|
||||
return true;
|
||||
}
|
||||
if(gfile_ && other_gfile) {
|
||||
return g_file_equal(gfile_.get(), other_gfile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator != (const FilePath& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
bool operator != (std::nullptr_t) const {
|
||||
return gfile_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return gfile_ != nullptr;
|
||||
}
|
||||
|
||||
static const FilePath& homeDir();
|
||||
|
||||
private:
|
||||
GObjectPtr<GFile> gfile_;
|
||||
static FilePath homeDir_;
|
||||
};
|
||||
|
||||
struct FilePathHash {
|
||||
std::size_t operator() (const FilePath& path) const {
|
||||
return path.hash();
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<FilePath> FilePathList;
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
Q_DECLARE_METATYPE(Fm::FilePath)
|
||||
|
||||
#endif // FM2_FILEPATH_H
|
@ -0,0 +1,24 @@
|
||||
#include "filesysteminfojob.h"
|
||||
#include "gobjectptr.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
void FileSystemInfoJob::exec() {
|
||||
GObjectPtr<GFileInfo> inf = GObjectPtr<GFileInfo>{
|
||||
g_file_query_filesystem_info(
|
||||
path_.gfile().get(),
|
||||
G_FILE_ATTRIBUTE_FILESYSTEM_SIZE","
|
||||
G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
|
||||
cancellable().get(), nullptr),
|
||||
false
|
||||
};
|
||||
if(!inf)
|
||||
return;
|
||||
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_SIZE)) {
|
||||
size_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
|
||||
freeSize_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
|
||||
isAvailable_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,45 @@
|
||||
#ifndef FM2_FILESYSTEMINFOJOB_H
|
||||
#define FM2_FILESYSTEMINFOJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "job.h"
|
||||
#include "filepath.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileSystemInfoJob : public Job {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FileSystemInfoJob(const FilePath& path):
|
||||
path_{path},
|
||||
isAvailable_{false},
|
||||
size_{0},
|
||||
freeSize_{0} {
|
||||
}
|
||||
|
||||
bool isAvailable() const {
|
||||
return isAvailable_;
|
||||
}
|
||||
|
||||
uint64_t size() const {
|
||||
return size_;
|
||||
}
|
||||
|
||||
uint64_t freeSize() const {
|
||||
return freeSize_;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
FilePath path_;
|
||||
bool isAvailable_;
|
||||
uint64_t size_;
|
||||
uint64_t freeSize_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_FILESYSTEMINFOJOB_H
|
@ -0,0 +1,908 @@
|
||||
/*
|
||||
* 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
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LIBFM2_QT_FM_FOLDER_H__
|
||||
#define __LIBFM2_QT_FM_FOLDER_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include "../libfmqtglobals.h"
|
||||
|
||||
#include "gioptrs.h"
|
||||
#include "fileinfo.h"
|
||||
#include "job.h"
|
||||
#include "volumemanager.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class DirListJob;
|
||||
class FileSystemInfoJob;
|
||||
class FileInfoJob;
|
||||
|
||||
|
||||
class LIBFM_QT_API Folder: public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit Folder();
|
||||
|
||||
explicit Folder(const FilePath& path);
|
||||
|
||||
virtual ~Folder();
|
||||
|
||||
static std::shared_ptr<Folder> fromPath(const FilePath& path);
|
||||
|
||||
bool makeDirectory(const char* name, GError** error);
|
||||
|
||||
void queryFilesystemInfo();
|
||||
|
||||
bool getFilesystemInfo(uint64_t* total_size, uint64_t* free_size) const;
|
||||
|
||||
void reload();
|
||||
|
||||
bool isIncremental() const;
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
bool isLoaded() const;
|
||||
|
||||
std::shared_ptr<const FileInfo> fileByName(const char* name) const;
|
||||
|
||||
bool isEmpty() const;
|
||||
|
||||
FileInfoList files() const;
|
||||
|
||||
const FilePath& path() const;
|
||||
|
||||
const std::shared_ptr<const FileInfo> &info() const;
|
||||
|
||||
bool hadCutFilesUnset();
|
||||
|
||||
bool hasCutFiles();
|
||||
|
||||
void setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet);
|
||||
|
||||
void forEachFile(std::function<void (const std::shared_ptr<const FileInfo>&)> func) const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
for(auto it = files_.begin(); it != files_.end(); ++it) {
|
||||
func(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void startLoading();
|
||||
|
||||
void finishLoading();
|
||||
|
||||
void filesAdded(FileInfoList& addedFiles);
|
||||
|
||||
void filesChanged(std::vector<FileInfoPair>& changePairs);
|
||||
|
||||
void filesRemoved(FileInfoList& removedFiles);
|
||||
|
||||
void removed();
|
||||
|
||||
void changed();
|
||||
|
||||
void unmount();
|
||||
|
||||
void contentChanged();
|
||||
|
||||
void fileSystemChanged();
|
||||
|
||||
// FIXME: this API design is bad. We leave this here to be compatible with the old libfm C API.
|
||||
// It might be better to remember the error state while loading the folder, and let the user of the
|
||||
// API handle the error on finish.
|
||||
void error(const GErrorPtr& err, Job::ErrorSeverity severity, Job::ErrorAction& response);
|
||||
|
||||
private:
|
||||
|
||||
static void _onFileChangeEvents(GFileMonitor* monitor, GFile* file, GFile* other_file, GFileMonitorEvent event_type, Folder* _this) {
|
||||
_this->onFileChangeEvents(monitor, file, other_file, event_type);
|
||||
}
|
||||
void onFileChangeEvents(GFileMonitor* monitor, GFile* file, GFile* other_file, GFileMonitorEvent event_type);
|
||||
void onDirChanged(GFileMonitorEvent event_type);
|
||||
|
||||
void queueUpdate();
|
||||
void queueReload();
|
||||
|
||||
bool eventFileAdded(const FilePath &path);
|
||||
bool eventFileChanged(const FilePath &path);
|
||||
void eventFileDeleted(const FilePath &path);
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
void processPendingChanges();
|
||||
|
||||
void onDirListFinished();
|
||||
|
||||
void onFileSystemInfoFinished();
|
||||
|
||||
void onFileInfoFinished();
|
||||
|
||||
void onIdleReload();
|
||||
|
||||
void onMountAdded(const Mount& mnt);
|
||||
|
||||
void onMountRemoved(const Mount& mnt);
|
||||
|
||||
private:
|
||||
FilePath dirPath_;
|
||||
GFileMonitorPtr dirMonitor_;
|
||||
|
||||
std::shared_ptr<const FileInfo> dirInfo_;
|
||||
DirListJob* dirlist_job;
|
||||
std::vector<FileInfoJob*> fileinfoJobs_;
|
||||
FileSystemInfoJob* fsInfoJob_;
|
||||
|
||||
std::shared_ptr<VolumeManager> volumeManager_;
|
||||
|
||||
/* for file monitor */
|
||||
bool has_idle_reload_handler;
|
||||
bool has_idle_update_handler;
|
||||
std::vector<FilePath> paths_to_add;
|
||||
std::vector<FilePath> paths_to_update;
|
||||
std::vector<FilePath> paths_to_del;
|
||||
// GSList* pending_jobs;
|
||||
bool pending_change_notify;
|
||||
bool filesystem_info_pending;
|
||||
|
||||
bool wants_incremental;
|
||||
bool stop_emission; /* don't set it 1 bit to not lock other bits */
|
||||
|
||||
std::unordered_map<const std::string, std::shared_ptr<const FileInfo>, std::hash<std::string>> files_;
|
||||
|
||||
/* filesystem info - set in query thread, read in main */
|
||||
uint64_t fs_total_size;
|
||||
uint64_t fs_free_size;
|
||||
GCancellablePtr fs_size_cancellable;
|
||||
|
||||
bool has_fs_info : 1;
|
||||
bool defer_content_test : 1;
|
||||
|
||||
static std::unordered_map<FilePath, std::weak_ptr<Folder>, FilePathHash> cache_;
|
||||
static FilePath cutFilesDirPath_;
|
||||
static FilePath lastCutFilesDirPath_;
|
||||
static std::shared_ptr<const HashSet> cutFilesHashSet_;
|
||||
static std::mutex mutex_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __LIBFM_QT_FM2_FOLDER_H__
|
@ -0,0 +1,137 @@
|
||||
#ifndef GIOPTRS_H
|
||||
#define GIOPTRS_H
|
||||
// define smart pointers for GIO data types
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
#include "gobjectptr.h"
|
||||
#include "cstrptr.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
typedef GObjectPtr<GFile> GFilePtr;
|
||||
typedef GObjectPtr<GFileInfo> GFileInfoPtr;
|
||||
typedef GObjectPtr<GFileMonitor> GFileMonitorPtr;
|
||||
typedef GObjectPtr<GCancellable> GCancellablePtr;
|
||||
typedef GObjectPtr<GFileEnumerator> GFileEnumeratorPtr;
|
||||
|
||||
typedef GObjectPtr<GInputStream> GInputStreamPtr;
|
||||
typedef GObjectPtr<GFileInputStream> GFileInputStreamPtr;
|
||||
typedef GObjectPtr<GOutputStream> GOutputStreamPtr;
|
||||
typedef GObjectPtr<GFileOutputStream> GFileOutputStreamPtr;
|
||||
|
||||
typedef GObjectPtr<GIcon> GIconPtr;
|
||||
|
||||
typedef GObjectPtr<GVolumeMonitor> GVolumeMonitorPtr;
|
||||
typedef GObjectPtr<GVolume> GVolumePtr;
|
||||
typedef GObjectPtr<GMount> GMountPtr;
|
||||
|
||||
typedef GObjectPtr<GAppInfo> GAppInfoPtr;
|
||||
|
||||
|
||||
class GErrorPtr {
|
||||
public:
|
||||
GErrorPtr(): err_{nullptr} {
|
||||
}
|
||||
|
||||
GErrorPtr(GError*&& err) noexcept: err_{err} {
|
||||
err = nullptr;
|
||||
}
|
||||
|
||||
GErrorPtr(const GErrorPtr& other) = delete;
|
||||
|
||||
GErrorPtr(GErrorPtr&& other) noexcept: err_{other.err_} {
|
||||
other.err_ = nullptr;
|
||||
}
|
||||
|
||||
GErrorPtr(std::uint32_t domain, unsigned int code, const char* msg):
|
||||
GErrorPtr{g_error_new_literal(domain, code, msg)} {
|
||||
}
|
||||
|
||||
GErrorPtr(std::uint32_t domain, unsigned int code, const QString& msg):
|
||||
GErrorPtr{domain, code, msg.toUtf8().constData()} {
|
||||
}
|
||||
|
||||
~GErrorPtr() {
|
||||
reset();
|
||||
}
|
||||
|
||||
std::uint32_t domain() const {
|
||||
if(err_ != nullptr) {
|
||||
return err_->domain;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int code() const {
|
||||
if(err_ != nullptr) {
|
||||
return err_->code;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString message() const {
|
||||
if(err_ != nullptr) {
|
||||
return err_->message;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if(err_) {
|
||||
g_error_free(err_);
|
||||
}
|
||||
err_ = nullptr;
|
||||
}
|
||||
|
||||
GError* get() const {
|
||||
return err_;
|
||||
}
|
||||
|
||||
GErrorPtr& operator = (const GErrorPtr& other) = delete;
|
||||
|
||||
GErrorPtr& operator = (GErrorPtr&& other) noexcept {
|
||||
reset();
|
||||
err_ = other.err_;
|
||||
other.err_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GErrorPtr& operator = (GError*&& err) {
|
||||
reset();
|
||||
err_ = err;
|
||||
err_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GError** operator&() {
|
||||
return &err_;
|
||||
}
|
||||
|
||||
GError* operator->() {
|
||||
return err_;
|
||||
}
|
||||
|
||||
bool operator == (const GErrorPtr& other) const {
|
||||
return err_ == other.err_;
|
||||
}
|
||||
|
||||
bool operator == (GError* err) const {
|
||||
return err_ == err;
|
||||
}
|
||||
|
||||
bool operator != (std::nullptr_t) const {
|
||||
return err_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return err_ != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
GError* err_;
|
||||
};
|
||||
|
||||
} //namespace Fm
|
||||
|
||||
#endif // GIOPTRS_H
|
@ -0,0 +1,104 @@
|
||||
#ifndef FM2_GOBJECTPTR_H
|
||||
#define FM2_GOBJECTPTR_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <cstddef>
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
template <typename T>
|
||||
class LIBFM_QT_API GObjectPtr {
|
||||
public:
|
||||
|
||||
explicit GObjectPtr(): gobj_{nullptr} {
|
||||
}
|
||||
|
||||
explicit GObjectPtr(T* gobj, bool add_ref = true): gobj_{gobj} {
|
||||
if(gobj_ != nullptr && add_ref)
|
||||
g_object_ref(gobj_);
|
||||
}
|
||||
|
||||
GObjectPtr(const GObjectPtr& other): gobj_{other.gobj_ ? reinterpret_cast<T*>(g_object_ref(other.gobj_)) : nullptr} {
|
||||
}
|
||||
|
||||
GObjectPtr(GObjectPtr&& other) noexcept: gobj_{other.release()} {
|
||||
}
|
||||
|
||||
~GObjectPtr() {
|
||||
if(gobj_ != nullptr)
|
||||
g_object_unref(gobj_);
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return gobj_;
|
||||
}
|
||||
|
||||
T* release() {
|
||||
T* tmp = gobj_;
|
||||
gobj_ = nullptr;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if(gobj_ != nullptr)
|
||||
g_object_unref(gobj_);
|
||||
gobj_ = nullptr;
|
||||
}
|
||||
|
||||
GObjectPtr& operator = (const GObjectPtr& other) {
|
||||
if (*this == other)
|
||||
return *this;
|
||||
|
||||
if(gobj_ != nullptr)
|
||||
g_object_unref(gobj_);
|
||||
gobj_ = other.gobj_ ? reinterpret_cast<T*>(g_object_ref(other.gobj_)) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GObjectPtr& operator = (GObjectPtr&& other) noexcept {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
|
||||
if(gobj_ != nullptr)
|
||||
g_object_unref(gobj_);
|
||||
gobj_ = other.release();
|
||||
return *this;
|
||||
}
|
||||
|
||||
GObjectPtr& operator = (T* gobj) {
|
||||
if (*this == gobj)
|
||||
return *this;
|
||||
|
||||
if(gobj_ != nullptr)
|
||||
g_object_unref(gobj_);
|
||||
gobj_ = gobj ? reinterpret_cast<T*>(g_object_ref(gobj_)) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator == (const GObjectPtr& other) const {
|
||||
return gobj_ == other.gobj_;
|
||||
}
|
||||
|
||||
bool operator == (T* gobj) const {
|
||||
return gobj_ == gobj;
|
||||
}
|
||||
|
||||
bool operator != (std::nullptr_t) const {
|
||||
return gobj_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return gobj_ != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable T* gobj_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_GOBJECTPTR_H
|
@ -0,0 +1,138 @@
|
||||
#include "iconinfo.h"
|
||||
#include "iconinfo_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::unordered_map<GIcon*, std::shared_ptr<IconInfo>, IconInfo::GIconHash, IconInfo::GIconEqual> IconInfo::cache_;
|
||||
std::mutex IconInfo::mutex_;
|
||||
QIcon IconInfo::fallbackQicon_;
|
||||
|
||||
static const char* fallbackIconNames[] = {
|
||||
"unknown",
|
||||
"application-octet-stream",
|
||||
"application-x-generic",
|
||||
"text-x-generic",
|
||||
nullptr
|
||||
};
|
||||
|
||||
IconInfo::IconInfo(const char* name):
|
||||
gicon_{g_themed_icon_new(name), false} {
|
||||
}
|
||||
|
||||
IconInfo::IconInfo(const GIconPtr gicon):
|
||||
gicon_{std::move(gicon)} {
|
||||
}
|
||||
|
||||
IconInfo::~IconInfo() {
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<const IconInfo> IconInfo::fromName(const char* name) {
|
||||
GObjectPtr<GIcon> gicon{g_themed_icon_new(name), false};
|
||||
return fromGIcon(gicon);
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<const IconInfo> IconInfo::fromGIcon(GIconPtr gicon) {
|
||||
if(Q_LIKELY(gicon)) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
auto it = cache_.find(gicon.get());
|
||||
if(it != cache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
// not found in the cache, create a new entry for it.
|
||||
auto icon = std::make_shared<IconInfo>(std::move(gicon));
|
||||
cache_.insert(std::make_pair(icon->gicon_.get(), icon));
|
||||
return icon;
|
||||
}
|
||||
return std::shared_ptr<const IconInfo>{};
|
||||
}
|
||||
|
||||
void IconInfo::updateQIcons() {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
fallbackQicon_ = QIcon();
|
||||
for(auto& elem: cache_) {
|
||||
auto& info = elem.second;
|
||||
info->internalQicon_ = QIcon();
|
||||
}
|
||||
}
|
||||
|
||||
QIcon IconInfo::qicon(const bool& transparent) const {
|
||||
if(Q_LIKELY(!transparent)) {
|
||||
if(Q_UNLIKELY(qicon_.isNull() && gicon_)) {
|
||||
if(!G_IS_FILE_ICON(gicon_.get())) {
|
||||
qicon_ = QIcon(new IconEngine{shared_from_this()});
|
||||
}
|
||||
else {
|
||||
qicon_ = internalQicon_;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // transparent == true
|
||||
if(Q_UNLIKELY(qiconTransparent_.isNull() && gicon_)) {
|
||||
if(!G_IS_FILE_ICON(gicon_.get())) {
|
||||
qiconTransparent_ = QIcon(new IconEngine{shared_from_this(), transparent});
|
||||
}
|
||||
else {
|
||||
qiconTransparent_ = internalQicon_;
|
||||
}
|
||||
}
|
||||
}
|
||||
return !transparent ? qicon_ : qiconTransparent_;
|
||||
}
|
||||
|
||||
QIcon IconInfo::qiconFromNames(const char* const* names) {
|
||||
const gchar* const* name;
|
||||
// qDebug("names: %p", names);
|
||||
for(name = names; *name; ++name) {
|
||||
// qDebug("icon name=%s", *name);
|
||||
QIcon qicon = QIcon::fromTheme(*name);
|
||||
if(!qicon.isNull()) {
|
||||
return qicon;
|
||||
}
|
||||
}
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
std::forward_list<std::shared_ptr<const IconInfo>> IconInfo::emblems() const {
|
||||
std::forward_list<std::shared_ptr<const IconInfo>> result;
|
||||
if(hasEmblems()) {
|
||||
const GList* emblems_glist = g_emblemed_icon_get_emblems(G_EMBLEMED_ICON(gicon_.get()));
|
||||
for(auto l = emblems_glist; l; l = l->next) {
|
||||
auto gemblem = G_EMBLEM(l->data);
|
||||
GIconPtr gemblem_icon{g_emblem_get_icon(gemblem), true};
|
||||
result.emplace_front(fromGIcon(gemblem_icon));
|
||||
}
|
||||
result.reverse();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QIcon IconInfo::internalQicon() const {
|
||||
if(Q_UNLIKELY(internalQicon_.isNull())) {
|
||||
GIcon* gicon = gicon_.get();
|
||||
if(G_IS_EMBLEMED_ICON(gicon_.get())) {
|
||||
gicon = g_emblemed_icon_get_icon(G_EMBLEMED_ICON(gicon));
|
||||
}
|
||||
if(G_IS_THEMED_ICON(gicon)) {
|
||||
const gchar* const* names = g_themed_icon_get_names(G_THEMED_ICON(gicon));
|
||||
internalQicon_ = qiconFromNames(names);
|
||||
}
|
||||
else if(G_IS_FILE_ICON(gicon)) {
|
||||
GFile* file = g_file_icon_get_file(G_FILE_ICON(gicon));
|
||||
CStrPtr fpath{g_file_get_path(file)};
|
||||
internalQicon_ = QIcon(fpath.get());
|
||||
}
|
||||
|
||||
// fallback to default icon
|
||||
if(Q_UNLIKELY(internalQicon_.isNull())) {
|
||||
if(Q_UNLIKELY(fallbackQicon_.isNull())) {
|
||||
fallbackQicon_ = qiconFromNames(fallbackIconNames);
|
||||
}
|
||||
internalQicon_ = fallbackQicon_;
|
||||
}
|
||||
}
|
||||
return internalQicon_;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* fm-icon.h
|
||||
*
|
||||
* Copyright 2009 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2013 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
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __FM2_ICON_INFO_H__
|
||||
#define __FM2_ICON_INFO_H__
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <gio/gio.h>
|
||||
#include "gioptrs.h"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <forward_list>
|
||||
#include <QIcon>
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API IconInfo: public std::enable_shared_from_this<IconInfo> {
|
||||
public:
|
||||
friend class IconEngine;
|
||||
|
||||
explicit IconInfo() {}
|
||||
|
||||
explicit IconInfo(const char* name);
|
||||
|
||||
explicit IconInfo(const GIconPtr gicon);
|
||||
|
||||
~IconInfo();
|
||||
|
||||
static std::shared_ptr<const IconInfo> fromName(const char* name);
|
||||
|
||||
static std::shared_ptr<const IconInfo> fromGIcon(GIconPtr gicon);
|
||||
|
||||
static std::shared_ptr<const IconInfo> fromGIcon(GIcon* gicon) {
|
||||
return fromGIcon(GIconPtr{gicon, true});
|
||||
}
|
||||
|
||||
static void updateQIcons();
|
||||
|
||||
GIconPtr gicon() const {
|
||||
return gicon_;
|
||||
}
|
||||
|
||||
QIcon qicon(const bool& transparent = false) const;
|
||||
|
||||
bool hasEmblems() const {
|
||||
return G_IS_EMBLEMED_ICON(gicon_.get());
|
||||
}
|
||||
|
||||
std::forward_list<std::shared_ptr<const IconInfo>> emblems() const;
|
||||
|
||||
bool isValid() const {
|
||||
return gicon_ != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static QIcon qiconFromNames(const char* const* names);
|
||||
|
||||
// actual QIcon loaded by QIcon::fromTheme
|
||||
QIcon internalQicon() const;
|
||||
|
||||
struct GIconHash {
|
||||
std::size_t operator()(GIcon* gicon) const {
|
||||
return g_icon_hash(gicon);
|
||||
}
|
||||
};
|
||||
|
||||
struct GIconEqual {
|
||||
bool operator()(GIcon* gicon1, GIcon* gicon2) const {
|
||||
return g_icon_equal(gicon1, gicon2);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
GIconPtr gicon_;
|
||||
mutable QIcon qicon_;
|
||||
mutable QIcon qiconTransparent_;
|
||||
mutable QIcon internalQicon_;
|
||||
|
||||
static std::unordered_map<GIcon*, std::shared_ptr<IconInfo>, GIconHash, GIconEqual> cache_;
|
||||
static std::mutex mutex_;
|
||||
static QIcon fallbackQicon_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<const Fm::IconInfo>)
|
||||
|
||||
#endif /* __FM2_ICON_INFO_H__ */
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_ICONENGINE_H
|
||||
#define FM_ICONENGINE_H
|
||||
|
||||
#include <QIconEngine>
|
||||
#include <QPainter>
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "iconinfo.h"
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class IconEngine: public QIconEngine {
|
||||
public:
|
||||
|
||||
IconEngine(std::shared_ptr<const Fm::IconInfo> info, const bool& transparent = false);
|
||||
|
||||
~IconEngine();
|
||||
|
||||
virtual QSize actualSize(const QSize& size, QIcon::Mode mode, QIcon::State state) override;
|
||||
|
||||
// not supported
|
||||
virtual void addFile(const QString& /*fileName*/, const QSize& /*size*/, QIcon::Mode /*mode*/, QIcon::State /*state*/) override {}
|
||||
|
||||
// not supported
|
||||
virtual void addPixmap(const QPixmap& /*pixmap*/, QIcon::Mode /*mode*/, QIcon::State /*state*/) override {}
|
||||
|
||||
virtual QIconEngine* clone() const override;
|
||||
|
||||
virtual QString key() const override;
|
||||
|
||||
virtual void paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) override;
|
||||
|
||||
virtual QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) override;
|
||||
|
||||
virtual void virtual_hook(int id, void* data) override;
|
||||
|
||||
private:
|
||||
std::weak_ptr<const Fm::IconInfo> info_;
|
||||
bool transparent_;
|
||||
};
|
||||
|
||||
IconEngine::IconEngine(std::shared_ptr<const IconInfo> info, const bool& transparent):
|
||||
info_{info}, transparent_{transparent} {
|
||||
}
|
||||
|
||||
IconEngine::~IconEngine() {
|
||||
}
|
||||
|
||||
QSize IconEngine::actualSize(const QSize& size, QIcon::Mode mode, QIcon::State state) {
|
||||
auto info = info_.lock();
|
||||
return info ? info->internalQicon().actualSize(size, mode, state) : QSize{};
|
||||
}
|
||||
|
||||
QIconEngine* IconEngine::clone() const {
|
||||
IconEngine* engine = new IconEngine(info_.lock());
|
||||
return engine;
|
||||
}
|
||||
|
||||
QString IconEngine::key() const {
|
||||
return QStringLiteral("Fm::IconEngine");
|
||||
}
|
||||
|
||||
void IconEngine::paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) {
|
||||
auto info = info_.lock();
|
||||
if(info) {
|
||||
if(transparent_) {
|
||||
painter->save();
|
||||
painter->setOpacity(0.45);
|
||||
}
|
||||
info->internalQicon().paint(painter, rect, Qt::AlignCenter, mode, state);
|
||||
if(transparent_) {
|
||||
painter->restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap IconEngine::pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) {
|
||||
auto info = info_.lock();
|
||||
return info ? info->internalQicon().pixmap(size, mode, state) : QPixmap{};
|
||||
}
|
||||
|
||||
void IconEngine::virtual_hook(int id, void* data) {
|
||||
auto info = info_.lock();
|
||||
switch(id) {
|
||||
case QIconEngine::AvailableSizesHook: {
|
||||
auto* args = reinterpret_cast<QIconEngine::AvailableSizesArgument*>(data);
|
||||
args->sizes = info ? info->internalQicon().availableSizes(args->mode, args->state) : QList<QSize>{};
|
||||
break;
|
||||
}
|
||||
case QIconEngine::IconNameHook: {
|
||||
QString* result = reinterpret_cast<QString*>(data);
|
||||
*result = info ? info->internalQicon().name() : QString{};
|
||||
break;
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
||||
case QIconEngine::IsNullHook: {
|
||||
bool* result = reinterpret_cast<bool*>(data);
|
||||
*result = info ? info->internalQicon().isNull() : true;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM_ICONENGINE_H
|
@ -0,0 +1,57 @@
|
||||
#include "job.h"
|
||||
#include "job_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
Job::Job():
|
||||
paused_{false},
|
||||
cancellable_{g_cancellable_new(), false},
|
||||
cancellableHandler_{g_signal_connect(cancellable_.get(), "cancelled", G_CALLBACK(_onCancellableCancelled), this)} {
|
||||
}
|
||||
|
||||
Job::~Job() {
|
||||
if(cancellable_) {
|
||||
g_cancellable_disconnect(cancellable_.get(), cancellableHandler_);
|
||||
}
|
||||
}
|
||||
|
||||
void Job::runAsync(QThread::Priority priority) {
|
||||
auto thread = new JobThread(this);
|
||||
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
if(autoDelete()) {
|
||||
connect(this, &Job::finished, this, &Job::deleteLater);
|
||||
}
|
||||
thread->start(priority);
|
||||
}
|
||||
|
||||
void Job::cancel() {
|
||||
g_cancellable_cancel(cancellable_.get());
|
||||
}
|
||||
|
||||
void Job::run() {
|
||||
exec();
|
||||
Q_EMIT finished();
|
||||
}
|
||||
|
||||
|
||||
Job::ErrorAction Job::emitError(const GErrorPtr &err, Job::ErrorSeverity severity) {
|
||||
ErrorAction response = ErrorAction::CONTINUE;
|
||||
// if the error is already handled, don't emit it.
|
||||
if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_FAILED_HANDLED) {
|
||||
return response;
|
||||
}
|
||||
Q_EMIT error(err, severity, response);
|
||||
|
||||
if(severity == ErrorSeverity::CRITICAL || response == ErrorAction::ABORT) {
|
||||
cancel();
|
||||
}
|
||||
else if(response == ErrorAction::RETRY ) {
|
||||
/* If the job is already cancelled, retry is not allowed. */
|
||||
if(isCancelled() || (err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_CANCELLED)) {
|
||||
response = ErrorAction::CONTINUE;
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LIBFM_QT_FM_JOB_H__
|
||||
#define __LIBFM_QT_FM_JOB_H__
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QThread>
|
||||
#include <QRunnable>
|
||||
#include <memory>
|
||||
#include <gio/gio.h>
|
||||
#include "gobjectptr.h"
|
||||
#include "gioptrs.h"
|
||||
#include "../libfmqtglobals.h"
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
/*
|
||||
* Fm::Job can be used in several different modes.
|
||||
* 1. run with QThreadPool::start()
|
||||
* 2. call runAsync(), which will create a new QThread and move the object to the thread.
|
||||
* 3. create a new QThread, and connect the started() signal to the slot Job::run()
|
||||
* 4. Directly call Job::run(), which executes synchrounously as a normal blocking call
|
||||
*/
|
||||
|
||||
class LIBFM_QT_API Job: public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
enum class ErrorAction{
|
||||
CONTINUE,
|
||||
RETRY,
|
||||
ABORT
|
||||
};
|
||||
|
||||
enum class ErrorSeverity {
|
||||
UNKNOWN,
|
||||
WARNING,
|
||||
MILD,
|
||||
MODERATE,
|
||||
SEVERE,
|
||||
CRITICAL
|
||||
};
|
||||
|
||||
explicit Job();
|
||||
|
||||
virtual ~Job();
|
||||
|
||||
bool isCancelled() const {
|
||||
return g_cancellable_is_cancelled(cancellable_.get());
|
||||
}
|
||||
|
||||
void runAsync(QThread::Priority priority = QThread::InheritPriority);
|
||||
|
||||
bool pause();
|
||||
|
||||
void resume();
|
||||
|
||||
const GCancellablePtr& cancellable() const {
|
||||
return cancellable_;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void cancelled();
|
||||
|
||||
void finished();
|
||||
|
||||
// this signal should be connected with Qt::BlockingQueuedConnection
|
||||
void error(const GErrorPtr& err, ErrorSeverity severity, ErrorAction& response);
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
void cancel();
|
||||
|
||||
void run() override;
|
||||
|
||||
protected:
|
||||
ErrorAction emitError(const GErrorPtr& err, ErrorSeverity severity = ErrorSeverity::MODERATE);
|
||||
|
||||
// all derived job subclasses should do their work in this method.
|
||||
virtual void exec() = 0;
|
||||
|
||||
private:
|
||||
static void _onCancellableCancelled(GCancellable* cancellable, Job* _this) {
|
||||
_this->onCancellableCancelled(cancellable);
|
||||
}
|
||||
|
||||
void onCancellableCancelled(GCancellable* /*cancellable*/) {
|
||||
Q_EMIT cancelled();
|
||||
}
|
||||
|
||||
private:
|
||||
bool paused_;
|
||||
GCancellablePtr cancellable_;
|
||||
gulong cancellableHandler_;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // __LIBFM_QT_FM_JOB_H__
|
@ -0,0 +1,26 @@
|
||||
#ifndef JOB_P_H
|
||||
#define JOB_P_H
|
||||
|
||||
#include <QThread>
|
||||
#include "job.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class JobThread: public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
JobThread(Job* job): job_{job} {
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void run() override {
|
||||
job_->run();
|
||||
}
|
||||
|
||||
Job* job_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // JOB_P_H
|
@ -0,0 +1,64 @@
|
||||
#include "mimetype.h"
|
||||
#include <cstring>
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::unordered_map<const char*, std::shared_ptr<const MimeType>, CStrHash, CStrEqual> MimeType::cache_;
|
||||
std::mutex MimeType::mutex_;
|
||||
|
||||
std::shared_ptr<const MimeType> MimeType::inodeDirectory_; // inode/directory
|
||||
std::shared_ptr<const MimeType> MimeType::inodeShortcut_; // inode/x-shortcut
|
||||
std::shared_ptr<const MimeType> MimeType::inodeMountPoint_; // inode/mount-point
|
||||
std::shared_ptr<const MimeType> MimeType::desktopEntry_; // application/x-desktop
|
||||
|
||||
|
||||
MimeType::MimeType(const char* typeName):
|
||||
name_{g_strdup(typeName)},
|
||||
desc_{nullptr} {
|
||||
|
||||
GObjectPtr<GIcon> gicon{g_content_type_get_icon(typeName), false};
|
||||
if(strcmp(typeName, "inode/directory") == 0)
|
||||
g_themed_icon_prepend_name(G_THEMED_ICON(gicon.get()), "folder");
|
||||
else if(g_content_type_can_be_executable(typeName))
|
||||
g_themed_icon_append_name(G_THEMED_ICON(gicon.get()), "application-x-executable");
|
||||
|
||||
icon_ = IconInfo::fromGIcon(gicon);
|
||||
}
|
||||
|
||||
MimeType::~MimeType () {
|
||||
}
|
||||
|
||||
//static
|
||||
std::shared_ptr<const MimeType> MimeType::fromName(const char* typeName) {
|
||||
std::shared_ptr<const MimeType> ret;
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto it = cache_.find(typeName);
|
||||
if(it == cache_.end()) {
|
||||
ret = std::make_shared<MimeType>(typeName);
|
||||
cache_.insert(std::make_pair(ret->name_.get(), ret));
|
||||
}
|
||||
else {
|
||||
ret = it->second;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<const MimeType> MimeType::guessFromFileName(const char* fileName) {
|
||||
gboolean uncertain;
|
||||
/* let skip scheme and host from non-native names */
|
||||
auto uri_scheme = g_strstr_len(fileName, -1, "://");
|
||||
if(uri_scheme)
|
||||
fileName = strchr(uri_scheme + 3, '/');
|
||||
if(fileName == nullptr)
|
||||
fileName = "unknown";
|
||||
auto type = CStrPtr{g_content_type_guess(fileName, nullptr, 0, &uncertain)};
|
||||
return fromName(type.get());
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* fm-mime-type.h
|
||||
*
|
||||
* Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _FM2_MIME_TYPE_H_
|
||||
#define _FM2_MIME_TYPE_H_
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <forward_list>
|
||||
#include <functional>
|
||||
|
||||
#include "cstrptr.h"
|
||||
#include "gobjectptr.h"
|
||||
#include "iconinfo.h"
|
||||
#include "thumbnailer.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API MimeType {
|
||||
public:
|
||||
friend class Thumbnailer;
|
||||
|
||||
explicit MimeType(const char* typeName);
|
||||
|
||||
MimeType() = delete;
|
||||
|
||||
~MimeType();
|
||||
|
||||
std::shared_ptr<const Thumbnailer> firstThumbnailer() const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
return thumbnailers_.empty() ? nullptr : thumbnailers_.front();
|
||||
}
|
||||
|
||||
void forEachThumbnailer(std::function<bool(const std::shared_ptr<const Thumbnailer>&)> func) const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
for(auto& thumbnailer: thumbnailers_) {
|
||||
if(func(thumbnailer)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<const IconInfo>& icon() const {
|
||||
return icon_;
|
||||
}
|
||||
|
||||
const char* name() const {
|
||||
return name_.get();
|
||||
}
|
||||
|
||||
const char* desc() const {
|
||||
if(!desc_) {
|
||||
desc_ = CStrPtr{g_content_type_get_description(name_.get())};
|
||||
}
|
||||
return desc_.get();
|
||||
}
|
||||
|
||||
static std::shared_ptr<const MimeType> fromName(const char* typeName);
|
||||
|
||||
static std::shared_ptr<const MimeType> guessFromFileName(const char* fileName);
|
||||
|
||||
bool isUnknownType() const {
|
||||
return g_content_type_is_unknown(name_.get());
|
||||
}
|
||||
|
||||
bool isDesktopEntry() const {
|
||||
return this == desktopEntry().get();
|
||||
}
|
||||
|
||||
bool isText() const {
|
||||
return g_content_type_is_a(name_.get(), "text/plain");
|
||||
}
|
||||
|
||||
bool isImage() const {
|
||||
return !std::strncmp("image/", name_.get(), 6);
|
||||
}
|
||||
|
||||
bool isMountable() const {
|
||||
return this == inodeMountPoint().get();
|
||||
}
|
||||
|
||||
bool isShortcut() const {
|
||||
return this == inodeShortcut().get();
|
||||
}
|
||||
|
||||
bool isDir() const {
|
||||
return this == inodeDirectory().get();
|
||||
}
|
||||
|
||||
bool canBeExecutable() const {
|
||||
return g_content_type_can_be_executable(name_.get());
|
||||
}
|
||||
|
||||
static std::shared_ptr<const MimeType> inodeDirectory() { // inode/directory
|
||||
if(!inodeDirectory_)
|
||||
inodeDirectory_ = fromName("inode/directory");
|
||||
return inodeDirectory_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<const MimeType> inodeShortcut() { // inode/x-shortcut
|
||||
if(!inodeShortcut_)
|
||||
inodeShortcut_ = fromName("inode/x-shortcut");
|
||||
return inodeShortcut_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<const MimeType> inodeMountPoint() { // inode/mount-point
|
||||
if(!inodeMountPoint_)
|
||||
inodeMountPoint_ = fromName("inode/mount-point");
|
||||
return inodeMountPoint_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<const MimeType> desktopEntry() { // application/x-desktop
|
||||
if(!desktopEntry_)
|
||||
desktopEntry_ = fromName("application/x-desktop");
|
||||
return desktopEntry_;
|
||||
}
|
||||
|
||||
private:
|
||||
void removeThumbnailer(std::shared_ptr<const Thumbnailer>& thumbnailer) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
thumbnailers_.remove(thumbnailer);
|
||||
}
|
||||
|
||||
void addThumbnailer(std::shared_ptr<const Thumbnailer> thumbnailer) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
thumbnailers_.push_front(std::move(thumbnailer));
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<const IconInfo> icon_;
|
||||
CStrPtr name_;
|
||||
mutable CStrPtr desc_;
|
||||
std::forward_list<std::shared_ptr<const Thumbnailer>> thumbnailers_;
|
||||
static std::unordered_map<const char*, std::shared_ptr<const MimeType>, CStrHash, CStrEqual> cache_;
|
||||
static std::mutex mutex_;
|
||||
|
||||
static std::shared_ptr<const MimeType> inodeDirectory_; // inode/directory
|
||||
static std::shared_ptr<const MimeType> inodeShortcut_; // inode/x-shortcut
|
||||
static std::shared_ptr<const MimeType> inodeMountPoint_; // inode/mount-point
|
||||
static std::shared_ptr<const MimeType> desktopEntry_; // application/x-desktop
|
||||
};
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif
|
@ -0,0 +1,127 @@
|
||||
#include "terminal.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2, 28, 0) && !HAVE_DECL_ENVIRON
|
||||
extern char** environ;
|
||||
#endif
|
||||
|
||||
static void child_setup(gpointer user_data) {
|
||||
/* Move child to grandparent group so it will not die with parent */
|
||||
setpgid(0, (pid_t)(gsize)user_data);
|
||||
}
|
||||
|
||||
bool launchTerminal(const char* programName, const FilePath& workingDir, Fm::GErrorPtr& error) {
|
||||
/* read system terminals file */
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(!g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/terminals.list", G_KEY_FILE_NONE, &error)) {
|
||||
g_key_file_free(kf);
|
||||
return false;
|
||||
}
|
||||
auto launch = g_key_file_get_string(kf, programName, "launch", nullptr);
|
||||
auto desktop_id = g_key_file_get_string(kf, programName, "desktop_id", nullptr);
|
||||
|
||||
GDesktopAppInfo* appinfo = nullptr;
|
||||
if(desktop_id) {
|
||||
appinfo = g_desktop_app_info_new(desktop_id);
|
||||
}
|
||||
|
||||
const gchar* cmd;
|
||||
gchar* _cmd = nullptr;
|
||||
if(appinfo) {
|
||||
cmd = g_app_info_get_commandline(G_APP_INFO(appinfo));
|
||||
}
|
||||
else if(launch) {
|
||||
cmd = _cmd = g_strdup_printf("%s %s", programName, launch);
|
||||
}
|
||||
else {
|
||||
cmd = programName;
|
||||
}
|
||||
|
||||
#if 0 // FIXME: what's this?
|
||||
if(custom_args) {
|
||||
cmd = g_strdup_printf("%s %s", cmd, custom_args);
|
||||
g_free(_cmd);
|
||||
_cmd = (char*)cmd;
|
||||
}
|
||||
#endif
|
||||
|
||||
char** argv;
|
||||
int argc;
|
||||
if(!g_shell_parse_argv(cmd, &argc, &argv, nullptr)) {
|
||||
argv = nullptr;
|
||||
}
|
||||
g_free(_cmd);
|
||||
|
||||
if(appinfo) {
|
||||
g_object_unref(appinfo);
|
||||
}
|
||||
if(!argv) { /* parsing failed */
|
||||
return false;
|
||||
}
|
||||
char** envp;
|
||||
#if GLIB_CHECK_VERSION(2, 28, 0)
|
||||
envp = g_get_environ();
|
||||
#else
|
||||
envp = g_strdupv(environ);
|
||||
#endif
|
||||
|
||||
auto dir = workingDir ? workingDir.localPath() : nullptr;
|
||||
if(dir) {
|
||||
#if GLIB_CHECK_VERSION(2, 32, 0)
|
||||
envp = g_environ_setenv(envp, "PWD", dir.get(), TRUE);
|
||||
#else
|
||||
char** env = envp;
|
||||
|
||||
if(env) while(*env != nullptr) {
|
||||
if(strncmp(*env, "PWD=", 4) == 0) {
|
||||
break;
|
||||
}
|
||||
env++;
|
||||
}
|
||||
if(env == nullptr || *env == nullptr) {
|
||||
gint length;
|
||||
|
||||
length = envp ? g_strv_length(envp) : 0;
|
||||
envp = g_renew(gchar*, envp, length + 2);
|
||||
env = &envp[length];
|
||||
env[1] = nullptr;
|
||||
}
|
||||
else {
|
||||
g_free(*env);
|
||||
}
|
||||
*env = g_strdup_printf("PWD=%s", dir);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ret = g_spawn_async(dir.get(), argv, envp, G_SPAWN_SEARCH_PATH,
|
||||
child_setup, (gpointer)(gsize)getpgid(getppid()),
|
||||
nullptr, &error);
|
||||
g_strfreev(argv);
|
||||
g_strfreev(envp);
|
||||
g_key_file_free(kf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<CStrPtr> allKnownTerminals() {
|
||||
std::vector<CStrPtr> terminals;
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/terminals.list", G_KEY_FILE_NONE, nullptr)) {
|
||||
gsize n;
|
||||
auto programs = g_key_file_get_groups(kf, &n);
|
||||
terminals.reserve(n);
|
||||
for(auto name = programs; *name; ++name) {
|
||||
terminals.emplace_back(*name);
|
||||
}
|
||||
g_free(programs);
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
return terminals;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,17 @@
|
||||
#ifndef TERMINAL_H
|
||||
#define TERMINAL_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "gioptrs.h"
|
||||
#include "filepath.h"
|
||||
#include <vector>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
LIBFM_QT_API bool launchTerminal(const char* programName, const FilePath& workingDir, GErrorPtr& error);
|
||||
|
||||
LIBFM_QT_API std::vector<CStrPtr> allKnownTerminals();
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // TERMINAL_H
|
@ -0,0 +1,140 @@
|
||||
#include "thumbnailer.h"
|
||||
#include "mimetype.h"
|
||||
#include <string>
|
||||
#include <QDebug>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::mutex Thumbnailer::mutex_;
|
||||
std::vector<std::shared_ptr<Thumbnailer>> Thumbnailer::allThumbnailers_;
|
||||
|
||||
Thumbnailer::Thumbnailer(const char* id, GKeyFile* kf):
|
||||
id_{g_strdup(id)},
|
||||
try_exec_{g_key_file_get_string(kf, "Thumbnailer Entry", "TryExec", nullptr)},
|
||||
exec_{g_key_file_get_string(kf, "Thumbnailer Entry", "Exec", nullptr)} {
|
||||
}
|
||||
|
||||
CStrPtr Thumbnailer::commandForUri(const char* uri, const char* output_file, guint size) const {
|
||||
if(exec_) {
|
||||
/* FIXME: how to handle TryExec? */
|
||||
|
||||
/* parse the command line and do required substitutions according to:
|
||||
* http://developer.gnome.org/integration-guide/stable/thumbnailer.html.en
|
||||
*/
|
||||
GString* cmd_line = g_string_sized_new(1024);
|
||||
const char* p;
|
||||
for(p = exec_.get(); *p; ++p) {
|
||||
if(G_LIKELY(*p != '%')) {
|
||||
g_string_append_c(cmd_line, *p);
|
||||
}
|
||||
else {
|
||||
char* quoted;
|
||||
++p;
|
||||
switch(*p) {
|
||||
case '\0':
|
||||
break;
|
||||
case 's':
|
||||
g_string_append_printf(cmd_line, "%d", size);
|
||||
break;
|
||||
case 'i': {
|
||||
char* src_path = g_filename_from_uri(uri, nullptr, nullptr);
|
||||
if(src_path) {
|
||||
quoted = g_shell_quote(src_path);
|
||||
g_string_append(cmd_line, quoted);
|
||||
g_free(quoted);
|
||||
g_free(src_path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'u':
|
||||
quoted = g_shell_quote(uri);
|
||||
g_string_append(cmd_line, quoted);
|
||||
g_free(quoted);
|
||||
break;
|
||||
case 'o':
|
||||
g_string_append(cmd_line, output_file);
|
||||
break;
|
||||
default:
|
||||
g_string_append_c(cmd_line, '%');
|
||||
if(*p != '%') {
|
||||
g_string_append_c(cmd_line, *p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return CStrPtr{g_string_free(cmd_line, FALSE)};
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Thumbnailer::run(const char* uri, const char* output_file, int size) const {
|
||||
auto cmd = commandForUri(uri, output_file, size);
|
||||
qDebug() << cmd.get();
|
||||
int status;
|
||||
bool ret = g_spawn_command_line_sync(cmd.get(), nullptr, nullptr, &status, nullptr);
|
||||
return ret && status == 0;
|
||||
}
|
||||
|
||||
static void find_thumbnailers_in_data_dir(std::unordered_map<std::string, const char*>& hash, const char* data_dir) {
|
||||
CStrPtr dir_path{g_build_filename(data_dir, "thumbnailers", nullptr)};
|
||||
GDir* dir = g_dir_open(dir_path.get(), 0, nullptr);
|
||||
if(dir) {
|
||||
const char* basename;
|
||||
while((basename = g_dir_read_name(dir)) != nullptr) {
|
||||
/* we only want filenames with .thumbnailer extension */
|
||||
if(G_LIKELY(g_str_has_suffix(basename, ".thumbnailer"))) {
|
||||
hash.insert(std::make_pair(basename, data_dir));
|
||||
}
|
||||
}
|
||||
g_dir_close(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void Thumbnailer::loadAll() {
|
||||
const gchar* const* data_dirs = g_get_system_data_dirs();
|
||||
const gchar* const* data_dir;
|
||||
|
||||
/* use a temporary hash table to collect thumbnailer basenames
|
||||
* key: basename of thumbnailer entry file
|
||||
* value: data dir the thumbnailer entry file is in */
|
||||
std::unordered_map<std::string, const char*> hash;
|
||||
|
||||
/* load user-specific thumbnailers */
|
||||
find_thumbnailers_in_data_dir(hash, g_get_user_data_dir());
|
||||
|
||||
/* load system-wide thumbnailers */
|
||||
for(data_dir = data_dirs; *data_dir; ++data_dir) {
|
||||
find_thumbnailers_in_data_dir(hash, *data_dir);
|
||||
}
|
||||
|
||||
/* load all found thumbnailers */
|
||||
if(!hash.empty()) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
for(auto& item: hash) {
|
||||
auto& base_name = item.first;
|
||||
auto& dir_path = item.second;
|
||||
CStrPtr file_path{g_build_filename(dir_path, "thumbnailers", base_name.c_str(), nullptr)};
|
||||
if(g_key_file_load_from_file(kf, file_path.get(), G_KEY_FILE_NONE, nullptr)) {
|
||||
auto thumbnailer = std::make_shared<Thumbnailer>(base_name.c_str(), kf);
|
||||
char** mime_types = g_key_file_get_string_list(kf, "Thumbnailer Entry", "MimeType", nullptr, nullptr);
|
||||
if(mime_types && thumbnailer->exec_) {
|
||||
for(char** name = mime_types; *name; ++name) {
|
||||
auto mime_type = MimeType::fromName(*name);
|
||||
if(mime_type) {
|
||||
thumbnailer->mimeTypes_.push_back(mime_type);
|
||||
std::const_pointer_cast<MimeType>(mime_type)->addThumbnailer(thumbnailer);
|
||||
}
|
||||
}
|
||||
g_strfreev(mime_types);
|
||||
}
|
||||
allThumbnailers_.push_back(std::move(thumbnailer));
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,37 @@
|
||||
#ifndef FM2_THUMBNAILER_H
|
||||
#define FM2_THUMBNAILER_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "cstrptr.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class MimeType;
|
||||
|
||||
class LIBFM_QT_API Thumbnailer {
|
||||
public:
|
||||
explicit Thumbnailer(const char *id, GKeyFile *kf);
|
||||
|
||||
CStrPtr commandForUri(const char* uri, const char* output_file, guint size) const;
|
||||
|
||||
bool run(const char* uri, const char* output_file, int size) const;
|
||||
|
||||
static void loadAll();
|
||||
|
||||
private:
|
||||
CStrPtr id_;
|
||||
CStrPtr try_exec_; /* FIXME: is this useful? */
|
||||
CStrPtr exec_;
|
||||
std::vector<std::shared_ptr<const MimeType>> mimeTypes_;
|
||||
|
||||
static std::mutex mutex_;
|
||||
static std::vector<std::shared_ptr<Thumbnailer>> allThumbnailers_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_THUMBNAILER_H
|
@ -0,0 +1,266 @@
|
||||
#include "thumbnailjob.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <libexif/exif-loader.h>
|
||||
#include <QImageReader>
|
||||
#include <QDir>
|
||||
#include "thumbnailer.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
QThreadPool* ThumbnailJob::threadPool_ = nullptr;
|
||||
|
||||
bool ThumbnailJob::localFilesOnly_ = true;
|
||||
int ThumbnailJob::maxThumbnailFileSize_ = 0;
|
||||
|
||||
ThumbnailJob::ThumbnailJob(FileInfoList files, int size):
|
||||
files_{std::move(files)},
|
||||
size_{size},
|
||||
md5Calc_{g_checksum_new(G_CHECKSUM_MD5)} {
|
||||
}
|
||||
|
||||
ThumbnailJob::~ThumbnailJob() {
|
||||
g_checksum_free(md5Calc_);
|
||||
// qDebug("delete ThumbnailJob");
|
||||
}
|
||||
|
||||
void ThumbnailJob::exec() {
|
||||
for(auto& file: files_) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
auto image = loadForFile(file);
|
||||
Q_EMIT thumbnailLoaded(file, size_, image);
|
||||
results_.emplace_back(std::move(image));
|
||||
}
|
||||
}
|
||||
|
||||
QImage ThumbnailJob::readImageFromStream(GInputStream* stream, size_t len) {
|
||||
// FIXME: should we set a limit here? Otherwise if len is too large, we can run out of memory.
|
||||
std::unique_ptr<unsigned char[]> buffer{new unsigned char[len]}; // allocate enough buffer
|
||||
unsigned char* pbuffer = buffer.get();
|
||||
size_t totalReadSize = 0;
|
||||
while(!isCancelled() && totalReadSize < len) {
|
||||
size_t bytesToRead = totalReadSize + 4096 > len ? len - totalReadSize : 4096;
|
||||
gssize readSize = g_input_stream_read(stream, pbuffer, bytesToRead, cancellable_.get(), nullptr);
|
||||
if(readSize == 0) { // end of file
|
||||
break;
|
||||
}
|
||||
else if(readSize == -1) { // error
|
||||
return QImage();
|
||||
}
|
||||
totalReadSize += readSize;
|
||||
pbuffer += readSize;
|
||||
}
|
||||
QImage image;
|
||||
image.loadFromData(buffer.get(), totalReadSize);
|
||||
return image;
|
||||
}
|
||||
|
||||
QImage ThumbnailJob::loadForFile(const std::shared_ptr<const FileInfo> &file) {
|
||||
if(!file->canThumbnail()) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
// thumbnails are stored in $XDG_CACHE_HOME/thumbnails/large|normal|failed
|
||||
QString thumbnailDir{g_get_user_cache_dir()};
|
||||
thumbnailDir += "/thumbnails/";
|
||||
|
||||
// don't make thumbnails for files inside the thumbnail directory
|
||||
if(FilePath::fromLocalPath(thumbnailDir.toLocal8Bit().constData()).isParentOf(file->dirPath())) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
const char* subdir = size_ > 128 ? "large" : "normal";
|
||||
thumbnailDir += subdir;
|
||||
|
||||
// generate base name of the thumbnail => {md5 of uri}.png
|
||||
auto origPath = file->path();
|
||||
auto uri = origPath.uri();
|
||||
|
||||
char thumbnailName[32 + 5];
|
||||
// calculate md5 hash for the uri of the original file
|
||||
g_checksum_update(md5Calc_, reinterpret_cast<const unsigned char*>(uri.get()), -1);
|
||||
memcpy(thumbnailName, g_checksum_get_string(md5Calc_), 32);
|
||||
mempcpy(thumbnailName + 32, ".png", 5);
|
||||
g_checksum_reset(md5Calc_); // reset the checksum calculator for next use
|
||||
|
||||
QString thumbnailFilename = thumbnailDir;
|
||||
thumbnailFilename += '/';
|
||||
thumbnailFilename += thumbnailName;
|
||||
// qDebug() << "thumbnail:" << file->getName().c_str() << thumbnailFilename;
|
||||
|
||||
// try to load the thumbnail file if it exists
|
||||
QImage thumbnail{thumbnailFilename};
|
||||
if(thumbnail.isNull() || isThumbnailOutdated(file, thumbnail)) {
|
||||
// the existing thumbnail cannot be loaded, generate a new one
|
||||
|
||||
// create the thumbnail dir as needd (FIXME: Qt file I/O is slow)
|
||||
QDir().mkpath(thumbnailDir);
|
||||
|
||||
thumbnail = generateThumbnail(file, origPath, uri.get(), thumbnailFilename);
|
||||
}
|
||||
// resize to the size we need
|
||||
if(thumbnail.width() > size_ || thumbnail.height() > size_) {
|
||||
thumbnail = thumbnail.scaled(size_, size_, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
bool ThumbnailJob::isSupportedImageType(const std::shared_ptr<const MimeType>& mimeType) const {
|
||||
if(mimeType->isImage()) {
|
||||
auto supportedTypes = QImageReader::supportedMimeTypes();
|
||||
auto found = std::find(supportedTypes.cbegin(), supportedTypes.cend(), mimeType->name());
|
||||
if(found != supportedTypes.cend())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ThumbnailJob::isThumbnailOutdated(const std::shared_ptr<const FileInfo>& file, const QImage &thumbnail) const {
|
||||
QString thumb_mtime = thumbnail.text("Thumb::MTime");
|
||||
return (thumb_mtime.isEmpty() || thumb_mtime.toInt() != file->mtime());
|
||||
}
|
||||
|
||||
bool ThumbnailJob::readJpegExif(GInputStream *stream, QImage& thumbnail, int& rotate_degrees) {
|
||||
/* try to extract thumbnails embedded in jpeg files */
|
||||
ExifLoader* exif_loader = exif_loader_new();
|
||||
while(!isCancelled()) {
|
||||
unsigned char buf[4096];
|
||||
gssize read_size = g_input_stream_read(stream, buf, 4096, cancellable_.get(), nullptr);
|
||||
if(read_size <= 0) { // EOF or error
|
||||
break;
|
||||
}
|
||||
if(exif_loader_write(exif_loader, buf, read_size) == 0) {
|
||||
break; // no more EXIF data
|
||||
}
|
||||
}
|
||||
ExifData* exif_data = exif_loader_get_data(exif_loader);
|
||||
exif_loader_unref(exif_loader);
|
||||
if(exif_data) {
|
||||
/* reference for EXIF orientation tag:
|
||||
* http://www.impulseadventure.com/photo/exif-orientation.html */
|
||||
ExifEntry* orient_ent = exif_data_get_entry(exif_data, EXIF_TAG_ORIENTATION);
|
||||
if(orient_ent) { /* orientation flag found in EXIF */
|
||||
gushort orient;
|
||||
ExifByteOrder bo = exif_data_get_byte_order(exif_data);
|
||||
/* bo == EXIF_BYTE_ORDER_INTEL ; */
|
||||
orient = exif_get_short(orient_ent->data, bo);
|
||||
switch(orient) {
|
||||
case 1: /* no rotation */
|
||||
rotate_degrees = 0;
|
||||
break;
|
||||
case 8:
|
||||
rotate_degrees = 90;
|
||||
break;
|
||||
case 3:
|
||||
rotate_degrees = 180;
|
||||
break;
|
||||
case 6:
|
||||
rotate_degrees = 270;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(exif_data->data) { // if an embedded thumbnail is available, load it
|
||||
thumbnail.loadFromData(exif_data->data, exif_data->size);
|
||||
}
|
||||
exif_data_unref(exif_data);
|
||||
}
|
||||
return !thumbnail.isNull();
|
||||
}
|
||||
|
||||
QImage ThumbnailJob::generateThumbnail(const std::shared_ptr<const FileInfo>& file, const FilePath& origPath, const char* uri, const QString& thumbnailFilename) {
|
||||
QImage result;
|
||||
auto mime_type = file->mimeType();
|
||||
if(isSupportedImageType(mime_type)) {
|
||||
GFileInputStreamPtr ins{g_file_read(origPath.gfile().get(), cancellable_.get(), nullptr), false};
|
||||
if(!ins)
|
||||
return QImage();
|
||||
bool fromExif = false;
|
||||
int rotate_degrees = 0;
|
||||
if(strcmp(mime_type->name(), "image/jpeg") == 0) { // if this is a jpeg file
|
||||
// try to get the thumbnail embedded in EXIF data
|
||||
if(readJpegExif(G_INPUT_STREAM(ins.get()), result, rotate_degrees)) {
|
||||
fromExif = true;
|
||||
}
|
||||
}
|
||||
if(!fromExif) { // not able to generate a thumbnail from the EXIF data
|
||||
// load the original file and do the scaling ourselves
|
||||
g_seekable_seek(G_SEEKABLE(ins.get()), 0, G_SEEK_SET, cancellable_.get(), nullptr);
|
||||
result = readImageFromStream(G_INPUT_STREAM(ins.get()), file->size());
|
||||
}
|
||||
g_input_stream_close(G_INPUT_STREAM(ins.get()), nullptr, nullptr);
|
||||
|
||||
if(!result.isNull()) { // the image is successfully loaded
|
||||
// scale the image as needed
|
||||
int target_size = size_ > 128 ? 256 : 128;
|
||||
|
||||
// only scale the original image if it's too large
|
||||
if(result.width() > target_size || result.height() > target_size) {
|
||||
result = result.scaled(target_size, target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
if(rotate_degrees != 0) {
|
||||
// degree values are 0, 90, 180, and 270 counterclockwise.
|
||||
// In Qt, QMatrix does rotation counterclockwise as well.
|
||||
// However, because the y axis of widget coordinate system is downward,
|
||||
// the real effect of the coordinate transformation becomes clockwise rotation.
|
||||
// So we need to use (360 - degree) here.
|
||||
// Quote from QMatrix API doc:
|
||||
// Note that if you apply a QMatrix to a point defined in widget
|
||||
// coordinates, the direction of the rotation will be clockwise because
|
||||
// the y-axis points downwards.
|
||||
result = result.transformed(QMatrix().rotate(360 - rotate_degrees));
|
||||
}
|
||||
|
||||
// save the generated thumbnail to disk (don't save png thumbnails for JPEG EXIF thumbnails since loading them is cheap)
|
||||
if(!fromExif) {
|
||||
result.setText("Thumb::MTime", QString::number(file->mtime()));
|
||||
result.setText("Thumb::URI", uri);
|
||||
result.save(thumbnailFilename, "PNG");
|
||||
}
|
||||
// qDebug() << "save thumbnail:" << thumbnailFilename;
|
||||
}
|
||||
}
|
||||
else { // the image format is not supported, try to find an external thumbnailer
|
||||
// try all available external thumbnailers for it until sucess
|
||||
int target_size = size_ > 128 ? 256 : 128;
|
||||
file->mimeType()->forEachThumbnailer([&](const std::shared_ptr<const Thumbnailer>& thumbnailer) {
|
||||
if(thumbnailer->run(uri, thumbnailFilename.toLocal8Bit().constData(), target_size)) {
|
||||
result = QImage(thumbnailFilename);
|
||||
}
|
||||
return !result.isNull(); // return true on success, and forEachThumbnailer() will stop.
|
||||
});
|
||||
|
||||
if(!result.isNull()) {
|
||||
// Some thumbnailers did not write the proper metadata required by the xdg spec to the output (such as evince-thumbnailer)
|
||||
// Here we waste some time to fix them so next time we don't need to re-generate these thumbnails. :-(
|
||||
bool changed = false;
|
||||
if(Q_UNLIKELY(result.text("Thumb::MTime").isEmpty())) {
|
||||
result.setText("Thumb::MTime", QString::number(file->mtime()));
|
||||
changed = true;
|
||||
}
|
||||
if(Q_UNLIKELY(result.text("Thumb::URI").isEmpty())) {
|
||||
result.setText("Thumb::URI", uri);
|
||||
changed = true;
|
||||
}
|
||||
if(Q_UNLIKELY(changed)) {
|
||||
// save the modified PNG file containing metadata to a file.
|
||||
result.save(thumbnailFilename, "PNG");
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QThreadPool* ThumbnailJob::threadPool() {
|
||||
if(Q_UNLIKELY(threadPool_ == nullptr)) {
|
||||
threadPool_ = new QThreadPool();
|
||||
threadPool_->setMaxThreadCount(1);
|
||||
}
|
||||
return threadPool_;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,144 @@
|
||||
#include "totalsizejob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
static const char query_str[] =
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE","
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME","
|
||||
G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL","
|
||||
G_FILE_ATTRIBUTE_STANDARD_SIZE","
|
||||
G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE","
|
||||
G_FILE_ATTRIBUTE_ID_FILESYSTEM;
|
||||
|
||||
|
||||
TotalSizeJob::TotalSizeJob(FilePathList paths, Flags flags):
|
||||
paths_{std::move(paths)},
|
||||
flags_{flags},
|
||||
totalSize_{0},
|
||||
totalOndiskSize_{0},
|
||||
fileCount_{0},
|
||||
dest_fs_id{nullptr} {
|
||||
}
|
||||
|
||||
|
||||
void TotalSizeJob::exec(FilePath path, GFileInfoPtr inf) {
|
||||
GFileType type;
|
||||
const char* fs_id;
|
||||
bool descend;
|
||||
|
||||
_retry_query_info:
|
||||
if(!inf) {
|
||||
GErrorPtr err;
|
||||
inf = GFileInfoPtr {
|
||||
g_file_query_info(path.gfile().get(), query_str,
|
||||
(flags_ & FOLLOW_LINKS) ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(!inf) {
|
||||
ErrorAction act = emitError( err, ErrorSeverity::MILD);
|
||||
err = nullptr;
|
||||
if(act == ErrorAction::RETRY) {
|
||||
goto _retry_query_info;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
type = g_file_info_get_file_type(inf.get());
|
||||
descend = true;
|
||||
|
||||
++fileCount_;
|
||||
/* SF bug #892: dir file size is not relevant in the summary */
|
||||
if(type != G_FILE_TYPE_DIRECTORY) {
|
||||
totalSize_ += g_file_info_get_size(inf.get());
|
||||
}
|
||||
totalOndiskSize_ += g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE);
|
||||
|
||||
/* prepare for moving across different devices */
|
||||
if(flags_ & PREPARE_MOVE) {
|
||||
fs_id = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM);
|
||||
fs_id = g_intern_string(fs_id);
|
||||
if(g_strcmp0(fs_id, dest_fs_id) != 0) {
|
||||
/* files on different device requires an additional 'delete' for the source file. */
|
||||
++totalSize_; /* this is for the additional delete */
|
||||
++totalOndiskSize_;
|
||||
++fileCount_;
|
||||
}
|
||||
else {
|
||||
descend = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(type == G_FILE_TYPE_DIRECTORY) {
|
||||
#if 0
|
||||
FmPath* fm_path = fm_path_new_for_gfile(gf);
|
||||
/* check if we need to decends into the dir. */
|
||||
/* trash:/// doesn't support deleting files recursively */
|
||||
if(flags & PREPARE_DELETE && fm_path_is_trash(fm_path) && ! fm_path_is_trash_root(fm_path)) {
|
||||
descend = false;
|
||||
}
|
||||
else {
|
||||
/* only descends into files on the same filesystem */
|
||||
if(flags & FM_DC_JOB_SAME_FS) {
|
||||
fs_id = g_file_info_get_attribute_string(inf, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
|
||||
descend = (g_strcmp0(fs_id, dest_fs_id) == 0);
|
||||
}
|
||||
}
|
||||
fm_path_unref(fm_path);
|
||||
#endif
|
||||
inf = nullptr;
|
||||
|
||||
if(descend) {
|
||||
_retry_enum_children:
|
||||
GErrorPtr err;
|
||||
auto enu = GFileEnumeratorPtr {
|
||||
g_file_enumerate_children(path.gfile().get(), query_str,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(enu) {
|
||||
while(!isCancelled()) {
|
||||
inf = GFileInfoPtr{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
|
||||
if(inf) {
|
||||
FilePath child = path.child(g_file_info_get_name(inf.get()));
|
||||
exec(std::move(child), std::move(inf));
|
||||
}
|
||||
else {
|
||||
if(err) { /* error! */
|
||||
/* ErrorAction::RETRY is not supported */
|
||||
emitError( err, ErrorSeverity::MILD);
|
||||
err = nullptr;
|
||||
}
|
||||
else {
|
||||
/* EOF is reached, do nothing. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_file_enumerator_close(enu.get(), nullptr, nullptr);
|
||||
}
|
||||
else {
|
||||
ErrorAction act = emitError( err, ErrorSeverity::MILD);
|
||||
err = nullptr;
|
||||
if(act == ErrorAction::RETRY) {
|
||||
goto _retry_enum_children;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TotalSizeJob::exec() {
|
||||
for(auto& path : paths_) {
|
||||
exec(path, GFileInfoPtr{});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,56 @@
|
||||
#ifndef FM2_TOTALSIZEJOB_H
|
||||
#define FM2_TOTALSIZEJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
#include "filepath.h"
|
||||
#include <cstdint>
|
||||
#include "gioptrs.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API TotalSizeJob : public Fm::FileOperationJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Flags {
|
||||
DEFAULT = 0,
|
||||
FOLLOW_LINKS = 1 << 0,
|
||||
SAME_FS = 1 << 1,
|
||||
PREPARE_MOVE = 1 << 2,
|
||||
PREPARE_DELETE = 1 << 3
|
||||
};
|
||||
|
||||
explicit TotalSizeJob(FilePathList paths = FilePathList{}, Flags flags = DEFAULT);
|
||||
|
||||
std::uint64_t totalSize() const {
|
||||
return totalSize_;
|
||||
}
|
||||
|
||||
std::uint64_t totalOnDiskSize() const {
|
||||
return totalOndiskSize_;
|
||||
}
|
||||
|
||||
unsigned int fileCount() const {
|
||||
return fileCount_;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
void exec(FilePath path, GFileInfoPtr inf);
|
||||
|
||||
private:
|
||||
FilePathList paths_;
|
||||
|
||||
int flags_;
|
||||
std::uint64_t totalSize_;
|
||||
std::uint64_t totalOndiskSize_;
|
||||
unsigned int fileCount_;
|
||||
const char* dest_fs_id;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_TOTALSIZEJOB_H
|
@ -0,0 +1,73 @@
|
||||
#include "trashjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
TrashJob::TrashJob(const FilePathList& paths): paths_{paths} {
|
||||
}
|
||||
|
||||
TrashJob::TrashJob(const FilePathList&& paths): paths_{paths} {
|
||||
}
|
||||
|
||||
void TrashJob::exec() {
|
||||
setTotalAmount(paths_.size(), paths_.size());
|
||||
Q_EMIT preparedToRun();
|
||||
|
||||
/* FIXME: we shouldn't trash a file already in trash:/// */
|
||||
for(auto& path : paths_) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
setCurrentFile(path);
|
||||
|
||||
for(;;) {
|
||||
GErrorPtr err;
|
||||
GFile* gf = path.gfile().get();
|
||||
GFileInfoPtr inf{
|
||||
g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
|
||||
bool ret = FALSE;
|
||||
if(fm_config->no_usb_trash) {
|
||||
err.reset();
|
||||
GMountPtr mnt{g_file_find_enclosing_mount(gf, nullptr, &err), false};
|
||||
if(mnt) {
|
||||
ret = g_mount_can_unmount(mnt.get()); /* TRUE if it's removable media */
|
||||
if(ret) {
|
||||
unsupportedFiles_.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!ret) {
|
||||
err.reset();
|
||||
ret = g_file_trash(gf, cancellable().get(), &err);
|
||||
}
|
||||
if(!ret) {
|
||||
/* if trashing is not supported by the file system */
|
||||
if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_NOT_SUPPORTED) {
|
||||
unsupportedFiles_.push_back(path);
|
||||
}
|
||||
else {
|
||||
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
||||
if(act == ErrorAction::RETRY) {
|
||||
err.reset();
|
||||
}
|
||||
else if(act == ErrorAction::ABORT) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addFinishedAmount(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,31 @@
|
||||
#ifndef FM2_TRASHJOB_H
|
||||
#define FM2_TRASHJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
#include "filepath.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API TrashJob : public Fm::FileOperationJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TrashJob(const FilePathList& paths);
|
||||
explicit TrashJob(const FilePathList&& paths);
|
||||
|
||||
FilePathList unsupportedFiles() const {
|
||||
return unsupportedFiles_;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
FilePathList paths_;
|
||||
FilePathList unsupportedFiles_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_TRASHJOB_H
|
@ -0,0 +1,132 @@
|
||||
#include "untrashjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
UntrashJob::UntrashJob() {
|
||||
|
||||
}
|
||||
|
||||
static const char trash_query[] =
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE","
|
||||
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME","
|
||||
G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL","
|
||||
G_FILE_ATTRIBUTE_STANDARD_SIZE","
|
||||
G_FILE_ATTRIBUTE_UNIX_BLOCKS","
|
||||
G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE","
|
||||
G_FILE_ATTRIBUTE_ID_FILESYSTEM","
|
||||
"trash::orig-path";
|
||||
|
||||
bool UntrashJob::ensure_parent_dir(GFile* orig_path) {
|
||||
GFile* parent = g_file_get_parent(orig_path);
|
||||
gboolean ret = g_file_query_exists(parent, cancellable().get());
|
||||
if(!ret) {
|
||||
GErrorPtr err;
|
||||
_retry_mkdir:
|
||||
if(!g_file_make_directory_with_parents(parent, cancellable().get(), &err)) {
|
||||
if(!isCancelled()) {
|
||||
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
||||
err = nullptr;
|
||||
if(act == ErrorAction::RETRY) {
|
||||
goto _retry_mkdir;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret = TRUE;
|
||||
}
|
||||
}
|
||||
g_object_unref(parent);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void UntrashJob::exec() {
|
||||
#if 0
|
||||
gboolean ret = TRUE;
|
||||
GList* l;
|
||||
GError* err = nullptr;
|
||||
FmJob* fmjob = FM_JOB(job);
|
||||
job->total = fm_path_list_get_length(job->srcs);
|
||||
fm_file_ops_job_emit_prepared(job);
|
||||
|
||||
l = fm_path_list_peek_head_link(job->srcs);
|
||||
for(; !fm_job_is_cancelled(fmjob) && l; l = l->next) {
|
||||
GFile* gf;
|
||||
GFileInfo* inf;
|
||||
FmPath* path = FM_PATH(l->data);
|
||||
if(!fm_path_is_trash(path)) {
|
||||
continue;
|
||||
}
|
||||
gf = fm_path_to_gfile(path);
|
||||
_retry_get_orig_path:
|
||||
inf = g_file_query_info(gf, trash_query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, fm_job_get_cancellable(fmjob), &err);
|
||||
if(inf) {
|
||||
const char* orig_path_str = g_file_info_get_attribute_byte_string(inf, "trash::orig-path");
|
||||
fm_file_ops_job_emit_cur_file(job, g_file_info_get_display_name(inf));
|
||||
|
||||
if(orig_path_str) {
|
||||
/* FIXME: what if orig_path_str is a relative path?
|
||||
* This is actually allowed by the horrible trash spec. */
|
||||
GFile* orig_path = fm_file_new_for_commandline_arg(orig_path_str);
|
||||
FmFolder* src_folder = fm_folder_find_by_path(fm_path_get_parent(path));
|
||||
FmPath* orig_fm_path = fm_path_new_for_gfile(orig_path);
|
||||
FmFolder* dst_folder = fm_folder_find_by_path(fm_path_get_parent(orig_fm_path));
|
||||
fm_path_unref(orig_fm_path);
|
||||
/* ensure the existence of parent folder. */
|
||||
if(ensure_parent_dir(fmjob, orig_path)) {
|
||||
ret = _fm_file_ops_job_move_file(job, gf, inf, orig_path, path, src_folder, dst_folder);
|
||||
}
|
||||
if(src_folder) {
|
||||
g_object_unref(src_folder);
|
||||
}
|
||||
if(dst_folder) {
|
||||
g_object_unref(dst_folder);
|
||||
}
|
||||
g_object_unref(orig_path);
|
||||
}
|
||||
else {
|
||||
ErrorAction act;
|
||||
|
||||
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
_("Cannot untrash file '%s': original path not known"),
|
||||
g_file_info_get_display_name(inf));
|
||||
act = emitError( err, ErrorSeverity::MODERATE);
|
||||
g_clear_error(&err);
|
||||
if(act == ErrorAction::ABORT) {
|
||||
g_object_unref(inf);
|
||||
g_object_unref(gf);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
g_object_unref(inf);
|
||||
}
|
||||
else {
|
||||
char* basename = g_file_get_basename(gf);
|
||||
char* disp = basename ? g_filename_display_name(basename) : nullptr;
|
||||
g_free(basename);
|
||||
/* FIXME: translate it */
|
||||
fm_file_ops_job_emit_cur_file(job, disp ? disp : "(invalid file)");
|
||||
g_free(disp);
|
||||
|
||||
if(err) {
|
||||
ErrorAction act = emitError( err, ErrorSeverity::MODERATE);
|
||||
g_error_free(err);
|
||||
err = nullptr;
|
||||
if(act == ErrorAction::RETRY) {
|
||||
goto _retry_get_orig_path;
|
||||
}
|
||||
else if(act == ErrorAction::ABORT) {
|
||||
g_object_unref(gf);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_object_unref(gf);
|
||||
++job->finished;
|
||||
fm_file_ops_job_emit_percent(job);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,22 @@
|
||||
#ifndef FM2_UNTRASHJOB_H
|
||||
#define FM2_UNTRASHJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API UntrashJob : public Fm::FileOperationJob {
|
||||
public:
|
||||
explicit UntrashJob();
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
bool ensure_parent_dir(GFile *orig_path);
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_UNTRASHJOB_H
|
@ -0,0 +1,47 @@
|
||||
#include "userinfocache.h"
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
UserInfoCache* UserInfoCache::globalInstance_ = nullptr;
|
||||
std::mutex UserInfoCache::mutex_;
|
||||
|
||||
UserInfoCache::UserInfoCache() : QObject() {
|
||||
}
|
||||
|
||||
const std::shared_ptr<const UserInfo>& UserInfoCache::userFromId(uid_t uid) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
auto it = users_.find(uid);
|
||||
if(it != users_.end())
|
||||
return it->second;
|
||||
std::shared_ptr<const UserInfo> user;
|
||||
auto pw = getpwuid(uid);
|
||||
if(pw) {
|
||||
user = std::make_shared<UserInfo>(uid, pw->pw_name, pw->pw_gecos);
|
||||
}
|
||||
return (users_[uid] = user);
|
||||
}
|
||||
|
||||
const std::shared_ptr<const GroupInfo>& UserInfoCache::groupFromId(gid_t gid) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
auto it = groups_.find(gid);
|
||||
if(it != groups_.end())
|
||||
return it->second;
|
||||
std::shared_ptr<const GroupInfo> group;
|
||||
auto gr = getgrgid(gid);
|
||||
if(gr) {
|
||||
group = std::make_shared<GroupInfo>(gid, gr->gr_name);
|
||||
}
|
||||
return (groups_[gid] = group);
|
||||
}
|
||||
|
||||
// static
|
||||
UserInfoCache* UserInfoCache::globalInstance() {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
if(!globalInstance_)
|
||||
globalInstance_ = new UserInfoCache();
|
||||
return globalInstance_;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,82 @@
|
||||
#ifndef FM2_USERINFOCACHE_H
|
||||
#define FM2_USERINFOCACHE_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <QObject>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <sys/types.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API UserInfo {
|
||||
public:
|
||||
explicit UserInfo(uid_t uid, const char* name, const char* realName):
|
||||
uid_{uid}, name_{name}, realName_{realName} {
|
||||
}
|
||||
|
||||
uid_t uid() const {
|
||||
return uid_;
|
||||
}
|
||||
|
||||
const QString& name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
const QString& realName() const {
|
||||
return realName_;
|
||||
}
|
||||
|
||||
private:
|
||||
uid_t uid_;
|
||||
QString name_;
|
||||
QString realName_;
|
||||
|
||||
};
|
||||
|
||||
class LIBFM_QT_API GroupInfo {
|
||||
public:
|
||||
explicit GroupInfo(gid_t gid, const char* name): gid_{gid}, name_{name} {
|
||||
}
|
||||
|
||||
gid_t gid() const {
|
||||
return gid_;
|
||||
}
|
||||
|
||||
const QString& name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
private:
|
||||
gid_t gid_;
|
||||
QString name_;
|
||||
};
|
||||
|
||||
// FIXME: handle file changes
|
||||
|
||||
class LIBFM_QT_API UserInfoCache : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UserInfoCache();
|
||||
|
||||
const std::shared_ptr<const UserInfo>& userFromId(uid_t uid);
|
||||
|
||||
const std::shared_ptr<const GroupInfo>& groupFromId(gid_t gid);
|
||||
|
||||
static UserInfoCache* globalInstance();
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed();
|
||||
|
||||
private:
|
||||
std::unordered_map<uid_t, std::shared_ptr<const UserInfo>> users_;
|
||||
std::unordered_map<gid_t, std::shared_ptr<const GroupInfo>> groups_;
|
||||
static UserInfoCache* globalInstance_;
|
||||
static std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_USERINFOCACHE_H
|
@ -0,0 +1,111 @@
|
||||
#include "volumemanager.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::mutex VolumeManager::mutex_;
|
||||
std::weak_ptr<VolumeManager> VolumeManager::globalInstance_;
|
||||
|
||||
VolumeManager::VolumeManager():
|
||||
QObject(),
|
||||
monitor_{g_volume_monitor_get(), false} {
|
||||
|
||||
// connect gobject signal handlers
|
||||
g_signal_connect(monitor_.get(), "volume-added", G_CALLBACK(_onGVolumeAdded), this);
|
||||
g_signal_connect(monitor_.get(), "volume-removed", G_CALLBACK(_onGVolumeRemoved), this);
|
||||
g_signal_connect(monitor_.get(), "volume-changed", G_CALLBACK(_onGVolumeChanged), this);
|
||||
|
||||
g_signal_connect(monitor_.get(), "mount-added", G_CALLBACK(_onGMountAdded), this);
|
||||
g_signal_connect(monitor_.get(), "mount-removed", G_CALLBACK(_onGMountRemoved), this);
|
||||
g_signal_connect(monitor_.get(), "mount-changed", G_CALLBACK(_onGMountChanged), this);
|
||||
|
||||
// g_get_volume_monitor() is a slow blocking call, so call it in a low priority thread
|
||||
auto job = new GetGVolumeMonitorJob();
|
||||
job->setAutoDelete(true);
|
||||
connect(job, &GetGVolumeMonitorJob::finished, this, &VolumeManager::onGetGVolumeMonitorFinished, Qt::BlockingQueuedConnection);
|
||||
job->runAsync(QThread::LowPriority);
|
||||
}
|
||||
|
||||
VolumeManager::~VolumeManager() {
|
||||
if(monitor_) {
|
||||
g_signal_handlers_disconnect_by_data(monitor_.get(), this);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<VolumeManager> VolumeManager::globalInstance() {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
auto mon = globalInstance_.lock();
|
||||
if(mon == nullptr) {
|
||||
mon = std::make_shared<VolumeManager>();
|
||||
globalInstance_ = mon;
|
||||
}
|
||||
return mon;
|
||||
}
|
||||
|
||||
void VolumeManager::onGetGVolumeMonitorFinished() {
|
||||
auto job = static_cast<GetGVolumeMonitorJob*>(sender());
|
||||
monitor_ = std::move(job->monitor_);
|
||||
GList* vols = g_volume_monitor_get_volumes(monitor_.get());
|
||||
for(GList* l = vols; l != nullptr; l = l->next) {
|
||||
volumes_.push_back(Volume{G_VOLUME(l->data), false});
|
||||
Q_EMIT volumeAdded(volumes_.back());
|
||||
}
|
||||
g_list_free(vols);
|
||||
|
||||
GList* mnts = g_volume_monitor_get_mounts(monitor_.get());
|
||||
for(GList* l = mnts; l != nullptr; l = l->next) {
|
||||
mounts_.push_back(Mount{G_MOUNT(l->data), false});
|
||||
Q_EMIT mountAdded(mounts_.back());
|
||||
}
|
||||
g_list_free(mnts);
|
||||
}
|
||||
|
||||
void VolumeManager::onGVolumeAdded(GVolume* vol) {
|
||||
if(std::find(volumes_.cbegin(), volumes_.cend(), vol) != volumes_.cend())
|
||||
return;
|
||||
volumes_.push_back(Volume{vol, true});
|
||||
Q_EMIT volumeAdded(volumes_.back());
|
||||
}
|
||||
|
||||
void VolumeManager::onGVolumeRemoved(GVolume* vol) {
|
||||
auto it = std::find(volumes_.begin(), volumes_.end(), vol);
|
||||
if(it == volumes_.end())
|
||||
return;
|
||||
Q_EMIT volumeRemoved(*it);
|
||||
volumes_.erase(it);
|
||||
}
|
||||
|
||||
void VolumeManager::onGVolumeChanged(GVolume* vol) {
|
||||
auto it = std::find(volumes_.begin(), volumes_.end(), vol);
|
||||
if(it == volumes_.end())
|
||||
return;
|
||||
Q_EMIT volumeChanged(*it);
|
||||
}
|
||||
|
||||
void VolumeManager::onGMountAdded(GMount* mnt) {
|
||||
if(std::find(mounts_.cbegin(), mounts_.cend(), mnt) != mounts_.cend())
|
||||
return;
|
||||
mounts_.push_back(Mount{mnt, true});
|
||||
Q_EMIT mountAdded(mounts_.back());
|
||||
}
|
||||
|
||||
void VolumeManager::onGMountRemoved(GMount* mnt) {
|
||||
auto it = std::find(mounts_.begin(), mounts_.end(), mnt);
|
||||
if(it == mounts_.end())
|
||||
return;
|
||||
Q_EMIT mountRemoved(*it);
|
||||
mounts_.erase(it);
|
||||
}
|
||||
|
||||
void VolumeManager::onGMountChanged(GMount* mnt) {
|
||||
auto it = std::find(mounts_.begin(), mounts_.end(), mnt);
|
||||
if(it == mounts_.end())
|
||||
return;
|
||||
Q_EMIT mountChanged(*it);
|
||||
}
|
||||
|
||||
void VolumeManager::GetGVolumeMonitorJob::exec() {
|
||||
monitor_ = GVolumeMonitorPtr{g_volume_monitor_get(), false};
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,237 @@
|
||||
#ifndef FM2_VOLUMEMANAGER_H
|
||||
#define FM2_VOLUMEMANAGER_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <QObject>
|
||||
#include <gio/gio.h>
|
||||
#include "gioptrs.h"
|
||||
#include "filepath.h"
|
||||
#include "iconinfo.h"
|
||||
#include "job.h"
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API Volume: public GVolumePtr {
|
||||
public:
|
||||
|
||||
explicit Volume(GVolume* gvol, bool addRef): GVolumePtr{gvol, addRef} {
|
||||
}
|
||||
|
||||
explicit Volume(GVolumePtr gvol): GVolumePtr{std::move(gvol)} {
|
||||
}
|
||||
|
||||
CStrPtr name() const {
|
||||
return CStrPtr{g_volume_get_name(get())};
|
||||
}
|
||||
|
||||
CStrPtr uuid() const {
|
||||
return CStrPtr{g_volume_get_uuid(get())};
|
||||
}
|
||||
|
||||
std::shared_ptr<const IconInfo> icon() const {
|
||||
return IconInfo::fromGIcon(GIconPtr{g_volume_get_icon(get()), false});
|
||||
}
|
||||
|
||||
// GDrive * g_volume_get_drive(get());
|
||||
GMountPtr mount() const {
|
||||
return GMountPtr{g_volume_get_mount(get()), false};
|
||||
}
|
||||
|
||||
bool canMount() const {
|
||||
return g_volume_can_mount(get());
|
||||
}
|
||||
|
||||
bool shouldAutoMount() const {
|
||||
return g_volume_should_automount(get());
|
||||
}
|
||||
|
||||
FilePath activationRoot() const {
|
||||
return FilePath{g_volume_get_activation_root(get()), false};
|
||||
}
|
||||
|
||||
/*
|
||||
void g_volume_mount(get());
|
||||
gboolean g_volume_mount_finish(get());
|
||||
*/
|
||||
bool canEject() const {
|
||||
return g_volume_can_eject(get());
|
||||
}
|
||||
|
||||
/*
|
||||
void g_volume_eject(get());
|
||||
gboolean g_volume_eject_finish(get());
|
||||
void g_volume_eject_with_operation(get());
|
||||
gboolean g_volume_eject_with_operation_finish(get());
|
||||
char ** g_volume_enumerate_identifiers(get());
|
||||
char * g_volume_get_identifier(get());
|
||||
const gchar * g_volume_get_sort_key(get());
|
||||
*/
|
||||
|
||||
CStrPtr device() const {
|
||||
return CStrPtr{g_volume_get_identifier(get(), G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE)};
|
||||
}
|
||||
|
||||
CStrPtr label() const {
|
||||
return CStrPtr{g_volume_get_identifier(get(), G_VOLUME_IDENTIFIER_KIND_LABEL)};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
class LIBFM_QT_API Mount: public GMountPtr {
|
||||
public:
|
||||
|
||||
explicit Mount(GMount* mnt, bool addRef): GMountPtr{mnt, addRef} {
|
||||
}
|
||||
|
||||
explicit Mount(GMountPtr gmnt): GMountPtr{std::move(gmnt)} {
|
||||
}
|
||||
|
||||
CStrPtr name() const {
|
||||
return CStrPtr{g_mount_get_name(get())};
|
||||
}
|
||||
|
||||
CStrPtr uuid() const {
|
||||
return CStrPtr{g_mount_get_uuid(get())};
|
||||
}
|
||||
|
||||
std::shared_ptr<const IconInfo> icon() const {
|
||||
return IconInfo::fromGIcon(GIconPtr{g_mount_get_icon(get()), false});
|
||||
}
|
||||
|
||||
// GIcon * g_mount_get_symbolic_icon(get());
|
||||
// GDrive * g_mount_get_drive(get());
|
||||
FilePath root() const {
|
||||
return FilePath{g_mount_get_root(get()), false};
|
||||
}
|
||||
|
||||
GVolumePtr volume() const {
|
||||
return GVolumePtr{g_mount_get_volume(get()), false};
|
||||
}
|
||||
|
||||
FilePath defaultLocation() const {
|
||||
return FilePath{g_mount_get_default_location(get()), false};
|
||||
}
|
||||
|
||||
bool canUnmount() const {
|
||||
return g_mount_can_unmount(get());
|
||||
}
|
||||
|
||||
/*
|
||||
void g_mount_unmount(get());
|
||||
gboolean g_mount_unmount_finish(get());
|
||||
void g_mount_unmount_with_operation(get());
|
||||
gboolean g_mount_unmount_with_operation_finish(get());
|
||||
void g_mount_remount(get());
|
||||
gboolean g_mount_remount_finish(get());
|
||||
*/
|
||||
bool canEject() const {
|
||||
return g_mount_can_eject(get());
|
||||
}
|
||||
|
||||
/*
|
||||
void g_mount_eject(get());
|
||||
gboolean g_mount_eject_finish(get());
|
||||
void g_mount_eject_with_operation(get());
|
||||
gboolean g_mount_eject_with_operation_finish(get());
|
||||
*/
|
||||
// void g_mount_guess_content_type(get());
|
||||
// gchar ** g_mount_guess_content_type_finish(get());
|
||||
// gchar ** g_mount_guess_content_type_sync(get());
|
||||
|
||||
bool isShadowed() const {
|
||||
return g_mount_is_shadowed(get());
|
||||
}
|
||||
|
||||
// void g_mount_shadow(get());
|
||||
// void g_mount_unshadow(get());
|
||||
// const gchar * g_mount_get_sort_key(get());
|
||||
};
|
||||
|
||||
|
||||
|
||||
class LIBFM_QT_API VolumeManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VolumeManager();
|
||||
|
||||
~VolumeManager();
|
||||
|
||||
const std::vector<Volume>& volumes() const {
|
||||
return volumes_;
|
||||
}
|
||||
|
||||
const std::vector<Mount>& mounts() const {
|
||||
return mounts_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<VolumeManager> globalInstance();
|
||||
|
||||
Q_SIGNALS:
|
||||
void volumeAdded(const Volume& vol);
|
||||
void volumeRemoved(const Volume& vol);
|
||||
void volumeChanged(const Volume& vol);
|
||||
|
||||
void mountAdded(const Mount& mnt);
|
||||
void mountRemoved(const Mount& mnt);
|
||||
void mountChanged(const Mount& mnt);
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
void onGetGVolumeMonitorFinished();
|
||||
|
||||
private:
|
||||
|
||||
class GetGVolumeMonitorJob: public Job {
|
||||
public:
|
||||
GetGVolumeMonitorJob() {}
|
||||
GVolumeMonitorPtr monitor_;
|
||||
protected:
|
||||
void exec() override;
|
||||
};
|
||||
|
||||
static void _onGVolumeAdded(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) {
|
||||
_this->onGVolumeAdded(vol);
|
||||
}
|
||||
void onGVolumeAdded(GVolume* vol);
|
||||
|
||||
static void _onGVolumeRemoved(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) {
|
||||
_this->onGVolumeRemoved(vol);
|
||||
}
|
||||
void onGVolumeRemoved(GVolume* vol);
|
||||
|
||||
static void _onGVolumeChanged(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) {
|
||||
_this->onGVolumeChanged(vol);
|
||||
}
|
||||
void onGVolumeChanged(GVolume* vol);
|
||||
|
||||
static void _onGMountAdded(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) {
|
||||
_this->onGMountAdded(mnt);
|
||||
}
|
||||
void onGMountAdded(GMount* mnt);
|
||||
|
||||
static void _onGMountRemoved(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) {
|
||||
_this->onGMountRemoved(mnt);
|
||||
}
|
||||
void onGMountRemoved(GMount* mnt);
|
||||
|
||||
static void _onGMountChanged(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) {
|
||||
_this->onGMountChanged(mnt);
|
||||
}
|
||||
void onGMountChanged(GMount* mnt);
|
||||
|
||||
private:
|
||||
GVolumeMonitorPtr monitor_;
|
||||
|
||||
std::vector<Volume> volumes_;
|
||||
std::vector<Mount> mounts_;
|
||||
|
||||
static std::mutex mutex_;
|
||||
static std::weak_ptr<VolumeManager> globalInstance_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_VOLUMEMANAGER_H
|
@ -0,0 +1,615 @@
|
||||
#include "fileaction.h"
|
||||
#include <unordered_map>
|
||||
#include <QDebug>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
static const char* desktop_env = nullptr; // current desktop environment
|
||||
static bool actions_loaded = false; // all actions are loaded?
|
||||
static unordered_map<const char*, shared_ptr<FileActionObject>, CStrHash, CStrEqual> all_actions; // cache all loaded actions
|
||||
|
||||
FileActionObject::FileActionObject() {
|
||||
}
|
||||
|
||||
FileActionObject::FileActionObject(GKeyFile* kf) {
|
||||
name = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Name", nullptr, nullptr)};
|
||||
tooltip = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Tooltip", nullptr, nullptr)};
|
||||
icon = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Icon", nullptr, nullptr)};
|
||||
desc = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Description", nullptr, nullptr)};
|
||||
GErrorPtr err;
|
||||
enabled = g_key_file_get_boolean(kf, "Desktop Entry", "Enabled", &err);
|
||||
if(err) { // key not found, default to true
|
||||
err.reset();
|
||||
enabled = true;
|
||||
}
|
||||
hidden = g_key_file_get_boolean(kf, "Desktop Entry", "Hidden", nullptr);
|
||||
suggested_shortcut = CStrPtr{g_key_file_get_string(kf, "Desktop Entry", "SuggestedShortcut", nullptr)};
|
||||
|
||||
condition = unique_ptr<FileActionCondition> {new FileActionCondition(kf, "Desktop Entry")};
|
||||
|
||||
has_parent = false;
|
||||
}
|
||||
|
||||
FileActionObject::~FileActionObject() {
|
||||
}
|
||||
|
||||
//static
|
||||
bool FileActionObject::is_plural_exec(const char* exec) {
|
||||
if(!exec) {
|
||||
return false;
|
||||
}
|
||||
// the first relevent code encountered in Exec parameter
|
||||
// determines whether the command accepts singular or plural forms
|
||||
for(int i = 0; exec[i]; ++i) {
|
||||
char ch = exec[i];
|
||||
if(ch == '%') {
|
||||
++i;
|
||||
ch = exec[i];
|
||||
switch(ch) {
|
||||
case 'B':
|
||||
case 'D':
|
||||
case 'F':
|
||||
case 'M':
|
||||
case 'O':
|
||||
case 'U':
|
||||
case 'W':
|
||||
case 'X':
|
||||
return true; // plural
|
||||
case 'b':
|
||||
case 'd':
|
||||
case 'f':
|
||||
case 'm':
|
||||
case 'o':
|
||||
case 'u':
|
||||
case 'w':
|
||||
case 'x':
|
||||
return false; // singular
|
||||
default:
|
||||
// irrelevent code, skip
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // singular form by default
|
||||
}
|
||||
|
||||
std::string FileActionObject::expand_str(const char* templ, const FileInfoList& files, bool for_display, std::shared_ptr<const FileInfo> first_file) {
|
||||
if(!templ) {
|
||||
return string{};
|
||||
}
|
||||
string result;
|
||||
|
||||
if(!first_file) {
|
||||
first_file = files.front();
|
||||
}
|
||||
|
||||
for(int i = 0; templ[i]; ++i) {
|
||||
char ch = templ[i];
|
||||
if(ch == '%') {
|
||||
++i;
|
||||
ch = templ[i];
|
||||
switch(ch) {
|
||||
case 'b': // (first) basename
|
||||
if(for_display) {
|
||||
result += first_file->name();
|
||||
}
|
||||
else {
|
||||
CStrPtr quoted{g_shell_quote(first_file->name().c_str())};
|
||||
result += quoted.get();
|
||||
}
|
||||
break;
|
||||
case 'B': // space-separated list of basenames
|
||||
for(auto& fi : files) {
|
||||
if(for_display) {
|
||||
result += fi->name();
|
||||
}
|
||||
else {
|
||||
CStrPtr quoted{g_shell_quote(fi->name().c_str())};
|
||||
result += quoted.get();
|
||||
}
|
||||
result += ' ';
|
||||
}
|
||||
if(result[result.length() - 1] == ' ') { // remove trailing space
|
||||
result.erase(result.length() - 1);
|
||||
}
|
||||
break;
|
||||
case 'c': // count of selected items
|
||||
result += to_string(files.size());
|
||||
break;
|
||||
case 'd': { // (first) base directory
|
||||
// FIXME: should the base dir be a URI?
|
||||
auto base_dir = first_file->dirPath();
|
||||
auto str = base_dir.toString();
|
||||
if(for_display) {
|
||||
// FIXME: str = Filename.display_name(str);
|
||||
}
|
||||
CStrPtr quoted{g_shell_quote(str.get())};
|
||||
result += quoted.get();
|
||||
break;
|
||||
}
|
||||
case 'D': // space-separated list of base directory of each selected items
|
||||
for(auto& fi : files) {
|
||||
auto base_dir = fi->dirPath();
|
||||
auto str = base_dir.toString();
|
||||
if(for_display) {
|
||||
// str = Filename.display_name(str);
|
||||
}
|
||||
CStrPtr quoted{g_shell_quote(str.get())};
|
||||
result += quoted.get();
|
||||
result += ' ';
|
||||
}
|
||||
if(result[result.length() - 1] == ' ') { // remove trailing space
|
||||
result.erase(result.length() - 1);
|
||||
}
|
||||
break;
|
||||
case 'f': { // (first) file name
|
||||
auto filename = first_file->path().toString();
|
||||
if(for_display) {
|
||||
// filename = Filename.display_name(filename);
|
||||
}
|
||||
CStrPtr quoted{g_shell_quote(filename.get())};
|
||||
result += quoted.get();
|
||||
break;
|
||||
}
|
||||
case 'F': // space-separated list of selected file names
|
||||
for(auto& fi : files) {
|
||||
auto filename = fi->path().toString();
|
||||
if(for_display) {
|
||||
// filename = Filename.display_name(filename);
|
||||
}
|
||||
CStrPtr quoted{g_shell_quote(filename.get())};
|
||||
result += quoted.get();
|
||||
result += ' ';
|
||||
}
|
||||
if(result[result.length() - 1] == ' ') { // remove trailing space
|
||||
result.erase(result.length() - 1);
|
||||
}
|
||||
break;
|
||||
case 'h': // hostname of the (first) URI
|
||||
// FIXME: how to support this correctly?
|
||||
// FIXME: currently we pass g_get_host_name()
|
||||
result += g_get_host_name();
|
||||
break;
|
||||
case 'm': // mimetype of the (first) selected item
|
||||
result += first_file->mimeType()->name();
|
||||
break;
|
||||
case 'M': // space-separated list of the mimetypes of the selected items
|
||||
for(auto& fi : files) {
|
||||
result += fi->mimeType()->name();
|
||||
result += ' ';
|
||||
}
|
||||
break;
|
||||
case 'n': // username of the (first) URI
|
||||
// FIXME: how to support this correctly?
|
||||
result += g_get_user_name();
|
||||
break;
|
||||
case 'o': // no-op operator which forces a singular form of execution when specified as first parameter,
|
||||
case 'O': // no-op operator which forces a plural form of execution when specified as first parameter,
|
||||
break;
|
||||
case 'p': // port number of the (first) URI
|
||||
// FIXME: how to support this correctly?
|
||||
// result.append("0");
|
||||
break;
|
||||
case 's': // scheme of the (first) URI
|
||||
result += first_file->path().uriScheme().get();
|
||||
break;
|
||||
case 'u': // (first) URI
|
||||
result += first_file->path().uri().get();
|
||||
break;
|
||||
case 'U': // space-separated list of selected URIs
|
||||
for(auto& fi : files) {
|
||||
result += fi->path().uri().get();
|
||||
result += ' ';
|
||||
}
|
||||
if(result[result.length() - 1] == ' ') { // remove trailing space
|
||||
result.erase(result.length() - 1);
|
||||
}
|
||||
break;
|
||||
case 'w': { // (first) basename without the extension
|
||||
auto basename = first_file->name();
|
||||
int pos = basename.rfind('.');
|
||||
// FIXME: handle non-UTF8 filenames
|
||||
if(pos != -1) {
|
||||
basename.erase(pos, string::npos);
|
||||
}
|
||||
CStrPtr quoted{g_shell_quote(basename.c_str())};
|
||||
result += quoted.get();
|
||||
break;
|
||||
}
|
||||
case 'W': // space-separated list of basenames without their extension
|
||||
for(auto& fi : files) {
|
||||
auto basename = fi->name();
|
||||
int pos = basename.rfind('.');
|
||||
// FIXME: for_display ? Shell.quote(str) : str);
|
||||
if(pos != -1) {
|
||||
basename.erase(pos, string::npos);
|
||||
}
|
||||
CStrPtr quoted{g_shell_quote(basename.c_str())};
|
||||
result += quoted.get();
|
||||
result += ' ';
|
||||
}
|
||||
if(result[result.length() - 1] == ' ') { // remove trailing space
|
||||
result.erase(result.length() - 1);
|
||||
}
|
||||
break;
|
||||
case 'x': { // (first) extension
|
||||
auto basename = first_file->name();
|
||||
int pos = basename.rfind('.');
|
||||
const char* ext = "";
|
||||
if(pos >= 0) {
|
||||
ext = basename.c_str() + pos + 1;
|
||||
}
|
||||
CStrPtr quoted{g_shell_quote(ext)};
|
||||
result += quoted.get();
|
||||
break;
|
||||
}
|
||||
case 'X': // space-separated list of extensions
|
||||
for(auto& fi : files) {
|
||||
auto basename = fi->name();
|
||||
int pos = basename.rfind('.');
|
||||
const char* ext = "";
|
||||
if(pos >= 0) {
|
||||
ext = basename.c_str() + pos + 1;
|
||||
}
|
||||
CStrPtr quoted{g_shell_quote(ext)};
|
||||
result += quoted.get();
|
||||
result += ' ';
|
||||
}
|
||||
if(result[result.length() - 1] == ' ') { // remove trailing space
|
||||
result.erase(result.length() - 1);
|
||||
}
|
||||
break;
|
||||
case '%': // the % character
|
||||
result += '%';
|
||||
break;
|
||||
case '\0':
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
result += ch;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
FileAction::FileAction(GKeyFile* kf): FileActionObject{kf}, target{FILE_ACTION_TARGET_CONTEXT} {
|
||||
type = FileActionType::ACTION;
|
||||
|
||||
GErrorPtr err;
|
||||
if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetContext", &err)) { // default to true
|
||||
target |= FILE_ACTION_TARGET_CONTEXT;
|
||||
}
|
||||
else if(!err) { // error means the key is abscent
|
||||
target &= ~FILE_ACTION_TARGET_CONTEXT;
|
||||
}
|
||||
if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetLocation", nullptr)) {
|
||||
target |= FILE_ACTION_TARGET_LOCATION;
|
||||
}
|
||||
if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetToolbar", nullptr)) {
|
||||
target |= FILE_ACTION_TARGET_TOOLBAR;
|
||||
}
|
||||
toolbar_label = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "ToolbarLabel", nullptr, nullptr)};
|
||||
|
||||
auto profile_names = CStrArrayPtr{g_key_file_get_string_list(kf, "Desktop Entry", "Profiles", nullptr, nullptr)};
|
||||
if(profile_names != nullptr) {
|
||||
for(auto profile_name = profile_names.get(); *profile_name; ++profile_name) {
|
||||
// stdout.printf("%s", profile);
|
||||
profiles.push_back(make_shared<FileActionProfile>(kf, *profile_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FileActionProfile> FileAction::match(const FileInfoList& files) const {
|
||||
//qDebug() << "FileAction.match: " << id.get();
|
||||
if(hidden || !enabled) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(!condition->match(files)) {
|
||||
return nullptr;
|
||||
}
|
||||
for(const auto& profile : profiles) {
|
||||
if(profile->match(files)) {
|
||||
//qDebug() << " profile matched!\n\n";
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
// stdout.printf("\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileActionMenu::FileActionMenu(GKeyFile* kf): FileActionObject{kf} {
|
||||
type = FileActionType::MENU;
|
||||
items_list = CStrArrayPtr{g_key_file_get_string_list(kf, "Desktop Entry", "ItemsList", nullptr, nullptr)};
|
||||
}
|
||||
|
||||
bool FileActionMenu::match(const FileInfoList& files) const {
|
||||
// stdout.printf("FileActionMenu.match: %s\n", id);
|
||||
if(hidden || !enabled) {
|
||||
return false;
|
||||
}
|
||||
if(!condition->match(files)) {
|
||||
return false;
|
||||
}
|
||||
// stdout.printf("menu matched!: %s\n\n", id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileActionMenu::cache_children(const FileInfoList& files, const char** items_list) {
|
||||
for(; *items_list; ++items_list) {
|
||||
const char* item_id_prefix = *items_list;
|
||||
size_t len = strlen(item_id_prefix);
|
||||
if(item_id_prefix[0] == '[' && item_id_prefix[len - 1] == ']') {
|
||||
// runtime dynamic item list
|
||||
char* output;
|
||||
int exit_status;
|
||||
string prefix{item_id_prefix + 1, len - 2}; // skip [ and ]
|
||||
auto command = expand_str(prefix.c_str(), files);
|
||||
if(g_spawn_command_line_sync(command.c_str(), &output, nullptr, &exit_status, nullptr) && exit_status == 0) {
|
||||
CStrArrayPtr item_ids{g_strsplit(output, ";", -1)};
|
||||
g_free(output);
|
||||
cache_children(files, (const char**)item_ids.get());
|
||||
}
|
||||
}
|
||||
else if(strcmp(item_id_prefix, "SEPARATOR") == 0) {
|
||||
// separator item
|
||||
cached_children.push_back(nullptr);
|
||||
}
|
||||
else {
|
||||
CStrPtr item_id{g_strconcat(item_id_prefix, ".desktop", nullptr)};
|
||||
auto it = all_actions.find(item_id.get());
|
||||
if(it != all_actions.end()) {
|
||||
auto child_action = it->second;
|
||||
child_action->has_parent = true;
|
||||
cached_children.push_back(child_action);
|
||||
// stdout.printf("add child: %s to menu: %s\n", item_id, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FileActionItem> FileActionItem::fromActionObject(std::shared_ptr<FileActionObject> action_obj, const FileInfoList& files) {
|
||||
std::shared_ptr<FileActionItem> item;
|
||||
if(action_obj->type == FileActionType::MENU) {
|
||||
auto menu = static_pointer_cast<FileActionMenu>(action_obj);
|
||||
if(menu->match(files)) {
|
||||
item = make_shared<FileActionItem>(menu, files);
|
||||
// eliminate empty menus
|
||||
if(item->children.empty()) {
|
||||
item = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// handle profiles here
|
||||
auto action = static_pointer_cast<FileAction>(action_obj);
|
||||
auto profile = action->match(files);
|
||||
if(profile) {
|
||||
item = make_shared<FileActionItem>(action, profile, files);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
FileActionItem::FileActionItem(std::shared_ptr<FileAction> _action, std::shared_ptr<FileActionProfile> _profile, const FileInfoList& files):
|
||||
FileActionItem{static_pointer_cast<FileActionObject>(_action), files} {
|
||||
profile = _profile;
|
||||
}
|
||||
|
||||
FileActionItem::FileActionItem(std::shared_ptr<FileActionMenu> menu, const FileInfoList& files):
|
||||
FileActionItem{static_pointer_cast<FileActionObject>(menu), files} {
|
||||
for(auto& action_obj : menu->cached_children) {
|
||||
if(action_obj == nullptr) { // separator
|
||||
children.push_back(nullptr);
|
||||
}
|
||||
else { // action item or menu
|
||||
auto subitem = fromActionObject(action_obj, files);
|
||||
if(subitem != nullptr) {
|
||||
children.push_back(subitem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileActionItem::FileActionItem(std::shared_ptr<FileActionObject> _action, const FileInfoList& files) {
|
||||
action = std::move(_action);
|
||||
name = FileActionObject::expand_str(action->name.get(), files, true);
|
||||
desc = FileActionObject::expand_str(action->desc.get(), files, true);
|
||||
icon = FileActionObject::expand_str(action->icon.get(), files, false);
|
||||
}
|
||||
|
||||
bool FileActionItem::launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output) const {
|
||||
if(action->type == FileActionType::ACTION) {
|
||||
if(profile != nullptr) {
|
||||
profile->launch(ctx, files, output);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void load_actions_from_dir(const char* dirname, const char* id_prefix) {
|
||||
//qDebug() << "loading from: " << dirname << endl;
|
||||
auto dir = g_dir_open(dirname, 0, nullptr);
|
||||
if(dir != nullptr) {
|
||||
for(;;) {
|
||||
const char* name = g_dir_read_name(dir);
|
||||
if(name == nullptr) {
|
||||
break;
|
||||
}
|
||||
// found a file in file-manager/actions dir, get its full path
|
||||
CStrPtr full_path{g_build_filename(dirname, name, nullptr)};
|
||||
// stdout.printf("\nfound %s\n", full_path);
|
||||
|
||||
// see if it's a sub dir
|
||||
if(g_file_test(full_path.get(), G_FILE_TEST_IS_DIR)) {
|
||||
// load sub dirs recursively
|
||||
CStrPtr new_id_prefix;
|
||||
if(id_prefix) {
|
||||
new_id_prefix = CStrPtr{g_strconcat(id_prefix, "-", name, nullptr)};
|
||||
}
|
||||
load_actions_from_dir(full_path.get(), id_prefix ? new_id_prefix.get() : name);
|
||||
}
|
||||
else if(g_str_has_suffix(name, ".desktop")) {
|
||||
CStrPtr new_id_prefix;
|
||||
if(id_prefix) {
|
||||
new_id_prefix = CStrPtr{g_strconcat(id_prefix, "-", name, nullptr)};
|
||||
}
|
||||
const char* id = id_prefix ? new_id_prefix.get() : name;
|
||||
// ensure that it's not already in the cache
|
||||
if(all_actions.find(id) == all_actions.cend()) {
|
||||
auto kf = g_key_file_new();
|
||||
if(g_key_file_load_from_file(kf, full_path.get(), G_KEY_FILE_NONE, nullptr)) {
|
||||
auto type = CStrPtr{g_key_file_get_string(kf, "Desktop Entry", "Type", nullptr)};
|
||||
if(!type) {
|
||||
continue;
|
||||
}
|
||||
std::shared_ptr<FileActionObject> action;
|
||||
if(strcmp(type.get(), "Action") == 0) {
|
||||
action = static_pointer_cast<FileActionObject>(make_shared<FileAction>(kf));
|
||||
// stdout.printf("load action: %s\n", id);
|
||||
}
|
||||
else if(strcmp(type.get(), "Menu") == 0) {
|
||||
action = static_pointer_cast<FileActionObject>(make_shared<FileActionMenu>(kf));
|
||||
// stdout.printf("load menu: %s\n", id);
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
action->setId(id);
|
||||
all_actions.insert(make_pair(action->id.get(), action)); // add the id/action pair to hash table
|
||||
// stdout.printf("add to cache %s\n", id);
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
else {
|
||||
// stdout.printf("cache found for action: %s\n", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
g_dir_close(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void file_actions_set_desktop_env(const char* env) {
|
||||
desktop_env = env;
|
||||
}
|
||||
|
||||
static void load_all_actions() {
|
||||
all_actions.clear();
|
||||
auto dirs = g_get_system_data_dirs();
|
||||
for(auto dir = dirs; *dir; ++dir) {
|
||||
CStrPtr dir_path{g_build_filename(*dir, "file-manager/actions", nullptr)};
|
||||
load_actions_from_dir(dir_path.get(), nullptr);
|
||||
}
|
||||
CStrPtr dir_path{g_build_filename(g_get_user_data_dir(), "file-manager/actions", nullptr)};
|
||||
load_actions_from_dir(dir_path.get(), nullptr);
|
||||
actions_loaded = true;
|
||||
}
|
||||
|
||||
bool FileActionItem::compare_items(std::shared_ptr<const FileActionItem> a, std::shared_ptr<const FileActionItem> b)
|
||||
{
|
||||
// first get the list of level-zero item names (http://www.nautilus-actions.org/?q=node/377)
|
||||
static QStringList itemNamesList;
|
||||
static bool level_zero_checked = false;
|
||||
if(!level_zero_checked) {
|
||||
level_zero_checked = true;
|
||||
auto level_zero = CStrPtr{g_build_filename(g_get_user_data_dir(),
|
||||
"file-manager/actions/level-zero.directory", nullptr)};
|
||||
if(g_file_test(level_zero.get(), G_FILE_TEST_IS_REGULAR)) {
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(g_key_file_load_from_file(kf, level_zero.get(), G_KEY_FILE_NONE, nullptr)) {
|
||||
auto itemsList = CStrArrayPtr{g_key_file_get_string_list(kf,
|
||||
"Desktop Entry",
|
||||
"ItemsList", nullptr, nullptr)};
|
||||
if(itemsList) {
|
||||
for(uint i = 0; i < g_strv_length(itemsList.get()); ++i) {
|
||||
CStrPtr desktop_file_name{g_strconcat(itemsList.get()[i], ".desktop", nullptr)};
|
||||
auto desktop_file = CStrPtr{g_build_filename(g_get_user_data_dir(),
|
||||
"file-manager/actions",
|
||||
desktop_file_name.get(), nullptr)};
|
||||
GKeyFile* desktop_file_key = g_key_file_new();
|
||||
if(g_key_file_load_from_file(desktop_file_key, desktop_file.get(), G_KEY_FILE_NONE, nullptr)) {
|
||||
auto actionName = CStrPtr{g_key_file_get_string(desktop_file_key,
|
||||
"Desktop Entry",
|
||||
"Name", NULL)};
|
||||
if(actionName) {
|
||||
itemNamesList << QString::fromUtf8(actionName.get());
|
||||
}
|
||||
}
|
||||
g_key_file_free(desktop_file_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
}
|
||||
if(!itemNamesList.isEmpty()) {
|
||||
int first = itemNamesList.indexOf(QString::fromStdString(a->get_name()));
|
||||
int second = itemNamesList.indexOf(QString::fromStdString(b->get_name()));
|
||||
if(first > -1) {
|
||||
if(second > -1) {
|
||||
return (first < second);
|
||||
}
|
||||
else {
|
||||
return true; // list items have priority
|
||||
}
|
||||
}
|
||||
else if(second > -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (a->get_name().compare(b->get_name()) < 0);
|
||||
}
|
||||
|
||||
FileActionItemList FileActionItem::get_actions_for_files(const FileInfoList& files) {
|
||||
if(!actions_loaded) {
|
||||
load_all_actions();
|
||||
}
|
||||
|
||||
// Iterate over all actions to establish association between parent menu
|
||||
// and children actions, and to find out toplevel ones which are not
|
||||
// attached to any parent menu
|
||||
for(auto& item : all_actions) {
|
||||
auto& action_obj = item.second;
|
||||
// stdout.printf("id = %s\n", action_obj.id);
|
||||
if(action_obj->type == FileActionType::MENU) { // this is a menu
|
||||
auto menu = static_pointer_cast<FileActionMenu>(action_obj);
|
||||
// stdout.printf("menu: %s\n", menu.name);
|
||||
// associate child items with menus
|
||||
menu->cache_children(files, (const char**)menu->items_list.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Output the menus
|
||||
FileActionItemList items;
|
||||
|
||||
for(auto& item : all_actions) {
|
||||
auto& action_obj = item.second;
|
||||
// only output toplevel items here
|
||||
if(action_obj->has_parent == false) { // this is a toplevel item
|
||||
auto item = FileActionItem::fromActionObject(action_obj, files);
|
||||
if(item != nullptr) {
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup temporary data cached during menu generation
|
||||
for(auto& item : all_actions) {
|
||||
auto& action_obj = item.second;
|
||||
action_obj->has_parent = false;
|
||||
if(action_obj->type == FileActionType::MENU) {
|
||||
auto menu = static_pointer_cast<FileActionMenu>(action_obj);
|
||||
menu->cached_children.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(items.begin(), items.end(), compare_items);
|
||||
return items;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,156 @@
|
||||
#ifndef FILEACTION_H
|
||||
#define FILEACTION_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <string>
|
||||
|
||||
#include "../core/fileinfo.h"
|
||||
#include "fileactioncondition.h"
|
||||
#include "fileactionprofile.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
enum class FileActionType {
|
||||
NONE,
|
||||
ACTION,
|
||||
MENU
|
||||
};
|
||||
|
||||
|
||||
enum FileActionTarget {
|
||||
FILE_ACTION_TARGET_NONE,
|
||||
FILE_ACTION_TARGET_CONTEXT = 1,
|
||||
FILE_ACTION_TARGET_LOCATION = 1 << 1,
|
||||
FILE_ACTION_TARGET_TOOLBAR = 1 << 2
|
||||
};
|
||||
|
||||
|
||||
class FileActionObject {
|
||||
public:
|
||||
explicit FileActionObject();
|
||||
|
||||
explicit FileActionObject(GKeyFile* kf);
|
||||
|
||||
virtual ~FileActionObject();
|
||||
|
||||
void setId(const char* _id) {
|
||||
id = CStrPtr{g_strdup(_id)};
|
||||
}
|
||||
|
||||
static bool is_plural_exec(const char* exec);
|
||||
|
||||
static std::string expand_str(const char* templ, const FileInfoList& files, bool for_display = false, std::shared_ptr<const FileInfo> first_file = nullptr);
|
||||
|
||||
FileActionType type;
|
||||
CStrPtr id;
|
||||
CStrPtr name;
|
||||
CStrPtr tooltip;
|
||||
CStrPtr icon;
|
||||
CStrPtr desc;
|
||||
bool enabled;
|
||||
bool hidden;
|
||||
CStrPtr suggested_shortcut;
|
||||
std::unique_ptr<FileActionCondition> condition;
|
||||
|
||||
// values cached during menu generation
|
||||
bool has_parent;
|
||||
};
|
||||
|
||||
|
||||
class FileAction: public FileActionObject {
|
||||
public:
|
||||
|
||||
FileAction(GKeyFile* kf);
|
||||
|
||||
std::shared_ptr<FileActionProfile> match(const FileInfoList& files) const;
|
||||
|
||||
int target; // bitwise or of FileActionTarget
|
||||
CStrPtr toolbar_label;
|
||||
|
||||
// FIXME: currently we don't support dynamic profiles
|
||||
std::vector<std::shared_ptr<FileActionProfile>> profiles;
|
||||
};
|
||||
|
||||
|
||||
class FileActionMenu : public FileActionObject {
|
||||
public:
|
||||
|
||||
FileActionMenu(GKeyFile* kf);
|
||||
|
||||
bool match(const FileInfoList &files) const;
|
||||
|
||||
// called during menu generation
|
||||
void cache_children(const FileInfoList &files, const char** items_list);
|
||||
|
||||
CStrArrayPtr items_list;
|
||||
|
||||
// values cached during menu generation
|
||||
std::vector<std::shared_ptr<FileActionObject>> cached_children;
|
||||
};
|
||||
|
||||
|
||||
class FileActionItem {
|
||||
public:
|
||||
|
||||
static std::shared_ptr<FileActionItem> fromActionObject(std::shared_ptr<FileActionObject> action_obj, const FileInfoList &files);
|
||||
|
||||
FileActionItem(std::shared_ptr<FileAction> _action, std::shared_ptr<FileActionProfile> _profile, const FileInfoList& files);
|
||||
|
||||
FileActionItem(std::shared_ptr<FileActionMenu> menu, const FileInfoList& files);
|
||||
|
||||
FileActionItem(std::shared_ptr<FileActionObject> _action, const FileInfoList& files);
|
||||
|
||||
const std::string& get_name() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
const std::string& get_desc() const {
|
||||
return desc;
|
||||
}
|
||||
|
||||
const std::string& get_icon() const {
|
||||
return icon;
|
||||
}
|
||||
|
||||
const char* get_id() const {
|
||||
return action->id.get();
|
||||
}
|
||||
|
||||
FileActionTarget get_target() const {
|
||||
if(action->type == FileActionType::ACTION) {
|
||||
return FileActionTarget(static_cast<FileAction*>(action.get())->target);
|
||||
}
|
||||
return FILE_ACTION_TARGET_CONTEXT;
|
||||
}
|
||||
|
||||
bool is_menu() const {
|
||||
return (action->type == FileActionType::MENU);
|
||||
}
|
||||
|
||||
bool is_action() const {
|
||||
return (action->type == FileActionType::ACTION);
|
||||
}
|
||||
|
||||
bool launch(GAppLaunchContext *ctx, const FileInfoList &files, CStrPtr &output) const;
|
||||
|
||||
const std::vector<std::shared_ptr<const FileActionItem>>& get_sub_items() const {
|
||||
return children;
|
||||
}
|
||||
|
||||
static bool compare_items(std::shared_ptr<const FileActionItem> a, std::shared_ptr<const FileActionItem> b);
|
||||
static std::vector<std::shared_ptr<const FileActionItem>> get_actions_for_files(const FileInfoList& files);
|
||||
|
||||
std::string name;
|
||||
std::string desc;
|
||||
std::string icon;
|
||||
std::shared_ptr<FileActionObject> action;
|
||||
std::shared_ptr<FileActionProfile> profile; // only used by action item
|
||||
std::vector<std::shared_ptr<const FileActionItem>> children; // only used by menu
|
||||
};
|
||||
|
||||
typedef std::vector<std::shared_ptr<const FileActionItem>> FileActionItemList;
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
|
||||
#endif // FILEACTION_H
|
@ -0,0 +1,503 @@
|
||||
#include "fileactioncondition.h"
|
||||
#include "fileaction.h"
|
||||
#include <string>
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileActionCondition::FileActionCondition(GKeyFile *kf, const char* group) {
|
||||
only_show_in = CStrArrayPtr{g_key_file_get_string_list(kf, group, "OnlyShowIn", nullptr, nullptr)};
|
||||
not_show_in = CStrArrayPtr{g_key_file_get_string_list(kf, group, "NotShowIn", nullptr, nullptr)};
|
||||
try_exec = CStrPtr{g_key_file_get_string(kf, group, "TryExec", nullptr)};
|
||||
show_if_registered = CStrPtr{g_key_file_get_string(kf, group, "ShowIfRegistered", nullptr)};
|
||||
show_if_true = CStrPtr{g_key_file_get_string(kf, group, "ShowIfTrue", nullptr)};
|
||||
show_if_running = CStrPtr{g_key_file_get_string(kf, group, "ShowIfRunning", nullptr)};
|
||||
mime_types = CStrArrayPtr{g_key_file_get_string_list(kf, group, "MimeTypes", nullptr, nullptr)};
|
||||
base_names = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Basenames", nullptr, nullptr)};
|
||||
match_case = g_key_file_get_boolean(kf, group, "Matchcase", nullptr);
|
||||
|
||||
CStrPtr selection_count_str{g_key_file_get_string(kf, group, "SelectionCount", nullptr)};
|
||||
if(selection_count_str != nullptr) {
|
||||
switch(selection_count_str[0]) {
|
||||
case '<':
|
||||
case '>':
|
||||
case '=':
|
||||
selection_count_cmp = selection_count_str[0];
|
||||
selection_count = atoi(selection_count_str.get() + 1);
|
||||
break;
|
||||
default:
|
||||
selection_count_cmp = '>';
|
||||
selection_count = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
selection_count_cmp = '>';
|
||||
selection_count = 0;
|
||||
}
|
||||
|
||||
schemes = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Schemes", nullptr, nullptr)};
|
||||
folders = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Folders", nullptr, nullptr)};
|
||||
auto caps = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Capabilities", nullptr, nullptr)};
|
||||
|
||||
// FIXME: implement Capabilities support
|
||||
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_try_exec(const FileInfoList& files) {
|
||||
if(try_exec != nullptr) {
|
||||
// stdout.printf(" TryExec: %s\n", try_exec);
|
||||
CStrPtr exec_path{g_find_program_in_path(FileActionObject::expand_str(try_exec.get(), files).c_str())};
|
||||
if(!g_file_test(exec_path.get(), G_FILE_TEST_IS_EXECUTABLE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_show_if_registered(const FileInfoList& files) {
|
||||
if(show_if_registered != nullptr) {
|
||||
// stdout.printf(" ShowIfRegistered: %s\n", show_if_registered);
|
||||
auto service = FileActionObject::expand_str(show_if_registered.get(), files);
|
||||
// References:
|
||||
// http://people.freedesktop.org/~david/eggdbus-20091014/eggdbus-interface-org.freedesktop.DBus.html#eggdbus-method-org.freedesktop.DBus.NameHasOwner
|
||||
// glib source code: gio/tests/gdbus-names.c
|
||||
auto con = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
|
||||
auto result = g_dbus_connection_call_sync(con,
|
||||
"org.freedesktop.DBus",
|
||||
"/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus",
|
||||
"NameHasOwner",
|
||||
g_variant_new("(s)", service.c_str()),
|
||||
g_variant_type_new("(b)"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1, nullptr, nullptr);
|
||||
bool name_has_owner;
|
||||
g_variant_get(result, "(b)", &name_has_owner);
|
||||
g_variant_unref(result);
|
||||
// stdout.printf("check if service: %s is in use: %d\n", service, (int)name_has_owner);
|
||||
if(!name_has_owner) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_show_if_true(const FileInfoList& files) {
|
||||
if(show_if_true != nullptr) {
|
||||
auto cmd = FileActionObject::expand_str(show_if_true.get(), files);
|
||||
int exit_status;
|
||||
// FIXME: Process.spawn cannot handle shell commands. Use Posix.system() instead.
|
||||
//if(!Process.spawn_command_line_sync(cmd, nullptr, nullptr, out exit_status)
|
||||
// || exit_status != 0)
|
||||
// return false;
|
||||
exit_status = system(cmd.c_str());
|
||||
if(exit_status != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_show_if_running(const FileInfoList& files) {
|
||||
if(show_if_running != nullptr) {
|
||||
auto process_name = FileActionObject::expand_str(show_if_running.get(), files);
|
||||
CStrPtr pgrep{g_find_program_in_path("pgrep")};
|
||||
bool running = false;
|
||||
// pgrep is not fully portable, but we don't have better options here
|
||||
if(pgrep != nullptr) {
|
||||
int exit_status;
|
||||
// cmd = "$pgrep -x '$process_name'"
|
||||
string cmd = pgrep.get();
|
||||
cmd += " -x \'";
|
||||
cmd += process_name;
|
||||
cmd += "\'";
|
||||
if(g_spawn_command_line_sync(cmd.c_str(), nullptr, nullptr, &exit_status, nullptr)) {
|
||||
if(exit_status == 0) {
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!running) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_mime_type(const FileInfoList& files, const char* type, bool negated) {
|
||||
// stdout.printf("match_mime_type: %s, neg: %d\n", type, (int)negated);
|
||||
|
||||
if(strcmp(type, "all/all") == 0 || strcmp(type, "*") == 0) {
|
||||
return negated ? false : true;
|
||||
}
|
||||
else if(strcmp(type, "all/allfiles") == 0) {
|
||||
// see if all fileinfos are files
|
||||
if(negated) { // all fileinfos should not be files
|
||||
for(auto& fi: files) {
|
||||
if(!fi->isDir()) { // at least 1 of the fileinfos is a file.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // all fileinfos should be files
|
||||
for(auto& fi: files) {
|
||||
if(fi->isDir()) { // at least 1 of the fileinfos is a file.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(g_str_has_suffix(type, "/*")) {
|
||||
// check if all are subtypes of allowed_type
|
||||
string prefix{type};
|
||||
prefix.erase(prefix.length() - 1); // remove the last char
|
||||
if(negated) { // all files should not have the prefix
|
||||
for(auto& fi: files) {
|
||||
if(g_str_has_prefix(fi->mimeType()->name(), prefix.c_str())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // all files should have the prefix
|
||||
for(auto& fi: files) {
|
||||
if(!g_str_has_prefix(fi->mimeType()->name(), prefix.c_str())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(negated) { // all files should not be of the type
|
||||
for(auto& fi: files) {
|
||||
if(strcmp(fi->mimeType()->name(),type) == 0) {
|
||||
// if(ContentType.is_a(type, fi.get_mime_type().get_type())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // all files should be of the type
|
||||
for(auto& fi: files) {
|
||||
// stdout.printf("get_type: %s, type: %s\n", fi.get_mime_type().get_type(), type);
|
||||
if(strcmp(fi->mimeType()->name(),type) != 0) {
|
||||
// if(!ContentType.is_a(type, fi.get_mime_type().get_type())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_mime_types(const FileInfoList& files) {
|
||||
if(mime_types != nullptr) {
|
||||
bool allowed = false;
|
||||
// FIXME: this is inefficient, but easier to implement
|
||||
// check if all of the mime_types are allowed
|
||||
for(auto mime_type = mime_types.get(); *mime_type; ++mime_type) {
|
||||
const char* allowed_type = *mime_type;
|
||||
const char* type;
|
||||
bool negated;
|
||||
if(allowed_type[0] == '!') {
|
||||
type = allowed_type + 1;
|
||||
negated = true;
|
||||
}
|
||||
else {
|
||||
type = allowed_type;
|
||||
negated = false;
|
||||
}
|
||||
|
||||
if(negated) { // negated mime_type rules are ANDed
|
||||
bool type_is_allowed = match_mime_type(files, type, negated);
|
||||
if(!type_is_allowed) { // so any mismatch is not allowed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else { // other mime_type rules are ORed
|
||||
// matching any one of the mime_type is enough
|
||||
if(!allowed) { // if no rule is matched yet
|
||||
allowed = match_mime_type(files, type, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_base_name(const FileInfoList& files, const char* base_name, bool negated) {
|
||||
// see if all files has the base_name
|
||||
// FIXME: this is inefficient, some optimization is needed later
|
||||
GPatternSpec* pattern;
|
||||
if(match_case) {
|
||||
pattern = g_pattern_spec_new(base_name);
|
||||
}
|
||||
else {
|
||||
CStrPtr case_fold{g_utf8_casefold(base_name, -1)};
|
||||
pattern = g_pattern_spec_new(case_fold.get()); // FIXME: is this correct?
|
||||
}
|
||||
for(auto& fi: files) {
|
||||
const char* name = fi->name().c_str();
|
||||
if(match_case) {
|
||||
if(g_pattern_match_string(pattern, name)) {
|
||||
// at least 1 file has the base_name
|
||||
if(negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// at least 1 file does not has the scheme
|
||||
if(!negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
CStrPtr case_fold{g_utf8_casefold(name, -1)};
|
||||
if(g_pattern_match_string(pattern, case_fold.get())) {
|
||||
// at least 1 file has the base_name
|
||||
if(negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// at least 1 file does not has the scheme
|
||||
if(!negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_base_names(const FileInfoList& files) {
|
||||
if(base_names != nullptr) {
|
||||
bool allowed = false;
|
||||
// FIXME: this is inefficient, but easier to implement
|
||||
// check if all of the base_names are allowed
|
||||
for(auto it = base_names.get(); *it; ++it) {
|
||||
auto allowed_name = *it;
|
||||
const char* name;
|
||||
bool negated;
|
||||
if(allowed_name[0] == '!') {
|
||||
name = allowed_name + 1;
|
||||
negated = true;
|
||||
}
|
||||
else {
|
||||
name = allowed_name;
|
||||
negated = false;
|
||||
}
|
||||
|
||||
if(negated) { // negated base_name rules are ANDed
|
||||
bool name_is_allowed = match_base_name(files, name, negated);
|
||||
if(!name_is_allowed) { // so any mismatch is not allowed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else { // other base_name rules are ORed
|
||||
// matching any one of the base_name is enough
|
||||
if(!allowed) { // if no rule is matched yet
|
||||
allowed = match_base_name(files, name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_scheme(const FileInfoList& files, const char* scheme, bool negated) {
|
||||
// FIXME: this is inefficient, some optimization is needed later
|
||||
// see if all files has the scheme
|
||||
for(auto& fi: files) {
|
||||
if(fi->path().hasUriScheme(scheme)) {
|
||||
// at least 1 file has the scheme
|
||||
if(negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// at least 1 file does not has the scheme
|
||||
if(!negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_schemes(const FileInfoList& files) {
|
||||
if(schemes != nullptr) {
|
||||
bool allowed = false;
|
||||
// FIXME: this is inefficient, but easier to implement
|
||||
// check if all of the schemes are allowed
|
||||
for(auto it = schemes.get(); *it; ++it) {
|
||||
auto allowed_scheme = *it;
|
||||
const char* scheme;
|
||||
bool negated;
|
||||
if(allowed_scheme[0] == '!') {
|
||||
scheme = allowed_scheme + 1;
|
||||
negated = true;
|
||||
}
|
||||
else {
|
||||
scheme = allowed_scheme;
|
||||
negated = false;
|
||||
}
|
||||
|
||||
if(negated) { // negated scheme rules are ANDed
|
||||
bool scheme_is_allowed = match_scheme(files, scheme, negated);
|
||||
if(!scheme_is_allowed) { // so any mismatch is not allowed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else { // other scheme rules are ORed
|
||||
// matching any one of the scheme is enough
|
||||
if(!allowed) { // if no rule is matched yet
|
||||
allowed = match_scheme(files, scheme, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_folder(const FileInfoList& files, const char* folder, bool negated) {
|
||||
// trailing /* should always be implied.
|
||||
// FIXME: this is inefficient, some optimization is needed later
|
||||
GPatternSpec* pattern;
|
||||
if(g_str_has_suffix(folder, "/*")) {
|
||||
pattern = g_pattern_spec_new(folder);
|
||||
}
|
||||
else {
|
||||
auto pat_str = string(folder) + "/*";
|
||||
pattern = g_pattern_spec_new(pat_str.c_str());
|
||||
}
|
||||
for(auto& fi: files) {
|
||||
auto dirname = fi->dirPath().toString();
|
||||
if(g_pattern_match_string(pattern, dirname.get())) { // at least 1 file is in the folder
|
||||
if(negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!negated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_folders(const FileInfoList& files) {
|
||||
if(folders != nullptr) {
|
||||
bool allowed = false;
|
||||
// FIXME: this is inefficient, but easier to implement
|
||||
// check if all of the schemes are allowed
|
||||
for(auto it = folders.get(); *it; ++it) {
|
||||
auto allowed_folder = *it;
|
||||
const char* folder;
|
||||
bool negated;
|
||||
if(allowed_folder[0] == '!') {
|
||||
folder = allowed_folder + 1;
|
||||
negated = true;
|
||||
}
|
||||
else {
|
||||
folder = allowed_folder;
|
||||
negated = false;
|
||||
}
|
||||
|
||||
if(negated) { // negated folder rules are ANDed
|
||||
bool folder_is_allowed = match_folder(files, folder, negated);
|
||||
if(!folder_is_allowed) { // so any mismatch is not allowed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else { // other folder rules are ORed
|
||||
// matching any one of the folder is enough
|
||||
if(!allowed) { // if no rule is matched yet
|
||||
allowed = match_folder(files, folder, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_selection_count(const FileInfoList& files) {
|
||||
const int n_files = files.size();
|
||||
switch(selection_count_cmp) {
|
||||
case '<':
|
||||
if(n_files >= selection_count) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '=':
|
||||
if(n_files != selection_count) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if(n_files <= selection_count) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match_capabilities(const FileInfoList& /*files*/) {
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileActionCondition::match(const FileInfoList& files) {
|
||||
// all of the condition are combined with AND
|
||||
// So, if any one of the conditions is not matched, we quit.
|
||||
|
||||
// TODO: OnlyShowIn, NotShowIn
|
||||
if(!match_try_exec(files)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!match_mime_types(files)) {
|
||||
return false;
|
||||
}
|
||||
if(!match_base_names(files)) {
|
||||
return false;
|
||||
}
|
||||
if(!match_selection_count(files)) {
|
||||
return false;
|
||||
}
|
||||
if(!match_schemes(files)) {
|
||||
return false;
|
||||
}
|
||||
if(!match_folders(files)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Capabilities
|
||||
// currently, due to limitations of Fm.FileInfo, this cannot
|
||||
// be implemanted correctly.
|
||||
if(!match_capabilities(files)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!match_show_if_registered(files)) {
|
||||
return false;
|
||||
}
|
||||
if(!match_show_if_true(files)) {
|
||||
return false;
|
||||
}
|
||||
if(!match_show_if_running(files)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,123 @@
|
||||
#ifndef FILEACTIONCONDITION_H
|
||||
#define FILEACTIONCONDITION_H
|
||||
|
||||
#include <glib.h>
|
||||
#include "../core/gioptrs.h"
|
||||
#include "../core/fileinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// FIXME: we can use getgroups() to get groups of current process
|
||||
// then, call stat() and stat.st_gid to handle capabilities
|
||||
// in this way, we don't have to call euidaccess
|
||||
|
||||
enum class FileActionCapability {
|
||||
OWNER = 0,
|
||||
READABLE = 1 << 1,
|
||||
WRITABLE = 1 << 2,
|
||||
EXECUTABLE = 1 << 3,
|
||||
LOCAL = 1 << 4
|
||||
};
|
||||
|
||||
|
||||
class FileActionCondition {
|
||||
public:
|
||||
explicit FileActionCondition(GKeyFile* kf, const char* group);
|
||||
|
||||
#if 0
|
||||
bool match_base_name_(const FileInfoList& files, const char* allowed_base_name) {
|
||||
// all files should match the base_name pattern.
|
||||
bool allowed = true;
|
||||
if(allowed_base_name.index_of_char('*') >= 0) {
|
||||
string allowed_base_name_ci;
|
||||
if(!match_case) {
|
||||
allowed_base_name_ci = allowed_base_name.casefold(); // FIXME: is this ok?
|
||||
allowed_base_name = allowed_base_name_ci;
|
||||
}
|
||||
var pattern = new PatternSpec(allowed_base_name);
|
||||
foreach(unowned FileInfo fi in files) {
|
||||
unowned string name = fi.get_name();
|
||||
if(match_case) {
|
||||
if(!pattern.match_string(name)) {
|
||||
allowed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!pattern.match_string(name.casefold())) {
|
||||
allowed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach(unowned FileInfo fi in files) {
|
||||
unowned string name = fi.get_name();
|
||||
if(match_case) {
|
||||
if(allowed_base_name != name) {
|
||||
allowed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(allowed_base_name.collate(name) != 0) {
|
||||
allowed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool match_try_exec(const FileInfoList& files);
|
||||
|
||||
bool match_show_if_registered(const FileInfoList& files);
|
||||
|
||||
bool match_show_if_true(const FileInfoList& files);
|
||||
|
||||
bool match_show_if_running(const FileInfoList& files);
|
||||
|
||||
bool match_mime_type(const FileInfoList& files, const char* type, bool negated);
|
||||
|
||||
bool match_mime_types(const FileInfoList& files);
|
||||
|
||||
bool match_base_name(const FileInfoList& files, const char* base_name, bool negated);
|
||||
|
||||
bool match_base_names(const FileInfoList& files);
|
||||
|
||||
static bool match_scheme(const FileInfoList& files, const char* scheme, bool negated);
|
||||
|
||||
bool match_schemes(const FileInfoList& files);
|
||||
|
||||
static bool match_folder(const FileInfoList& files, const char* folder, bool negated);
|
||||
|
||||
bool match_folders(const FileInfoList& files);
|
||||
|
||||
bool match_selection_count(const FileInfoList &files);
|
||||
|
||||
bool match_capabilities(const FileInfoList& files);
|
||||
|
||||
bool match(const FileInfoList& files);
|
||||
|
||||
CStrArrayPtr only_show_in;
|
||||
CStrArrayPtr not_show_in;
|
||||
CStrPtr try_exec;
|
||||
CStrPtr show_if_registered;
|
||||
CStrPtr show_if_true;
|
||||
CStrPtr show_if_running;
|
||||
CStrArrayPtr mime_types;
|
||||
CStrArrayPtr base_names;
|
||||
bool match_case;
|
||||
char selection_count_cmp;
|
||||
int selection_count;
|
||||
CStrArrayPtr schemes;
|
||||
CStrArrayPtr folders;
|
||||
FileActionCapability capabilities;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FILEACTIONCONDITION_H
|
@ -0,0 +1,121 @@
|
||||
#include "fileactionprofile.h"
|
||||
#include "fileaction.h"
|
||||
#include <QDebug>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileActionProfile::FileActionProfile(GKeyFile *kf, const char* profile_name) {
|
||||
id = profile_name;
|
||||
std::string group_name = "X-Action-Profile " + id;
|
||||
name = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Name", nullptr)};
|
||||
exec = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Exec", nullptr)};
|
||||
// stdout.printf("id: %s, Exec: %s\n", id, exec);
|
||||
|
||||
path = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Path", nullptr)};
|
||||
auto s = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "ExecutionMode", nullptr)};
|
||||
if(s) {
|
||||
if(strcmp(s.get(), "Normal") == 0) {
|
||||
exec_mode = FileActionExecMode::NORMAL;
|
||||
}
|
||||
else if(strcmp(s.get(), "Terminal") == 0) {
|
||||
exec_mode = FileActionExecMode::TERMINAL;
|
||||
}
|
||||
else if(strcmp(s.get(), "Embedded") == 0) {
|
||||
exec_mode = FileActionExecMode::EMBEDDED;
|
||||
}
|
||||
else if(strcmp(s.get(), "DisplayOutput") == 0) {
|
||||
exec_mode = FileActionExecMode::DISPLAY_OUTPUT;
|
||||
}
|
||||
else {
|
||||
exec_mode = FileActionExecMode::NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
startup_notify = g_key_file_get_boolean(kf, group_name.c_str(), "StartupNotify", nullptr);
|
||||
startup_wm_class = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "StartupWMClass", nullptr)};
|
||||
exec_as = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "ExecuteAs", nullptr)};
|
||||
|
||||
condition = make_shared<FileActionCondition>(kf, group_name.c_str());
|
||||
}
|
||||
|
||||
|
||||
bool FileActionProfile::launch_once(GAppLaunchContext* /*ctx*/, std::shared_ptr<const FileInfo> first_file, const FileInfoList& files, CStrPtr& output) {
|
||||
if(exec == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto exec_cmd = FileActionObject::expand_str(exec.get(), files, false, first_file);
|
||||
bool ret = false;
|
||||
if(exec_mode == FileActionExecMode::DISPLAY_OUTPUT) {
|
||||
int exit_status;
|
||||
char* output_buf = nullptr;
|
||||
ret = g_spawn_command_line_sync(exec_cmd.c_str(), &output_buf, nullptr, &exit_status, nullptr);
|
||||
if(ret) {
|
||||
ret = (exit_status == 0);
|
||||
}
|
||||
output = CStrPtr{output_buf};
|
||||
}
|
||||
else {
|
||||
/*
|
||||
AppInfoCreateFlags flags = AppInfoCreateFlags.NONE;
|
||||
if(startup_notify)
|
||||
flags |= AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION;
|
||||
if(exec_mode == FileActionExecMode::TERMINAL ||
|
||||
exec_mode == FileActionExecMode::EMBEDDED)
|
||||
flags |= AppInfoCreateFlags.NEEDS_TERMINAL;
|
||||
GLib.AppInfo app = Fm.AppInfo.create_from_commandline(exec, nullptr, flags);
|
||||
stdout.printf("Execute command line: %s\n\n", exec);
|
||||
ret = app.launch(nullptr, ctx);
|
||||
*/
|
||||
|
||||
// NOTE: we cannot use GAppInfo here since GAppInfo does
|
||||
// command line parsing which involving %u, %f, and other
|
||||
// code defined in desktop entry spec.
|
||||
// This may conflict with DES EMA parameters.
|
||||
// FIXME: so how to handle this cleaner?
|
||||
// Maybe we should leave all %% alone and don't translate
|
||||
// them to %. Then GAppInfo will translate them to %, not
|
||||
// codes specified in DES.
|
||||
ret = g_spawn_command_line_async(exec_cmd.c_str(), nullptr);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool FileActionProfile::launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output) {
|
||||
bool plural_form = FileActionObject::is_plural_exec(exec.get());
|
||||
bool ret;
|
||||
if(plural_form) { // plural form command, handle all files at a time
|
||||
ret = launch_once(ctx, files.front(), files, output);
|
||||
}
|
||||
else { // singular form command, run once for each file
|
||||
GString* all_output = g_string_sized_new(1024);
|
||||
bool show_output = false;
|
||||
for(auto& fi: files) {
|
||||
CStrPtr one_output;
|
||||
launch_once(ctx, fi, files, one_output);
|
||||
if(one_output) {
|
||||
show_output = true;
|
||||
// FIXME: how to handle multiple output std::strings properly?
|
||||
g_string_append(all_output, one_output.get());
|
||||
g_string_append(all_output, "\n");
|
||||
}
|
||||
}
|
||||
if(show_output) {
|
||||
output = CStrPtr{g_string_free(all_output, false)};
|
||||
}
|
||||
else {
|
||||
g_string_free(all_output, true);
|
||||
}
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileActionProfile::match(FileInfoList files) {
|
||||
// stdout.printf(" match profile: %s\n", id);
|
||||
return condition->match(files);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
#ifndef FILEACTIONPROFILE_H
|
||||
#define FILEACTIONPROFILE_H
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "../core/fileinfo.h"
|
||||
#include "fileactioncondition.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
enum class FileActionExecMode {
|
||||
NORMAL,
|
||||
TERMINAL,
|
||||
EMBEDDED,
|
||||
DISPLAY_OUTPUT
|
||||
};
|
||||
|
||||
class FileActionProfile {
|
||||
public:
|
||||
explicit FileActionProfile(GKeyFile* kf, const char* profile_name);
|
||||
|
||||
bool launch_once(GAppLaunchContext* ctx, std::shared_ptr<const FileInfo> first_file, const FileInfoList& files, CStrPtr& output);
|
||||
|
||||
bool launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output);
|
||||
|
||||
bool match(FileInfoList files);
|
||||
|
||||
std::string id;
|
||||
CStrPtr name;
|
||||
CStrPtr exec;
|
||||
CStrPtr path;
|
||||
FileActionExecMode exec_mode;
|
||||
bool startup_notify;
|
||||
CStrPtr startup_wm_class;
|
||||
CStrPtr exec_as;
|
||||
|
||||
std::shared_ptr<FileActionCondition> condition;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FILEACTIONPROFILE_H
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LIBFM_QT_FM_DEEP_COUNT_JOB_H__
|
||||
#define __LIBFM_QT_FM_DEEP_COUNT_JOB_H__
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include "libfmqtglobals.h"
|
||||
#include "job.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
|
||||
class LIBFM_QT_API DeepCountJob: public Job {
|
||||
public:
|
||||
|
||||
|
||||
DeepCountJob(FmPathList* paths, FmDeepCountJobFlags flags) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(fm_deep_count_job_new(paths, flags));
|
||||
}
|
||||
|
||||
|
||||
// default constructor
|
||||
DeepCountJob() {
|
||||
dataPtr_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
DeepCountJob(FmDeepCountJob* dataPtr){
|
||||
dataPtr_ = dataPtr != nullptr ? reinterpret_cast<GObject*>(g_object_ref(dataPtr)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// copy constructor
|
||||
DeepCountJob(const DeepCountJob& other) {
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<GObject*>(g_object_ref(other.dataPtr_)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// move constructor
|
||||
DeepCountJob(DeepCountJob&& other) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(other.takeDataPtr());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// create a wrapper for the data pointer without increasing the reference count
|
||||
static DeepCountJob wrapPtr(FmDeepCountJob* dataPtr) {
|
||||
DeepCountJob obj;
|
||||
obj.dataPtr_ = reinterpret_cast<GObject*>(dataPtr);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// disown the managed data pointer
|
||||
FmDeepCountJob* takeDataPtr() {
|
||||
FmDeepCountJob* data = reinterpret_cast<FmDeepCountJob*>(dataPtr_);
|
||||
dataPtr_ = nullptr;
|
||||
return data;
|
||||
}
|
||||
|
||||
// get the raw pointer wrapped
|
||||
FmDeepCountJob* dataPtr() {
|
||||
return reinterpret_cast<FmDeepCountJob*>(dataPtr_);
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator FmDeepCountJob*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator void*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
|
||||
// copy assignment
|
||||
DeepCountJob& operator=(const DeepCountJob& other) {
|
||||
if(dataPtr_ != nullptr) {
|
||||
g_object_unref(dataPtr_);
|
||||
}
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<GObject*>(g_object_ref(other.dataPtr_)) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// move assignment
|
||||
DeepCountJob& operator=(DeepCountJob&& other) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(other.takeDataPtr());
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isNull() {
|
||||
return (dataPtr_ == nullptr);
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
void setDest(dev_t dev, const char* fs_id) {
|
||||
fm_deep_count_job_set_dest(dataPtr(), dev, fs_id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // __LIBFM_QT_FM_DEEP_COUNT_JOB_H__
|
@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LIBFM_QT_FM_DIR_LIST_JOB_H__
|
||||
#define __LIBFM_QT_FM_DIR_LIST_JOB_H__
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include "libfmqtglobals.h"
|
||||
#include "job.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
|
||||
class LIBFM_QT_API DirListJob: public Job {
|
||||
public:
|
||||
|
||||
|
||||
DirListJob(FmPath* path, gboolean dir_only) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(fm_dir_list_job_new(path, dir_only));
|
||||
}
|
||||
|
||||
|
||||
// default constructor
|
||||
DirListJob() {
|
||||
dataPtr_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
DirListJob(FmDirListJob* dataPtr){
|
||||
dataPtr_ = dataPtr != nullptr ? reinterpret_cast<GObject*>(g_object_ref(dataPtr)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// copy constructor
|
||||
DirListJob(const DirListJob& other) {
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<GObject*>(g_object_ref(other.dataPtr_)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// move constructor
|
||||
DirListJob(DirListJob&& other) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(other.takeDataPtr());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// create a wrapper for the data pointer without increasing the reference count
|
||||
static DirListJob wrapPtr(FmDirListJob* dataPtr) {
|
||||
DirListJob obj;
|
||||
obj.dataPtr_ = reinterpret_cast<GObject*>(dataPtr);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// disown the managed data pointer
|
||||
FmDirListJob* takeDataPtr() {
|
||||
FmDirListJob* data = reinterpret_cast<FmDirListJob*>(dataPtr_);
|
||||
dataPtr_ = nullptr;
|
||||
return data;
|
||||
}
|
||||
|
||||
// get the raw pointer wrapped
|
||||
FmDirListJob* dataPtr() {
|
||||
return reinterpret_cast<FmDirListJob*>(dataPtr_);
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator FmDirListJob*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator void*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
|
||||
// copy assignment
|
||||
DirListJob& operator=(const DirListJob& other) {
|
||||
if(dataPtr_ != nullptr) {
|
||||
g_object_unref(dataPtr_);
|
||||
}
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<GObject*>(g_object_ref(other.dataPtr_)) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// move assignment
|
||||
DirListJob& operator=(DirListJob&& other) {
|
||||
dataPtr_ = reinterpret_cast<GObject*>(other.takeDataPtr());
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isNull() {
|
||||
return (dataPtr_ == nullptr);
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
void addFoundFile(FmFileInfo* file) {
|
||||
fm_dir_list_job_add_found_file(dataPtr(), file);
|
||||
}
|
||||
|
||||
|
||||
void setIncremental(gboolean set) {
|
||||
fm_dir_list_job_set_incremental(dataPtr(), set);
|
||||
}
|
||||
|
||||
|
||||
FmFileInfoList* getFiles(void) {
|
||||
return fm_dir_list_job_get_files(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
static DirListJob newForGfile(GFile* gf) {
|
||||
return DirListJob::wrapPtr(fm_dir_list_job_new_for_gfile(gf));
|
||||
}
|
||||
|
||||
|
||||
static DirListJob new2(FmPath* path, FmDirListJobFlags flags) {
|
||||
return DirListJob::wrapPtr(fm_dir_list_job_new2(path, flags));
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // __LIBFM_QT_FM_DIR_LIST_JOB_H__
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue