1 #include "libfmqtglobals.h"
2 #include "archiver.h"
3 
4 #include <cstring>
5 #include <glib.h>
6 #include <gio/gdesktopappinfo.h>
7 
8 #include <string>
9 
10 namespace Fm {
11 
12 Archiver* Archiver::defaultArchiver_ = nullptr;  // static
13 std::vector<std::unique_ptr<Archiver>> Archiver::allArchivers_;  // static
14 
Archiver()15 Archiver::Archiver() {
16 }
17 
isMimeTypeSupported(const char * type)18 bool Archiver::isMimeTypeSupported(const char* type) {
19     char** p;
20     if(G_UNLIKELY(!type)) {
21         return false;
22     }
23     for(p = mimeTypes_.get(); *p; ++p) {
24         if(strcmp(*p, type) == 0) {
25             return true;
26         }
27     }
28     return false;
29 }
30 
launchProgram(GAppLaunchContext * ctx,const char * cmd,const FilePathList & files,const FilePath & dir)31 bool Archiver::launchProgram(GAppLaunchContext* ctx, const char* cmd, const FilePathList& files, const FilePath& dir) {
32     char* _cmd = nullptr;
33     const char* dir_place_holder;
34     GKeyFile* dummy;
35 
36     if(dir.isValid() && (dir_place_holder = strstr(cmd, "%d"))) {
37         CStrPtr dir_str;
38         int len;
39         if(strstr(cmd, "%U") || strstr(cmd, "%u")) { /* supports URI */
40             dir_str = dir.uri();
41         }
42         else {
43             dir_str = dir.localPath();
44         }
45 
46         // FIXME: remove libfm dependency here
47         /* replace all % with %% so encoded URI can be handled correctly when parsing Exec key. */
48         std::string percentEscapedDir;
49         for(auto p = dir_str.get(); *p; ++p) {
50             percentEscapedDir += *p;
51             if(*p == '%') {
52                 percentEscapedDir += '%';
53             }
54         }
55 
56         /* quote the path or URI */
57         dir_str = CStrPtr{g_shell_quote(percentEscapedDir.c_str())};
58 
59         len = strlen(cmd) - 2 + strlen(dir_str.get()) + 1;
60         _cmd = (char*)g_malloc(len);
61         len = (dir_place_holder - cmd);
62         strncpy(_cmd, cmd, len);
63         strcpy(_cmd + len, dir_str.get());
64         strcat(_cmd, dir_place_holder + 2);
65         cmd = _cmd;
66     }
67 
68     /* create a fake key file to cheat GDesktopAppInfo */
69     dummy = g_key_file_new();
70     g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Type", "Application");
71     g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Name", program_.get());
72 
73     /* replace all % with %% so encoded URI can be handled correctly when parsing Exec key. */
74     g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Exec", cmd);
75     GAppInfoPtr app{reinterpret_cast<GAppInfo*>(g_desktop_app_info_new_from_keyfile(dummy)), false};
76 
77     g_key_file_free(dummy);
78     g_debug("cmd = %s", cmd);
79     if(app) {
80         GList* uris = nullptr;
81         for(auto& file: files) {
82             uris = g_list_prepend(uris, g_strdup(file.uri().get()));
83         }
84         g_app_info_launch_uris(app.get(), uris, ctx, nullptr);
85         g_list_free_full(uris, g_free);
86     }
87     g_free(_cmd);
88     return true;
89 }
90 
createArchive(GAppLaunchContext * ctx,const FilePathList & files)91 bool Archiver::createArchive(GAppLaunchContext* ctx, const FilePathList& files) {
92     if(createCmd_ && !files.empty()) {
93         launchProgram(ctx, createCmd_.get(), files, FilePath{});
94     }
95     return false;
96 }
97 
extractArchives(GAppLaunchContext * ctx,const FilePathList & files)98 bool Archiver::extractArchives(GAppLaunchContext* ctx, const FilePathList& files) {
99     if(extractCmd_ && !files.empty()) {
100         launchProgram(ctx, extractCmd_.get(), files, FilePath{});
101     }
102     return false;
103 }
104 
extractArchivesTo(GAppLaunchContext * ctx,const FilePathList & files,const FilePath & dest_dir)105 bool Archiver::extractArchivesTo(GAppLaunchContext* ctx, const FilePathList& files, const FilePath& dest_dir) {
106     if(extractToCmd_ && !files.empty()) {
107         launchProgram(ctx, extractToCmd_.get(), files, dest_dir);
108     }
109     return false;
110 }
111 
112 // static
defaultArchiver()113 Archiver* Archiver::defaultArchiver() {
114     allArchivers(); // to have a preliminary default archiver
115     return defaultArchiver_;
116 }
117 
setDefaultArchiverByName(const char * name)118 void Archiver::setDefaultArchiverByName(const char *name) {
119     if(name) {
120         auto& all = allArchivers();
121         for(auto& archiver: all) {
122             if(archiver->program_ && strcmp(archiver->program_.get(), name) == 0) {
123                 defaultArchiver_ = archiver.get();
124                 break;
125             }
126         }
127     }
128 }
129 
130 // static
setDefaultArchiver(Archiver * archiver)131 void Archiver::setDefaultArchiver(Archiver* archiver) {
132     if(archiver) {
133         defaultArchiver_ = archiver;
134     }
135 }
136 
137 // static
allArchivers()138 const std::vector<std::unique_ptr<Archiver> >& Archiver::allArchivers() {
139     // load all archivers on demand
140     if(allArchivers_.empty()) {
141         GKeyFile* kf = g_key_file_new();
142         if(g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/archivers.list", G_KEY_FILE_NONE, nullptr)) {
143             gsize n_archivers;
144             CStrArrayPtr programs{g_key_file_get_groups(kf, &n_archivers)};
145             if(programs) {
146                 gsize i;
147                 for(i = 0; i < n_archivers; ++i) {
148                     auto program = programs[i];
149                     std::unique_ptr<Archiver> archiver{new Archiver{}};
150                     archiver->createCmd_ = CStrPtr{g_key_file_get_string(kf, program, "create", nullptr)};
151                     archiver->extractCmd_ = CStrPtr{g_key_file_get_string(kf, program, "extract", nullptr)};
152                     archiver->extractToCmd_ = CStrPtr{g_key_file_get_string(kf, program, "extract_to", nullptr)};
153                     archiver->mimeTypes_ = CStrArrayPtr{g_key_file_get_string_list(kf, program, "mime_types", nullptr, nullptr)};
154                     archiver->program_ = CStrPtr{g_strdup(program)};
155 
156                     // if default archiver is not set, find the first program existing in the current system.
157                     if(!defaultArchiver_) {
158                         CStrPtr fullPath{g_find_program_in_path(program)};
159                         if(fullPath) {
160                             defaultArchiver_ = archiver.get();
161                         }
162                     }
163 
164                     allArchivers_.emplace_back(std::move(archiver));
165                 }
166             }
167         }
168         g_key_file_free(kf);
169     }
170     return allArchivers_;
171 }
172 
173 } // namespace Fm
174