@ -1,4 +1,4 @@
/ *
/ *
* 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
@ -22,6 +22,8 @@ import android.text.TextUtils;
import com.squareup.okhttp.HttpUrl ;
import com.squareup.okhttp.RequestBody ;
import org.slf4j.Logger ;
import java.io.IOException ;
import java.util.Date ;
import java.util.HashMap ;
@ -42,6 +44,7 @@ import at.bitfire.dav4android.property.GetETag;
import at.bitfire.davdroid.Constants ;
import at.bitfire.davdroid.HttpClient ;
import at.bitfire.davdroid.R ;
import at.bitfire.davdroid.log.ExternalFileLogger ;
import at.bitfire.davdroid.resource.LocalCollection ;
import at.bitfire.davdroid.resource.LocalResource ;
import at.bitfire.davdroid.ui.DebugInfoActivity ;
@ -75,6 +78,8 @@ abstract public class SyncManager {
protected final AccountSettings settings ;
protected LocalCollection localCollection ;
protected Logger log ;
protected final HttpClient httpClient ;
protected HttpUrl collectionURL ;
protected DavResource davCollection ;
@ -101,9 +106,19 @@ abstract public class SyncManager {
this . authority = authority ;
this . syncResult = syncResult ;
// get account settings and generate httpClient
// get account settings and log to file (if requested)
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
notificationManager = ( NotificationManager ) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
@ -115,60 +130,60 @@ abstract public class SyncManager {
public void performSync ( ) {
int syncPhase = SYNC_PHASE_PREPARE ;
try {
Constants. log. info ( "Preparing synchronization" ) ;
log. info ( "Preparing synchronization" ) ;
prepare ( ) ;
if ( Thread . interrupted ( ) )
return ;
syncPhase = SYNC_PHASE_QUERY_CAPABILITIES ;
Constants. log. info ( "Querying capabilities" ) ;
log. info ( "Querying capabilities" ) ;
queryCapabilities ( ) ;
syncPhase = SYNC_PHASE_PROCESS_LOCALLY_DELETED ;
Constants. log. info ( "Processing locally deleted entries" ) ;
log. info ( "Processing locally deleted entries" ) ;
processLocallyDeleted ( ) ;
if ( Thread . interrupted ( ) )
return ;
syncPhase = SYNC_PHASE_PREPARE_DIRTY ;
Constants. log. info ( "Locally preparing dirty entries" ) ;
log. info ( "Locally preparing dirty entries" ) ;
prepareDirty ( ) ;
syncPhase = SYNC_PHASE_UPLOAD_DIRTY ;
Constants. log. info ( "Uploading dirty entries" ) ;
log. info ( "Uploading dirty entries" ) ;
uploadDirty ( ) ;
syncPhase = SYNC_PHASE_CHECK_SYNC_STATE ;
Constants. log. info ( "Checking sync state" ) ;
log. info ( "Checking sync state" ) ;
if ( checkSyncState ( ) ) {
syncPhase = SYNC_PHASE_LIST_LOCAL ;
Constants. log. info ( "Listing local entries" ) ;
log. info ( "Listing local entries" ) ;
listLocal ( ) ;
if ( Thread . interrupted ( ) )
return ;
syncPhase = SYNC_PHASE_LIST_REMOTE ;
Constants. log. info ( "Listing remote entries" ) ;
log. info ( "Listing remote entries" ) ;
listRemote ( ) ;
if ( Thread . interrupted ( ) )
return ;
syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE ;
Constants. log. info ( "Comparing local/remote entries" ) ;
log. info ( "Comparing local/remote entries" ) ;
compareLocalRemote ( ) ;
syncPhase = SYNC_PHASE_DOWNLOAD_REMOTE ;
Constants. log. info ( "Downloading remote entries" ) ;
log. info ( "Downloading remote entries" ) ;
downloadRemote ( ) ;
syncPhase = SYNC_PHASE_SAVE_SYNC_STATE ;
Constants. log. info ( "Saving sync state" ) ;
log. info ( "Saving sync state" ) ;
saveSyncState ( ) ;
} 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 ) {
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 + + ;
if ( e instanceof ServiceUnavailableException ) {
@ -183,19 +198,19 @@ abstract public class SyncManager {
final int messageString ;
if ( e instanceof UnauthorizedException ) {
Constants. log. error ( "Not authorized anymore" , e ) ;
log. error ( "Not authorized anymore" , e ) ;
messageString = R . string . sync_error_unauthorized ;
syncResult . stats . numAuthExceptions + + ;
} 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 ;
syncResult . stats . numParseExceptions + + ;
} 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 ;
syncResult . databaseError = true ;
} else {
Constants. log. error ( "Unknown sync error" , e ) ;
log. error ( "Unknown sync error" , e ) ;
messageString = R . string . sync_error ;
syncResult . stats . numParseExceptions + + ;
}
@ -237,6 +252,13 @@ abstract public class SyncManager {
notification = builder . getNotification ( ) ;
}
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 ( ) ;
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 {
new DavResource ( httpClient, collectionURL . newBuilder ( ) . addPathSegment ( fileName ) . build ( ) )
new DavResource ( log, httpClient, collectionURL . newBuilder ( ) . addPathSegment ( fileName ) . build ( ) )
. delete ( local . getETag ( ) ) ;
} 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
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 ( ) ;
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
for ( LocalResource local : localCollection . getWithoutFileName ( ) ) {
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 ) ;
}
}
@ -296,7 +318,7 @@ abstract public class SyncManager {
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)
RequestBody body = prepareUpload ( local ) ;
@ -304,25 +326,25 @@ abstract public class SyncManager {
try {
if ( local . getETag ( ) = = null ) {
Constants. log. info ( "Uploading new record " + fileName ) ;
log. info ( "Uploading new record " + fileName ) ;
remote . put ( body , null , true ) ;
} else {
Constants. log. info ( "Uploading locally modified record " + fileName ) ;
log. info ( "Uploading locally modified record " + fileName ) ;
remote . put ( body , local . getETag ( ) , false ) ;
}
} catch ( ConflictException | PreconditionFailedException e ) {
// 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 ;
GetETag newETag = ( GetETag ) remote . properties . get ( GetETag . NAME ) ;
if ( newETag ! = null ) {
eTag = newETag . eTag ;
Constants. log. debug ( "Received new ETag=" + eTag + " after uploading" ) ;
log. debug ( "Received new ETag=" + eTag + " after uploading" ) ;
} 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 ) ;
}
@ -343,12 +365,12 @@ abstract public class SyncManager {
String localCTag = null ;
if ( extras . containsKey ( ContentResolver . SYNC_EXTRAS_MANUAL ) )
Constants. log. info ( "Manual sync, ignoring CTag" ) ;
log. info ( "Manual sync, ignoring CTag" ) ;
else
localCTag = localCollection . getCTag ( ) ;
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 ;
} else
return true ;
@ -362,7 +384,7 @@ abstract public class SyncManager {
LocalResource [ ] localList = localCollection . getAll ( ) ;
localResources = new HashMap < > ( localList . length ) ;
for ( LocalResource resource : localList ) {
Constants. log. debug ( "Found local resource: " + resource . getFileName ( ) ) ;
log. debug ( "Found local resource: " + resource . getFileName ( ) ) ;
localResources . put ( resource . getFileName ( ) , resource ) ;
}
}
@ -389,7 +411,7 @@ abstract public class SyncManager {
for ( String localName : localResources . keySet ( ) ) {
DavResource remote = remoteResources . get ( localName ) ;
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 ( ) ;
syncResult . stats . numDeletes + + ;
} else {
@ -402,7 +424,7 @@ abstract public class SyncManager {
if ( remoteETag . equals ( localETag ) )
syncResult . stats . numSkippedEntries + + ;
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 ) ;
}
@ -413,7 +435,7 @@ abstract public class SyncManager {
// add all unseen (= remotely added) remote contacts
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 ( ) ) ;
}
}
@ -428,7 +450,7 @@ abstract public class SyncManager {
/ * 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
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 ) ;
}