parent
b2567a5f34
commit
ca7bd7b1c8
@ -1,6 +1,6 @@
|
||||
Upstream Authors:
|
||||
LXQt team: http://lxqt.org
|
||||
LXQt team: https://lxqt.org
|
||||
Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
|
||||
Copyright:
|
||||
Copyright (c) 2013-2017 LXQt team
|
||||
Copyright (c) 2013-2018 LXQt team
|
||||
|
@ -1,143 +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_ARCHIVER_H__
|
||||
#define __LIBFM_QT_FM_ARCHIVER_H__
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include "libfmqtglobals.h"
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
|
||||
class LIBFM_QT_API Archiver {
|
||||
public:
|
||||
|
||||
|
||||
// default constructor
|
||||
Archiver() {
|
||||
dataPtr_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
// move constructor
|
||||
Archiver(Archiver&& other) noexcept {
|
||||
dataPtr_ = reinterpret_cast<FmArchiver*>(other.takeDataPtr());
|
||||
}
|
||||
|
||||
|
||||
// destructor
|
||||
~Archiver() {
|
||||
if(dataPtr_ != nullptr) {
|
||||
(dataPtr_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create a wrapper for the data pointer without increasing the reference count
|
||||
static Archiver wrapPtr(FmArchiver* dataPtr) {
|
||||
Archiver obj;
|
||||
obj.dataPtr_ = reinterpret_cast<FmArchiver*>(dataPtr);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// disown the managed data pointer
|
||||
FmArchiver* takeDataPtr() {
|
||||
FmArchiver* data = reinterpret_cast<FmArchiver*>(dataPtr_);
|
||||
dataPtr_ = nullptr;
|
||||
return data;
|
||||
}
|
||||
|
||||
// get the raw pointer wrapped
|
||||
FmArchiver* dataPtr() {
|
||||
return reinterpret_cast<FmArchiver*>(dataPtr_);
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator FmArchiver*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator void*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// move assignment
|
||||
Archiver& operator=(Archiver&& other) noexcept {
|
||||
dataPtr_ = reinterpret_cast<FmArchiver*>(other.takeDataPtr());
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isNull() {
|
||||
return (dataPtr_ == nullptr);
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
void setDefault(void) {
|
||||
fm_archiver_set_default(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
static Archiver getDefault( ) {
|
||||
return wrapPtr(fm_archiver_get_default());
|
||||
}
|
||||
|
||||
|
||||
bool extractArchivesTo(GAppLaunchContext* ctx, FmPathList* files, FmPath* dest_dir) {
|
||||
return fm_archiver_extract_archives_to(dataPtr(), ctx, files, dest_dir);
|
||||
}
|
||||
|
||||
|
||||
bool extractArchives(GAppLaunchContext* ctx, FmPathList* files) {
|
||||
return fm_archiver_extract_archives(dataPtr(), ctx, files);
|
||||
}
|
||||
|
||||
|
||||
bool createArchive(GAppLaunchContext* ctx, FmPathList* files) {
|
||||
return fm_archiver_create_archive(dataPtr(), ctx, files);
|
||||
}
|
||||
|
||||
|
||||
bool isMimeTypeSupported(const char* type) {
|
||||
return fm_archiver_is_mime_type_supported(dataPtr(), type);
|
||||
}
|
||||
|
||||
|
||||
// the wrapped object cannot be copied.
|
||||
private:
|
||||
Archiver(const Archiver& other) = delete;
|
||||
Archiver& operator=(const Archiver& other) = delete;
|
||||
|
||||
|
||||
private:
|
||||
FmArchiver* dataPtr_; // data pointer for the underlying C struct
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // __LIBFM_QT_FM_ARCHIVER_H__
|
@ -0,0 +1,174 @@
|
||||
#include "libfmqtglobals.h"
|
||||
#include "archiver.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
Archiver* Archiver::defaultArchiver_ = nullptr; // static
|
||||
std::vector<std::unique_ptr<Archiver>> Archiver::allArchivers_; // static
|
||||
|
||||
Archiver::Archiver() {
|
||||
}
|
||||
|
||||
bool Archiver::isMimeTypeSupported(const char* type) {
|
||||
char** p;
|
||||
if(G_UNLIKELY(!type)) {
|
||||
return false;
|
||||
}
|
||||
for(p = mimeTypes_.get(); *p; ++p) {
|
||||
if(strcmp(*p, type) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Archiver::launchProgram(GAppLaunchContext* ctx, const char* cmd, const FilePathList& files, const FilePath& dir) {
|
||||
char* _cmd = NULL;
|
||||
const char* dir_place_holder;
|
||||
GKeyFile* dummy;
|
||||
|
||||
if(dir.isValid() && (dir_place_holder = strstr(cmd, "%d"))) {
|
||||
CStrPtr dir_str;
|
||||
int len;
|
||||
if(strstr(cmd, "%U") || strstr(cmd, "%u")) { /* supports URI */
|
||||
dir_str = dir.uri();
|
||||
}
|
||||
else {
|
||||
dir_str = dir.localPath();
|
||||
}
|
||||
|
||||
// FIXME: remove libfm dependency here
|
||||
/* replace all % with %% so encoded URI can be handled correctly when parsing Exec key. */
|
||||
std::string percentEscapedDir;
|
||||
for(auto p = dir_str.get(); *p; ++p) {
|
||||
percentEscapedDir += *p;
|
||||
if(*p == '%') {
|
||||
percentEscapedDir += '%';
|
||||
}
|
||||
}
|
||||
|
||||
/* quote the path or URI */
|
||||
dir_str = CStrPtr{g_shell_quote(percentEscapedDir.c_str())};
|
||||
|
||||
len = strlen(cmd) - 2 + strlen(dir_str.get()) + 1;
|
||||
_cmd = (char*)g_malloc(len);
|
||||
len = (dir_place_holder - cmd);
|
||||
strncpy(_cmd, cmd, len);
|
||||
strcpy(_cmd + len, dir_str.get());
|
||||
strcat(_cmd, dir_place_holder + 2);
|
||||
cmd = _cmd;
|
||||
}
|
||||
|
||||
/* create a fake key file to cheat GDesktopAppInfo */
|
||||
dummy = g_key_file_new();
|
||||
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Type", "Application");
|
||||
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Name", program_.get());
|
||||
|
||||
/* replace all % with %% so encoded URI can be handled correctly when parsing Exec key. */
|
||||
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Exec", cmd);
|
||||
GAppInfoPtr app{reinterpret_cast<GAppInfo*>(g_desktop_app_info_new_from_keyfile(dummy)), false};
|
||||
|
||||
g_key_file_free(dummy);
|
||||
g_debug("cmd = %s", cmd);
|
||||
if(app) {
|
||||
GList* uris = NULL;
|
||||
for(auto& file: files) {
|
||||
uris = g_list_prepend(uris, g_strdup(file.uri().get()));
|
||||
}
|
||||
g_app_info_launch_uris(app.get(), uris, ctx, NULL);
|
||||
g_list_foreach(uris, (GFunc)g_free, NULL);
|
||||
g_list_free(uris);
|
||||
}
|
||||
g_free(_cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Archiver::createArchive(GAppLaunchContext* ctx, const FilePathList& files) {
|
||||
if(createCmd_ && !files.empty()) {
|
||||
launchProgram(ctx, createCmd_.get(), files, FilePath{});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Archiver::extractArchives(GAppLaunchContext* ctx, const FilePathList& files) {
|
||||
if(extractCmd_ && !files.empty()) {
|
||||
launchProgram(ctx, extractCmd_.get(), files, FilePath{});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Archiver::extractArchivesTo(GAppLaunchContext* ctx, const FilePathList& files, const FilePath& dest_dir) {
|
||||
if(extractToCmd_ && !files.empty()) {
|
||||
launchProgram(ctx, extractToCmd_.get(), files, dest_dir);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
Archiver* Archiver::defaultArchiver() {
|
||||
allArchivers(); // to have a preliminary default archiver
|
||||
return defaultArchiver_;
|
||||
}
|
||||
|
||||
void Archiver::setDefaultArchiverByName(const char *name) {
|
||||
if(name) {
|
||||
auto& all = allArchivers();
|
||||
for(auto& archiver: all) {
|
||||
if(archiver->program_ && strcmp(archiver->program_.get(), name) == 0) {
|
||||
defaultArchiver_ = archiver.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Archiver::setDefaultArchiver(Archiver* archiver) {
|
||||
if(archiver) {
|
||||
defaultArchiver_ = archiver;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
const std::vector<std::unique_ptr<Archiver> >& Archiver::allArchivers() {
|
||||
// load all archivers on demand
|
||||
if(allArchivers_.empty()) {
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/archivers.list", G_KEY_FILE_NONE, NULL)) {
|
||||
gsize n_archivers;
|
||||
CStrArrayPtr programs{g_key_file_get_groups(kf, &n_archivers)};
|
||||
if(programs) {
|
||||
gsize i;
|
||||
for(i = 0; i < n_archivers; ++i) {
|
||||
auto program = programs[i];
|
||||
std::unique_ptr<Archiver> archiver{new Archiver{}};
|
||||
archiver->createCmd_ = CStrPtr{g_key_file_get_string(kf, program, "create", NULL)};
|
||||
archiver->extractCmd_ = CStrPtr{g_key_file_get_string(kf, program, "extract", NULL)};
|
||||
archiver->extractToCmd_ = CStrPtr{g_key_file_get_string(kf, program, "extract_to", NULL)};
|
||||
archiver->mimeTypes_ = CStrArrayPtr{g_key_file_get_string_list(kf, program, "mime_types", NULL, NULL)};
|
||||
archiver->program_ = CStrPtr{g_strdup(program)};
|
||||
|
||||
// if default archiver is not set, find the first program existing in the current system.
|
||||
if(!defaultArchiver_) {
|
||||
CStrPtr fullPath{g_find_program_in_path(program)};
|
||||
if(fullPath) {
|
||||
defaultArchiver_ = archiver.get();
|
||||
}
|
||||
}
|
||||
|
||||
allArchivers_.emplace_back(std::move(archiver));
|
||||
}
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
return allArchivers_;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,69 @@
|
||||
#ifndef ARCHIVER_H
|
||||
#define ARCHIVER_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "filepath.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API Archiver {
|
||||
public:
|
||||
Archiver();
|
||||
|
||||
bool isMimeTypeSupported(const char* type);
|
||||
|
||||
bool canCreateArchive() const {
|
||||
return createCmd_ != nullptr;
|
||||
}
|
||||
|
||||
bool createArchive(GAppLaunchContext* ctx, const FilePathList& files);
|
||||
|
||||
bool canExtractArchives() const {
|
||||
return extractCmd_ != nullptr;
|
||||
}
|
||||
|
||||
bool extractArchives(GAppLaunchContext* ctx, const FilePathList& files);
|
||||
|
||||
bool canExtractArchivesTo() const {
|
||||
return extractToCmd_ != nullptr;
|
||||
}
|
||||
|
||||
bool extractArchivesTo(GAppLaunchContext* ctx, const FilePathList& files, const FilePath& dest_dir);
|
||||
|
||||
/* get default GUI archivers used by libfm */
|
||||
static Archiver* defaultArchiver();
|
||||
|
||||
/* set default GUI archivers used by libfm */
|
||||
static void setDefaultArchiverByName(const char* name);
|
||||
|
||||
/* set default GUI archivers used by libfm */
|
||||
static void setDefaultArchiver(Archiver* archiver);
|
||||
|
||||
/* get a list of FmArchiver* of all GUI archivers known to libfm */
|
||||
static const std::vector<std::unique_ptr<Archiver>>& allArchivers();
|
||||
|
||||
const char* program() const {
|
||||
return program_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
bool launchProgram(GAppLaunchContext* ctx, const char* cmd, const FilePathList& files, const FilePath &dir);
|
||||
|
||||
private:
|
||||
CStrPtr program_;
|
||||
CStrPtr createCmd_;
|
||||
CStrPtr extractCmd_;
|
||||
CStrPtr extractToCmd_;
|
||||
CStrArrayPtr mimeTypes_;
|
||||
|
||||
static Archiver* defaultArchiver_;
|
||||
static std::vector<std::unique_ptr<Archiver>> allArchivers_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // ARCHIVER_H
|
@ -0,0 +1,345 @@
|
||||
#include "basicfilelauncher.h"
|
||||
#include "fileinfojob.h"
|
||||
#include "mountoperation.h"
|
||||
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
#include <QObject>
|
||||
#include <QEventLoop>
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
BasicFileLauncher::BasicFileLauncher():
|
||||
quickExec_{false} {
|
||||
}
|
||||
|
||||
BasicFileLauncher::~BasicFileLauncher() {
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchFiles(const FileInfoList& fileInfos, GAppLaunchContext* ctx) {
|
||||
std::unordered_map<std::string, FileInfoList> mimeTypeToFiles;
|
||||
FileInfoList folderInfos;
|
||||
FilePathList pathsToLaunch;
|
||||
// classify files according to different mimetypes
|
||||
for(auto& fileInfo : fileInfos) {
|
||||
// qDebug("path: %s, target: %s", fileInfo->path().toString().get(), fileInfo->target().c_str());
|
||||
if(fileInfo->isDir()) {
|
||||
folderInfos.emplace_back(fileInfo);
|
||||
}
|
||||
else if(fileInfo->isMountable()) {
|
||||
if(fileInfo->target().empty()) {
|
||||
// the mountable is not yet mounted so we have no target URI.
|
||||
GErrorPtr err{G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED,
|
||||
QObject::tr("The path is not mounted.")};
|
||||
if(!showError(ctx, err, fileInfo->path(), fileInfo)) {
|
||||
// the user fail to handle the error, skip this file.
|
||||
continue;
|
||||
}
|
||||
|
||||
// we do not have the target path in the FileInfo object.
|
||||
// try to launch our path again to query the new file info later so we can get the mounted target URI.
|
||||
pathsToLaunch.emplace_back(fileInfo->path());
|
||||
}
|
||||
else {
|
||||
// we have the target path, launch it later
|
||||
pathsToLaunch.emplace_back(FilePath::fromPathStr(fileInfo->target().c_str()));
|
||||
}
|
||||
}
|
||||
else if(fileInfo->isDesktopEntry()) {
|
||||
// launch the desktop entry
|
||||
launchDesktopEntry(fileInfo, FilePathList{}, ctx);
|
||||
}
|
||||
else if(fileInfo->isExecutableType()) {
|
||||
// directly execute the file
|
||||
launchExecutable(fileInfo, ctx);
|
||||
}
|
||||
else if(fileInfo->isShortcut()) {
|
||||
// for shortcuts, launch their targets instead
|
||||
auto path = handleShortcut(fileInfo, ctx);
|
||||
if(path.isValid()) {
|
||||
pathsToLaunch.emplace_back(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto& mimeType = fileInfo->mimeType();
|
||||
mimeTypeToFiles[mimeType->name()].emplace_back(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// open folders
|
||||
if(!folderInfos.empty()) {
|
||||
GErrorPtr err;
|
||||
openFolder(ctx, folderInfos, err);
|
||||
}
|
||||
|
||||
// open files of different mime-types with their default app
|
||||
for(auto& typeFiles : mimeTypeToFiles) {
|
||||
auto& mimeType = typeFiles.first;
|
||||
auto& files = typeFiles.second;
|
||||
GErrorPtr err;
|
||||
GAppInfoPtr app{g_app_info_get_default_for_type(mimeType.c_str(), false), false};
|
||||
if(!app) {
|
||||
app = chooseApp(files, mimeType.c_str(), err);
|
||||
}
|
||||
if(app) {
|
||||
launchWithApp(app.get(), files.paths(), ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if(!pathsToLaunch.empty()) {
|
||||
launchPaths(pathsToLaunch, ctx);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchPaths(FilePathList paths, GAppLaunchContext* ctx) {
|
||||
// FIXME: blocking with an event loop is not a good design :-(
|
||||
QEventLoop eventLoop;
|
||||
|
||||
auto job = new FileInfoJob{paths};
|
||||
job->setAutoDelete(false); // do not automatically delete the job since we want its results later.
|
||||
|
||||
GObjectPtr<GAppLaunchContext> ctxPtr{ctx};
|
||||
QObject::connect(job, &FileInfoJob::finished,
|
||||
[&eventLoop]() {
|
||||
// exit the event loop when the job is done
|
||||
eventLoop.exit();
|
||||
});
|
||||
// run the job in another thread to not block the UI
|
||||
job->runAsync();
|
||||
|
||||
// blocking until the job is done with a event loop
|
||||
eventLoop.exec();
|
||||
|
||||
// launch the file info
|
||||
launchFiles(job->files(), ctx);
|
||||
|
||||
delete job;
|
||||
return false;
|
||||
}
|
||||
|
||||
GAppInfoPtr BasicFileLauncher::chooseApp(const FileInfoList& /* fileInfos */, const char* /*mimeType*/, GErrorPtr& /* err */) {
|
||||
return GAppInfoPtr{};
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::openFolder(GAppLaunchContext* ctx, const FileInfoList& folderInfos, GErrorPtr& err) {
|
||||
auto app = chooseApp(folderInfos, "inode/directory", err);
|
||||
if(app) {
|
||||
launchWithApp(app.get(), folderInfos.paths(), ctx);
|
||||
}
|
||||
else {
|
||||
showError(ctx, err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BasicFileLauncher::ExecAction BasicFileLauncher::askExecFile(const FileInfoPtr & /* file */) {
|
||||
return ExecAction::DIRECT_EXEC;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::showError(GAppLaunchContext* /* ctx */, GErrorPtr& /* err */, const FilePath& /* path */, const FileInfoPtr& /* info */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int BasicFileLauncher::ask(const char* /* msg */, char* const* /* btn_labels */, int default_btn) {
|
||||
return default_btn;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchWithApp(GAppInfo* app, const FilePathList& paths, GAppLaunchContext* ctx) {
|
||||
GList* uris = nullptr;
|
||||
for(auto& path : paths) {
|
||||
auto uri = path.uri();
|
||||
uris = g_list_prepend(uris, uri.release());
|
||||
}
|
||||
GErrorPtr err;
|
||||
bool ret = bool(g_app_info_launch_uris(app, uris, ctx, &err));
|
||||
g_list_foreach(uris, reinterpret_cast<GFunc>(g_free), nullptr);
|
||||
g_list_free(uris);
|
||||
if(!ret) {
|
||||
// FIXME: show error for all files
|
||||
showError(ctx, err, paths[0]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool BasicFileLauncher::launchDesktopEntry(const FileInfoPtr &fileInfo, const FilePathList &paths, GAppLaunchContext* ctx) {
|
||||
/* treat desktop entries as executables */
|
||||
auto target = fileInfo->target();
|
||||
CStrPtr filename;
|
||||
const char* desktopEntryName = nullptr;
|
||||
FilePathList shortcutTargetPaths;
|
||||
if(fileInfo->isExecutableType()) {
|
||||
auto act = quickExec_ ? ExecAction::DIRECT_EXEC : askExecFile(fileInfo);
|
||||
switch(act) {
|
||||
case ExecAction::EXEC_IN_TERMINAL:
|
||||
case ExecAction::DIRECT_EXEC: {
|
||||
if(fileInfo->isShortcut()) {
|
||||
auto path = handleShortcut(fileInfo, ctx);
|
||||
if(path.isValid()) {
|
||||
shortcutTargetPaths.emplace_back(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(target.empty()) {
|
||||
filename = fileInfo->path().localPath();
|
||||
}
|
||||
desktopEntryName = !target.empty() ? target.c_str() : filename.get();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExecAction::OPEN_WITH_DEFAULT_APP:
|
||||
return launchWithDefaultApp(fileInfo, ctx);
|
||||
case ExecAction::CANCEL:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/* make exception for desktop entries under menu */
|
||||
else if(fileInfo->isNative() /* an exception */ ||
|
||||
fileInfo->path().hasUriScheme("menu")) {
|
||||
if(target.empty()) {
|
||||
filename = fileInfo->path().localPath();
|
||||
}
|
||||
desktopEntryName = !target.empty() ? target.c_str() : filename.get();
|
||||
}
|
||||
|
||||
if(desktopEntryName) {
|
||||
return launchDesktopEntry(desktopEntryName, paths, ctx);
|
||||
}
|
||||
if(!shortcutTargetPaths.empty()) {
|
||||
launchPaths(shortcutTargetPaths, ctx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchDesktopEntry(const char *desktopEntryName, const FilePathList &paths, GAppLaunchContext *ctx) {
|
||||
bool ret = false;
|
||||
GAppInfo* app;
|
||||
|
||||
/* Let GDesktopAppInfo try first. */
|
||||
if(g_path_is_absolute(desktopEntryName)) {
|
||||
app = G_APP_INFO(g_desktop_app_info_new_from_filename(desktopEntryName));
|
||||
}
|
||||
else {
|
||||
app = G_APP_INFO(g_desktop_app_info_new(desktopEntryName));
|
||||
}
|
||||
/* we handle Type=Link in FmFileInfo so if GIO failed then
|
||||
it cannot be launched in fact */
|
||||
|
||||
if(app) {
|
||||
return launchWithApp(app, paths, ctx);
|
||||
}
|
||||
else {
|
||||
QString msg = QObject::tr("Invalid desktop entry file: '%1'").arg(desktopEntryName);
|
||||
GErrorPtr err{G_IO_ERROR, G_IO_ERROR_FAILED, msg};
|
||||
showError(ctx, err);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FilePath BasicFileLauncher::handleShortcut(const FileInfoPtr& fileInfo, GAppLaunchContext* ctx) {
|
||||
auto target = fileInfo->target();
|
||||
auto scheme = CStrPtr{g_uri_parse_scheme(target.c_str())};
|
||||
if(scheme) {
|
||||
// collect the uri schemes we support
|
||||
if(strcmp(scheme.get(), "file") == 0
|
||||
|| strcmp(scheme.get(), "trash") == 0
|
||||
|| strcmp(scheme.get(), "network") == 0
|
||||
|| strcmp(scheme.get(), "computer") == 0) {
|
||||
return FilePath::fromUri(fileInfo->target().c_str());
|
||||
}
|
||||
else {
|
||||
// ask gio to launch the default handler for the uri scheme
|
||||
GAppInfoPtr app{g_app_info_get_default_for_uri_scheme(scheme.get()), false};
|
||||
FilePathList uris{FilePath::fromUri(fileInfo->target().c_str())};
|
||||
launchWithApp(app.get(), uris, ctx);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// see it as a local path
|
||||
return FilePath::fromLocalPath(fileInfo->target().c_str());
|
||||
}
|
||||
return FilePath();
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchExecutable(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx) {
|
||||
/* if it's an executable file, directly execute it. */
|
||||
auto filename = fileInfo->path().localPath();
|
||||
/* FIXME: we need to use eaccess/euidaccess here. */
|
||||
if(g_file_test(filename.get(), G_FILE_TEST_IS_EXECUTABLE)) {
|
||||
auto act = quickExec_ ? ExecAction::DIRECT_EXEC : askExecFile(fileInfo);
|
||||
int flags = G_APP_INFO_CREATE_NONE;
|
||||
switch(act) {
|
||||
case ExecAction::EXEC_IN_TERMINAL:
|
||||
flags |= G_APP_INFO_CREATE_NEEDS_TERMINAL;
|
||||
/* Falls through. */
|
||||
case ExecAction::DIRECT_EXEC: {
|
||||
/* filename may contain spaces. Fix #3143296 */
|
||||
CStrPtr quoted{g_shell_quote(filename.get())};
|
||||
// FIXME: remove libfm dependency
|
||||
GAppInfoPtr app{fm_app_info_create_from_commandline(quoted.get(), nullptr, GAppInfoCreateFlags(flags), nullptr)};
|
||||
if(app) {
|
||||
CStrPtr run_path{g_path_get_dirname(filename.get())};
|
||||
CStrPtr cwd;
|
||||
/* bug #3589641: scripts are ran from $HOME.
|
||||
since GIO launcher is kinda ugly - it has
|
||||
no means to set running directory so we
|
||||
do workaround - change directory to it */
|
||||
if(run_path && strcmp(run_path.get(), ".")) {
|
||||
cwd = CStrPtr{g_get_current_dir()};
|
||||
if(chdir(run_path.get()) != 0) {
|
||||
cwd.reset();
|
||||
// show errors
|
||||
QString msg = QObject::tr("Cannot set working directory to '%1': %2").arg(run_path.get()).arg(g_strerror(errno));
|
||||
GErrorPtr err{G_IO_ERROR, g_io_error_from_errno(errno), msg};
|
||||
showError(ctx, err);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: remove libfm dependency
|
||||
GErrorPtr err;
|
||||
if(!fm_app_info_launch(app.get(), nullptr, ctx, &err)) {
|
||||
showError(ctx, err);
|
||||
}
|
||||
if(cwd) { /* return back */
|
||||
if(chdir(cwd.get()) != 0) {
|
||||
g_warning("fm_launch_files(): chdir() failed");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExecAction::OPEN_WITH_DEFAULT_APP:
|
||||
return launchWithDefaultApp(fileInfo, ctx);
|
||||
case ExecAction::CANCEL:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchWithDefaultApp(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx) {
|
||||
FileInfoList files;
|
||||
files.emplace_back(fileInfo);
|
||||
GErrorPtr err;
|
||||
GAppInfoPtr app{g_app_info_get_default_for_type(fileInfo->mimeType()->name(), false), false};
|
||||
if(app) {
|
||||
return launchWithApp(app.get(), files.paths(), ctx);
|
||||
}
|
||||
else {
|
||||
showError(ctx, err, fileInfo->path());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,72 @@
|
||||
#ifndef BASICFILELAUNCHER_H
|
||||
#define BASICFILELAUNCHER_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
|
||||
#include "fileinfo.h"
|
||||
#include "filepath.h"
|
||||
#include "mimetype.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API BasicFileLauncher {
|
||||
public:
|
||||
|
||||
enum class ExecAction {
|
||||
NONE,
|
||||
DIRECT_EXEC,
|
||||
EXEC_IN_TERMINAL,
|
||||
OPEN_WITH_DEFAULT_APP,
|
||||
CANCEL
|
||||
};
|
||||
|
||||
explicit BasicFileLauncher();
|
||||
virtual ~BasicFileLauncher();
|
||||
|
||||
bool launchFiles(const FileInfoList &fileInfos, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchPaths(FilePathList paths, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchDesktopEntry(const FileInfoPtr &fileInfo, const FilePathList& paths = FilePathList{}, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchDesktopEntry(const char* desktopEntryName, const FilePathList& paths = FilePathList{}, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchWithDefaultApp(const FileInfoPtr& fileInfo, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchWithApp(GAppInfo* app, const FilePathList& paths, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchExecutable(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool quickExec() const {
|
||||
return quickExec_;
|
||||
}
|
||||
|
||||
void setQuickExec(bool value) {
|
||||
quickExec_ = value;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
virtual GAppInfoPtr chooseApp(const FileInfoList& fileInfos, const char* mimeType, GErrorPtr& err);
|
||||
|
||||
virtual bool openFolder(GAppLaunchContext* ctx, const FileInfoList& folderInfos, GErrorPtr& err);
|
||||
|
||||
virtual bool showError(GAppLaunchContext* ctx, GErrorPtr& err, const FilePath& path = FilePath{}, const FileInfoPtr& info = FileInfoPtr{});
|
||||
|
||||
virtual ExecAction askExecFile(const FileInfoPtr& file);
|
||||
|
||||
virtual int ask(const char* msg, char* const* btn_labels, int default_btn);
|
||||
|
||||
private:
|
||||
|
||||
FilePath handleShortcut(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
private:
|
||||
bool quickExec_; // Don't ask options on launch executable file
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // BASICFILELAUNCHER_H
|
@ -1,53 +0,0 @@
|
||||
#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
|
@ -1,453 +0,0 @@
|
||||
#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
|
@ -1,46 +0,0 @@
|
||||
#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
|
@ -1,9 +1,324 @@
|
||||
#include "filechangeattrjob.h"
|
||||
#include "totalsizejob.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileChangeAttrJob::FileChangeAttrJob() {
|
||||
static const char query[] = G_FILE_ATTRIBUTE_STANDARD_TYPE","
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME","
|
||||
G_FILE_ATTRIBUTE_UNIX_GID","
|
||||
G_FILE_ATTRIBUTE_UNIX_UID","
|
||||
G_FILE_ATTRIBUTE_UNIX_MODE","
|
||||
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME;
|
||||
|
||||
FileChangeAttrJob::FileChangeAttrJob(FilePathList paths):
|
||||
paths_{std::move(paths)},
|
||||
recursive_{false},
|
||||
// chmod
|
||||
fileModeEnabled_{false},
|
||||
newMode_{0},
|
||||
newModeMask_{0},
|
||||
// chown
|
||||
ownerEnabled_{false},
|
||||
uid_{0},
|
||||
groupEnabled_{false},
|
||||
gid_{0},
|
||||
// Display name
|
||||
displayNameEnabled_{false},
|
||||
// icon
|
||||
iconEnabled_{false},
|
||||
// hidden
|
||||
hiddenEnabled_{false},
|
||||
hidden_{false},
|
||||
// target uri
|
||||
targetUriEnabled_{false} {
|
||||
|
||||
// the progress of chmod/chown is not related to file size
|
||||
setCalcProgressUsingSize(false);
|
||||
}
|
||||
|
||||
void FileChangeAttrJob::exec() {
|
||||
// count total amount of the work
|
||||
if(recursive_) {
|
||||
TotalSizeJob totalSizeJob{paths_};
|
||||
connect(&totalSizeJob, &TotalSizeJob::error, this, &FileChangeAttrJob::error);
|
||||
connect(this, &FileChangeAttrJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
|
||||
totalSizeJob.run();
|
||||
std::uint64_t totalSize, totalCount;
|
||||
totalSizeJob.totalAmount(totalSize, totalCount);
|
||||
setTotalAmount(totalSize, totalCount);
|
||||
}
|
||||
else {
|
||||
setTotalAmount(paths_.size(), paths_.size());
|
||||
}
|
||||
|
||||
// ready to start
|
||||
Q_EMIT preparedToRun();
|
||||
|
||||
// do the actual change attrs job
|
||||
for(auto& path : paths_) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr info{
|
||||
g_file_query_info(path.gfile().get(), query,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(info) {
|
||||
processFile(path, info);
|
||||
}
|
||||
else {
|
||||
handleError(err, path, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::processFile(const FilePath& path, const GFileInfoPtr& info) {
|
||||
setCurrentFile(path);
|
||||
bool ret = true;
|
||||
|
||||
if(ownerEnabled_) {
|
||||
changeFileOwner(path, info, uid_);
|
||||
}
|
||||
if(groupEnabled_) {
|
||||
changeFileGroup(path, info, gid_);
|
||||
}
|
||||
if(fileModeEnabled_) {
|
||||
changeFileMode(path, info, newMode_, newModeMask_);
|
||||
}
|
||||
/* change display name, icon, hidden, target */
|
||||
if(displayNameEnabled_ && !displayName().empty()) {
|
||||
changeFileDisplayName(path, info, displayName_.c_str());
|
||||
}
|
||||
if(iconEnabled_ && icon_) {
|
||||
changeFileIcon(path, info, icon_);
|
||||
}
|
||||
if(hiddenEnabled_) {
|
||||
changeFileHidden(path, info, hidden_);
|
||||
}
|
||||
if(targetUriEnabled_ && !targetUri_.empty()) {
|
||||
changeFileTargetUri(path, info, targetUri_.c_str());
|
||||
}
|
||||
|
||||
// FIXME: do not use size 1 here.
|
||||
addFinishedAmount(1, 1);
|
||||
|
||||
// recursively apply to subfolders
|
||||
auto type = g_file_info_get_file_type(info.get());
|
||||
if(!isCancelled() && recursive_ && type == G_FILE_TYPE_DIRECTORY) {
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
GErrorPtr err;
|
||||
GFileEnumeratorPtr enu{
|
||||
g_file_enumerate_children(path.gfile().get(), query,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(enu) {
|
||||
while(!isCancelled()) {
|
||||
err.reset();
|
||||
GFileInfoPtr childInfo{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
|
||||
if(childInfo) {
|
||||
auto childPath = path.child(g_file_info_get_name(childInfo.get()));
|
||||
ret = processFile(childPath, childInfo);
|
||||
if(!ret) { /* _fm_file_ops_job_change_attr_file() failed */
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
handleError(err, path, info, ErrorSeverity::MILD);
|
||||
retry = false;
|
||||
/* FM_JOB_RETRY is not supported here */
|
||||
}
|
||||
else { /* EOF */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_file_enumerator_close(enu.get(), cancellable().get(), nullptr);
|
||||
}
|
||||
else {
|
||||
retry = handleError(err, path, info);
|
||||
}
|
||||
} while(!isCancelled() && retry);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::handleError(GErrorPtr &err, const FilePath &path, const GFileInfoPtr &info, ErrorSeverity severity) {
|
||||
auto act = emitError(err, severity);
|
||||
if (act == ErrorAction::RETRY) {
|
||||
err.reset();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileOwner(const FilePath& path, const GFileInfoPtr& info, uid_t uid) {
|
||||
/* change owner */
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_UID,
|
||||
uid, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileGroup(const FilePath& path, const GFileInfoPtr& info, gid_t gid) {
|
||||
/* change group */
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_GID,
|
||||
gid, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileMode(const FilePath& path, const GFileInfoPtr& info, mode_t newMode, mode_t newModeMask) {
|
||||
bool ret = false;
|
||||
/* change mode */
|
||||
if(newModeMask) {
|
||||
guint32 mode = g_file_info_get_attribute_uint32(info.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
|
||||
mode &= ~newModeMask;
|
||||
mode |= (newMode & newModeMask);
|
||||
|
||||
auto type = g_file_info_get_file_type(info.get());
|
||||
/* FIXME: this behavior should be optional. */
|
||||
/* treat dirs with 'r' as 'rx' */
|
||||
if(type == G_FILE_TYPE_DIRECTORY) {
|
||||
if((newModeMask & S_IRUSR) && (mode & S_IRUSR)) {
|
||||
mode |= S_IXUSR;
|
||||
}
|
||||
if((newModeMask & S_IRGRP) && (mode & S_IRGRP)) {
|
||||
mode |= S_IXGRP;
|
||||
}
|
||||
if((newModeMask & S_IROTH) && (mode & S_IROTH)) {
|
||||
mode |= S_IXOTH;
|
||||
}
|
||||
}
|
||||
|
||||
/* new mode */
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_MODE,
|
||||
mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileDisplayName(const FilePath& path, const GFileInfoPtr& info, const char* displayName) {
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_display_name(path.gfile().get(), displayName, cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileIcon(const FilePath& path, const GFileInfoPtr& info, GIconPtr& icon) {
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_ICON,
|
||||
G_FILE_ATTRIBUTE_TYPE_OBJECT, icon.get(),
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileHidden(const FilePath& path, const GFileInfoPtr& info, bool hidden) {
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
gboolean g_hidden = hidden;
|
||||
if(!g_file_set_attribute(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
|
||||
G_FILE_ATTRIBUTE_TYPE_BOOLEAN, &g_hidden,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileTargetUri(const FilePath& path, const GFileInfoPtr& info, const char* targetUri) {
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute_string(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
|
||||
targetUri, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
||||
|
@ -0,0 +1,641 @@
|
||||
#include "filetransferjob.h"
|
||||
#include "totalsizejob.h"
|
||||
#include "fileinfo_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileTransferJob::FileTransferJob(FilePathList srcPaths, Mode mode):
|
||||
FileOperationJob{},
|
||||
srcPaths_{std::move(srcPaths)},
|
||||
mode_{mode} {
|
||||
}
|
||||
|
||||
FileTransferJob::FileTransferJob(FilePathList srcPaths, FilePathList destPaths, Mode mode):
|
||||
FileTransferJob{std::move(srcPaths), mode} {
|
||||
destPaths_ = std::move(destPaths);
|
||||
}
|
||||
|
||||
FileTransferJob::FileTransferJob(FilePathList srcPaths, const FilePath& destDirPath, Mode mode):
|
||||
FileTransferJob{std::move(srcPaths), mode} {
|
||||
setDestDirPath(destDirPath);
|
||||
}
|
||||
|
||||
void FileTransferJob::setSrcPaths(FilePathList srcPaths) {
|
||||
srcPaths_ = std::move(srcPaths);
|
||||
}
|
||||
|
||||
void FileTransferJob::setDestPaths(FilePathList destPaths) {
|
||||
destPaths_ = std::move(destPaths);
|
||||
}
|
||||
|
||||
void FileTransferJob::setDestDirPath(const FilePath& destDirPath) {
|
||||
destPaths_.clear();
|
||||
destPaths_.reserve(srcPaths_.size());
|
||||
for(const auto& srcPath: srcPaths_) {
|
||||
FilePath destPath;
|
||||
if(mode_ == Mode::LINK && !srcPath.isNative()) {
|
||||
// special handling for URLs
|
||||
auto fullBasename = srcPath.baseName();
|
||||
char* basename = fullBasename.get();
|
||||
char* dname = nullptr;
|
||||
// if we drop URI query onto native filesystem, omit query part
|
||||
if(!srcPath.isNative()) {
|
||||
dname = strchr(basename, '?');
|
||||
}
|
||||
// if basename consist only from query then use first part of it
|
||||
if(dname == basename) {
|
||||
basename++;
|
||||
dname = strchr(basename, '&');
|
||||
}
|
||||
|
||||
CStrPtr _basename;
|
||||
if(dname) {
|
||||
_basename = CStrPtr{g_strndup(basename, dname - basename)};
|
||||
dname = strrchr(_basename.get(), G_DIR_SEPARATOR);
|
||||
g_debug("cutting '%s' to '%s'", basename, dname ? &dname[1] : _basename.get());
|
||||
if(dname) {
|
||||
basename = &dname[1];
|
||||
}
|
||||
else {
|
||||
basename = _basename.get();
|
||||
}
|
||||
}
|
||||
destPath = destDirPath.child(basename);
|
||||
}
|
||||
else {
|
||||
destPath = destDirPath.child(srcPath.baseName().get());
|
||||
}
|
||||
destPaths_.emplace_back(std::move(destPath));
|
||||
}
|
||||
}
|
||||
|
||||
void FileTransferJob::gfileCopyProgressCallback(goffset current_num_bytes, goffset total_num_bytes, FileTransferJob* _this) {
|
||||
_this->setCurrentFileProgress(total_num_bytes, current_num_bytes);
|
||||
}
|
||||
|
||||
bool FileTransferJob::moveFileSameFs(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath) {
|
||||
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
||||
GErrorPtr err;
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
err.reset();
|
||||
// do the file operation
|
||||
if(!g_file_move(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
|
||||
nullptr, this, &err)) {
|
||||
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileTransferJob::copyRegularFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath) {
|
||||
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
||||
GErrorPtr err;
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
err.reset();
|
||||
|
||||
// reset progress of the current file (only for copy)
|
||||
auto size = g_file_info_get_size(srcInfo.get());
|
||||
setCurrentFileProgress(size, 0);
|
||||
|
||||
// do the file operation
|
||||
if(!g_file_copy(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
|
||||
(GFileProgressCallback)&gfileCopyProgressCallback, this, &err)) {
|
||||
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileTransferJob::copySpecialFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath) {
|
||||
bool ret = false;
|
||||
// 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) {
|
||||
GErrorPtr err;
|
||||
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
("Cannot copy file '%s': not supported"),
|
||||
g_file_info_get_display_name(srcInfo.get()));
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::copyDirContent(const FilePath& srcPath, GFileInfoPtr srcInfo, FilePath& destPath, bool skip) {
|
||||
bool ret = false;
|
||||
// copy dir content
|
||||
GErrorPtr err;
|
||||
auto enu = GFileEnumeratorPtr{
|
||||
g_file_enumerate_children(srcPath.gfile().get(),
|
||||
defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false};
|
||||
if(enu) {
|
||||
int n_children = 0;
|
||||
int n_copied = 0;
|
||||
ret = true;
|
||||
while(!isCancelled()) {
|
||||
err.reset();
|
||||
GFileInfoPtr inf{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
|
||||
if(inf) {
|
||||
++n_children;
|
||||
const char* name = g_file_info_get_name(inf.get());
|
||||
FilePath childPath = srcPath.child(name);
|
||||
bool child_ret = copyFile(childPath, inf, destPath, name, skip);
|
||||
if(child_ret) {
|
||||
++n_copied;
|
||||
}
|
||||
else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
// fail to read directory content
|
||||
// NOTE: since we cannot read the source dir, we cannot calculate the progress correctly, either.
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
err.reset();
|
||||
/* 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) {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
/* else job->skip_dir_content is true */
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_file_enumerator_close(enu.get(), nullptr, &err);
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::makeDir(const FilePath& srcPath, GFileInfoPtr srcInfo, FilePath& destPath) {
|
||||
if(isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mkdir_done = false;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
mkdir_done = g_file_make_directory_with_parents(destPath.gfile().get(), cancellable().get(), &err);
|
||||
if(!mkdir_done) {
|
||||
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 destInfo = GFileInfoPtr {
|
||||
g_file_query_info(destPath.gfile().get(),
|
||||
defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), nullptr),
|
||||
false
|
||||
};
|
||||
if(!destInfo) {
|
||||
// FIXME: error handling
|
||||
break;
|
||||
}
|
||||
|
||||
FilePath newDestPath;
|
||||
FileExistsAction opt = askRename(FileInfo{srcInfo, srcPath.parent()}, FileInfo{destInfo, destPath.parent()}, newDestPath);
|
||||
switch(opt) {
|
||||
case FileOperationJob::RENAME:
|
||||
destPath = std::move(newDestPath);
|
||||
break;
|
||||
case FileOperationJob::SKIP:
|
||||
/* when a dir is skipped, we need to know its total size to calculate correct progress */
|
||||
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();
|
||||
return false;
|
||||
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
|
||||
}
|
||||
}
|
||||
else {
|
||||
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
||||
if(act != ErrorAction::RETRY) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(!mkdir_done && !isCancelled());
|
||||
|
||||
bool chmod_done = false;
|
||||
if(mkdir_done && !isCancelled()) {
|
||||
mode_t mode = g_file_info_get_attribute_uint32(srcInfo.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
|
||||
if(mode) {
|
||||
mode |= (S_IRUSR | S_IWUSR); /* ensure we have rw permission to this file. */
|
||||
do {
|
||||
GErrorPtr err;
|
||||
// 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);
|
||||
if(act != ErrorAction::RETRY) {
|
||||
break;
|
||||
}
|
||||
/* FIXME: some filesystems may not support this. */
|
||||
}
|
||||
} while(!chmod_done && !isCancelled());
|
||||
}
|
||||
}
|
||||
return mkdir_done && chmod_done;
|
||||
}
|
||||
|
||||
bool FileTransferJob::handleError(GErrorPtr &err, const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath, int& flags) {
|
||||
bool retry = false;
|
||||
/* 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)) {
|
||||
flags &= ~G_FILE_COPY_OVERWRITE;
|
||||
|
||||
// get info of the existing file
|
||||
GFileInfoPtr destInfo = GFileInfoPtr {
|
||||
g_file_query_info(destPath.gfile().get(),
|
||||
defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), nullptr),
|
||||
false
|
||||
};
|
||||
|
||||
// ask the user to rename or overwrite the existing file
|
||||
if(!isCancelled() && destInfo) {
|
||||
FilePath newDestPath;
|
||||
FileExistsAction opt = askRename(FileInfo{srcInfo, srcPath.parent()},
|
||||
FileInfo{destInfo, destPath.parent()},
|
||||
newDestPath);
|
||||
switch(opt) {
|
||||
case FileOperationJob::RENAME:
|
||||
// try a new file name
|
||||
if(newDestPath.isValid()) {
|
||||
destPath = std::move(newDestPath);
|
||||
// FIXME: handle the error when newDestPath is invalid.
|
||||
}
|
||||
retry = true;
|
||||
break;
|
||||
case FileOperationJob::OVERWRITE:
|
||||
// overwrite existing file
|
||||
flags |= G_FILE_COPY_OVERWRITE;
|
||||
retry = true;
|
||||
err.reset();
|
||||
break;
|
||||
case FileOperationJob::CANCEL:
|
||||
// cancel the whole job.
|
||||
cancel();
|
||||
break;
|
||||
case FileOperationJob::SKIP:
|
||||
// skip current file and don't copy it
|
||||
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
|
||||
retry = false;
|
||||
break;
|
||||
}
|
||||
err.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// show error message
|
||||
if(!isCancelled() && err) {
|
||||
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
||||
err.reset();
|
||||
if(act == ErrorAction::RETRY) {
|
||||
// the user wants retry the operation again
|
||||
retry = true;
|
||||
}
|
||||
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) {
|
||||
// run out of disk space. delete the partial content we copied.
|
||||
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
||||
}
|
||||
}
|
||||
return retry;
|
||||
}
|
||||
|
||||
bool FileTransferJob::processPath(const FilePath& srcPath, const FilePath& destDirPath, const char* destFileName) {
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr srcInfo = GFileInfoPtr {
|
||||
g_file_query_info(srcPath.gfile().get(),
|
||||
defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(!srcInfo || isCancelled()) {
|
||||
// FIXME: report error
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret;
|
||||
switch(mode_) {
|
||||
case Mode::MOVE:
|
||||
ret = moveFile(srcPath, srcInfo, destDirPath, destFileName);
|
||||
break;
|
||||
case Mode::COPY: {
|
||||
bool deleteSrc = false;
|
||||
ret = copyFile(srcPath, srcInfo, destDirPath, destFileName, deleteSrc);
|
||||
break;
|
||||
}
|
||||
case Mode::LINK:
|
||||
ret = linkFile(srcPath, srcInfo, destDirPath, destFileName);
|
||||
break;
|
||||
default:
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::moveFile(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()) {
|
||||
// FIXME: report errors
|
||||
return false;
|
||||
}
|
||||
|
||||
// If src and dest are on the same filesystem, do move.
|
||||
// Exception: if src FS is trash:///, we always do move
|
||||
// Otherwise, do copy & delete src files.
|
||||
auto src_fs = g_file_info_get_attribute_string(srcInfo.get(), "id::filesystem");
|
||||
auto dest_fs = g_file_info_get_attribute_string(destDirInfo.get(), "id::filesystem");
|
||||
bool ret;
|
||||
if(src_fs && dest_fs && (strcmp(src_fs, dest_fs) == 0 || g_str_has_prefix(src_fs, "trash"))) {
|
||||
// src and dest are on the same filesystem
|
||||
auto destPath = destDirPath.child(destFileName);
|
||||
ret = moveFileSameFs(srcPath, srcInfo, destPath);
|
||||
|
||||
// increase current progress
|
||||
// FIXME: it's not appropriate to calculate the progress of move operations using file size
|
||||
// since the time required to move a file is not related to it's file size.
|
||||
auto size = g_file_info_get_size(srcInfo.get());
|
||||
addFinishedAmount(size, 1);
|
||||
}
|
||||
else {
|
||||
// cross device/filesystem move: copy & delete
|
||||
ret = copyFile(srcPath, srcInfo, destDirPath, destFileName);
|
||||
// NOTE: do not need to increase progress here since it's done by copyPath().
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::copyFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, const FilePath& destDirPath, const char* destFileName, bool skip) {
|
||||
setCurrentFile(srcPath);
|
||||
|
||||
auto size = g_file_info_get_size(srcInfo.get());
|
||||
bool success = false;
|
||||
setCurrentFileProgress(size, 0);
|
||||
|
||||
auto destPath = destDirPath.child(destFileName);
|
||||
auto file_type = g_file_info_get_file_type(srcInfo.get());
|
||||
if(!skip) {
|
||||
switch(file_type) {
|
||||
case G_FILE_TYPE_DIRECTORY:
|
||||
success = makeDir(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
case G_FILE_TYPE_SPECIAL:
|
||||
success = copySpecialFile(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
default:
|
||||
success = copyRegularFile(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else { // skip the file
|
||||
success = true;
|
||||
}
|
||||
|
||||
if(success) {
|
||||
// finish copying the file
|
||||
addFinishedAmount(size, 1);
|
||||
setCurrentFileProgress(0, 0);
|
||||
|
||||
// recursively copy dir content
|
||||
if(file_type == G_FILE_TYPE_DIRECTORY) {
|
||||
success = copyDirContent(srcPath, srcInfo, destPath, skip);
|
||||
}
|
||||
|
||||
if(!skip && success && mode_ == Mode::MOVE) {
|
||||
// delete the source file for cross-filesystem move
|
||||
GErrorPtr err;
|
||||
if(g_file_delete(srcPath.gfile().get(), cancellable().get(), &err)) {
|
||||
// FIXME: add some file size to represent the amount of work need to delete a file
|
||||
addFinishedAmount(1, 1);
|
||||
}
|
||||
else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool FileTransferJob::linkFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName) {
|
||||
setCurrentFile(srcPath);
|
||||
|
||||
bool ret = false;
|
||||
// cannot create links on non-native filesystems
|
||||
if(!destDirPath.isNative()) {
|
||||
auto msg = tr("Cannot create a link on non-native filesystem");
|
||||
GErrorPtr err{g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED, msg.toUtf8().constData())};
|
||||
emitError(err, ErrorSeverity::CRITICAL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(srcPath.isNative()) {
|
||||
// create symlinks for native files
|
||||
auto destPath = destDirPath.child(destFileName);
|
||||
ret = createSymlink(srcPath, srcInfo, destPath);
|
||||
}
|
||||
else {
|
||||
// ensure that the dest file has *.desktop filename extension.
|
||||
CStrPtr desktopEntryFileName{g_strconcat(destFileName, ".desktop", nullptr)};
|
||||
auto destPath = destDirPath.child(desktopEntryFileName.get());
|
||||
ret = createShortcut(srcPath, srcInfo, destPath);
|
||||
}
|
||||
|
||||
// update progress
|
||||
// FIXME: increase the progress by 1 byte is not appropriate
|
||||
addFinishedAmount(1, 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::createSymlink(const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath) {
|
||||
bool ret = false;
|
||||
auto src = srcPath.localPath();
|
||||
int flags = 0;
|
||||
GErrorPtr err;
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
if(flags & G_FILE_COPY_OVERWRITE) { // overwrite existing file
|
||||
// creating symlink cannot overwrite existing files directly, so we delete the existing file first.
|
||||
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
||||
}
|
||||
if(!g_file_make_symbolic_link(destPath.gfile().get(), src.get(), cancellable().get(), &err)) {
|
||||
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
} while(!isCancelled() && retry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::createShortcut(const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath) {
|
||||
bool ret = false;
|
||||
const char* iconName = nullptr;
|
||||
GIcon* icon = g_file_info_get_icon(srcInfo.get());
|
||||
if(icon && G_IS_THEMED_ICON(icon)) {
|
||||
auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(icon));
|
||||
if(iconNames && iconNames[0]) {
|
||||
iconName = iconNames[0];
|
||||
}
|
||||
}
|
||||
|
||||
CStrPtr srcPathUri;
|
||||
auto uri = g_file_info_get_attribute_string(srcInfo.get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
||||
if(!uri) {
|
||||
srcPathUri = srcPath.uri();
|
||||
uri = srcPathUri.get();
|
||||
}
|
||||
|
||||
CStrPtr srcPathDispName;
|
||||
auto name = g_file_info_get_display_name(srcInfo.get());
|
||||
if(!name) {
|
||||
srcPathDispName = srcPath.displayName();
|
||||
name = srcPathDispName.get();
|
||||
}
|
||||
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(kf) {
|
||||
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, "Link");
|
||||
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, name);
|
||||
if(iconName) {
|
||||
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, iconName);
|
||||
}
|
||||
if(uri) {
|
||||
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, uri);
|
||||
}
|
||||
gsize contentLen;
|
||||
CStrPtr content{g_key_file_to_data(kf, &contentLen, nullptr)};
|
||||
g_key_file_free(kf);
|
||||
|
||||
int flags = 0;
|
||||
if(content) {
|
||||
bool retry;
|
||||
GErrorPtr err;
|
||||
do {
|
||||
retry = false;
|
||||
if(flags & G_FILE_COPY_OVERWRITE) { // overwrite existing file
|
||||
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
||||
}
|
||||
|
||||
if(!g_file_replace_contents(destPath.gfile().get(), content.get(), contentLen, nullptr, false, G_FILE_CREATE_NONE, nullptr, cancellable().get(), &err)) {
|
||||
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
}
|
||||
} while(!isCancelled() && retry);
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void FileTransferJob::exec() {
|
||||
// calculate the total size of files to copy
|
||||
auto totalSizeFlags = (mode_ == Mode::COPY ? TotalSizeJob::DEFAULT : TotalSizeJob::PREPARE_MOVE);
|
||||
TotalSizeJob totalSizeJob{srcPaths_, totalSizeFlags};
|
||||
connect(&totalSizeJob, &TotalSizeJob::error, this, &FileTransferJob::error);
|
||||
connect(this, &FileTransferJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
|
||||
totalSizeJob.run();
|
||||
if(isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ready to start
|
||||
setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount());
|
||||
Q_EMIT preparedToRun();
|
||||
|
||||
if(srcPaths_.size() != destPaths_.size()) {
|
||||
qWarning("error: srcPaths.size() != destPaths.size() when copying files");
|
||||
return;
|
||||
}
|
||||
|
||||
// copy the files
|
||||
for(size_t i = 0; i < srcPaths_.size(); ++i) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
const auto& srcPath = srcPaths_[i];
|
||||
const auto& destPath = destPaths_[i];
|
||||
auto destDirPath = destPath.parent();
|
||||
processPath(srcPath, destDirPath, destPath.baseName().get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,58 @@
|
||||
#ifndef FM2_COPYJOB_H
|
||||
#define FM2_COPYJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileTransferJob : public Fm::FileOperationJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
enum class Mode {
|
||||
COPY,
|
||||
MOVE,
|
||||
LINK
|
||||
};
|
||||
|
||||
explicit FileTransferJob(FilePathList srcPaths, Mode mode = Mode::COPY);
|
||||
explicit FileTransferJob(FilePathList srcPaths, FilePathList destPaths, Mode mode = Mode::COPY);
|
||||
explicit FileTransferJob(FilePathList srcPaths, const FilePath &destDirPath, Mode mode = Mode::COPY);
|
||||
|
||||
void setSrcPaths(FilePathList srcPaths);
|
||||
void setDestPaths(FilePathList destPaths);
|
||||
void setDestDirPath(const FilePath &destDirPath);
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
bool processPath(const FilePath& srcPath, const FilePath& destPath, const char *destFileName);
|
||||
bool moveFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName);
|
||||
bool copyFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName, bool skip = false);
|
||||
bool linkFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName);
|
||||
|
||||
bool moveFileSameFs(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath);
|
||||
bool copyRegularFile(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath);
|
||||
bool copySpecialFile(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
|
||||
bool copyDirContent(const FilePath &srcPath, GFileInfoPtr srcInfo, FilePath &destPath, bool skip = false);
|
||||
bool makeDir(const FilePath &srcPath, GFileInfoPtr srcInfo, FilePath &destPath);
|
||||
bool createSymlink(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
|
||||
bool createShortcut(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
|
||||
|
||||
bool handleError(GErrorPtr& err, const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath, int& flags);
|
||||
|
||||
static void gfileCopyProgressCallback(goffset current_num_bytes, goffset total_num_bytes, FileTransferJob* _this);
|
||||
|
||||
private:
|
||||
FilePathList srcPaths_;
|
||||
FilePathList destPaths_;
|
||||
Mode mode_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_COPYJOB_H
|
@ -0,0 +1,130 @@
|
||||
#include "templates.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <QDebug>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::weak_ptr<Templates> Templates::globalInstance_;
|
||||
|
||||
TemplateItem::TemplateItem(std::shared_ptr<const FileInfo> file): fileInfo_{file} {
|
||||
}
|
||||
|
||||
FilePath TemplateItem::filePath() const {
|
||||
auto& target = fileInfo_->target();
|
||||
if(fileInfo_->isDesktopEntry() && !target.empty()) {
|
||||
if(target[0] == '/') { // target is an absolute path
|
||||
return FilePath::fromLocalPath(target.c_str());
|
||||
}
|
||||
else { // resolve relative path
|
||||
return fileInfo_->dirPath().relativePath(target.c_str());
|
||||
}
|
||||
}
|
||||
return fileInfo_->path();
|
||||
}
|
||||
|
||||
Templates::Templates() : QObject() {
|
||||
auto* data_dirs = g_get_system_data_dirs();
|
||||
// system-wide template dirs
|
||||
for(auto data_dir = data_dirs; *data_dir; ++data_dir) {
|
||||
CStrPtr dir_name{g_build_filename(*data_dir, "templates", nullptr)};
|
||||
addTemplateDir(dir_name.get());
|
||||
}
|
||||
|
||||
// user-specific template dir
|
||||
CStrPtr dir_name{g_build_filename(g_get_user_data_dir(), "templates", nullptr)};
|
||||
addTemplateDir(dir_name.get());
|
||||
|
||||
// $XDG_TEMPLATES_DIR (FIXME: this might change at runtime)
|
||||
const gchar *special_dir = g_get_user_special_dir(G_USER_DIRECTORY_TEMPLATES);
|
||||
if (special_dir) {
|
||||
addTemplateDir(special_dir);
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Templates> Templates::globalInstance() {
|
||||
auto templates = globalInstance_.lock();
|
||||
if(!templates) {
|
||||
templates = make_shared<Templates>();
|
||||
globalInstance_ = templates;
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
void Templates::addTemplateDir(const char* dirPathName) {
|
||||
auto dir_path = FilePath::fromLocalPath(dirPathName);
|
||||
if(dir_path.isValid()) {
|
||||
auto folder = Folder::fromPath(dir_path);
|
||||
connect(folder.get(), &Folder::filesAdded, this, &Templates::onFilesAdded);
|
||||
connect(folder.get(), &Folder::filesChanged, this, &Templates::onFilesChanged);
|
||||
connect(folder.get(), &Folder::filesRemoved, this, &Templates::onFilesRemoved);
|
||||
connect(folder.get(), &Folder::removed, this, &Templates::onTemplateDirRemoved);
|
||||
templateFolders_.emplace_back(std::move(folder));
|
||||
}
|
||||
}
|
||||
|
||||
void Templates::onFilesAdded(FileInfoList& addedFiles) {
|
||||
for(auto& file : addedFiles) {
|
||||
// FIXME: we do not support subdirs right now (only XFCE supports this)
|
||||
if(file->isHidden() || file->isDir()) {
|
||||
continue;
|
||||
}
|
||||
items_.emplace_back(std::make_shared<TemplateItem>(file));
|
||||
// emit a signal for the addition
|
||||
Q_EMIT itemAdded(items_.back());
|
||||
}
|
||||
}
|
||||
|
||||
void Templates::onFilesChanged(std::vector<FileInfoPair>& changePairs) {
|
||||
for(auto& change: changePairs) {
|
||||
auto& old_file = change.first;
|
||||
auto& new_file = change.second;
|
||||
auto it = std::find_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
|
||||
return item->fileInfo() == old_file;
|
||||
});
|
||||
if(it != items_.end()) {
|
||||
// emit a signal for the change
|
||||
auto old = *it;
|
||||
*it = std::make_shared<TemplateItem>(new_file);
|
||||
Q_EMIT itemChanged(old, *it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Templates::onFilesRemoved(FileInfoList& removedFiles) {
|
||||
for(auto& file : removedFiles) {
|
||||
auto filePath = file->path();
|
||||
auto it = std::remove_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
|
||||
return item->fileInfo() == file;
|
||||
});
|
||||
for(auto removed_it = it; it != items_.end(); ++it) {
|
||||
// emit a signal for the removal
|
||||
Q_EMIT itemRemoved(*removed_it);
|
||||
}
|
||||
items_.erase(it, items_.end());
|
||||
}
|
||||
}
|
||||
|
||||
void Templates::onTemplateDirRemoved() {
|
||||
// the whole template dir is removed
|
||||
auto folder = static_cast<Folder*>(sender());
|
||||
if(!folder) {
|
||||
return;
|
||||
}
|
||||
auto dirPath = folder->path();
|
||||
|
||||
// remote all files under this dir
|
||||
auto it = std::remove_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
|
||||
return dirPath.isPrefixOf(item->filePath());
|
||||
});
|
||||
for(auto removed_it = it; it != items_.end(); ++it) {
|
||||
// emit a signal for the removal
|
||||
Q_EMIT itemRemoved(*removed_it);
|
||||
}
|
||||
items_.erase(it, items_.end());
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,100 @@
|
||||
#ifndef TEMPLATES_H
|
||||
#define TEMPLATES_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "folder.h"
|
||||
#include "fileinfo.h"
|
||||
#include "mimetype.h"
|
||||
#include "iconinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API TemplateItem {
|
||||
public:
|
||||
explicit TemplateItem(std::shared_ptr<const FileInfo> fileInfo);
|
||||
|
||||
QString displayName() const {
|
||||
return fileInfo_->displayName();
|
||||
}
|
||||
|
||||
const std::string& name() const {
|
||||
return fileInfo_->name();
|
||||
}
|
||||
|
||||
std::shared_ptr<const IconInfo> icon() const {
|
||||
return fileInfo_->icon();
|
||||
}
|
||||
|
||||
std::shared_ptr<const FileInfo> fileInfo() const {
|
||||
return fileInfo_;
|
||||
}
|
||||
|
||||
std::shared_ptr<const MimeType> mimeType() const {
|
||||
return fileInfo_->mimeType();
|
||||
}
|
||||
|
||||
FilePath filePath() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const FileInfo> fileInfo_;
|
||||
};
|
||||
|
||||
|
||||
class LIBFM_QT_API Templates : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Templates();
|
||||
|
||||
// FIXME: the first call to this method will get no templates since dir loading is in progress.
|
||||
static std::shared_ptr<Templates> globalInstance();
|
||||
|
||||
void forEachItem(std::function<void (const std::shared_ptr<const TemplateItem>&)> func) const {
|
||||
for(const auto& item : items_) {
|
||||
func(item);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const TemplateItem>> items() const {
|
||||
std::vector<std::shared_ptr<const TemplateItem>> tmp_items;
|
||||
for(auto& item: items_) {
|
||||
tmp_items.emplace_back(item);
|
||||
}
|
||||
return tmp_items;
|
||||
}
|
||||
|
||||
bool hasTemplates() const {
|
||||
return !items_.empty();
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void itemAdded(const std::shared_ptr<const TemplateItem>& item);
|
||||
|
||||
void itemChanged(const std::shared_ptr<const TemplateItem>& oldItem, const std::shared_ptr<const TemplateItem>& newItem);
|
||||
|
||||
void itemRemoved(const std::shared_ptr<const TemplateItem>& item);
|
||||
|
||||
private:
|
||||
void addTemplateDir(const char* dirPathName);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onFilesAdded(FileInfoList& addedFiles);
|
||||
|
||||
void onFilesChanged(std::vector<FileInfoPair>& changePairs);
|
||||
|
||||
void onFilesRemoved(FileInfoList& removedFiles);
|
||||
|
||||
void onTemplateDirRemoved();
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<TemplateItem>> items_;
|
||||
std::vector<std::shared_ptr<Folder>> templateFolders_;
|
||||
static std::weak_ptr<Templates> globalInstance_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // TEMPLATES_H
|
@ -1,132 +1,76 @@
|
||||
#include "untrashjob.h"
|
||||
#include "filetransferjob.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;
|
||||
UntrashJob::UntrashJob(FilePathList srcPaths):
|
||||
srcPaths_{std::move(srcPaths)} {
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
// preparing for the job
|
||||
FilePathList validSrcPaths;
|
||||
FilePathList origPaths;
|
||||
for(auto& srcPath: srcPaths_) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
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));
|
||||
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr srcInfo{
|
||||
g_file_query_info(srcPath.gfile().get(),
|
||||
"trash::orig-path",
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(),
|
||||
&err),
|
||||
false
|
||||
};
|
||||
if(srcInfo) {
|
||||
const char* orig_path_str = g_file_info_get_attribute_byte_string(srcInfo.get(), "trash::orig-path");
|
||||
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);
|
||||
validSrcPaths.emplace_back(srcPath);
|
||||
origPaths.emplace_back(FilePath::fromPathStr(orig_path_str));
|
||||
}
|
||||
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;
|
||||
}
|
||||
tr("Cannot untrash file '%s': original path not known").toUtf8().constData(),
|
||||
g_file_info_get_display_name(srcInfo.get()));
|
||||
// FIXME: do we need to retry here?
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
}
|
||||
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;
|
||||
// FIXME: do we need to retry here?
|
||||
emitError(err);
|
||||
}
|
||||
}
|
||||
|
||||
// collected original paths of the trashed files
|
||||
// use the file transfer job to handle the actual file move
|
||||
FileTransferJob fileTransferJob{std::move(validSrcPaths), std::move(origPaths), FileTransferJob::Mode::MOVE};
|
||||
// FIXME:
|
||||
// I'm not sure why specifying Qt::DirectConnection is needed here since the caller & receiver are in the same thread. :-(
|
||||
// However without this, the signals/slots here will cause deadlocks.
|
||||
connect(&fileTransferJob, &FileTransferJob::preparedToRun, this, &UntrashJob::preparedToRun, Qt::DirectConnection);
|
||||
connect(&fileTransferJob, &FileTransferJob::error, this, &UntrashJob::error, Qt::DirectConnection);
|
||||
connect(&fileTransferJob, &FileTransferJob::fileExists, this, &UntrashJob::fileExists, Qt::DirectConnection);
|
||||
|
||||
// cancel the file transfer subjob if the parent job is cancelled
|
||||
connect(this, &UntrashJob::cancelled, &fileTransferJob,
|
||||
[&fileTransferJob]() {
|
||||
if(!fileTransferJob.isCancelled()) {
|
||||
fileTransferJob.cancel();
|
||||
}
|
||||
g_object_unref(gf);
|
||||
++job->finished;
|
||||
fm_file_ops_job_emit_percent(job);
|
||||
}, Qt::DirectConnection);
|
||||
|
||||
// cancel the parent job if the file transfer subjob is cancelled
|
||||
connect(&fileTransferJob, &FileTransferJob::cancelled, this,
|
||||
[this]() {
|
||||
if(!isCancelled()) {
|
||||
cancel();
|
||||
}
|
||||
#endif
|
||||
}, Qt::DirectConnection);
|
||||
fileTransferJob.run();
|
||||
}
|
||||
|
||||
} // namespace Fm
|
||||
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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_FILEOPERATIONDIALOG_P_H
|
||||
#define FM_FILEOPERATIONDIALOG_P_H
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#include <QLabel>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class ElidedLabel : public QLabel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ElidedLabel(QWidget *parent = 0, Qt::WindowFlags f = Qt::WindowFlags()):
|
||||
QLabel(parent, f),
|
||||
lastWidth_(0) {
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
// set a min width to prevent the window from widening with long texts
|
||||
setMinimumWidth(fontMetrics().averageCharWidth() * 10);
|
||||
}
|
||||
|
||||
protected:
|
||||
// A simplified version of QLabel::paintEvent() without pixmap or shortcut but with eliding.
|
||||
void paintEvent(QPaintEvent* /*event*/) override {
|
||||
QRect cr = contentsRect().adjusted(margin(), margin(), -margin(), -margin());
|
||||
QString txt = text();
|
||||
// if the text is changed or its rect is resized (due to window resizing),
|
||||
// find whether it needs to be elided...
|
||||
if (txt != lastText_ || cr.width() != lastWidth_) {
|
||||
lastText_ = txt;
|
||||
lastWidth_ = cr.width();
|
||||
elidedText_ = fontMetrics().elidedText(txt, Qt::ElideMiddle, cr.width());
|
||||
}
|
||||
// ... then, draw the (elided) text
|
||||
if(!elidedText_.isEmpty()) {
|
||||
QPainter painter(this);
|
||||
QStyleOption opt;
|
||||
opt.initFrom(this);
|
||||
style()->drawItemText(&painter, cr, alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QString elidedText_;
|
||||
QString lastText_;
|
||||
int lastWidth_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FILEOPERATIONDIALOG_P_H
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 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
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "icontheme.h"
|
||||
#include <libfm/fm.h>
|
||||
#include <QList>
|
||||
#include <QIcon>
|
||||
#include <QtGlobal>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
|
||||
#include "core/iconinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
static IconTheme* theIconTheme = nullptr; // the global single instance of IconTheme.
|
||||
|
||||
IconTheme::IconTheme():
|
||||
currentThemeName_(QIcon::themeName()) {
|
||||
// NOTE: only one instance is allowed
|
||||
Q_ASSERT(theIconTheme == nullptr);
|
||||
Q_ASSERT(qApp != nullptr); // QApplication should exists before contructing IconTheme.
|
||||
|
||||
theIconTheme = this;
|
||||
|
||||
// We need to get notified when there is a QEvent::StyleChange event so
|
||||
// we can check if the current icon theme name is changed.
|
||||
// To do this, we can filter QApplication object itself to intercept
|
||||
// signals of all widgets, but this may be too inefficient.
|
||||
// So, we only filter the events on QDesktopWidget instead.
|
||||
qApp->desktop()->installEventFilter(this);
|
||||
}
|
||||
|
||||
IconTheme::~IconTheme() {
|
||||
}
|
||||
|
||||
IconTheme* IconTheme::instance() {
|
||||
return theIconTheme;
|
||||
}
|
||||
|
||||
// check if the icon theme name is changed and emit "changed()" signal if any change is detected.
|
||||
void IconTheme::checkChanged() {
|
||||
if(QIcon::themeName() != theIconTheme->currentThemeName_) {
|
||||
// if the icon theme is changed
|
||||
theIconTheme->currentThemeName_ = QIcon::themeName();
|
||||
// invalidate the cached data
|
||||
Fm::IconInfo::updateQIcons();
|
||||
Q_EMIT theIconTheme->changed();
|
||||
}
|
||||
}
|
||||
|
||||
// this method is called whenever there is an event on the QDesktopWidget object.
|
||||
bool IconTheme::eventFilter(QObject* obj, QEvent* event) {
|
||||
// we're only interested in the StyleChange event.
|
||||
// FIXME: QEvent::ThemeChange seems to be interal to Qt 5 and is not documented
|
||||
if(event->type() == QEvent::StyleChange || event->type() == QEvent::ThemeChange) {
|
||||
checkChanged(); // check if the icon theme is changed
|
||||
}
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 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_ICONTHEME_H
|
||||
#define FM_ICONTHEME_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QIcon>
|
||||
#include <QString>
|
||||
#include "libfm/fm.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API IconTheme: public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
IconTheme();
|
||||
~IconTheme();
|
||||
|
||||
static IconTheme* instance();
|
||||
|
||||
static void checkChanged(); // check if current icon theme name is changed
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed(); // emitted when the name of current icon theme is changed
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event);
|
||||
|
||||
private:
|
||||
QString currentThemeName_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_ICONTHEME_H
|
@ -1,441 +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_PATH_H__
|
||||
#define __LIBFM_QT_FM_PATH_H__
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QMetaType>
|
||||
#include "libfmqtglobals.h"
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
|
||||
class LIBFM_QT_API PathList {
|
||||
public:
|
||||
|
||||
|
||||
PathList(void ) {
|
||||
dataPtr_ = reinterpret_cast<FmPathList*>(fm_path_list_new());
|
||||
}
|
||||
|
||||
|
||||
PathList(FmPathList* dataPtr){
|
||||
dataPtr_ = dataPtr != nullptr ? reinterpret_cast<FmPathList*>(fm_list_ref(FM_LIST(dataPtr))) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// copy constructor
|
||||
PathList(const PathList& other) {
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<FmPathList*>(fm_list_ref(FM_LIST(other.dataPtr_))) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// move constructor
|
||||
PathList(PathList&& other) {
|
||||
dataPtr_ = reinterpret_cast<FmPathList*>(other.takeDataPtr());
|
||||
}
|
||||
|
||||
|
||||
// destructor
|
||||
~PathList() {
|
||||
if(dataPtr_ != nullptr) {
|
||||
fm_list_unref(FM_LIST(dataPtr_));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create a wrapper for the data pointer without increasing the reference count
|
||||
static PathList wrapPtr(FmPathList* dataPtr) {
|
||||
PathList obj;
|
||||
obj.dataPtr_ = reinterpret_cast<FmPathList*>(dataPtr);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// disown the managed data pointer
|
||||
FmPathList* takeDataPtr() {
|
||||
FmPathList* data = reinterpret_cast<FmPathList*>(dataPtr_);
|
||||
dataPtr_ = nullptr;
|
||||
return data;
|
||||
}
|
||||
|
||||
// get the raw pointer wrapped
|
||||
FmPathList* dataPtr() {
|
||||
return reinterpret_cast<FmPathList*>(dataPtr_);
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator FmPathList*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
// copy assignment
|
||||
PathList& operator=(const PathList& other) {
|
||||
if(dataPtr_ != nullptr) {
|
||||
fm_list_unref(FM_LIST(dataPtr_));
|
||||
}
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<FmPathList*>(fm_list_ref(FM_LIST(other.dataPtr_))) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// move assignment
|
||||
PathList& operator=(PathList&& other) {
|
||||
dataPtr_ = reinterpret_cast<FmPathList*>(other.takeDataPtr());
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isNull() {
|
||||
return (dataPtr_ == nullptr);
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
void writeUriList(GString* buf) {
|
||||
fm_path_list_write_uri_list(dataPtr(), buf);
|
||||
}
|
||||
|
||||
char* toUriList(void) {
|
||||
return fm_path_list_to_uri_list(dataPtr());
|
||||
}
|
||||
|
||||
unsigned int getLength() {
|
||||
return fm_path_list_get_length(dataPtr());
|
||||
}
|
||||
|
||||
bool isEmpty() {
|
||||
return fm_path_list_is_empty(dataPtr());
|
||||
}
|
||||
|
||||
FmPath* peekHead() {
|
||||
return fm_path_list_peek_head(dataPtr());
|
||||
}
|
||||
|
||||
GList* peekHeadLink() {
|
||||
return fm_path_list_peek_head_link(dataPtr());
|
||||
}
|
||||
|
||||
void pushTail(FmPath* path) {
|
||||
fm_path_list_push_tail(dataPtr(), path);
|
||||
}
|
||||
|
||||
static PathList newFromFileInfoGslist(GSList* fis) {
|
||||
return PathList::wrapPtr(fm_path_list_new_from_file_info_gslist(fis));
|
||||
}
|
||||
|
||||
|
||||
static PathList newFromFileInfoGlist(GList* fis) {
|
||||
return PathList::wrapPtr(fm_path_list_new_from_file_info_glist(fis));
|
||||
}
|
||||
|
||||
|
||||
static PathList newFromFileInfoList(FmFileInfoList* fis) {
|
||||
return PathList::wrapPtr(fm_path_list_new_from_file_info_list(fis));
|
||||
}
|
||||
|
||||
|
||||
static PathList newFromUris(char* const* uris) {
|
||||
return PathList::wrapPtr(fm_path_list_new_from_uris(uris));
|
||||
}
|
||||
|
||||
|
||||
static PathList newFromUriList(const char* uri_list) {
|
||||
return PathList::wrapPtr(fm_path_list_new_from_uri_list(uri_list));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
FmPathList* dataPtr_; // data pointer for the underlying C struct
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class LIBFM_QT_API Path {
|
||||
public:
|
||||
|
||||
|
||||
// default constructor
|
||||
Path() {
|
||||
dataPtr_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
Path(FmPath* dataPtr){
|
||||
dataPtr_ = dataPtr != nullptr ? reinterpret_cast<FmPath*>(fm_path_ref(dataPtr)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// copy constructor
|
||||
Path(const Path& other) {
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<FmPath*>(fm_path_ref(other.dataPtr_)) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
// move constructor
|
||||
Path(Path&& other) {
|
||||
dataPtr_ = reinterpret_cast<FmPath*>(other.takeDataPtr());
|
||||
}
|
||||
|
||||
|
||||
// destructor
|
||||
~Path() {
|
||||
if(dataPtr_ != nullptr) {
|
||||
fm_path_unref(dataPtr_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create a wrapper for the data pointer without increasing the reference count
|
||||
static Path wrapPtr(FmPath* dataPtr) {
|
||||
Path obj;
|
||||
obj.dataPtr_ = reinterpret_cast<FmPath*>(dataPtr);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// disown the managed data pointer
|
||||
FmPath* takeDataPtr() {
|
||||
FmPath* data = reinterpret_cast<FmPath*>(dataPtr_);
|
||||
dataPtr_ = nullptr;
|
||||
return data;
|
||||
}
|
||||
|
||||
// get the raw pointer wrapped
|
||||
FmPath* dataPtr() {
|
||||
return reinterpret_cast<FmPath*>(dataPtr_);
|
||||
}
|
||||
|
||||
// automatic type casting
|
||||
operator FmPath*() {
|
||||
return dataPtr();
|
||||
}
|
||||
|
||||
// copy assignment
|
||||
Path& operator=(const Path& other) {
|
||||
if(dataPtr_ != nullptr) {
|
||||
fm_path_unref(dataPtr_);
|
||||
}
|
||||
dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<FmPath*>(fm_path_ref(other.dataPtr_)) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// move assignment
|
||||
Path& operator=(Path&& other) {
|
||||
dataPtr_ = reinterpret_cast<FmPath*>(other.takeDataPtr());
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isNull() {
|
||||
return (dataPtr_ == nullptr);
|
||||
}
|
||||
|
||||
// methods
|
||||
bool isNative() {
|
||||
return fm_path_is_native(dataPtr());
|
||||
}
|
||||
|
||||
bool isTrash() {
|
||||
return fm_path_is_trash(dataPtr());
|
||||
}
|
||||
|
||||
bool isTrashRoot() {
|
||||
return fm_path_is_trash_root(dataPtr());
|
||||
}
|
||||
|
||||
bool isNativeOrTrash() {
|
||||
return fm_path_is_native_or_trash(dataPtr());
|
||||
}
|
||||
|
||||
int depth(void) {
|
||||
return fm_path_depth(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
bool equalStr(const gchar* str, int n) {
|
||||
return fm_path_equal_str(dataPtr(), str, n);
|
||||
}
|
||||
|
||||
|
||||
int compare(FmPath* p2) {
|
||||
return fm_path_compare(dataPtr(), p2);
|
||||
}
|
||||
|
||||
int compare(Path& p2) {
|
||||
return fm_path_compare(dataPtr(), p2.dataPtr());
|
||||
}
|
||||
|
||||
bool equal(FmPath* p2) {
|
||||
return fm_path_equal(dataPtr(), p2);
|
||||
}
|
||||
|
||||
bool equal(Path& p2) {
|
||||
return fm_path_equal(dataPtr(), p2.dataPtr());
|
||||
}
|
||||
|
||||
bool operator == (Path& other) {
|
||||
return fm_path_equal(dataPtr(), other.dataPtr());
|
||||
}
|
||||
|
||||
bool operator != (Path& other) {
|
||||
return !fm_path_equal(dataPtr(), other.dataPtr());
|
||||
}
|
||||
|
||||
bool operator < (Path& other) {
|
||||
return compare(other);
|
||||
}
|
||||
|
||||
bool operator > (Path& other) {
|
||||
return (other < *this);
|
||||
}
|
||||
|
||||
unsigned int hash(void) {
|
||||
return fm_path_hash(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
char* displayBasename(void) {
|
||||
return fm_path_display_basename(dataPtr());
|
||||
}
|
||||
|
||||
char* displayName(gboolean human_readable) {
|
||||
return fm_path_display_name(dataPtr(), human_readable);
|
||||
}
|
||||
|
||||
|
||||
GFile* toGfile(void) {
|
||||
return fm_path_to_gfile(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
char* toUri(void) {
|
||||
return fm_path_to_uri(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
char* toStr(void) {
|
||||
return fm_path_to_str(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
Path getSchemePath(void) {
|
||||
return Path(fm_path_get_scheme_path(dataPtr()));
|
||||
}
|
||||
|
||||
|
||||
bool hasPrefix(FmPath* prefix) {
|
||||
return fm_path_has_prefix(dataPtr(), prefix);
|
||||
}
|
||||
|
||||
|
||||
FmPathFlags getFlags(void) {
|
||||
return fm_path_get_flags(dataPtr());
|
||||
}
|
||||
|
||||
|
||||
Path getParent(void) {
|
||||
return Path(fm_path_get_parent(dataPtr()));
|
||||
}
|
||||
|
||||
|
||||
static Path getAppsMenu(void ) {
|
||||
return Path(fm_path_get_apps_menu());
|
||||
}
|
||||
|
||||
|
||||
static Path getTrash(void ) {
|
||||
return Path(fm_path_get_trash());
|
||||
}
|
||||
|
||||
|
||||
static Path getDesktop(void ) {
|
||||
return Path(fm_path_get_desktop());
|
||||
}
|
||||
|
||||
|
||||
static Path getHome(void ) {
|
||||
return Path(fm_path_get_home());
|
||||
}
|
||||
|
||||
|
||||
static Path getRoot(void ) {
|
||||
return Path(fm_path_get_root());
|
||||
}
|
||||
|
||||
|
||||
static Path newForGfile(GFile* gf) {
|
||||
return Path::wrapPtr(fm_path_new_for_gfile(gf));
|
||||
}
|
||||
|
||||
|
||||
Path newRelative(const char* rel) {
|
||||
return Path::wrapPtr(fm_path_new_relative(dataPtr(), rel));
|
||||
}
|
||||
|
||||
|
||||
Path newChildLen(const char* basename, int name_len) {
|
||||
return Path::wrapPtr(fm_path_new_child_len(dataPtr(), basename, name_len));
|
||||
}
|
||||
|
||||
|
||||
Path newChild(const char* basename) {
|
||||
return Path::wrapPtr(fm_path_new_child(dataPtr(), basename));
|
||||
}
|
||||
|
||||
|
||||
static Path newForCommandlineArg(const char* arg) {
|
||||
return Path::wrapPtr(fm_path_new_for_commandline_arg(arg));
|
||||
}
|
||||
|
||||
|
||||
static Path newForStr(const char* path_str) {
|
||||
return Path::wrapPtr(fm_path_new_for_str(path_str));
|
||||
}
|
||||
|
||||
|
||||
static Path newForDisplayName(const char* path_name) {
|
||||
return Path::wrapPtr(fm_path_new_for_display_name(path_name));
|
||||
}
|
||||
|
||||
|
||||
static Path newForUri(const char* uri) {
|
||||
return Path::wrapPtr(fm_path_new_for_uri(uri));
|
||||
}
|
||||
|
||||
|
||||
static Path newForPath(const char* path_name) {
|
||||
return Path::wrapPtr(fm_path_new_for_path(path_name));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
FmPath* dataPtr_; // data pointer for the underlying C struct
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_OPAQUE_POINTER(FmPath*)
|
||||
|
||||
#endif // __LIBFM_QT_FM_PATH_H__
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue