package com.buddycloud.model;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import org.apache.http.entity.StringEntity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.os.AsyncTask;
import com.buddycloud.http.BuddycloudHTTPHelper;
import com.buddycloud.log.Logger;
import com.buddycloud.model.dao.PostsDAO;
import com.buddycloud.model.dao.ThreadsDAO;
import com.buddycloud.preferences.Preferences;
import com.buddycloud.utils.TimeUtils;
public class PostsModel extends AbstractModel<JSONArray, JSONObject, String> {
private static final String TAG = PostsModel.class.getName();
private static PostsModel instance;
private static final int REMOTE_PAGE_SIZE = 10;
private static final int LOCAL_PAGE_SIZE = 10;
private static final String POSTS_ENDPOINT = "/content/posts";
private static final String THREADS_ENDPOINT = "/content/posts/threads";
private PostsModel() {}
public static PostsModel getInstance() {
if (instance == null) {
instance = new PostsModel();
}
return instance;
}
private void persistThreads(Context context, String channel, JSONArray postsPerThreads) throws JSONException, ParseException {
PostsDAO postsDAO = PostsDAO.getInstance(context);
String newestThreadUpdated = null;
JSONObject newestThread = ThreadsDAO.getInstance(context).getNewest(channel);
if (newestThread != null) {
newestThreadUpdated = newestThread.optString("updated");
}
for (int i = 0; i < postsPerThreads.length(); i++) {
JSONObject thread = postsPerThreads.optJSONObject(i);
String threadUpdated = thread.optString("updated");
if (newestThreadUpdated != null &&
TimeUtils.after(threadUpdated, newestThreadUpdated)) {
continue;
}
String threadId = thread.optString("id");
updateThreadTimestamp(context, channel, threadId, threadUpdated);
JSONArray items = thread.optJSONArray("items");
for (int j = 0; j < items.length(); j++) {
JSONObject item = items.optJSONObject(j);
normalize(item);
item.put("threadId", threadId);
if (postsDAO.get(channel, item.optString("id")) == null) {
postsDAO.insert(channel, item);
}
}
}
}
public void persistSinglePost(Context context, String channel,
JSONObject post) throws JSONException, ParseException {
PostsDAO postsDAO = PostsDAO.getInstance(context);
String updated = post.optString("updated");
String threadId = post.has("replyTo") ? post.optString("replyTo")
: post.optString("id");
JSONObject thread = ThreadsDAO.getInstance(context).get(threadId);
if (thread != null) {
String threadUpdated = thread.optString("updated");
if (TimeUtils.after(threadUpdated, updated)) {
updateThreadTimestamp(context, channel, threadId, updated);
}
} else {
updateThreadTimestamp(context, channel, threadId, updated);
}
normalize(post);
post.put("threadId", threadId);
if (postsDAO.get(channel, post.optString("id")) == null) {
postsDAO.insert(channel, post);
}
}
private void updateThreadTimestamp(Context context, String channel,
String threadId, String threadUpdated) throws JSONException {
JSONObject thread = new JSONObject();
thread.put("id", threadId);
thread.put("channel", channel);
thread.put("updated", threadUpdated);
ThreadsDAO dao = ThreadsDAO.getInstance(context);
JSONObject existingThread = dao.get(threadId, channel);
if (existingThread == null) {
dao.insert(threadId, thread);
} else {
dao.update(threadId, thread);
}
}
private void normalize(JSONObject item) {
String author = item.optString("author");
if (author.contains("acct:")) {
String[] split = author.split(":");
author = split[1];
try {
item.put("author", author);
} catch (JSONException e) {}
}
}
@Override
public JSONArray getFromCache(Context context, String... p) {
String channelJid = p[0];
String after = null;
if (p.length > 1) {
after = p[1];
}
final PostsDAO postsDAO = PostsDAO.getInstance(context);
try {
return postsDAO.get(channelJid, after, LOCAL_PAGE_SIZE);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
private void fetchPosts(final Context context, final String channelJid,
final ModelCallback<Void> callback, String after, String before) {
BuddycloudHTTPHelper.getArray(postsUrl(context, channelJid, after), context,
new ModelCallback<JSONArray>() {
@Override
public void success(final JSONArray response) {
new AsyncTask<Void, Void, Exception>() {
@Override
protected Exception doInBackground(Void... params) {
try {
persistThreads(context, channelJid, response);
return null;
} catch (Exception e) {
return e;
}
}
@Override
protected void onPostExecute(Exception e) {
if (e != null) {
error(e);
} else {
callback.success(null);
}
}
}.execute();
}
@Override
public void error(Throwable throwable) {
callback.error(throwable);
}
});
}
private String postsUrl(Context context, String channel, String after) {
String apiAddress = Preferences.getPreference(context, Preferences.API_ADDRESS);
String postsURL = apiAddress + "/" + channel + THREADS_ENDPOINT + "?max=" + REMOTE_PAGE_SIZE;
if (after != null) {
postsURL += "&after=" + after;
}
return postsURL;
}
private String postsUrl(Context context, String channel) {
String apiAddress = Preferences.getPreference(context, Preferences.API_ADDRESS);
return apiAddress + "/" + channel + POSTS_ENDPOINT;
}
private String postUrl(Context context, String channel, String postId) {
String apiAddress = Preferences.getPreference(context, Preferences.API_ADDRESS);
return apiAddress + "/" + channel + POSTS_ENDPOINT + "/" + postId;
}
public synchronized void savePendingPosts(final Context context) {
Semaphore semaphore = new Semaphore(0);
int permits = 0;
Map<String, JSONArray> pending = PostsDAO.getInstance(context).getPending();
for (Entry<String, JSONArray> postsPerChannel : pending.entrySet()) {
String channelJid = postsPerChannel.getKey();
JSONArray posts = postsPerChannel.getValue();
for (int i = 0; i < posts.length(); i++) {
JSONObject post = posts.optJSONObject(i);
permits++;
savePendingPost(context, channelJid, post, semaphore);
}
}
try {
semaphore.acquire(permits);
} catch (InterruptedException e) {}
}
private void savePendingPost(final Context context, final String channelJid,
final JSONObject post, final Semaphore semaphore) {
try {
JSONObject tempPost = new JSONObject(post, new String[] {"content", "replyTo", "media" });
StringEntity requestEntity = new StringEntity(tempPost.toString(), "UTF-8");
requestEntity.setContentType("application/json");
BuddycloudHTTPHelper.post(postsUrl(context, channelJid), true, false, requestEntity, context,
new ModelCallback<JSONObject>() {
@Override
public void success(JSONObject response) {
String postId = post.optString("id");
PostsDAO.getInstance(context).delete(channelJid, postId);
notifyDeleted(channelJid, postId, post.optString("replyTo", null));
notifyChanged();
semaphore.release();
}
@Override
public void error(Throwable throwable) {
semaphore.release();
}
});
} catch (Exception e) {
semaphore.release();
}
}
@Override
public void save(final Context context, JSONObject object,
final ModelCallback<JSONObject> callback, String... p) {
if (p == null || p.length < 1) {
return;
}
try {
Logger.debug(TAG, object.toString());
StringEntity requestEntity = new StringEntity(object.toString(), "UTF-8");
requestEntity.setContentType("application/json");
String author = (String) Preferences.getPreference(context, Preferences.MY_CHANNEL_JID);
final String channelJid = p[0];
final String tempItemId = UUID.randomUUID().toString();
final JSONObject tempObject = new JSONObject(object, new String[]{"content", "replyTo", "media"});
tempObject.put("id", tempItemId);
tempObject.put("updated", TimeUtils.formatISO(new Date()));
tempObject.put("threadUpdated", TimeUtils.formatISO(new Date()));
tempObject.put("threadId", tempObject.has("replyTo") ?
tempObject.optString("replyTo") : tempItemId);
tempObject.put("author", author);
tempObject.put("channel", channelJid);
final PostsDAO postsDAO = PostsDAO.getInstance(context);
postsDAO.insert(channelJid, tempObject);
notifyAdded(channelJid, tempObject);
BuddycloudHTTPHelper.post(postsUrl(context, channelJid), true, false, requestEntity, context,
new ModelCallback<JSONObject>() {
@Override
public void success(JSONObject response) {
postsDAO.delete(channelJid, tempItemId);
notifyDeleted(channelJid, tempItemId,
tempObject.optString("replyTo", null));
callback.success(response);
sync(context);
}
@Override
public void error(Throwable throwable) {
callback.error(throwable);
}
});
} catch (UnsupportedEncodingException e) {
callback.error(e);
} catch (JSONException e) {
callback.error(e);
}
}
protected void sync(final Context context) {
final SyncModel syncModel = SyncModel.getInstance();
SyncModel.getInstance().syncNoSummary(context, new ModelCallbackImpl<Void>(){
@Override
public void success(Void response) {
syncModel.fill(context, new ModelCallbackImpl<Void>());
}
@Override
public void error(Throwable throwable) {
success(null);
}
});
}
@Override
public void getFromServer(Context context, ModelCallback<JSONArray> callback,
String... p) {
// TODO Auto-generated method stub
}
public void fillMore(Context context, ModelCallback<Void> callback, String... p) {
String channelJid = p[0];
String oldestPostId = p[1];
fetchPosts(context, channelJid, callback, oldestPostId, null);
}
@Override
public void fill(Context context, ModelCallback<Void> callback, String... p) {
String channelJid = p[0];
fetchPosts(context, channelJid, callback, null, null);
}
public static boolean isPending(JSONObject post) {
String published = post.optString("published", null);
return published == null || published.length() == 0;
}
@Override
public void delete(final Context context, final ModelCallback<Void> callback, String... p) {
final String channelJid = p[0];
final String itemId = p[1];
final JSONObject oldPost = PostsDAO.getInstance(context).get(channelJid, itemId);
if (oldPost != null && isPending(oldPost)) {
PostsDAO.getInstance(context).delete(channelJid, itemId);
notifyDeleted(channelJid, itemId, oldPost.optString("replyTo", null));
callback.success(null);
return;
}
String url = postUrl(context, channelJid, itemId);
BuddycloudHTTPHelper.delete(url, true, false, context,
new ModelCallback<JSONObject>() {
@Override
public void success(JSONObject response) {
if (oldPost != null) {
PostsDAO.getInstance(context).delete(channelJid, itemId);
notifyDeleted(channelJid, itemId, oldPost.optString("replyTo", null));
}
callback.success(null);
}
@Override
public void error(Throwable throwable) {
callback.error(throwable);
}
});
}
}