mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Refactor Forum Controller, so it can be used by private groups
This commit is contained in:
@@ -26,6 +26,7 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
"briar.LOCAL_AUTHOR_HANDLE";
|
||||
public static final String KEY_STARTUP_FAILED = "briar.STARTUP_FAILED";
|
||||
public static final String GROUP_ID = "briar.GROUP_ID";
|
||||
public static final String GROUP_NAME = "briar.GROUP_NAME";
|
||||
|
||||
public static final int REQUEST_PASSWORD = 1;
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
|
||||
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||
|
||||
public class CreateForumActivity extends BriarActivity
|
||||
@@ -150,7 +149,7 @@ public class CreateForumActivity extends BriarActivity
|
||||
Intent i = new Intent(CreateForumActivity.this,
|
||||
ForumActivity.class);
|
||||
i.putExtra(GROUP_ID, f.getId().getBytes());
|
||||
i.putExtra(FORUM_NAME, f.getName());
|
||||
i.putExtra(GROUP_NAME, f.getName());
|
||||
startActivity(i);
|
||||
Toast.makeText(CreateForumActivity.this,
|
||||
R.string.forum_created_toast, LENGTH_LONG).show();
|
||||
|
||||
@@ -2,9 +2,7 @@ package org.briarproject.android.forum;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
@@ -17,18 +15,13 @@ import android.widget.Toast;
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.android.forum.ForumController.ForumPostListener;
|
||||
import org.briarproject.android.sharing.ShareForumActivity;
|
||||
import org.briarproject.android.sharing.SharingStatusForumActivity;
|
||||
import org.briarproject.android.threaded.ThreadListActivity;
|
||||
import org.briarproject.android.threaded.ThreadListController;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -37,10 +30,8 @@ import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
|
||||
public class ForumActivity extends ThreadListActivity<ForumEntry, NestedForumAdapter>
|
||||
implements ForumPostListener {
|
||||
|
||||
static final String FORUM_NAME = "briar.FORUM_NAME";
|
||||
public class ForumActivity extends
|
||||
ThreadListActivity<Forum, ForumEntry, ForumPostHeader, NestedForumAdapter> {
|
||||
|
||||
private static final int REQUEST_FORUM_SHARED = 3;
|
||||
|
||||
@@ -53,41 +44,8 @@ public class ForumActivity extends ThreadListActivity<ForumEntry, NestedForumAda
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
String forumName = i.getStringExtra(FORUM_NAME);
|
||||
if (forumName != null) setTitle(forumName);
|
||||
|
||||
forumController.loadForum(groupId,
|
||||
new UiResultExceptionHandler<List<ForumEntry>, DbException>(
|
||||
this) {
|
||||
@Override
|
||||
public void onResultUi(List<ForumEntry> result) {
|
||||
Forum forum = forumController.getForum();
|
||||
if (forum != null) setTitle(forum.getName());
|
||||
List<ForumEntry> entries = new ArrayList<>(result);
|
||||
if (entries.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.setItems(entries);
|
||||
list.showData();
|
||||
if (state != null) {
|
||||
byte[] replyId =
|
||||
state.getByteArray(KEY_REPLY_ID);
|
||||
if (replyId != null)
|
||||
adapter.setReplyItemById(replyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO Improve UX ?
|
||||
finish();
|
||||
}
|
||||
});
|
||||
protected ThreadListController<Forum, ForumEntry, ForumPostHeader> getController() {
|
||||
return forumController;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,12 +59,6 @@ public class ForumActivity extends ThreadListActivity<ForumEntry, NestedForumAda
|
||||
return new NestedForumAdapter(this, layoutManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
notificationManager.clearForumPostNotification(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
@@ -157,55 +109,6 @@ public class ForumActivity extends ThreadListActivity<ForumEntry, NestedForumAda
|
||||
}
|
||||
}
|
||||
|
||||
protected void markItemRead(ForumEntry entry) {
|
||||
forumController.entryRead(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForumPostReceived(ForumPostHeader header) {
|
||||
forumController.loadPost(header,
|
||||
new UiResultExceptionHandler<ForumEntry, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(final ForumEntry result) {
|
||||
addItem(result, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO add proper exception handling
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendItem(String text, @Nullable ForumEntry replyItem) {
|
||||
UiResultExceptionHandler<ForumEntry, DbException> handler =
|
||||
new UiResultExceptionHandler<ForumEntry, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(ForumEntry result) {
|
||||
addItem(result, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO Improve UX ?
|
||||
finish();
|
||||
}
|
||||
};
|
||||
if (replyItem == null) {
|
||||
// root post
|
||||
forumController.createPost(StringUtils.toUtf8(text), handler);
|
||||
} else {
|
||||
forumController.createPost(StringUtils.toUtf8(text),
|
||||
replyItem.getId(), handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForumRemoved() {
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getItemPostedString() {
|
||||
return R.string.forum_new_entry_posted;
|
||||
@@ -220,18 +123,24 @@ public class ForumActivity extends ThreadListActivity<ForumEntry, NestedForumAda
|
||||
DialogInterface.OnClickListener okListener =
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
forumController.unsubscribe(
|
||||
new UiResultHandler<Boolean>(
|
||||
public void onClick(final DialogInterface dialog,
|
||||
int which) {
|
||||
forumController.deleteGroupItem(
|
||||
new UiResultExceptionHandler<Void, DbException>(
|
||||
ForumActivity.this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
Toast.makeText(ForumActivity.this,
|
||||
R.string.forum_left_toast,
|
||||
LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
public void onResultUi(Void v) {
|
||||
Toast.makeText(ForumActivity.this,
|
||||
R.string.forum_left_toast,
|
||||
LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(
|
||||
DbException exception) {
|
||||
// TODO proper error handling
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -241,8 +150,8 @@ public class ForumActivity extends ThreadListActivity<ForumEntry, NestedForumAda
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.dialog_title_leave_forum));
|
||||
builder.setMessage(getString(R.string.dialog_message_leave_forum));
|
||||
builder.setPositiveButton(R.string.dialog_button_leave, okListener);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.setNegativeButton(R.string.dialog_button_leave, okListener);
|
||||
builder.setPositiveButton(R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +1,12 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.android.DestroyableContext;
|
||||
import org.briarproject.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.android.controller.handler.ResultHandler;
|
||||
import org.briarproject.android.threaded.ThreadListController;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface ForumController extends ActivityLifecycleController {
|
||||
|
||||
void loadForum(GroupId groupId,
|
||||
ResultExceptionHandler<List<ForumEntry>, DbException> resultHandler);
|
||||
|
||||
@Nullable
|
||||
Forum getForum();
|
||||
|
||||
void loadPost(ForumPostHeader header,
|
||||
ResultExceptionHandler<ForumEntry, DbException> resultHandler);
|
||||
|
||||
void unsubscribe(ResultHandler<Boolean> resultHandler);
|
||||
|
||||
void entryRead(ForumEntry forumEntry);
|
||||
|
||||
void entriesRead(Collection<ForumEntry> messageIds);
|
||||
|
||||
void createPost(byte[] body,
|
||||
ResultExceptionHandler<ForumEntry, DbException> resultHandler);
|
||||
|
||||
void createPost(byte[] body, MessageId parentId,
|
||||
ResultExceptionHandler<ForumEntry, DbException> resultHandler);
|
||||
|
||||
interface ForumPostListener extends DestroyableContext {
|
||||
|
||||
@UiThread
|
||||
void onForumPostReceived(ForumPostHeader header);
|
||||
|
||||
@UiThread
|
||||
void onForumRemoved();
|
||||
}
|
||||
public interface ForumController
|
||||
extends ThreadListController<Forum, ForumEntry, ForumPostHeader> {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.android.controller.handler.ResultHandler;
|
||||
import org.briarproject.android.threaded.ThreadListControllerImpl;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
@@ -15,9 +14,7 @@ import org.briarproject.api.db.DatabaseExecutor;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.ForumPostReceivedEvent;
|
||||
import org.briarproject.api.event.GroupRemovedEvent;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPost;
|
||||
@@ -26,19 +23,12 @@ import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -47,25 +37,15 @@ import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.identity.Author.Status.OURSELVES;
|
||||
|
||||
public class ForumControllerImpl extends DbControllerImpl
|
||||
implements ForumController, EventListener {
|
||||
public class ForumControllerImpl
|
||||
extends ThreadListControllerImpl<Forum, ForumEntry, ForumPostHeader>
|
||||
implements ForumController {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumControllerImpl.class.getName());
|
||||
|
||||
private final Executor cryptoExecutor;
|
||||
private final ForumPostFactory forumPostFactory;
|
||||
private final CryptoComponent crypto;
|
||||
private final ForumManager forumManager;
|
||||
private final EventBus eventBus;
|
||||
private final IdentityManager identityManager;
|
||||
|
||||
private final Map<MessageId, byte[]> bodyCache = new ConcurrentHashMap<>();
|
||||
private final AtomicLong newestTimeStamp = new AtomicLong();
|
||||
|
||||
private volatile LocalAuthor localAuthor = null;
|
||||
private volatile Forum forum = null;
|
||||
private volatile ForumPostListener listener;
|
||||
|
||||
@Inject
|
||||
ForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
@@ -73,257 +53,70 @@ public class ForumControllerImpl extends DbControllerImpl
|
||||
@CryptoExecutor Executor cryptoExecutor,
|
||||
ForumPostFactory forumPostFactory, CryptoComponent crypto,
|
||||
ForumManager forumManager, EventBus eventBus,
|
||||
IdentityManager identityManager) {
|
||||
super(dbExecutor, lifecycleManager);
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.forumPostFactory = forumPostFactory;
|
||||
this.crypto = crypto;
|
||||
IdentityManager identityManager,
|
||||
AndroidNotificationManager notificationManager) {
|
||||
super(dbExecutor, lifecycleManager, cryptoExecutor, crypto, eventBus,
|
||||
identityManager, notificationManager);
|
||||
this.forumManager = forumManager;
|
||||
this.eventBus = eventBus;
|
||||
this.identityManager = identityManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreate(Activity activity) {
|
||||
if (activity instanceof ForumPostListener) {
|
||||
listener = (ForumPostListener) activity;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"An activity that injects the ForumController must " +
|
||||
"implement the ForumPostListener");
|
||||
}
|
||||
this.forumPostFactory = forumPostFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResume() {
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPause() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
super.onActivityResume();
|
||||
notificationManager.clearForumPostNotification(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (forum == null) return;
|
||||
super.eventOccurred(e);
|
||||
|
||||
if (e instanceof ForumPostReceivedEvent) {
|
||||
final ForumPostReceivedEvent pe = (ForumPostReceivedEvent) e;
|
||||
if (pe.getGroupId().equals(forum.getId())) {
|
||||
if (pe.getGroupId().equals(groupId)) {
|
||||
LOG.info("Forum post received, adding...");
|
||||
final ForumPostHeader fph = pe.getForumPostHeader();
|
||||
updateNewestTimestamp(fph.getTimestamp());
|
||||
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onForumPostReceived(fph);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
if (s.getGroup().getId().equals(forum.getId())) {
|
||||
LOG.info("Forum removed");
|
||||
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onForumRemoved();
|
||||
listener.onHeaderReceived(fph);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be run from the DbThread.
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
private void loadForum(GroupId groupId) throws DbException {
|
||||
// Get Forum
|
||||
long now = System.currentTimeMillis();
|
||||
forum = forumManager.getForum(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading forum took " + duration + " ms");
|
||||
|
||||
// Get First Identity
|
||||
now = System.currentTimeMillis();
|
||||
localAuthor = identityManager.getLocalAuthor();
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading author took " + duration + " ms");
|
||||
@Override
|
||||
protected Forum loadGroupItem() throws DbException {
|
||||
return forumManager.getForum(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be run from the DbThread.
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
private Collection<ForumPostHeader> loadHeaders() throws DbException {
|
||||
if (forum == null)
|
||||
throw new RuntimeException("Forum has not been initialized");
|
||||
|
||||
// Get Headers
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<ForumPostHeader> headers =
|
||||
forumManager.getPostHeaders(forum.getId());
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading headers took " + duration + " ms");
|
||||
return headers;
|
||||
@Override
|
||||
protected Collection<ForumPostHeader> loadHeaders() throws DbException {
|
||||
return forumManager.getPostHeaders(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be run from the DbThread.
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
private void loadBodies(Collection<ForumPostHeader> headers)
|
||||
@Override
|
||||
protected void loadBodies(Collection<ForumPostHeader> headers)
|
||||
throws DbException {
|
||||
// Get Bodies
|
||||
long now = System.currentTimeMillis();
|
||||
for (ForumPostHeader header : headers) {
|
||||
if (!bodyCache.containsKey(header.getId())) {
|
||||
byte[] body = forumManager.getPostBody(header.getId());
|
||||
String body = StringUtils
|
||||
.fromUtf8(forumManager.getPostBody(header.getId()));
|
||||
bodyCache.put(header.getId(), body);
|
||||
}
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading bodies took " + duration + " ms");
|
||||
}
|
||||
|
||||
private List<ForumEntry> buildForumEntries(
|
||||
Collection<ForumPostHeader> headers) {
|
||||
List<ForumEntry> entries = new ArrayList<>();
|
||||
for (ForumPostHeader h : headers) {
|
||||
byte[] body = bodyCache.get(h.getId());
|
||||
entries.add(new ForumEntry(h, StringUtils.fromUtf8(body)));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
private void updateNewestTimeStamp(Collection<ForumPostHeader> headers) {
|
||||
for (ForumPostHeader h : headers) {
|
||||
updateNewestTimestamp(h.getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadForum(final GroupId groupId,
|
||||
final ResultExceptionHandler<List<ForumEntry>, DbException> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Loading forum...");
|
||||
try {
|
||||
if (forum == null) {
|
||||
loadForum(groupId);
|
||||
}
|
||||
// Get Forum Posts and Bodies
|
||||
Collection<ForumPostHeader> headers = loadHeaders();
|
||||
updateNewestTimeStamp(headers);
|
||||
loadBodies(headers);
|
||||
resultHandler.onResult(buildForumEntries(headers));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
protected void markRead(MessageId id) throws DbException {
|
||||
forumManager.setReadFlag(groupId, id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Forum getForum() {
|
||||
return forum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPost(final ForumPostHeader header,
|
||||
final ResultExceptionHandler<ForumEntry, DbException> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Loading post...");
|
||||
try {
|
||||
loadBodies(Collections.singletonList(header));
|
||||
resultHandler.onResult(new ForumEntry(header, StringUtils
|
||||
.fromUtf8(bodyCache.get(header.getId()))));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe(final ResultHandler<Boolean> resultHandler) {
|
||||
if (forum == null) return;
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
forumManager.removeForum(forum);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Removing forum took " + duration + " ms");
|
||||
resultHandler.onResult(true);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryRead(ForumEntry forumEntry) {
|
||||
entriesRead(Collections.singletonList(forumEntry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entriesRead(final Collection<ForumEntry> forumEntries) {
|
||||
if (forum == null) return;
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
for (ForumEntry fe : forumEntries) {
|
||||
forumManager
|
||||
.setReadFlag(forum.getId(), fe.getId(), true);
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking read took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPost(byte[] body,
|
||||
ResultExceptionHandler<ForumEntry, DbException> resultHandler) {
|
||||
createPost(body, null, resultHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPost(final byte[] body, final MessageId parentId,
|
||||
final ResultExceptionHandler<ForumEntry, DbException> resultHandler) {
|
||||
public void send(final String body, @Nullable final MessageId parentId,
|
||||
final ResultExceptionHandler<ForumEntry, DbException> handler) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -332,17 +125,24 @@ public class ForumControllerImpl extends DbControllerImpl
|
||||
timestamp = Math.max(timestamp, newestTimeStamp.get());
|
||||
ForumPost p;
|
||||
try {
|
||||
LocalAuthor a = identityManager.getLocalAuthor();
|
||||
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||
byte[] b = localAuthor.getPrivateKey();
|
||||
PrivateKey authorKey = keyParser.parsePrivateKey(b);
|
||||
p = forumPostFactory.createPseudonymousPost(
|
||||
forum.getId(), timestamp, parentId, localAuthor,
|
||||
"text/plain", body, authorKey);
|
||||
byte[] k = a.getPrivateKey();
|
||||
PrivateKey authorKey = keyParser.parsePrivateKey(k);
|
||||
byte[] b = StringUtils.toUtf8(body);
|
||||
p = forumPostFactory
|
||||
.createPseudonymousPost(groupId, timestamp,
|
||||
parentId, a, "text/plain", b, authorKey);
|
||||
} catch (GeneralSecurityException | FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
return;
|
||||
}
|
||||
bodyCache.put(p.getMessage().getId(), body);
|
||||
storePost(p, resultHandler);
|
||||
storePost(p, handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -366,9 +166,8 @@ public class ForumControllerImpl extends DbControllerImpl
|
||||
p.getMessage().getTimestamp(),
|
||||
p.getAuthor(), OURSELVES, true);
|
||||
|
||||
resultHandler.onResult(new ForumEntry(h, StringUtils
|
||||
.fromUtf8(bodyCache.get(p.getMessage().getId()))));
|
||||
|
||||
resultHandler.onResult(new ForumEntry(h,
|
||||
bodyCache.get(p.getMessage().getId())));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
@@ -378,11 +177,14 @@ public class ForumControllerImpl extends DbControllerImpl
|
||||
});
|
||||
}
|
||||
|
||||
private void updateNewestTimestamp(long update) {
|
||||
long newest = newestTimeStamp.get();
|
||||
while (newest < update) {
|
||||
if (newestTimeStamp.compareAndSet(newest, update)) return;
|
||||
newest = newestTimeStamp.get();
|
||||
}
|
||||
@Override
|
||||
protected void deleteGroupItem(Forum forum) throws DbException {
|
||||
forumManager.removeForum(forum);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ForumEntry buildItem(ForumPostHeader header) {
|
||||
return new ForumEntry(header, bodyCache.get(header.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.android.forum;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -21,7 +20,7 @@ import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_NAME;
|
||||
|
||||
class ForumListAdapter
|
||||
extends BriarAdapter<ForumListItem, ForumListAdapter.ForumViewHolder> {
|
||||
@@ -84,7 +83,7 @@ class ForumListAdapter
|
||||
Intent i = new Intent(ctx, ForumActivity.class);
|
||||
Forum f = item.getForum();
|
||||
i.putExtra(GROUP_ID, f.getId().getBytes());
|
||||
i.putExtra(FORUM_NAME, f.getName());
|
||||
i.putExtra(GROUP_NAME, f.getName());
|
||||
ctx.startActivity(i);
|
||||
}
|
||||
});
|
||||
@@ -115,17 +114,6 @@ class ForumListAdapter
|
||||
return a.getForum().equals(b.getForum());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ForumListItem findItem(GroupId g) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
ForumListItem item = items.get(i);
|
||||
if (item.getForum().getGroup().getId().equals(g)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int findItemPosition(GroupId g) {
|
||||
int count = getItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
@@ -14,36 +14,44 @@ import android.view.View;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
import org.briarproject.android.threaded.ThreadListController.ThreadListListener;
|
||||
import org.briarproject.android.view.BriarRecyclerView;
|
||||
import org.briarproject.android.view.TextInputView;
|
||||
import org.briarproject.android.view.TextInputView.TextInputListener;
|
||||
import org.briarproject.api.clients.BaseGroup;
|
||||
import org.briarproject.api.clients.PostHeader;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static android.support.design.widget.Snackbar.make;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadItemAdapter<I>>
|
||||
public abstract class ThreadListActivity<G extends BaseGroup, I extends ThreadItem, H extends PostHeader, A extends ThreadItemAdapter<I>>
|
||||
extends BriarActivity
|
||||
implements TextInputListener, ThreadItemListener<I> {
|
||||
implements ThreadListListener<H>, TextInputListener,
|
||||
ThreadItemListener<I> {
|
||||
|
||||
protected static final String KEY_INPUT_VISIBILITY = "inputVisibility";
|
||||
protected static final String KEY_REPLY_ID = "replyId";
|
||||
|
||||
@Inject
|
||||
protected AndroidNotificationManager notificationManager;
|
||||
|
||||
protected A adapter;
|
||||
protected BriarRecyclerView list;
|
||||
protected TextInputView textInput;
|
||||
protected GroupId groupId;
|
||||
private byte[] replyId;
|
||||
|
||||
protected abstract ThreadListController<G, I, H> getController();
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public void onCreate(final Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
@@ -53,6 +61,10 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No GroupId in intent.");
|
||||
groupId = new GroupId(b);
|
||||
getController().setGroupId(groupId);
|
||||
String groupName = i.getStringExtra(GROUP_NAME);
|
||||
if (groupName != null) setTitle(groupName);
|
||||
else loadAndSetTitle();
|
||||
|
||||
textInput = (TextInputView) findViewById(R.id.text_input_container);
|
||||
textInput.setVisibility(GONE);
|
||||
@@ -62,17 +74,64 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
|
||||
list.setLayoutManager(linearLayoutManager);
|
||||
adapter = createAdapter(linearLayoutManager);
|
||||
list.setAdapter(adapter);
|
||||
|
||||
if (state != null) {
|
||||
replyId = state.getByteArray(KEY_REPLY_ID);
|
||||
}
|
||||
|
||||
loadItems();
|
||||
}
|
||||
|
||||
protected abstract @LayoutRes int getLayout();
|
||||
|
||||
protected abstract A createAdapter(LinearLayoutManager layoutManager);
|
||||
|
||||
private void loadAndSetTitle() {
|
||||
getController().loadGroupItem(
|
||||
new UiResultExceptionHandler<G, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(G forum) {
|
||||
setTitle(forum.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO Proper error handling
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadItems() {
|
||||
getController().loadItems(
|
||||
new UiResultExceptionHandler<Collection<I>, DbException>(
|
||||
this) {
|
||||
@Override
|
||||
public void onResultUi(Collection<I> result) {
|
||||
// FIXME What's the benefit of copying the collection?
|
||||
List<I> items = new ArrayList<>(result);
|
||||
if (items.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.setItems(items);
|
||||
list.showData();
|
||||
if (replyId != null)
|
||||
adapter.setReplyItemById(replyId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO Proper error handling
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
notificationManager.blockNotification(groupId);
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
@@ -80,7 +139,6 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
notificationManager.unblockNotification(groupId);
|
||||
list.stopPeriodicUpdate();
|
||||
}
|
||||
|
||||
@@ -130,12 +188,10 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
|
||||
public void onItemVisible(I item) {
|
||||
if (!item.isRead()) {
|
||||
item.setRead(true);
|
||||
markItemRead(item);
|
||||
getController().markItemRead(item);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void markItemRead(I item);
|
||||
|
||||
@Override
|
||||
public void onReplyClick(I item) {
|
||||
showTextInput(item);
|
||||
@@ -167,13 +223,50 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
|
||||
if (text.trim().length() == 0)
|
||||
return;
|
||||
I replyItem = adapter.getReplyItem();
|
||||
sendItem(text, replyItem);
|
||||
UiResultExceptionHandler<I, DbException> handler =
|
||||
new UiResultExceptionHandler<I, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(I result) {
|
||||
addItem(result, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO add proper exception handling
|
||||
finish();
|
||||
}
|
||||
};
|
||||
if (replyItem == null) {
|
||||
// root post
|
||||
getController().send(text, handler);
|
||||
} else {
|
||||
getController().send(text, replyItem.getId(), handler);
|
||||
}
|
||||
textInput.hideSoftKeyboard();
|
||||
textInput.setVisibility(GONE);
|
||||
adapter.setReplyItem(null);
|
||||
}
|
||||
|
||||
protected abstract void sendItem(String text, I replyToItem);
|
||||
@Override
|
||||
public void onHeaderReceived(H header) {
|
||||
getController().loadItem(header,
|
||||
new UiResultExceptionHandler<I, DbException>(this) {
|
||||
@Override
|
||||
public void onResultUi(final I result) {
|
||||
addItem(result, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO add proper exception handling
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupRemoved() {
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
protected void addItem(final I item, boolean isLocal) {
|
||||
adapter.add(item);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.briarproject.android.threaded;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.android.DestroyableContext;
|
||||
import org.briarproject.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.api.clients.BaseGroup;
|
||||
import org.briarproject.api.clients.PostHeader;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ThreadListController<G extends BaseGroup, I extends ThreadItem, H extends PostHeader>
|
||||
extends ActivityLifecycleController {
|
||||
|
||||
void setGroupId(GroupId groupId);
|
||||
|
||||
void loadGroupItem(ResultExceptionHandler<G, DbException> handler);
|
||||
|
||||
void loadItem(H header, ResultExceptionHandler<I, DbException> handler);
|
||||
|
||||
void loadItems(ResultExceptionHandler<Collection<I>, DbException> handler);
|
||||
|
||||
void markItemRead(I item);
|
||||
|
||||
void markItemsRead(Collection<I> items);
|
||||
|
||||
void send(String body, ResultExceptionHandler<I, DbException> handler);
|
||||
|
||||
void send(String body, @Nullable MessageId parentId,
|
||||
ResultExceptionHandler<I, DbException> handler);
|
||||
|
||||
void deleteGroupItem(ResultExceptionHandler<Void, DbException> handler);
|
||||
|
||||
interface ThreadListListener<H> extends DestroyableContext {
|
||||
@UiThread
|
||||
void onHeaderReceived(H header);
|
||||
|
||||
@UiThread
|
||||
void onGroupRemoved();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package org.briarproject.android.threaded;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.CallSuper;
|
||||
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.api.clients.BaseGroup;
|
||||
import org.briarproject.api.clients.PostHeader;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.db.DatabaseExecutor;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.GroupRemovedEvent;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPostFactory;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public abstract class ThreadListControllerImpl<G extends BaseGroup, I extends ThreadItem, H extends PostHeader>
|
||||
extends DbControllerImpl
|
||||
implements ThreadListController<G, I, H>, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ThreadListControllerImpl.class.getName());
|
||||
|
||||
protected final Executor cryptoExecutor;
|
||||
protected final CryptoComponent crypto;
|
||||
protected final EventBus eventBus;
|
||||
protected final IdentityManager identityManager;
|
||||
protected final AndroidNotificationManager notificationManager;
|
||||
|
||||
protected final Map<MessageId, String> bodyCache =
|
||||
new ConcurrentHashMap<>();
|
||||
protected final AtomicLong newestTimeStamp = new AtomicLong();
|
||||
|
||||
protected volatile GroupId groupId;
|
||||
|
||||
protected ThreadListListener<H> listener;
|
||||
|
||||
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager,
|
||||
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
|
||||
EventBus eventBus, IdentityManager identityManager,
|
||||
AndroidNotificationManager notificationManager) {
|
||||
super(dbExecutor, lifecycleManager);
|
||||
this.cryptoExecutor = cryptoExecutor;
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.identityManager = identityManager;
|
||||
this.notificationManager = notificationManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupId(GroupId groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onActivityCreate(Activity activity) {
|
||||
listener = (ThreadListListener<H>) activity;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onActivityResume() {
|
||||
checkGroupId();
|
||||
notificationManager.blockNotification(groupId);
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onActivityPause() {
|
||||
notificationManager.unblockNotification(groupId);
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
if (s.getGroup().getId().equals(groupId)) {
|
||||
LOG.info("Group removed");
|
||||
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onGroupRemoved();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGroupItem(
|
||||
final ResultExceptionHandler<G, DbException> handler) {
|
||||
checkGroupId();
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
G groupItem = loadGroupItem();
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading forum took " + duration + " ms");
|
||||
handler.onResult(groupItem);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be run from the DbThread.
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
protected abstract G loadGroupItem() throws DbException;
|
||||
|
||||
@Override
|
||||
public void loadItems(
|
||||
final ResultExceptionHandler<Collection<I>, DbException> handler) {
|
||||
checkGroupId();
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Loading items...");
|
||||
try {
|
||||
// Load headers
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<H> headers = loadHeaders();
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading headers took " + duration + " ms");
|
||||
|
||||
// Update timestamp of newest item
|
||||
updateNewestTimeStamp(headers);
|
||||
|
||||
// Load bodies
|
||||
now = System.currentTimeMillis();
|
||||
loadBodies(headers);
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading bodies took " + duration + " ms");
|
||||
|
||||
// Build and hand over items
|
||||
handler.onResult(buildItems(headers));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be run from the DbThread.
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
protected abstract Collection<H> loadHeaders() throws DbException;
|
||||
|
||||
/**
|
||||
* This should only be run from the DbThread.
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
protected abstract void loadBodies(Collection<H> headers)
|
||||
throws DbException;
|
||||
|
||||
@Override
|
||||
public void loadItem(final H header,
|
||||
final ResultExceptionHandler<I, DbException> handler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Loading item...");
|
||||
try {
|
||||
loadBodies(Collections.singletonList(header));
|
||||
I item = buildItem(header);
|
||||
handler.onResult(item);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markItemRead(I item) {
|
||||
markItemsRead(Collections.singletonList(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markItemsRead(final Collection<I> items) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
for (I i : items) {
|
||||
markRead(i.getId());
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking read took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be run from the DbThread.
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
protected abstract void markRead(MessageId id) throws DbException;
|
||||
|
||||
@Override
|
||||
public void send(String body,
|
||||
ResultExceptionHandler<I, DbException> resultHandler) {
|
||||
send(body, null, resultHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteGroupItem(
|
||||
final ResultExceptionHandler<Void, DbException> handler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
G groupItem = loadGroupItem();
|
||||
deleteGroupItem(groupItem);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Removing group took " + duration + " ms");
|
||||
//noinspection ConstantConditions
|
||||
handler.onResult(null);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be run from the DbThread.
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
protected abstract void deleteGroupItem(G groupItem) throws DbException;
|
||||
|
||||
private List<I> buildItems(Collection<H> headers) {
|
||||
List<I> entries = new ArrayList<>();
|
||||
for (H h : headers) {
|
||||
entries.add(buildItem(h));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* When building the item, the body can be assumed to be cached
|
||||
*/
|
||||
protected abstract I buildItem(H header);
|
||||
|
||||
private void updateNewestTimeStamp(Collection<H> headers) {
|
||||
for (H h : headers) {
|
||||
updateNewestTimestamp(h.getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateNewestTimestamp(long update) {
|
||||
long newest = newestTimeStamp.get();
|
||||
while (newest < update) {
|
||||
if (newestTimeStamp.compareAndSet(newest, update)) return;
|
||||
newest = newestTimeStamp.get();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkGroupId() {
|
||||
if (groupId == null) {
|
||||
throw new IllegalStateException(
|
||||
"You must set the GroupId before the controller is started.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user