mirror of
https://github.com/etesync/android
synced 2024-11-29 11:28:19 +00:00
Synchronization logging to external file
* use ExternalFileLogger to log synchronization, if enabled in Settings * new settings: log to external file / log verbose * DavResource: check for well-known even if service type of user-given URL can't be determined * remove oblsete testing assets
This commit is contained in:
parent
dd50f10c58
commit
58f05986c9
@ -1,11 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:all-day-0sec@example.com
|
|
||||||
DTSTAMP:20140101T000000Z
|
|
||||||
DTSTART;VALUE=DATE:19970714
|
|
||||||
DTEND;VALUE=DATE:19970714
|
|
||||||
SUMMARY:0 Sec Event
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
@ -1,11 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:all-day-10days@example.com
|
|
||||||
DTSTAMP:20140101T000000Z
|
|
||||||
DTSTART;VALUE=DATE:19970714
|
|
||||||
DTEND;VALUE=DATE:19970724
|
|
||||||
SUMMARY:All-Day 10 Days
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
@ -1,11 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:all-day-1day@example.com
|
|
||||||
DTSTAMP:20140101T000000Z
|
|
||||||
DTSTART;VALUE=DATE:19970714
|
|
||||||
DTEND;VALUE=DATE:19970714
|
|
||||||
SUMMARY:All-Day 1 Day
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB |
@ -1,11 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:event-on-that-day@example.com
|
|
||||||
DTSTAMP:19970714T170000Z
|
|
||||||
ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
|
|
||||||
DTSTART;VALUE=DATE:19970714
|
|
||||||
SUMMARY:Bastille Day Party
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
@ -1,9 +0,0 @@
|
|||||||
BEGIN:VCARD
|
|
||||||
VERSION:3.0
|
|
||||||
UID:2de59c6cc9
|
|
||||||
PRODID:-//ownCloud//NONSGML Contacts 0.2.5//EN
|
|
||||||
REV:2013-12-08T00:04:30+00:00
|
|
||||||
FN:test mctest
|
|
||||||
N:mctest;test;;;
|
|
||||||
IMPP;TYPE=WORK;X-SERVICE-TYPE=jabber:test-without-valid-scheme@test.tld
|
|
||||||
END:VCARD
|
|
@ -1,5 +0,0 @@
|
|||||||
BEGIN:VCARD
|
|
||||||
VERSION:3.0
|
|
||||||
FN:VCard with invalid unknown properties
|
|
||||||
X-UNKNOWN@PROPERTY:MUST-NOT_CONTAIN?OTHER*LETTERS;
|
|
||||||
END:VCARD
|
|
@ -1,5 +0,0 @@
|
|||||||
BEGIN:VCARD
|
|
||||||
VERSION:3.0
|
|
||||||
N:Äuçek;Özkan
|
|
||||||
FN:Özkan Äuçek
|
|
||||||
END:VCARD
|
|
@ -1,17 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:fcb42e4d-bc6e-4499-97f0-6616a02da7bc
|
|
||||||
SUMMARY:Recurring event with one exception
|
|
||||||
RRULE:FREQ=DAILY;COUNT=5
|
|
||||||
DTSTART;VALUE=DATE:20150501
|
|
||||||
DTEND;VALUE=DATE:20150502
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:fcb42e4d-bc6e-4499-97f0-6616a02da7bc
|
|
||||||
RECURRENCE-ID;VALUE=DATE:20150503
|
|
||||||
DTSTART;VALUE=DATE:20150503
|
|
||||||
DTEND;VALUE=DATE:20150504
|
|
||||||
SUMMARY:Another summary for the third day
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
@ -1,16 +0,0 @@
|
|||||||
BEGIN:VCARD
|
|
||||||
VERSION:3.0
|
|
||||||
N:Gümp;Förrest;Mr.
|
|
||||||
FN:Förrest Gümp
|
|
||||||
ORG:Bubba Gump Shrimpß Co.
|
|
||||||
TITLE:Shrimp Man
|
|
||||||
PHOTO;VALUE=URL;TYPE=PNG:http://192.168.0.11:3000/assets/davdroid-logo-192.png
|
|
||||||
TEL;TYPE=WORK,VOICE:(111) 555-1212
|
|
||||||
TEL;TYPE=HOME,VOICE:(404) 555-1212
|
|
||||||
ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
|
|
||||||
LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America
|
|
||||||
ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
|
|
||||||
LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America
|
|
||||||
EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com
|
|
||||||
REV:2008-04-24T19:52:43Z
|
|
||||||
END:VCARD
|
|
Binary file not shown.
@ -1,14 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
PRODID:Blabla
|
|
||||||
BEGIN:VEVENT
|
|
||||||
CLASS:PUBLIC
|
|
||||||
CREATED;VALUE=DATE-TIME:20131008T205713
|
|
||||||
LAST-MODIFIED;VALUE=DATE-TIME:20131008T205740
|
|
||||||
SUMMARY:online Anmeldung
|
|
||||||
DESCRIPTION:http://www.tgbornheim.de/index.php?sessionid=&page=&id=&sportce
|
|
||||||
ntergroup=&day=6
|
|
||||||
UID:b99c41704b
|
|
||||||
DTSTART;VALUE=DATE-TIME;TZID=Europe/Berlin:20131019T060000
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
@ -1,16 +0,0 @@
|
|||||||
BEGIN:VCARD
|
|
||||||
VERSION:3.0
|
|
||||||
N:Gump;Forrest
|
|
||||||
FN:Forrest Gump
|
|
||||||
ORG:Bubba Gump Shrimp Co.
|
|
||||||
TITLE:Shrimp Man
|
|
||||||
PHOTO;VALUE=URL;TYPE=GIF:http://www.example.com/dir_photos/my_photo.gif
|
|
||||||
TEL;TYPE=WORK,VOICE:(111) 555-1212
|
|
||||||
TEL;TYPE=HOME,VOICE:(404) 555-1212
|
|
||||||
ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
|
|
||||||
LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America
|
|
||||||
ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
|
|
||||||
LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America
|
|
||||||
EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com
|
|
||||||
REV:2008-04-24T19:52:43Z
|
|
||||||
END:VCARD
|
|
@ -1,33 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
|
||||||
VERSION:2.0
|
|
||||||
METHOD:PUBLISH
|
|
||||||
BEGIN:VTIMEZONE
|
|
||||||
TZID:/freeassociation.sourceforge.net/Tzfile/Europe/Vienna
|
|
||||||
X-LIC-LOCATION:Europe/Vienna
|
|
||||||
BEGIN:STANDARD
|
|
||||||
TZNAME:CET
|
|
||||||
DTSTART:19701027T030000
|
|
||||||
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
|
||||||
TZOFFSETFROM:+0200
|
|
||||||
TZOFFSETTO:+0100
|
|
||||||
END:STANDARD
|
|
||||||
BEGIN:DAYLIGHT
|
|
||||||
TZNAME:CEST
|
|
||||||
DTSTART:19700331T020000
|
|
||||||
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
|
|
||||||
TZOFFSETFROM:+0100
|
|
||||||
TZOFFSETTO:+0200
|
|
||||||
END:DAYLIGHT
|
|
||||||
END:VTIMEZONE
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:c252087c-7354-4722-aea9-0e7d86c01a25
|
|
||||||
DTSTAMP:20130926T151211Z
|
|
||||||
SUMMARY:Test-Ereignis im schönen Wien
|
|
||||||
DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T170000
|
|
||||||
DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T180000
|
|
||||||
X-RADICALE-NAME:97929342-291a-434e-bf1a-fa1749bf99d0.ics
|
|
||||||
X-EVOLUTION-CALDAV-HREF:/radicale/rfc2822/default.ics/97929342-291a-434e-bf1a-fa1749bf99d0.ics
|
|
||||||
X-EVOLUTION-CALDAV-ETAG:\"-3264224243575339985\"
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
@ -20,6 +20,7 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
|
||||||
|
|
||||||
<uses-permission android:name="org.dmfs.permission.READ_TASKS" />
|
<uses-permission android:name="org.dmfs.permission.READ_TASKS" />
|
||||||
<uses-permission android:name="org.dmfs.permission.WRITE_TASKS" />
|
<uses-permission android:name="org.dmfs.permission.WRITE_TASKS" />
|
||||||
|
@ -21,6 +21,8 @@ import com.squareup.okhttp.Response;
|
|||||||
import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
|
import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
|
||||||
import com.squareup.okhttp.logging.HttpLoggingInterceptor;
|
import com.squareup.okhttp.logging.HttpLoggingInterceptor;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
@ -39,18 +41,7 @@ import de.duenndns.ssl.MemorizingTrustManager;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
public class HttpClient extends OkHttpClient {
|
public class HttpClient extends OkHttpClient {
|
||||||
protected static final String HEADER_AUTHORIZATION = "Authorization";
|
|
||||||
|
|
||||||
final static UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor();
|
final static UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor();
|
||||||
final static HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
|
||||||
@Override
|
|
||||||
public void log(String message) {
|
|
||||||
Constants.log.trace(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
static {
|
|
||||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
|
||||||
}
|
|
||||||
|
|
||||||
static final String userAgent;
|
static final String userAgent;
|
||||||
static {
|
static {
|
||||||
@ -58,12 +49,14 @@ public class HttpClient extends OkHttpClient {
|
|||||||
userAgent = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; dav4android) Android/" + Build.VERSION.RELEASE;
|
userAgent = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; dav4android) Android/" + Build.VERSION.RELEASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Logger log;
|
||||||
final Context context;
|
final Context context;
|
||||||
protected String username, password;
|
protected String username, password;
|
||||||
|
|
||||||
|
|
||||||
protected HttpClient(Context context) {
|
protected HttpClient(final Logger log, Context context) {
|
||||||
super();
|
super();
|
||||||
|
this.log = (log != null) ? log : Constants.log;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
@ -90,12 +83,20 @@ public class HttpClient extends OkHttpClient {
|
|||||||
networkInterceptors().add(userAgentInterceptor);
|
networkInterceptors().add(userAgentInterceptor);
|
||||||
|
|
||||||
// enable verbose logs, if requested
|
// enable verbose logs, if requested
|
||||||
if (Constants.log.isTraceEnabled())
|
if (log.isTraceEnabled()) {
|
||||||
enableLogs();
|
HttpLoggingInterceptor logger = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
||||||
|
@Override
|
||||||
|
public void log(String message) {
|
||||||
|
log.trace(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
logger.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
interceptors().add(logger);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClient(Context context, String username, String password, boolean preemptive) {
|
public HttpClient(Logger log, Context context, String username, String password, boolean preemptive) {
|
||||||
this(context);
|
this(log, context);
|
||||||
|
|
||||||
// authentication
|
// authentication
|
||||||
this.username = username;
|
this.username = username;
|
||||||
@ -113,8 +114,8 @@ public class HttpClient extends OkHttpClient {
|
|||||||
* @param client user name and password from this client will be used
|
* @param client user name and password from this client will be used
|
||||||
* @param host authentication will be restricted to this host
|
* @param host authentication will be restricted to this host
|
||||||
*/
|
*/
|
||||||
public HttpClient(HttpClient client, String host) {
|
public HttpClient(Logger log, HttpClient client, String host) {
|
||||||
this(client.context);
|
this(log, client.context);
|
||||||
|
|
||||||
username = client.username;
|
username = client.username;
|
||||||
password = client.password;
|
password = client.password;
|
||||||
@ -123,12 +124,7 @@ public class HttpClient extends OkHttpClient {
|
|||||||
|
|
||||||
// for testing (mock server doesn't need auth)
|
// for testing (mock server doesn't need auth)
|
||||||
protected HttpClient() {
|
protected HttpClient() {
|
||||||
this(null, null, null, false);
|
this(null, null, null, null, false);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void enableLogs() {
|
|
||||||
interceptors().add(loggingInterceptor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -142,7 +138,6 @@ public class HttpClient extends OkHttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
static class PreemptiveAuthenticationInterceptor implements Interceptor {
|
static class PreemptiveAuthenticationInterceptor implements Interceptor {
|
||||||
final String username, password;
|
final String username, password;
|
||||||
|
348
app/src/main/java/at/bitfire/davdroid/log/CustomLogger.java
Normal file
348
app/src/main/java/at/bitfire/davdroid/log/CustomLogger.java
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package at.bitfire.davdroid.log;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.Marker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DAVdroid logger base class that wraps all calls around some standard log() calls.
|
||||||
|
* @throws UnsupportedOperationException for all methods with Marker
|
||||||
|
* arguments (as Markers are not used by DAVdroid logging).
|
||||||
|
*/
|
||||||
|
public abstract class CustomLogger implements Logger {
|
||||||
|
|
||||||
|
private static final String
|
||||||
|
PREFIX_ERROR = "[error] ",
|
||||||
|
PREFIX_WARN = "[warn ] ",
|
||||||
|
PREFIX_INFO = "[info ] ",
|
||||||
|
PREFIX_DEBUG = "[debug] ",
|
||||||
|
PREFIX_TRACE = "[trace] ";
|
||||||
|
|
||||||
|
protected boolean verbose;
|
||||||
|
|
||||||
|
protected abstract void log(String prefix, String msg);
|
||||||
|
protected abstract void log(String prefix, String format, Object arg);
|
||||||
|
protected abstract void log(String prefix, String format, Object arg1, Object arg2);
|
||||||
|
protected abstract void log(String prefix, String format, Object... args);
|
||||||
|
protected abstract void log(String prefix, String msg, Throwable t);
|
||||||
|
|
||||||
|
|
||||||
|
// STANDARD CALLS
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTraceEnabled() {
|
||||||
|
return verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(String msg) {
|
||||||
|
if (verbose)
|
||||||
|
log(PREFIX_TRACE, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(String format, Object arg) {
|
||||||
|
if (verbose)
|
||||||
|
log(PREFIX_TRACE, format, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(String format, Object arg1, Object arg2) {
|
||||||
|
if (verbose) log(PREFIX_TRACE, format, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(String format, Object... arguments) {
|
||||||
|
if (verbose)
|
||||||
|
log(PREFIX_TRACE, format, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(String msg, Throwable t) {
|
||||||
|
if (verbose)
|
||||||
|
log(PREFIX_TRACE, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDebugEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(String msg) {
|
||||||
|
log(PREFIX_DEBUG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(String format, Object arg) {
|
||||||
|
log(PREFIX_DEBUG, format, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(String format, Object arg1, Object arg2) {
|
||||||
|
log(PREFIX_DEBUG, format, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(String format, Object... arguments) {
|
||||||
|
log(PREFIX_DEBUG, format, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(String msg, Throwable t) {
|
||||||
|
log(PREFIX_DEBUG, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInfoEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String msg) {
|
||||||
|
log(PREFIX_INFO, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String format, Object arg) {
|
||||||
|
log(PREFIX_INFO, format, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String format, Object arg1, Object arg2) {
|
||||||
|
log(PREFIX_INFO, format, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String format, Object... arguments) {
|
||||||
|
log(PREFIX_INFO, format, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(String msg, Throwable t) {
|
||||||
|
log(PREFIX_INFO, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWarnEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(String msg) {
|
||||||
|
log(PREFIX_WARN, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(String format, Object arg) {
|
||||||
|
log(PREFIX_WARN, format, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(String format, Object... arguments) {
|
||||||
|
log(PREFIX_WARN, format, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(String format, Object arg1, Object arg2) {
|
||||||
|
log(PREFIX_WARN, format, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(String msg, Throwable t) {
|
||||||
|
log(PREFIX_WARN, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isErrorEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String msg) {
|
||||||
|
log(PREFIX_ERROR, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String format, Object arg) {
|
||||||
|
log(PREFIX_ERROR, format, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String format, Object arg1, Object arg2) {
|
||||||
|
log(PREFIX_ERROR, format, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String format, Object... arguments) {
|
||||||
|
log(PREFIX_ERROR, format, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String msg, Throwable t) {
|
||||||
|
log(PREFIX_ERROR, msg, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// CALLS WITH MARKER
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTraceEnabled(Marker marker) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(Marker marker, String msg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(Marker marker, String format, Object arg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(Marker marker, String format, Object arg1, Object arg2) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(Marker marker, String format, Object... argArray) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void trace(Marker marker, String msg, Throwable t) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDebugEnabled(Marker marker) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(Marker marker, String msg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(Marker marker, String format, Object arg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(Marker marker, String format, Object arg1, Object arg2) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(Marker marker, String format, Object... arguments) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debug(Marker marker, String msg, Throwable t) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInfoEnabled(Marker marker) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(Marker marker, String msg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(Marker marker, String format, Object arg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(Marker marker, String format, Object arg1, Object arg2) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(Marker marker, String format, Object... arguments) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void info(Marker marker, String msg, Throwable t) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWarnEnabled(Marker marker) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(Marker marker, String msg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(Marker marker, String format, Object arg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(Marker marker, String format, Object arg1, Object arg2) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(Marker marker, String format, Object... arguments) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(Marker marker, String msg, Throwable t) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isErrorEnabled(Marker marker) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(Marker marker, String msg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(Marker marker, String format, Object arg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(Marker marker, String format, Object arg1, Object arg2) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(Marker marker, String format, Object... arguments) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(Marker marker, String msg, Throwable t) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package at.bitfire.davdroid.log;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public class ExternalFileLogger extends CustomLogger implements Closeable {
|
||||||
|
|
||||||
|
@Getter protected final String name;
|
||||||
|
|
||||||
|
protected final PrintWriter writer;
|
||||||
|
|
||||||
|
public ExternalFileLogger(Context context, String fileName, boolean verbose) throws IOException {
|
||||||
|
this.verbose = verbose;
|
||||||
|
|
||||||
|
File dir = context.getExternalFilesDir(null);
|
||||||
|
if (dir == null)
|
||||||
|
throw new IOException("External media not available for log creation");
|
||||||
|
|
||||||
|
File log = new File(dir, name = fileName);
|
||||||
|
writer = new PrintWriter(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void log(String prefix, String msg) {
|
||||||
|
writer.write(prefix + msg + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void log(String prefix, String format, Object arg) {
|
||||||
|
writer.write(prefix + format.replace("{}", arg.toString()) + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void log(String prefix, String format, Object arg1, Object arg2) {
|
||||||
|
writer.write(prefix + format.replaceFirst("\\{\\}", arg1.toString()).replaceFirst("\\{\\}", arg2.toString()) + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void log(String prefix, String format, Object... args) {
|
||||||
|
String message = prefix;
|
||||||
|
for (Object arg : args)
|
||||||
|
format.replaceFirst("\\{\\}", arg.toString());
|
||||||
|
writer.write(prefix + format + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void log(String prefix, String msg, Throwable t) {
|
||||||
|
writer.write(prefix + msg + " - EXCEPTION:");
|
||||||
|
t.printStackTrace(writer);
|
||||||
|
writer.write("CAUSED BY:\n");
|
||||||
|
ExceptionUtils.printRootCauseStackTrace(t, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,6 +12,7 @@ import android.text.TextUtils;
|
|||||||
|
|
||||||
import com.squareup.okhttp.HttpUrl;
|
import com.squareup.okhttp.HttpUrl;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
import org.xbill.DNS.Lookup;
|
import org.xbill.DNS.Lookup;
|
||||||
import org.xbill.DNS.Record;
|
import org.xbill.DNS.Record;
|
||||||
import org.xbill.DNS.SRVRecord;
|
import org.xbill.DNS.SRVRecord;
|
||||||
@ -57,7 +58,8 @@ public class DavResourceFinder {
|
|||||||
@Override public String toString() { return name; }
|
@Override public String toString() { return name; }
|
||||||
};
|
};
|
||||||
|
|
||||||
protected Context context;
|
protected final Logger log;
|
||||||
|
protected final Context context;
|
||||||
protected final HttpClient httpClient;
|
protected final HttpClient httpClient;
|
||||||
protected final ServerInfo serverInfo;
|
protected final ServerInfo serverInfo;
|
||||||
|
|
||||||
@ -67,11 +69,12 @@ public class DavResourceFinder {
|
|||||||
taskLists = new HashMap<>();
|
taskLists = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
public DavResourceFinder(Context context, ServerInfo serverInfo) {
|
public DavResourceFinder(Logger log, Context context, ServerInfo serverInfo) {
|
||||||
|
this.log = log;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.serverInfo = serverInfo;
|
this.serverInfo = serverInfo;
|
||||||
|
|
||||||
httpClient = new HttpClient(context, serverInfo.getUserName(), serverInfo.getPassword(), serverInfo.authPreemptive);
|
httpClient = new HttpClient(log, context, serverInfo.getUserName(), serverInfo.getPassword(), serverInfo.authPreemptive);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void findResources() throws URISyntaxException, IOException, HttpException, DavException {
|
public void findResources() throws URISyntaxException, IOException, HttpException, DavException {
|
||||||
@ -101,7 +104,7 @@ public class DavResourceFinder {
|
|||||||
2. user-given URL has a calendar-home-set property (i.e. is a principal URL)
|
2. user-given URL has a calendar-home-set property (i.e. is a principal URL)
|
||||||
*/
|
*/
|
||||||
Constants.log.info("Check whether user-given URL is a calendar collection and/or contains <calendar-home-set> and/or has <current-user-principal>");
|
Constants.log.info("Check whether user-given URL is a calendar collection and/or contains <calendar-home-set> and/or has <current-user-principal>");
|
||||||
DavResource davBase = new DavResource(httpClient, userURL);
|
DavResource davBase = new DavResource(log, httpClient, userURL);
|
||||||
try {
|
try {
|
||||||
if (service == Service.CALDAV) {
|
if (service == Service.CALDAV) {
|
||||||
davBase.propfind(0,
|
davBase.propfind(0,
|
||||||
@ -158,18 +161,23 @@ public class DavResourceFinder {
|
|||||||
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal)davBase.properties.get(CurrentUserPrincipal.NAME);
|
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal)davBase.properties.get(CurrentUserPrincipal.NAME);
|
||||||
if (currentUserPrincipal != null && currentUserPrincipal.href != null)
|
if (currentUserPrincipal != null && currentUserPrincipal.href != null)
|
||||||
principalUrl = davBase.location.resolve(currentUserPrincipal.href);
|
principalUrl = davBase.location.resolve(currentUserPrincipal.href);
|
||||||
|
|
||||||
if (principalUrl == null) {
|
|
||||||
Constants.log.info("User-given URL doesn't contain <current-user-principal>, trying /.well-known/" + service.name);
|
|
||||||
principalUrl = getCurrentUserPrincipal(userURL.resolve("/.well-known/" + service.name));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch(IOException|HttpException|DavException e) {
|
} catch(IOException|HttpException|DavException e) {
|
||||||
Constants.log.debug("Couldn't find <current-user-principal>", e);
|
Constants.log.debug("Couldn't find <current-user-principal> at user-given URL", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// try service discovery with "domain" = user-given host name
|
if (principalUrl == null)
|
||||||
domain = baseURI.getHost();
|
try {
|
||||||
|
Constants.log.info("User-given URL doesn't contain <current-user-principal>, trying /.well-known/" + service.name);
|
||||||
|
principalUrl = getCurrentUserPrincipal(userURL.resolve("/.well-known/" + service.name));
|
||||||
|
} catch(IOException|HttpException e) {
|
||||||
|
Constants.log.debug("Couldn't determine <current-user-principal> from well-known " + service + " path", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (principalUrl == null)
|
||||||
|
// still no principal URL, try service discovery with "domain" = user-given host name
|
||||||
|
domain = baseURI.getHost();
|
||||||
|
|
||||||
} else if ("mailto".equals(baseURI.getScheme())) {
|
} else if ("mailto".equals(baseURI.getScheme())) {
|
||||||
String mailbox = baseURI.getSchemeSpecificPart();
|
String mailbox = baseURI.getSchemeSpecificPart();
|
||||||
|
|
||||||
@ -190,7 +198,7 @@ public class DavResourceFinder {
|
|||||||
if (principalUrl != null) {
|
if (principalUrl != null) {
|
||||||
Constants.log.info("Principal URL=" + principalUrl + ", getting <calendar-home-set>");
|
Constants.log.info("Principal URL=" + principalUrl + ", getting <calendar-home-set>");
|
||||||
try {
|
try {
|
||||||
DavResource principal = new DavResource(httpClient, principalUrl);
|
DavResource principal = new DavResource(log, httpClient, principalUrl);
|
||||||
|
|
||||||
if (service == Service.CALDAV) {
|
if (service == Service.CALDAV) {
|
||||||
principal.propfind(0, CalendarHomeSet.NAME);
|
principal.propfind(0, CalendarHomeSet.NAME);
|
||||||
@ -226,7 +234,7 @@ public class DavResourceFinder {
|
|||||||
if (service == Service.CALDAV)
|
if (service == Service.CALDAV)
|
||||||
try {
|
try {
|
||||||
Constants.log.info("Listing calendar collections in home set " + url);
|
Constants.log.info("Listing calendar collections in home set " + url);
|
||||||
DavResource homeSet = new DavResource(httpClient, url);
|
DavResource homeSet = new DavResource(log, httpClient, url);
|
||||||
homeSet.propfind(1, SupportedCalendarComponentSet.NAME, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME,
|
homeSet.propfind(1, SupportedCalendarComponentSet.NAME, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME,
|
||||||
CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME);
|
CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME);
|
||||||
|
|
||||||
@ -242,7 +250,7 @@ public class DavResourceFinder {
|
|||||||
else if (service == Service.CARDDAV)
|
else if (service == Service.CARDDAV)
|
||||||
try {
|
try {
|
||||||
Constants.log.info("Listing address books in home set " + url);
|
Constants.log.info("Listing address books in home set " + url);
|
||||||
DavResource homeSet = new DavResource(httpClient, url);
|
DavResource homeSet = new DavResource(log, httpClient, url);
|
||||||
homeSet.propfind(1, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME, AddressbookDescription.NAME);
|
homeSet.propfind(1, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME, AddressbookDescription.NAME);
|
||||||
|
|
||||||
// home set should not be an address book, but some servers have only one address book and it's the home set
|
// home set should not be an address book, but some servers have only one address book and it's the home set
|
||||||
@ -361,45 +369,40 @@ public class DavResourceFinder {
|
|||||||
* @param service service to discover (CALDAV or CARDDAV)
|
* @param service service to discover (CALDAV or CARDDAV)
|
||||||
* @return principal URL, or null if none found
|
* @return principal URL, or null if none found
|
||||||
*/
|
*/
|
||||||
protected HttpUrl discoverPrincipalUrl(String domain, Service service) {
|
protected HttpUrl discoverPrincipalUrl(String domain, Service service) throws IOException, HttpException, DavException {
|
||||||
String scheme = null;
|
String scheme = null;
|
||||||
String fqdn = null;
|
String fqdn = null;
|
||||||
Integer port = null;
|
Integer port = null;
|
||||||
List<String> paths = new LinkedList<>(); // there may be multiple paths to try
|
List<String> paths = new LinkedList<>(); // there may be multiple paths to try
|
||||||
|
|
||||||
try {
|
final String query = "_" + service.name + "s._tcp." + domain;
|
||||||
final String query = "_" + service.name + "s._tcp." + domain;
|
Constants.log.debug("Looking up SRV records for " + query);
|
||||||
Constants.log.debug("Looking up SRV records for " + query);
|
Record[] records = new Lookup(query, Type.SRV).run();
|
||||||
Record[] records = new Lookup(query, Type.SRV).run();
|
if (records != null && records.length >= 1) {
|
||||||
|
// choose SRV record to use (query may return multiple SRV records)
|
||||||
|
SRVRecord srv = selectSRVRecord(records);
|
||||||
|
|
||||||
|
scheme = "https";
|
||||||
|
fqdn = srv.getTarget().toString(true);
|
||||||
|
port = srv.getPort();
|
||||||
|
Constants.log.info("Found " + service + " service: fqdn=" + fqdn + ", port=" + port);
|
||||||
|
|
||||||
|
// look for TXT record too (for initial context path)
|
||||||
|
records = new Lookup(domain, Type.TXT).run();
|
||||||
if (records != null && records.length >= 1) {
|
if (records != null && records.length >= 1) {
|
||||||
// choose SRV record to use (query may return multiple SRV records)
|
TXTRecord txt = (TXTRecord)records[0];
|
||||||
SRVRecord srv = selectSRVRecord(records);
|
for (String segment : (String[])txt.getStrings().toArray(new String[0]))
|
||||||
|
if (segment.startsWith("path=")) {
|
||||||
scheme = "https";
|
paths.add(segment.substring(5));
|
||||||
fqdn = srv.getTarget().toString(true);
|
Constants.log.info("Found TXT record; initial context path=" + paths);
|
||||||
port = srv.getPort();
|
break;
|
||||||
Constants.log.info("Found " + service + " service: fqdn=" + fqdn + ", port=" + port);
|
}
|
||||||
|
|
||||||
// look for TXT record too (for initial context path)
|
|
||||||
records = new Lookup(domain, Type.TXT).run();
|
|
||||||
if (records != null && records.length >= 1) {
|
|
||||||
TXTRecord txt = (TXTRecord)records[0];
|
|
||||||
for (String segment : (String[])txt.getStrings().toArray(new String[0]))
|
|
||||||
if (segment.startsWith("path=")) {
|
|
||||||
paths.add(segment.substring(5));
|
|
||||||
Constants.log.info("Found TXT record; initial context path=" + paths);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's TXT record if it it's wrong, try well-known
|
|
||||||
paths.add("/.well-known/" + service.name);
|
|
||||||
// if this fails, too, try "/"
|
|
||||||
paths.add("/");
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
Constants.log.debug("SRV/TXT record discovery failed", e);
|
// if there's TXT record if it it's wrong, try well-known
|
||||||
return null;
|
paths.add("/.well-known/" + service.name);
|
||||||
|
// if this fails, too, try "/"
|
||||||
|
paths.add("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String path : paths) {
|
for (String path : paths) {
|
||||||
@ -424,20 +427,16 @@ public class DavResourceFinder {
|
|||||||
* @param url URL to query with PROPFIND (Depth: 0)
|
* @param url URL to query with PROPFIND (Depth: 0)
|
||||||
* @return current-user-principal URL, or null if none
|
* @return current-user-principal URL, or null if none
|
||||||
*/
|
*/
|
||||||
protected HttpUrl getCurrentUserPrincipal(HttpUrl url) {
|
protected HttpUrl getCurrentUserPrincipal(HttpUrl url) throws IOException, HttpException, DavException {
|
||||||
try {
|
DavResource dav = new DavResource(log, httpClient, url);
|
||||||
DavResource dav = new DavResource(httpClient, url);
|
dav.propfind(0, CurrentUserPrincipal.NAME);
|
||||||
dav.propfind(0, CurrentUserPrincipal.NAME);
|
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME);
|
||||||
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME);
|
if (currentUserPrincipal != null && currentUserPrincipal.href != null) {
|
||||||
if (currentUserPrincipal != null && currentUserPrincipal.href != null) {
|
HttpUrl principal = url.resolve(currentUserPrincipal.href);
|
||||||
HttpUrl principal = url.resolve(currentUserPrincipal.href);
|
if (principal != null) {
|
||||||
if (principal != null) {
|
Constants.log.info("Found current-user-principal: " + principal);
|
||||||
Constants.log.info("Found current-user-principal: " + principal);
|
return principal;
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch(IOException|HttpException|DavException e) {
|
|
||||||
Constants.log.debug("PROPFIND for current-user-principal on " + url + " failed", e);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,8 @@ public class AccountSettings {
|
|||||||
|
|
||||||
KEY_USERNAME = "user_name",
|
KEY_USERNAME = "user_name",
|
||||||
KEY_AUTH_PREEMPTIVE = "auth_preemptive",
|
KEY_AUTH_PREEMPTIVE = "auth_preemptive",
|
||||||
|
KEY_LOG_TO_EXTERNAL_FILE = "log_external_file",
|
||||||
|
KEY_LOG_VERBOSE = "log_verbose",
|
||||||
KEY_LAST_ANDROID_VERSION = "last_android_version";
|
KEY_LAST_ANDROID_VERSION = "last_android_version";
|
||||||
|
|
||||||
public final static long SYNC_INTERVAL_MANUALLY = -1;
|
public final static long SYNC_INTERVAL_MANUALLY = -1;
|
||||||
@ -84,9 +86,8 @@ public class AccountSettings {
|
|||||||
showNotification(Constants.NOTIFICATION_ANDROID_VERSION_UPDATED,
|
showNotification(Constants.NOTIFICATION_ANDROID_VERSION_UPDATED,
|
||||||
context.getString(R.string.settings_android_update_title),
|
context.getString(R.string.settings_android_update_title),
|
||||||
context.getString(R.string.settings_android_update_description));
|
context.getString(R.string.settings_android_update_description));
|
||||||
|
|
||||||
accountManager.setUserData(account, KEY_LAST_ANDROID_VERSION, String.valueOf(Build.VERSION.SDK_INT));
|
|
||||||
}
|
}
|
||||||
|
accountManager.setUserData(account, KEY_LAST_ANDROID_VERSION, String.valueOf(Build.VERSION.SDK_INT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,18 +120,23 @@ public class AccountSettings {
|
|||||||
|
|
||||||
// authentication settings
|
// authentication settings
|
||||||
|
|
||||||
public String getUserName() {
|
public String username() { return accountManager.getUserData(account, KEY_USERNAME); }
|
||||||
return accountManager.getUserData(account, KEY_USERNAME);
|
public void username(String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); }
|
||||||
}
|
|
||||||
public void setUserName(String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); }
|
|
||||||
|
|
||||||
public String getPassword() {
|
public String password() { return accountManager.getPassword(account); }
|
||||||
return accountManager.getPassword(account);
|
public void password(String password) { accountManager.setPassword(account, password); }
|
||||||
}
|
|
||||||
public void setPassword(String password) { accountManager.setPassword(account, password); }
|
|
||||||
|
|
||||||
public boolean getPreemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); }
|
public boolean preemptiveAuth() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); }
|
||||||
public void setPreemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); }
|
public void preemptiveAuth(boolean preemptive) { accountManager.setUserData(account, KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive)); }
|
||||||
|
|
||||||
|
|
||||||
|
// logging settings
|
||||||
|
|
||||||
|
public boolean logToExternalFile() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_LOG_TO_EXTERNAL_FILE)); }
|
||||||
|
public void logToExternalFile(boolean newValue) { accountManager.setUserData(account, KEY_LOG_TO_EXTERNAL_FILE, Boolean.toString(newValue)); }
|
||||||
|
|
||||||
|
public boolean logVerbose() { return Boolean.parseBoolean(accountManager.getUserData(account, KEY_LOG_VERBOSE)); }
|
||||||
|
public void logVerbose(boolean newValue) { accountManager.setUserData(account, KEY_LOG_VERBOSE, Boolean.toString(newValue)); }
|
||||||
|
|
||||||
|
|
||||||
// sync. settings
|
// sync. settings
|
||||||
@ -229,7 +235,7 @@ public class AccountSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void update_1_2() throws ContactsStorageException {
|
private void update_1_2() throws ContactsStorageException {
|
||||||
/* - KEY_ADDRESSBOOK_URL ("addressbook_url"),,
|
/* - KEY_ADDRESSBOOK_URL ("addressbook_url"),
|
||||||
- KEY_ADDRESSBOOK_CTAG ("addressbook_ctag"),
|
- KEY_ADDRESSBOOK_CTAG ("addressbook_ctag"),
|
||||||
- KEY_ADDRESSBOOK_VCARD_VERSION ("addressbook_vcard_version") are not used anymore (now stored in ContactsContract.SyncState)
|
- KEY_ADDRESSBOOK_VCARD_VERSION ("addressbook_vcard_version") are not used anymore (now stored in ContactsContract.SyncState)
|
||||||
- KEY_LAST_ANDROID_VERSION ("last_android_version") has been added
|
- KEY_LAST_ANDROID_VERSION ("last_android_version") has been added
|
||||||
@ -237,22 +243,25 @@ public class AccountSettings {
|
|||||||
|
|
||||||
// move previous address book info to ContactsContract.SyncState
|
// move previous address book info to ContactsContract.SyncState
|
||||||
@Cleanup("release") ContentProviderClient provider = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
|
@Cleanup("release") ContentProviderClient provider = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
|
||||||
if (provider != null) {
|
if (provider == null)
|
||||||
LocalAddressBook addr = new LocalAddressBook(account, provider);
|
throw new ContactsStorageException("Couldn't access Contacts provider");
|
||||||
|
|
||||||
String url = accountManager.getUserData(account, "addressbook_url");
|
LocalAddressBook addr = new LocalAddressBook(account, provider);
|
||||||
if (!TextUtils.isEmpty(url))
|
|
||||||
addr.setURL(url);
|
|
||||||
accountManager.setUserData(account, "addressbook_url", null);
|
|
||||||
|
|
||||||
String cTag = accountManager.getUserData(account, "addressbook_ctag");
|
// until now, ContactsContract.Settings.UNGROUPED_VISIBLE was not set explicitly
|
||||||
if (!TextUtils.isEmpty(cTag))
|
ContentValues values = new ContentValues();
|
||||||
addr.setCTag(cTag);
|
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1);
|
||||||
accountManager.setUserData(account, "addressbook_ctag", null);
|
addr.updateSettings(values);
|
||||||
}
|
|
||||||
|
|
||||||
// store current Android version
|
String url = accountManager.getUserData(account, "addressbook_url");
|
||||||
accountManager.setUserData(account, KEY_LAST_ANDROID_VERSION, String.valueOf(Build.VERSION.SDK_INT));
|
if (!TextUtils.isEmpty(url))
|
||||||
|
addr.setURL(url);
|
||||||
|
accountManager.setUserData(account, "addressbook_url", null);
|
||||||
|
|
||||||
|
String cTag = accountManager.getUserData(account, "addressbook_ctag");
|
||||||
|
if (!TextUtils.isEmpty(cTag))
|
||||||
|
addr.setCTag(cTag);
|
||||||
|
accountManager.setUserData(account, "addressbook_ctag", null);
|
||||||
|
|
||||||
accountManager.setUserData(account, KEY_SETTINGS_VERSION, "2");
|
accountManager.setUserData(account, KEY_SETTINGS_VERSION, "2");
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ public class CalendarSyncManager extends SyncManager {
|
|||||||
Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j
|
Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j
|
||||||
|
|
||||||
collectionURL = HttpUrl.parse(localCalendar().getName());
|
collectionURL = HttpUrl.parse(localCalendar().getName());
|
||||||
davCollection = new DavCalendar(httpClient, collectionURL);
|
davCollection = new DavCalendar(log, httpClient, collectionURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,7 +90,7 @@ public class CalendarSyncManager extends SyncManager {
|
|||||||
int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor;
|
int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor;
|
||||||
|
|
||||||
ContentValues values = new ContentValues(2);
|
ContentValues values = new ContentValues(2);
|
||||||
Constants.log.info("Setting new calendar name \"" + displayName + "\" and color 0x" + Integer.toHexString(color));
|
log.info("Setting new calendar name \"" + displayName + "\" and color 0x" + Integer.toHexString(color));
|
||||||
values.put(Calendars.CALENDAR_DISPLAY_NAME, displayName);
|
values.put(Calendars.CALENDAR_DISPLAY_NAME, displayName);
|
||||||
values.put(Calendars.CALENDAR_COLOR, color);
|
values.put(Calendars.CALENDAR_COLOR, color);
|
||||||
localCalendar().update(values);
|
localCalendar().update(values);
|
||||||
@ -119,20 +119,20 @@ public class CalendarSyncManager extends SyncManager {
|
|||||||
remoteResources = new HashMap<>(davCollection.members.size());
|
remoteResources = new HashMap<>(davCollection.members.size());
|
||||||
for (DavResource vCard : davCollection.members) {
|
for (DavResource vCard : davCollection.members) {
|
||||||
String fileName = vCard.fileName();
|
String fileName = vCard.fileName();
|
||||||
Constants.log.debug("Found remote VEVENT: " + fileName);
|
log.debug("Found remote VEVENT: " + fileName);
|
||||||
remoteResources.put(fileName, vCard);
|
remoteResources.put(fileName, vCard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException {
|
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException {
|
||||||
Constants.log.info("Downloading " + toDownload.size() + " events (" + MAX_MULTIGET + " at once)");
|
log.info("Downloading " + toDownload.size() + " events (" + MAX_MULTIGET + " at once)");
|
||||||
|
|
||||||
// download new/updated iCalendars from server
|
// download new/updated iCalendars from server
|
||||||
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
return;
|
return;
|
||||||
Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
|
log.info("Downloading " + StringUtils.join(bunch, ", "));
|
||||||
|
|
||||||
if (bunch.length == 1) {
|
if (bunch.length == 1) {
|
||||||
// only one contact, use GET
|
// only one contact, use GET
|
||||||
@ -190,7 +190,7 @@ public class CalendarSyncManager extends SyncManager {
|
|||||||
try {
|
try {
|
||||||
events = Event.fromStream(stream, charset);
|
events = Event.fromStream(stream, charset);
|
||||||
} catch (InvalidCalendarException e) {
|
} catch (InvalidCalendarException e) {
|
||||||
Constants.log.error("Received invalid iCalendar, ignoring");
|
log.error("Received invalid iCalendar, ignoring");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,18 +200,18 @@ public class CalendarSyncManager extends SyncManager {
|
|||||||
// delete local event, if it exists
|
// delete local event, if it exists
|
||||||
LocalEvent localEvent = (LocalEvent)localResources.get(fileName);
|
LocalEvent localEvent = (LocalEvent)localResources.get(fileName);
|
||||||
if (localEvent != null) {
|
if (localEvent != null) {
|
||||||
Constants.log.info("Updating " + fileName + " in local calendar");
|
log.info("Updating " + fileName + " in local calendar");
|
||||||
localEvent.setETag(eTag);
|
localEvent.setETag(eTag);
|
||||||
localEvent.update(newData);
|
localEvent.update(newData);
|
||||||
syncResult.stats.numUpdates++;
|
syncResult.stats.numUpdates++;
|
||||||
} else {
|
} else {
|
||||||
Constants.log.info("Adding " + fileName + " to local calendar");
|
log.info("Adding " + fileName + " to local calendar");
|
||||||
localEvent = new LocalEvent(localCalendar(), newData, fileName, eTag);
|
localEvent = new LocalEvent(localCalendar(), newData, fileName, eTag);
|
||||||
localEvent.add();
|
localEvent.add();
|
||||||
syncResult.stats.numInserts++;
|
syncResult.stats.numInserts++;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
Constants.log.error("Received VCALENDAR with not exactly one VEVENT with UID, but without RECURRENCE-ID; ignoring " + fileName);
|
log.error("Received VCALENDAR with not exactly one VEVENT with UID, but without RECURRENCE-ID; ignoring " + fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
if (url == null)
|
if (url == null)
|
||||||
throw new ContactsStorageException("Couldn't get address book URL");
|
throw new ContactsStorageException("Couldn't get address book URL");
|
||||||
collectionURL = HttpUrl.parse(url);
|
collectionURL = HttpUrl.parse(url);
|
||||||
davCollection = new DavAddressBook(httpClient, collectionURL);
|
davCollection = new DavAddressBook(log, httpClient, collectionURL);
|
||||||
|
|
||||||
processChangedGroups();
|
processChangedGroups();
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
for (MediaType type : supportedAddressData.types)
|
for (MediaType type : supportedAddressData.types)
|
||||||
if ("text/vcard; version=4.0".equalsIgnoreCase(type.toString()))
|
if ("text/vcard; version=4.0".equalsIgnoreCase(type.toString()))
|
||||||
hasVCard4 = true;
|
hasVCard4 = true;
|
||||||
Constants.log.info("Server advertises VCard/4 support: " + hasVCard4);
|
log.info("Server advertises VCard/4 support: " + hasVCard4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -118,7 +118,7 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
davAddressBook().addressbookQuery();
|
davAddressBook().addressbookQuery();
|
||||||
} catch(HttpException e) {
|
} catch(HttpException e) {
|
||||||
if (e.status/100 == 4) {
|
if (e.status/100 == 4) {
|
||||||
Constants.log.warn("Server error on REPORT addressbook query, falling back to PROPFIND", e);
|
log.warn("Server error on REPORT addressbook query, falling back to PROPFIND", e);
|
||||||
davAddressBook().propfind(1, GetETag.NAME);
|
davAddressBook().propfind(1, GetETag.NAME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,14 +126,14 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
remoteResources = new HashMap<>(davCollection.members.size());
|
remoteResources = new HashMap<>(davCollection.members.size());
|
||||||
for (DavResource vCard : davCollection.members) {
|
for (DavResource vCard : davCollection.members) {
|
||||||
String fileName = vCard.fileName();
|
String fileName = vCard.fileName();
|
||||||
Constants.log.debug("Found remote VCard: " + fileName);
|
log.debug("Found remote VCard: " + fileName);
|
||||||
remoteResources.put(fileName, vCard);
|
remoteResources.put(fileName, vCard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException {
|
protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException {
|
||||||
Constants.log.info("Downloading " + toDownload.size() + " contacts (" + MAX_MULTIGET + " at once)");
|
log.info("Downloading " + toDownload.size() + " contacts (" + MAX_MULTIGET + " at once)");
|
||||||
|
|
||||||
// prepare downloader which may be used to download external resource like contact photos
|
// prepare downloader which may be used to download external resource like contact photos
|
||||||
Contact.Downloader downloader = new ResourceDownloader(httpClient, collectionURL);
|
Contact.Downloader downloader = new ResourceDownloader(httpClient, collectionURL);
|
||||||
@ -143,7 +143,7 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
|
log.info("Downloading " + StringUtils.join(bunch, ", "));
|
||||||
|
|
||||||
if (bunch.length == 1) {
|
if (bunch.length == 1) {
|
||||||
// only one contact, use GET
|
// only one contact, use GET
|
||||||
@ -202,7 +202,7 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
// groups with DELETED=1: remove group finally
|
// groups with DELETED=1: remove group finally
|
||||||
for (LocalGroup group : addressBook.getDeletedGroups()) {
|
for (LocalGroup group : addressBook.getDeletedGroups()) {
|
||||||
long groupId = group.getId();
|
long groupId = group.getId();
|
||||||
Constants.log.debug("Finally removing group #" + groupId);
|
log.debug("Finally removing group #" + groupId);
|
||||||
// remove group memberships, but not as sync adapter (should marks contacts as DIRTY)
|
// remove group memberships, but not as sync adapter (should marks contacts as DIRTY)
|
||||||
// NOTE: doesn't work that way because Contact Provider removes the group memberships even for DELETED groups
|
// NOTE: doesn't work that way because Contact Provider removes the group memberships even for DELETED groups
|
||||||
// addressBook.removeGroupMemberships(groupId, false);
|
// addressBook.removeGroupMemberships(groupId, false);
|
||||||
@ -212,7 +212,7 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
// groups with DIRTY=1: mark all memberships as dirty, then clean DIRTY flag of group
|
// groups with DIRTY=1: mark all memberships as dirty, then clean DIRTY flag of group
|
||||||
for (LocalGroup group : addressBook.getDirtyGroups()) {
|
for (LocalGroup group : addressBook.getDirtyGroups()) {
|
||||||
long groupId = group.getId();
|
long groupId = group.getId();
|
||||||
Constants.log.debug("Marking members of modified group #" + groupId + " as dirty");
|
log.debug("Marking members of modified group #" + groupId + " as dirty");
|
||||||
addressBook.markMembersDirty(groupId);
|
addressBook.markMembersDirty(groupId);
|
||||||
group.clearDirty();
|
group.clearDirty();
|
||||||
}
|
}
|
||||||
@ -226,32 +226,32 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
// update local contact, if it exists
|
// update local contact, if it exists
|
||||||
LocalContact localContact = (LocalContact)localResources.get(fileName);
|
LocalContact localContact = (LocalContact)localResources.get(fileName);
|
||||||
if (localContact != null) {
|
if (localContact != null) {
|
||||||
Constants.log.info("Updating " + fileName + " in local address book");
|
log.info("Updating " + fileName + " in local address book");
|
||||||
localContact.eTag = eTag;
|
localContact.eTag = eTag;
|
||||||
localContact.update(newData);
|
localContact.update(newData);
|
||||||
syncResult.stats.numUpdates++;
|
syncResult.stats.numUpdates++;
|
||||||
} else {
|
} else {
|
||||||
Constants.log.info("Adding " + fileName + " to local address book");
|
log.info("Adding " + fileName + " to local address book");
|
||||||
localContact = new LocalContact(localAddressBook(), newData, fileName, eTag);
|
localContact = new LocalContact(localAddressBook(), newData, fileName, eTag);
|
||||||
localContact.add();
|
localContact.add();
|
||||||
syncResult.stats.numInserts++;
|
syncResult.stats.numInserts++;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
Constants.log.error("Received VCard with not exactly one VCARD, ignoring " + fileName);
|
log.error("Received VCard with not exactly one VCARD, ignoring " + fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// downloader helper class
|
// downloader helper class
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
private static class ResourceDownloader implements Contact.Downloader {
|
private class ResourceDownloader implements Contact.Downloader {
|
||||||
final HttpClient httpClient;
|
final HttpClient httpClient;
|
||||||
final HttpUrl baseUrl;
|
final HttpUrl baseUrl;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] download(String url, String accepts) {
|
public byte[] download(String url, String accepts) {
|
||||||
HttpUrl httpUrl = HttpUrl.parse(url);
|
HttpUrl httpUrl = HttpUrl.parse(url);
|
||||||
HttpClient resourceClient = new HttpClient(httpClient, httpUrl.host());
|
HttpClient resourceClient = new HttpClient(log, httpClient, httpUrl.host());
|
||||||
try {
|
try {
|
||||||
Response response = resourceClient.newCall(new Request.Builder()
|
Response response = resourceClient.newCall(new Request.Builder()
|
||||||
.get()
|
.get()
|
||||||
@ -264,10 +264,10 @@ public class ContactsSyncManager extends SyncManager {
|
|||||||
if (response.isSuccessful() && stream != null) {
|
if (response.isSuccessful() && stream != null) {
|
||||||
return IOUtils.toByteArray(stream);
|
return IOUtils.toByteArray(stream);
|
||||||
} else
|
} else
|
||||||
Constants.log.error("Couldn't download external resource");
|
log.error("Couldn't download external resource");
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
Constants.log.error("Couldn't download external resource", e);
|
log.error("Couldn't download external resource", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
@ -22,6 +22,8 @@ import android.text.TextUtils;
|
|||||||
import com.squareup.okhttp.HttpUrl;
|
import com.squareup.okhttp.HttpUrl;
|
||||||
import com.squareup.okhttp.RequestBody;
|
import com.squareup.okhttp.RequestBody;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -42,6 +44,7 @@ import at.bitfire.dav4android.property.GetETag;
|
|||||||
import at.bitfire.davdroid.Constants;
|
import at.bitfire.davdroid.Constants;
|
||||||
import at.bitfire.davdroid.HttpClient;
|
import at.bitfire.davdroid.HttpClient;
|
||||||
import at.bitfire.davdroid.R;
|
import at.bitfire.davdroid.R;
|
||||||
|
import at.bitfire.davdroid.log.ExternalFileLogger;
|
||||||
import at.bitfire.davdroid.resource.LocalCollection;
|
import at.bitfire.davdroid.resource.LocalCollection;
|
||||||
import at.bitfire.davdroid.resource.LocalResource;
|
import at.bitfire.davdroid.resource.LocalResource;
|
||||||
import at.bitfire.davdroid.ui.DebugInfoActivity;
|
import at.bitfire.davdroid.ui.DebugInfoActivity;
|
||||||
@ -75,6 +78,8 @@ abstract public class SyncManager {
|
|||||||
protected final AccountSettings settings;
|
protected final AccountSettings settings;
|
||||||
protected LocalCollection localCollection;
|
protected LocalCollection localCollection;
|
||||||
|
|
||||||
|
protected Logger log;
|
||||||
|
|
||||||
protected final HttpClient httpClient;
|
protected final HttpClient httpClient;
|
||||||
protected HttpUrl collectionURL;
|
protected HttpUrl collectionURL;
|
||||||
protected DavResource davCollection;
|
protected DavResource davCollection;
|
||||||
@ -101,9 +106,19 @@ abstract public class SyncManager {
|
|||||||
this.authority = authority;
|
this.authority = authority;
|
||||||
this.syncResult = syncResult;
|
this.syncResult = syncResult;
|
||||||
|
|
||||||
// get account settings and generate httpClient
|
// get account settings and log to file (if requested)
|
||||||
settings = new AccountSettings(context, account);
|
settings = new AccountSettings(context, account);
|
||||||
httpClient = new HttpClient(context, settings.getUserName(), settings.getPassword(), settings.getPreemptiveAuth());
|
try {
|
||||||
|
if (settings.logToExternalFile())
|
||||||
|
log = new ExternalFileLogger(context, "davdroid-SyncManager-" + account.name + "-" + authority + ".txt", settings.logVerbose());
|
||||||
|
} catch(IOException e) {
|
||||||
|
log.error("Couldn't log to external file", e);
|
||||||
|
}
|
||||||
|
if (log == null)
|
||||||
|
log = Constants.log;
|
||||||
|
|
||||||
|
// create HttpClient with given logger
|
||||||
|
httpClient = new HttpClient(log, context, settings.username(), settings.password(), settings.preemptiveAuth());
|
||||||
|
|
||||||
// dismiss previous error notifications
|
// dismiss previous error notifications
|
||||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
@ -115,60 +130,60 @@ abstract public class SyncManager {
|
|||||||
public void performSync() {
|
public void performSync() {
|
||||||
int syncPhase = SYNC_PHASE_PREPARE;
|
int syncPhase = SYNC_PHASE_PREPARE;
|
||||||
try {
|
try {
|
||||||
Constants.log.info("Preparing synchronization");
|
log.info("Preparing synchronization");
|
||||||
prepare();
|
prepare();
|
||||||
|
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
return;
|
return;
|
||||||
syncPhase = SYNC_PHASE_QUERY_CAPABILITIES;
|
syncPhase = SYNC_PHASE_QUERY_CAPABILITIES;
|
||||||
Constants.log.info("Querying capabilities");
|
log.info("Querying capabilities");
|
||||||
queryCapabilities();
|
queryCapabilities();
|
||||||
|
|
||||||
syncPhase = SYNC_PHASE_PROCESS_LOCALLY_DELETED;
|
syncPhase = SYNC_PHASE_PROCESS_LOCALLY_DELETED;
|
||||||
Constants.log.info("Processing locally deleted entries");
|
log.info("Processing locally deleted entries");
|
||||||
processLocallyDeleted();
|
processLocallyDeleted();
|
||||||
|
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
return;
|
return;
|
||||||
syncPhase = SYNC_PHASE_PREPARE_DIRTY;
|
syncPhase = SYNC_PHASE_PREPARE_DIRTY;
|
||||||
Constants.log.info("Locally preparing dirty entries");
|
log.info("Locally preparing dirty entries");
|
||||||
prepareDirty();
|
prepareDirty();
|
||||||
|
|
||||||
syncPhase = SYNC_PHASE_UPLOAD_DIRTY;
|
syncPhase = SYNC_PHASE_UPLOAD_DIRTY;
|
||||||
Constants.log.info("Uploading dirty entries");
|
log.info("Uploading dirty entries");
|
||||||
uploadDirty();
|
uploadDirty();
|
||||||
|
|
||||||
syncPhase = SYNC_PHASE_CHECK_SYNC_STATE;
|
syncPhase = SYNC_PHASE_CHECK_SYNC_STATE;
|
||||||
Constants.log.info("Checking sync state");
|
log.info("Checking sync state");
|
||||||
if (checkSyncState()) {
|
if (checkSyncState()) {
|
||||||
syncPhase = SYNC_PHASE_LIST_LOCAL;
|
syncPhase = SYNC_PHASE_LIST_LOCAL;
|
||||||
Constants.log.info("Listing local entries");
|
log.info("Listing local entries");
|
||||||
listLocal();
|
listLocal();
|
||||||
|
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
return;
|
return;
|
||||||
syncPhase = SYNC_PHASE_LIST_REMOTE;
|
syncPhase = SYNC_PHASE_LIST_REMOTE;
|
||||||
Constants.log.info("Listing remote entries");
|
log.info("Listing remote entries");
|
||||||
listRemote();
|
listRemote();
|
||||||
|
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
return;
|
return;
|
||||||
syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE;
|
syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE;
|
||||||
Constants.log.info("Comparing local/remote entries");
|
log.info("Comparing local/remote entries");
|
||||||
compareLocalRemote();
|
compareLocalRemote();
|
||||||
|
|
||||||
syncPhase = SYNC_PHASE_DOWNLOAD_REMOTE;
|
syncPhase = SYNC_PHASE_DOWNLOAD_REMOTE;
|
||||||
Constants.log.info("Downloading remote entries");
|
log.info("Downloading remote entries");
|
||||||
downloadRemote();
|
downloadRemote();
|
||||||
|
|
||||||
syncPhase = SYNC_PHASE_SAVE_SYNC_STATE;
|
syncPhase = SYNC_PHASE_SAVE_SYNC_STATE;
|
||||||
Constants.log.info("Saving sync state");
|
log.info("Saving sync state");
|
||||||
saveSyncState();
|
saveSyncState();
|
||||||
} else
|
} else
|
||||||
Constants.log.info("Remote collection didn't change, skipping remote sync");
|
log.info("Remote collection didn't change, skipping remote sync");
|
||||||
|
|
||||||
} catch (IOException|ServiceUnavailableException e) {
|
} catch (IOException|ServiceUnavailableException e) {
|
||||||
Constants.log.error("I/O exception during sync, trying again later", e);
|
log.error("I/O exception during sync, trying again later", e);
|
||||||
syncResult.stats.numIoExceptions++;
|
syncResult.stats.numIoExceptions++;
|
||||||
|
|
||||||
if (e instanceof ServiceUnavailableException) {
|
if (e instanceof ServiceUnavailableException) {
|
||||||
@ -183,19 +198,19 @@ abstract public class SyncManager {
|
|||||||
final int messageString;
|
final int messageString;
|
||||||
|
|
||||||
if (e instanceof UnauthorizedException) {
|
if (e instanceof UnauthorizedException) {
|
||||||
Constants.log.error("Not authorized anymore", e);
|
log.error("Not authorized anymore", e);
|
||||||
messageString = R.string.sync_error_unauthorized;
|
messageString = R.string.sync_error_unauthorized;
|
||||||
syncResult.stats.numAuthExceptions++;
|
syncResult.stats.numAuthExceptions++;
|
||||||
} else if (e instanceof HttpException || e instanceof DavException) {
|
} else if (e instanceof HttpException || e instanceof DavException) {
|
||||||
Constants.log.error("HTTP/DAV Exception during sync", e);
|
log.error("HTTP/DAV Exception during sync", e);
|
||||||
messageString = R.string.sync_error_http_dav;
|
messageString = R.string.sync_error_http_dav;
|
||||||
syncResult.stats.numParseExceptions++;
|
syncResult.stats.numParseExceptions++;
|
||||||
} else if (e instanceof CalendarStorageException || e instanceof ContactsStorageException) {
|
} else if (e instanceof CalendarStorageException || e instanceof ContactsStorageException) {
|
||||||
Constants.log.error("Couldn't access local storage", e);
|
log.error("Couldn't access local storage", e);
|
||||||
messageString = R.string.sync_error_local_storage;
|
messageString = R.string.sync_error_local_storage;
|
||||||
syncResult.databaseError = true;
|
syncResult.databaseError = true;
|
||||||
} else {
|
} else {
|
||||||
Constants.log.error("Unknown sync error", e);
|
log.error("Unknown sync error", e);
|
||||||
messageString = R.string.sync_error;
|
messageString = R.string.sync_error;
|
||||||
syncResult.stats.numParseExceptions++;
|
syncResult.stats.numParseExceptions++;
|
||||||
}
|
}
|
||||||
@ -237,6 +252,13 @@ abstract public class SyncManager {
|
|||||||
notification = builder.getNotification();
|
notification = builder.getNotification();
|
||||||
}
|
}
|
||||||
notificationManager.notify(account.name, notificationId, notification);
|
notificationManager.notify(account.name, notificationId, notification);
|
||||||
|
} finally {
|
||||||
|
if (log instanceof ExternalFileLogger)
|
||||||
|
try {
|
||||||
|
((ExternalFileLogger)log).close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Constants.log.error("Couldn't close external log file", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,15 +281,15 @@ abstract public class SyncManager {
|
|||||||
|
|
||||||
final String fileName = local.getFileName();
|
final String fileName = local.getFileName();
|
||||||
if (!TextUtils.isEmpty(fileName)) {
|
if (!TextUtils.isEmpty(fileName)) {
|
||||||
Constants.log.info(fileName + " has been deleted locally -> deleting from server");
|
log.info(fileName + " has been deleted locally -> deleting from server");
|
||||||
try {
|
try {
|
||||||
new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build())
|
new DavResource(log, httpClient, collectionURL.newBuilder().addPathSegment(fileName).build())
|
||||||
.delete(local.getETag());
|
.delete(local.getETag());
|
||||||
} catch (IOException|HttpException e) {
|
} catch (IOException|HttpException e) {
|
||||||
Constants.log.warn("Couldn't delete " + fileName + " from server; ignoring (may be downloaded again)");
|
log.warn("Couldn't delete " + fileName + " from server; ignoring (may be downloaded again)");
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
Constants.log.info("Removing local record #" + local.getId() + " which has been deleted locally and was never uploaded");
|
log.info("Removing local record #" + local.getId() + " which has been deleted locally and was never uploaded");
|
||||||
local.delete();
|
local.delete();
|
||||||
syncResult.stats.numDeletes++;
|
syncResult.stats.numDeletes++;
|
||||||
}
|
}
|
||||||
@ -277,7 +299,7 @@ abstract public class SyncManager {
|
|||||||
// assign file names and UIDs to new contacts so that we can use the file name as an index
|
// assign file names and UIDs to new contacts so that we can use the file name as an index
|
||||||
for (LocalResource local : localCollection.getWithoutFileName()) {
|
for (LocalResource local : localCollection.getWithoutFileName()) {
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
Constants.log.info("Found local record #" + local.getId() + " without file name; assigning file name/UID based on " + uuid);
|
log.info("Found local record #" + local.getId() + " without file name; assigning file name/UID based on " + uuid);
|
||||||
local.updateFileNameAndUID(uuid);
|
local.updateFileNameAndUID(uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,7 +318,7 @@ abstract public class SyncManager {
|
|||||||
|
|
||||||
final String fileName = local.getFileName();
|
final String fileName = local.getFileName();
|
||||||
|
|
||||||
DavResource remote = new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build());
|
DavResource remote = new DavResource(log, httpClient, collectionURL.newBuilder().addPathSegment(fileName).build());
|
||||||
|
|
||||||
// generate entity to upload (VCard, iCal, whatever)
|
// generate entity to upload (VCard, iCal, whatever)
|
||||||
RequestBody body = prepareUpload(local);
|
RequestBody body = prepareUpload(local);
|
||||||
@ -304,25 +326,25 @@ abstract public class SyncManager {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
if (local.getETag() == null) {
|
if (local.getETag() == null) {
|
||||||
Constants.log.info("Uploading new record " + fileName);
|
log.info("Uploading new record " + fileName);
|
||||||
remote.put(body, null, true);
|
remote.put(body, null, true);
|
||||||
} else {
|
} else {
|
||||||
Constants.log.info("Uploading locally modified record " + fileName);
|
log.info("Uploading locally modified record " + fileName);
|
||||||
remote.put(body, local.getETag(), false);
|
remote.put(body, local.getETag(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (ConflictException|PreconditionFailedException e) {
|
} catch (ConflictException|PreconditionFailedException e) {
|
||||||
// we can't interact with the user to resolve the conflict, so we treat 409 like 412
|
// we can't interact with the user to resolve the conflict, so we treat 409 like 412
|
||||||
Constants.log.info("Resource has been modified on the server before upload, ignoring", e);
|
log.info("Resource has been modified on the server before upload, ignoring", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
String eTag = null;
|
String eTag = null;
|
||||||
GetETag newETag = (GetETag) remote.properties.get(GetETag.NAME);
|
GetETag newETag = (GetETag) remote.properties.get(GetETag.NAME);
|
||||||
if (newETag != null) {
|
if (newETag != null) {
|
||||||
eTag = newETag.eTag;
|
eTag = newETag.eTag;
|
||||||
Constants.log.debug("Received new ETag=" + eTag + " after uploading");
|
log.debug("Received new ETag=" + eTag + " after uploading");
|
||||||
} else
|
} else
|
||||||
Constants.log.debug("Didn't receive new ETag after uploading, setting to null");
|
log.debug("Didn't receive new ETag after uploading, setting to null");
|
||||||
|
|
||||||
local.clearDirty(eTag);
|
local.clearDirty(eTag);
|
||||||
}
|
}
|
||||||
@ -343,12 +365,12 @@ abstract public class SyncManager {
|
|||||||
|
|
||||||
String localCTag = null;
|
String localCTag = null;
|
||||||
if (extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL))
|
if (extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL))
|
||||||
Constants.log.info("Manual sync, ignoring CTag");
|
log.info("Manual sync, ignoring CTag");
|
||||||
else
|
else
|
||||||
localCTag = localCollection.getCTag();
|
localCTag = localCollection.getCTag();
|
||||||
|
|
||||||
if (remoteCTag != null && remoteCTag.equals(localCTag)) {
|
if (remoteCTag != null && remoteCTag.equals(localCTag)) {
|
||||||
Constants.log.info("Remote collection didn't change (CTag=" + remoteCTag + "), no need to query children");
|
log.info("Remote collection didn't change (CTag=" + remoteCTag + "), no need to query children");
|
||||||
return false;
|
return false;
|
||||||
} else
|
} else
|
||||||
return true;
|
return true;
|
||||||
@ -362,7 +384,7 @@ abstract public class SyncManager {
|
|||||||
LocalResource[] localList = localCollection.getAll();
|
LocalResource[] localList = localCollection.getAll();
|
||||||
localResources = new HashMap<>(localList.length);
|
localResources = new HashMap<>(localList.length);
|
||||||
for (LocalResource resource : localList) {
|
for (LocalResource resource : localList) {
|
||||||
Constants.log.debug("Found local resource: " + resource.getFileName());
|
log.debug("Found local resource: " + resource.getFileName());
|
||||||
localResources.put(resource.getFileName(), resource);
|
localResources.put(resource.getFileName(), resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -389,7 +411,7 @@ abstract public class SyncManager {
|
|||||||
for (String localName : localResources.keySet()) {
|
for (String localName : localResources.keySet()) {
|
||||||
DavResource remote = remoteResources.get(localName);
|
DavResource remote = remoteResources.get(localName);
|
||||||
if (remote == null) {
|
if (remote == null) {
|
||||||
Constants.log.info(localName + " is not on server anymore, deleting");
|
log.info(localName + " is not on server anymore, deleting");
|
||||||
localResources.get(localName).delete();
|
localResources.get(localName).delete();
|
||||||
syncResult.stats.numDeletes++;
|
syncResult.stats.numDeletes++;
|
||||||
} else {
|
} else {
|
||||||
@ -402,7 +424,7 @@ abstract public class SyncManager {
|
|||||||
if (remoteETag.equals(localETag))
|
if (remoteETag.equals(localETag))
|
||||||
syncResult.stats.numSkippedEntries++;
|
syncResult.stats.numSkippedEntries++;
|
||||||
else {
|
else {
|
||||||
Constants.log.info(localName + " has been changed on server (current ETag=" + remoteETag + ", last known ETag=" + localETag + ")");
|
log.info(localName + " has been changed on server (current ETag=" + remoteETag + ", last known ETag=" + localETag + ")");
|
||||||
toDownload.add(remote);
|
toDownload.add(remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,7 +435,7 @@ abstract public class SyncManager {
|
|||||||
|
|
||||||
// add all unseen (= remotely added) remote contacts
|
// add all unseen (= remotely added) remote contacts
|
||||||
if (!remoteResources.isEmpty()) {
|
if (!remoteResources.isEmpty()) {
|
||||||
Constants.log.info("New VCards have been found on the server: " + TextUtils.join(", ", remoteResources.keySet()));
|
log.info("New VCards have been found on the server: " + TextUtils.join(", ", remoteResources.keySet()));
|
||||||
toDownload.addAll(remoteResources.values());
|
toDownload.addAll(remoteResources.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -428,7 +450,7 @@ abstract public class SyncManager {
|
|||||||
/* Save sync state (CTag). It doesn't matter if it has changed during the sync process
|
/* Save sync state (CTag). It doesn't matter if it has changed during the sync process
|
||||||
(for instance, because another client has uploaded changes), because this will simply
|
(for instance, because another client has uploaded changes), because this will simply
|
||||||
cause all remote entries to be listed at the next sync. */
|
cause all remote entries to be listed at the next sync. */
|
||||||
Constants.log.info("Saving CTag=" + remoteCTag);
|
log.info("Saving CTag=" + remoteCTag);
|
||||||
localCollection.setCTag(remoteCTag);
|
localCollection.setCTag(remoteCTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ public class TasksSyncManager extends SyncManager {
|
|||||||
Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j
|
Thread.currentThread().setContextClassLoader(context.getClassLoader()); // required for ical4j
|
||||||
|
|
||||||
collectionURL = HttpUrl.parse(localTaskList().getSyncId());
|
collectionURL = HttpUrl.parse(localTaskList().getSyncId());
|
||||||
davCollection = new DavCalendar(httpClient, collectionURL);
|
davCollection = new DavCalendar(log, httpClient, collectionURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -95,7 +95,7 @@ public class TasksSyncManager extends SyncManager {
|
|||||||
int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor;
|
int color = (pColor != null && pColor.color != null) ? pColor.color : LocalCalendar.defaultColor;
|
||||||
|
|
||||||
ContentValues values = new ContentValues(2);
|
ContentValues values = new ContentValues(2);
|
||||||
Constants.log.info("Setting new task list name \"" + displayName + "\" and color 0x" + Integer.toHexString(color));
|
log.info("Setting new task list name \"" + displayName + "\" and color 0x" + Integer.toHexString(color));
|
||||||
values.put(TaskLists.LIST_NAME, displayName);
|
values.put(TaskLists.LIST_NAME, displayName);
|
||||||
values.put(TaskLists.LIST_COLOR, color);
|
values.put(TaskLists.LIST_COLOR, color);
|
||||||
localTaskList().update(values);
|
localTaskList().update(values);
|
||||||
@ -117,21 +117,21 @@ public class TasksSyncManager extends SyncManager {
|
|||||||
remoteResources = new HashMap<>(davCollection.members.size());
|
remoteResources = new HashMap<>(davCollection.members.size());
|
||||||
for (DavResource vCard : davCollection.members) {
|
for (DavResource vCard : davCollection.members) {
|
||||||
String fileName = vCard.fileName();
|
String fileName = vCard.fileName();
|
||||||
Constants.log.debug("Found remote VTODO: " + fileName);
|
log.debug("Found remote VTODO: " + fileName);
|
||||||
remoteResources.put(fileName, vCard);
|
remoteResources.put(fileName, vCard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException {
|
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException {
|
||||||
Constants.log.info("Downloading " + toDownload.size() + " tasks (" + MAX_MULTIGET + " at once)");
|
log.info("Downloading " + toDownload.size() + " tasks (" + MAX_MULTIGET + " at once)");
|
||||||
|
|
||||||
// download new/updated iCalendars from server
|
// download new/updated iCalendars from server
|
||||||
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Constants.log.info("Downloading " + StringUtils.join(bunch, ", "));
|
log.info("Downloading " + StringUtils.join(bunch, ", "));
|
||||||
|
|
||||||
if (bunch.length == 1) {
|
if (bunch.length == 1) {
|
||||||
// only one contact, use GET
|
// only one contact, use GET
|
||||||
@ -189,7 +189,7 @@ public class TasksSyncManager extends SyncManager {
|
|||||||
try {
|
try {
|
||||||
tasks = Task.fromStream(stream, charset);
|
tasks = Task.fromStream(stream, charset);
|
||||||
} catch (InvalidCalendarException e) {
|
} catch (InvalidCalendarException e) {
|
||||||
Constants.log.error("Received invalid iCalendar, ignoring", e);
|
log.error("Received invalid iCalendar, ignoring", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,18 +199,18 @@ public class TasksSyncManager extends SyncManager {
|
|||||||
// update local task, if it exists
|
// update local task, if it exists
|
||||||
LocalTask localTask = (LocalTask)localResources.get(fileName);
|
LocalTask localTask = (LocalTask)localResources.get(fileName);
|
||||||
if (localTask != null) {
|
if (localTask != null) {
|
||||||
Constants.log.info("Updating " + fileName + " in local tasklist");
|
log.info("Updating " + fileName + " in local tasklist");
|
||||||
localTask.setETag(eTag);
|
localTask.setETag(eTag);
|
||||||
localTask.update(newData);
|
localTask.update(newData);
|
||||||
syncResult.stats.numUpdates++;
|
syncResult.stats.numUpdates++;
|
||||||
} else {
|
} else {
|
||||||
Constants.log.info("Adding " + fileName + " to local task list");
|
log.info("Adding " + fileName + " to local task list");
|
||||||
localTask = new LocalTask(localTaskList(), newData, fileName, eTag);
|
localTask = new LocalTask(localTaskList(), newData, fileName, eTag);
|
||||||
localTask.add();
|
localTask.add();
|
||||||
syncResult.stats.numInserts++;
|
syncResult.stats.numInserts++;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
Constants.log.error("Received VCALENDAR with not exactly one VTODO; ignoring " + fileName);
|
log.error("Received VCALENDAR with not exactly one VTODO; ignoring " + fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ public class AccountActivity extends Activity {
|
|||||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
final FragmentManager fm = getFragmentManager();
|
final FragmentManager fm = getFragmentManager();
|
||||||
|
|
||||||
AccountFragment fragment = (AccountFragment)fm.findFragmentById(R.id.account_fragment);
|
AccountFragment fragment = (AccountFragment)fm.findFragmentById(R.id.account_fragment);
|
||||||
if (fragment == null) {
|
if (fragment == null) {
|
||||||
fragment = new AccountFragment();
|
fragment = new AccountFragment();
|
||||||
@ -36,8 +35,9 @@ public class AccountActivity extends Activity {
|
|||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
|
|
||||||
getFragmentManager().beginTransaction()
|
getFragmentManager().beginTransaction()
|
||||||
.add(R.id.account_fragment, fragment)
|
.add(R.id.account_fragment, fragment, SettingsActivity.TAG_ACCOUNT_SETTINGS)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,10 @@
|
|||||||
package at.bitfire.davdroid.ui.settings;
|
package at.bitfire.davdroid.ui.settings;
|
||||||
|
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.DialogFragment;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.CheckBoxPreference;
|
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
@ -22,7 +24,6 @@ import android.provider.ContactsContract;
|
|||||||
import at.bitfire.davdroid.R;
|
import at.bitfire.davdroid.R;
|
||||||
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
import at.bitfire.davdroid.syncadapter.AccountSettings;
|
||||||
import at.bitfire.ical4android.TaskProvider;
|
import at.bitfire.ical4android.TaskProvider;
|
||||||
import ezvcard.VCardVersion;
|
|
||||||
|
|
||||||
public class AccountFragment extends PreferenceFragment {
|
public class AccountFragment extends PreferenceFragment {
|
||||||
final static String ARG_ACCOUNT = "account";
|
final static String ARG_ACCOUNT = "account";
|
||||||
@ -36,44 +37,41 @@ public class AccountFragment extends PreferenceFragment {
|
|||||||
addPreferencesFromResource(R.xml.settings_account_prefs);
|
addPreferencesFromResource(R.xml.settings_account_prefs);
|
||||||
|
|
||||||
account = getArguments().getParcelable(ARG_ACCOUNT);
|
account = getArguments().getParcelable(ARG_ACCOUNT);
|
||||||
readFromAccount();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readFromAccount() {
|
public void refresh() {
|
||||||
final AccountSettings settings = new AccountSettings(getActivity(), account);
|
final AccountSettings settings = new AccountSettings(getActivity(), account);
|
||||||
|
|
||||||
// category: authentication
|
// category: authentication
|
||||||
final EditTextPreference prefUserName = (EditTextPreference)findPreference("username");
|
final EditTextPreference prefUserName = (EditTextPreference)findPreference("username");
|
||||||
prefUserName.setSummary(settings.getUserName());
|
prefUserName.setSummary(settings.username());
|
||||||
prefUserName.setText(settings.getUserName());
|
prefUserName.setText(settings.username());
|
||||||
prefUserName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
prefUserName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
settings.setUserName((String)newValue);
|
settings.username((String) newValue);
|
||||||
readFromAccount();
|
refresh(); return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final EditTextPreference prefPassword = (EditTextPreference)findPreference("password");
|
final EditTextPreference prefPassword = (EditTextPreference)findPreference("password");
|
||||||
prefPassword.setText(settings.getPassword());
|
prefPassword.setText(settings.password());
|
||||||
prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
settings.setPassword((String)newValue);
|
settings.password((String) newValue);
|
||||||
readFromAccount();
|
refresh(); return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final SwitchPreference prefPreemptive = (SwitchPreference)findPreference("preemptive");
|
final SwitchPreference prefPreemptive = (SwitchPreference)findPreference("preemptive");
|
||||||
prefPreemptive.setChecked(settings.getPreemptiveAuth());
|
prefPreemptive.setChecked(settings.preemptiveAuth());
|
||||||
prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
settings.setPreemptiveAuth((Boolean)newValue);
|
settings.preemptiveAuth((Boolean) newValue);
|
||||||
readFromAccount();
|
refresh(); return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,8 +88,7 @@ public class AccountFragment extends PreferenceFragment {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue));
|
settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue));
|
||||||
readFromAccount();
|
refresh(); return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -111,8 +108,7 @@ public class AccountFragment extends PreferenceFragment {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue));
|
settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue));
|
||||||
readFromAccount();
|
refresh(); return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -132,8 +128,7 @@ public class AccountFragment extends PreferenceFragment {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String) newValue));
|
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String) newValue));
|
||||||
readFromAccount();
|
refresh(); return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -141,5 +136,80 @@ public class AccountFragment extends PreferenceFragment {
|
|||||||
prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available);
|
prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// category: debug info
|
||||||
|
|
||||||
|
final SwitchPreference prefLogExternalFile = (SwitchPreference)findPreference("log_external_file");
|
||||||
|
prefLogExternalFile.setChecked(settings.logToExternalFile());
|
||||||
|
prefLogExternalFile.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
Boolean external = (Boolean)newValue;
|
||||||
|
if (external) {
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.add(LogExternalFileDialogFragment.newInstance(account), null)
|
||||||
|
.commit();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
settings.logToExternalFile(false);
|
||||||
|
refresh(); return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final SwitchPreference prefLogVerbose = (SwitchPreference)findPreference("log_verbose");
|
||||||
|
prefLogVerbose.setChecked(settings.logVerbose());
|
||||||
|
prefLogVerbose.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
settings.logVerbose((Boolean) newValue);
|
||||||
|
refresh(); return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class LogExternalFileDialogFragment extends DialogFragment {
|
||||||
|
private static final String
|
||||||
|
KEY_ACCOUNT = "account";
|
||||||
|
|
||||||
|
public static LogExternalFileDialogFragment newInstance(Account account) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putParcelable(KEY_ACCOUNT, account);
|
||||||
|
LogExternalFileDialogFragment fragment = new LogExternalFileDialogFragment();
|
||||||
|
fragment.setArguments(args);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AlertDialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
final AccountSettings settings = new AccountSettings(getActivity(), (Account)getArguments().getParcelable(KEY_ACCOUNT));
|
||||||
|
return new AlertDialog.Builder(getActivity())
|
||||||
|
.setTitle(R.string.settings_security_warning)
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setMessage(R.string.settings_log_to_external_file_confirmation)
|
||||||
|
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
settings.logToExternalFile(false);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
settings.logToExternalFile(true);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
AccountFragment fragment = (AccountFragment)getActivity().getFragmentManager().findFragmentByTag(SettingsActivity.TAG_ACCOUNT_SETTINGS);
|
||||||
|
if (fragment != null)
|
||||||
|
fragment.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import android.os.Bundle;
|
|||||||
import at.bitfire.davdroid.R;
|
import at.bitfire.davdroid.R;
|
||||||
|
|
||||||
public class SettingsActivity extends Activity {
|
public class SettingsActivity extends Activity {
|
||||||
private final static String KEY_SELECTED_ACCOUNT = "selected_account";
|
public final static String TAG_ACCOUNT_SETTINGS = "account_settings";
|
||||||
|
|
||||||
boolean tabletLayout;
|
boolean tabletLayout;
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ public class SettingsActivity extends Activity {
|
|||||||
|
|
||||||
getFragmentManager().beginTransaction()
|
getFragmentManager().beginTransaction()
|
||||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||||
.replace(R.id.right_pane, fragment)
|
.replace(R.id.right_pane, fragment, TAG_ACCOUNT_SETTINGS)
|
||||||
.commit();
|
.commit();
|
||||||
} else { // phone layout
|
} else { // phone layout
|
||||||
Intent intent = new Intent(getApplicationContext(), AccountActivity.class);
|
Intent intent = new Intent(getApplicationContext(), AccountActivity.class);
|
||||||
|
@ -110,7 +110,7 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DavResourceFinder finder = new DavResourceFinder(context, serverInfo);
|
DavResourceFinder finder = new DavResourceFinder(null, context, serverInfo);
|
||||||
finder.findResources();
|
finder.findResources();
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage()));
|
serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage()));
|
||||||
|
@ -154,10 +154,15 @@
|
|||||||
<item>Every 4 hours</item>
|
<item>Every 4 hours</item>
|
||||||
<item>Once a day</item>
|
<item>Once a day</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="settings_carddav">Address book</string>
|
<string name="settings_debug">Debugging</string>
|
||||||
<string name="settings_carddav_vcard4_support">VCard 4.0 support</string>
|
<string name="settings_security_warning">Potential security risk!</string>
|
||||||
<string name="settings_carddav_vcard4_supported">Contacts are sent in VCard 4.0 format</string>
|
<string name="settings_log_to_external_file">Log to external file</string>
|
||||||
<string name="settings_carddav_vcard4_not_supported">Contacts are sent in VCard 3.0 format</string>
|
<string name="settings_log_to_external_file_confirmation">External log files will contain private data and be accessible by other apps. Turn off external logging and delete the log files after use.</string>
|
||||||
|
<string name="settings_log_to_external_file_on">Logs are written to external files (if possible)</string>
|
||||||
|
<string name="settings_log_to_external_file_off">Logs are written to ADB</string>
|
||||||
|
<string name="settings_log_verbose">Verbose logging</string>
|
||||||
|
<string name="settings_log_verbose_on">Log synchronization information and network traffic</string>
|
||||||
|
<string name="settings_log_verbose_off">Log only synchronization information</string>
|
||||||
|
|
||||||
<string name="settings_android_update_title">Android version update</string>
|
<string name="settings_android_update_title">Android version update</string>
|
||||||
<string name="settings_android_update_description">Android version updates may have an impact on how DAVdroid works. If there are problems, please delete your DAVdroid accounts and add them again.</string>
|
<string name="settings_android_update_description">Android version updates may have an impact on how DAVdroid works. If there are problems, please delete your DAVdroid accounts and add them again.</string>
|
||||||
|
@ -59,10 +59,22 @@
|
|||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<!--
|
<PreferenceCategory android:title="@string/settings_debug">
|
||||||
<PreferenceCategory android:title="@string/settings_carddav">
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:key="log_external_file"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/settings_log_to_external_file"
|
||||||
|
android:summaryOn="@string/settings_log_to_external_file_on"
|
||||||
|
android:summaryOff="@string/settings_log_to_external_file_off" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:key="log_verbose"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/settings_log_verbose"
|
||||||
|
android:summaryOn="@string/settings_log_verbose_on"
|
||||||
|
android:summaryOff="@string/settings_log_verbose_off" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
-->
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit e7218aeb8ad8a96620208140bc7b86f63bf05fad
|
Subproject commit 47541c169b970a6991b0f2f6c589232009e258ad
|
Loading…
Reference in New Issue
Block a user