WebDAV: added extensive resending of requests

This is motivated by the observation that the Yahoo server becomes
unresponsive (various 5xx errors) quite often, which used to abort
the sync. Now there are better chances that it'll complete eventually,
although the root cause (server not responsive enough) remains.

PROPFIND, PUT and GET are all tried again. For a failed PUT this is
problematic because it is simply not known whether the command was
already executed. A check for "item does not exist" therefore can only
be done on the first attempt. Likewise, any future "eTag matches"
check (not yet implemented) will be also impossible.

Because of the retry, a PUT is no longer marked as not
idem-potent. The advantage is that an existing connection will be
reused for it.

The implementation is based around the idea that the higher levels in
the stack define a deadline until which the operation must succeed,
and then the lower levels (individual PROPFIND/PUT/GET) retry inside
that allowed time interval. That ensures that the initial service
discovery never takes longer that the configured timeout, which
wouldn't be the case if the timeout was applied to every single
PROPFIND inside the semantic operation.
This commit is contained in:
Patrick Ohly 2011-04-14 10:10:17 +02:00
parent 0d96fa07d3
commit 106598bd16
5 changed files with 310 additions and 128 deletions

View file

@ -64,19 +64,26 @@ void CalDAVSource::listAllSubItems(SubRevisionMap_t &revisions)
"</C:comp-filter>\n"
"</C:filter>\n"
"</C:calendar-query>\n";
string result;
string href, etag, data;
Neon::XMLParser parser;
parser.initReportParser(href, etag);
parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3),
boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3),
boost::bind(&CalDAVSource::appendItem, this,
boost::ref(revisions),
boost::ref(href), boost::ref(etag), boost::ref(data)));
Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
report.addHeader("Depth", "1");
report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
report.run();
Timespec deadline = createDeadline();
while (true) {
string result;
string href, etag, data;
Neon::XMLParser parser;
parser.initReportParser(href, etag);
m_cache.clear();
parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3),
boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3),
boost::bind(&CalDAVSource::appendItem, this,
boost::ref(revisions),
boost::ref(href), boost::ref(etag), boost::ref(data)));
Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
report.addHeader("Depth", "1");
report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
if (report.run(deadline)) {
break;
}
}
m_cache.m_initialized = true;
}
@ -600,17 +607,22 @@ CalDAVSource::Event &CalDAVSource::loadItem(Event &event)
"</C:filter>\n"
"</C:calendar-query>\n",
event.m_UID.c_str());
string result;
string href, etag;
Neon::XMLParser parser;
parser.initReportParser(href, etag);
item = "";
parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3),
boost::bind(Neon::XMLParser::append, boost::ref(item), _2, _3));
Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
report.addHeader("Depth", "1");
report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
report.run();
Timespec deadline = createDeadline();
while (true) {
string result;
string href, etag;
Neon::XMLParser parser;
parser.initReportParser(href, etag);
item = "";
parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3),
boost::bind(Neon::XMLParser::append, boost::ref(item), _2, _3));
Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
report.addHeader("Depth", "1");
report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
if (report.run(deadline)) {
break;
}
}
#endif
} else {
throw;
@ -762,7 +774,9 @@ void CalDAVSource::backupData(const SyncSource::Operations::ConstBackupInfo &old
Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
report.addHeader("Depth", "1");
report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
report.run();
// TODO: try multiple times to create data dump (must change ItemCache.init() such that
// it wipes out incomplete previous dump)
report.run(Timespec());
cache.finalize(backupReport);
}

View file

@ -377,8 +377,10 @@ unsigned int Session::options(const std::string &path)
void Session::propfindURI(const std::string &path, int depth,
const ne_propname *props,
const PropfindURICallback_t &callback)
const PropfindURICallback_t &callback,
const Timespec &deadline)
{
retry:
ne_propfind_handler *handler;
int error;
@ -406,7 +408,9 @@ void Session::propfindURI(const std::string &path, int depth,
StringPrintf("%d status: redirected to %s", code, location.c_str()),
code, location);
} else {
check(error, code);
if (!check(error, code, deadline)) {
goto retry;
}
}
}
@ -423,10 +427,12 @@ void Session::propsResult(void *userdata, const ne_uri *uri,
void Session::propfindProp(const std::string &path, int depth,
const ne_propname *props,
const PropfindPropCallback_t &callback)
const PropfindPropCallback_t &callback,
const Timespec &deadline)
{
propfindURI(path, depth, props,
boost::bind(&Session::propsIterate, _1, _2, boost::cref(callback)));
boost::bind(&Session::propsIterate, _1, _2, boost::cref(callback)),
deadline);
}
void Session::propsIterate(const URI &uri, const ne_prop_result_set *results,
@ -464,36 +470,92 @@ void Session::flush()
}
}
void Session::check(int error, int code)
bool Session::check(int error, int code, const Timespec &deadline, const ne_status *status)
{
flush();
// determine error description, may be made more specific below
string descr = StringPrintf("Neon error code %d: %s",
error,
ne_get_error(m_session));
// true for specific errors which might go away after a retry
bool retry = false;
switch (error) {
case NE_AUTH:
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
StringPrintf("Neon error code %d: %s",
error,
ne_get_error(m_session)),
STATUS_UNAUTHORIZED);
break;
case NE_OK:
// request itself completed, but might still have resulted in bad status
if (code &&
(code < 200 || code >= 300)) {
if (status) {
descr = std::string("bad HTTP status: ") + Status2String(status);
} else {
descr = StringPrintf("bad HTTP status: %d", code);
}
if (code >= 500 && code <= 599) {
// potentially temporary server failure, may try again
retry = true;
}
} else {
// all fine, no retry necessary
return true;
}
break;
case NE_AUTH:
// tell caller what kind of transport error occurred
code = STATUS_UNAUTHORIZED;
break;
case NE_ERROR:
if (code) {
// copy error code into exception
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
StringPrintf("Neon error code %d: %s",
error,
ne_get_error(m_session)),
SyncMLStatus(code));
descr = StringPrintf("Neon error code %d: %s",
error,
ne_get_error(m_session));
if (code >= 500 && code <= 599) {
// potentially temporary server failure, may try again
retry = true;
}
} else if (descr.find("Secure connection truncated") != descr.npos) {
// occasionally seen with Google server; let's retry
retry = true;
}
// no break
default:
SE_THROW_EXCEPTION(TransportException,
StringPrintf("Neon error code %d: %s",
error,
ne_get_error(m_session)));
break;
case NE_LOOKUP:
case NE_TIMEOUT:
case NE_CONNECT:
retry = true;
break;
}
SE_LOG_DEBUG(NULL, NULL, "%s, %s",
descr.c_str(),
retry ? "might retry" : "must not retry");
if (retry) {
if (!deadline) {
SE_LOG_DEBUG(NULL, NULL, "retrying not allowed for operation");
} else {
Timespec now = Timespec::monotonic();
if (now < deadline) {
int retrySeconds = m_settings->retrySeconds();
if (retrySeconds >= 0) {
SE_LOG_DEBUG(NULL, NULL, "retry operation in %ds", retrySeconds);
Sleep(retrySeconds);
} else {
SE_LOG_DEBUG(NULL, NULL, "retry operation immediately");
}
return false;
} else {
SE_LOG_DEBUG(NULL, NULL, "retrying would exceed deadline, bailing out");
}
}
}
if (code) {
// copy error code into exception
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
descr,
SyncMLStatus(code));
} else {
SE_THROW_EXCEPTION(TransportException,
descr);
}
}
@ -638,12 +700,10 @@ Request::~Request()
ne_request_destroy(m_req);
}
void Request::run()
bool Request::run(const Timespec &deadline)
{
int error;
int attempt = 0;
retry:
if (m_result) {
m_result->clear();
ne_add_response_body_reader(m_req, ne_accept_2xx,
@ -655,34 +715,11 @@ void Request::run()
m_session.flush();
if (false && error == NE_OK && getStatus()->code == 500) {
// Internal server error: seems to be Yahoo! Contacts way of
// throttling requests. Try again later. A similar loop
// exists *inside* neon for the credentials error seen
// with Google Calendar, see Session::getCredentials().
time_t last = m_session.getLastRequestEnd();
if (last) {
// repeat request after exponentially increasing
// delays since the last successful request (5
// seconds, 10 seconds, 20 seconds, ...) until it
// succeeds, but not for more than 1 minute
time_t delay = 5 * (1<<attempt);
if (delay <= 60) {
time_t now = time(NULL);
if (now < last + delay) {
SE_LOG_DEBUG(NULL, NULL, "500 internal server error due to throttling (?), retry #%d in %ld seconds",
attempt,
(long)(last + delay - now));
sleep(last + delay - now);
}
attempt++;
goto retry;
}
}
bool success = check(error, deadline);
if (success) {
m_session.setLastRequestEnd(time(NULL));
}
check(error);
m_session.setLastRequestEnd(time(NULL));
return success;
}
int Request::addResultData(void *userdata, const char *buf, size_t len)
@ -692,7 +729,7 @@ int Request::addResultData(void *userdata, const char *buf, size_t len)
return 0;
}
void Request::check(int error)
bool Request::check(int error, const Timespec &deadline)
{
if (error == NE_ERROR &&
getStatus()->klass == 3) {
@ -702,12 +739,7 @@ void Request::check(int error)
getStatus()->klass,
location);
}
m_session.check(error, getStatus()->code);
if (getStatus()->klass != 2) {
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
std::string("bad status: ") + Status2String(getStatus()),
SyncMLStatus(getStatus()->code));
}
return m_session.check(error, getStatus()->code, deadline, getStatus());
}
}

