view mgtool.php @ 109:c8cfc6cc161a

Adjust image scaling to be delayed and not being done on each resize event (for example Firefox fullscreen switching animates by default and triggers LOTS of resize events, which makes things slow.)
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 30 Oct 2016 15:22:03 +0200
parents 417cdd9f8864
children 9da8bab49711
line wrap: on
line source

#!/usr/bin/php
<?php
//
// Yet Another Image Gallery
// -- Commandline tool for creating / updating gallery
// Programmed and designed by Matti 'ccr' Hamalainen <ccr@tnsp.org>
// (C) Copyright 2015-2016 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 =
[
  [ MG_STR, "caption"     , "ImageDescription" ],
  [ MG_STR, "copyright"   , "Copyright" ],
  [ MG_STR, "model"       , "Model" ],
  [ MG_INT, "width"       , ["COMPUTED", "Width"] ],
  [ MG_INT, "height"      , ["COMPUTED", "Height"] ],
  [ MG_DVA, "fnumber"     , "FNumber" ],
  [ MG_DVA, "exposure"    , "ExposureTime" ],
  [ MG_INT, "iso"         , "ISOSpeedRatings" ],
  [ MG_STR, "lensmodel"   , "UndefinedTag:0xA434" ],
  [ MG_DVA, "focallength" , "FocalLength" ],
  [ MG_STR, "datetime"    , "DateTimeOriginal" ],
  [ MG_STR, "datetime"    , "DateTimeDigitized" ],
  [ 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
  try
  {
    $img = new Imagick($inFilename);
  }
  catch (Exception $e)
  {
    return mgError("ImageMagick exception for file '".$inFilename."':\n".$e->getMessage()."\n");
  }

  if ($img === FALSE)
    return mgError("ImageMagick could not digest the file '".$inFilename."'.\n");

  $profiles = $img->getImageProfiles("icc", true);
  $img->setImageDepth(16);
  $img->transformImageColorspace(Imagick::COLORSPACE_SRGB);
  $img->setImageColorspace(Imagick::COLORSPACE_SRGB);

  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_LANCZOS, 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_LANCZOS, 1);
      $img->setImageExtent($outDim[0], $outDim[1]);
//      $img->normalizeImage();
      $img->unsharpMaskImage(0, 0.5, 1, 0.05);
    }
  }


  $img->setImageDepth(8);
  $img->setFormat($outFormat);
  $img->setImageCompression(Imagick::COMPRESSION_JPEG);
  $img->setImageCompressionQuality($outQuality);

  $img->stripImage();
  if (!empty($profiles))
    $img->profileImage("icc", $profiles["icc"]);

  $img->writeImage($outFilename);
  $img->removeImage();
  return TRUE;
}


