diff mgtool.php @ 0:ac688606ec4b

Initial import of code.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 13 May 2015 07:27:40 +0300
parents
children a07447c02b13
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mgtool.php	Wed May 13 07:27:40 2015 +0300
@@ -0,0 +1,780 @@
+#!/usr/bin/php
+<?php
+//
+// Yet Another Image Gallery
+// Commandline tool for creating / updating gallery
+// (C) Copyright 2015 Tecnic Software productions (TNSP)
+//
+require_once "mgallery.inc.php";
+
+//
+// Array for specifying what will be copied and converted
+// from the image file's EXIF information tag(s).
+//
+$galExifConversions = array(
+  array(MG_STR, "caption"     , "ImageDescription"),
+  array(MG_STR, "copyright"   , "Copyright"),
+  array(MG_STR, "model"       , "Model"),
+  array(MG_INT, "width"       , array("COMPUTED", "Width")),
+  array(MG_INT, "height"      , array("COMPUTED", "Height")),
+  array(MG_DVA, "fnumber"     , "FNumber"),
+  array(MG_DVA, "exposure"    , "ExposureTime"),
+  array(MG_INT, "iso"         , "ISOSpeedRatings"),
+  array(MG_DVA, "focallength" , "FocalLength"),
+  array(MG_STR, "datetime"    , "DateTimeOriginal"),
+  array(MG_STR, "datetime"    , "DateTimeDigitized"),
+  array(MG_INT, "filesize"    , "FileSize"),
+);
+
+define("GCMD_UPDATE"      , 1);
+define("GCMD_RESCAN"      , 2);
+define("GCMD_CLEAN"       , 3);
+
+define("GCLEAN_CACHES"    , 0x01);
+define("GCLEAN_THUMBNAILS", 0x02);
+define("GCLEAN_ALL"       , 0x0f);
+
+
+define("GUPD_MED_IMAGE"   , 0x01);
+define("GUPD_TN_IMAGE"    , 0x02);
+define("GUPD_IMAGES"      , 0x0f);
+define("GUPD_EXIF_INFO"   , 0x10);
+define("GUPD_CAPTION"     , 0x20);
+
+
+//
+// Convert and scale image file function, for generating
+// the intermediate size images and thumbnails. Uses the
+// PHP ImageMagick bindings.
+//
+function mgConvertImage($inFilename, $outFilename, $outDim, $outFormat, $outQuality, $thumb)
+{
+  // Create conversion entity
+  $img = new Imagick($inFilename);
+  if ($img === FALSE)
+    return mgError("ImageMagick could not digest the file '".$inFilename."'.\n");
+
+  if ($outDim !== FALSE)
+  {
+    // Get dimensions, setup background
+    $dim = $img->getImageGeometry();
+    //$img->setImageBackgroundColor(imagick::COLOR_BLACK);
+    $img->setGravity(imagick::GRAVITY_CENTER);
+
+
+    if ($dim["width"] < $dim["height"])
+    {
+      $stmp = $outDim[0];
+      $outDim[0] = $outDim[1];
+      $outDim[1] = $stmp;
+    }
+
+    if ($dim["width"] != $outDim[0] || $dim["height"] != $outDim[1])
+    {
+      $outDim[1] = ($dim["height"] * $outDim[0]) / $dim["width"];
+    }
+
+    // Act based on image size vs. desired size and $thumb mode
+    if ($thumb || $dim["width"] > $outDim[0] || $dim["height"] > $outDim[1])
+    {
+      // Image is larger
+      $img->resizeImage($outDim[0], $outDim[1], Imagick::FILTER_QUADRATIC, 1);
+      $img->setImageExtent($outDim[0], $outDim[1]);
+      $img->normalizeImage();
+      $img->unsharpMaskImage(0, 0.5, 1, 0.05);
+    }
+    if ($dim["width"] < $outDim[0] || $dim["height"] < $outDim[1])
+    {
+      // Image is smaller than requested dimension(s)?
+      $img->resizeImage($outDim[0], $outDim[1], Imagick::FILTER_QUADRATIC, 1);
+      $img->setImageExtent($outDim[0], $outDim[1]);
+      $img->unsharpMaskImage(0, 0.5, 1, 0.05);
+    }
+  }
+
+  $img->setFormat($outFormat);
+  $img->setCompressionQuality($outQuality);
+
+  $img->stripImage();
+  $img->writeImage($outFilename);
+  $img->removeImage();
+  return TRUE;
+}
+
+
+//
+// Converts one value (mainly from EXIF tag information)
+// by doing explicing type casting and special conversions.
+//
+function mgConvertExifData($val, $vtype)
+{
+  switch ($vtype)
+  {
+    case MG_STR: return (string) $val;
+    case MG_INT: return intval($val);
+    case MG_BOOL: return intval($val);
+    case MG_DVA:
+      if (sscanf($val, "%d/%d", $v1, $v2) == 2)
+      {
+        if ($v1 < $v2)
+          return $val;
+        else
+          return sprintf("%1.1f", $v1 / $v2);
+      }
+      else
+        return $val;
+
+    default:
+      return $val;
+  }
+}
+
+
+//
+// Conditionally copies one "field" from an associated array/hash to another.
+// If destination is already SET, nothing will be done. If source does
+// not exist (e.g. one or more of the keys do not exist in source array),
+// a default value will be used, if provided.
+// Source may have multi-depth keys, destination has one key.
+//
+function mgCopyEntryData(&$dst, $src, $vtype, $dkey, $skeys, $default = NULL)
+{
+  // Is destination already set?
+  if (isset($dst[$dkey]))
+    return FALSE;
+
+  // If input key is not array, change it into one
+  if (!is_array($skeys))
+    $skeys = array($skeys);
+
+  // Traverse input array by using consequent keys
+  $tmp = &$src;
+  foreach ($skeys as $skey)
+  {
+    if (!array_key_exists($skey, $tmp))
+    {
+      // Key didn't exist, try for default
+      if ($default !== NULL)
+        $dst[$dkey] = $default;
+      return FALSE;
+    }
+    else
+      $tmp = &$tmp[$skey];
+  }
+
+  // Optionally convert the input value
+  $dst[$dkey] = mgConvertExifData($tmp, $vtype);
+  return TRUE;
+}
+
+
+//
+// Attempt to get gallery album data from various sources.
+//
+function mgGetAlbumData($galBasePath, $galPath)
+{
+  // Check path permissions
+  $galData = array();
+  if (is_readable($galPath))
+  {
+    // First, try to read gallery/album info file
+    $filename = mgGetPath($galPath, "info_file");
+    if ($filename !== FALSE && file_exists($filename))
+    {
+      mgDebug("Reading INFOFILE: ".$filename."\n");
+      if (($galData = parse_ini_file($filename, FALSE)) === FALSE)
+        $galData = array();
+    }
+
+    // Read header file, if any, and we don't have "header" field set yet
+    $filename = mgGetPath($galPath, "header_file");
+    if ($filename !== FALSE && file_exists($filename) &&
+        !isset($galData["header"]))
+    {
+      mgDebug("Reading HEADERFILE: ".$filename."\n");
+      $galData["header"] = file_get_contents($filename);
+    }
+
+    // Check for alternate key/values for album title/caption
+    if (isset($galData["title"]) && !isset($galData["caption"]))
+    {
+      $galData["caption"] = $galData["title"];
+      unset($galData["title"]);
+    }
+  }
+  else
+    $galData["hide"] = TRUE;
+
+  // If caption is not set, use last path component for
+  // a fallback value in case we don't discover proper title
+  // from other sources we can't check here yet.
+  $path = explode("/", $galPath);
+  $galData["fallback_caption"] = ucfirst(str_replace("_", " ", end($path)));
+
+  // Last, store the current gallery path
+  $len = strlen($galBasePath);
+  if ($len < strlen($galPath) && substr($galPath, 0, $len) == $galBasePath)
+    $galData["path"] = substr($galPath, $len);
+  else
+    $galData["path"] = "";
+
+  return $galData;
+}
+
+
+function mgReadCaptionsFile($galBasePath, $galPath)
+{
+  $captions = array();
+  $filename = mgGetPath($galPath, "captions_file");
+  if ($filename === FALSE || ($fp = @fopen($filename, "rb")) === FALSE)
+    return $captions;
+
+  mgDebug("Reading CAPTIONS: ".$filename."\n");
+
+  // Read and parse data
+  while (!feof($fp))
+  {
+    $str = trim(fgets($fp));
+    // Ignore comments and empty lines
+    if ($str != "#" && $str != "")
+    {
+      if (preg_match("/^(#?)\s*(\S+?)\s+(.+)$/", $str, $m))
+        $captions[$m[2]] = array("caption" => $m[3], "hide" => ($m[1] == "#"), "used" => FALSE);
+      else
+      if (preg_match("/^(#?)\s*(\S+?)$/", $str, $m))
+        $captions[$m[2]] = array("hide" => ($m[1] == "#"), "used" => FALSE);
+    }
+  }
+
+  fclose($fp);
+  return $captions;
+}
+
+
+function mgMakeDir($path, $perm)
+{
+  if (!file_exists($path))
+  {
+    if (mkdir($path, $perm, TRUE) === false)
+      return mgError("Could not create directory '".$path."'\n");
+  }
+  return TRUE;
+}
+
+
+function mgYesNoPrompt($msg, $default = FALSE)
+{
+  echo $msg." [".($default ? "Y/n" : "y/N")."]? ";
+  $sprompt = strtolower(trim(fgets(STDIN)));
+
+  if ($default)
+    return ($sprompt == "n");
+  else
+    return ($sprompt == "y");
+}
+
+
+function mgDelete($path, $recurse)
+{
+  global $flagDoDelete;
+  if (is_dir($path))
+  {
+    if (($dirHandle = @opendir($path)) === FALSE)
+      return mgError("Could not read directory '".$path."'.\n");
+
+    while (($dirFile = @readdir($dirHandle)) !== FALSE)
+    {
+      if ($dirFile != "." && $dirFile != "..")
+        mgDelete($path."/".$dirFile, $recurse);
+    }
+    closedir($dirHandle);
+
+    echo " - ".$path." [DIR]\n";
+    if ($flagDoDelete)
+      rmdir($path);
+  }
+  else
+  {
+    if (!$recurse)
+      echo " - ".$path."\n";
+    if ($flagDoDelete)
+      unlink($path);
+  }
+}
+
+
+function mgCheckQuit($now = FALSE)
+{
+  global $flagQuit;
+
+  // Dispatch pending signals
+  pcntl_signal_dispatch();
+
+  // Check result
+  if ($now && $flagQuit)
+    mgFatal("Quitting.\n");
+
+  return $flagQuit;
+}
+
+
+function mgNeedUpdate($entry, $field, $cvalue)
+{
+  if (!array_key_exists($field, $entry))
+    return TRUE;
+  
+  return ($entry[$field] < $cvalue);
+}
+
+
+function mgHandleDirectory($mode, $basepath, $path, $parentData, $parentEntry, $writeMode, $startAt)
+{
+  global $galExifConversions, $galTNPath, $galCleanFlags;
+
+  // Get cache file path
+  if (($cacheFilename = mgGetPath($path, "cache_file")) === FALSE)
+    return mgError("Cache filename / path not set.\n");
+
+  mgCheckQuit(TRUE);
+
+  // Read directory contents
+  $entries = array();
+  if (($dirHandle = @opendir($path)) === FALSE)
+    return mgError("Could not read directory '".$path."'.\n");
+
+  while (($dirFile = @readdir($dirHandle)) !== FALSE)
+  {
+    $realFile = $path."/".$dirFile;
+    if (is_dir($realFile))
+    {
+      if ($dirFile[0] != "." && $dirFile != $galTNPath)
+        $entries[$dirFile] = array("type" => 1, "base" => $dirFile, "ext" => "", "mtime" => filemtime($realFile));
+    }
+    else
+    if (preg_match("/^(\S+)(".mgGetSetting("format_exts").")$/i", $dirFile, $dirMatch))
+      $entries[$dirFile] = array("type" => 0, "base" => $dirMatch[1], "ext" => $dirMatch[2], "mtime" => filemtime($realFile), "hide" => false);
+  }
+  closedir($dirHandle);
+
+  mgCheckQuit();
+
+  // Cleanup mode
+  if ($mode == GCMD_CLEAN)
+  {
+    $gallery = array();
+
+    if ($writeMode)
+    {
+      if ($galCleanFlags & GCLEAN_CACHES)
+        mgDelete($cacheFilename, FALSE);
+
+      if ($galCleanFlags & GCLEAN_THUMBNAILS)
+        mgDelete($path."/".$galTNPath, TRUE);
+    }
+  }
+  else
+  // Update modes
+  if ($mode == GCMD_UPDATE || $mode == GCMD_RESCAN)
+  {
+    // Load current cache file, if it exists
+    $galEntries = array();
+    $cacheTime = -1;
+    if ($mode == GCMD_UPDATE && file_exists($cacheFilename))
+    {
+      $cacheTime = filemtime($cacheFilename);
+      @include $cacheFilename;
+    }
+
+    // Read caption data
+    $captions = mgReadCaptionsFile($basepath, $path);
+    $gallery = mgGetAlbumData($basepath, $path);
+    if ($parentData !== NULL && $parentEntry !== NULL)
+    {
+      $gallery["parent"] = $parentData;
+      mgCopyEntryData($gallery, $parentEntry, MG_STR, "caption", "caption");
+    }
+
+    // Start actual processing
+    $nentries = count($entries);
+    $nentry = 0;
+    echo $path." .. ";
+    foreach ($entries as $ename => &$edata)
+    {
+      printf("\r%s (%1.1f%%) ..", $path, ($nentry * 100.0) / $nentries);
+
+      $nentry++;
+      $efilename = $path."/".$ename;
+
+      if (array_key_exists($ename, $galEntries))
+        $galEntry = &$galEntries[$ename];
+      else
+        $galEntry = array();
+
+      mgCheckQuit(FALSE);
+
+      // Update with captions file data, if any
+      if (array_key_exists($ename, $captions))
+      {
+        foreach ($captions[$ename] as $ckey => $cval)
+          $edata[$ckey] = $cval;
+      }
+
+      // Handle entry based on type
+      if ($edata["type"] == 0)
+      {
+        $updFlags = 0;
+        $tnPath = $path."/".$galTNPath;
+        $medFilename = $tnPath."/".$edata["base"].mgGetSetting("med_suffix").$edata["ext"];
+        $tnFilename = $tnPath."/".$ename;
+        $capFilename = $path."/".$edata["base"].".txt";
+
+        // Check what we need to update ..
+        if (!file_exists($medFilename) || filemtime($medFilename) < $edata["mtime"])
+          $updFlags |= GUPD_MED_IMAGE;
+
+        if (!file_exists($tnFilename) || filemtime($tnFilename) < $edata["mtime"])
+          $updFlags |= GUPD_TN_IMAGE;
+
+        if (mgNeedUpdate($galEntry, "mtime", $edata["mtime"]))
+          $updFlags |= GUPD_EXIF_INFO;
+
+        if (file_exists($capFilename) &&
+          mgNeedUpdate($galEntry, "mtime", filemtime($capFilename)))
+          $updFlags |= GUPD_CAPTION;
+
+        // Check for EXIF info
+        if (($updFlags & GUPD_EXIF_INFO) &&
+            ($exif = @exif_read_data($efilename)) !== FALSE)
+        {
+          echo "%";
+          foreach ($galExifConversions as $conv)
+            mgCopyEntryData($edata, $exif, $conv[0], $conv[1], $conv[2]);
+        }
+        else
+        {
+          // Copy old data that is not yet in new
+          echo "*";
+          foreach ($galEntry as $okey => $odata)
+          {
+            if (!array_key_exists($okey, $edata))
+              $edata[$okey] = $odata;
+          }
+        }
+
+        // Generate thumbnails, etc.
+        if ($updFlags & GUPD_IMAGES)
+        {
+          mgMakeDir($tnPath, 0755);
+
+          if ($updFlags & GUPD_MED_IMAGE)
+          {
+            echo "1";
+            mgConvertImage($efilename, $medFilename,
+              array(mgGetSetting("med_width"), mgGetSetting("med_height")),
+              "JPEG", mgGetSetting("med_quality"), TRUE);
+          }
+
+          if ($updFlags & GUPD_TN_IMAGE)
+          {
+            echo "2";
+            mgConvertImage($efilename, $tnFilename,
+              array(mgGetSetting("tn_width"), mgGetSetting("tn_height")),
+              "JPEG", mgGetSetting("tn_quality"), TRUE);
+          }
+        }
+
+        // Check for .txt caption file
+        if ($updFlags & GUPD_CAPTION)
+        {
+          echo "?";
+          if (($tmpData = @file_get_contents($capFilename)) !== FALSE)
+            $edata["caption"] = $tmpData;
+        }
+      }
+      else
+      if ($edata["type"] == 1)
+      {
+        $tmp = mgGetAlbumData($basepath, $efilename);
+        mgCopyEntryData($edata, $tmp, MG_STR, "caption", "caption");
+        mgCopyEntryData($edata, $tmp, MG_STR, "caption", "fallback_caption");
+        mgCopyEntryData($edata, $tmp, MG_STR, "caption", "title");
+        mgCopyEntryData($edata, $tmp, MG_BOOL, "hide", "hide", FALSE);
+      }
+    }
+
+    echo "\r".$path." ..... DONE\n";
+
+    mgCheckQuit(TRUE);
+
+    // Store gallery cache for this directory
+    if ($writeMode)
+    {
+      $images = array();
+      $albums = array();
+      foreach ($entries as $ename => &$edata)
+      {
+        if ($edata["hide"])
+          continue;
+
+        unset($edata["hide"]);
+        if ($edata["type"] == 0)
+          $images[$ename] = &$edata;
+        else
+          $albums[$ename] = &$edata;
+      }
+      ksort($images);
+      ksort($albums);
+
+      $str =
+        "<?\n".
+        "\$galData = ".var_export($gallery, TRUE).";\n".
+        "\$galAlbumsIndex = ".var_export(array_keys($albums), TRUE).";\n".
+        "\$galImagesIndex = ".var_export(array_keys($images), TRUE).";\n".
+        "\$galEntries = ".var_export($entries, TRUE).";\n".
+        "?>";
+
+      if (@file_put_contents($cacheFilename, $str, LOCK_EX) === FALSE)
+        return mgError("Error writing '".$cacheFilename."'\n");
+    }
+  }
+  else
+    mgFatal("Invalid work mode '".$mode."'.\n");
+
+  mgCheckQuit(TRUE);
+
+  // Recurse to subdirectories
+  foreach ($entries as $ename => $edata)
+  if ($edata["type"] == 1)
+  {
+    $epath = $path."/".$ename."/";
+    $newWriteMode = ($writeMode === FALSE && $epath == $startAt) || $writeMode;
+
+    if (!mgHandleDirectory($mode, $basepath, $epath, $gallery, $edata, $newWriteMode, $startAt))
+      return FALSE;
+  }
+
+  mgCheckQuit(TRUE);
+
+  return TRUE;
+}
+
+
+function mgSigHandler($signo)
+{
+  global $flagQuit;
+  switch ($signo)
+  {
+    case SIGTERM:
+      mgFatal("Received SIGTERM.\n");
+      break;
+
+    case SIGQUIT:
+    case SIGINT:
+      $flagQuit = TRUE;
+      break;
+
+  }
+}
+
+
+function mgProcessGalleries($cmd, $path)
+{
+  global $galTNPath;
+
+  // Fetch the settings we need
+  if (mgReadSettings() === FALSE)
+    die("MGallery not configured.\n");
+
+  // Check validity of some settings
+  $galPath = mgGetSetting("base_path");
+  $galTNPath = mgCleanPath(TRUE, mgGetSetting("tn_path"));
+  if ($galTNPath != mgGetSetting("tn_path"))
+    mgError("Invalid tn_path, using '".$galTNPath."'.\n");
+
+  if (strpos($galTNPath, "/") !== FALSE || $galTNPath == "")
+    mgFatal("Invalid tn_path '".$galTNPath."'.\n");
+
+  $parentData = $parentEntry = NULL;
+  $writeMode = TRUE;
+  $startAt = NULL;
+
+  // Check for path argument
+  if ($path !== FALSE)
+  {
+    // Check the given path, needs to be "under" the gallery path
+    $cmp = mgCleanPath(TRUE, mgRealPath($galPath))."/";
+    $tmp = mgCleanPath(TRUE, mgRealPath($path))."/";
+    if (substr($tmp, 0, strlen($cmp)) != $cmp)
+      mgFatal("Path '".$path."' ('".$tmp."') does not reside under '".$galPath."' ('".$cmp."')!\n");
+
+    // Check if we need to bootstrap
+    if ($cmp != $tmp)
+    {
+      $bpath = mgCleanPath(TRUE, mgRealPath($tmp."../"));
+
+      if ($cmd != GCMD_CLEAN)
+      {
+        $cacheFile = mgGetPath($bpath, "cache_file");
+        if (!file_exists($cacheFile))
+          mgFatal("Can't start working from '".$path."', parent '".$cacheFile."' does not exist!\n");
+
+        @include($cacheFile);
+        if (!isset($galEntries) || !isset($galData))
+          mgFatal("Cache file '".$cacheFile."' is broken or stale.\n");
+      }
+
+      $writeMode = FALSE;
+      $startAt = $tmp;
+      $path = $bpath;
+
+      echo "Starting: '".$startAt."' inside '".$path."'.\n";
+    }
+    else
+      $path = $tmp;
+  }
+  else
+    $path = $galPath;
+
+  // Start working
+  echo "Gallery path: '".$galPath."', starting at '".$path."' ...\n";
+  mgHandleDirectory($cmd, $galPath, $path, $parentData, $parentEntry, $writeMode, $startAt);
+}
+
+
+function mgShowCopyright()
+{
+  global $mgProgVersion, $mgProgCopyright,
+    $mgProgInfo, $mgProgEmail;
+
+  echo
+  "MGTool ".$mgProgVersion." - MGallery management tool\n".
+  $mgProgInfo." ".$mgProgEmail."\n".
+  "(C) Copyright ".$mgProgCopyright."\n";
+}
+
+
+function mgShowHelp()
+{
+  global $argv;
+  echo
+    "Usage: ".basename($argv[0])." <command> [arguments]\n".
+    "\n".
+    "  --help      - Show this help.\n".
+    "  --version   - Show version information.\n".
+    "\n".
+    "  update [path]\n".
+    "    Update directories under <path> or all gallery dirs.\n".
+    "    Conditionally scans dirs for new or changed images and\n".
+    "    updates cache files as needed.\n".
+    "\n".
+    "  rescan [path]\n".
+    "    Like 'update', but forces all cache files to be regenerated\n".
+    "    and EXIF/caption/etc information to be re-scanned even\n".
+    "    if the file timestamps do not justify re-scanning.\n".
+    "\n".
+    "  clean <all|caches|thumbnails> [path]\n".
+    "    Delete all generated files or selectively cache files or\n".
+    "    everything inside thumbnail directories.\n".
+    "\n".
+    "  config\n".
+    "    Display configuration values.\n".
+    "\n";
+}
+
+
+//
+// Main code starts
+//
+if (php_sapi_name() != "cli" || !empty($_SERVER["REMOTE_ADDR"]))
+{
+  header("Status: 404 Not Found");
+  die();
+}
+
+pcntl_signal(SIGTERM, "mgSigHandler");
+pcntl_signal(SIGHUP,  "mgSigHandler");
+pcntl_signal(SIGQUIT, "mgSigHandler");
+pcntl_signal(SIGINT,  "mgSigHandler");
+
+
+$cmd = mgCArgLC(1);
+switch ($cmd)
+{
+  case "--version":
+  case "version":
+  case "ver":
+    mgShowCopyright();
+    break;
+
+  case FALSE:
+    // No arguments
+    mgError("Nothing to do. Showing help:\n");
+
+  case "--help":
+  case "help":
+    if ($cmd !== FALSE)
+      mgShowCopyright();
+    mgShowHelp();
+    break;
+
+  case "update": case "up": case "upd": case "upda":
+    mgProcessGalleries(GCMD_UPDATE, mgCArg(2));
+    break;
+
+  case "rescan": case "re": case "res":
+    mgProcessGalleries(GCMD_RESCAN, mgCArg(2));
+    break;
+
+  case "clean": case "cl": case "cle":
+    $cmode = mgCArgLC(2, 2);
+    switch ($cmode)
+    {
+      case "al": $galCleanFlags = GCLEAN_ALL; break;
+      case "ca": $galCleanFlags = GCLEAN_CACHES; break;
+      case "th": $galCleanFlags = GCLEAN_THUMBNAILS; break;
+      case FALSE:
+        mgFatal("Cleaning requires a mode argument.\n");
+
+      default:
+        mgFatal("Invalid clean mode '".mgCArg(2)."'.\n");
+    }
+    $flagDoDelete = FALSE;
+    mgProcessGalleries(GCMD_CLEAN, mgCArg(3));
+    echo "--\n";
+    if (mgYesNoPrompt("Really delete the above files and directories?"))
+    {
+      echo "OKAY.\n";
+    }
+    break;
+
+  case "config":
+    if (mgReadSettings() === FALSE)
+      die("MGallery not configured.\n");
+
+    foreach ($mgDefaults as $key => $dval)
+    {
+      $sval = mgGetSetting($key);
+      printf("%-20s = %s%s\n",
+        $key,
+        mgGetDValStr($dval[0], $sval),
+        ($dval[1] !== NULL && $sval !== $dval[1]) ? " (default: ".mgGetDValStr($dval[0], $dval[1]).")" : "");
+    }
+    break;
+
+  case "dump":
+    if (mgReadSettings() === FALSE)
+      die("MGallery not configured.\n");
+
+    foreach ($mgDefaults as $key => $dval)
+    {
+      $sval = mgGetSetting($key);
+      printf("%-20s = %s\n", $key, mgGetDValStr($dval[0], $sval));
+    }
+    break;
+
+  default:
+    mgError("Unknown option/command '".$cmd."'.\n");
+    break;
+}
+
+?>
\ No newline at end of file