package com.raizlabs.android.dbflow.runtime; import android.os.Looper; import com.raizlabs.android.dbflow.config.DatabaseDefinition; import com.raizlabs.android.dbflow.config.FlowLog; import com.raizlabs.android.dbflow.config.FlowManager; import com.raizlabs.android.dbflow.structure.Model; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; import com.raizlabs.android.dbflow.structure.database.transaction.ProcessModelTransaction; import com.raizlabs.android.dbflow.structure.database.transaction.Transaction; import java.util.ArrayList; import java.util.Collection; /** * Description: This queue will bulk save items added to it when it gets access to the DB. It should only exist as one entity. * It will save the {@link #MODEL_SAVE_SIZE} at a time or more only when the limit is reached. It will not */ public class DBBatchSaveQueue extends Thread { /** * Once the queue size reaches 50 or larger, the thread will be interrupted and we will batch save the models. */ private static final int MODEL_SAVE_SIZE = 50; /** * The default time that it will awake the save queue thread to check if any models are still waiting to be saved */ private static final int sMODEL_SAVE_CHECK_TIME = 30000; /** * Tells how many items to save at a time. This can be set using {@link #setModelSaveSize(int)} */ private int modelSaveSize = MODEL_SAVE_SIZE; /** * Sets the time we check periodically for leftover DB objects in our queue to save. */ private long modelSaveCheckTime = sMODEL_SAVE_CHECK_TIME; /** * The list of DB objects that we will save here */ private final ArrayList<Object> models; /** * If true, this queue will quit. */ private boolean isQuitting = false; private Transaction.Error errorListener; private Transaction.Success successListener; private Runnable emptyTransactionListener; private DatabaseDefinition databaseDefinition; /** * Creates a new instance of this class to batch save DB object classes. */ DBBatchSaveQueue(DatabaseDefinition databaseDefinition) { super("DBBatchSaveQueue"); this.databaseDefinition = databaseDefinition; models = new ArrayList<>(); } /** * Sets how many models to save at a time in this queue. * Increase it for larger batches, but slower recovery time. * Smaller the batch, the more time it takes to save overall. */ public void setModelSaveSize(int mModelSaveSize) { this.modelSaveSize = mModelSaveSize; } /** * Sets how long, in millis that this queue will check for leftover DB objects that have not been saved yet. * The default is {@link #sMODEL_SAVE_CHECK_TIME} * * @param time The time, in millis that queue automatically checks for leftover DB objects in this queue. */ public void setModelSaveCheckTime(long time) { this.modelSaveCheckTime = time; } /** * Listener for errors in each batch {@link Transaction}. Called from the DBBatchSaveQueue thread. * * @param errorListener The listener to use. */ public void setErrorListener(Transaction.Error errorListener) { this.errorListener = errorListener; } /** * Listener for batch updates. Called from the DBBatchSaveQueue thread. * * @param successListener The listener to get notified when changes are successful. */ public void setSuccessListener(Transaction.Success successListener) { this.successListener = successListener; } /** * Listener for when there is no work done. Called from the DBBatchSaveQueue thread. * * @param emptyTransactionListener The listener to get notified when the save queue thread ran but was empty. */ public void setEmptyTransactionListener(Runnable emptyTransactionListener) { this.emptyTransactionListener = emptyTransactionListener; } @SuppressWarnings("unchecked") @Override public void run() { super.run(); Looper.prepare(); android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); while (true) { final ArrayList<Object> tmpModels; synchronized (models) { tmpModels = new ArrayList<>(models); models.clear(); } if (tmpModels.size() > 0) { databaseDefinition.beginTransactionAsync( new ProcessModelTransaction.Builder(modelSaver) .addAll(tmpModels) .build()) .success(successCallback) .error(errorCallback) .build() .execute(); } else if (emptyTransactionListener != null) { emptyTransactionListener.run(); } try { //sleep, and then check for leftovers Thread.sleep(modelSaveCheckTime); } catch (InterruptedException e) { FlowLog.log(FlowLog.Level.I, "DBRequestQueue Batch interrupted to start saving"); } if (isQuitting) { return; } } } /** * Will cause the queue to wake from sleep and handle it's current list of items. */ public void purgeQueue() { interrupt(); } /** * Adds an object to this queue. */ public void add(final Object inModel) { synchronized (models) { models.add(inModel); if (models.size() > modelSaveSize) { interrupt(); } } } /** * Adds a {@link java.util.Collection} of DB objects to this queue */ public void addAll(final Collection<Object> list) { synchronized (models) { models.addAll(list); if (models.size() > modelSaveSize) { interrupt(); } } } /** * Adds a {@link java.util.Collection} of class that extend Object to this queue */ public void addAll2(final Collection<?> list) { synchronized (models) { models.addAll(list); if (models.size() > modelSaveSize) { interrupt(); } } } /** * Removes a DB object from this queue before it is processed. */ public void remove(final Object outModel) { synchronized (models) { models.remove(outModel); } } /** * Removes a {@link java.util.Collection} of DB object from this queue * before it is processed. */ public void removeAll(final Collection<Object> outCollection) { synchronized (models) { models.removeAll(outCollection); } } /** * Removes a {@link java.util.Collection} of DB objects from this queue * before it is processed. */ public void removeAll2(final Collection<?> outCollection) { synchronized (models) { models.removeAll(outCollection); } } /** * Quits this queue after it sleeps for the {@link #modelSaveCheckTime} */ public void quit() { isQuitting = true; } private final ProcessModelTransaction.ProcessModel modelSaver = new ProcessModelTransaction.ProcessModel() { @Override public void processModel(Object model, DatabaseWrapper wrapper) { if (model instanceof Model) { ((Model) model).save(); } else if (model != null) { Class modelClass = model.getClass(); //noinspection unchecked FlowManager.getModelAdapter(modelClass).save(model); } } }; private final Transaction.Success successCallback = new Transaction.Success() { @Override public void onSuccess(Transaction transaction) { if (successListener != null) { successListener.onSuccess(transaction); } } }; private final Transaction.Error errorCallback = new Transaction.Error() { @Override public void onError(Transaction transaction, Throwable error) { if (errorListener != null) { errorListener.onError(transaction, error); } } }; }