summaryrefslogtreecommitdiff
path: root/src/syncevo/MapSyncSource.h
blob: 215ac5f62e9a5c5e85104a03cca4ad5363c861dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*
 * Copyright (C) 2010 Intel Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) version 3.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#ifndef INCL_MAPSYNCSOURCE
#define INCL_MAPSYNCSOURCE

#include <syncevo/TrackingSyncSource.h>
#include <syncevo/util.h>

#include <syncevo/declarations.h>
SE_BEGIN_CXX

class MapSyncSource;

/**
 * rev + uid + list of subid; mainid is part of the context
 */
struct SubRevisionEntry {
    std::string m_revision;
    std::string m_uid;
    set<string> m_subids;
};

/**
 * mainid to rev + uid + list of subid
 *
 * List must contain an empty entry for the main item, if and only
 * if one exists.
 */
typedef map<string, SubRevisionEntry> SubRevisionMap_t;

/**
 * This is the API that must be implemented in addition to
 * TrackingSyncSource and SyncSourceLogging by a source to
 * be wrapped by MapSyncSource.
 *
 * The original interface will only be used in "raw" mode, which
 * should bypass any kind of cache used by the implementation.
 * They are guaranteed to be passed merged items.
 *
 * The new methods with mainid and subid are using during a sync
 * and should use the cache. They work on single items but modify
 * merged items. Thus the revision string of all sub items in
 * the same merged item will get modified when manipulating
 * one of its sub items.
 */
class SubSyncSource : virtual public SyncSourceBase
{
 public:
    class SubItemResult {
    public:
        SubItemResult() :
            m_state(ITEM_OKAY)
        {}
        
        /**
         * @param mainid    the ID used to access a set of items; may be different
         *                  from a (iCalendar 2.0) UID; during an update the mainid must
         *                  not be changed, so return the original one here
         * @param subid     optional subid, same rules as for mainid
         * @param revision  the revision string of the merged item after the operation; leave empty if not used
         * @param uid       an arbitrary string, stored, but not used by MapSyncSource;
         *                  used in the CalDAV backend to associate mainid (= resource path)
         *                  with UID (= part of the item content, but with special semantic)
         * @param state     report about what was done with the data
         */
        SubItemResult(const string &mainid,
                      const string &subid,
                      const string &revision,
                      const string &uid,
                      InsertItemResultState state) :
            m_mainid(mainid),
            m_subid(subid),
            m_revision(revision),
            m_uid(uid),
            m_state(state)
        {}

        string m_mainid;
        string m_subid;
        string m_revision;
        string m_uid;
        InsertItemResultState m_state;
    };

    SubSyncSource() : m_parent(NULL) {}

    /**
     * tells SubSyncSource about MapSyncSource which wraps it,
     * for getSynthesisAPI()
     */
    void setParent(MapSyncSource *parent) { m_parent = parent; }
    MapSyncSource *getParent() const { return m_parent; }

    virtual SDKInterface *getSynthesisAPI() const;

    /** called after open() and before any of the following methods */
    virtual void begin() = 0;

    /** called after a sync */
    virtual void endSubSync(bool success) = 0;

    /**
     * A unique identifier for the current state of the complete database.
     * The semantic is the following:
     * - empty string implies "state unknown" or "identifier not supported"
     * - id not empty and ID1 == ID2 implies "nothing has changed";
     *   the inverse is not true (ids may be different although nothing has changed)
     *
     * Matches TrackingSyncSource::databaseRevision().
     */
    virtual std::string subDatabaseRevision() { return ""; }

    /**
     * Either listAllSubItems(), setAllSubItems(), or updateAllSubitems()
     * will be called after begin().
     *
     * In the first case, the sub source is expected to provide a full list
     * of its items. In the second case, the caller was able to determine
     * that its cached copy of that list is still correct and provides it
     * the the source. In the third case, some revision information is know,
     * but it may be obsolete (revision string and/or subids changed or removed)
     * or incomplete (new items missing). The callee then must update the
     * information, possibly by falling back to listAllSubItems().
     */
    virtual void listAllSubItems(SubRevisionMap_t &revisions) = 0;

    /**
     * Called instead of listAllSubItems().
     */
    virtual void updateAllSubItems(SubRevisionMap_t &revisions) {
        revisions.clear();
        listAllSubItems(revisions);
    }

    /**
     * Called instead of listAllSubItems().
     */
    virtual void setAllSubItems(const SubRevisionMap_t &revisions) = 0;

    virtual SubItemResult insertSubItem(const std::string &mainid, const std::string &subid,
                                        const std::string &item) = 0;
    virtual void readSubItem(const std::string &mainid, const std::string &subid, std::string &item) = 0;

    /**
     * Ensure that the sub-item does not exist. It is not an error to be called
     * for a non-existent sub-item or item.
     *
     * @return empty string if item is empty after removal, otherwise new revision string
     */
    virtual std::string removeSubItem(const string &mainid, const std::string &subid) = 0;

    /**
     * Remove all sub-items belonging to mainid.
     */
    virtual void removeMergedItem(const std::string &mainid) = 0;

    /**
     * Called whenever this class thinks that the item may no longer be
     * needed. Might be wrong...
     */
    virtual void flushItem(const string &mainid) = 0;

    /**
     * Describe sub-item. Might be called for item which does not exist and
     * must not throw an error in that case. Providing a description is optional
     * and should only be done when it is reasonably cheap.
     */
    virtual std::string getSubDescription(const string &mainid, const string &subid) = 0;

    /**
     * Called after MapSyncSource already populated the info structure.
     */
    virtual void updateSynthesisInfo(SynthesisInfo &info,
                                     XMLConfigFragments &fragments) {}

 private:
    MapSyncSource *m_parent;
};

