mirror of https://github.com/tasks/tasks
Refactored GtasksTaskListUpdater into OrderedListUpdater to create a new child class
parent
d6345b4b32
commit
57f3a70089
@ -0,0 +1,288 @@
|
|||||||
|
package com.todoroo.astrid.subtasks;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||||
|
import com.todoroo.andlib.data.Property.LongProperty;
|
||||||
|
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities;
|
||||||
|
import com.todoroo.astrid.api.Filter;
|
||||||
|
import com.todoroo.astrid.core.PluginServices;
|
||||||
|
import com.todoroo.astrid.data.Metadata;
|
||||||
|
import com.todoroo.astrid.data.Task;
|
||||||
|
|
||||||
|
abstract public class OrderedListUpdater<LIST> {
|
||||||
|
|
||||||
|
public OrderedListUpdater() {
|
||||||
|
DependencyInjectionService.getInstance().inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OrderedListIterator {
|
||||||
|
public void processTask(long taskId, Metadata metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- abstract
|
||||||
|
|
||||||
|
abstract protected Metadata getTaskMetadata(LIST list, long taskId);
|
||||||
|
|
||||||
|
abstract protected IntegerProperty indentProperty();
|
||||||
|
|
||||||
|
abstract protected LongProperty orderProperty();
|
||||||
|
|
||||||
|
abstract protected LongProperty parentProperty();
|
||||||
|
|
||||||
|
abstract protected void iterateThroughList(Filter filter, LIST list, OrderedListIterator iterator);
|
||||||
|
|
||||||
|
abstract protected Metadata createEmptyMetadata(LIST list, long taskId);
|
||||||
|
|
||||||
|
/** @param list */
|
||||||
|
protected void beforeIndent(LIST list) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list
|
||||||
|
* @param taskId
|
||||||
|
* @param metadata
|
||||||
|
* @param indent
|
||||||
|
* @param order
|
||||||
|
*/
|
||||||
|
protected void beforeSaveIndent(LIST list, long taskId, Metadata metadata, int indent, int order) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- task indenting
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indent a task and all its children
|
||||||
|
*/
|
||||||
|
public void indent(final Filter filter, final LIST list, final long targetTaskId, final int delta) {
|
||||||
|
if(list == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
beforeIndent(list);
|
||||||
|
|
||||||
|
final AtomicInteger targetTaskIndent = new AtomicInteger(-1);
|
||||||
|
final AtomicInteger previousIndent = new AtomicInteger(-1);
|
||||||
|
final AtomicLong previousTask = new AtomicLong(-1);
|
||||||
|
|
||||||
|
iterateThroughList(filter, list, new OrderedListIterator() {
|
||||||
|
@Override
|
||||||
|
public void processTask(long taskId, Metadata metadata) {
|
||||||
|
int indent = metadata.getValue(indentProperty());
|
||||||
|
|
||||||
|
if(targetTaskId == taskId) {
|
||||||
|
// if indenting is warranted, indent me and my children
|
||||||
|
if(indent + delta <= previousIndent.get() + 1 && indent + delta >= 0) {
|
||||||
|
targetTaskIndent.set(indent);
|
||||||
|
metadata.setValue(indentProperty(), indent + delta);
|
||||||
|
|
||||||
|
if(parentProperty() != null) {
|
||||||
|
long newParent = computeNewParent(filter, list,
|
||||||
|
taskId, indent + delta - 1);
|
||||||
|
if (newParent == taskId)
|
||||||
|
metadata.setValue(parentProperty(), Task.NO_ID);
|
||||||
|
else
|
||||||
|
metadata.setValue(parentProperty(), newParent);
|
||||||
|
}
|
||||||
|
saveAndUpdateModifiedDate(metadata, taskId);
|
||||||
|
}
|
||||||
|
} else if(targetTaskIndent.get() > -1) {
|
||||||
|
// found first task that is not beneath target
|
||||||
|
if(indent <= targetTaskIndent.get())
|
||||||
|
targetTaskIndent.set(-1);
|
||||||
|
else {
|
||||||
|
metadata.setValue(indentProperty(), indent + delta);
|
||||||
|
saveAndUpdateModifiedDate(metadata, taskId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
previousIndent.set(indent);
|
||||||
|
previousTask.set(taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to iterate through a list and compute a new parent for the target task
|
||||||
|
* based on the target parent's indent
|
||||||
|
* @param list
|
||||||
|
* @param targetTaskId
|
||||||
|
* @param newIndent
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private long computeNewParent(Filter filter, LIST list, long targetTaskId, int targetParentIndent) {
|
||||||
|
final AtomicInteger desiredParentIndent = new AtomicInteger(targetParentIndent);
|
||||||
|
final AtomicLong targetTask = new AtomicLong(targetTaskId);
|
||||||
|
final AtomicLong lastPotentialParent = new AtomicLong(-1);
|
||||||
|
final AtomicBoolean computedParent = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
iterateThroughList(filter, list, new OrderedListIterator() {
|
||||||
|
@Override
|
||||||
|
public void processTask(long taskId, Metadata metadata) {
|
||||||
|
if (targetTask.get() == taskId) {
|
||||||
|
computedParent.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int indent = metadata.getValue(indentProperty());
|
||||||
|
if (!computedParent.get() && indent == desiredParentIndent.get()) {
|
||||||
|
lastPotentialParent.set(taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lastPotentialParent.get() == -1) return Task.NO_ID;
|
||||||
|
return lastPotentialParent.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- task moving
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a task and all its children to the position right above
|
||||||
|
* taskIdToMoveto. Will change the indent level to match taskIdToMoveTo.
|
||||||
|
*
|
||||||
|
* @param newTaskId task we will move above. if -1, moves to end of list
|
||||||
|
*/
|
||||||
|
public void moveTo(Filter filter, LIST list, final long targetTaskId,
|
||||||
|
final long moveBeforeTaskId) {
|
||||||
|
if(list == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Node root = buildTreeModel(filter, list);
|
||||||
|
Node target = findNode(root, targetTaskId);
|
||||||
|
|
||||||
|
if(target != null && target.parent != null) {
|
||||||
|
if(moveBeforeTaskId == -1) {
|
||||||
|
target.parent.children.remove(target);
|
||||||
|
root.children.add(target);
|
||||||
|
target.parent = root;
|
||||||
|
} else {
|
||||||
|
Node sibling = findNode(root, moveBeforeTaskId);
|
||||||
|
if(sibling != null) {
|
||||||
|
int index = sibling.parent.children.indexOf(sibling);
|
||||||
|
target.parent.children.remove(target);
|
||||||
|
sibling.parent.children.add(index, target);
|
||||||
|
target.parent = sibling.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverseTreeAndWriteValues(list, root, new AtomicLong(0), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Node {
|
||||||
|
public final long taskId;
|
||||||
|
public Node parent;
|
||||||
|
public final ArrayList<Node> children = new ArrayList<Node>();
|
||||||
|
|
||||||
|
public Node(long taskId, Node parent) {
|
||||||
|
this.taskId = taskId;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void traverseTreeAndWriteValues(LIST list, Node node, AtomicLong order, int indent) {
|
||||||
|
if(node.taskId != -1) {
|
||||||
|
Metadata metadata = getTaskMetadata(list, node.taskId);
|
||||||
|
if(metadata == null)
|
||||||
|
metadata = createEmptyMetadata(list, node.taskId);
|
||||||
|
metadata.setValue(orderProperty(), order.getAndIncrement());
|
||||||
|
metadata.setValue(indentProperty(), indent);
|
||||||
|
if(parentProperty() != null)
|
||||||
|
metadata.setValue(parentProperty(), node.parent.taskId);
|
||||||
|
saveAndUpdateModifiedDate(metadata, node.taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Node child : node.children) {
|
||||||
|
traverseTreeAndWriteValues(list, child, order, indent + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Node findNode(Node node, long taskId) {
|
||||||
|
if(node.taskId == taskId)
|
||||||
|
return node;
|
||||||
|
for(Node child : node.children) {
|
||||||
|
Node found = findNode(child, taskId);
|
||||||
|
if(found != null)
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Node buildTreeModel(Filter filter, LIST list) {
|
||||||
|
final Node root = new Node(-1, null);
|
||||||
|
final AtomicInteger previoustIndent = new AtomicInteger(-1);
|
||||||
|
final AtomicReference<Node> currentNode = new AtomicReference<Node>(root);
|
||||||
|
|
||||||
|
iterateThroughList(filter, list, new OrderedListIterator() {
|
||||||
|
@Override
|
||||||
|
public void processTask(long taskId, Metadata metadata) {
|
||||||
|
int indent = metadata.getValue(indentProperty());
|
||||||
|
|
||||||
|
int previousIndentValue = previoustIndent.get();
|
||||||
|
if(indent == previousIndentValue) { // sibling
|
||||||
|
Node parent = currentNode.get().parent;
|
||||||
|
currentNode.set(new Node(taskId, parent));
|
||||||
|
parent.children.add(currentNode.get());
|
||||||
|
} else if(indent > previousIndentValue) { // child
|
||||||
|
Node parent = currentNode.get();
|
||||||
|
currentNode.set(new Node(taskId, parent));
|
||||||
|
parent.children.add(currentNode.get());
|
||||||
|
} else { // in a different tree
|
||||||
|
Node node = currentNode.get().parent;
|
||||||
|
for(int i = indent; i < previousIndentValue; i++)
|
||||||
|
node = node.parent;
|
||||||
|
if(node == null)
|
||||||
|
node = root;
|
||||||
|
currentNode.set(new Node(taskId, node));
|
||||||
|
node.children.add(currentNode.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
previoustIndent.set(indent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Task taskContainer = new Task();
|
||||||
|
|
||||||
|
protected void saveAndUpdateModifiedDate(Metadata metadata, long taskId) {
|
||||||
|
if(metadata.getSetValues().size() == 0)
|
||||||
|
return;
|
||||||
|
PluginServices.getMetadataService().save(metadata);
|
||||||
|
taskContainer.setId(taskId);
|
||||||
|
taskContainer.setValue(Task.MODIFICATION_DATE, DateUtilities.now());
|
||||||
|
taskContainer.setValue(Task.DETAILS_DATE, DateUtilities.now());
|
||||||
|
PluginServices.getTaskService().save(taskContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- utility
|
||||||
|
|
||||||
|
public void debugPrint(Filter filter, LIST list) {
|
||||||
|
iterateThroughList(filter, list, new OrderedListIterator() {
|
||||||
|
public void processTask(long taskId, Metadata metadata) {
|
||||||
|
System.err.format("id %d: order %d, indent:%d, parent:%d\n", taskId, //$NON-NLS-1$
|
||||||
|
metadata.getValue(orderProperty()),
|
||||||
|
metadata.getValue(indentProperty()),
|
||||||
|
parentProperty() == null ? -1 : metadata.getValue(parentProperty()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("nls")
|
||||||
|
public void debugPrint(Node root, int depth) {
|
||||||
|
for(int i = 0; i < depth; i++) System.err.print(" + ");
|
||||||
|
System.err.format("%03d", root.taskId);
|
||||||
|
System.err.print("\n");
|
||||||
|
|
||||||
|
for(int i = 0; i < root.children.size(); i++)
|
||||||
|
debugPrint(root.children.get(i), depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package com.todoroo.astrid.subtasks;
|
||||||
|
|
||||||
|
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||||
|
import com.todoroo.andlib.data.Property.LongProperty;
|
||||||
|
import com.todoroo.andlib.data.TodorooCursor;
|
||||||
|
import com.todoroo.andlib.service.Autowired;
|
||||||
|
import com.todoroo.andlib.sql.Criterion;
|
||||||
|
import com.todoroo.andlib.sql.Query;
|
||||||
|
import com.todoroo.astrid.api.Filter;
|
||||||
|
import com.todoroo.astrid.data.Metadata;
|
||||||
|
import com.todoroo.astrid.data.Task;
|
||||||
|
import com.todoroo.astrid.service.MetadataService;
|
||||||
|
import com.todoroo.astrid.service.TaskService;
|
||||||
|
|
||||||
|
public class SubtasksUpdater extends OrderedListUpdater<Long> {
|
||||||
|
|
||||||
|
private static final String METADATA_ID = "mdi"; //$NON-NLS-1$
|
||||||
|
|
||||||
|
@Autowired MetadataService metadataService;
|
||||||
|
@Autowired TaskService taskService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IntegerProperty indentProperty() {
|
||||||
|
return SubtasksMetadata.INDENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LongProperty orderProperty() {
|
||||||
|
return SubtasksMetadata.ORDER;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected LongProperty parentProperty() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Metadata getTaskMetadata(Long list, long taskId) {
|
||||||
|
TodorooCursor<Metadata> cursor = metadataService.query(Query.select(Metadata.PROPERTIES).where(
|
||||||
|
Criterion.and(
|
||||||
|
Metadata.TASK.eq(taskId),
|
||||||
|
Metadata.KEY.eq(SubtasksMetadata.METADATA_KEY),
|
||||||
|
SubtasksMetadata.LIST_ID.eq(list))));
|
||||||
|
try {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
if(cursor.isAfterLast())
|
||||||
|
return null;
|
||||||
|
return new Metadata(cursor);
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Metadata createEmptyMetadata(Long list, long taskId) {
|
||||||
|
Metadata m = new Metadata();
|
||||||
|
m.setValue(Metadata.TASK, taskId);
|
||||||
|
m.setValue(Metadata.KEY, SubtasksMetadata.METADATA_KEY);
|
||||||
|
m.setValue(SubtasksMetadata.LIST_ID, list);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void iterateThroughList(Filter filter, Long list, OrderedListIterator iterator) {
|
||||||
|
TodorooCursor<Task> cursor = taskService.query(Query.select(Task.ID,
|
||||||
|
Metadata.ID.as(METADATA_ID), Metadata.TASK, Metadata.KEY, SubtasksMetadata.INDENT,
|
||||||
|
SubtasksMetadata.ORDER).withQueryTemplate(filter.sqlQuery));
|
||||||
|
TodorooCursor<Metadata> metadataCursor = new TodorooCursor<Metadata>(cursor.getCursor(),
|
||||||
|
cursor.getProperties());
|
||||||
|
Metadata metadata = new Metadata();
|
||||||
|
try {
|
||||||
|
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
||||||
|
metadata.readFromCursor(metadataCursor);
|
||||||
|
metadata.setId(cursor.getLong(cursor.getColumnIndex(METADATA_ID)));
|
||||||
|
iterator.processTask(cursor.get(Task.ID), metadata);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue