You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
509 lines
17 KiB
509 lines
17 KiB
#include "fileactioncondition.h"
|
|
#include "fileaction.h"
|
|
#include <string>
|
|
|
|
|
|
using namespace std;
|
|
|
|
namespace Fm {
|
|
|
|
FileActionCondition::FileActionCondition(GKeyFile *kf, const char* group) {
|
|
only_show_in = CStrArrayPtr{g_key_file_get_string_list(kf, group, "OnlyShowIn", nullptr, nullptr)};
|
|
not_show_in = CStrArrayPtr{g_key_file_get_string_list(kf, group, "NotShowIn", nullptr, nullptr)};
|
|
try_exec = CStrPtr{g_key_file_get_string(kf, group, "TryExec", nullptr)};
|
|
show_if_registered = CStrPtr{g_key_file_get_string(kf, group, "ShowIfRegistered", nullptr)};
|
|
show_if_true = CStrPtr{g_key_file_get_string(kf, group, "ShowIfTrue", nullptr)};
|
|
show_if_running = CStrPtr{g_key_file_get_string(kf, group, "ShowIfRunning", nullptr)};
|
|
mime_types = CStrArrayPtr{g_key_file_get_string_list(kf, group, "MimeTypes", nullptr, nullptr)};
|
|
base_names = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Basenames", nullptr, nullptr)};
|
|
match_case = g_key_file_get_boolean(kf, group, "Matchcase", nullptr);
|
|
|
|
CStrPtr selection_count_str{g_key_file_get_string(kf, group, "SelectionCount", nullptr)};
|
|
if(selection_count_str != nullptr) {
|
|
switch(selection_count_str[0]) {
|
|
case '<':
|
|
case '>':
|
|
case '=':
|
|
selection_count_cmp = selection_count_str[0];
|
|
selection_count = atoi(selection_count_str.get() + 1);
|
|
break;
|
|
default:
|
|
selection_count_cmp = '>';
|
|
selection_count = 0;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
selection_count_cmp = '>';
|
|
selection_count = 0;
|
|
}
|
|
|
|
schemes = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Schemes", nullptr, nullptr)};
|
|
folders = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Folders", nullptr, nullptr)};
|
|
auto caps = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Capabilities", nullptr, nullptr)};
|
|
|
|
// FIXME: implement Capabilities support
|
|
|
|
}
|
|
|
|
bool FileActionCondition::match_try_exec(const FileInfoList& files) {
|
|
if(try_exec != nullptr) {
|
|
// stdout.printf(" TryExec: %s\n", try_exec);
|
|
CStrPtr exec_path{g_find_program_in_path(FileActionObject::expand_str(try_exec.get(), files).c_str())};
|
|
if(!g_file_test(exec_path.get(), G_FILE_TEST_IS_EXECUTABLE)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_show_if_registered(const FileInfoList& files) {
|
|
if(show_if_registered != nullptr) {
|
|
// stdout.printf(" ShowIfRegistered: %s\n", show_if_registered);
|
|
auto service = FileActionObject::expand_str(show_if_registered.get(), files);
|
|
// References:
|
|
// http://people.freedesktop.org/~david/eggdbus-20091014/eggdbus-interface-org.freedesktop.DBus.html#eggdbus-method-org.freedesktop.DBus.NameHasOwner
|
|
// glib source code: gio/tests/gdbus-names.c
|
|
auto con = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
|
|
auto result = g_dbus_connection_call_sync(con,
|
|
"org.freedesktop.DBus",
|
|
"/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus",
|
|
"NameHasOwner",
|
|
g_variant_new("(s)", service.c_str()),
|
|
g_variant_type_new("(b)"),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1, nullptr, nullptr);
|
|
bool name_has_owner;
|
|
g_variant_get(result, "(b)", &name_has_owner);
|
|
g_variant_unref(result);
|
|
// stdout.printf("check if service: %s is in use: %d\n", service, (int)name_has_owner);
|
|
if(!name_has_owner) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_show_if_true(const FileInfoList& files) {
|
|
if(show_if_true != nullptr) {
|
|
auto cmd = FileActionObject::expand_str(show_if_true.get(), files);
|
|
int exit_status;
|
|
// FIXME: Process.spawn cannot handle shell commands. Use Posix.system() instead.
|
|
//if(!Process.spawn_command_line_sync(cmd, nullptr, nullptr, out exit_status)
|
|
// || exit_status != 0)
|
|
// return false;
|
|
exit_status = system(cmd.c_str());
|
|
if(exit_status != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_show_if_running(const FileInfoList& files) {
|
|
if(show_if_running != nullptr) {
|
|
auto process_name = FileActionObject::expand_str(show_if_running.get(), files);
|
|
CStrPtr pgrep{g_find_program_in_path("pgrep")};
|
|
bool running = false;
|
|
// pgrep is not fully portable, but we don't have better options here
|
|
if(pgrep != nullptr) {
|
|
int exit_status;
|
|
// cmd = "$pgrep -x '$process_name'"
|
|
string cmd = pgrep.get();
|
|
cmd += " -x \'";
|
|
cmd += process_name;
|
|
cmd += "\'";
|
|
if(g_spawn_command_line_sync(cmd.c_str(), nullptr, nullptr, &exit_status, nullptr)) {
|
|
if(exit_status == 0) {
|
|
running = true;
|
|
}
|
|
}
|
|
}
|
|
if(!running) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_mime_type(const FileInfoList& files, const char* type, bool negated) {
|
|
// stdout.printf("match_mime_type: %s, neg: %d\n", type, (int)negated);
|
|
|
|
if(strcmp(type, "all/all") == 0 || strcmp(type, "*") == 0) {
|
|
return negated ? false : true;
|
|
}
|
|
else if(strcmp(type, "all/allfiles") == 0) {
|
|
// see if all fileinfos are files
|
|
if(negated) { // all fileinfos should not be files
|
|
for(auto& fi: files) {
|
|
if(!fi->isDir()) { // at least 1 of the fileinfos is a file.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else { // all fileinfos should be files
|
|
for(auto& fi: files) {
|
|
if(fi->isDir()) { // at least 1 of the fileinfos is a file.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(g_str_has_suffix(type, "/*")) {
|
|
// check if all are subtypes of allowed_type
|
|
string prefix{type};
|
|
prefix.erase(prefix.length() - 1); // remove the last char
|
|
if(negated) { // all files should not have the prefix
|
|
for(auto& fi: files) {
|
|
if(g_str_has_prefix(fi->mimeType()->name(), prefix.c_str())) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else { // all files should have the prefix
|
|
for(auto& fi: files) {
|
|
if(!g_str_has_prefix(fi->mimeType()->name(), prefix.c_str())) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if(negated) { // all files should not be of the type
|
|
for(auto& fi: files) {
|
|
if(strcmp(fi->mimeType()->name(),type) == 0) {
|
|
// if(ContentType.is_a(type, fi.get_mime_type().get_type())) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else { // all files should be of the type
|
|
for(auto& fi: files) {
|
|
// stdout.printf("get_type: %s, type: %s\n", fi.get_mime_type().get_type(), type);
|
|
if(strcmp(fi->mimeType()->name(),type) != 0) {
|
|
// if(!ContentType.is_a(type, fi.get_mime_type().get_type())) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_mime_types(const FileInfoList& files) {
|
|
if(mime_types != nullptr) {
|
|
bool allowed = false;
|
|
// FIXME: this is inefficient, but easier to implement
|
|
// check if all of the mime_types are allowed
|
|
for(auto mime_type = mime_types.get(); *mime_type; ++mime_type) {
|
|
const char* allowed_type = *mime_type;
|
|
const char* type;
|
|
bool negated;
|
|
if(allowed_type[0] == '!') {
|
|
type = allowed_type + 1;
|
|
negated = true;
|
|
}
|
|
else {
|
|
type = allowed_type;
|
|
negated = false;
|
|
}
|
|
|
|
if(negated) { // negated mime_type rules are ANDed
|
|
bool type_is_allowed = match_mime_type(files, type, negated);
|
|
if(!type_is_allowed) { // so any mismatch is not allowed
|
|
return false;
|
|
}
|
|
}
|
|
else { // other mime_type rules are ORed
|
|
// matching any one of the mime_type is enough
|
|
if(!allowed) { // if no rule is matched yet
|
|
allowed = match_mime_type(files, type, false);
|
|
}
|
|
}
|
|
}
|
|
return allowed;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_base_name(const FileInfoList& files, const char* base_name, bool negated) {
|
|
// see if all files has the base_name
|
|
// FIXME: this is inefficient, some optimization is needed later
|
|
GPatternSpec* pattern;
|
|
if(match_case) {
|
|
pattern = g_pattern_spec_new(base_name);
|
|
}
|
|
else {
|
|
CStrPtr case_fold{g_utf8_casefold(base_name, -1)};
|
|
pattern = g_pattern_spec_new(case_fold.get()); // FIXME: is this correct?
|
|
}
|
|
for(auto& fi: files) {
|
|
const char* name = fi->name().c_str();
|
|
if(match_case) {
|
|
if(g_pattern_match_string(pattern, name)) {
|
|
// at least 1 file has the base_name
|
|
if(negated) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
// at least 1 file does not has the scheme
|
|
if(!negated) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
CStrPtr case_fold{g_utf8_casefold(name, -1)};
|
|
if(g_pattern_match_string(pattern, case_fold.get())) {
|
|
// at least 1 file has the base_name
|
|
if(negated) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
// at least 1 file does not has the scheme
|
|
if(!negated) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_base_names(const FileInfoList& files) {
|
|
if(base_names != nullptr) {
|
|
bool allowed = false;
|
|
// FIXME: this is inefficient, but easier to implement
|
|
// check if all of the base_names are allowed
|
|
for(auto it = base_names.get(); *it; ++it) {
|
|
auto allowed_name = *it;
|
|
const char* name;
|
|
bool negated;
|
|
if(allowed_name[0] == '!') {
|
|
name = allowed_name + 1;
|
|
negated = true;
|
|
}
|
|
else {
|
|
name = allowed_name;
|
|
negated = false;
|
|
}
|
|
|
|
if(negated) { // negated base_name rules are ANDed
|
|
bool name_is_allowed = match_base_name(files, name, negated);
|
|
if(!name_is_allowed) { // so any mismatch is not allowed
|
|
return false;
|
|
}
|
|
}
|
|
else { // other base_name rules are ORed
|
|
// matching any one of the base_name is enough
|
|
if(!allowed) { // if no rule is matched yet
|
|
allowed = match_base_name(files, name, false);
|
|
}
|
|
}
|
|
}
|
|
return allowed;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_scheme(const FileInfoList& files, const char* scheme, bool negated) {
|
|
// FIXME: this is inefficient, some optimization is needed later
|
|
// see if all files has the scheme
|
|
for(auto& fi: files) {
|
|
if(fi->path().hasUriScheme(scheme)) {
|
|
// at least 1 file has the scheme
|
|
if(negated) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
// at least 1 file does not has the scheme
|
|
if(!negated) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_schemes(const FileInfoList& files) {
|
|
if(schemes != nullptr) {
|
|
bool allowed = false;
|
|
// FIXME: this is inefficient, but easier to implement
|
|
// check if all of the schemes are allowed
|
|
for(auto it = schemes.get(); *it; ++it) {
|
|
auto allowed_scheme = *it;
|
|
const char* scheme;
|
|
bool negated;
|
|
if(allowed_scheme[0] == '!') {
|
|
scheme = allowed_scheme + 1;
|
|
negated = true;
|
|
}
|
|
else {
|
|
scheme = allowed_scheme;
|
|
negated = false;
|
|
}
|
|
|
|
if(negated) { // negated scheme rules are ANDed
|
|
bool scheme_is_allowed = match_scheme(files, scheme, negated);
|
|
if(!scheme_is_allowed) { // so any mismatch is not allowed
|
|
return false;
|
|
}
|
|
}
|
|
else { // other scheme rules are ORed
|
|
// matching any one of the scheme is enough
|
|
if(!allowed) { // if no rule is matched yet
|
|
allowed = match_scheme(files, scheme, false);
|
|
}
|
|
}
|
|
}
|
|
return allowed;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_folder(const FileInfoList& files, const char* folder, bool negated) {
|
|
// trailing /* should always be implied.
|
|
// FIXME: this is inefficient, some optimization is needed later
|
|
GPatternSpec* pattern;
|
|
if(g_str_has_suffix(folder, "/*")) {
|
|
pattern = g_pattern_spec_new(folder);
|
|
}
|
|
else {
|
|
auto pat_str = g_str_has_suffix(folder, "/") ? string(folder) + "*" // be tolerant
|
|
: string(folder) + "/*";
|
|
pattern = g_pattern_spec_new(pat_str.c_str());
|
|
}
|
|
for(auto& fi: files) {
|
|
auto dirname = fi->isDir() ? fi->path().toString() // also match "folder" itself
|
|
: fi->dirPath().toString();
|
|
// Since the pattern ends with "/*", if the directory path is equal to "folder",
|
|
// it should end with "/" to be found as a match. Adding "/" is always harmless.
|
|
auto path_str = string(dirname.get()) + "/";
|
|
if(g_pattern_match_string(pattern, path_str.c_str())) { // at least 1 file is in the folder
|
|
if(negated) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
if(!negated) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_folders(const FileInfoList& files) {
|
|
if(folders != nullptr) {
|
|
bool allowed = false;
|
|
// FIXME: this is inefficient, but easier to implement
|
|
// check if all of the schemes are allowed
|
|
for(auto it = folders.get(); *it; ++it) {
|
|
auto allowed_folder = *it;
|
|
const char* folder;
|
|
bool negated;
|
|
if(allowed_folder[0] == '!') {
|
|
folder = allowed_folder + 1;
|
|
negated = true;
|
|
}
|
|
else {
|
|
folder = allowed_folder;
|
|
negated = false;
|
|
}
|
|
|
|
if(negated) { // negated folder rules are ANDed
|
|
bool folder_is_allowed = match_folder(files, folder, negated);
|
|
if(!folder_is_allowed) { // so any mismatch is not allowed
|
|
return false;
|
|
}
|
|
}
|
|
else { // other folder rules are ORed
|
|
// matching any one of the folder is enough
|
|
if(!allowed) { // if no rule is matched yet
|
|
allowed = match_folder(files, folder, false);
|
|
}
|
|
}
|
|
}
|
|
return allowed;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_selection_count(const FileInfoList& files) {
|
|
const int n_files = files.size();
|
|
switch(selection_count_cmp) {
|
|
case '<':
|
|
if(n_files >= selection_count) {
|
|
return false;
|
|
}
|
|
break;
|
|
case '=':
|
|
if(n_files != selection_count) {
|
|
return false;
|
|
}
|
|
break;
|
|
case '>':
|
|
if(n_files <= selection_count) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match_capabilities(const FileInfoList& /*files*/) {
|
|
// TODO
|
|
return true;
|
|
}
|
|
|
|
bool FileActionCondition::match(const FileInfoList& files) {
|
|
// all of the condition are combined with AND
|
|
// So, if any one of the conditions is not matched, we quit.
|
|
|
|
// TODO: OnlyShowIn, NotShowIn
|
|
if(!match_try_exec(files)) {
|
|
return false;
|
|
}
|
|
|
|
if(!match_mime_types(files)) {
|
|
return false;
|
|
}
|
|
if(!match_base_names(files)) {
|
|
return false;
|
|
}
|
|
if(!match_selection_count(files)) {
|
|
return false;
|
|
}
|
|
if(!match_schemes(files)) {
|
|
return false;
|
|
}
|
|
if(!match_folders(files)) {
|
|
return false;
|
|
}
|
|
// TODO: Capabilities
|
|
// currently, due to limitations of Fm.FileInfo, this cannot
|
|
// be implemanted correctly.
|
|
if(!match_capabilities(files)) {
|
|
return false;
|
|
}
|
|
|
|
if(!match_show_if_registered(files)) {
|
|
return false;
|
|
}
|
|
if(!match_show_if_true(files)) {
|
|
return false;
|
|
}
|
|
if(!match_show_if_running(files)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
} // namespace Fm
|