Index: trunk/ippTools/src/Makefile.am
===================================================================
--- trunk/ippTools/src/Makefile.am	(revision 9264)
+++ trunk/ippTools/src/Makefile.am	(revision 9271)
@@ -4,4 +4,5 @@
 	p0tool \
 	p2tool\
+	p3tool\
 	dettool \
 	pzgetexp \
@@ -19,4 +20,5 @@
 	p1tool.h \
 	p2tool.h \
+	p3tool.h \
 	dettool.h \
 	pxinject.h
@@ -58,4 +60,10 @@
     p2toolConfig.c
 
+p3tool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS) $(pxtools_CFLAGS)
+p3tool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+p3tool_SOURCES = \
+    p3tool.c \
+    p3toolConfig.c
+
 pxadmin_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS) $(pxtools_CFLAGS)
 pxadmin_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
Index: trunk/ippTools/src/camtool.c
===================================================================
--- trunk/ippTools/src/camtool.c	(revision 9271)
+++ trunk/ippTools/src/camtool.c	(revision 9271)
@@ -0,0 +1,177 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "p3tool.h"
+
+static bool pendingexpMode(pxConfig *config);
+static bool pendingimfileMode(pxConfig *config);
+static bool addprocessedimfileMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = p3toolConfig(NULL, argc, argv);
+
+    switch (config->mode) {
+        MODECASE(P3TOOL_MODE_PENDINGEXP,      pendingexpMode);
+        MODECASE(P3TOOL_MODE_PENDINGIMFILE,   pendingimfileMode);
+        MODECASE(P3TOOL_MODE_ADDPROCESSEDIMFILE, addprocessedimfileMode);
+        default:
+            psAbort(argv[0], "invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psFree(config);
+    pmConfigDone();
+    psErrorStackPrint(stderr, "\n");
+    psLibFinalize();
+
+    exit(EXIT_FAILURE);
+}
+
+static bool pendingexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // return only exps that:
+    // are not in rawScienceExp 
+    // are not in rawDetrendExp 
+    // have ALL of their imfiles in rawImfile (by count)
+    // and have no associated imfiles left in newImfile
+
+    char *query =
+        "SELECT newExp.*"
+        " FROM newExp"
+        " LEFT JOIN newImfile USING(exp_tag)"
+        " LEFT JOIN rawScienceExp USING(exp_tag)"
+        " LEFT JOIN rawDetrendExp USING(exp_tag)"
+        " WHERE"
+        "   newExp.exp_tag IS NOT NULL"
+        "   AND newImfile.exp_tag IS NULL"
+        "   AND rawScienceExp.exp_tag IS NULL"
+        "   AND newExp.imfiles ="
+        "   (SELECT COUNT(exp_tag) FROM rawImfile"
+        "       WHERE rawImfile.exp_tag = newExp.exp_tag)"
+        ;
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no pending newExp rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "newImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool pendingimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // select newImfiles that:
+    // exp_tag is in newExp
+    // don't have their exp_tag in rawScienceExp 
+    // don't have their exp_tag in rawDetrendExp
+    // XXX having the same exp_tag in newExp and raw*Exp is probably an error
+    // that should be checked for
+    char *query =
+        "SELECT newImfile.* FROM newImfile"
+        " LEFT JOIN newExp USING(exp_tag)"
+        " LEFT JOIN rawScienceExp USING(exp_tag)"
+        " LEFT JOIN rawDetrendExp USING (exp_tag)"
+        " WHERE newExp.exp_tag is NOT NULL"
+        " AND rawScienceExp.exp_tag IS NULL"
+        " AND rawDetrendExp.exp_tag IS NULL";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no pending newImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "newImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    return true;
+}
Index: trunk/ippTools/src/camtool.h
===================================================================
--- trunk/ippTools/src/camtool.h	(revision 9271)
+++ trunk/ippTools/src/camtool.h	(revision 9271)
@@ -0,0 +1,15 @@
+#ifndef P3TOOL_H
+#define P3TOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    P3TOOL_MODE_NONE      = 0x0,
+    P3TOOL_MODE_PENDINGEXP,
+    P3TOOL_MODE_PENDINGIMFILE,
+    P3TOOL_MODE_ADDPROCESSEDIMFILE,
+} p3toolMode;
+
+pxConfig *p3toolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // P3TOOL_H
Index: trunk/ippTools/src/camtoolConfig.c
===================================================================
--- trunk/ippTools/src/camtoolConfig.c	(revision 9271)
+++ trunk/ippTools/src/camtoolConfig.c	(revision 9271)
@@ -0,0 +1,178 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pmConfig.h>
+#include <math.h>
+
+#include "pxtools.h"
+#include "p3tool.h"
+
+// this function can not fail -- exits on error
+pxConfig *p3toolConfig(pxConfig *config, int argc, char **argv) {
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        goto FAIL;
+    }
+
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "search by exposure ID", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-inst",  0,
+        "search by camera", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-telescope",  0,
+        "search by telescope", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-exp_type",  0,
+        "search by exposure type", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-imfiles",  0,
+        "search for exps with N imfiles", NULL);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple",  0,
+        "use the simple output format", false);
+    
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "search by exposure ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class",  0,
+        "search by class", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+        "search by class ID", NULL);
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple",  0,
+        "use the simple output format", false);
+
+
+    // -addprocessedimfile
+    psMetadata *addprocessedimfileArgs = psMetadataAlloc();
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            goto FAIL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option);\
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-pendingexp",   P3TOOL_MODE_PENDINGEXP,  pendingexpArgs);
+    PXTOOL_MODE("-pendingimfile",P3TOOL_MODE_PENDINGIMFILE,pendingimfileArgs);
+    PXTOOL_MODE("-addprocessedimfile", P3TOOL_MODE_ADDPROCESSEDIMFILE, addprocessedimfileArgs);
+
+    bool argErr = false;
+    if (config->mode == P3TOOL_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Phase 3 Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n");
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort(argv[0], "all options must be specified as a metadata");            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        goto FAIL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            goto FAIL; \
+        } \
+    } \
+}
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+    addWhereStr(exp_tag);
+    // convert '-inst' to 'camera'
+    {
+        psString str = NULL; 
+        bool status = false;
+        if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+            if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+                goto FAIL;
+            }
+        }
+    }
+    addWhereStr(telescope);
+    addWhereStr(exp_type);
+    {
+        int imfiles = 0; 
+        bool status = false;
+        if ((imfiles = psMetadataLookupS32(&status, config->args, "-imfiles"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "imfiles", 0, "==", imfiles)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+                goto FAIL;
+            }
+        }
+    }
+    addWhereStr(class);
+    addWhereStr(class_id);
+    addWhereStr(filter);
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        goto FAIL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+
+    return config;
+
+FAIL:
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+    exit(EXIT_FAILURE);
+}
