Index: branches/simmosaic_branches/ippTools/src/pxtools.c
===================================================================
--- branches/simmosaic_branches/ippTools/src/pxtools.c	(revision 24860)
+++ branches/simmosaic_branches/ippTools/src/pxtools.c	(revision 27839)
@@ -37,4 +37,6 @@
     if (!strcmp(state, "goto_cleaned")) return true;
     if (!strcmp(state, "error_cleaned")) return true;
+    if (!strcmp(state, "goto_purged")) return true;
+    if (!strcmp(state, "error_purged")) return true;
     if (!strcmp(state, "goto_scrubbed")) return true;
     if (!strcmp(state, "error_scrubbed")) return true;
@@ -42,12 +44,11 @@
     if (!strcmp(state, "update")) return true;
     if (!strcmp(state, "purged")) return true;
-    if (!strcmp(state, "goto_purged")) return true;
-    if (!strcmp(state, "error_purged")) return true;
-
+    if (!strcmp(state, "scrubbed")) return true;
     return false;
 }
 
-// 'scrubbed' is a virtual state equivalent to cleaned, but allows files to be removed
-// even if the config files is missing
+// 'scrubbed' is no longer a virtual state equivalent to cleaned, but allows files to be removed
+// even if the config files is missing.  This change was prompted as files that are cleaned can
+// be regenerated, but that is not certain after being scrubbed.
 
 
@@ -60,5 +61,5 @@
         if (!state) {
             psError(PS_ERR_PROGRAMMING, false, "%s not found in row %ld of table %s",
-		    columnName, i, tableName);
+                    columnName, i, tableName);
             return false;
         }
@@ -69,5 +70,5 @@
             // if state isn't cleaned or full we can't set it to cleaned
             psError(PS_ERR_PROGRAMMING, true, "%s with state %s may not be exported cleaned",
-		    tableName, state);
+                    tableName, state);
             return false;
         }
