| // Copyright 2015 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.syncslides.db; |
| |
| import android.os.Handler; |
| import android.os.Looper; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| |
| import io.v.syncslides.model.DynamicList; |
| import io.v.syncslides.model.ListListener; |
| import io.v.v23.context.CancelableVContext; |
| import io.v.v23.context.VContext; |
| |
| /** |
| * WatchedList manages an in-memory copy of data that is in syncbase. The Watcher |
| * passed to the WatchedList constructor contains the logic for fetching the |
| * actual data from syncbase and sorting it appropriately. WatchedList keeps |
| * track of the data and listeners and notifies the listeners when the data |
| * changes. |
| */ |
| class WatchedList<E> implements DynamicList<E> { |
| private static final String TAG = "WatchedList"; |
| |
| private final VContext mBaseContext; |
| private final Set<ListListener> mListeners; |
| private final ExecutorService mExecutor; |
| private final Handler mHandler; |
| private final Watcher mWatcher; |
| private final List<E> mElems; |
| private CancelableVContext mCurrentContext; |
| |
| WatchedList(VContext context, Watcher watcher) { |
| mListeners = Sets.newHashSet(); |
| mBaseContext = context; |
| mExecutor = Executors.newSingleThreadExecutor(); |
| mHandler = new Handler(Looper.getMainLooper()); |
| mWatcher = watcher; |
| mElems = Lists.newArrayList(); |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mElems.size(); |
| } |
| |
| @Override |
| public E get(int i) { |
| return mElems.get(i); |
| } |
| |
| @Override |
| public void addListener(final ListListener listener) { |
| mListeners.add(listener); |
| if (mListeners.size() == 1) { |
| // First listener. Start the thread. |
| mCurrentContext = mBaseContext.withCancel(); |
| mExecutor.submit(() -> mWatcher.watch(mCurrentContext, new Watcher.Listener<E>() { |
| @Override |
| public void onPut(final E elem) { |
| mHandler.post(() -> put(elem)); |
| } |
| |
| @Override |
| public void onDelete(final E elem) { |
| mHandler.post(() -> delete(elem)); |
| } |
| |
| @Override |
| public void onError(final Exception e) { |
| mHandler.post(() -> error(e)); |
| } |
| })); |
| } |
| mHandler.post(() -> listener.notifyDataSetChanged()); |
| } |
| |
| @Override |
| public void removeListener(ListListener listener) { |
| mListeners.remove(listener); |
| if (mListeners.isEmpty()) { |
| // Stop mWatcher via cancel. |
| mCurrentContext.cancel(); |
| mCurrentContext = null; |
| mHandler.removeCallbacksAndMessages(null); |
| } |
| } |
| |
| private void put(E elem) { |
| int idx = 0; |
| for (; idx < mElems.size(); idx++) { |
| int comp = mWatcher.compare(mElems.get(idx), elem); |
| if (comp == 0) { |
| // Existing entry with a change. |
| mElems.set(idx, elem); |
| for (ListListener listener : mListeners) { |
| listener.notifyItemChanged(idx); |
| } |
| return; |
| } else if (comp > 0) { |
| break; |
| } |
| } |
| // New element. |
| mElems.add(idx, elem); |
| for (ListListener listener : mListeners) { |
| listener.notifyItemInserted(idx); |
| } |
| |
| } |
| |
| private void delete(E elem) { |
| for (int i = 0; i < mElems.size(); i++) { |
| if (mWatcher.compare(mElems.get(i), elem) == 0) { |
| mElems.remove(i); |
| for (ListListener listener : mListeners) { |
| listener.notifyItemRemoved(i); |
| } |
| } |
| } |
| } |
| |
| private void error(Exception e) { |
| for (ListListener listener : mListeners) { |
| listener.onError(e); |
| } |
| } |
| } |