| // 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)); |
| } |
| } |