package de.blau.android.tasks;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.locks.ReentrantLock;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import de.blau.android.R;
import de.blau.android.osm.BoundingBox;
import de.blau.android.util.SavingHelper;
import de.blau.android.util.Snack;
import de.blau.android.util.rtree.BoundedObject;
import de.blau.android.util.rtree.RTree;
/**
* Storage for bugs and the corresponding coverage bounding boxes
* @author simon
*
*/
public class TaskStorage implements Serializable {
private static final long serialVersionUID = 5L;
private final static String DEBUG_TAG = TaskStorage.class.getSimpleName();
private int newId=-1;
private RTree tasks;
private RTree boxes;
private transient boolean dirty = true;
/**
* when reading state lockout writing/reading
*/
private transient ReentrantLock readingLock = new ReentrantLock();
public final static String FILENAME = "tasks.res";
private transient SavingHelper<TaskStorage> savingHelper = new SavingHelper<TaskStorage>();
public TaskStorage() {
reset();
dirty = false;
}
public synchronized void reset() {
tasks = new RTree(30,100);
boxes = new RTree(2,20);
dirty = true;
}
public synchronized void add(Task b) {
tasks.insert(b);
dirty = true;
}
public synchronized void add(BoundingBox b) {
boxes.insert(b);
dirty = true;
}
public synchronized void delete(Task b) {
tasks.remove(b);
dirty = true;
}
public synchronized void delete(BoundingBox b) {
boxes.remove(b);
dirty = true;
}
/**
* Returns true if there is an bug with the same id in the same location
* (bug location is immutable so shoudn't be a problem)
* @param b
* @return
*/
public boolean contains(Task b) {
Collection<BoundedObject> queryResult = new ArrayList<BoundedObject>();
tasks.query(queryResult, b.getLon(), b.getLat());
Log.d(DEBUG_TAG,"candidates for contain " + queryResult.size());
for (BoundedObject bo:queryResult) {
if (b instanceof Note && bo instanceof Note && b.getId() == ((Task)bo).getId()) {
return true;
} else if (b instanceof OsmoseBug && bo instanceof OsmoseBug && b.getId() == ((Task)bo).getId()) {
return true;
}
}
return false;
}
/**
* Return all bugs
* @return
*/
public ArrayList<Task>getTasks() {
Collection<BoundedObject> queryResult = new ArrayList<BoundedObject>();
tasks.query(queryResult);
Log.d(DEBUG_TAG,"getTasks result count " + queryResult.size());
ArrayList<Task>result = new ArrayList<Task>();
for (BoundedObject bo:queryResult) {
result.add((Task)bo);
}
return result;
}
public ArrayList<Task>getTasks(BoundingBox box) {
Collection<BoundedObject> queryResult = new ArrayList<BoundedObject>();
tasks.query(queryResult,box.getBounds());
Log.d(DEBUG_TAG,"getTasks result count " + queryResult.size());
ArrayList<Task>result = new ArrayList<Task>();
for (BoundedObject bo:queryResult) {
result.add((Task)bo);
}
return result;
}
public boolean isEmpty() {
// TODO Auto-generated method stub
return tasks.count() == 0;
}
/**
* Stores the current storage data to the default storage file
* @param ctx TODO
* @throws IOException
*/
public synchronized void writeToFile(Context ctx) throws IOException {
if (!dirty) {
Log.i(DEBUG_TAG, "storage not dirty, skipping save");
return;
}
if (readingLock.tryLock()) {
try {
// TODO this doesn't really help with error conditions need to throw exception
if (savingHelper.save(ctx, FILENAME, this, true)) {
dirty = false;
} else {
// this is essentially catastrophic and can only happen if something went really wrong
// running out of memory or disk, or HW failure
if (ctx != null && ctx instanceof Activity) {
Snack.barError((Activity)ctx, R.string.toast_statesave_failed);
}
}
} finally {
readingLock.unlock();
}
} else {
Log.i(DEBUG_TAG, "bug state being read, skipping save");
}
}
/**
* Loads the storage data from the default storage file
* NOTE: lock is acquired in logic before this is called
*/
public synchronized boolean readFromFile(Context context) {
try{
readingLock.lock();
TaskStorage newStorage = savingHelper.load(context, FILENAME, true);
if (newStorage != null) {
Log.d(DEBUG_TAG, "read saved state");
tasks = newStorage.tasks;
boxes = newStorage.boxes;
dirty = false; // data was just read, i.e. memory and file are in sync
return true;
} else {
Log.d(DEBUG_TAG, "saved state null");
return false;
}
} finally {
readingLock.unlock();
}
}
public void setDirty() {
dirty = true;
}
public String toString() {
return "task r-tree: " + tasks.count() + " boxes r-tree " + boxes.count();
}
public long getNextId() {
return newId--;
}
public ArrayList<BoundingBox> getBoundingBoxes() {
Collection<BoundedObject> queryResult = new ArrayList<BoundedObject>();
boxes.query(queryResult);
ArrayList<BoundingBox>result = new ArrayList<BoundingBox>();
for (BoundedObject bo:queryResult) {
result.add((BoundingBox)bo);
}
return result;
}
public ArrayList<BoundingBox> getBoundingBoxes(BoundingBox box) {
Collection<BoundedObject> queryResult = new ArrayList<BoundedObject>();
boxes.query(queryResult,box.getBounds());
Log.d(DEBUG_TAG,"getBoundingBoxes result count " + queryResult.size());
ArrayList<BoundingBox>result = new ArrayList<BoundingBox>();
for (BoundedObject bo:queryResult) {
result.add((BoundingBox)bo);
}
return result;
}
/**
* Check for changes
* @return
*/
public boolean hasChanges() {
Collection<BoundedObject> queryResult = new ArrayList<BoundedObject>();
tasks.query(queryResult);
for (BoundedObject b:queryResult) {
if (((Task)b).hasBeenChanged()) {
return true;
}
}
return false;
}
}