1384bba5f5c6be54093d0ecdcf83f7803ab74886
[platform/upstream/syncevolution.git] / src / core / 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 "EvolutionSyncClient.h"
23 #include "SyncEvolutionUtil.h"
24 #include "Logging.h"
25
26 #include <boost/algorithm/string.hpp>
27 #include <boost/algorithm/string/join.hpp>
28 #include <boost/foreach.hpp>
29
30 #include <list>
31 using namespace std;
32
33 #include <dlfcn.h>
34
35 #ifdef HAVE_EDS
36 ESource *EvolutionSyncSource::findSource( ESourceList *list, const string &id )
37 {
38     for (GSList *g = e_source_list_peek_groups (list); g; g = g->next) {
39         ESourceGroup *group = E_SOURCE_GROUP (g->data);
40         GSList *s;
41         for (s = e_source_group_peek_sources (group); s; s = s->next) {
42             ESource *source = E_SOURCE (s->data);
43             char *uri = e_source_get_uri(source);
44             bool found = id.empty() ||
45                 !id.compare(e_source_peek_name(source)) ||
46                 (uri && !id.compare(uri));
47             g_free(uri);
48             if (found) {
49                 return source;
50             }
51         }
52     }
53     return NULL;
54 }
55 #endif // HAVE_EDS
56
57 #ifdef HAVE_EDS
58 void EvolutionSyncSource::throwError(const string &action, GError *gerror)
59 {
60     string gerrorstr;
61     if (gerror) {
62         gerrorstr += ": ";
63         gerrorstr += gerror->message;
64         g_clear_error(&gerror);
65     } else {
66         gerrorstr = ": failure";
67     }
68
69     throwError(action + gerrorstr);
70 }
71 #endif
72
73 void EvolutionSyncSource::throwError(const string &action, int error)
74 {
75     throwError(action + ": " + strerror(error));
76 }
77
78 void EvolutionSyncSource::throwError(const string &failure)
79 {
80     setFailed(true);
81     EvolutionSyncClient::throwError(string(getName()) + ": " + failure);
82 }
83
84 void EvolutionSyncSource::resetItems()
85 {
86     m_allItems.clear();
87     m_newItems.clear();
88     m_updatedItems.clear();
89     m_deletedItems.clear();
90 }
91
92 void EvolutionSyncSource::handleException()
93 {
94     SyncEvolutionException::handle();
95     setFailed(true);
96 }
97
98 SourceRegistry &EvolutionSyncSource::getSourceRegistry()
99 {
100     static SourceRegistry sourceRegistry;
101     return sourceRegistry;
102 }
103
104 RegisterSyncSource::RegisterSyncSource(const string &shortDescr,
105                                        bool enabled,
106                                        Create_t create,
107                                        const string &typeDescr,
108                                        const Values &typeValues) :
109     m_shortDescr(shortDescr),
110     m_enabled(enabled),
111     m_create(create),
112     m_typeDescr(typeDescr),
113     m_typeValues(typeValues)
114 {
115     SourceRegistry &registry(EvolutionSyncSource::getSourceRegistry());
116
117     // insert sorted by description to have deterministic ordering
118     for(SourceRegistry::iterator it = registry.begin();
119         it != registry.end();
120         ++it) {
121         if ((*it)->m_shortDescr > shortDescr) {
122             registry.insert(it, this);
123             return;
124         }
125     }
126     registry.push_back(this);
127 }
128
129 #if 0
130 static ostream & operator << (ostream &out, const RegisterSyncSource &rhs)
131 {
132     out << rhs.m_shortDescr << (rhs.m_enabled ? " (enabled)" : " (disabled)");
133 }
134 #endif
135
136 EvolutionSyncSource *const RegisterSyncSource::InactiveSource = (EvolutionSyncSource *)1;
137
138 TestRegistry &EvolutionSyncSource::getTestRegistry()
139 {
140     static TestRegistry testRegistry;
141     return testRegistry;
142 }
143
144 RegisterSyncSourceTest::RegisterSyncSourceTest(const string &configName, const string &testCaseName) :
145     m_configName(configName),
146     m_testCaseName(testCaseName)
147 {
148     EvolutionSyncSource::getTestRegistry().push_back(this);
149 }
150
151 static class ScannedModules {
152 public:
153     ScannedModules() {
154 #ifdef ENABLE_MODULES
155         list<string> *state;
156
157         // possible extension: scan directories for matching module names instead of hard-coding known names
158         const char *modules[] = {
159             "syncebook.so.0",
160             "syncecal.so.0",
161             "syncsqlite.so.0",
162             "syncfile.so.0",
163             "addressbook.so.0",
164             NULL
165         };
166
167         for (int i = 0; modules[i]; i++) {
168             void *dlhandle;
169
170             // Open the shared object so that backend can register
171             // itself. We keep that pointer, so never close the
172             // module!
173             dlhandle = dlopen(modules[i], RTLD_NOW|RTLD_GLOBAL);
174             if (!dlhandle) {
175                 string fullpath = LIBDIR "/syncevolution/";
176                 fullpath += modules[i];
177                 dlhandle = dlopen(fullpath.c_str(), RTLD_NOW|RTLD_GLOBAL);
178             }
179             // remember which modules were found and which were not
180             state = dlhandle ? &m_available : &m_missing;
181             state->push_back(modules[i]);
182         }
183 #endif
184     }
185     list<string> m_available;
186     list<string> m_missing;
187 } scannedModules;
188
189
190 EvolutionSyncSource *EvolutionSyncSource::createSource(const EvolutionSyncSourceParams &params, bool error)
191 {
192     string sourceTypeString = getSourceTypeString(params.m_nodes);
193     SourceType sourceType = EvolutionSyncSource::getSourceType(params.m_nodes);
194
195     const SourceRegistry &registry(getSourceRegistry());
196     BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
197         EvolutionSyncSource *source = sourceInfos->m_create(params);
198         if (source) {
199             if (source == RegisterSyncSource::InactiveSource) {
200                 EvolutionSyncClient::throwError(params.m_name + ": access to " + sourceInfos->m_shortDescr +
201                                                 " not enabled, therefore type = " + sourceTypeString + " not supported");
202             }
203             return source;
204         }
205     }
206
207     if (error) {
208         string problem = params.m_name + ": type '" + sourceTypeString + "' not supported";
209         if (scannedModules.m_available.size()) {
210             problem += " by any of the backends (";
211             problem += boost::join(scannedModules.m_available, ", ");
212             problem += ")";
213         }
214         if (scannedModules.m_missing.size()) {
215             problem += ". The following backend(s) were not found: ";
216             problem += boost::join(scannedModules.m_missing, ", ");
217         }
218         EvolutionSyncClient::throwError(problem);
219     }
220
221     return NULL;
222 }
223
224 EvolutionSyncSource *EvolutionSyncSource::createTestingSource(const string &name, const string &type, bool error,
225                                                               const char *prefix)
226 {
227     EvolutionSyncConfig config("testing");
228     SyncSourceNodes nodes = config.getSyncSourceNodes(name);
229     EvolutionSyncSourceParams params(name, nodes, "");
230     PersistentEvolutionSyncSourceConfig sourceconfig(name, nodes);
231     sourceconfig.setSourceType(type);
232     if (prefix) {
233         sourceconfig.setDatabaseID(string(prefix) + name + "_1");
234     }
235     return createSource(params, error);
236 }
237
238 void EvolutionSyncSource::getSynthesisInfo(string &profile,
239                                            string &datatypes,
240                                            string &native,
241                                            XMLConfigFragments &fragments)
242 {
243     string type = getMimeType();
244
245     if (type == "text/x-vcard") {
246         native = "vCard21";
247         profile = "\"vCard\", 1";
248         datatypes =
249             "        <use datatype='vCard21' mode='rw' preferred='yes'/>\n"
250             "        <use datatype='vCard30' mode='rw'/>\n";
251     } else if (type == "text/vcard") {
252         native = "vCard30";
253         profile = "\"vCard\", 2";
254         datatypes =
255             "        <use datatype='vCard21' mode='rw'/>\n"
256             "        <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
257     } else if (type == "text/x-calendar") {
258         native = "vCalendar10";
259         profile = "\"vCalendar\", 1";
260         datatypes =
261             "        <use datatype='vCalendar10' mode='rw' preferred='yes'/>\n"
262             "        <use datatype='iCalendar20' mode='rw'/>\n";
263     } else if (type == "text/calendar") {
264         native = "iCalendar20";
265         profile = "\"vCalendar\", 2";
266         datatypes =
267             "        <use datatype='vCalendar10' mode='rw'/>\n"
268             "        <use datatype='iCalendar20' mode='rw' preferred='yes'/>\n";
269     } else if (type == "text/plain") {
270         profile = "\"Note\", 2";
271     } else {
272         throwError(string("default MIME type not supported: ") + type);
273     }
274
275     SourceType sourceType = getSourceType();
276     if (!sourceType.m_format.empty()) {
277         type = sourceType.m_format;
278     }
279
280     if (type == "text/x-vcard:2.1" || type == "text/x-vcard") {
281         datatypes =
282             "        <use datatype='vCard21' mode='rw' preferred='yes'/>\n";
283         if (!sourceType.m_forceFormat) {
284             datatypes +=
285                 "        <use datatype='vCard30' mode='rw'/>\n";
286         }
287     } else if (type == "text/vcard:3.0" || type == "text/vcard") {
288         datatypes =
289             "        <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
290         if (!sourceType.m_forceFormat) {
291             datatypes +=
292                 "        <use datatype='vCard21' mode='rw'/>\n";
293         }
294     } else if (type == "text/x-vcalendar:2.0" || type == "text/x-vcalendar") {
295         datatypes =
296             "        <use datatype='vcalendar10' mode='rw' preferred='yes'/>\n";
297         if (!sourceType.m_forceFormat) {
298             datatypes +=
299                 "        <use datatype='icalendar20' mode='rw'/>\n";
300         }
301     } else if (type == "text/calendar:2.0" || type == "text/calendar") {
302         datatypes =
303             "        <use datatype='icalendar20' mode='rw' preferred='yes'/>\n";
304         if (!sourceType.m_forceFormat) {
305             datatypes +=
306                 "        <use datatype='vcalendar10' mode='rw'/>\n";
307         }
308     } else if (type == "text/plain:1.0" || type == "text/plain") {
309         // note10 are the same as note11, so ignore force format
310         datatypes =
311             "        <use datatype='note10' mode='rw' preferred='yes'/>\n"
312             "        <use datatype='note11' mode='rw'/>\n";
313     } else {
314         throwError(string("configured MIME type not supported: ") + type);
315     }
316 }
317
318 void EvolutionSyncSource::getDatastoreXML(string &xml, XMLConfigFragments &fragments)
319 {
320     stringstream xmlstream;
321     string profile;
322     string datatypes;
323     string native;
324
325     getSynthesisInfo(profile, datatypes, native, fragments);
326
327     xmlstream <<
328         "      <plugin_module>SyncEvolution</plugin_module>\n"
329         "      <plugin_datastoreadmin>no</plugin_datastoreadmin>\n"
330         "\n"
331         "      <!-- General datastore settings for all DB types -->\n"
332         "\n"
333         "      <!-- if this is set to 'yes', SyncML clients can only read\n"
334         "           from the database, but make no modifications -->\n"
335         "      <readonly>no</readonly>\n"
336         "\n"
337         "      <!-- conflict strategy: Newer item wins\n"
338         "           You can set 'server-wins' or 'client-wins' as well\n"
339         "           if you want to give one side precedence\n"
340         "      -->\n"
341         "      <conflictstrategy>newer-wins</conflictstrategy>\n"
342         "\n"
343         "      <!-- on slowsync: duplicate items that are not fully equal\n"
344         "           You can set this to 'newer-wins' as well to avoid\n"
345         "           duplicates as much as possible\n"
346         "      -->\n"
347         "      <slowsyncstrategy>duplicate</slowsyncstrategy>\n"
348         "\n"
349         "      <!-- text db plugin is designed for UTF-8, make sure data is passed as UTF-8 (and not the ISO-8859-1 default) -->\n"
350         "      <datacharset>UTF-8</datacharset>\n"
351         "      <!-- use C-language (unix style) linefeeds (\n, 0x0A) -->\n"
352         "      <datalineends>unix</datalineends>\n"
353         "\n"
354         "      <!-- set this to 'UTC' if time values should be stored in UTC into the database\n"
355         "           rather than local time. 'SYSTEM' denotes local server time zone. -->\n"
356         "      <datatimezone>SYSTEM</datatimezone>\n"
357         "\n"
358         "      <!-- plugin DB may have its own identifiers to determine the point in time of changes, so\n"
359         "           we must make sure this identifier is stored (and not only the sync time) -->\n"
360         "      <storesyncidentifiers>yes</storesyncidentifiers>\n"
361         "\n"
362         "      <!-- Mapping of the fields to the fieldlist 'contacts' -->\n"
363         "      <fieldmap fieldlist='contacts'>\n"
364         "        <initscript><![CDATA[\n"
365         "           string itemdata;\n"
366         "        ]]></initscript>\n"
367         "        <beforewritescript><![CDATA[\n"
368         "           itemdata = MAKETEXTWITHPROFILE(" << profile << ", \"EVOLUTION\");\n"
369         "        ]]></beforewritescript>\n"
370         "        <afterreadscript><![CDATA[\n"
371         "           PARSETEXTWITHPROFILE(itemdata, " << profile << ", \"EVOLUTION\");\n"
372         "        ]]></afterreadscript>\n"
373         "        <map name='data' references='itemdata' type='string'/>\n"
374         "      </fieldmap>\n"
375         "\n"
376         "      <!-- datatypes supported by this datastore -->\n"
377         "      <typesupport>\n" <<
378         datatypes <<
379         "      </typesupport>\n";
380
381     xml = xmlstream.str();
382 }
383
384 SyncMLStatus EvolutionSyncSource::beginSync(SyncMode mode) throw()
385 {
386     // start background thread if not running yet:
387     // necessary to catch problems with Evolution backend
388     EvolutionSyncClient::startLoopThread();
389
390     try {
391         // @TODO: force slow sync if something goes wrong
392         //
393         // reset anchors now, once we proceed there is no going back
394         // (because the change marker is about to be moved)
395         // and the sync must either complete or result in a slow sync
396         // the next time
397         
398         const char *error = getenv("SYNCEVOLUTION_BEGIN_SYNC_ERROR");
399         if (error && strstr(error, getName())) {
400             EvolutionSyncClient::throwError("artificial error in beginSync()");
401         }
402
403         // reset state
404         m_isModified = false;
405         m_allItems.clear();
406         m_newItems.clear();
407         m_updatedItems.clear();
408         m_deletedItems.clear();
409
410         // determine what to do
411         bool needAll = false;
412         bool needPartial = false;
413         bool deleteLocal = false;
414         switch (mode) {
415          case SYNC_SLOW:
416             needAll = true;
417             m_isModified = true;
418             break;
419          case SYNC_ONE_WAY_FROM_CLIENT:
420          case SYNC_TWO_WAY:
421             needPartial = true;
422             break;
423          case SYNC_REFRESH_FROM_SERVER:
424             deleteLocal = true;
425             m_isModified = true;
426             break;
427          case SYNC_REFRESH_FROM_CLIENT:
428             needAll = true;
429             m_isModified = true;
430             break;
431          case SYNC_NONE:
432             // special mode for testing: prepare both all and partial lists
433             needAll = needPartial = true;
434             break;
435          case SYNC_ONE_WAY_FROM_SERVER:
436             // nothing to do, just wait for server's changes
437             break;
438          default:
439             EvolutionSyncClient::throwError("unsupported sync mode, valid are only: slow, two-way, refresh");
440             break;
441         }
442
443         beginSyncThrow(needAll, needPartial, deleteLocal);
444
445         // This code here puts iterators in a state
446         // where iterating with nextItem() is possible.
447         rewindItems();
448     } catch( ... ) {
449         handleException();
450         return STATUS_FATAL;
451     }
452     return STATUS_OK;
453 }
454
455 void EvolutionSyncSource::rewindItems() throw()
456 {
457     m_allItems.rewind();
458 }
459
460 SyncItem::State EvolutionSyncSource::nextItem(string *data, string &luid) throw()
461 {
462     cxxptr<SyncItem> item(m_allItems.iterate(data ? false : true));
463     SyncItem::State state = SyncItem::NO_MORE_ITEMS;
464
465     if (item) {
466         if (m_newItems.find(item->getKey()) != m_newItems.end()) {
467             state = SyncItem::NEW;
468         } else if (m_updatedItems.find(item->getKey()) != m_updatedItems.end()) {
469             state = SyncItem::UPDATED;
470         } else {
471             state = SyncItem::UNCHANGED;
472         }
473         if (data) {
474             data->assign((const char *)item->getData(), item->getDataSize());
475         }
476         luid = item->getKey();
477     }
478     return state;
479 }
480
481 SyncMLStatus EvolutionSyncSource::endSync() throw()
482 {
483     try {
484         endSyncThrow();
485     } catch ( ... ) {
486         handleException();
487     }
488
489     // @TODO: Do _not_ tell the caller (SyncManager) if an error occurred
490     // because that causes Sync4jClient to abort processing for all
491     // sync sources. Instead deal with failed sync sources in
492     // EvolutionSyncClient::sync().
493     return STATUS_OK;
494 }
495
496 SyncMLStatus EvolutionSyncSource::addItem(SyncItem& item) throw()
497 {
498     return processItem("add", &EvolutionSyncSource::addItemThrow, item, true);
499 }
500
501 SyncMLStatus EvolutionSyncSource::updateItem(SyncItem& item) throw()
502 {
503     return processItem("update", &EvolutionSyncSource::updateItemThrow, item, true);
504 }
505
506 SyncMLStatus EvolutionSyncSource::deleteItem(SyncItem& item) throw()
507 {
508     return processItem("delete", &EvolutionSyncSource::deleteItemThrow, item, false);
509 }
510
511 SyncMLStatus EvolutionSyncSource::removeAllItems() throw()
512 {
513     SyncMLStatus status = STATUS_OK;
514     
515     try {
516         BOOST_FOREACH(const string &key, m_allItems) {
517             SyncItem item;
518             item.setKey(key.c_str());
519             logItem(item, "delete all items");
520             deleteItemThrow(item);
521             m_isModified = true;
522         }
523     } catch (...) {
524         handleException();
525         status = STATUS_FATAL;
526     }
527     return status;
528 }
529
530 SyncMLStatus EvolutionSyncSource::processItem(const char *action,
531                                               SyncMLStatus (EvolutionSyncSource::*func)(SyncItem& item),
532                                               SyncItem& item,
533                                               bool needData) throw()
534 {
535     SyncMLStatus status = STATUS_FATAL;
536     
537     try {
538         logItem(item, action);
539         if (needData && (item.getDataSize() < 0 || !item.getData())) {
540             // Something went wrong in the server: update or add without data.
541             // Shouldn't happen, but it did with one server and thus this
542             // security check was added to prevent segfaults.
543             logItem(item, "ignored due to missing data");
544             status = STATUS_OK;
545         } else {
546             status = (this->*func)(item);
547         }
548         m_isModified = true;
549     } catch (...) {
550         handleException();
551     }
552     databaseModified();
553     return status;
554 }
555
556 void EvolutionSyncSource::sleepSinceModification(int seconds)
557 {
558     time_t current = time(NULL);
559     while (current - m_modTimeStamp < seconds) {
560         sleep(seconds - (current - m_modTimeStamp));
561         current = time(NULL);
562     }
563 }
564
565 void EvolutionSyncSource::databaseModified()
566 {
567     m_modTimeStamp = time(NULL);
568 }
569
570 void EvolutionSyncSource::logItemUtil(const string data, const string &mimeType, const string &mimeVersion,
571                                       const string &uid, const string &info, bool debug)
572 {
573     if (getLevel() >= (debug ? Logger::DEBUG : Logger::INFO)) {
574         string name;
575
576         if (mimeType == "text/plain") {
577             size_t eol = data.find('\n');
578             if (eol != data.npos) {
579                 name.assign(data, 0, eol);
580             } else {
581                 name = data;
582             }
583         } else {
584             // Avoid pulling in a full vCard/iCalendar parser by just
585             // searching for a specific property. This is rather crude
586             // and does not handle encoding correctly at the moment, too.
587             string prop;
588             
589             if (mimeType == "text/vcard" ||
590                 mimeType == "text/x-vcard") {
591                 prop = "FN";
592             } else if (mimeType == "text/calendar" ||
593                        mimeType == "text/x-calendar") {
594                 prop = "SUMMARY";
595             }
596
597             if (prop.size()) {
598                 size_t start = 0;
599
600                 while (start < data.size()) {
601                     start = data.find(prop, start);
602                     if (start == data.npos) {
603                         break;
604                     }
605                     // must follow line break and continue with
606                     // : or ;
607                     if (start > 0 && data[start - 1] == '\n' &&
608                     start + prop.size() < data.size() &&
609                         (data[start + prop.size()] == ';' ||
610                          data[start + prop.size()] == ':')) {
611                         start = data.find(':', start);
612                         if (start != data.npos) {
613                             start++;
614                             size_t end = data.find_first_of("\n\r", start);
615                             name.assign(data,
616                                         start,
617                                         end == data.npos ? data.npos : (end - start));
618                         }
619                         break;
620                     } else {
621                         start += prop.size();
622                     }
623                 }
624             }
625         }
626
627         if (name.size()) {
628             SE_LOG(debug ? Logger::DEBUG : Logger::INFO, this, NULL,
629                    "%s %s",
630                    name.c_str(),
631                    info.c_str());
632         } else {
633             SE_LOG(debug ? Logger::DEBUG : Logger::INFO, this, NULL,
634                    "LUID %s %s",
635                    uid.c_str(),
636                    info.c_str());
637         }
638     }
639 }
640
641 void EvolutionSyncSource::setLevel(Level level)
642 {
643     LoggerBase::instance().setLevel(level);
644 }
645
646 Logger::Level EvolutionSyncSource::getLevel()
647 {
648     return LoggerBase::instance().getLevel();
649 }
650
651 void EvolutionSyncSource::messagev(Level level,
652                                    const char *prefix,
653                                    const char *file,
654                                    int line,
655                                    const char *function,
656                                    const char *format,
657                                    va_list args)
658 {
659     string newprefix = getName();
660     if (prefix) {
661         newprefix += ": ";
662         newprefix += prefix;
663     }
664     LoggerBase::instance().messagev(level, newprefix.c_str(),
665                                     file, line, function,
666                                     format, args);
667 }
668
669 SyncItem *EvolutionSyncSource::Items::start()
670 {
671     m_it = begin();
672     SE_LOG_DEBUG(&m_source, NULL, "start scanning %s items", m_type.c_str());
673     return iterate();
674 }
675
676 SyncItem *EvolutionSyncSource::Items::iterate(bool idOnly)
677 {
678     if (m_it != end()) {
679         const string &uid( *m_it );
680         SE_LOG_DEBUG(&m_source, NULL, "next %s item: %s", m_type.c_str(), uid.c_str());
681         ++m_it;
682         if (&m_source.m_deletedItems == this || idOnly) {
683             // just tell caller the uid of the (possibly deleted) item
684             cxxptr<SyncItem> item(new SyncItem());
685             item->setKey(uid);
686             return item.release();
687         } else {
688             // retrieve item with all its data
689             try {
690                 cxxptr<SyncItem> item(m_source.createItem(uid));
691                 return item.release();
692             } catch(...) {
693                 m_source.handleException();
694                 return NULL;
695             }
696         }
697     } else {
698         return NULL;
699     }
700 }
701
702 bool EvolutionSyncSource::Items::addItem(const string &uid) {
703     pair<iterator, bool> res = insert(uid);
704     if (res.second) {
705         m_source.logItem(uid, m_type, true);
706     }
707     return res.second;
708 }