~ci-train-bot/syncevolution/syncevolution-ubuntu-yakkety-landing-098

« back to all changes in this revision

Viewing changes to src/backends/webdav/WebDAVSource.cpp

  • Committer: Patrick Ohly
  • Date: 2015-03-03 09:15:44 UTC
  • Revision ID: git-v1:75115022c525d21341be4113d789eccd6e23cca8
CalDAV: more efficient "is empty" check (FDO #86335)

Since 1.4.99.4, syncing WebDAV collections always checks first
whether there are items in the collections. This was partly done for
slow sync prevention (which is not necessary for empty collections),
partly for the "is the datastore usable" check.

However, this did not take into account that for CalDAV collections,
the entire content gets downloaded for this check. That is because
filtering by item type (VEVENT vs. VJOURNAL) is not implemented
correctly by all servers. So now all CalDAV syncs, whether incremental
or slow, always transfered all items, which is not the
intention (incremental syncs should be fast and efficient).

This commit adds a more efficient isEmpty() check: for simple CardDAV
collections, only luid and etag get transferred, as in
listAllItems(). This is the behavior from 1.5.

For CalDAV, a report with a filter for the content type is used and
the transfer gets aborted after the first item, without actually
double-checking the content of the item. This is different from
listAllItems(), which really transfers the content. This extra content
check would only be needed for some old servers (Radical 0.7) and is
not essential, because reporting "not empty" even when empty is safe.

Show diffs side-by-side

added added

removed removed

Lines of Context:
10
10
#include <boost/algorithm/string/classification.hpp>
11
11
#include <boost/algorithm/string/find.hpp>
12
12
#include <boost/scoped_ptr.hpp>
 
13
#include <boost/lambda/lambda.hpp>
13
14
 
14
15
#include <syncevo/LogRedirect.h>
15
16
#include <syncevo/IdentityProvider.h>
1579
1580
    }
1580
1581
}
1581
1582
 
 
1583
static const ne_propname getetag[] = {
 
1584
    { "DAV:", "getetag" },
 
1585
    { "DAV:", "resourcetype" },
 
1586
    { NULL, NULL }
 
1587
};
 
1588
 
 
1589
static int FoundItem(bool &isEmpty,
 
1590
                     const std::string &href,
 
1591
                     const std::string &etag,
 
1592
                     const std::string &status)
 
1593
{
 
1594
    if (isEmpty) {
 
1595
        Neon::Status parsed;
 
1596
        // Err on the side of caution: if unsure about status, include item.
 
1597
        if (parsed.parse(status.c_str()) ||
 
1598
            parsed.klass == 2) {
 
1599
            isEmpty = false;
 
1600
        }
 
1601
    }
 
1602
    return isEmpty ? 0 : 100;
 
1603
}
 
1604
 
1582
1605
bool WebDAVSource::isEmpty()
1583
1606
{
1584
1607
    contactServer();
1585
1608
 
1586
 
    // listing all items is relatively efficient, let's use that
1587
 
    // TODO: use truncated result search
1588
 
    RevisionMap_t revisions;
1589
 
    listAllItems(revisions);
1590
 
    return revisions.empty();
 
1609
    bool isEmpty;
 
1610
    if (!getContentMixed()) {
 
1611
        // Can use simple PROPFIND because we do not have to
 
1612
        // double-check that each item really contains the right data.
 
1613
        bool failed = false;
 
1614
        RevisionMap_t revisions;
 
1615
        Timespec deadline = createDeadline();
 
1616
        m_session->propfindURI(m_calendar.m_path, 1, getetag,
 
1617
                               boost::bind(&WebDAVSource::listAllItemsCallback,
 
1618
                                           this, _1, _2, boost::ref(revisions),
 
1619
                                           boost::ref(failed)),
 
1620
                               deadline);
 
1621
        if (failed) {
 
1622
            SE_THROW("incomplete listing of all items");
 
1623
        }
 
1624
        isEmpty = revisions.empty();
 
1625
    } else {
 
1626
        // Have to filter items on the server and set result to false
 
1627
        // when we get items back.
 
1628
        isEmpty = true;
 
1629
        const std::string query =
 
1630
            "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
 
1631
            "<C:calendar-query xmlns:D=\"DAV:\"\n"
 
1632
            "xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n"
 
1633
            "<D:prop>\n"
 
1634
            "<D:getetag/>\n"
 
1635
            "</D:prop>\n"
 
1636
            // Only get items of the right kind. In listAllItems() we
 
1637
            // don't trust the server to implement this correctly and
 
1638
            // double-check by downloading the data and looking into
 
1639
            // it, but here we are less concerned. It is less important
 
1640
            // to have reliable "is empty" information as long as we
 
1641
            // err on the side of returning "not empty" too often.
 
1642
            "<C:filter>\n"
 
1643
            "<C:comp-filter name=\"VCALENDAR\">\n"
 
1644
            "<C:comp-filter name=\"" + getContent() + "\">\n"
 
1645
            "</C:comp-filter>\n"
 
1646
            "</C:comp-filter>\n"
 
1647
            "</C:filter>\n"
 
1648
            "</C:calendar-query>\n";
 
1649
        Timespec deadline = createDeadline();
 
1650
        getSession()->startOperation("REPORT 'check for items'", deadline);
 
1651
        while (true) {
 
1652
            Neon::XMLParser parser;
 
1653
            parser.initAbortingReportParser(boost::bind(FoundItem,
 
1654
                                                        boost::ref(isEmpty),
 
1655
                                                        _1, _2, _3));
 
1656
            Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
 
1657
            report.addHeader("Depth", "1");
 
1658
            report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
 
1659
            if (getSession()->run(report, NULL, !boost::lambda::var(isEmpty))) {
 
1660
                break;
 
1661
            }
 
1662
        }
 
1663
    }
 
1664
 
 
1665
    SE_LOG_DEBUG(getDisplayName(), "is %s", isEmpty ? "empty" : "not empty");
 
1666
    return isEmpty;
1591
1667
}
1592
1668
 
1593
1669
bool WebDAVSource::isUsable()
1820
1896
    return ctag;
1821
1897
}
1822
1898
 
1823
 
 
1824
 
static const ne_propname getetag[] = {
1825
 
    { "DAV:", "getetag" },
1826
 
    { "DAV:", "resourcetype" },
1827
 
    { NULL, NULL }
1828
 
};
1829
 
 
1830
1899
void WebDAVSource::listAllItems(RevisionMap_t &revisions)
1831
1900
{
1832
1901
    contactServer();