Fetch resources for uploading one-by-one to save memory

pull/2/head
rfc2822 11 years ago
parent 64068f7007
commit 385e7b5e13

@ -0,0 +1,25 @@
package at.bitfire.davdroid;
import at.bitfire.davdroid.resource.Resource;
public class ArrayUtils {
public static <T> Resource[][] partition(Resource[] bigArray, int max) {
int nItems = bigArray.length;
int nPartArrays = (nItems + max-1)/max;
Resource[][] partArrays = new Resource[nPartArrays][];
// nItems: number of remaining items
for (int i = 0; nItems > 0; i++) {
int n = (nItems < max) ? nItems : max;
partArrays[i] = new Resource[n];
System.arraycopy(bigArray, i*max, partArrays[i], 0, n);
nItems -= n;
}
return partArrays;
}
}

@ -8,10 +8,9 @@ import java.io.InputStream;
import android.util.Log;
public class LoggingInputStream extends FilterInputStream {
private static final int MAX_LENGTH = 10*1024; // don't log more than 10 kB of data
private static final int MAX_LENGTH = 1000; // don't log more than this amount of data
String tag;
InputStream proxy;
ByteArrayOutputStream log = new ByteArrayOutputStream(MAX_LENGTH);
int logSize = 0;
@ -21,7 +20,6 @@ public class LoggingInputStream extends FilterInputStream {
public LoggingInputStream(String tag, InputStream proxy) {
super(proxy);
this.tag = tag;
this.proxy = proxy;
}
@ -59,7 +57,7 @@ public class LoggingInputStream extends FilterInputStream {
@Override
public void close() throws IOException {
Log.d(tag, "Content: " + log.toString() + (overflow ? "…" : ""));
Log.d(tag, log.toString() + (overflow ? "…" : ""));
super.close();
}

@ -562,7 +562,7 @@ public class LocalCalendar extends LocalCollection<Event> {
if (alarm.getTrigger() != null && (duration = alarm.getTrigger().getDuration()) != null)
minutes = duration.getDays() * 24*60 + duration.getHours()*60 + duration.getMinutes();
Log.i(TAG, "Adding alarm " + minutes + " min before");
Log.d(TAG, "Adding alarm " + minutes + " min before");
return builder
.withValue(Reminders.METHOD, Reminders.METHOD_ALERT)

@ -10,12 +10,15 @@ package at.bitfire.davdroid.resource;
import java.util.ArrayList;
import java.util.LinkedList;
import org.apache.commons.lang.ArrayUtils;
import lombok.Cleanup;
import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentProviderOperation.Builder;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
@ -64,7 +67,7 @@ public abstract class LocalCollection<T extends Resource> {
// content provider (= database) querying
public Resource[] findDirty() throws LocalStorageException {
public long[] findDirty() throws LocalStorageException {
String where = entryColumnDirty() + "=1";
if (entryColumnParentID() != null)
where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId());
@ -72,23 +75,16 @@ public abstract class LocalCollection<T extends Resource> {
@Cleanup Cursor cursor = providerClient.query(entriesURI(),
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
where, null, null);
LinkedList<T> dirty = new LinkedList<T>();
while (cursor != null && cursor.moveToNext()) {
long id = cursor.getLong(0);
try {
T resource = findById(id, true);
dirty.add(resource);
} catch (RecordNotFoundException e) {
Log.w(TAG, "Couldn't load dirty resource: " + ContentUris.appendId(entriesURI().buildUpon(), id), e);
}
}
return dirty.toArray(new Resource[0]);
LinkedList<Long> dirty = new LinkedList<Long>();
while (cursor != null && cursor.moveToNext())
dirty.add(cursor.getLong(0));
return ArrayUtils.toPrimitive(dirty.toArray(new Long[0]), -1);
} catch(RemoteException ex) {
throw new LocalStorageException(ex);
}
}
public Resource[] findDeleted() throws LocalStorageException {
public long[] findDeleted() throws LocalStorageException {
String where = entryColumnDeleted() + "=1";
if (entryColumnParentID() != null)
where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId());
@ -96,23 +92,17 @@ public abstract class LocalCollection<T extends Resource> {
@Cleanup Cursor cursor = providerClient.query(entriesURI(),
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
where, null, null);
LinkedList<T> deleted = new LinkedList<T>();
while (cursor != null && cursor.moveToNext()) {
long id = cursor.getLong(0);
try {
T resource = findById(id, false);
deleted.add(resource);
} catch (RecordNotFoundException e) {
Log.w(TAG, "Couldn't load resource marked for deletion: " + ContentUris.appendId(entriesURI().buildUpon(), id), e);
}
}
return deleted.toArray(new Resource[0]);
LinkedList<Long> deleted = new LinkedList<Long>();
while (cursor != null && cursor.moveToNext())
deleted.add(cursor.getLong(0));
return ArrayUtils.toPrimitive(deleted.toArray(new Long[0]), -1);
} catch(RemoteException ex) {
throw new LocalStorageException(ex);
}
}
public Resource[] findNew() throws LocalStorageException {
public long[] findNew() throws LocalStorageException {
// new records are 1) dirty, and 2) don't have a remote file name yet
String where = entryColumnDirty() + "=1 AND " + entryColumnRemoteName() + " IS NULL";
if (entryColumnParentID() != null)
where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId());
@ -120,26 +110,22 @@ public abstract class LocalCollection<T extends Resource> {
@Cleanup Cursor cursor = providerClient.query(entriesURI(),
new String[] { entryColumnID() },
where, null, null);
LinkedList<T> fresh = new LinkedList<T>();
LinkedList<Long> fresh = new LinkedList<Long>();
while (cursor != null && cursor.moveToNext()) {
long id = cursor.getLong(0);
try {
T resource = findById(id, true);
resource.initialize();
// new record: set generated UID + remote file name in database
pendingOperations.add(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID()))
.withValue(entryColumnUID(), resource.getUid())
.withValue(entryColumnRemoteName(), resource.getName())
.build());
fresh.add(resource);
} catch (RecordNotFoundException e) {
Log.w(TAG, "Couldn't load fresh resource: " + ContentUris.appendId(entriesURI().buildUpon(), id), e);
}
// new record: generate UID + remote file name so that we can upload
T resource = findById(id, false);
resource.initialize();
// write generated UID + remote file name into database
ContentValues values = new ContentValues(2);
values.put(entryColumnUID(), resource.getUid());
values.put(entryColumnRemoteName(), resource.getName());
providerClient.update(ContentUris.withAppendedId(entriesURI(), id), values, null, null);
fresh.add(id);
}
return fresh.toArray(new Resource[0]);
return ArrayUtils.toPrimitive(fresh.toArray(new Long[0]), -1);
} catch(RemoteException ex) {
throw new LocalStorageException(ex);
}

@ -21,9 +21,7 @@ import lombok.Getter;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.ValidationException;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpException;
import org.apache.http.protocol.HTTP;
import android.util.Log;
import at.bitfire.davdroid.webdav.DavException;
@ -82,11 +80,11 @@ public abstract class RemoteCollection<T extends Resource> {
for (Resource resource : resources)
names.add(resource.getName());
LinkedList<T> foundResources = new LinkedList<T>();
collection.multiGet(multiGetType(), names.toArray(new String[0]));
if (collection.getMembers() == null)
throw new DavNoContentException();
LinkedList<T> foundResources = new LinkedList<T>();
for (WebDavResource member : collection.getMembers()) {
T resource = newResourceSkeleton(member.getName(), member.getETag());
try {
@ -124,9 +122,6 @@ public abstract class RemoteCollection<T extends Resource> {
if (data == null)
throw new DavNoContentException();
Log.i(TAG, "Received content:");
Log.i(TAG, IOUtils.toString(data, HTTP.UTF_8));
@Cleanup InputStream is = new ByteArrayInputStream(data);
resource.parseEntity(is);
return resource;

@ -95,20 +95,21 @@ public class SyncManager {
private int pushDeleted(LocalCollection<? extends Resource> local, RemoteCollection<? extends Resource> remote) throws LocalStorageException, IOException, HttpException {
int count = 0;
Resource[] deletedResources = local.findDeleted();
if (deletedResources != null) {
Log.i(TAG, "Remotely removing " + deletedResources.length + " deleted resource(s) (if not changed)");
for (Resource res : deletedResources) {
long[] deletedIDs = local.findDeleted();
if (deletedIDs != null) {
Log.i(TAG, "Remotely removing " + deletedIDs.length + " deleted resource(s) (if not changed)");
for (long id : deletedIDs) {
try {
Resource res = local.findById(id, false);
if (res.getName() != null) // is this resource even present remotely?
remote.delete(res);
local.delete(res);
count++;
} catch(NotFoundException e) {
Log.i(TAG, "Locally-deleted resource has already been removed from server");
} catch(PreconditionFailedException e) {
Log.i(TAG, "Locally-deleted resource has been changed on the server in the meanwhile");
}
local.delete(res);
count++;
}
local.commit();
}
@ -117,18 +118,20 @@ public class SyncManager {
private int pushNew(LocalCollection<? extends Resource> local, RemoteCollection<? extends Resource> remote) throws LocalStorageException, IOException, HttpException {
int count = 0;
Resource[] newResources = local.findNew();
if (newResources != null) {
Log.i(TAG, "Uploading " + newResources.length + " new resource(s) (if not existing)");
for (Resource res : newResources) {
long[] newIDs = local.findNew();
if (newIDs != null) {
Log.i(TAG, "Uploading " + newIDs.length + " new resource(s) (if not existing)");
for (long id : newIDs) {
try {
Resource res = local.findById(id, true);
Log.d(TAG, "Uploading " + res.toString());
remote.add(res);
local.clearDirty(res);
} catch(PreconditionFailedException e) {
Log.i(TAG, "Didn't overwrite existing resource with other content");
} catch (ValidationException e) {
Log.e(TAG, "Couldn't create entity for adding: " + e.toString());
}
local.clearDirty(res);
count++;
}
local.commit();
@ -138,18 +141,19 @@ public class SyncManager {
private int pushDirty(LocalCollection<? extends Resource> local, RemoteCollection<? extends Resource> remote) throws LocalStorageException, IOException, HttpException {
int count = 0;
Resource[] dirtyResources = local.findDirty();
if (dirtyResources != null) {
Log.i(TAG, "Uploading " + dirtyResources.length + " modified resource(s) (if not changed)");
for (Resource res : dirtyResources) {
long[] dirtyIDs = local.findDirty();
if (dirtyIDs != null) {
Log.i(TAG, "Uploading " + dirtyIDs.length + " modified resource(s) (if not changed)");
for (long id : dirtyIDs) {
try {
Resource res = local.findById(id, true);
remote.update(res);
local.clearDirty(res);
} catch(PreconditionFailedException e) {
Log.i(TAG, "Locally changed resource has been changed on the server in the meanwhile");
} catch (ValidationException e) {
Log.e(TAG, "Couldn't create entity for updating: " + e.toString());
}
local.clearDirty(res);
count++;
}
local.commit();

@ -240,8 +240,8 @@ public class WebDavResource {
if (entity == null)
throw new DavNoContentException();
InputStream rawContent = entity.getContent();
if (content == null)
@Cleanup InputStream rawContent = entity.getContent();
if (rawContent == null)
throw new DavNoContentException();
@Cleanup LoggingInputStream content = new LoggingInputStream(TAG, rawContent);
@ -304,9 +304,15 @@ public class WebDavResource {
checkResponse(response);
@Cleanup("consumeContent") HttpEntity entity = response.getEntity();
if (entity.getContent() == null)
if (entity == null)
throw new DavNoContentException();
@Cleanup InputStream rawContent = entity.getContent();
if (rawContent == null)
throw new DavNoContentException();
content = IOUtils.toByteArray(entity.getContent());
@Cleanup LoggingInputStream content = new LoggingInputStream(TAG, rawContent);
this.content = IOUtils.toByteArray(content);
}
public void put(byte[] data, PutMode mode) throws IOException, HttpException {
@ -328,7 +334,9 @@ public class WebDavResource {
if (getContentType() != null)
put.addHeader("Content-Type", getContentType());
checkResponse(client.execute(put));
HttpResponse response = client.execute(put);
@Cleanup("consumeContent") HttpEntity entity = response.getEntity();
checkResponse(response);
}
public void delete() throws IOException, HttpException {
@ -337,7 +345,9 @@ public class WebDavResource {
if (getETag() != null)
delete.addHeader("If-Match", getETag());
checkResponse(client.execute(delete));
HttpResponse response = client.execute(delete);
@Cleanup("consumeContent") HttpEntity entity = response.getEntity();
checkResponse(response);
}

Loading…
Cancel
Save