//
// Converts one value (mainly from EXIF tag information)
// by doing explicit type casting and special conversions.
//
function mgConvertExifData($val, $vtype)
{
  switch ($vtype)
  {
    case MG_STR: return is_array($val) ? $val : (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 = [];
  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 = [];
    }

    // 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 = [];
  $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]] = ["caption" => $m[3], "hide" => ($m[1] == "#"), "hide_contents" => ($m[1] == "%"), "used" => FALSE];
      else
      if (preg_match("/^([#%]?)\s*(\S+?)$/", $str, $m))
        $captions[$m[2]] = ["hide" => ($m[1] == "#"), "hide_contents" => ($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, $countDoDelete;
  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";
    $countDoDelete++;
    if ($flagDoDelete)
      rmdir($path);
  }
  else
  if (file_exists($path))
  {
    if (!$recurse)
      echo " - ".$path."\n";

    $countDoDelete++;
    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 mgSortFunc($a, $b)
{
  if (isset($a["datetime"]) && isset($b["datetime"]))
    return strcmp($b["datetime"], $a["datetime"]);
  else
  if (isset($a["base"]) && isset($b["base"]))
    return strcmp($b["base"], $a["base"]);
  else
    return 0;
}


function mgWriteGalleryCache($cacheFilename, &$gallery, &$entries, &$parentEntry)
{
  // Store gallery cache for this directory
  $images = [];
  $albums = [];
  $output = [];

  // If we are hiding contents of an album, generate no data
  if (!$parentEntry["hide_contents"])
  {
    foreach ($entries as $ename => &$edata)
    {
      if ($edata["hide"])
        continue;

      unset($edata["hide"]);
      if ($edata["type"] == 0)
        $images[$ename] = &$edata;
      else
        $albums[$ename] = &$edata;

      $output[$ename] = &$edata;
    }

    uasort($images, "mgSortFunc");
    krsort($albums);

    // Choose gallery album image
    if (count($images) > 0)
    {
      if (isset($gallery["albumpic"]) &&
          isset($images[$gallery["albumpic"]]))
      {
        $parentEntry["image"] = $gallery["albumpic"];
      }
      else
      {
        end($images);
        $parentEntry["image"] = key($images);
      }
    }
    else
    {
      foreach ($albums as $aid => &$adata)
      if (isset($adata["image"]))
      {
        $parentEntry["image"] = &$adata;
        break;
      }
    }
  }

  $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($output, TRUE).";\n".
    "?>";

  if (@file_put_contents($cacheFilename, $str, LOCK_EX) === FALSE)
    return mgError("Error writing '".$cacheFilename."'\n");

  return TRUE;
}


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 = [];
  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] = ["type" => 1, "base" => $dirFile, "ext" => "", "mtime" => filemtime($realFile)];
    }
    else
    if (preg_match("/^([^\/]+)(".mgGetSetting("format_exts").")$/i", $dirFile, $dirMatch))
      $entries[$dirFile] = ["type" => 0, "base" => $dirMatch[1], "ext" => $dirMatch[2], "mtime" => filemtime($realFile), "hide" => false];
  }
  closedir($dirHandle);

  mgCheckQuit();

  // Cleanup mode
  if ($mode == GCMD_CLEAN)
  {
    $gallery = [];

    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 = [];
    $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 = [];

      mgCheckQuit(TRUE);

      // 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,
              [mgGetSetting("med_width"), mgGetSetting("med_height")],
              "JPEG", mgGetSetting("med_quality"), TRUE);
          }

          if ($updFlags & GUPD_TN_IMAGE)
          {
            echo "2";
            mgConvertImage($efilename, $tnFilename,
              [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)
      {
        // Set some of the album data here
        $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);
        mgCopyEntryData($edata, $tmp, MG_BOOL, "hide_contents", "hide_contents", FALSE);
      }
    }

    echo "\r".$path." ..... DONE\n";

    mgCheckQuit(TRUE);
  }
  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;
  }

  // Finish update modes
  if ($mode == GCMD_UPDATE || $mode == GCMD_RESCAN)
  {
    // Store gallery cache for this directory
    if ($writeMode && !mgWriteGalleryCache($cacheFilename, $gallery, $entries, $parentEntry))
      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");
    }
    $countDoDelete = 0;
    $flagDoDelete = FALSE;
    mgProcessGalleries(GCMD_CLEAN, mgCArg(3));
    echo "--\n";
    if ($countDoDelete == 0)
    {
      echo "Nothing to clean!\n";
    }
    else
    if (mgYesNoPrompt("Really delete the above files and directories?"))
    {
      echo "Deleting ...\n";
      $flagDoDelete = TRUE;
      mgProcessGalleries(GCMD_CLEAN, mgCArg(3));
    }
    else
    {
      echo "Okay, canceling operation.\n";
    }
    break;

  case "config":
  case "dump":
    if (mgReadSettings() === FALSE)
      die("MGallery not configured.\n");

    foreach ($mgDefaults as $key => $dval)
    {
      $sval = mgGetSetting($key);
      if ($cmd == "dump")
      {
        printf("%-20s = %s\n", $key, mgGetDValStr($dval[0], $sval));
      }
      else
      {
        printf("%-20s = %s%s\n",
          $key,
          mgGetDValStr($dval[0], $sval),
          ($dval[1] !== NULL && $sval !== $dval[1]) ? " (default: ".mgGetDValStr($dval[0], $dval[1]).")" : "");
      }
    }
    break;

  default:
    mgError("Unknown option/command '".$cmd."'.\n");
    break;
}

?>