/**
 * This class wraps an underlying SyncSource and maps each of the
 * underlying items into one or more items in this class. The main use case
 * are CalDAV and Exchange Web Services, where each item is a set
 * of all events with the same UID, whereas SyncEvolution and SyncML
 * treat each individual event as one item.
 *
 * Terminology:
 * - single item = item as presented by this class (VEVENT)
 * - merged item = combination of all items sharing the same luid/uid (VCALENDAR)
 * - luid = SyncEvolution locally unique ID (VEVENT), mapped to mainid+subid
 * - mainid = ID for accessing the set of items (WebDAV resource path)
 * - subid = unique ID (RECURRENCE-ID) for sub-items (VEVENT) inside underlying item (VCALENDAR)
 * - uid = another unique ID shared by underlying items (iCalendar 2.0 UID),
 *         not used by this class
 *
 * "luid" is composed from "mainid" and "subid" by backslash-escaping a
 * slash (/), then concatenating the results with a slash as
 * separator. If the subid is empty, no slash is added. The main
 * advantage of this scheme is that it works well with sub sources
 * which use URI escaping.
 *
 * This class expects an extended TrackingSyncSource (=
 * SubSyncSource) merely because that interface is the most convenient
 * to work with. If necessary, the logic may be refactored later on.
 *
 * This class uses much of the same infrastructure as the TrackingSyncSource,
 * except for change detection. This used to be done with TrackingSyncSource
 * as base class, but there are some key differences which made this very
 * awkward, primarily the fact that all merged items share the same revision
 * string. Now the tracking node is used to store one entry per merged item, in
 * the format "ref-<mainid> = <revision>/<uid>/<subid1>/<subid2>/..."
 *
 * The following rules apply:
 * - A single item is added if its luid is new, updated if it exists and
 *   the merged item's revision string is different, deleted if the luid is
 *   gone (same logic as in normal TrackingSyncSource).
 * - A mainid is assigned to a new merged item by creating the merged item.
 * - Changes for an existing merged item may be applied to a cache,
 *   which is explicitly flushed by this class. This implies that
 *   such local changes must keep the mainid stable and have control
 *   over the subid.
 * - Item logging is offered by this class (LoggingSyncSource), but
 *   entirely depends on the sub source to implement the functionality.
 */
class MapSyncSource :
    public TestingSyncSource, // == SyncSourceSession, SyncSourceChanges, SyncSourceDelete, SyncSourceSerialize
    virtual public SyncSourceAdmin,
    virtual public SyncSourceBlob,
    virtual public SyncSourceLogging
{
  public:
    /**
     * @param sub      must also implement TrackingSyncSource and SyncSourceLogging interfaces!
     */
    MapSyncSource(const SyncSourceParams &params,
                  const boost::shared_ptr<SubSyncSource> &sub);
    ~MapSyncSource() {}

    /** compose luid from mainid and subid */
    static std::string createLUID(const std::string &mainid, const std::string &subid);

    /** split luid into mainid (first) and subid (second) */
    static StringPair splitLUID(const std::string &luid);

    virtual void enableServerMode();
    virtual bool serverModeEnabled() const;
    virtual std::string getPeerMimeType() const { return getMimeType(); }
    virtual Databases getDatabases() { return dynamic_cast<SyncSource &>(*m_sub).getDatabases(); }
    virtual void open() { dynamic_cast<SyncSource &>(*m_sub).open(); }
    virtual void beginSync(const std::string &lastToken, const std::string &resumeToken);
    virtual std::string endSync(bool success);
    virtual bool isEmpty() { return dynamic_cast<SyncSource &>(*m_sub).getOperations().m_isEmpty(); }
    virtual InsertItemResult insertItem(const std::string &luid, const std::string &item);
    virtual void readItem(const std::string &luid, std::string &item);
    virtual void deleteItem(const string &luid);
    virtual void close() { dynamic_cast<SyncSource &>(*m_sub).close(); }
    virtual std::string getMimeType() const { return dynamic_cast<SyncSourceSerialize &>(*m_sub).getMimeType(); }
    virtual std::string getMimeVersion() const { return dynamic_cast<SyncSourceSerialize &>(*m_sub).getMimeVersion(); }
    virtual std::string getDescription(sysync::KeyH aItemKey) { return dynamic_cast<SyncSourceLogging &>(*m_sub).getDescription(aItemKey); }
    virtual std::string getDescription(const string &luid);

    /* TestingSyncSource */
    virtual void removeAllItems();

 protected:
    virtual void getSynthesisInfo(SynthesisInfo &info,
                                  XMLConfigFragments &fragments) {
        TestingSyncSource::getSynthesisInfo(info, fragments);
        m_sub->updateSynthesisInfo(info, fragments);
    }

 private:
    boost::shared_ptr<SubSyncSource> m_sub;
    /** escape / in uid with %2F, so that splitMainIDValue() and splitLUID() can use / as separator */
    static StringEscape m_escape;
    std::string m_oldLUID;

    /**
     * information about the current set of items:
     * initialized as part of beginSync(),
     * updated as items are modified,
     * stored in endSync()
     */
    SubRevisionMap_t m_revisions;

    /** on-disk representation of m_revisions */
    boost::shared_ptr<ConfigNode> m_trackingNode;

    /**
     * Stores meta information besides the item list:
     * - "databaseRevision" = result of databaseRevision() at end of last sync
     *
     * Shares the same key/value store as m_trackingNode,
     * which uses the "item-" prefix in its keys to
     * avoid name clashes.
     */
    boost::shared_ptr<ConfigNode> m_metaNode;

    /** mirrors SyncSourceRevisions::detectChanges() */
    void detectChanges(SyncSourceRevisions::ChangeMode mode);
};

SE_END_CXX
#endif // INCL_MAPSYNCSOURCE