Index: trunk/ippTools/src/Makefile.am
===================================================================
--- trunk/ippTools/src/Makefile.am	(revision 42285)
+++ trunk/ippTools/src/Makefile.am	(revision 42387)
@@ -35,4 +35,5 @@
 	releasetool \
 	fftool \
+	fpcamtool \
 	remotetool
 
@@ -55,4 +56,5 @@
 	pxtoolsErrorCodes.h \
 	pxtree.h \
+	pxfpcam.h \
 	pxwarp.h
 
@@ -91,4 +93,5 @@
 	releasetool.h \
 	fftool.h \
+	fpcamtool.h \
 	remotetool.h 
 
@@ -116,4 +119,5 @@
 	pxtoolsErrorCodes.c \
 	pxtree.c \
+	pxfpcam.c \
 	pxwarp.c
 
@@ -174,4 +178,10 @@
     camtool.c \
     camtoolConfig.c
+
+fpcamtool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+fpcamtool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+fpcamtool_SOURCES = \
+    fpcamtool.c \
+    fpcamtoolConfig.c
 
 faketool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
Index: trunk/ippTools/src/fpcamtool.c
===================================================================
--- trunk/ippTools/src/fpcamtool.c	(revision 42387)
+++ trunk/ippTools/src/fpcamtool.c	(revision 42387)
@@ -0,0 +1,830 @@
+/*
+ * fpcamtool.c
+ *
+ * Copyright (C) 2022 Eugene Magnier
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "pxfpcam.h"
+#include "fpcamtool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool pendingexpMode(pxConfig *config);
+static bool inputchipsMode(pxConfig *config);
+static bool inputastromMode(pxConfig *config);
+static bool addprocessedexpMode(pxConfig *config);
+static bool processedexpMode(pxConfig *config);
+static bool revertprocessedexpMode(pxConfig *config);
+static bool updateprocessedexpMode(pxConfig *config);
+
+# define MODECASE(caseName, func) case caseName: if (!func(config)) { goto FAIL; } break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = fpcamtoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(FPCAMTOOL_MODE_DEFINEBYQUERY,        definebyqueryMode);
+        MODECASE(FPCAMTOOL_MODE_UPDATERUN,            updaterunMode);
+        MODECASE(FPCAMTOOL_MODE_PENDINGEXP,           pendingexpMode);
+        MODECASE(FPCAMTOOL_MODE_INPUTCHIPS,           inputchipsMode);
+        MODECASE(FPCAMTOOL_MODE_INPUTASTROM,          inputastromMode);
+        MODECASE(FPCAMTOOL_MODE_ADDPROCESSEDEXP,      addprocessedexpMode);
+        MODECASE(FPCAMTOOL_MODE_PROCESSEDEXP,         processedexpMode);
+        MODECASE(FPCAMTOOL_MODE_REVERTPROCESSEDEXP,   revertprocessedexpMode);
+        MODECASE(FPCAMTOOL_MODE_UPDATEPROCESSEDEXP,   updateprocessedexpMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    
+    psMetadata *whereChp = psMetadataAlloc();
+    psMetadata *whereCam = psMetadataAlloc();
+
+    // The user can specify the chipRun and camRun independently
+
+    // selections for the relevant chip-stage
+    pxfpcamGetSearchArgs (config, whereChp);
+    pxAddLabelSearchArgs (config, whereChp, "-chip_label",      "chipRun.label",      "==");
+    PXOPT_COPY_S64(config->args,  whereChp, "-chip_id",         "chipRun.chip_id",    "==");
+    PXOPT_COPY_STR(config->args,  whereChp, "-chip_reduction",  "chipRun.reduction",  "==");
+    PXOPT_COPY_STR(config->args,  whereChp, "-chip_data_group", "chipRun.data_group", "==");
+    
+    // selections for the relevant cam-stage
+    pxfpcamGetSearchArgs (config, whereCam);
+    pxAddLabelSearchArgs (config, whereCam, "-cam_label",      "camRun.label",      "==");
+    PXOPT_COPY_S64(config->args,  whereCam, "-cam_id",         "camRun.cam_id",     "==");
+    PXOPT_COPY_STR(config->args,  whereCam, "-cam_reduction",  "camRun.reduction",  "==");
+    PXOPT_COPY_STR(config->args,  whereCam, "-cam_data_group", "camRun.data_group", "==");
+
+    // pxfpcam provides rawExp constraints to select the exposures
+
+    if (!psListLength(whereCam->list)) {
+	psFree(whereCam);
+	psFree(whereChp);
+	psError(PXTOOLS_ERR_CONFIG, false, "search parameters (at least for camRun) are required");
+	return false;
+    }
+    
+    PXOPT_LOOKUP_STR(workdir,    config->args, "-set_workdir",    false, false);
+    PXOPT_LOOKUP_STR(label,      config->args, "-set_label",      false, false);
+    PXOPT_LOOKUP_STR(data_group, config->args, "-set_data_group", false, false);
+    PXOPT_LOOKUP_STR(dist_group, config->args, "-set_dist_group", false, false);
+    PXOPT_LOOKUP_STR(reduction,  config->args, "-set_reduction",  false, false);
+    PXOPT_LOOKUP_STR(dvodb,      config->args, "-set_dvodb",      false, false);
+    PXOPT_LOOKUP_STR(note,       config->args, "-set_note",       false, false);
+
+    // default
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+    PXOPT_LOOKUP_BOOL(simple,  config->args, "-simple",  false);
+
+    // find the exp_id of all the exposures that we want to queue up.
+
+    if (psListLength(whereChp->list)) {
+	// if selections are set for chip (and cam), use query set for both where strings
+	psString query = pxDataGet("fpcamtool_find_cam_and_chip_id.sql");
+	if (!query) {
+	    psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+	    psFree(whereChp);
+	    psFree(whereCam);
+	    return false;
+	}
+	psString whereChpStr = psDBGenerateWhereConditionSQL(whereChp, NULL);
+	psString whereCamStr = psDBGenerateWhereConditionSQL(whereCam, NULL);
+
+	// XXX add limits to the chip & cam queries?
+        if (!p_psDBRunQueryF(config->dbh, query, whereChpStr, whereCamStr)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(query);
+	    psFree(whereChp);
+	    psFree(whereCam);
+	    psFree(whereChpStr);
+	    psFree(whereCamStr);
+            return false;
+        }
+	psFree(query);
+	psFree(whereChp);
+	psFree(whereCam);
+	psFree(whereChpStr);
+	psFree(whereCamStr);
+    } else {
+	// if we are not selecting the chipRun explicitly, use the camRun selection for both
+	psString query = pxDataGet("fpcamtool_find_cam_id.sql");
+	if (!query) {
+	    psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+	    psFree(whereChp);
+	    psFree(whereCam);
+	    return false;
+	}
+	psString whereCamStr = psDBGenerateWhereConditionSQL(whereCam, NULL);
+
+	// XXX add limits to the chip & cam queries?
+        if (!p_psDBRunQueryF(config->dbh, query, whereCamStr)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(query);
+	    psFree(whereChp);
+	    psFree(whereCam);
+	    psFree(whereCamStr);
+            return false;
+        }
+	psFree(query);
+	psFree(whereChp);
+	psFree(whereCam);
+	psFree(whereCamStr);
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("fpcamtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (pretend) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "fpcamRun", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an exp without any associted
+    // imfiles
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // loop over our list of fpcamRun rows
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *md = output->data[i];
+
+        fpcamInsertRow *row = fpcamInsertObjectFromMetadata(md);
+        if (!row) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert metadata into fpcamInsert object");
+            psFree(output);
+            return false;
+        }
+
+        // queue the exp
+        if (!pxfpcamQueueByCamAndChipID(config,
+                    row->chip_id,
+                    row->cam_id,
+                    workdir     ? workdir   : row->workdir,
+                    label       ? label     : row->label,
+                    data_group  ? data_group: row->data_group,
+                    dist_group  ? dist_group: row->dist_group,
+                    reduction   ? reduction : row->reduction,
+                    dvodb       ? dvodb     : row->dvodb,
+                    note
+        )) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false,
+                    "failed to trying to queue cam_id: %lld, chip_id: %lld", (long long) row->cam_id, (long long) row->chip_id);
+            psFree(row);
+            psFree(output);
+            return false;
+        }
+        psFree(row);
+    }
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    pxfpcamGetSearchArgs (config, where);
+    PXOPT_COPY_S64(config->args, where, "-fpcam_id",  "fpcamRun.fpcam_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-cam_id",    "fpcamRun.cam_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id",   "fpcamRun.chip_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-label",     "fpcamRun.label", "==");
+    PXOPT_COPY_STR(config->args, where, "-data_group","fpcamRun.data_group", "==");
+    PXOPT_COPY_STR(config->args, where, "-state",     "fpcamRun.state", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "fpcamRun.reduction", "==");
+
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+    psString query = psStringCopy("UPDATE fpcamRun JOIN camRun USING(cam_id) JOIN chipRun ON(chipRun.chip_id = camRun.chip_id) JOIN rawExp USING(exp_id)");
+
+    // pxUpdateRun gets parameters from config->args and updates
+    bool result = pxUpdateRun(config, where, &query, "fpcamRun", "fpcam_id", "fpcamProcessedExp", true, false);
+    if (!result) {
+        psError(psErrorCodeLast(), false, "pxUpdateRun failed");
+    }
+
+    psFree(query);
+    psFree(where);
+
+    return result;
+}
+
+static bool pendingexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(all, config->args, "-all", false);
+
+    psMetadata *where = psMetadataAlloc();
+    pxfpcamGetSearchArgs (config, where);
+    pxAddLabelSearchArgs (config, where, "-label",    "fpcamRun.label", "==");
+    PXOPT_COPY_S64(config->args, where, "-fpcam_id",  "fpcamRun.fpcam_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-cam_id",    "fpcamRun.cam_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id",   "fpcamRun.chip_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "fpcamRun.reduction", "==");
+
+    psString query = pxDataGet("fpcamtool_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    } else {
+      if (!all) {
+        psError(PXTOOLS_ERR_SYS, false, "unrestricted query not allowed (try -all)");
+        return false;
+      }
+    }
+    psFree(where);
+
+    psStringAppend(&query, "\nORDER BY priority DESC, fpcam_id");
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("fpcamtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fpcamPendingExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool inputchipsMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S64(fpcam_id, config->args, "-fpcam_id", true, false);
+    PXOPT_LOOKUP_U64(limit, config->args,    "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args,  "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fpcam_id",  "fpcamRun.fpcam_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "fpcamRun.reduction", "==");
+
+    psString query = pxDataGet("fpcamtool_inputchips.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement for %d", (int) fpcam_id);
+        return false;
+    }
+
+    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
+    if (!psListLength(where->list)) {
+        psError(PXTOOLS_ERR_SYS, false, "unrestricted query not allowed (-fpcam_id required)");
+        return false;
+    }
+
+    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+    psStringAppend(&query, " WHERE %s", whereClause);
+    psFree(whereClause);
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("fpcamtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fpcamInputChips", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool inputastromMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S64(fpcam_id, config->args, "-fpcam_id", true, false);
+    PXOPT_LOOKUP_U64(limit, config->args,    "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args,  "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fpcam_id",  "fpcamRun.fpcam_id", "==");
+
+    psString query = pxDataGet("fpcamtool_inputastrom.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement for %d", (int) fpcam_id);
+        return false;
+    }
+
+    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
+    if (!psListLength(where->list)) {
+        psError(PXTOOLS_ERR_SYS, false, "unrestricted query not allowed (-fpcam_id required)");
+        return false;
+    }
+
+    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+    psStringAppend(&query, " WHERE %s", whereClause);
+    psFree(whereClause);
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("fpcamtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fpcamInputAstrom", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(fpcam_id, config->args,      "-fpcam_id", true, false);
+    PXOPT_LOOKUP_STR(path_base, config->args,     "-path_base", true, false);
+
+    // optional
+    PXOPT_LOOKUP_F32(zpt_obs, config->args,       "-zpt_obs", false, false);
+    PXOPT_LOOKUP_F32(zpt_err, config->args,       "-zpt_err", false, false);
+    PXOPT_LOOKUP_F32(zpt_uq,  config->args,       "-zpt_uq", false, false);
+    PXOPT_LOOKUP_F32(zpt_lq,  config->args,       "-zpt_lq", false, false);
+
+    PXOPT_LOOKUP_F32(dtime_script, config->args,   "-dtime_script", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args,       "-hostname", false, false);
+    PXOPT_LOOKUP_S32(n_stars, config->args,        "-n_stars", false, false);
+ 
+    PXOPT_LOOKUP_S16(fault, config->args,          "-fault", false, false);
+    PXOPT_LOOKUP_S16(quality, config->args,        "-quality", false, false);
+
+    // we store actual detection efficiency by adding in zpt_obs
+    PXOPT_LOOKUP_F32(deteff_inst, config->args,    "-deteff_inst", false, false);
+    PXOPT_LOOKUP_F32(deteff_inst_lq, config->args, "-deteff_inst_lq", false, false);
+    PXOPT_LOOKUP_F32(deteff_inst_uq, config->args, "-deteff_inst_uq", false, false);
+    PXOPT_LOOKUP_F32(deteff_err, config->args,     "-deteff_inst_err", false, false);
+
+    psF32 deteff_obs = NAN;
+    psF32 deteff_uq = NAN;
+    psF32 deteff_lq = NAN;
+    if (isfinite(zpt_obs)) {
+        if (isfinite(deteff_inst)) {
+            deteff_obs = deteff_inst + zpt_obs;
+        }
+        if (isfinite(deteff_inst_uq)) {
+            deteff_uq = deteff_inst_uq + zpt_obs;
+        }
+        if (isfinite(deteff_inst_lq)) {
+            deteff_lq = deteff_inst_lq + zpt_obs;
+        }
+    }
+
+    PXOPT_LOOKUP_STR(ver_pslib,     config->args, "-ver_pslib",     false, false);
+    PXOPT_LOOKUP_STR(ver_psmodules, config->args, "-ver_psmodules", false, false);
+    PXOPT_LOOKUP_STR(ver_psphot,    config->args, "-ver_psphot",    false, false);
+    PXOPT_LOOKUP_STR(ver_ppstats,   config->args, "-ver_ppstats",   false, false);
+    PXOPT_LOOKUP_STR(ver_fpcamera,  config->args, "-ver_fpcamera",  false, false);
+
+    psString software_ver = pxMergeCodeVersions(ver_pslib,ver_psmodules);
+    software_ver = pxMergeCodeVersions(software_ver,ver_psphot);
+    software_ver = pxMergeCodeVersions(software_ver,ver_ppstats);
+    software_ver = pxMergeCodeVersions(software_ver,ver_fpcamera);
+
+    // generate restrictions
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    fpcamProcessedExpRow *row = fpcamProcessedExpRowAlloc(
+        fpcam_id,
+        path_base,
+        zpt_obs,
+        zpt_err,
+        zpt_lq,
+        zpt_uq,
+        dtime_script,
+        hostname,
+        n_stars,
+        fault,
+	NULL, // XXX : this should be epoch
+        software_ver,
+        deteff_obs,
+        deteff_err,
+        deteff_lq,
+        deteff_uq,
+        quality
+        );
+
+    if (!fpcamProcessedExpInsertObject(config->dbh, row)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(row);
+        return false;
+    }
+
+    if (fault) {
+        psFree(row);
+        if (!psDBCommit(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+        return true;
+    }
+    // else continue on...
+
+    // since there is only one exp per 'new' set camRun.state = 'full'
+    if (!pxfpcamRunSetState(config, row->fpcam_id, "full")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to change fpcamRun.state for fpcam_id: %" PRId64, row->fpcam_id);
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    // fpcam is a stand-alone stage: it does not trigger another stage to follow
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    return true;
+}
+
+static bool processedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // some options
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    // generate restrictions
+    psMetadata *where = psMetadataAlloc();
+    pxfpcamGetSearchArgs (config, where);
+    PXOPT_COPY_S64(config->args,  where, "-fpcam_id",    "fpcamRun.fpcam_id", "==");
+    PXOPT_COPY_S64(config->args,  where, "-cam_id",      "fpcamRun.cam_id", "==");
+    PXOPT_COPY_S64(config->args,  where, "-chip_id",     "fpcamRun.chip_id", "==");
+    pxAddLabelSearchArgs (config, where, "-label",       "fpcamRun.label",     "==");
+    pxAddLabelSearchArgs (config, where, "-data_group",  "fpcamRun.data_group",     "LIKE");
+    PXOPT_COPY_STR(config->args,  where, "-reduction",   "fpcamRun.reduction", "==");
+    
+    psString where2 = NULL;
+    if (!pxspaceAddWhere(config, &where2, "rawExp")) {
+        psError(psErrorCodeLast(), false, "pxSpaceAddWhere failed");
+        return false;
+    }
+    if (!psListLength(where->list) && !where2) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = pxDataGet("fpcamtool_find_processedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " WHERE %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // we either add AND (condition) or WHERE (condition):
+    if (where->list && faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", " AND fpcamProcessedExp.fault != 0");
+    }
+    if (where->list && !faulted) {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", " AND fpcamProcessedExp.fault = 0");
+    }
+    if (!where->list && faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", " WHERE fpcamProcessedExp.fault != 0");
+    }
+    if (!where->list && !faulted) {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", " WHERE fpcamProcessedExp.fault = 0");
+    }
+    psFree(where);
+    if (where2) {
+        psStringAppend(&query, " %s", where2);
+        psFree(where2);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("fpcamtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fpcamProcessedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    pxfpcamGetSearchArgs (config, where);
+
+    PXOPT_COPY_S64(config->args,  where, "-fpcam_id",    "fpcamRun.fpcam_id", "==");
+    PXOPT_COPY_S64(config->args,  where, "-cam_id",      "fpcamRun.cam_id", "==");
+    PXOPT_COPY_S64(config->args,  where, "-chip_id",     "fpcamRun.chip_id", "==");
+    PXOPT_COPY_STR(config->args,  where, "-reduction",   "fpcamRun.reduction", "==");
+    PXOPT_COPY_S16(config->args, where,  "-fault",       "fpcamProcessedExp.fault", "==");
+
+    pxAddLabelSearchArgs (config, where, "-label",       "fpcamRun.label",     "==");
+    pxAddLabelSearchArgs (config, where, "-data_group",  "fpcamRun.data_group",     "LIKE");
+
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(where);
+        return false;
+    }
+
+    psString whereClause = NULL;
+    {
+        psString query = pxDataGet("fpcamtool_revertprocessedexp.sql");
+        if (!query) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+            psFree(where);
+            return false;
+        }
+
+        // use psDBGenerateWhereConditionalSQL with AND ... because the SQL ends in a WHERE
+        if (where && psListLength(where->list)) {
+            whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+            psStringAppend(&query, " AND %s", whereClause);
+        }
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(query);
+            psFree(where);
+            return false;
+        }
+        psFree(query);
+    }
+    psFree(where);
+
+    int numDeleted = psDBAffectedRows(config->dbh);
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    psLogMsg("fpcamtool", PS_LOG_INFO, "Deleted %d fpcamProcessedExps", numDeleted);
+
+    {
+        psString query = pxDataGet("fpcamtool_revertupdatedexp.sql");
+        if (!query) {
+            psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+            return false;
+        }
+
+        if (whereClause) {
+            psStringAppend(&query, "\n AND %s", whereClause);
+            psFree(whereClause);
+        }
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(query);
+            return false;
+        }
+        psFree(query);
+    }
+    int numUpdated = psDBAffectedRows(config->dbh);
+    psLogMsg("fpcamtool", PS_LOG_INFO, "Updated %d fpcamProcessedExps", numUpdated);
+
+    return true;
+}
+
+
+static bool updateprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", true, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-set_quality", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fpcam_id", "fpcam_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-cam_id",   "cam_id",   "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id",  "chip_id",  "==");
+
+    if (!pxSetFaultCode(config->dbh, "fpcamProcessedExp", where, fault, quality)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        psFree (where);
+        return false;
+    }
+    psFree (where);
+    return true;
+}
+
Index: trunk/ippTools/src/fpcamtool.h
===================================================================
--- trunk/ippTools/src/fpcamtool.h	(revision 42387)
+++ trunk/ippTools/src/fpcamtool.h	(revision 42387)
@@ -0,0 +1,40 @@
+/*
+ * fpcamtool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef FPCAMTOOL_H
+#define FPCAMTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    FPCAMTOOL_MODE_NONE      = 0x0,
+    FPCAMTOOL_MODE_DEFINEBYQUERY,
+    FPCAMTOOL_MODE_UPDATERUN,
+    FPCAMTOOL_MODE_PENDINGEXP,
+    FPCAMTOOL_MODE_INPUTCHIPS,
+    FPCAMTOOL_MODE_INPUTASTROM,
+    FPCAMTOOL_MODE_ADDPROCESSEDEXP,
+    FPCAMTOOL_MODE_PROCESSEDEXP,
+    FPCAMTOOL_MODE_REVERTPROCESSEDEXP,
+    FPCAMTOOL_MODE_UPDATEPROCESSEDEXP,
+} fpcamtoolMode;
+
+pxConfig *fpcamtoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // FPCAMTOOL_H
Index: trunk/ippTools/src/fpcamtoolConfig.c
===================================================================
--- trunk/ippTools/src/fpcamtoolConfig.c	(revision 42387)
+++ trunk/ippTools/src/fpcamtoolConfig.c	(revision 42387)
@@ -0,0 +1,199 @@
+/*
+ * fpcamtoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <stdint.h>
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "pxfpcam.h"
+#include "fpcamtool.h"
+
+pxConfig *fpcamtoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(psErrorCodeLast(), false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -definebyquery
+    psMetadata *definebyqueryArgs = psMetadataAlloc();
+    pxfpcamSetSearchArgs(definebyqueryArgs);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-chip_label", PS_META_DUPLICATE_OK, "search by chipRun label", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-chip_reduction",          0, "search by chipRun reduction class", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-chip_data_group",         0, "search by chipRun data_group", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-cam_label", PS_META_DUPLICATE_OK, "search by camRun label", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-cam_reduction",          0, "search by camRun reduction class", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-cam_data_group",         0, "search by camRun data_group", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_workdir",        0, "define workdir", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_label",          0, "define label", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_data_group",     0, "define data group", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_dist_group",     0, "define dist group", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_reduction",      0, "define reduction class", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_dvodb",          0, "define DVO db", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_note",           0, "define note", NULL);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-pretend",           0, "do not actual modify the database", false);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-simple",            0, "use the simple output format", false);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    pxfpcamSetSearchArgs(updaterunArgs);
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-fpcam_id",           0, "search by fpcam_id", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state",              0, "search by fpcamRun state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label",              0, "search by fpcamRun label", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-data_group",         0, "search by fpcamRun data_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-dist_group",         0, "search by fpcamRun dist_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-reduction",          0, "search by fpcamRun reduction class", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_state",          0, "set state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_label",          0, "set label", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_data_group",     0,   "define new data_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_dist_group",     0,   "define new dist_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_note",           0,         "define new note", NULL);
+
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    pxfpcamSetSearchArgs(pendingexpArgs);
+    psMetadataAddS64(pendingexpArgs, PS_LIST_TAIL, "-fpcam_id",            0, "search by fpcam_id", 0);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by fpcamRun label", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-reduction",         0, "search by fpcamRun reduction class", NULL);
+    psMetadataAddU64(pendingexpArgs, PS_LIST_TAIL, "-limit",             0, "limit result set to N items", 0);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple",           0, "use the simple output format", false);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-all",               0, "allow everything to be queued without search terms", false);
+
+    // -inputchips
+    psMetadata *inputchipsArgs = psMetadataAlloc();
+    psMetadataAddS64(inputchipsArgs, PS_LIST_TAIL, "-fpcam_id",          0, "search by fpcam_id (required)", 0);
+    psMetadataAddStr(inputchipsArgs, PS_LIST_TAIL, "-class_id",          0, "limit by class_id", NULL);
+    psMetadataAddU64(inputchipsArgs, PS_LIST_TAIL, "-limit",             0, "limit result set to N items", 0);
+    psMetadataAddBool(inputchipsArgs, PS_LIST_TAIL, "-simple",           0, "use the simple output format", false);
+
+    // -inputastrom
+    psMetadata *inputastromArgs = psMetadataAlloc();
+    psMetadataAddS64(inputastromArgs, PS_LIST_TAIL, "-fpcam_id",            0, "search by fpcam_id", 0);
+    psMetadataAddU64(inputastromArgs, PS_LIST_TAIL, "-limit",             0, "limit result set to N items", 0);
+    psMetadataAddBool(inputastromArgs, PS_LIST_TAIL, "-simple",           0, "use the simple output format", false);
+
+    // -addprocessedexp
+    psMetadata *addprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(addprocessedexpArgs, PS_LIST_TAIL, "-fpcam_id", 0,        "define fpcamtool ID (required)", 0);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-path_base", 0,            "define base output location", NULL);
+
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-zpt_obs", 0,   "define observed zero point", 0);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-zpt_err", 0,   "define observed zero point error", 0);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-zpt_uq", 0,   "define observed zero point upper quartile", 0);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-zpt_lq", 0,   "define observed zero point lower quartile", 0);
+
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-dtime_script", 0, "define elapsed time in script (seconds)", NAN);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-hostname", 0,            "define hostname", NULL);
+    psMetadataAddS32(addprocessedexpArgs, PS_LIST_TAIL, "-n_stars", 0,            "define number of stars", 0);
+
+    psMetadataAddS16(addprocessedexpArgs, PS_LIST_TAIL, "-fault",  0,            "set fault code", 0);
+    psMetadataAddS16(addprocessedexpArgs, PS_LIST_TAIL, "-quality",  0,            "set quality", 0);
+
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-deteff_inst", 0, "define deteff", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-deteff_inst_err", 0, "define deteff_err", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-deteff_inst_lq", 0, "define deteff_lq", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-deteff_inst_uq", 0, "define deteff_uq", NAN);
+
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-ver_pslib", 0, "define psLib version", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-ver_psmodules", 0, "define psModules version", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-ver_psphot", 0, "define psphot version", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-ver_ppstats", 0, "define ppStats version", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-ver_fpcamera", 0, "define ppImage version", NULL);
+
+    // -processedexp
+    psMetadata *processedexpArgs = psMetadataAlloc();
+    pxfpcamSetSearchArgs(processedexpArgs);
+    psMetadataAddS64(processedexpArgs, PS_LIST_TAIL, "-fpcam_id",   0,            "search by fpcam_id", 0);
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by fpcamRun label", NULL);
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-data_group", PS_META_DUPLICATE_OK, "search by fpcamRun data_group (LIKE comparison)", NULL);
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-reduction",0,            "search by fpcamRun reduction class", NULL);
+    pxspaceAddArguments(processedexpArgs);
+
+    psMetadataAddU64(processedexpArgs, PS_LIST_TAIL, "-limit",    0,            "limit result set to N items", 0);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-faulted", 0,            "only return imfiles with a fault status set", false);
+
+    // -revertprocessedexp
+    psMetadata *revertprocessedexpArgs = psMetadataAlloc();
+    pxfpcamSetSearchArgs(revertprocessedexpArgs);
+    psMetadataAddS64(revertprocessedexpArgs, PS_LIST_TAIL, "-fpcam_id",   0,            "search by cam_id", 0);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-label",    PS_META_DUPLICATE_OK, "search by fpcamRun label", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-reduction",0,            "search by fpcamRun reduction class", NULL);
+    psMetadataAddS16(revertprocessedexpArgs, PS_LIST_TAIL, "-code",     0,            "search by fault code", 0);
+
+    psMetadataAddBool(revertprocessedexpArgs, PS_LIST_TAIL, "-all",  0,            "allow everything to be queued without search terms", false);
+    psMetadataAddS16(revertprocessedexpArgs, PS_LIST_TAIL, "-fault",  0,            "search by fault code", 0);
+
+    // -updateprocessedexp
+    psMetadata *updateprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(updateprocessedexpArgs, PS_LIST_TAIL, "-fpcam_id", 0,            "search by fpcamRun ID", 0);
+    psMetadataAddS64(updateprocessedexpArgs, PS_LIST_TAIL, "-cam_id", 0,            "search by camRun ID", 0);
+    psMetadataAddS64(updateprocessedexpArgs, PS_LIST_TAIL, "-chip_id",  0,            "search by chipRun ID", 0);
+    psMetadataAddS16(updateprocessedexpArgs, PS_LIST_TAIL, "-fault",  0,            "set fault code (required)", INT16_MAX);
+    psMetadataAddS16(updateprocessedexpArgs, PS_LIST_TAIL, "-set_quality",  0,            "set quality", 0);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery",        "create runs from chip stage",          FPCAMTOOL_MODE_DEFINEBYQUERY,      definebyqueryArgs);
+    PXOPT_ADD_MODE("-updaterun",            "change cam run properties",            FPCAMTOOL_MODE_UPDATERUN,          updaterunArgs);
+    PXOPT_ADD_MODE("-pendingexp",           "show pending exposures",               FPCAMTOOL_MODE_PENDINGEXP,         pendingexpArgs);
+    PXOPT_ADD_MODE("-inputchips",           "list input chips",                     FPCAMTOOL_MODE_INPUTCHIPS,         inputchipsArgs);
+    PXOPT_ADD_MODE("-inputastrom",          "list input astrometry file",           FPCAMTOOL_MODE_INPUTASTROM,        inputastromArgs);
+    PXOPT_ADD_MODE("-addprocessedexp",      "add a processed exposure",             FPCAMTOOL_MODE_ADDPROCESSEDEXP,    addprocessedexpArgs);
+    PXOPT_ADD_MODE("-processedexp",         "show processed exposures",             FPCAMTOOL_MODE_PROCESSEDEXP,       processedexpArgs);
+    PXOPT_ADD_MODE("-revertprocessedexp",   "undo a processed exposure",            FPCAMTOOL_MODE_REVERTPROCESSEDEXP, revertprocessedexpArgs);
+    PXOPT_ADD_MODE("-updateprocessedexp",   "changed processed exp properties",     FPCAMTOOL_MODE_UPDATEPROCESSEDEXP, updateprocessedexpArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, false, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: trunk/ippTools/src/pxadmin.c
===================================================================
--- trunk/ippTools/src/pxadmin.c	(revision 42285)
+++ trunk/ippTools/src/pxadmin.c	(revision 42387)
@@ -184,4 +184,20 @@
         psString dbName = name->data.str;
 
+	// if name is e.g., gpc1, strcasecmp returns FALSE
+	bool allowDelete = true;
+	allowDelete = allowDelete && strcasecmp(dbName, "gpc1");  
+	allowDelete = allowDelete && strcasecmp(dbName, "gpc2");  
+	allowDelete = allowDelete && strcasecmp(dbName, "nebulous");  
+	allowDelete = allowDelete && strcasecmp(dbName, "isp");  
+	allowDelete = allowDelete && strcasecmp(dbName, "ssp");  
+	allowDelete = allowDelete && strcasecmp(dbName, "uic");  
+	allowDelete = allowDelete && strcasecmp(dbName, "hsc_v1");  
+	allowDelete = allowDelete && strcasecmp(dbName, "megacam_v1");  
+
+	if (!allowDelete) {
+            fprintf (stdout, "**** WARNING: not allowed to delete database %s.\n", dbName);
+            return false;
+        }
+
         fprintf(stdout, "*** delete the tables from database %s? ***\n", dbName);
         fprintf(stdout, "*** to delete the tables, answer YES, and give password ***\n");
Index: trunk/ippTools/src/pxcam.c
===================================================================
--- trunk/ippTools/src/pxcam.c	(revision 42285)
+++ trunk/ippTools/src/pxcam.c	(revision 42387)
@@ -174,5 +174,5 @@
                          "new", // state
                          workdir  ? workdir  : "NULL",
-                         "dirty", //workdir_state
+                         "dirty", // workdir_state
                          label    ? label    : "NULL",
                          data_group ? data_group : "NULL",
Index: trunk/ippTools/src/pxfpcam.c
===================================================================
--- trunk/ippTools/src/pxfpcam.c	(revision 42387)
+++ trunk/ippTools/src/pxfpcam.c	(revision 42387)
@@ -0,0 +1,307 @@
+/*
+ * pxfpcam.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxfpcam.h"
+
+bool pxfpcamSetSearchArgs (psMetadata *md) {
+
+    // standard query options for fpcamera
+    psMetadataAddS64(md,  PS_LIST_TAIL, "-chip_id",            0, "search by chip_id", 0);
+    psMetadataAddS64(md,  PS_LIST_TAIL, "-cam_id",             0, "search by cam_id", 0);
+    psMetadataAddS64(md,  PS_LIST_TAIL, "-exp_id",             0, "search by exp_id", 0);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_name",           0, "search by exp_name", NULL);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-inst",               0, "search for camera", NULL);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-telescope",          0, "search for telescope", NULL);
+    psMetadataAddTime(md, PS_LIST_TAIL, "-dateobs_begin",      0, "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(md, PS_LIST_TAIL, "-dateobs_end",        0, "search for exposures by time (<)", NULL);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_tag",            0, "search by exp_tag", NULL);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_type",           0, "search by exp_type", NULL);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-obs_mode",           0, "search by obs_mode", NULL);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-comment",            0, "search by comment", NULL);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-filelevel",          0, "search by filelevel", NULL);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-filter",             0, "search for filter", NULL);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-airmass_min",        0, "define min airmass", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-airmass_max",        0, "define max airmass", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-ra_min",             0, "define min RA (degrees) ", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-ra_max",             0, "define max RA (degrees) ", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-decl_min",           0, "define min DEC (degrees)", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-decl_max",           0, "define max DEC (degrees)", NAN);
+    psMetadataAddF32(md,  PS_LIST_TAIL, "-exp_time_min",       0, "define min exposure time", NAN);
+    psMetadataAddF32(md,  PS_LIST_TAIL, "-exp_time_max",       0, "define max exposure time", NAN);
+    psMetadataAddF32(md,  PS_LIST_TAIL, "-sat_pixel_frac_min", 0, "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(md,  PS_LIST_TAIL, "-sat_pixel_frac_max", 0, "define max fraction of saturated pixels", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_min",             0, "define min background", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_max",             0, "define max background", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_stdev_min",       0, "define min background standard deviation", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_stdev_max",       0, "define max background standard deviation", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_mean_stdev_min",  0, "define min background mean standard deviation (across imfiles)", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_mean_stdev_max",  0, "define max background mean standard deviation (across imfiles)", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-alt_min",            0, "define min altitude", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-alt_max",            0, "define max altitude", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-az_min",             0, "define min azimuth ", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-az_max",             0, "define max azimuth ", NAN);
+    psMetadataAddF32(md,  PS_LIST_TAIL, "-ccd_temp_min",       0, "define min ccd tempature", NAN);
+    psMetadataAddF32(md,  PS_LIST_TAIL, "-ccd_temp_max",       0, "define max ccd tempature", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-posang_min",         0, "define min rotator position angle", NAN);
+    psMetadataAddF64(md,  PS_LIST_TAIL, "-posang_max",         0, "define max rotator position angle", NAN);
+    psMetadataAddStr(md,  PS_LIST_TAIL, "-object",             0, "search by exposure object", NULL);
+    psMetadataAddF32(md,  PS_LIST_TAIL, "-sun_angle_min",      0, "define min solar angle", NAN);
+    psMetadataAddF32(md,  PS_LIST_TAIL, "-sun_angle_max",      0, "define max solar angle", NAN);
+
+    return true;
+}
+
+bool pxfpcamGetSearchArgs (pxConfig *config, psMetadata *where) {
+
+    // only add the rawExp constraints to the where MD
+    // chip and cam constraints are added independently to two different where MDs
+    PXOPT_COPY_S64(config->args,   where, "-exp_id",             "rawExp.exp_id",         "==");
+    PXOPT_COPY_STR(config->args,   where, "-exp_name",           "rawExp.exp_name",       "==");
+    PXOPT_COPY_STR(config->args,   where, "-inst",               "rawExp.camera",         "==");
+    PXOPT_COPY_STR(config->args,   where, "-telescope",          "rawExp.telescope",      "==");
+    PXOPT_COPY_TIME(config->args,  where, "-dateobs_begin",      "rawExp.dateobs",        ">=");
+    PXOPT_COPY_TIME(config->args,  where, "-dateobs_end",        "rawExp.dateobs",        "<=");
+    PXOPT_COPY_STR(config->args,   where, "-exp_tag",            "rawExp.exp_tag",        "==");
+    PXOPT_COPY_STR(config->args,   where, "-exp_type",           "rawExp.exp_type",       "==");
+    PXOPT_COPY_STR(config->args,   where, "-obs_mode",           "rawExp.obs_mode",       "==");
+    PXOPT_COPY_STR(config->args,   where, "-comment",            "rawExp.comment",        "LIKE");
+    PXOPT_COPY_STR(config->args,   where, "-filelevel",          "rawExp.filelevel",      "==");
+    PXOPT_COPY_STR(config->args,   where, "-filter",             "rawExp.filter",         "LIKE");
+    PXOPT_COPY_F64(config->args,   where, "-airmass_min",        "rawExp.airmass",        ">=");
+    PXOPT_COPY_F64(config->args,   where, "-airmass_max",        "rawExp.airmass",        "<");
+    PXOPT_COPY_RADEC(config->args, where, "-ra_min",             "rawExp.ra",             ">=");
+    PXOPT_COPY_RADEC(config->args, where, "-ra_max",             "rawExp.ra",             "<");
+    PXOPT_COPY_RADEC(config->args, where, "-decl_min",           "rawExp.decl",           ">=");
+    PXOPT_COPY_RADEC(config->args, where, "-decl_max",           "rawExp.decl",           "<");
+    PXOPT_COPY_F32(config->args,   where, "-exp_time_min",       "rawExp.exp_time",       ">=");
+    PXOPT_COPY_F32(config->args,   where, "-exp_time_max",       "rawExp.exp_time",       "<");
+    PXOPT_COPY_F32(config->args,   where, "-sat_pixel_frac_min", "rawExp.sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args,   where, "-sat_pixel_frac_max", "rawExp.sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args,   where, "-bg_min",             "rawExp.bg",             ">=");
+    PXOPT_COPY_F64(config->args,   where, "-bg_max",             "rawExp.bg",             "<");
+    PXOPT_COPY_F64(config->args,   where, "-bg_stdev_min",       "rawExp.bg_stdev",       ">=");
+    PXOPT_COPY_F64(config->args,   where, "-bg_stdev_max",       "rawExp.bg_stdev",       "<");
+    PXOPT_COPY_F64(config->args,   where, "-bg_mean_stdev_min",  "rawExp.bg_mean_stdev",  ">=");
+    PXOPT_COPY_F64(config->args,   where, "-bg_mean_stdev_max",  "rawExp.bg_mean_stdev",  "<");
+    PXOPT_COPY_F64(config->args,   where, "-alt_min",            "rawExp.alt",            ">=");
+    PXOPT_COPY_F64(config->args,   where, "-alt_max",            "rawExp.alt",            "<");
+    PXOPT_COPY_F64(config->args,   where, "-az_min",             "rawExp.az",             ">=");
+    PXOPT_COPY_F64(config->args,   where, "-az_max",             "rawExp.az",             "<");
+    PXOPT_COPY_F32(config->args,   where, "-ccd_temp_min",       "rawExp.ccd_temp",       ">=");
+    PXOPT_COPY_F32(config->args,   where, "-ccd_temp_max",       "rawExp.ccd_temp",       "<");
+    PXOPT_COPY_F64(config->args,   where, "-posang_min",         "rawExp.posang",         ">=");
+    PXOPT_COPY_F64(config->args,   where, "-posang_max",         "rawExp.posang",         "<");
+    PXOPT_COPY_STR(config->args,   where, "-object",             "rawExp.object",         "==");
+    PXOPT_COPY_F32(config->args,   where, "-sun_angle_min",      "rawExp.sun_angle",      ">=");
+    PXOPT_COPY_F32(config->args,   where, "-sun_angle_max",      "rawExp.sun_angle",      "<");
+
+    return true;
+}
+
+
+bool pxfpcamRunSetState(pxConfig *config, psS64 fpcam_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!pxIsValidState(state)) {
+        psError(PS_ERR_UNKNOWN, false, "invalid fpcamRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE fpcamRun SET state = '%s' WHERE fpcam_id = %" PRId64;
+    if (!p_psDBRunQueryF(config->dbh, query, state, fpcam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to change state for fpcam_id %" PRId64, fpcam_id);
+        return false;
+    }
+
+    return true;
+}
+
+bool pxfpcamQueueByCamAndChipID(pxConfig *config,
+                    psS64 chip_id,
+                    psS64 cam_id,
+                    char *workdir,
+                    char *label,
+                    char *data_group,
+                    char *dist_group,
+                    char *reduction,
+                    char *dvodb,
+                    char *note)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // load the SQL to enqueue our exp_ids from disk once
+    static psString query = NULL;
+    if (!query) {
+        query = pxDataGet("fpcamtool_queue_cam_id.sql");
+        psMemSetPersistent(query, true);
+        if (!query) {
+            psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+            return false;
+        }
+    }
+
+    // queue the exposure
+    if (!p_psDBRunQueryF(config->dbh, query,
+			 (long long) chip_id,
+			 (long long) cam_id,
+                         "new",     // state
+                         workdir    ? workdir  : "NULL",
+                         "dirty",   // workdir_state
+                         label      ? label    : "NULL",
+                         data_group ? data_group : "NULL",
+                         dist_group ? dist_group : "NULL",
+                         reduction  ? reduction   : "NULL",
+                         dvodb      ? dvodb    : "NULL",
+			 note       ? note     : "NULL"
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // just to be safe, we should have changed at least one row
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false,
+                "no rows affected - should have changed at least one row");
+        return false;
+    }
+
+    return true;
+}
+
+/** fpcamInsertRow tools **/
+
+static void fpcamInsertRowFree(fpcamInsertRow *object)
+{
+    psFree(object->state);
+    psFree(object->workdir);
+    psFree(object->workdir_state);
+    psFree(object->label);
+    psFree(object->data_group);
+    psFree(object->dist_group);
+    psFree(object->reduction);
+    psFree(object->dvodb);
+    psFree(object->software_ver);
+    psFree(object->note);
+}
+
+static fpcamInsertRow *fpcamInsertRowAlloc(psS64 chip_id, psS64 cam_id, 
+				    const char *state, const char *workdir, const char *workdir_state,
+				    const char *label, const char *data_group, const char *dist_group,
+				    const char *reduction, const char *dvodb, const char *software_ver, const char *note)
+{
+    fpcamInsertRow     *_object;
+
+    _object = psAlloc(sizeof(fpcamInsertRow));
+    psMemSetDeallocator(_object, (psFreeFunc)fpcamInsertRowFree);
+
+    _object->chip_id = chip_id;
+    _object->cam_id = cam_id;
+    _object->state = psStringCopy(state);
+    _object->workdir = psStringCopy(workdir);
+    _object->workdir_state = psStringCopy(workdir_state);
+    _object->label = psStringCopy(label);
+    _object->data_group = psStringCopy(data_group);
+    _object->dist_group = psStringCopy(dist_group);
+    _object->reduction = psStringCopy(reduction);
+    _object->dvodb = psStringCopy(dvodb);
+    _object->software_ver = psStringCopy(software_ver);
+    _object->note = psStringCopy(note);
+
+    return _object;
+}
+
+fpcamInsertRow *fpcamInsertObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
+        return false;
+    }
+    psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cam_id");
+        return false;
+    }
+    char* state = psMetadataLookupPtr(&status, md, "state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* workdir_state = psMetadataLookupPtr(&status, md, "workdir_state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir_state");
+        return false;
+    }
+    char* label = psMetadataLookupPtr(&status, md, "label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
+        return false;
+    }
+    char* data_group = psMetadataLookupPtr(&status, md, "data_group");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item data_group");
+        return false;
+    }
+    char* dist_group = psMetadataLookupPtr(&status, md, "dist_group");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dist_group");
+        return false;
+    }
+    char* reduction = psMetadataLookupPtr(&status, md, "reduction");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item reduction");
+        return false;
+    }
+    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
+        return false;
+    }
+    char* software_ver = psMetadataLookupPtr(&status, md, "software_ver");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item software_ver");
+        return false;
+    }
+    char* note = psMetadataLookupPtr(&status, md, "note");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item note");
+        return false;
+    }
+
+    return fpcamInsertRowAlloc(chip_id, cam_id, state, workdir, workdir_state, label, data_group, dist_group, reduction, dvodb, software_ver, note);
+}
Index: trunk/ippTools/src/pxfpcam.h
===================================================================
--- trunk/ippTools/src/pxfpcam.h	(revision 42387)
+++ trunk/ippTools/src/pxfpcam.h	(revision 42387)
@@ -0,0 +1,65 @@
+/*
+ * pxfpcam.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXFPCAM_H
+#define PXFPCAM_H 1
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxfpcamSetSearchArgs (psMetadata *md);
+bool pxfpcamGetSearchArgs (pxConfig *config, psMetadata *where);
+
+bool pxfpcamRunSetState(pxConfig *config, psS64 cam_id, const char *state);
+
+bool pxfpcamQueueByCamAndChipID(pxConfig *config,
+                        psS64 chip_id,
+                        psS64 cam_id,
+                        char *workdir,
+                        char *label,
+                        char *data_group,
+                        char *dist_group,
+                        char *reduction,
+                        char *dvodb,
+                        char *note);
+
+/* Structure for fpcamInsertRow.  The fpcamRun is different from most because it merges the
+   fields of possibly different chipRun and camRuns.  We cannot use the standard camRunRow
+   tools because those functions refer to the linked chip_id.
+ */
+
+typedef struct {
+    psS64           chip_id;
+    psS64           cam_id;
+    char            *state;
+    char            *workdir;
+    char            *workdir_state;
+    char            *label;
+    char            *data_group;
+    char            *dist_group;
+    char            *reduction;
+    char            *dvodb;
+    char            *software_ver;
+    char            *note;
+} fpcamInsertRow;
+
+fpcamInsertRow *fpcamInsertObjectFromMetadata(psMetadata *md);
+
+#endif // PXFPCAM_H
Index: trunk/ippTools/src/pzgetexp.c
===================================================================
--- trunk/ippTools/src/pzgetexp.c	(revision 42285)
+++ trunk/ippTools/src/pzgetexp.c	(revision 42387)
@@ -136,5 +136,5 @@
     psFree(cmdOutput);
     if (!newSummitExps) {
-        // XXX not nessicarily an error
+        // XXX not necessarily an error?
         psError(PS_ERR_UNKNOWN, true, "no new fileSet/exp IDs");
         return false;
@@ -145,4 +145,13 @@
         return true;
     }
+    // If we supply an unknown last_fileset (exposure name) to the summit datastore,
+    // it will return all filesets it knows about.  This can cause problems (table overflow)
+    // if this is too large a number.  Raise an error and let the user fix the underlying
+    // problem (last exposure not known to the summit datastore)
+    // Do we need to be able to configure this number?
+    if (psArrayLength(newSummitExps) > 10000) {
+        psError(PS_ERR_UNKNOWN, true, "too many new fileSet/exp IDs? unknown exposure? problem with summitExp table?");
+        return false;
+    }
 
     // start a transaction so it's all rows or nothing
@@ -153,6 +162,9 @@
     }
 
-    // increase memory table size limits; deafult is 16MB
-    {
+    // increase memory table size limits; default is 16MB, but our current (2022) config
+    // in /etc/mysql/my.cnf sets to default to 1024M.
+    // This command sets the max_heap_table_size within the transaction, and does not
+    // apparently stick.  But this is not really needed, so we are deactivating it.
+    if (0) {
         // 512MB
         char *query = "SET max_heap_table_size = 1024*1024*512";
