blob: beb8fc53616a368ecffd8b1c28f1c446acad801f [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.todos.persistence.syncbase;
import android.app.Activity;
import android.support.annotation.NonNull;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.HashSet;
import java.util.Set;
import io.v.impl.google.services.syncbase.SyncbaseServer;
import io.v.todos.model.ListSpec;
import io.v.todos.model.Task;
import io.v.todos.model.TaskSpec;
import io.v.todos.persistence.TodoListListener;
import io.v.todos.persistence.TodoListPersistence;
import io.v.v23.InputChannel;
import io.v.v23.InputChannelCallback;
import io.v.v23.InputChannels;
import io.v.v23.context.VContext;
import io.v.v23.syncbase.ChangeType;
import io.v.v23.syncbase.Collection;
import io.v.v23.syncbase.WatchChange;
import io.v.v23.verror.NoExistException;
import io.v.v23.verror.VException;
public class SyncbaseTodoList extends SyncbasePersistence implements TodoListPersistence {
public static final String
LIST_METADATA_ROW_NAME = "list",
TASKS_PREFIX = "tasks_";
private static final String
SHOW_DONE_ROW_NAME = "ShowDone";
private final Collection mList;
private final TodoListListener mListener;
private final IdGenerator mIdGenerator = new IdGenerator(IdAlphabets.ROW_NAME, true);
private final Set<String> mTaskIds = new HashSet<>();
/**
* This assumes that the collection for this list already exists.
*/
public SyncbaseTodoList(Activity activity, String listId, TodoListListener listener)
throws VException, SyncbaseServer.StartException {
super(activity);
mListener = listener;
mList = getDatabase().getCollection(mVContext, listId);
InputChannel<WatchChange> listWatch = getDatabase().watch(mVContext, mList.id(), "");
ListenableFuture<Void> listWatchFuture = InputChannels.withCallback(listWatch,
new InputChannelCallback<WatchChange>() {
@Override
public ListenableFuture<Void> onNext(WatchChange change) {
processWatchChange(change);
return null;
}
});
Futures.addCallback(listWatchFuture, new TrappingCallback<Void>(activity) {
@Override
public void onFailure(@NonNull Throwable t) {
if (t instanceof NoExistException) {
// The collection has been deleted.
mListener.onDelete();
} else {
super.onFailure(t);
}
}
});
// Watch the "showDone" boolean in the userdata collection and forward changes to the
// listener.
InputChannel<WatchChange> showDoneWatch = getDatabase()
.watch(mVContext, getUserCollection().id(), SHOW_DONE_ROW_NAME);
trap(InputChannels.withCallback(showDoneWatch, new InputChannelCallback<WatchChange>() {
@Override
public ListenableFuture<Void> onNext(WatchChange result) {
mListener.onUpdateShowDone((boolean)result.getValue());
return null;
}
}));
}
private void processWatchChange(WatchChange change) {
String rowName = change.getRowName();
if (rowName.equals(SyncbaseTodoList.LIST_METADATA_ROW_NAME)) {
ListSpec listSpec = SyncbasePersistence.castWatchValue(change.getValue(),
ListSpec.class);
mListener.onUpdate(listSpec);
} else if (change.getChangeType() == ChangeType.DELETE_CHANGE) {
mTaskIds.remove(rowName);
mListener.onItemDelete(rowName);
} else {
mIdGenerator.registerId(change.getRowName().substring(TASKS_PREFIX.length()));
TaskSpec taskSpec = SyncbasePersistence.castWatchValue(change.getValue(),
TaskSpec.class);
Task task = new Task(rowName, taskSpec);
if (mTaskIds.add(rowName)) {
mListener.onItemAdd(task);
} else {
mListener.onItemUpdate(task);
}
}
}
@Override
public void updateTodoList(ListSpec listSpec) {
trap(mList.put(mVContext, LIST_METADATA_ROW_NAME, listSpec, ListSpec.class));
}
@Override
public void deleteTodoList() {
trap(getUserCollection().delete(mVContext, mList.id().getName()));
trap(mList.destroy(mVContext));
}
public static ListenableFuture<Void> updateListTimestamp(final VContext vContext,
final Collection list) {
ListenableFuture<Object> get = list.get(vContext, LIST_METADATA_ROW_NAME, ListSpec.class);
return Futures.transformAsync(get, new AsyncFunction<Object, Void>() {
@Override
public ListenableFuture<Void> apply(Object oldValue) throws Exception {
ListSpec listSpec = (ListSpec) oldValue;
listSpec.setUpdatedAt(System.currentTimeMillis());
return list.put(vContext, LIST_METADATA_ROW_NAME, listSpec, ListSpec.class);
}
});
}
private void updateListTimestamp() {
trap(updateListTimestamp(mVContext, mList));
}
@Override
public void addTask(TaskSpec task) {
trap(mList.put(mVContext, TASKS_PREFIX + mIdGenerator.generateTailId(), task,
TaskSpec.class));
updateListTimestamp();
}
@Override
public void updateTask(Task task) {
trap(mList.put(mVContext, task.key, task.toSpec(), TaskSpec.class));
updateListTimestamp();
}
@Override
public void deleteTask(String key) {
trap(mList.delete(mVContext, key));
updateListTimestamp();
}
@Override
public void setShowDone(boolean showDone) {
trap(getUserCollection().put(mVContext, SHOW_DONE_ROW_NAME, showDone, Boolean.TYPE));
}
}