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