View file

@ -97,6 +97,12 @@ class Settings {
*/
virtual int timeoutSeconds() const = 0;
/**
* for network operations which fail before reaching timeoutSeconds()
* and can/should be retried: try again if > 0
*/
virtual int retrySeconds() const = 0;
/**
* use this to create a boost_shared pointer for a
* Settings instance which needs to be freed differently
@ -203,13 +209,21 @@ class Session {
/** ne_simple_propfind(): invoke callback for each URI */
void propfindURI(const std::string &path, int depth,
const ne_propname *props,
const PropfindURICallback_t &callback);
const ne_propname *props,
const PropfindURICallback_t &callback,
const Timespec &deadline);
/** ne_simple_propfind(): invoke callback for each property of each URI */
/**
* ne_simple_propfind(): invoke callback for each property of each URI
* @param deadline stop resending after that point in time,
* zero disables resending
* @param retrySeconds number of seconds to wait between resending,
* must not be negative
*/
void propfindProp(const std::string &path, int depth,
const ne_propname *props,
const PropfindPropCallback_t &callback);
const PropfindPropCallback_t &callback,
const Timespec &deadline);
/** URL which is in use */
std::string getURL() const { return m_uri.toURL(); }
@ -226,8 +240,18 @@ class Session {
/**
* throw error if error code indicates failure;
* pass additional status code from a request whenever possible
*
* @param error return code from Neon API call
* @param code HTTP status code
* @param deadline if set, then check whether error code and/or status indicated
* a temporary error and return true if also still before
* the deadline; otherwise throw error
* @param status optional ne_status pointer
*
* @return true for success, false if retry needed (only if deadline not empty);
* errors reported via exceptions
*/
void check(int error, int code = 0);
bool check(int error, int code = 0, const Timespec &deadline = Timespec(), const ne_status *status = NULL);
ne_session *getSession() const { return m_session; }
@ -408,7 +432,17 @@ class Request
void addHeader(const std::string &name, const std::string &value) {
ne_add_request_header(m_req, name.c_str(), value.c_str());
}
void run();
/**
* Execute the request. May only be called once per request. Uses
* Session::check() underneath to detect fatal errors and throw
* exceptions. The deadline parameter is passed to check(), see
* there.
*
* @return result of Session::check()
*/
bool run(const Timespec &deadline);
std::string getResponseHeader(const std::string &name) {
const char *value = ne_get_response_header(m_req, name.c_str());
return value ? value : "";
@ -431,7 +465,7 @@ class Request
static int addResultData(void *userdata, const char *buf, size_t len);
/** throw error if error code *or* current status indicates failure */
void check(int error);
bool check(int error, const Timespec &deadline);
};
/** thrown for 301 HTTP status */

View file

@ -10,6 +10,7 @@
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/scoped_ptr.hpp>
#include <syncevo/LogRedirect.h>
@ -109,11 +110,8 @@ public:
virtual bool googleChildHack() const { return m_googleChildHack; }
virtual bool googleAlarmHack() const { return m_googleChildHack; }
/**
* Communication is aborted after the configured retry duration.
* TODO: resend after retryInterval() seconds.
*/
virtual int timeoutSeconds() const { return m_context->getRetryDuration(); }
virtual int retrySeconds() const { return m_context->getRetryInterval(); }
virtual void getCredentials(const std::string &realm,
std::string &username,
@ -260,6 +258,13 @@ void WebDAVSource::replaceHTMLEntities(std::string &item)
void WebDAVSource::open()
{
int timeoutSeconds = m_settings->timeoutSeconds();
int retrySeconds = m_settings->retrySeconds();
SE_LOG_DEBUG(this, NULL, "timout %ds, retry %ds => %s",
timeoutSeconds, retrySeconds,
(timeoutSeconds <= 0 ||
retrySeconds <= 0) ? "resending disabled" : "resending allowed");
// ignore the "Request ends, status 207 class 2xx, error line:" printed by neon
LogRedirect::addIgnoreError(", error line:");
// ignore error messages in returned data
@ -289,6 +294,9 @@ void WebDAVSource::open()
FILE *in = NULL;
try {
Timespec startTime = Timespec::monotonic();
retry:
in = popen(StringPrintf("syncevo-webdav-lookup '%s' '%s'",
serviceType().c_str(),
domain.c_str()).c_str(),
@ -317,11 +325,25 @@ void WebDAVSource::open()
case 3:
throwError(StringPrintf("syncURL not configured and DNS SRV search for %s in %s did not find the service", serviceType().c_str(), domain.c_str()));
break;
default:
default: {
Timespec now = Timespec::monotonic();
if (retrySeconds > 0 &&
timeoutSeconds > 0) {
if (now < startTime + timeoutSeconds) {
SE_LOG_DEBUG(this, NULL, "DNS SRV search failed due to network issues, retry in %d seconds",
retrySeconds);
Sleep(retrySeconds);
goto retry;
} else {
SE_LOG_INFO(this, NULL, "DNS SRV search timed out after %d seconds", timeoutSeconds);
}
}
// probably network problem
throwError(STATUS_TRANSPORT_FAILURE, StringPrintf("syncURL not configured and DNS SRV search for %s in %s failed", serviceType().c_str(), domain.c_str()));
break;
}
}
} catch (...) {
if (in) {
pclose(in);
@ -359,6 +381,18 @@ void WebDAVSource::open()
boost::bind(&WebDAVSource::openPropCallback,
this, _1, _2, _3, _4);
// With Yahoo! the initial connection often failed with 50x
// errors. Retrying individual requests is error prone because at
// least one (asking for .well-known/[caldav|carddav]) always
// results in 502. Let the PROPFIND requests be resent, but in
// such a way that the overall discovery will never take longer
// than the total configured timeout period.
//
// The PROPFIND with openPropCallback is idempotent, because it
// will just overwrite previously found information in m_davProps.
// Therefore resending is okay.
Timespec finalDeadline = createDeadline(); // no resending if left empty
while (true) {
std::string next;
@ -373,8 +407,7 @@ void WebDAVSource::open()
// a Principal resource - perhaps reading that would lead further.
//
// So anyway, let's try the well-known URI first, but also add
// a hard-coded "well-known" fallback that will be tried
// next. Same for some other servers.
// the root path as fallback.
if (path == "/.well-known/caldav/" ||
path == "/.well-known/carddav/") {
// remove trailing slash added by normalization, to be aligned with draft-daboo-srv-caldav-10
@ -390,6 +423,13 @@ void WebDAVSource::open()
bool success = false;
try {
// disable resending for some known cases where it never succeeds
Timespec deadline = finalDeadline;
if (boost::starts_with(path, "/.well-known") &&
m_settings->getURL().find("yahoo.com") != string::npos) {
deadline = Timespec();
}
if (LoggerBase::instance().getLevel() >= Logger::DEV) {
// First dump WebDAV "allprops" properties (does not contain
// properties which must be asked for explicitly!). Only
@ -398,7 +438,7 @@ void WebDAVSource::open()
Neon::Session::PropfindPropCallback_t callback =
boost::bind(&WebDAVSource::openPropCallback,
this, _1, _2, _3, _4);
m_session->propfindProp(path, 0, NULL, callback);
m_session->propfindProp(path, 0, NULL, callback, deadline);
}
// Now ask for some specific properties of interest for us.
@ -460,7 +500,7 @@ void WebDAVSource::open()
{ NULL, NULL }
};
SE_LOG_DEBUG(NULL, NULL, "read relevant properties of %s", path.c_str());
m_session->propfindProp(path, 0, caldav, callback);
m_session->propfindProp(path, 0, caldav, callback, deadline);
success = true;
} catch (const Neon::RedirectException &ex) {
// follow to new location
@ -547,7 +587,7 @@ void WebDAVSource::open()
{ NULL, NULL }
};
m_davProps.clear();
m_session->propfindProp(path, 1, props, callback);
m_session->propfindProp(path, 1, props, callback, finalDeadline);
BOOST_FOREACH(Props_t::value_type &entry, m_davProps) {
const std::string &sub = entry.first;
const std::string &subType = entry.second["DAV::resourcetype"];
@ -737,10 +777,12 @@ static const ne_propname getetag[] = {
void WebDAVSource::listAllItems(RevisionMap_t &revisions)
{
bool failed = false;
Timespec deadline = createDeadline();
m_session->propfindURI(m_calendar.m_path, 1, getetag,
boost::bind(&WebDAVSource::listAllItemsCallback,
this, _1, _2, boost::ref(revisions),
boost::ref(failed)));
boost::ref(failed)),
deadline);
if (failed) {
SE_THROW("incomplete listing of all items");
}
@ -805,12 +847,17 @@ std::string WebDAVSource::luid2path(const std::string &luid)
void WebDAVSource::readItem(const string &uid, std::string &item, bool raw)
{
item.clear();
Neon::Request req(*m_session, "GET", luid2path(uid),
"", item);
// useful with CardDAV: server might support more than vCard 3.0, but we don't
req.addHeader("Accept", contentType());
req.run();
Timespec deadline = createDeadline();
while (true) {
item.clear();
Neon::Request req(*m_session, "GET", luid2path(uid),
"", item);
// useful with CardDAV: server might support more than vCard 3.0, but we don't
req.addHeader("Accept", contentType());
if (req.run(deadline)) {
break;
}
}
}
TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid, const std::string &item, bool raw)
@ -819,7 +866,12 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
std::string rev;
bool update = false; /* true if adding item was turned into update */
Timespec deadline = createDeadline(); // no resending if left empty
std::string result;
int counter = 0;
retry:
counter++;
result = "";
if (uid.empty()) {
// Pick a resource name (done by derived classes, by default random),
// catch unexpected conflicts via If-None-Match: *.
@ -827,10 +879,30 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
const std::string *data = createResourceName(item, buffer, new_uid);
Neon::Request req(*m_session, "PUT", luid2path(new_uid),
*data, result);
req.setFlag(NE_REQFLAG_IDEMPOTENT, 0);
req.addHeader("If-None-Match", "*");
// Clearing the idempotent flag would allow us to clearly
// distinguish between a connection error (no changes made
// on server) and a server failure (may or may not have
// changed something) because it'll close the connection
// first.
//
// But because we are going to try resending
// the PUT anyway in case of 5xx errors we might as well
// treat it like an idempotent request (which it is,
// in a way, because we'll try to get our data onto
// the server no matter what) and keep reusing an
// existing connection.
// req.setFlag(NE_REQFLAG_IDEMPOTENT, 0);
// For this to work we must allow the server to overwrite
// an item that we might have created before. Don't allow
// that in the first attempt.
if (counter == 1) {
req.addHeader("If-None-Match", "*");
}
req.addHeader("Content-Type", contentType().c_str());
req.run();
if (!req.run(deadline)) {
goto retry;
}
SE_LOG_DEBUG(NULL, NULL, "add item status: %s",
Neon::Status2String(req.getStatus()).c_str());
switch (req.getStatusCode()) {
@ -873,7 +945,8 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
m_session->propfindURI(luid2path(new_uid), 0, getetag,
boost::bind(&WebDAVSource::listAllItemsCallback,
this, _1, _2, boost::ref(revisions),
boost::ref(failed)));
boost::ref(failed)),
deadline);
// Turns out we get a result for our original path even in
// the case of a merge, although the original path is not
// listed when looking at the collection. Let's use that
@ -893,7 +966,8 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
const std::string *data = setResourceName(item, buffer, new_uid);
Neon::Request req(*m_session, "PUT", luid2path(new_uid),
*data, result);
req.setFlag(NE_REQFLAG_IDEMPOTENT, 0);
// See above for discussion of idempotent and PUT.
// req.setFlag(NE_REQFLAG_IDEMPOTENT, 0);
req.addHeader("Content-Type", contentType());
// TODO: match exactly the expected revision, aka ETag,
// or implement locking. Note that the ETag might not be
@ -904,7 +978,9 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
// - Is retried? Might need slow sync in this case!
//
// req.addHeader("If-Match", etag);
req.run();
if (!req.run(deadline)) {
goto retry;
}
SE_LOG_DEBUG(NULL, NULL, "update item status: %s",
Neon::Status2String(req.getStatus()).c_str());
switch (req.getStatusCode()) {
@ -941,7 +1017,8 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
m_session->propfindURI(luid2path(new_uid), 0, getetag,
boost::bind(&WebDAVSource::listAllItemsCallback,
this, _1, _2, boost::ref(revisions),
boost::ref(failed)));
boost::ref(failed)),
deadline);
rev = revisions[new_uid];
if (failed || rev.empty()) {
SE_THROW("could not retrieve ETag");
@ -973,26 +1050,44 @@ std::string WebDAVSource::getLUID(Neon::Request &req)
}
}
Timespec WebDAVSource::createDeadline() const
{
int timeoutSeconds = m_settings->timeoutSeconds();
int retrySeconds = m_settings->retrySeconds();
if (timeoutSeconds > 0 &&
retrySeconds > 0) {
return Timespec::monotonic() + timeoutSeconds;
} else {
return Timespec();
}
}
void WebDAVSource::removeItem(const string &uid)
{
Timespec deadline = createDeadline();
std::string item, result;
Neon::Request req(*m_session, "DELETE", luid2path(uid),
item, result);
// TODO: match exactly the expected revision, aka ETag,
// or implement locking.
// req.addHeader("If-Match", etag);
req.run();
boost::scoped_ptr<Neon::Request> req;
while (true) {
req.reset(new Neon::Request(*m_session, "DELETE", luid2path(uid),
item, result));
// TODO: match exactly the expected revision, aka ETag,
// or implement locking.
// req.addHeader("If-Match", etag);
if (req->run(deadline)) {
break;
}
}
SE_LOG_DEBUG(NULL, NULL, "remove item status: %s",
Neon::Status2String(req.getStatus()).c_str());
switch (req.getStatusCode()) {
Neon::Status2String(req->getStatus()).c_str());
switch (req->getStatusCode()) {
case 204:
// the expected outcome
break;
default:
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
std::string("unexpected status for removal: ") +
Neon::Status2String(req.getStatus()),
SyncMLStatus(req.getStatus()->code));
Neon::Status2String(req->getStatus()),
SyncMLStatus(req->getStatus()->code));
break;
}
}

View file

@ -76,7 +76,14 @@ class WebDAVSource : public TrackingSyncSource, private boost::noncopyable
*/
std::string ETag2Rev(const std::string &etag);
protected:
/**
* Calculates the time after which the next operation is
* expected to complete before giving up, based on
* current time and retry settings.
* @return absolute time, empty if no retrying allowed
*/
Timespec createDeadline() const;
// access to neon session and calendar, valid between open() and close()
boost::shared_ptr<Neon::Session> getSession() { return m_session; }
Neon::URI &getCalendar() { return m_calendar; }