@@ -89,20 +90,223 @@
     psAssert (entry, "%s should at least have a place-holder", name);
     if (entry->data.str) {
-	psListIterator *iter = psListIteratorAlloc (item->data.list, PS_LIST_HEAD, true);
-	psMetadataItem *item = NULL;
-	while ((item = psListGetAndIncrement(iter))) {
-	    // need to change the name and comment
-	    psFree (item->name);
-	    item->name = psStringCopy (field);
-	    psFree (item->comment);
-	    item->comment = psStringCopy (op);
-	    if (!psMetadataAddItem(where, item, PS_LIST_TAIL, PS_META_DUPLICATE_OK)) {
-		psError(PS_ERR_UNKNOWN, false, "failed to add item %s", field);
-		psFree(where);
-		return false;
-	    }
-	}
-	psFree (iter);
-    }
-    return true;
-}
+        psListIterator *iter = psListIteratorAlloc (item->data.list, PS_LIST_HEAD, true);
+        psMetadataItem *item = NULL;
+        while ((item = psListGetAndIncrement(iter))) {
+            // need to change the name and comment
+            psFree (item->name);
+            item->name = psStringCopy (field);
+            psFree (item->comment);
+            item->comment = psStringCopy (op);
+            if (!psMetadataAddItem(where, item, PS_LIST_TAIL, PS_META_DUPLICATE_OK)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item %s", field);
+                psFree(where);
+                return false;
+            }
+        }
+        psFree (iter);
+    }
+    return true;
+}
+
+// shared code for updating the various strings for a Run
+bool pxUpdateRun(pxConfig *config, psMetadata *where, psString *pQuery, psString runTable, psString idColumn, psString fileTable, bool has_dist_group)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(where, false);
+    PS_ASSERT_PTR_NON_NULL(pQuery, false);
+    PS_ASSERT_PTR_NON_NULL(*pQuery, false);
+    PS_ASSERT_PTR_NON_NULL(runTable, false);
+    PS_ASSERT_PTR_NON_NULL(idColumn, false);
+    PS_ASSERT_PTR_NON_NULL(fileTable, false);
+
+    // make sure that -state is not the only selection parameter
+    PXOPT_LOOKUP_STR(where_state, config->args, "-state", false, false);
+    if (where_state && (psListLength(where->list) < 2)) {
+        psError(PXTOOLS_ERR_CONFIG, true, "selection by -state alone is not allowed");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(state, config->args,       "-set_state", 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(note, config->args,        "-set_note", false, false);
+
+    psString dist_group = NULL;
+    if (has_dist_group) {
+        PXOPT_LOOKUP_STR(tmp_dist_group, config->args,  "-set_dist_group", false, false);
+        dist_group = tmp_dist_group;
+    }
+
+    if ((!state) && (!label) && (!data_group) && (has_dist_group && !dist_group) && !(note)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "parameters are required");
+        return false;
+    }
+
+    if (state && ! pxIsValidState(state)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "pxIsValidState failed");
+        return false;
+    }
+
+    // first paramter is added with "SET param = 'value'"
+    // subseqent ones with ", param = 'value'"
+    char *separator = " SET ";
+    char *comma = ",";
+
+#   define addColumn(_tab, _val) \
+        do { \
+            if (_val) { \
+                psStringAppend(pQuery, "%s %s.%s = '%s'", separator, _tab, #_val, _val); \
+                separator = comma; \
+            } \
+        } while (0)
+
+    addColumn(runTable, state);
+    addColumn(runTable, data_group);
+    if (has_dist_group) {
+        addColumn(runTable, dist_group);
+    }
+    addColumn(runTable, note);
+    addColumn(runTable, label);
+
+    psString joinHook = psStringCopy("");
+    psString fileWhere = NULL;
+    if (state && !strcmp(state, "update")) {
+        psStringAppend(&joinHook, "\n JOIN %s USING(%s)", fileTable, idColumn);
+        psStringAppend(pQuery, ", %s.data_state = 'update'", fileTable);
+        psStringAppend(&fileWhere, "AND %s.data_state = 'cleaned'", fileTable);
+    }
+
+    psString whereClause =  psDBGenerateWhereSQL(where, NULL);
+    psStringAppend(pQuery, " %s", whereClause);
+    psFree(whereClause);
+    if (fileWhere) {
+        psStringAppend(pQuery, "%s", fileWhere);
+    }
+
+    bool mdok;                          // Status of MD lookup
+    if (psMetadataLookupBool(&mdok, config->args, "-pretend")) {
+        psLogMsg("pxtools", PS_LOG_INFO, "Query to run: %s\n", *pQuery);
+        return true;
+    }
+
+    if (!p_psDBRunQueryF(config->dbh, *pQuery, joinHook)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+bool pxLookupVersion(pxConfig *config, psArray **pArray)
+{
+    const char *query = "SELECT * FROM dbversion";
+
+    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)) {
+        psFree(output);
+        psError(PS_ERR_UNKNOWN, true, "no rows in dbversion");
+        return false;
+    }
+    if (psArrayLength(output) > 1) {
+        psError(PS_ERR_UNKNOWN, true, "unexpected number of rows found in dbversion: %ld",
+                psArrayLength(output));
+        return false;
+    }
+    *pArray = output;
+
+    return true;
+}
+
+psString pxGetDBVersion(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psArray *array = NULL;
+    if (!pxLookupVersion(config, &array)) {
+        psError(PS_ERR_UNKNOWN, false, "pxLookupVersion failed");
+        return NULL;
+    }
+    psMetadata *md = array->data[0];
+    if (!md) {
+        psError(PS_ERR_UNKNOWN, true, "output of pxLookupVersion is null");
+        return NULL;
+    }
+
+    psString version = psMetadataLookupStr(NULL, md, "schema_version");
+
+    return version;
+}
+
+bool pxExportVersion(pxConfig *config, FILE *file)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(file, NULL);
+
+    psArray *array = NULL;
+    if (!pxLookupVersion(config, &array) || !array) {
+        psError(PS_ERR_UNKNOWN, false, "pxLookupVersion failed");
+        return false;
+    }
+    if (!ippdbPrintMetadatas(file, array, "dbversion", true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(array);
+        return false;
+    }
+    return true;
+}
+
+bool pxCheckImportVersion(pxConfig *config, psMetadata *input)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(input, NULL);
+
+    // This code was adapted from the way camtool parses the structures.
+    // Is this really the way to do it?
+    psMetadataItem *multi_item =  psMetadataLookup(input, "dbversion");
+    if (!multi_item || (multi_item->type != PS_DATA_METADATA_MULTI)) {
+        psError(PS_ERR_UNKNOWN, true, "dbversion multi not found in input");
+        return false;
+    }
+
+    psMetadataItem *dbversion = psListGet(multi_item->data.list, 0);
+    if (!dbversion) {
+        psError(PS_ERR_UNKNOWN, true, "dbversion not found in input");
+        return false;
+    }
+
+    if (!strcmp(dbversion->name, "dbversion")) {
+        // horray
+        psMetadata *md = dbversion->data.md;
+        psString schema_version = pxGetDBVersion(config);
+        if (!schema_version) {
+            psError(PS_ERR_UNKNOWN, false, "pxGetDBVersion failed");
+            return false;
+        }
+
+        psString import_version = psMetadataLookupStr(NULL, md, "schema_version");
+        if (import_version && strcmp(import_version, schema_version)) {
+            psError(PS_ERR_UNKNOWN, true, "input file schema_version: %s does not match data base: %s",
+                import_version, schema_version);
+            return false;
+        } else if (!import_version) {
+            psError(PS_ERR_UNKNOWN, true, "input file schema_version is NULL");
+            return false;
+        } else {
+            // YIPPEE this file is the same version
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "Unexpected config dump format");
+        return false;
+    }
+
+    return true;
+}
