ff219003349c74e496a5487d331fad8dbb3f316a
[platform/upstream/syncevolution.git] / src / syncevo / SyncML.cpp
1 /*
2  * Copyright (C) 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 <syncevo/SyncML.h>
22 #include <syncevo/ConfigNode.h>
23 #include <syncevo/util.h>
24 #include <sstream>
25 #include <iomanip>
26 #include <vector>
27
28 #include <boost/foreach.hpp>
29 #include <boost/algorithm/string/split.hpp>
30 #include <boost/algorithm/string/classification.hpp>
31 #include <boost/algorithm/string/predicate.hpp>
32 #include <boost/algorithm/string/replace.hpp>
33 #include <boost/algorithm/string/join.hpp>
34
35 #include <synthesis/syerror.h>
36
37 #include <syncevo/declarations.h>
38 SE_BEGIN_CXX
39
40 std::string PrettyPrintSyncMode(SyncMode mode, bool userVisible)
41 {
42     switch (mode) {
43     case SYNC_NONE:
44         return userVisible ? "disabled" : "SYNC_NONE";
45     case SYNC_TWO_WAY:
46     case SA_SYNC_TWO_WAY:
47         return userVisible ? "two-way" : "SYNC_TWO_WAY";
48     case SYNC_SLOW:
49         return userVisible ? "slow" : "SYNC_SLOW";
50     case SYNC_ONE_WAY_FROM_CLIENT:
51     case SA_SYNC_ONE_WAY_FROM_CLIENT:
52         return userVisible ? "one-way-from-client" : "SYNC_ONE_WAY_FROM_CLIENT";
53     case SYNC_REFRESH_FROM_CLIENT:
54     case SA_SYNC_REFRESH_FROM_CLIENT:
55         return userVisible ? "refresh-from-client" : "SYNC_REFRESH_FROM_CLIENT";
56     case SYNC_ONE_WAY_FROM_SERVER:
57     case SA_SYNC_ONE_WAY_FROM_SERVER:
58         return userVisible ? "one-way-from-server" : "SYNC_ONE_WAY_FROM_SERVER";
59     case SYNC_REFRESH_FROM_SERVER:
60     case SA_SYNC_REFRESH_FROM_SERVER:
61         return userVisible ? "refresh-from-server" : "SYNC_REFRESH_FROM_SERVER";
62     case SYNC_RESTORE_FROM_BACKUP:
63         return userVisible ? "restore-from-backup" : "SYNC_RESTORE_FROM_BACKUP";
64     default:
65         std::stringstream res;
66
67         res << (userVisible ? "sync-mode-" : "SYNC_") << int(mode);
68         return res.str();
69     }
70 }
71
72 SyncMode StringToSyncMode(const std::string &mode, bool serverAlerted)
73 {
74     if (boost::iequals(mode, "slow") || boost::iequals(mode, "SYNC_SLOW")) {
75         return SYNC_SLOW;
76     } else if (boost::iequals(mode, "two-way") || boost::iequals(mode, "SYNC_TWO_WAY")) {
77         return serverAlerted ?SA_SYNC_TWO_WAY: SYNC_TWO_WAY;
78     } else if (boost::iequals(mode, "refresh-from-server") || boost::iequals(mode, "SYNC_REFRESH_FROM_SERVER")) {
79         return serverAlerted? SA_SYNC_REFRESH_FROM_SERVER: SYNC_REFRESH_FROM_SERVER;
80     } else if (boost::iequals(mode, "refresh-from-client") || boost::iequals(mode, "SYNC_REFRESH_FROM_CLIENT")) {
81         return serverAlerted? SA_SYNC_REFRESH_FROM_CLIENT: SYNC_REFRESH_FROM_CLIENT;
82     } else if (boost::iequals(mode, "one-way-from-server") || boost::iequals(mode, "SYNC_ONE_WAY_FROM_SERVER")) {
83         return serverAlerted? SA_SYNC_ONE_WAY_FROM_SERVER: SYNC_ONE_WAY_FROM_SERVER;
84     } else if (boost::iequals(mode, "one-way-from-client") || boost::iequals(mode, "SYNC_ONE_WAY_FROM_CLIENT")) {
85         return serverAlerted? SA_SYNC_ONE_WAY_FROM_CLIENT: SYNC_ONE_WAY_FROM_CLIENT;
86     } else if (boost::iequals(mode, "disabled") || boost::iequals(mode, "SYNC_NONE")) {
87         return SYNC_NONE;
88     } else {
89         return SYNC_INVALID;
90     }
91 }
92
93
94 ContentType StringToContentType(const std::string &type, bool force) {
95     if (boost::iequals (type, "text/x-vcard") || boost::iequals (type, "text/x-vcard:2.1")) {
96         return WSPCTC_XVCARD;
97     } else if (boost::iequals (type, "text/vcard") ||boost::iequals (type, "text/vcard:3.0")) {
98         return force ? WSPCTC_VCARD : WSPCTC_XVCARD;
99     } else if (boost::iequals (type, "text/x-vcalendar") ||boost::iequals (type, "text/x-vcalendar:1.0")
100               ||boost::iequals (type, "text/x-calendar") || boost::iequals (type, "text/x-calendar:1.0")) {
101         return WSPCTC_XVCALENDAR;
102     } else if (boost::iequals (type, "text/calendar") ||boost::iequals (type, "text/calendar:2.0")) {
103         return force ? WSPCTC_ICALENDAR : WSPCTC_XVCALENDAR;
104     } else if (boost::iequals (type, "text/plain") ||boost::iequals (type, "text/plain:1.0")) {
105         return WSPCTC_TEXT_PLAIN;
106     } else {
107         return WSPCTC_UNKNOWN;
108     }
109 }
110
111 std::string Status2String(SyncMLStatus status)
112 {
113     string error;
114     bool local;
115     int code = status;
116
117     local = status >= static_cast<int>(sysync::LOCAL_STATUS_CODE);
118     if (local &&
119         status <= static_cast<int>(sysync::LOCAL_STATUS_CODE_END)) {
120         code = status - static_cast<int>(sysync::LOCAL_STATUS_CODE);
121     } else {
122         code = status;
123     }
124
125     switch (code) {
126     case STATUS_OK:
127     case STATUS_HTTP_OK:
128         error = "no error";
129         break;
130     case STATUS_NO_CONTENT:
131         error = "no content/end of data";
132         break;
133     case STATUS_DATA_MERGED:
134         error = "data merged";
135         break;
136     case STATUS_FORBIDDEN:
137         error = "access denied";
138         break;
139     case STATUS_NOT_FOUND:
140         error = "object not found";
141         break;
142     case STATUS_COMMAND_NOT_ALLOWED:
143         error = "operation not allowed";
144         break;
145     case STATUS_ALREADY_EXISTS:
146         error = "object exists already";
147         break;
148     case STATUS_FATAL:
149         error = "fatal error";
150         break;
151     case STATUS_DATASTORE_FAILURE:
152         error = "database failure";
153         break;
154     case STATUS_FULL:
155         error = "out of space";
156         break;
157
158     case STATUS_UNEXPECTED_SLOW_SYNC:
159         error = "unexpected slow sync";
160         break;
161
162     case STATUS_PARTIAL_FAILURE:
163         error = "some changes could not be transferred";
164         break;
165
166     case sysync::LOCERR_BADPROTO:
167         error = "bad or unknown protocol";
168         break;
169     case sysync::LOCERR_SMLFATAL:
170         error = "fatal problem with SML";
171         break;
172     case sysync::LOCERR_COMMOPEN:
173         error = "cannot open communication";
174         break;
175     case sysync::LOCERR_SENDDATA:
176         error = "cannot send data";
177         break;
178     case sysync::LOCERR_RECVDATA:
179         error = "cannot receive data";
180         break;
181     case sysync::LOCERR_BADCONTENT:
182         error = "bad content in response";
183         break;
184     case sysync::LOCERR_PROCESSMSG:
185         error = "SML (or SAN) error processing incoming message";
186         break;
187     case sysync::LOCERR_COMMCLOSE:
188         error = "cannot close communication";
189         break;
190     case sysync::LOCERR_AUTHFAIL:
191         error = "transport layer authorisation failed";
192         break;
193     case sysync::LOCERR_CFGPARSE:
194         error = "error parsing config file";
195         break;
196     case sysync::LOCERR_CFGREAD:
197         error = "error reading config file";
198         break;
199     case sysync::LOCERR_NOCFG:
200         error = "no config found";
201         break;
202     case sysync::LOCERR_NOCFGFILE:
203         error = "config file could not be found";
204         break;
205     case sysync::LOCERR_EXPIRED:
206         error = "expired";
207         break;
208     case sysync::LOCERR_WRONGUSAGE:
209         error = "bad usage";
210         break;
211     case sysync::LOCERR_BADHANDLE:
212         error = "bad handle";
213         break;
214     case sysync::LOCERR_USERABORT:
215         error = "aborted on behalf of user";
216         break;
217     case sysync::LOCERR_BADREG:
218         error = "bad registration";
219         break;
220     case sysync::LOCERR_LIMITED:
221         error = "limited trial version";
222         break;
223     case sysync::LOCERR_TIMEOUT:
224         error = "connection timeout";
225         break;
226     case sysync::LOCERR_CERT_EXPIRED:
227         error = "connection SSL certificate expired";
228         break;
229     case sysync::LOCERR_CERT_INVALID:
230         error = "connection SSL certificate invalid";
231         break;
232     case sysync::LOCERR_INCOMPLETE:
233         error = "incomplete sync session";
234         break;
235     case sysync::LOCERR_RETRYMSG:
236         error = "retry sending message";
237         break;
238     case sysync::LOCERR_OUTOFMEM:
239         error = "out of memory";
240         break;
241     case sysync::LOCERR_NOCONN:
242         error = "no means to open a connection";
243         break;
244     case sysync::LOCERR_CONN:
245         error = "connection cannot be established";
246         break;
247     case sysync::LOCERR_ALREADY:
248         error = "element is already installed";
249         break;
250     case sysync::LOCERR_TOONEW:
251         error = "this build is too new for this license";
252         break;
253     case sysync::LOCERR_NOTIMP:
254         error = "function not implemented";
255         break;
256     case sysync::LOCERR_WRONGPROD:
257         error = "this license code is valid, but not for this product";
258         break;
259     case sysync::LOCERR_USERSUSPEND:
260         error = "explicitly suspended on behalf of user";
261         break;
262     case sysync::LOCERR_TOOOLD:
263         error = "this build is too old for this SDK/plugin";
264         break;
265     case sysync::LOCERR_UNKSUBSYSTEM:
266         error = "unknown subsystem";
267         break;
268     case sysync::LOCERR_SESSIONRST:
269         error = "next message will be a session restart";
270         break;
271     case sysync::LOCERR_LOCDBNOTRDY:
272         error = "local datastore is not ready";
273         break;
274     case sysync::LOCERR_RESTART:
275         error = "session should be restarted from scratch";
276         break;
277     case sysync::LOCERR_PIPECOMM:
278         error = "internal pipe communication problem";
279         break;
280     case sysync::LOCERR_BUFTOOSMALL:
281         error = "buffer too small for requested value";
282         break;
283     case sysync::LOCERR_TRUNCATED:
284         error = "value truncated to fit into field or buffer";
285         break;
286     case sysync::LOCERR_BADPARAM:
287         error = "bad parameter";
288         break;
289     case sysync::LOCERR_OUTOFRANGE:
290         error = "out of range";
291         break;
292     case sysync::LOCERR_TRANSPFAIL:
293         error = "external transport failure";
294         break;
295     case sysync::LOCERR_CLASSNOTREG:
296         error = "class not registered";
297         break;
298     case sysync::LOCERR_IIDNOTREG:
299         error = "interface not registered";
300         break;
301     case sysync::LOCERR_BADURL:
302         error = "bad URL";
303         break;
304     case sysync::LOCERR_SRVNOTFOUND:
305         error = "server not found";
306         break;
307
308     case STATUS_MAX:
309         break;
310     }
311
312     string statusstr = StringPrintf("%s, status %d",
313                                     local ? "local" : "remote",
314                                     status);
315     string description;
316     if (error.empty()) {
317         description = statusstr;
318     } else {
319         description = StringPrintf("%s (%s)",
320                                    error.c_str(),
321                                    statusstr.c_str());
322     }
323
324     return description;
325 }
326
327 namespace {
328     const char * const locNames[] = { "local", "remote", NULL };
329     const char * const stateNames[] = { "added", "updated", "removed", "any", NULL };
330     const char * const resultNames[] = { "total", "reject", "match",
331                                          "conflict_server_won",
332                                          "conflict_client_won",
333                                          "conflict_duplicated",
334                                          "sent",
335                                          "received",
336                                          NULL };
337
338     int toIndex(const char * const names[],
339                 const std::string &name) {
340         int i;
341         for (i = 0;
342              names[i] && name != names[i];
343              i++)
344             {}
345         return i;
346     }
347     std::string toString(const char * const names[],
348                     int index) {
349         for (int i = 0;
350              names[i];
351              i++) {
352             if (i == index) {
353                 return names[i];
354             }
355         }
356         return "unknown";
357     }
358 }
359
360 std::string SyncSourceReport::LocationToString(ItemLocation location) { return toString(locNames, location); }
361 SyncSourceReport::ItemLocation SyncSourceReport::StringToLocation(const std::string &location) { return static_cast<ItemLocation>(toIndex(locNames, location)); }
362 std::string SyncSourceReport::StateToString(ItemState state) { return toString(stateNames, state); }
363 SyncSourceReport::ItemState SyncSourceReport::StringToState(const std::string &state) { return static_cast<ItemState>(toIndex(stateNames, state)); }
364 std::string SyncSourceReport::ResultToString(ItemResult result) { return toString(resultNames, result); }
365 SyncSourceReport::ItemResult SyncSourceReport::StringToResult(const std::string &result) { return static_cast<ItemResult>(toIndex(resultNames, result)); }
366
367 std::string SyncSourceReport::StatTupleToString(ItemLocation location, ItemState state, ItemResult result)
368 {
369     return std::string("") +
370         LocationToString(location) + "-" +
371         StateToString(state) + "-" +
372         ResultToString(result);
373 }
374 void SyncSourceReport::StringToStatTuple(const std::string &str, ItemLocation &location, ItemState &state, ItemResult &result)
375 {
376     std::vector< std::string > tokens;
377     boost::split(tokens, str, boost::is_any_of("-"));
378     location = tokens.size() > 0 ? StringToLocation(tokens[0]) : ITEM_LOCATION_MAX;
379     state = tokens.size() > 1 ? StringToState(tokens[1]) : ITEM_STATE_MAX;
380     result = tokens.size() > 2 ? StringToResult(tokens[2]) : ITEM_RESULT_MAX;
381 }
382
383 bool SyncSourceReport::wasChanged(ItemLocation location)
384 {
385     for (int i = ITEM_ADDED; i < ITEM_ANY; i++) {
386         if (getItemStat(location, (ItemState)i, ITEM_TOTAL) > 0) {
387             return true;
388         }
389     }
390     return false;
391 }
392
393
394 std::ostream &operator << (std::ostream &out, const SyncReport &report)
395 {
396     report.prettyPrint(out, 0);
397     return out;
398 }
399
400 namespace {
401     string fill(char sep, size_t width) {
402         string res;
403         res.resize(width - 1, sep);
404         return res;
405     }
406     string center(char sep, const string &str, size_t width) {
407         if (str.size() + 1 >= width) {
408             return str;
409         } else {
410             string res;
411             res.resize(width - 1, sep);
412             res.replace((width - 1 - str.size()) / 2, str.size(), str);
413             return res;
414         }
415     }
416     string right(char sep, const string &str, size_t width) {
417         if (str.size() + 1 >= width) {
418             return str;
419         } else {
420             string res;
421             res.resize(width - 1, sep);
422             res.replace(width - 2 - str.size(), str.size(), str);
423             return res;
424         }
425     }
426     string left(char sep, const string &str, size_t width) {
427         if (str.size() + 1 >= width) {
428             return str;
429         } else {
430             string res;
431             res.resize(width - 1, sep);
432             res.replace(1, str.size(), str);
433             return res;
434         }
435     }
436
437     // insert string at column if it fits, otherwise flush right
438     string align(char sep, const string &str, size_t width, size_t column) {
439         if (column + str.size() + 1 >= width) {
440             return right(sep, str, width);
441         } else {
442             string res;
443             res.resize(width - 1, sep);
444             res.replace(column, str.size(), str);
445             return res;
446         }
447     }
448 }
449
450 void SyncReport::prettyPrint(std::ostream &out, int flags) const
451 {
452     // table looks like this:
453     // +-------------------+-------------------------------+-------------------------------|-CON-+
454     // |                   |             LOCAL             |           REMOTE              | FLI |
455     // |            Source | NEW | MOD | DEL | ERR | TOTAL | NEW | MOD | DEL | ERR | TOTAL | CTS |
456     // +-------------------+-----+-----+-----+-----+-------+-----+-----+-----+-----+-------+-----+
457     //
458     // Most of the columns can be turned on or off dynamically.
459     // Their width is calculated once (including right separators and spaces):
460     // | name_width        |count_width|                   |                       |conflict_width|
461     //                     |client_width                   | server_width          |
462     // | text_width                                                                      |
463
464     // name column is sized dynamically based on column header and actual names
465     size_t name_width = strlen("Source");
466     BOOST_FOREACH(const SyncReport::value_type &entry, *this) {
467         const std::string &name = entry.first;
468         if (name_width < name.size()) {
469             name_width = name.size();
470         }
471     }
472     name_width += 1; // separator
473     if (name_width < 20) {
474         // enough room for spaces
475         name_width += 2;
476     }
477
478     int count_width = 6;
479     int num_counts = 3;
480     if (flags & WITH_TOTAL) {
481         num_counts++;
482     }
483     if (!(flags & WITHOUT_REJECTS)) {
484         num_counts++;
485     }
486     int client_width = (flags & WITHOUT_CLIENT) ? 0 :
487         num_counts * count_width;
488     int server_width = (flags & WITHOUT_SERVER) ? 0 :
489         num_counts * count_width;
490     int conflict_width = (flags & WITHOUT_CONFLICTS) ? 0 : 6;
491     int text_width = name_width + client_width + server_width + conflict_width;
492
493     if (text_width < 70) {
494         // enlarge name column to make room for long lines of text
495         name_width += 70 - text_width;
496         text_width = 70;
497     }
498
499     out << "+" << fill('-', name_width);
500     if (!(flags & WITHOUT_CLIENT)) {
501         out << '|' << center('-', "", client_width);
502     }
503     if (!(flags & WITHOUT_SERVER)) {
504         out << '|' << center('-', "", server_width);
505     }
506     if (!(flags & WITHOUT_CONFLICTS)) {
507         out << '|' << center('-', "CON", conflict_width);
508     }
509     out << "+\n";
510
511     if (!(flags & WITHOUT_REJECTS) || !(flags & WITHOUT_CONFLICTS)) {
512         out << "|" << fill(' ', name_width);
513         if (!(flags & WITHOUT_CLIENT)) {
514             out << '|' << center(' ', "LOCAL", client_width);
515         }
516         if (!(flags & WITHOUT_SERVER)) {
517             out << '|' << center(' ', "REMOTE", server_width);
518         }
519         if (!(flags & WITHOUT_CONFLICTS)) {
520             out << '|' << center(' ', "FLI", conflict_width);
521         }
522         out << "|\n";
523     }
524
525     out << '|' << right(' ', "Source", name_width);
526     if (!(flags & WITHOUT_CLIENT)) {
527         out << '|' << center(' ', "NEW", count_width);
528         out << '|' << center(' ', "MOD", count_width);
529         out << '|' << center(' ', "DEL", count_width);
530         if (!(flags & WITHOUT_REJECTS)) {
531             out << '|' << center(' ', "ERR", count_width);
532         }
533         if (flags & WITH_TOTAL) {
534             out << '|' << center(' ', "TOTAL", count_width);
535         }
536     }
537     if (!(flags & WITHOUT_SERVER)) {
538         out << '|' << center(' ', "NEW", count_width);
539         out << '|' << center(' ', "MOD", count_width);
540         out << '|' << center(' ', "DEL", count_width);
541         if (!(flags & WITHOUT_REJECTS)) {
542             out << '|' << center(' ', "ERR", count_width);
543         }
544         if (flags & WITH_TOTAL) {
545             out << '|' << center(' ', "TOTAL", count_width);
546         }
547     }
548     if (!(flags & WITHOUT_CONFLICTS)) {
549         out << '|' << center(' ', "CTS", conflict_width);
550     }
551     out << "|\n";
552
553     stringstream sepstream;
554     sepstream << '+' << fill('-', name_width);
555     if (!(flags & WITHOUT_CLIENT)) {
556         sepstream << '+' << fill('-', count_width);
557         sepstream << '+' << fill('-', count_width);
558         sepstream << '+' << fill('-', count_width);
559         if (!(flags & WITHOUT_REJECTS)) {
560             sepstream << '+' << fill('-', count_width);
561         }
562         if (flags & WITH_TOTAL) {
563             sepstream << '+' << fill('-', count_width);
564         }
565     }
566     if (!(flags & WITHOUT_SERVER)) {
567         sepstream << '+' << fill('-', count_width);
568         sepstream << '+' << fill('-', count_width);
569         sepstream << '+' << fill('-', count_width);
570         if (!(flags & WITHOUT_REJECTS)) {
571             sepstream << '+' << fill('-', count_width);
572         }
573         if (flags & WITH_TOTAL) {
574             sepstream << '+' << fill('-', count_width);
575         }
576     }
577     if (!(flags & WITHOUT_CONFLICTS)) {
578         sepstream << '+' << fill('-', conflict_width);
579     }
580     sepstream << "+\n";
581     string sep = sepstream.str();
582     out << sep;
583
584     BOOST_FOREACH(const SyncReport::value_type &entry, *this) {
585         const std::string &name = entry.first;
586         const SyncSourceReport &source = entry.second;
587         out << '|' << right(' ', name, name_width);
588         ssize_t name_column = name_width - 2 - name.size();
589         if (name_column < 0) {
590             name_column = 0;
591         }
592         for (SyncSourceReport::ItemLocation location =
593                  ((flags & WITHOUT_CLIENT) ? SyncSourceReport::ITEM_REMOTE : SyncSourceReport::ITEM_LOCAL);
594              location <= ((flags & WITHOUT_SERVER) ? SyncSourceReport::ITEM_LOCAL : SyncSourceReport::ITEM_REMOTE);
595              location = SyncSourceReport::ItemLocation(int(location) + 1)) {
596             for (SyncSourceReport::ItemState state = SyncSourceReport::ITEM_ADDED;
597                  state <= SyncSourceReport::ITEM_REMOVED;
598                  state = SyncSourceReport::ItemState(int(state) + 1)) {
599                 stringstream count;
600                 count << source.getItemStat(location, state, SyncSourceReport::ITEM_TOTAL);
601                 out << '|' << center(' ', count.str(), count_width);
602             }
603             if (!(flags & WITHOUT_REJECTS)) {
604                 stringstream count;
605                 count << source.getItemStat(location,
606                                             SyncSourceReport::ITEM_ANY,
607                                             SyncSourceReport::ITEM_REJECT);
608                 out << '|' << center(' ', count.str(), count_width);
609             }
610             if (flags & WITH_TOTAL) {
611                 stringstream count;
612                 count << source.getItemStat(location,
613                                             SyncSourceReport::ITEM_ANY,
614                                             SyncSourceReport::ITEM_TOTAL);
615                 out << '|' << center(' ', count.str(), count_width);
616             }
617         }
618
619         int total_conflicts = 0;
620         if (!(flags & WITHOUT_CONFLICTS)) {
621             total_conflicts =
622                 source.getItemStat(SyncSourceReport::ITEM_REMOTE,
623                                    SyncSourceReport::ITEM_ANY,
624                                    SyncSourceReport::ITEM_CONFLICT_SERVER_WON) +
625                 source.getItemStat(SyncSourceReport::ITEM_REMOTE,
626                                    SyncSourceReport::ITEM_ANY,
627                                    SyncSourceReport::ITEM_CONFLICT_CLIENT_WON) +
628                 source.getItemStat(SyncSourceReport::ITEM_REMOTE,
629                                    SyncSourceReport::ITEM_ANY,
630                                    SyncSourceReport::ITEM_CONFLICT_DUPLICATED);
631             stringstream conflicts;
632             conflicts << total_conflicts;
633             out << '|' << center(' ', conflicts.str(), conflict_width);
634         }
635         out << "|\n";
636
637         std::stringstream line;
638
639         if (source.getFinalSyncMode() != SYNC_NONE ||
640             source.getItemStat(SyncSourceReport::ITEM_LOCAL,
641                                SyncSourceReport::ITEM_ANY,
642                                SyncSourceReport::ITEM_SENT_BYTES) ||
643             source.getItemStat(SyncSourceReport::ITEM_LOCAL,
644                                SyncSourceReport::ITEM_ANY,
645                                SyncSourceReport::ITEM_RECEIVED_BYTES)) {
646             line <<
647                 PrettyPrintSyncMode(source.getFinalSyncMode()) << ", " <<
648                 source.getItemStat(SyncSourceReport::ITEM_LOCAL,
649                                    SyncSourceReport::ITEM_ANY,
650                                    SyncSourceReport::ITEM_SENT_BYTES) / 1024 <<
651                 " KB sent by client, " <<
652                 source.getItemStat(SyncSourceReport::ITEM_LOCAL,
653                                    SyncSourceReport::ITEM_ANY,
654                                    SyncSourceReport::ITEM_RECEIVED_BYTES) / 1024 <<
655                 " KB received";
656             out << '|' << align(' ', line.str(), text_width, name_column) << "|\n";
657         }
658
659         if (total_conflicts > 0) {
660             for (SyncSourceReport::ItemResult result = SyncSourceReport::ITEM_CONFLICT_SERVER_WON;
661                  result <= SyncSourceReport::ITEM_CONFLICT_DUPLICATED;
662                  result = SyncSourceReport::ItemResult(int(result) + 1)) {
663                 int count;
664                 if ((count = source.getItemStat(SyncSourceReport::ITEM_REMOTE,
665                                                 SyncSourceReport::ITEM_ANY,
666                                                 result)) != 0 || true) {
667                     std::stringstream line;
668                     line << count << " " <<
669                         (result == SyncSourceReport::ITEM_CONFLICT_SERVER_WON ? "client item(s) discarded" :
670                          result == SyncSourceReport::ITEM_CONFLICT_CLIENT_WON ? "server item(s) discarded" :
671                      "item(s) duplicated");
672
673                     out << '|' << align(' ', line.str(), text_width, name_column) << "|\n";
674                 }
675             }
676         }
677
678         int total_matched = source.getItemStat(SyncSourceReport::ITEM_REMOTE,
679                                                SyncSourceReport::ITEM_ANY,
680                                                SyncSourceReport::ITEM_MATCH);
681         if (total_matched) {
682             stringstream line;
683             line << total_matched << " item(s) matched";
684             out << '|' << align(' ', line.str(), text_width, name_column)
685                 << "|\n";
686         }
687
688         if (source.m_backupBefore.isAvailable() ||
689             source.m_backupAfter.isAvailable()) {
690             std::stringstream backup;
691             backup << "item(s) in database backup: ";
692             if (source.m_backupBefore.isAvailable()) {
693                 backup << source.m_backupBefore.getNumItems() << " before sync, ";
694             } else {
695                 backup << "no backup before sync, ";
696             }
697             if (source.m_backupAfter.isAvailable()) {
698                 backup << source.m_backupAfter.getNumItems() << " after it";
699             } else {
700                 backup << "no backup after it";
701             }
702             out << '|' << align(' ', backup.str(), text_width, name_column) << "|\n";
703         }
704         if (source.getStatus()) {
705             out  << '|' << align(' ',
706                                  Status2String(source.getStatus()),
707                                  text_width, name_column) << "|\n";
708         }
709         out << sep;
710     }
711
712     if (getStart()) {
713         out << '|' << center(' ', formatSyncTimes(), text_width) << "|\n";
714     }
715     if (getStatus()) {
716         out << '|' << center(' ',
717                              getStatus() != STATUS_HTTP_OK ?
718                              Status2String(getStatus()) :
719                              "synchronization completed successfully",
720                              text_width)
721             << "|\n";
722     }
723     if (getStatus() || getStart()) {
724         out << sep;
725     }
726     if (!getError().empty()) {
727         out << "First ERROR encountered: " << getError() << endl;
728     }
729 }
730
731 std::string SyncReport::formatSyncTimes() const
732 {
733     std::stringstream out;
734     time_t duration = m_end - m_start;
735
736     out << "start ";
737     if (!m_start) {
738         out << "unknown";
739     } else {
740         char buffer[160];
741         strftime(buffer, sizeof(buffer), "%c", localtime(&m_start));
742         out << buffer;
743         if (!m_end) {
744             out << ", unknown duration (crashed?!)";
745         } else {
746             out << ", duration " << duration / 60 << ":"
747                 << std::setw(2) << std::setfill('0') << duration % 60
748                 << "min";
749         }
750     }
751     return out.str();
752 }
753
754 std::string SyncReport::slowSyncExplanation(const std::string &peer,
755                                             const std::set<std::string> &sources)
756 {
757     if (sources.empty()) {
758         return "";
759     }
760
761     string sourceparam = boost::join(sources, " ");
762     std::string explanation =
763         StringPrintf("Doing a slow synchronization may lead to duplicated items or\n"
764                      "lost data when the server merges items incorrectly. Choosing\n"
765                      "a different synchronization mode may be the better alternative.\n"
766                      "Restart synchronization of affected source(s) with one of the\n"
767                      "following sync modes to recover from this problem:\n"
768                      "    slow, refresh-from-server, refresh-from-client\n\n"
769                      "Analyzing the current state:\n"
770                      "    syncevolution --status %s %s\n\n"
771                      "Running with one of the three modes:\n"
772                      "    syncevolution --sync [slow|refresh-from-server|refresh-from-client] %s %s\n",
773                      peer.c_str(), sourceparam.c_str(),
774                      peer.c_str(), sourceparam.c_str());
775     return explanation;
776 }
777
778 std::string SyncReport::slowSyncExplanation(const std::string &peer) const
779 {
780     std::set<std::string> sources;
781     BOOST_FOREACH(const SyncReport::value_type &entry, *this) {
782         const std::string &name = entry.first;
783         const SyncSourceReport &source = entry.second;
784         if (source.getStatus() == STATUS_UNEXPECTED_SLOW_SYNC) {
785             string virtualsource = source.getVirtualSource();
786             sources.insert(virtualsource.empty() ?
787                            name :
788                            virtualsource);
789         }
790     }
791     return slowSyncExplanation(peer, sources);
792 }
793
794 ConfigNode &operator << (ConfigNode &node, const SyncReport &report)
795 {
796     node.setProperty("start", static_cast<long>(report.getStart()));
797     node.setProperty("end", static_cast<long>(report.getEnd()));
798     node.setProperty("status", static_cast<int>(report.getStatus()));
799     string error = report.getError();
800     if (!error.empty()) {
801         node.setProperty("error", error);
802     } else {
803         node.removeProperty("error");
804     }
805
806     BOOST_FOREACH(const SyncReport::value_type &entry, report) {
807         const std::string &name = entry.first;
808         const SyncSourceReport &source = entry.second;
809
810         string prefix = name;
811         boost::replace_all(prefix, "_", "__");
812         boost::replace_all(prefix, "-", "_+");
813         prefix = "source-" + prefix;
814
815         string key;
816         key = prefix + "-mode";
817         node.setProperty(key, PrettyPrintSyncMode(source.getFinalSyncMode()));
818         key = prefix + "-first";
819         node.setProperty(key, source.isFirstSync());
820         key = prefix + "-resume";
821         node.setProperty(key, source.isResumeSync());
822         key = prefix + "-status";
823         node.setProperty(key, static_cast<long>(source.getStatus()));
824         string virtualsource = source.getVirtualSource();
825         if (!virtualsource.empty()) {
826             key = prefix + "-virtualsource";
827             node.setProperty(key, virtualsource);
828         }
829         key = prefix + "-backup-before";
830         node.setProperty(key, source.m_backupBefore.getNumItems());
831         key = prefix + "-backup-after";
832         node.setProperty(key, source.m_backupAfter.getNumItems());
833
834         for (int location = 0;
835              location < SyncSourceReport::ITEM_LOCATION_MAX;
836              location++) {
837             for (int state = 0;
838                  state < SyncSourceReport::ITEM_STATE_MAX;
839                  state++) {
840                 for (int result = 0;
841                      result < SyncSourceReport::ITEM_RESULT_MAX;
842                      result++) {
843                     int intval = source.getItemStat(SyncSourceReport::ItemLocation(location),
844                                                     SyncSourceReport::ItemState(state),
845                                                     SyncSourceReport::ItemResult(result));
846                     if (intval) {
847                         key = prefix + "-stat-" +
848                             SyncSourceReport::StatTupleToString(SyncSourceReport::ItemLocation(location),
849                                                                 SyncSourceReport::ItemState(state),
850                                                                 SyncSourceReport::ItemResult(result));
851                         node.setProperty(key, intval);
852                     }
853                 }
854             }
855         }
856     }
857
858     return node;
859 }
860
861 ConfigNode &operator >> (ConfigNode &node, SyncReport &report)
862 {
863     long ts;
864     if (node.getProperty("start", ts)) {
865         report.setStart(ts);
866     }
867     if (node.getProperty("end", ts)) {
868         report.setEnd(ts);
869     }
870     int status;
871     if (node.getProperty("status", status)) {
872         report.setStatus(static_cast<SyncMLStatus>(status));
873     }
874     string error;
875     if (node.getProperty("error", error)) {
876         report.setError(error);
877     }
878
879     ConfigNode::PropsType props;
880     node.readProperties(props);
881     BOOST_FOREACH(const ConfigNode::PropsType::value_type &prop, props) {
882         string key = prop.first;
883         if (boost::starts_with(key, "source-")) {
884             key.erase(0, strlen("source-"));
885             size_t off = key.find('-');
886             if (off != key.npos) {
887                 string sourcename = key.substr(0, off);
888                 boost::replace_all(sourcename, "_+", "-");
889                 boost::replace_all(sourcename, "__", "_");
890                 SyncSourceReport &source = report.getSyncSourceReport(sourcename);
891                 key.erase(0, off + 1);
892                 if (boost::starts_with(key, "stat-")) {
893                     key.erase(0, strlen("stat-"));
894                     SyncSourceReport::ItemLocation location;
895                     SyncSourceReport::ItemState state;
896                     SyncSourceReport::ItemResult result;
897                     SyncSourceReport::StringToStatTuple(key, location, state, result);
898                     stringstream in(prop.second);
899                     int intval;
900                     in >> intval;
901                     source.setItemStat(location, state, result, intval);
902                 } else if (key == "mode") {
903                     source.recordFinalSyncMode(StringToSyncMode(prop.second));
904                 } else if (key == "first") {
905                     bool value;
906                     if (node.getProperty(prop.first, value)) {
907                         source.recordFirstSync(value);
908                     }
909                 } else if (key == "resume") {
910                     bool value;
911                     if (node.getProperty(prop.first, value)) {
912                         source.recordResumeSync(value);
913                     }
914                 } else if (key == "status") {
915                     long value;
916                     if (node.getProperty(prop.first, value)) {
917                         source.recordStatus(static_cast<SyncMLStatus>(value));
918                     }
919                 } else if (key == "virtualsource") {
920                     source.recordVirtualSource(node.readProperty(prop.first));
921                 } else if (key == "backup-before") {
922                     long value;
923                     if (node.getProperty(prop.first, value)) {
924                         source.m_backupBefore.setNumItems(value);
925                     }
926                 } else if (key == "backup-after") {
927                     long value;
928                     if (node.getProperty(prop.first, value)) {
929                         source.m_backupAfter.setNumItems(value);
930                     }
931                 }
932             }
933         }
934     }
935
936     return node;
937 }
938
939 SE_END_CXX