public void synchronize(boolean manualSync, SyncResult syncResult)
      throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException {
    // PHASE 1: push local changes to server
    int deletedRemotely = pushDeleted(), addedRemotely = pushNew(), updatedRemotely = pushDirty();

    syncResult.stats.numEntries = deletedRemotely + addedRemotely + updatedRemotely;

    // PHASE 2A: check if there's a reason to do a sync with remote (= forced sync or remote CTag
    // changed)
    boolean fetchCollection = syncResult.stats.numEntries > 0;
    if (manualSync) {
      Log.i(TAG, "Synchronization forced");
      fetchCollection = true;
    }
    if (!fetchCollection) {
      String currentCTag = remote.getCTag(), lastCTag = local.getCTag();
      Log.d(TAG, "Last local CTag = " + lastCTag + "; current remote CTag = " + currentCTag);
      if (currentCTag == null || !currentCTag.equals(lastCTag)) fetchCollection = true;
    }

    if (!fetchCollection) {
      Log.i(TAG, "No local changes and CTags match, no need to sync");
      return;
    }

    // PHASE 2B: detect details of remote changes
    Log.i(TAG, "Fetching remote resource list");
    Set<Resource> remotelyAdded = new HashSet<Resource>(),
        remotelyUpdated = new HashSet<Resource>();

    Resource[] remoteResources = remote.getMemberETags();
    for (Resource remoteResource : remoteResources) {
      try {
        Resource localResource = local.findByRemoteName(remoteResource.getName(), false);
        if (localResource.getETag() == null
            || !localResource.getETag().equals(remoteResource.getETag()))
          remotelyUpdated.add(remoteResource);
      } catch (RecordNotFoundException e) {
        remotelyAdded.add(remoteResource);
      }
    }

    // PHASE 3: pull remote changes from server
    syncResult.stats.numInserts = pullNew(remotelyAdded.toArray(new Resource[0]));
    syncResult.stats.numUpdates = pullChanged(remotelyUpdated.toArray(new Resource[0]));
    syncResult.stats.numEntries += syncResult.stats.numInserts + syncResult.stats.numUpdates;

    Log.i(TAG, "Removing non-dirty resources that are not present remotely anymore");
    local.deleteAllExceptRemoteNames(remoteResources);
    local.commit();

    // update collection CTag
    Log.i(TAG, "Sync complete, fetching new CTag");
    local.setCTag(remote.getCTag());
  }