diff --git a/runtime/twodue/.cvsignore b/runtime/twodue/.cvsignore
new file mode 100644
index 00000000..0ac996b7
--- /dev/null
+++ b/runtime/twodue/.cvsignore
@@ -0,0 +1 @@
+twodue.war
diff --git a/runtime/twodue/build.xml b/runtime/twodue/build.xml
new file mode 100644
index 00000000..7a173802
--- /dev/null
+++ b/runtime/twodue/build.xml
@@ -0,0 +1,63 @@
+
+twodue which you'll probably need to know to properly
+ * configure your connection provider.
+ */
+ public static final String REPOSITORY_DB_IDENT = "twodue";
+
+ /**
+ * Creates the repository and opens the task database. A connection
+ * provider should be supplied that will be used to obtain the
+ * necessary database connection. The database identifier used to
+ * obtain our connection is documented by {@link
+ * #REPOSITORY_DB_IDENT}.
+ *
+ * @param provider a connection provider via which the repository will
+ * get its database connection.
+ */
+ public TaskRepository (ConnectionProvider provider)
+ throws PersistenceException
+ {
+ super(provider, REPOSITORY_DB_IDENT);
+
+ // make sure we can get our database connection
+ Connection conn = provider.getConnection(REPOSITORY_DB_IDENT);
+ provider.releaseConnection(REPOSITORY_DB_IDENT, conn);
+ }
+
+ // documented inherited
+ protected void createTables (Session session)
+ {
+ // create our table objects
+ _ttable = new Table(Task.class.getName(),
+ "TASKS", session, "TASK_ID", true);
+ }
+
+ /**
+ * Creates a task and inserts it into the repository.
+ *
+ * @return the newly created task.
+ */
+ public Task createTask (String summary, String category, String complexity,
+ int priority, String creator)
+ throws PersistenceException
+ {
+ // create and configure a task instance
+ Task task = new Task();
+ task.summary = summary;
+ task.category = category;
+ task.complexity = complexity;
+ task.priority = priority;
+ task.creator = creator;
+ createTask(task);
+ return task;
+ }
+
+ /**
+ * Inserts the supplied (properly populated) new task into the task
+ * repository.
+ */
+ public void createTask (final Task task)
+ throws PersistenceException
+ {
+ task.creation = new Date(System.currentTimeMillis());
+ execute(new Operation() {
+ public Object invoke (Connection conn, DatabaseLiaison liaison)
+ throws PersistenceException, SQLException
+ {
+ // insert the task into the entry table
+ _ttable.insert(task);
+ // update the taskId now that it's known
+ task.taskId = liaison.lastInsertedId(conn);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Loads up the specified task.
+ */
+ public Task loadTask (int taskId)
+ throws PersistenceException
+ {
+ return (Task)execute(new Operation () {
+ public Object invoke (Connection conn, DatabaseLiaison liaison)
+ throws PersistenceException, SQLException
+ {
+ String query = "where COMPLETOR IS NULL AND OWNER IS NULL " +
+ "ORDER BY CATEGORY, PRIORITY";
+ Cursor tc = _ttable.select(query);
+ return tc.toArrayList();
+ }
+ });
+ }
+
+ /**
+ * Loads up and returns all unowned, uncompleted tasks, ordered by
+ * priority.
+ */
+ public ArrayList loadTasks ()
+ throws PersistenceException
+ {
+ return loadTasks("where COMPLETOR IS NULL AND OWNER IS NULL " +
+ "ORDER BY PRIORITY DESC");
+ }
+
+ /**
+ * Loads up all owned tasks, ordered by priority.
+ */
+ public ArrayList loadOwnedTasks ()
+ throws PersistenceException
+ {
+ return loadTasks("where COMPLETOR IS NULL AND OWNER IS NOT NULL " +
+ "ORDER BY PRIORITY DESC");
+ }
+
+ /**
+ * Loads up and returns all completed tasks, sorted by completion
+ * date.
+ *
+ * @param start the offset into the sorted list of completed tasks to
+ * start returning tasks.
+ * @param limit the limit to the number of tasks to be returned, or -1
+ * if all completed should be returned.
+ */
+ public ArrayList loadCompletedTasks (int start, int limit)
+ throws PersistenceException
+ {
+ String query = "where COMPLETOR IS NOT NULL " +
+ "ORDER BY COMPLETION DESC LIMIT " + start;
+ if (limit != -1) {
+ query += (", " + limit);
+ }
+ return loadTasks(query);
+ }
+
+ /** Loads lists of tasks. */
+ protected ArrayList loadTasks (final String query)
+ throws PersistenceException
+ {
+ return (ArrayList)execute(new Operation () {
+ public Object invoke (Connection conn, DatabaseLiaison liaison)
+ throws PersistenceException, SQLException
+ {
+ Cursor tc = _ttable.select(query);
+ return tc.toArrayList();
+ }
+ });
+ }
+
+ /**
+ * Marks the specified task as completed by the specified user.
+ */
+ public void completeTask (final int taskId, final String completor)
+ throws PersistenceException
+ {
+ execute(new Operation () {
+ public Object invoke (Connection conn, DatabaseLiaison liaison)
+ throws PersistenceException, SQLException
+ {
+ String query = "update TASKS " +
+ "set COMPLETOR = ?, COMPLETION = ? where TASK_ID = ?";
+ PreparedStatement stmt = null;
+
+ try {
+ stmt = conn.prepareStatement(query);
+ stmt.setString(1, completor);
+ stmt.setDate(2, new Date(System.currentTimeMillis()));
+ stmt.setInt(3, taskId);
+ JDBCUtil.checkedUpdate(stmt, 1);
+
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Marks the specified task as owned by the specified user.
+ */
+ public void claimTask (final int taskId, final String owner)
+ throws PersistenceException
+ {
+ execute(new Operation () {
+ public Object invoke (Connection conn, DatabaseLiaison liaison)
+ throws PersistenceException, SQLException
+ {
+ String query = "update TASKS " +
+ "set OWNER = ? where TASK_ID = ?";
+ PreparedStatement stmt = null;
+
+ try {
+ stmt = conn.prepareStatement(query);
+ stmt.setString(1, owner);
+ stmt.setInt(2, taskId);
+ JDBCUtil.checkedUpdate(stmt, 1);
+
+ } finally {
+ JDBCUtil.close(stmt);
+ }
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Updates a task that was previously loaded from the repository.
+ */
+ public void updateTask (final Task task)
+ throws PersistenceException
+ {
+ execute(new Operation() {
+ public Object invoke (Connection conn, DatabaseLiaison liaison)
+ throws PersistenceException, SQLException
+ {
+ _ttable.update(task);
+ return null;
+ }
+ });
+ }
+
+ protected Table _ttable;
+}
diff --git a/runtime/twodue/src/java/com/samskivert/twodue/logic/UserLogic.java b/runtime/twodue/src/java/com/samskivert/twodue/logic/UserLogic.java
new file mode 100644
index 00000000..359a5119
--- /dev/null
+++ b/runtime/twodue/src/java/com/samskivert/twodue/logic/UserLogic.java
@@ -0,0 +1,38 @@
+//
+// $Id: UserLogic.java,v 1.1 2002/11/08 09:14:21 mdb Exp $
+
+package com.samskivert.twodue.logic;
+
+import com.samskivert.servlet.user.User;
+import com.samskivert.velocity.Application;
+import com.samskivert.velocity.InvocationContext;
+import com.samskivert.velocity.Logic;
+
+import com.samskivert.twodue.TwoDueApp;
+
+/**
+ * A base logic class for pages that require an authenticated user.
+ */
+public abstract class UserLogic implements Logic
+{
+ /**
+ * Logic classes should implement this method to perform their normal
+ * duties.
+ *
+ * @param ctx the context in which the request is being invoked.
+ * @param app the web application.
+ * @param user the user record for the authenticated user.
+ */
+ public abstract void invoke (
+ InvocationContext ctx, TwoDueApp app, User user)
+ throws Exception;
+
+ // documentation inherited from interface
+ public void invoke (Application app, InvocationContext ctx)
+ throws Exception
+ {
+ TwoDueApp tdapp = (TwoDueApp)app;
+ User user = tdapp.getUserManager().requireUser(ctx.getRequest());
+ invoke(ctx, tdapp, user);
+ }
+}
diff --git a/runtime/twodue/src/java/com/samskivert/twodue/logic/index.java b/runtime/twodue/src/java/com/samskivert/twodue/logic/index.java
new file mode 100644
index 00000000..ab0cef3e
--- /dev/null
+++ b/runtime/twodue/src/java/com/samskivert/twodue/logic/index.java
@@ -0,0 +1,164 @@
+//
+// $Id: index.java,v 1.1 2002/11/08 09:14:21 mdb Exp $
+
+package com.samskivert.twodue.logic;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import javax.servlet.http.HttpServletRequest;
+
+import com.samskivert.util.QuickSort;
+import com.samskivert.util.StringUtil;
+
+import com.samskivert.servlet.RedirectException;
+import com.samskivert.servlet.user.User;
+import com.samskivert.servlet.util.ParameterUtil;
+import com.samskivert.velocity.InvocationContext;
+
+import com.samskivert.twodue.Log;
+import com.samskivert.twodue.TwoDueApp;
+import com.samskivert.twodue.data.Task;
+
+/**
+ * Displays a summary out outstanding and completed tasks.
+ */
+public class index extends UserLogic
+{
+ public void invoke (InvocationContext ctx, TwoDueApp app, User user)
+ throws Exception
+ {
+ HttpServletRequest req = ctx.getRequest();
+
+ // display any message we've been asked to display
+ String msg = ParameterUtil.getParameter(req, "message", true);
+ if (!StringUtil.blank(msg)) {
+ ctx.put("error", msg);
+ }
+
+ // we use this to determine whether to show "complete" buttons for
+ // tasks
+ ctx.put("username", user.username);
+
+ // if they've submitted the form, we create a new task and stick
+ // it into the dataabse
+ if (ParameterUtil.parameterEquals(
+ ctx.getRequest(), "action", "create")) {
+ // set the creator from the username of the calling user
+ Task task = new Task();
+ task.creator = user.username;
+
+ // parse our fields
+ task.summary = ParameterUtil.requireParameter(
+ ctx.getRequest(), "summary", "index.error.missing_summary");
+ task.category = ParameterUtil.requireParameter(
+ ctx.getRequest(), "category",
+ "index.error.missing_category");
+ task.complexity = ParameterUtil.requireParameter(
+ ctx.getRequest(), "complexity",
+ "index.error.missing_complexity");
+ task.priority = ParameterUtil.requireIntParameter(
+ ctx.getRequest(), "priority",
+ "index.error.invalid_priority");
+
+ // insert the task into the repository
+ app.getRepository().createTask(task);
+
+ // flip back to this same page minus our query parameters to
+ // clear out the creation form
+ throw new RedirectException(
+ "index.wm?msg=index.message.task_created");
+
+ } else if (ParameterUtil.parameterEquals(
+ ctx.getRequest(), "action", "complete")) {
+ int taskId = ParameterUtil.requireIntParameter(
+ ctx.getRequest(), "task", "index.error.missing_taskid");
+ app.getRepository().completeTask(taskId, user.username);
+
+ // let the user know we updated the database
+ ctx.put("error", "index.message.task_completed");
+
+ } else if (ParameterUtil.parameterEquals(
+ ctx.getRequest(), "action", "claim")) {
+ int taskId = ParameterUtil.requireIntParameter(
+ ctx.getRequest(), "task", "index.error.missing_taskid");
+ app.getRepository().claimTask(taskId, user.username);
+
+ // let the user know we updated the database
+ ctx.put("error", "index.message.task_claimed");
+ }
+
+ // load up outstanding tasks and break them down by complexity
+ String expand = ParameterUtil.getParameter(req, "expand", false);
+ ArrayList tasks = app.getRepository().loadTasks();
+ CatList[] xtasks = categorize(tasks, expand, new Categorizer() {
+ public String category (Task task) {
+ return task.complexity;
+ }
+ });
+ ctx.put("xtasks", xtasks);
+
+ // load up owned tasks and break them down by owner
+ tasks = app.getRepository().loadOwnedTasks();
+ CatList[] otasks = categorize(tasks, null, new Categorizer() {
+ public String category (Task task) {
+ return task.owner;
+ }
+ });
+ ctx.put("otasks", otasks);
+
+ // load up recently completed tasks
+ tasks = app.getRepository().loadCompletedTasks(0, 6);
+ ctx.put("dtasks", tasks);
+ }
+
+ protected static class CatList
+ implements Comparable
+ {
+ public String name;
+ public ArrayList tasks;
+ public int pruned;
+ public int compareTo (Object other)
+ {
+ return name.compareTo(((CatList)other).name);
+ }
+ }
+
+ protected static interface Categorizer
+ {
+ public String category (Task task);
+ }
+
+ protected CatList[] categorize (
+ ArrayList tasks, String expand, Categorizer catter)
+ {
+ HashMap cmap = new HashMap();
+ int tcount = tasks.size();
+ for (int ii = 0; ii < tcount; ii++) {
+ Task task = (Task)tasks.get(ii);
+ String category = catter.category(task);
+ CatList clist = (CatList)cmap.get(category);
+ if (clist == null) {
+ clist = new CatList();
+ clist.name = category;
+ clist.tasks = new ArrayList();
+ cmap.put(category, clist);
+ }
+ if (expand == null || clist.tasks.size() < 2 ||
+ (expand.equals(category))) {
+ clist.tasks.add(task);
+ } else {
+ clist.pruned++;
+ }
+ }
+
+ CatList[] ctasks = new CatList[cmap.size()];
+ Iterator iter = cmap.values().iterator();
+ for (int ii = 0; iter.hasNext(); ii++) {
+ ctasks[ii] = (CatList)iter.next();
+ }
+ QuickSort.sort(ctasks);
+
+ return ctasks;
+ }
+}
diff --git a/runtime/twodue/src/sql/tasks-schema.sql b/runtime/twodue/src/sql/tasks-schema.sql
new file mode 100644
index 00000000..bf7ca300
--- /dev/null
+++ b/runtime/twodue/src/sql/tasks-schema.sql
@@ -0,0 +1,72 @@
+/**
+ * $Id: tasks-schema.sql,v 1.1 2002/11/08 09:14:21 mdb Exp $
+ *
+ * Schema for the Two Due tasks and notes table.
+ */
+
+drop table if exists TASKS;
+
+/**
+ * Contains basic data for every task in the system.
+ */
+CREATE TABLE TASKS
+(
+ /**
+ * A unique identifier for this task.
+ */
+ TASK_ID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+
+ /**
+ * The summary description of this task.
+ */
+ SUMMARY VARCHAR(255) NOT NULL,
+
+ /**
+ * The comma separated list of categories occupied by this task.
+ */
+ CATEGORY VARCHAR(255) NOT NULL,
+
+ /**
+ * The complexity identifier for this task.
+ */
+ COMPLEXITY VARCHAR(32) NOT NULL,
+
+ /**
+ * The priority of this task.
+ */
+ PRIORITY INTEGER UNSIGNED NOT NULL,
+
+ /**
+ * The user to which this task is currently assigned.
+ */
+ OWNER VARCHAR(255),
+
+ /**
+ * The name of the creator of this task.
+ */
+ CREATOR VARCHAR(255) NOT NULL,
+
+ /**
+ * The time of creation of this task.
+ */
+ CREATION DATE NOT NULL,
+
+ /**
+ * The name of the completor(s) of this task.
+ */
+ COMPLETOR VARCHAR(255),
+
+ /**
+ * The time of completion of this task.
+ */
+ COMPLETION DATE,
+
+ /**
+ * Defines our table keys.
+ */
+ PRIMARY KEY (TASK_ID),
+ KEY (CATEGORY),
+ KEY (PRIORITY),
+ KEY (CREATION),
+ KEY (COMPLETION)
+);
diff --git a/runtime/twodue/web/app_footer.wm b/runtime/twodue/web/app_footer.wm
new file mode 100644
index 00000000..ecd4c8b4
--- /dev/null
+++ b/runtime/twodue/web/app_footer.wm
@@ -0,0 +1,3 @@
+Two Due:
+$i18n.xlate("app_footer.summary_link") |
+$i18n.xlate("app_footer.account_link")
diff --git a/runtime/twodue/web/edit.wm b/runtime/twodue/web/edit.wm
new file mode 100644
index 00000000..8cbdef75
--- /dev/null
+++ b/runtime/twodue/web/edit.wm
@@ -0,0 +1,51 @@
+#set ($title = $i18n.xlate("edit.title"))
+#import ("/header.wm")
+
+
+ +
+ +#import ("/footer.wm") diff --git a/runtime/twodue/web/footer.wm b/runtime/twodue/web/footer.wm new file mode 100644 index 00000000..989f5a1d --- /dev/null +++ b/runtime/twodue/web/footer.wm @@ -0,0 +1,16 @@ ++
| +#import ("app_footer.wm") + | + +