Imported Upstream version 1.3.99.5_20131030_SE_05e5911_SYSYNC_69de386
[platform/upstream/syncevolution.git] / src / backends / evolution / EvolutionSyncSource.cpp
1 /*
2  * Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) version 3.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301  USA
19  */
20
21 #include "EvolutionSyncSource.h"
22 #include <syncevo/SmartPtr.h>
23 #include <syncevo/SyncContext.h>
24 #include <syncevo/GLibSupport.h>
25
26 #ifdef USE_EDS_CLIENT
27 #include <syncevo/GValueSupport.h>
28 SE_GLIB_TYPE(GKeyFile, g_key_file)
29 #endif
30
31 #include <syncevo/declarations.h>
32 SE_BEGIN_CXX
33
34 #ifdef HAVE_EDS
35
36 #ifdef USE_EDS_CLIENT
37
38 void EvolutionSyncSource::getDatabasesFromRegistry(SyncSource::Databases &result,
39                                                    const char *extension,
40                                                    ESource *(*refDef)(ESourceRegistry *))
41 {
42     ESourceRegistryCXX registry = EDSRegistryLoader::getESourceRegistry();
43     ESourceListCXX sources(e_source_registry_list_sources(registry, extension));
44     ESourceCXX def(refDef ? refDef(registry) : NULL,
45                    TRANSFER_REF);
46     BOOST_FOREACH (ESource *source, sources) {
47         result.push_back(Database(e_source_get_display_name(source),
48                                   e_source_get_uid(source),
49                                   e_source_equal(def, source)));
50     }
51 }
52
53 static void handleErrorCB(EClient */*client*/, const gchar *error_msg, gpointer user_data)
54 {
55     EvolutionSyncSource *that = static_cast<EvolutionSyncSource *>(user_data);
56     SE_LOG_ERROR(that->getDisplayName(), "%s", error_msg);
57 }
58
59 EClientCXX EvolutionSyncSource::openESource(const char *extension,
60                                             ESource *(*refBuiltin)(ESourceRegistry *),
61                                             const boost::function<EClient *(ESource *, GError **gerror)> &newClient)
62 {
63     EClientCXX client;
64     GErrorCXX gerror;
65     ESourceRegistryCXX registry = EDSRegistryLoader::getESourceRegistry();
66     ESourceListCXX sources(e_source_registry_list_sources(registry, extension));
67     string id = getDatabaseID();
68     ESource *source = findSource(sources, id);
69     bool created = false;
70
71     if (!source) {
72         if (refBuiltin && (id.empty() || id == "<<system>>")) {
73             ESourceCXX builtin(refBuiltin(registry), TRANSFER_REF);
74             client = EClientCXX::steal(newClient(builtin, gerror));
75             // } else if (!id.compare(0, 7, "file://")) {
76                 // TODO: create source
77                 // m_calendar = ECalClientCXX::steal(e_cal_client_new_from_uri(id.c_str(), sourceType(), gerror));
78         } else {
79             throwError(string("database not found: '") + id + "'");
80         }
81         created = true;
82     } else {
83         client = EClientCXX::steal(newClient(source, gerror));
84     }
85
86     if (!client) {
87         throwError("accessing database", gerror);
88     }
89
90     // Listen for errors
91     g_signal_connect (client, "backend-error", G_CALLBACK(handleErrorCB), this); 
92     g_signal_connect_after(client,
93                            "backend-died",
94                            G_CALLBACK(SyncContext::fatalError),
95                            (void *)"Evolution Data Server has died unexpectedly.");
96
97
98     while (true) {
99         // Always allow EDS to create the database. "only-if-exists =
100         // true" does not make sense.
101         if (!e_client_open_sync(client, false, NULL, gerror)) {
102             if (gerror && g_error_matches(gerror, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY)) {
103                 gerror.clear();
104                 sleep(1);
105             } else if (created) {
106                 // Opening newly created address books often failed in
107                 // old EDS releases - try again. Probably covered by
108                 // more recently added E_CLIENT_ERROR_BUSY check above.
109                 gerror.clear();
110                 sleep(5);
111             } else {
112                 throwError("opening database", gerror);
113             }
114         } else {
115             // Success!
116             break;
117         }
118     }
119
120     // record result for SyncSource::getDatabase()
121     source = e_client_get_source(client);
122     if (source) {
123         Database database(e_source_get_display_name(source),
124                           e_source_get_uid(source));
125         setDatabase(database);
126     }
127
128     return client;
129 }
130
131 SyncSource::Database EvolutionSyncSource::createDatabase(const Database &database)
132 {
133     // We'll need this later. Create it before doing any real work.
134     ESourceRegistryCXX registry = EDSRegistryLoader::getESourceRegistry();
135
136     // Clone the system DB. This allows the distro to change the
137     // configuration (backend, extensions (= in particular
138     // the contacts DB summary fields)) without having to
139     // modify SyncEvolution.
140     ESourceCXX systemSource = refSystemDB();
141     gsize len;
142     PlainGStr ini(e_source_to_string(systemSource, &len));
143
144     // Modify the entries in the key file directly. We can't
145     // instantiate an ESource (no API for it), copying the values from
146     // the key file into a fresh ESource is difficult (would have to
147     // reimplement EDS internal encoding/decoding), and copying from
148     // systemSource is hard (don't know which extensions it has,
149     // cannot instantiate extensions of unknown types, because
150     // e_source_get_extension() only works for types that were
151     // created).
152     static const char *mainSection = "Data Source";
153     GKeyFileCXX keyfile(g_key_file_new(), TRANSFER_REF);
154     GErrorCXX gerror;
155     if (!g_key_file_load_from_data(keyfile, ini, len, G_KEY_FILE_NONE, gerror)) {
156         gerror.throwError("parsing ESource .ini data");
157     }
158     PlainGStrArray keys(g_key_file_get_keys(keyfile, mainSection, NULL, gerror));
159     if (!keys) {
160         gerror.throwError("listing keys in main section");
161     }
162     for (int i = 0; keys.at(i); i++) {
163         if (boost::starts_with(keys.at(i), "DisplayName[")) {
164             if (!g_key_file_remove_key(keyfile, mainSection, keys.at(i), gerror)) {
165                 gerror.throwError("remove key");
166             }
167         }
168     }
169     g_key_file_set_string(keyfile, mainSection, "DisplayName", database.m_name.c_str());
170     g_key_file_set_boolean(keyfile, mainSection, "Enabled", true);
171     ini = g_key_file_to_data(keyfile, &len, NULL);
172     const char *configDir = g_get_user_config_dir();
173     int fd;
174     std::string filename;
175     std::string uid;
176
177     // Create sources dir. It might have been removed (for example, while
178     // testing) without having been recreated by evolution-source-registry.
179     std::string sourceDir = StringPrintf("%s/evolution/sources",
180                                          configDir);
181     mkdir_p(sourceDir);
182
183     // Create unique ID if necessary.
184     while (true) {
185         uid = database.m_uri.empty() ?
186             UUID() :
187             database.m_uri;
188         filename = StringPrintf("%s/%s.source", sourceDir.c_str(), uid.c_str());
189         fd = ::open(filename.c_str(),
190                     O_WRONLY|O_CREAT|O_EXCL,
191                     S_IRUSR|S_IWUSR);
192         if (fd >= 0) {
193             break;
194         }
195         if (errno == EEXIST) {
196             if (!database.m_uri.empty()) {
197                 SE_THROW(StringPrintf("ESource UUID %s already in use", database.m_uri.c_str()));
198             } else {
199                 // try again with new random UUID
200             }
201         } else {
202             SE_THROW(StringPrintf("creating %s failed: %s", filename.c_str(), strerror(errno)));
203         }
204     }
205     ssize_t written = write(fd, ini.get(), len);
206     int res = ::close(fd);
207     if (written != (ssize_t)len || res) {
208         SE_THROW(StringPrintf("writing to %s failed: %s", filename.c_str(), strerror(errno)));
209     }
210
211     // We need to wait until ESourceRegistry notices the new file.
212     SE_LOG_DEBUG(getDisplayName(), "waiting for ESourceRegistry to notice new ESource %s", uid.c_str());
213     while (!ESourceCXX(e_source_registry_ref_source(registry, uid.c_str()), TRANSFER_REF)) {
214         // This will block forever if called from the non-main-thread.
215         // Don't do that...
216         g_main_context_iteration(NULL, true);
217     }
218     SE_LOG_DEBUG(getDisplayName(), "ESourceRegistry has new ESource %s", uid.c_str());
219
220     // Try triggering that by attempting to create an ESource with the same
221     // UUID. Does not work! evolution-source-registry simply overwrites the
222     // file that we created earlier.
223     // ESourceCXX source(e_source_new_with_uid(uid.c_str(),
224     //                                         NULL, gerror),
225     //                   TRANSFER_REF);
226     // e_source_set_display_name(source, "syncevolution-fake");
227     // e_source_set_parent(source, "local-stub");
228     // ESourceListCXX sources;
229     // sources.push_back(source.ref()); // ESourceListCXX unrefs sources it points to
230     // if (!e_source_registry_create_sources_sync(registry,
231     //                                            sources,
232     //                                            NULL,
233     //                                            gerror)) {
234     //     gerror.throwError(StringPrintf("creating EDS database of type %s with name '%s'%s%s",
235     //                                    sourceExtension(),
236     //                                    database.m_name.c_str(),
237     //                                    database.m_uri.empty() ? "" : " and URI ",
238     //                                    database.m_uri.c_str()));
239     // } else {
240     //     SE_THROW("creating syncevolution-fake ESource succeeded although it should have failed");
241     // }
242
243     return Database(database.m_name, uid);
244 }
245
246 void EvolutionSyncSource::deleteDatabase(const std::string &uri, RemoveData removeData)
247 {
248     ESourceRegistryCXX registry = EDSRegistryLoader::getESourceRegistry();
249     ESourceCXX source(e_source_registry_ref_source(registry, uri.c_str()), TRANSFER_REF);
250     if (!source) {
251         throwError(StringPrintf("EDS database with URI '%s' cannot be deleted, does not exist",
252                                 uri.c_str()));
253     }
254     GErrorCXX gerror;
255     if (!e_source_remove_sync(source, NULL, gerror)) {
256         throwError(StringPrintf("deleting EDS database with URI '%s'", uri.c_str()),
257                    gerror);
258     }
259     if (removeData == REMOVE_DATA_FORCE) {
260         // Don't wait for evolution-source-registry cache-reaper to
261         // run, instead remove files ourselves. The reaper runs only
262         // once per day and also only moves the data into a trash
263         // folder, were it would linger until finally removed after 30
264         // days.
265         //
266         // This is equivalent to "rm -rf $XDG_DATA_HOME/evolution/*/<uuid>".
267         std::string basedir = StringPrintf("%s/evolution", g_get_user_data_dir());
268         if (isDir(basedir)) {
269             BOOST_FOREACH (const std::string &kind, ReadDir(basedir)) {
270                 std::string subdir = basedir + "/" + kind;
271                 if (isDir(subdir)) {
272                     BOOST_FOREACH (const std::string &source, ReadDir(subdir)) {
273                         // We assume that the UUID of the database
274                         // consists only of characters which can be
275                         // used in the directory name, i.e., no
276                         // special encoding of the directory name.
277                         if (source == uri) {
278                             rm_r(subdir + "/" + source);
279                             // Keep searching, just in case, although
280                             // there should only be one.
281                         }
282                     }
283                 }
284             }
285         }
286     }
287 }
288
289
290 #endif // USE_EDS_CLIENT
291
292 ESource *EvolutionSyncSource::findSource(const ESourceListCXX &list, const string &id )
293 {
294     string finalID;
295     if (!id.empty()) {
296         finalID = id;
297     } else {
298         // Nothing selected specifically, use the one marked as default.
299         BOOST_FOREACH(const Database &db, getDatabases()) {
300             if (db.m_isDefault) {
301                 finalID = db.m_uri;
302                 break;
303             }
304         }
305     }
306
307 #ifdef USE_EDS_CLIENT
308     BOOST_FOREACH (ESource *source, list) {
309         bool found =
310             !finalID.compare(e_source_get_display_name(source)) ||
311             !finalID.compare(e_source_get_uid(source));
312         if (found) {
313             return source;
314         }
315     }
316 #else
317     for (GSList *g = e_source_list_peek_groups (list.get()); g; g = g->next) {
318         ESourceGroup *group = E_SOURCE_GROUP (g->data);
319         GSList *s;
320         for (s = e_source_group_peek_sources (group); s; s = s->next) {
321             ESource *source = E_SOURCE (s->data);
322             GStringPtr uri(e_source_get_uri(source));
323             bool found = finalID.empty() ||
324                 !finalID.compare(e_source_peek_name(source)) ||
325                 (uri && !finalID.compare(uri));
326             if (found) {
327                 return source;
328             }
329         }
330     }
331 #endif
332     return NULL;
333 }
334
335 void EvolutionSyncSource::throwError(const string &action, GErrorCXX &gerror)
336 {
337     string gerrorstr;
338     if (gerror) {
339         gerrorstr += ": ";
340         gerrorstr += gerror->message;
341     } else {
342         gerrorstr = ": failure";
343     }
344
345     throwError(action + gerrorstr);
346 }
347 #endif // HAVE_EDS
348
349 SE_END_CXX