package com.vaguehope.onosendai.update;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.json.JSONException;
import android.content.Context;
import android.content.Intent;
import com.vaguehope.onosendai.config.Account;
import com.vaguehope.onosendai.config.AccountProvider;
import com.vaguehope.onosendai.config.Column;
import com.vaguehope.onosendai.config.Config;
import com.vaguehope.onosendai.config.InternalColumnType;
import com.vaguehope.onosendai.config.Prefs;
import com.vaguehope.onosendai.model.ScrollState;
import com.vaguehope.onosendai.model.ScrollState.ScrollDirection;
import com.vaguehope.onosendai.provider.hosaka.HosakaColumn;
import com.vaguehope.onosendai.provider.hosaka.HosakaProvider;
import com.vaguehope.onosendai.storage.DbBindingService;
import com.vaguehope.onosendai.storage.DbInterface;
import com.vaguehope.onosendai.storage.DbInterface.ScrollChangeType;
import com.vaguehope.onosendai.storage.SaveScrollNow;
import com.vaguehope.onosendai.ui.pref.FetchingPrefFragment;
import com.vaguehope.onosendai.util.DateHelper;
import com.vaguehope.onosendai.util.ExcpetionHelper;
import com.vaguehope.onosendai.util.LogWrapper;
public class HosakaSyncService extends DbBindingService {
protected static final LogWrapper LOG = new LogWrapper("HSS");
public static void startServiceIfConfigured (final Context context, final Config conf, final int[] columnIds) {
if (columnIds != null && columnIds.length > 0) return; // Not on manual with specific columns.
if (conf.firstAccountOfType(AccountProvider.HOSAKA) == null) return; // Must have account configured.
startService(context);
}
private static void startService (final Context context) {
context.startService(new Intent(context, HosakaSyncService.class));
}
public HosakaSyncService () {
super(HosakaSyncService.class.getSimpleName(), LOG);
}
@Override
protected void doWork (final Intent i) {
final Prefs prefs = new Prefs(getBaseContext());
final Config conf;
try {
conf = prefs.asConfig();
}
catch (final JSONException e) {
LOG.w("Can not send to Hosaka: %s", e.toString());
return;
}
// XXX Currently this assumes only one Hosaka account.
// TODO Make UI stop user adding more than one Hosaka account.
final Account account = conf.firstAccountOfType(AccountProvider.HOSAKA);
if (account == null) {
LOG.i("Not sending to Hosaka: no account found.");
return;
}
if (!waitForDbReady()) return;
final DbInterface db = getDb();
SaveScrollNow.requestAndWaitForUiToSaveScroll(db);
final Map<String, Column> hashToCol = new HashMap<String, Column>();
final Map<String, HosakaColumn> toPush = new HashMap<String, HosakaColumn>();
for (final Column col : conf.getColumns()) {
if (InternalColumnType.fromColumn(col) != null) continue; // Do not sync internal columns.
final String hash = HosakaColumn.columnHash(col, conf);
hashToCol.put(hash, col);
final ScrollState ss = db.getScroll(col.getId());
if (ss == null) continue; // In case of (new) empty columns.
// Always add all columns, even if sent before new values.
// - Old / regressed values will be filtered server side.
// - Values sent can be used to filter response.
// - Also useful as do not know state of remote DB.
toPush.put(hash, new HosakaColumn(null /* ss.getItemId(); TODO ScrollState to also store sid? */, ss.getItemTime(), ss.getUnreadTime(), ss.getScrollDirection()));
}
final HosakaProvider prov = new HosakaProvider();
try {
// Make POST even if not really sending anything new, as may be fetching new state.
final long startTime = now();
final Map<String, HosakaColumn> returnedColumns = prov.sendColumns(account, toPush);
final long durationMillis = TimeUnit.NANOSECONDS.toMillis(now() - startTime);
LOG.i("Sent %s in %d millis: %s", account.getAccessToken(), durationMillis, toPush);
final boolean syncScroll = prefs.getSharedPreferences().getBoolean(FetchingPrefFragment.KEY_SYNC_SCROLL, false);
final Map<Column, ScrollState> colToNewScroll = new HashMap<Column, ScrollState>();
for (final Entry<String, HosakaColumn> e : returnedColumns.entrySet()) {
final String hash = e.getKey();
final Column col = hashToCol.get(hash);
final HosakaColumn before = toPush.get(hash);
final HosakaColumn after = e.getValue();
if (col != null && before != null &&
(after.getUnreadTime() > before.getUnreadTime() ||
(syncScroll && before.getScrollDirection() == ScrollDirection.UP && after.getItemTime() > before.getItemTime()))) {
colToNewScroll.put(col, after.toScrollState());
}
}
db.mergeAndStoreScrolls(colToNewScroll, syncScroll ? ScrollChangeType.UNREAD_AND_SCROLL : ScrollChangeType.UNREAD);
LOG.i("Merged %s columns: %s.", colToNewScroll.size(), colToNewScroll);
storeResult(db, toPush.size(), colToNewScroll.size(), null);
}
catch (final IOException e) {
storeResult(db, toPush.size(), 0, e);
}
catch (final JSONException e) {
storeResult(db, toPush.size(), 0, e);
}
finally {
prov.shutdown();
}
}
private void storeResult (final DbInterface db, final int pushedCount, final int pulledCount, final Exception e) {
final String status;
if (e != null) {
status = String.format("Failed: %s", ExcpetionHelper.causeTrace(e)); //ES
LOG.w(status);
}
else {
status = String.format("Success: pushed %s and pulled %s columns.", pushedCount, pulledCount); //ES
}
db.storeValue(KvKeys.KEY_HOSAKA_STATUS,
String.format("%s %s",
DateHelper.formatDateTime(this, System.currentTimeMillis()),
status));
}
private static final long NANO_ORIGIN = System.nanoTime();
protected static long now () {
return System.nanoTime() - NANO_ORIGIN;
}
}