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