package org.wikipedia.useroption.database; import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import org.wikipedia.WikipediaApp; import org.wikipedia.concurrency.SaneAsyncTask; import org.wikipedia.database.BaseDao; import org.wikipedia.database.async.AsyncConstant; import org.wikipedia.database.contract.UserOptionContract; import org.wikipedia.database.http.HttpRowDao; import org.wikipedia.theme.Theme; import org.wikipedia.useroption.UserOption; import org.wikipedia.useroption.sync.UserOptionContentResolver; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; public final class UserOptionDao extends BaseDao<UserOption> { public interface Callback<T> { void success(@Nullable T item); } @NonNull private static final String THEME_KEY = "userjs-app-pref-theme"; @NonNull private static final String FONT_SIZE_KEY = "userjs-app-pref-font-size"; @NonNull private static final List<String> SYNCED_OPTIONS = Arrays.asList(THEME_KEY, FONT_SIZE_KEY); @NonNull private static UserOptionDao INSTANCE = new UserOptionDao(); @NonNull private final HttpRowDao<UserOption, UserOptionRow> httpDao; public static UserOptionDao instance() { return INSTANCE; } public void theme(@NonNull Theme theme) { new UpsertTask(new UserOption(THEME_KEY, String.valueOf(theme.isLight()))).execute(); } public void theme(final Callback<Theme> callback) { new QueryTask(THEME_KEY) { @Override public void onFinish(UserOption result) { callback.success(result == null ? null : Boolean.valueOf(result.val()) ? Theme.LIGHT : Theme.DARK); } }.execute(); } public void fontSize(int size) { new UpsertTask(new UserOption(FONT_SIZE_KEY, String.valueOf(size))).execute(); } public void fontSize(final Callback<Integer> callback) { new QueryTask(FONT_SIZE_KEY) { @Override public void onFinish(UserOption result) { super.onFinish(result); callback.success(result == null ? null : (int) Float.parseFloat(result.val())); } }.execute(); } public synchronized void reconcileTransaction(@NonNull Collection<UserOption> rows) { for (UserOption row : rows) { if (SYNCED_OPTIONS.contains(row.key())) { httpDao.completeTransaction(new UserOptionRow(row)); upsert(row); } } // TODO: the user option sync adapter downloads all options from the service and should // delete rows deleted and synchronized rows present in the database but not in the // service. Alternatively, delete anything older than the service's oldest timestamp. } @NonNull public synchronized Collection<UserOptionRow> startTransaction() { Collection<UserOptionRow> rows = queryPendingTransactions(); httpDao.startTransaction(rows); return rows; } public synchronized void completeTransaction(@NonNull UserOptionRow row) { httpDao.completeTransaction(row); } public synchronized void failTransaction(@NonNull Collection<UserOptionRow> rows) { httpDao.failTransaction(rows); } @Override public synchronized void clear() { httpDao.clear(); super.clear(); } private synchronized void markUpserted(@NonNull UserOption row) { httpDao.markUpserted(new UserOptionRow(row)); upsert(row); } @NonNull private Collection<UserOptionRow> queryPendingTransactions() { String selection = Sql.SELECT_ROWS_PENDING_TRANSACTION; final String[] selectionArgs = null; final String order = null; Cursor cursor = client().select(UserOptionContract.HttpWithOption.URI, selection, selectionArgs, order); Collection<UserOptionRow> rows = new ArrayList<>(); try { while (cursor.moveToNext()) { rows.add(UserOptionRow.fromCursor(cursor)); } } finally { cursor.close(); } return rows; } private UserOptionDao() { super(WikipediaApp.getInstance().getDatabaseClient(UserOption.class)); httpDao = new HttpRowDao<>(WikipediaApp.getInstance().getDatabaseClient(UserOptionRow.class)); } private static class Sql { private static String SELECT_ROWS_PENDING_TRANSACTION = ":transactionIdCol == :noTransactionId" .replaceAll(":transactionIdCol", UserOptionContract.HttpWithOption.HTTP_TRANSACTION_ID.qualifiedName()) .replaceAll(":noTransactionId", String.valueOf(AsyncConstant.NO_TRANSACTION_ID)); } // TODO: replace AsyncTasks with SQLBrite. private class UpsertTask extends SaneAsyncTask<Void> { @NonNull private final UserOption row; UpsertTask(@NonNull UserOption row) { this.row = row; } @Override public Void performTask() throws Throwable { markUpserted(row); return null; } @Override public void onFinish(Void result) { super.onFinish(result); UserOptionContentResolver.requestManualUpload(); } } private class QueryTask extends SaneAsyncTask<UserOption> { private final String key; QueryTask(String key) { this.key = key; } @Override public UserOption performTask() throws Throwable { return queryPrimaryKey(new UserOption(key)); } } }