1 /**
2  * SECTION:fm-folder-config
3  * @short_description: Folder specific settings cache.
4  * @title: FmFolderConfig
5  *
6  * @include: libfm/fm.h
7  *
8  * This API represents access to folder-specific configuration settings.
9  * Each setting is a key/value pair. To use it the descriptor should be
10  * opened first, then required operations performed, then closed. Each
11  * opened descriptor holds a lock on the cache so it is not adviced to
12  * keep it somewhere.
13  */
14 
15 #include "folderconfig.h"
16 
17 #include <glib.h>
18 #include <glib/gstdio.h>
19 #include <cerrno>
20 
21 namespace Fm {
22 
23 CStrPtr FolderConfig::globalConfigFile_;
24 
25 // FIXME: sharing the same keyfile object everywhere is problematic
26 // FIXME: this is MT-unsafe
27 static GKeyFile* fc_cache = nullptr;
28 static bool fc_cache_changed = FALSE;
29 
FolderConfig()30 FolderConfig::FolderConfig():
31     keyFile_{nullptr},
32     changed_{false} {
33 }
34 
FolderConfig(const Fm::FilePath & path)35 FolderConfig::FolderConfig(const Fm::FilePath& path): FolderConfig{} {
36     (void)open(path);
37 }
38 
~FolderConfig()39 FolderConfig::~FolderConfig() {
40     if(isOpened()) {
41         GErrorPtr err;
42         close(err);
43     }
44 }
45 
open(const Fm::FilePath & path)46 bool FolderConfig::open(const Fm::FilePath& path) {
47     if(isOpened()) {  // the config is already opened
48         return false;
49     }
50 
51     changed_ = FALSE;
52     if(path.isNative()) {
53         /* clear .directory file first */
54         auto sub_path = path.child(".directory");
55         configFilePath_ = sub_path.toString();
56 
57         // FIXME: this only works for local filesystem and it's a blocking call. :-(
58         if(g_file_test(configFilePath_.get(), G_FILE_TEST_EXISTS)) {
59             keyFile_ = g_key_file_new();
60             if(g_key_file_load_from_file(keyFile_, configFilePath_.get(),
61                                          GKeyFileFlags(G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS),
62                                          nullptr) &&
63                     g_key_file_has_group(keyFile_, "File Manager")) {
64                 group_ = CStrPtr{g_strdup("File Manager")};
65                 return true;
66             }
67             g_key_file_free(keyFile_);
68         }
69     }
70 
71     // No per-folder config file.
72     // use the global key file instead and use the folder path as group key
73     configFilePath_.reset();
74     group_ = path.toString();
75 
76     // FIXME: we should use ref counting here. glib 2.36+ supports g_key_file_ref()
77     keyFile_ = fc_cache;
78     return true;
79 }
80 
81 
close(GErrorPtr & err)82 bool FolderConfig::close(GErrorPtr& err) {
83     bool ret = TRUE;
84     if(!isOpened()) {
85         return false;
86     }
87 
88     if(configFilePath_) {
89         if(changed_) {
90             char* out;
91             gsize len;
92 
93             out = g_key_file_to_data(keyFile_, &len, &err);
94             if(!out || !g_file_set_contents(configFilePath_.get(), out, len, &err)) {
95                 ret = FALSE;
96             }
97             g_free(out);
98         }
99         configFilePath_.reset();
100         g_key_file_free(keyFile_);
101     }
102     else {
103         group_.reset();
104         if(changed_) {
105             fc_cache_changed = TRUE;
106         }
107     }
108     keyFile_ = nullptr;
109     return ret;
110 }
111 
isOpened() const112 bool FolderConfig::isOpened() const {
113     return keyFile_ != nullptr;
114 }
115 
isEmpty()116 bool FolderConfig::isEmpty() {
117     return !g_key_file_has_group(keyFile_, group_.get());
118 }
119 
getInteger(const char * key,int * val)120 bool FolderConfig::getInteger(const char* key, int* val) {
121     GErrorPtr err;
122     auto ret = g_key_file_get_integer(keyFile_, group_.get(), key, &err);
123     if(err) {
124         return false;
125     }
126     *val = ret;
127     return true;
128 }
129 
getUint64(const char * key,uint64_t * val)130 bool FolderConfig::getUint64(const char* key, uint64_t *val) {
131     GError* error = nullptr;
132 #if GLIB_CHECK_VERSION(2, 26, 0)
133     guint64 ret = g_key_file_get_uint64(keyFile_, group_.get(), key, &error);
134 #else
135     gchar* s, *end;
136     guint64 ret;
137 
138     s = g_key_file_get_value(keyFile_, group_.get(), key, &error);
139 #endif
140     if(error) {
141         g_error_free(error);
142         return FALSE;
143     }
144 #if !GLIB_CHECK_VERSION(2, 26, 0)
145     ret = g_ascii_strtoull(s, &end, 10);
146     if(*s == '\0' || *end != '\0') {
147         g_free(s);
148         return FALSE;
149     }
150     g_free(s);
151 #endif
152     *val = ret;
153     return TRUE;
154 }
155 
getDouble(const char * key,double * val)156 bool FolderConfig::getDouble(const char* key,
157                                      double* val) {
158     GError* error = nullptr;
159     double ret = g_key_file_get_double(keyFile_, group_.get(), key, &error);
160     if(error) {
161         g_error_free(error);
162         return FALSE;
163     }
164     *val = ret;
165     return TRUE;
166 }
167 
getBoolean(const char * key,bool * val)168 bool FolderConfig::getBoolean(const char* key, bool* val) {
169     GErrorPtr err;
170     auto ret = g_key_file_get_boolean(keyFile_, group_.get(), key, &err);
171     if(err) {
172         return false;
173     }
174     *val = ret;
175     return true;
176 }
177 
getString(const char * key)178 char* FolderConfig::getString(const char* key) {
179     return g_key_file_get_string(keyFile_, group_.get(), key, nullptr);
180 }
181 
getStringList(const char * key,gsize * length)182 char** FolderConfig::getStringList(const char* key, gsize* length) {
183     return g_key_file_get_string_list(keyFile_, group_.get(), key, length, nullptr);
184 }
185 
186 
setInteger(const char * key,int val)187 void FolderConfig::setInteger(const char* key, int val) {
188     changed_ = TRUE;
189     g_key_file_set_integer(keyFile_, group_.get(), key, val);
190 }
191 
setUint64(const char * key,uint64_t val)192 void FolderConfig::setUint64(const char* key, uint64_t val) {
193     changed_ = TRUE;
194 #if GLIB_CHECK_VERSION(2, 26, 0)
195     g_key_file_set_uint64(keyFile_, group_.get(), key, val);
196 #else
197     gchar* result = g_strdup_printf("%" G_GUINT64_FORMAT, val);
198     g_key_file_set_value(keyFile_, group_.get(), key, result);
199     g_free(result);
200 #endif
201 }
202 
setDouble(const char * key,double val)203 void FolderConfig::setDouble(const char* key, double val) {
204     changed_ = TRUE;
205     g_key_file_set_double(keyFile_, group_.get(), key, val);
206 }
207 
setBoolean(const char * key,bool val)208 void FolderConfig::setBoolean(const char* key, bool val) {
209     changed_ = TRUE;
210     g_key_file_set_boolean(keyFile_, group_.get(), key, val);
211 }
212 
setString(const char * key,const char * string)213 void FolderConfig::setString(const char* key, const char* string) {
214     changed_ = TRUE;
215     g_key_file_set_string(keyFile_, group_.get(), key, string);
216 }
217 
setStringList(const char * key,const gchar * const list[],gsize length)218 void FolderConfig::setStringList(const char* key,
219                                       const gchar* const list[], gsize length) {
220     changed_ = TRUE;
221     g_key_file_set_string_list(keyFile_, group_.get(), key, list, length);
222 }
223 
removeKey(const char * key)224 void FolderConfig::removeKey(const char* key) {
225     changed_ = TRUE;
226     g_key_file_remove_key(keyFile_, group_.get(), key, nullptr);
227 }
228 
purge()229 void FolderConfig::purge() {
230     changed_ = TRUE;
231     g_key_file_remove_group(keyFile_, group_.get(), nullptr);
232 }
233 
234 // static
saveCache(void)235 void FolderConfig::saveCache(void) {
236     char* out;
237     gsize len;
238 
239     /* if per-directory cache was changed since last invocation then save it */
240     if(fc_cache_changed && (out = g_key_file_to_data(fc_cache, &len, nullptr))) {
241         /* FIXME: create dir */
242         /* create temp file with settings */
243         GFilePtr gfile{g_file_new_for_path(globalConfigFile_.get()), false};
244         GErrorPtr err;
245         /* do safe replace now, the file is important enough to be lost */
246         if(g_file_replace_contents(gfile.get(), out, len, nullptr, true, G_FILE_CREATE_PRIVATE, nullptr, nullptr, &err)) {
247             fc_cache_changed = FALSE;
248         }
249         else {
250             g_warning("cannot save %s: %s", globalConfigFile_.get(), err->message);
251         }
252         g_free(out);
253     }
254 }
255 
256 // static
finalize(void)257 void FolderConfig::finalize(void) {
258     saveCache();
259     g_key_file_free(fc_cache);
260     fc_cache = nullptr;
261 }
262 
263 // static
init(const char * globalConfigFile)264 void FolderConfig::init(const char* globalConfigFile) {
265     globalConfigFile_ = CStrPtr{g_strdup(globalConfigFile)};
266     fc_cache = g_key_file_new();
267     if(!g_key_file_load_from_file(fc_cache, globalConfigFile_.get(), G_KEY_FILE_NONE, nullptr)) {
268         // fail to load the config file.
269         // fallback to the legacy libfm config file for backward compatibility
270         CStrPtr legacyConfigFlie{g_build_filename(g_get_user_config_dir(), "libfm/dir-settings.conf", nullptr)};
271         g_key_file_load_from_file(fc_cache, legacyConfigFlie.get(), G_KEY_FILE_NONE, nullptr);
272     }
273 }
274 
275 } // namespace Fm
276