#!/usr/bin/php
<?php

date_default_timezone_set("America/Los_Angeles");
$time_start = microtime(true);

// Yuba
//                                      //
//////////////////////////////////////////
$version = "0.4.7.2";

// Treat these directories as files
$wopt_bundles = array(	"app",
						"bundle", 
						"sparsebundle", 
						"photoslibrary", 
						"aplibrary", 
						"apvault", 
						"abbu", 
						"calendar",
						"framework",
						"plugin",
						"kext", 
						"rtfd"
						);
							
foreach ($wopt_bundles as $bundle) {
	$wopt_nodescend[] = "*.".$bundle;
	}

// Ignore matching files and directories
$wopt_ignore = array(		".DS_Store",
							".DocumentRevisions-V100", 
							".Spotlight-V100",
							".TemporaryItems",
							".apdisk",
							".com.apple.timemachine.donotpresent",
							".fseventsd",
							".metadata-never-index",
							".neofinder.abemeda.volinfo.xml"
							);

if (in_array("-hash", $argv)) { $wopt_hash = 1; } else { $wopt_hash = 0; }

$wopt_thumb_size = "256";
$wopt_thumb_factor = $wopt_thumb_size/128;

// Location of binaries used to collect metadata

$bin_gfi = "/Applications/Xcode.app/Contents/Developer/usr/bin/GetFileInfo";
$bin_mediainfo = "/opt/local/bin/mediainfo";
$bin_exiftool = "/opt/local/bin/exiftool";

// Functions

function stringPrint($string) {
	echo $string.@str_repeat(" ", (10-strlen($string)));
	}

function getWoptString() {
	global $wopt_nodescend, $wopt_ignore, $wopt_hash, $wopt_mediainfo, $wopt_exiftool, $wopt_thumb_size, $wopt_thumb_factor, $wopt_tmpdir, $wopt_paranoid;
	return array(	array("nodescend", $wopt_nodescend), 
					array("ignore", $wopt_ignore),
					array("hash", $wopt_hash),
					array("mediainfo", $wopt_mediainfo),
					array("exiftool", $wopt_exiftool),
					array("thumb_size", $wopt_thumb_size),
					array("thumb_factor", $wopt_thumb_factor),
					array("wopt_paranoid", $wopt_paranoid)
					);
	}

class plistParser extends XMLReader {
	public function parseString($string) { $this->XML($string); return $this->process(); }
    private function process() {
    	$this->read();
    	if($this->nodeType !== XMLReader::DOC_TYPE || $this->name !== "plist") { throw new Exception(sprintf("Error parsing plist. nodeType: %d -- Name: %s", $this->nodeType, $this->name), 2); }
		if(!$this->next("plist") || $this->nodeType !== XMLReader::ELEMENT || $this->name !== "plist") { throw new Exception(sprintf("Error parsing plist. nodeType: %d -- Name: %s", $this->nodeType, $this->name), 3); }
		$plist = array(); while($this->read()) { if($this->nodeType == XMLReader::ELEMENT) { $plist[] = $this->parse_node(); } }
		if(count($plist) == 1 && $plist[0]) { return $plist[0]; } else { return $plist; }
		}
	private function parse_node() {
		if($this->nodeType !== XMLReader::ELEMENT) return;
		switch($this->name) {
			case 'data': return base64_decode($this->getNodeText()); break;
			case 'real': return floatval($this->getNodeText()); break;
			case 'string': return $this->getNodeText(); break;
			case 'integer': return intval($this->getNodeText()); break;
			case 'date': return $this->getNodeText(); break;
			case 'true': return true; break;
			case 'false': return false; break;
			case 'array': return $this->parse_array(); break;
			case 'dict': return $this->parse_dict(); break;
			default: throw new Exception(sprintf("Not a valid plist. %s is not a valid type", $this->name), 4);
			}			
		}
	private function parse_dict() {
		$array = array(); $this->nextOfType(XMLReader::ELEMENT);
		do { if($this->nodeType !== XMLReader::ELEMENT || $this->name !== "key") { if(!$this->next("key")) { return $array; } } $key = $this->getNodeText(); $this->nextOfType(XMLReader::ELEMENT); $array[$key] = $this->parse_node(); $this->nextOfType(XMLReader::ELEMENT, XMLReader::END_ELEMENT); }
		while($this->nodeType && !$this->isNodeOfTypeName(XMLReader::END_ELEMENT, "dict")); return $array;
		}
	private function parse_array() {
		$array = array(); $this->nextOfType(XMLReader::ELEMENT);
		do { $array[] = $this->parse_node(); $this->nextOfType(XMLReader::ELEMENT, XMLReader::END_ELEMENT); }
		while($this->nodeType && !$this->isNodeOfTypeName(XMLReader::END_ELEMENT, "array")); return $array;
		}
	private function getNodeText() { $string = $this->readString(); $this->nextOfType(XMLReader::END_ELEMENT); return $string; }
	private function nextOfType() { $types = func_get_args(); $this->read(); while($this->nodeType && !(in_array($this->nodeType, $types))) { $this->read(); } }
	private function isNodeOfTypeName($type, $name) { return $this->nodeType === $type && $this->name === $name; }
	}

function parseMediaInfo ($xml) {
	$xml = simplexml_load_string($xml);
    $data = array();
    $data['version'] = (string) $xml['version'];
    foreach ($xml->File->track as $track) {
		$trackType = strtolower($track['type']);
		$trackId = isset($track['streamid']) ? $track['streamid'] : 1;
		$trackId = (string)$trackId;
		$trackData = [];
		foreach ($track as $rawKey => $rawVal) {
			$key = strtolower($rawKey);
			$val = (string)$rawVal;
			if ($key == 'stream_identifier') { continue; }
			if (!array_key_exists($key, $trackData)) {
				$trackData[$key] = array($val);
				} elseif (!in_array($val, $trackData[$key])) {
				$trackData[$key][] = $val;
				}
            }
		if ($trackType == 'general') {
			$data['file']['general'] = $trackData;
			} else {
			$data['file'][$trackType][$trackId] = $trackData;
			}
		}
	return $data;
	}

function bashcolor($str,$fgcolor="white",$bgcolor=null) {
	static $fgcolors = array('black' => '0;30', 'dark gray' => '1;30', 'blue' => '0;34', 'light blue' => '1;34', 'green' => '0;32', 'light green' => '1;32', 'cyan' => '0;36', 'light cyan' => '1;36', 'red' => '0;31', 'light red' => '1;31', 'purple' => '0;35', 'light purple' => '1;35', 'brown' => '0;33', 'yellow' => '1;33', 'light gray' => '0;37', 'white' => '1;37', 'underline' => '4');
	static $bgcolors = array('black' => '40', 'red' => '41', 'green' => '42', 'yellow' => '43', 'blue' => '44', 'magenta' => '45', 'cyan' => '46', 'light gray' => '47');
	$out="";
	if (!isset($fgcolors[$fgcolor])) { $fgcolor='white'; }
	if (!isset($bgcolors[$bgcolor])) { $bgcolor=null; }
	if ($fgcolor) { $out .= "\033[{$fgcolors[$fgcolor]}m"; }
	if ($bgcolor) { $out .= "\033[{$bgcolors[$bgcolor]}m"; }
	$out .= $str."\033[0m";
	return $out;
	}

// Path arguments

$zpath = realpath($argv[1]);
if (!is_dir($zpath)) { echo "Usage: walk <path> <dbfile>"; die; }
if (isset($argv[2]) && is_dir($argv[2])) {
	$dbprefix = realpath($argv[2]);
	} else {
	$dbprefix = ".";
	}

if (is_writable($zpath)) { $wopt_paranoid = 1; } else { $wopt_paranoid = 0; }

// File checks

$stamp = date("Y-m-d_H-i-s", time());
$wopt_tmpdir = "/tmp/WalkWalk_".$stamp."/";
if (!is_dir($wopt_tmpdir)) { mkdir($wopt_tmpdir); }
$base = preg_replace("/[^A-Za-z0-9\.]/", "_", basename($zpath));
if (!$base) { $base = "root"; }
$dbfile = $dbprefix."/".$stamp."_".$base.".sqlite3";
if (file_exists($dbfile)) { echo "File \"".$dbfile."\" already exists!"; die; }

// Banner

echo "Yuba ".$version."\n";
echo "-----------------------------------------------\n";
$banner = $zpath." -> ".$dbfile;
echo $banner."\n";
echo str_repeat("-", strlen($banner))."\n";

// Disk info

$host = gethostname();
$disks = shell_exec("diskutil list 2>&1");

if (substr($zpath, 0, 9) != "/Volumes/") {
	$zbase = "/";
	} else {
	$zparts = explode("/", $zpath);
	$zbase = "/Volumes/".$zparts[2];
	}

$diskutil = shell_exec("diskutil info ".$zbase." 2>&1");
$getstats = array(	"Volume Name", 
					"Protocol", 
					"Volume UUID", 
					"Device Location", 
					"Volume Total Space",
					"Volume Available Space",
					"Level Type"
					);
foreach ($getstats as $stat) {
	preg_match("/(".$stat.":)(.*)(\n)/",$diskutil,$matches);
	if (isset($matches[2])) {
		if (substr($stat, -5, 5) == "Space") {
			$pieces = explode(" ", trim($matches[2]));
			$summary = $pieces[0]." ".$pieces[1];
			$stats[$stat] = $summary;
			} else {
			$stats[$stat] = trim($matches[2]);
			}
		}
	}

$dstats = serialize($stats);
if ($zpath == "/") {
	$type = "Startup disk";
	} elseif (strtolower($zpath) == strtolower("/Volumes/".$stats["Volume Name"])) {
	if ($stats["Protocol"] == "Disk Image") {
		$type = "Disk image";
		} else {
		$type = "External disk";
		}
	} else {
	$type = "Folder";
	}

$profile = shell_exec("system_profiler SPHardwareDataType SPStorageDataType SPThunderboltDataType SPUSBDataType 2>&1");
$qlmanage = shell_exec("qlmanage -m 2>&1");
$sysvers = shell_exec("sw_vers 2>&1");

// Database

// use mysql?
//$dbo = new PDO("mysql:dbname=testdb;host=127.0.0.1", $user, $pass);

$dbo = new PDO("sqlite:".$dbfile);
$dbo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$dbo->query("PRAGMA page_size = 4096");
$dbo->query("PRAGMA cache_size = 10000");
$dbo->query("PRAGMA locking_mode = EXCLUSIVE");
$dbo->query("PRAGMA synchronous = NORMAL");
$dbo->query("PRAGMA journal_mode = WAL");

$dbo->exec("CREATE TABLE files (
				id INTEGER PRIMARY KEY, 
				parent INTEGER, 
				Pathname TEXT, 
				Path TEXT, 
				Filename TEXT, 
				Extension TEXT, 
				Type TEXT, 
				items INTEGER, 
				newest INTEGER, 
				stat TEXT, 
				LinkTarget TEXT, 
				RealPath TEXT, 
				Inode INTEGER, 
				Size INTEGER, 
				Perms INTEGER, 
				Owner TEXT, 
				ATime INTEGER, 
				MTime INTEGER, 
				CTime INTEGER, 
				gfi_type TEXT, 
				gfi_attr TEXT, 
				gfi_created TEXT, 
				hash TEXT, 
				tinfo TEXT
				)");

$dbo->exec("CREATE TABLE mdls (
				id INTEGER PRIMARY KEY, 
				hasmeta INTEGER, 
				DateAdded TEXT, 
				ContentType TEXT, 
				Creator TEXT, 
				Kind TEXT, 
				UserTags TEXT, 
				FSInvisible INTEGER, 
				PixelWidth INTEGER, 
				PixelHeight INTEGER, 
				spotlight TEXT
				)");
				
$dbo->exec("CREATE TABLE metadata (
				id INTEGER PRIMARY KEY, 
				duration TEXT, 
				mediainfo TEXT, 
				exiftool TEXT
				)");

$dbo->exec("CREATE TABLE thumbs (
				id INTEGER PRIMARY KEY, 
				thumb BLOB
				)");

$dbo->exec("CREATE TABLE _walkwalk (
				version TEXT, 
				opts TEXT, 
				host TEXT, 
				zpath TEXT, 
				type TEXT, 
				nodescended INTEGER, 
				ignored INTEGER, 
				stats TEXT, 
				qlmanage TEXT, 
				sysvers TEXT, 
				diskutil TEXT, 
				disks TEXT, 
				profile TEXT, 
				status TEXT
				)");

$stmt = $dbo->prepare("INSERT INTO _walkwalk VALUES (:version, :opts, :host, :zpath, :type, :nodescended, :ignored, :stats, :qlmanage, :sysvers, :diskutil, :disks, :profile, :status)");
$stmt->BindValue(":version",$version." (".posix_getuid().")");
$stmt->BindValue(":opts",serialize(getWoptString()));
$stmt->BindValue(":host",$host);
$stmt->BindValue(":zpath",$zpath);
$stmt->BindValue(":type",$type);
$stmt->BindValue(":nodescended",null);
$stmt->BindValue(":ignored",null);
$stmt->BindValue(":stats",$dstats);
$stmt->BindValue(":qlmanage",$qlmanage);
$stmt->BindValue(":sysvers",$sysvers);
$stmt->BindValue(":diskutil",$diskutil);
$stmt->BindValue(":disks",$disks);
$stmt->BindValue(":profile",$profile);
$stmt->BindValue(":status","aborted");
$stmt->execute();

// Iterate

$nodescended = 0;
$ignored = 0;
$files = new RecursiveIteratorIterator(
	new RecursiveCallbackFilterIterator(
		new RecursiveDirectoryIterator(
			// start in parent dir to include self
      		dirname($zpath),
			RecursiveDirectoryIterator::SKIP_DOTS
			),
		function ($current, $key, $iterator) use ($wopt_ignore, $wopt_nodescend) {
			global $nodescended, $ignored, $zpath;
			$clean = true;
			// ensure we don't traverse zpath siblings
			if ($zpath != "/" && (substr($current->getRealpath(), 0, strlen($zpath)+1) != $zpath."/") && ($current->getRealpath() != $zpath)) {
				$clean = false;
				}
			// filenames to ignore
			if (is_array($wopt_ignore)) {
				foreach ($wopt_ignore as $wildcard) {
					if (fnmatch($wildcard, $current->getFilename())) {
						$clean = false;
						$ignored++;
						echo "\nSkipping: ".$current->getRealpath()."\n\n";
						}
					}
				}
			// directories to ignore
			if (is_array($wopt_nodescend)) {
				foreach ($wopt_nodescend as $wildcard) {
					if (fnmatch($wildcard, $current->getPath())) {
						$clean = false;
						$nodescended++;
						echo "\nNodescending: ".$current->getRealpath()."\n\n";
						}
					}
				}
			return $clean;
			}
		),
	RecursiveIteratorIterator::SELF_FIRST,
	RecursiveIteratorIterator::CATCH_GET_CHILD
	);

// Debug input array

/*
foreach ($files as $splFileInfo) {
	echo "====================================================================================\n";
	print_r($splFileInfo);
	echo "====================================================================================\n";
	echo "getRealPath =   ".$splFileInfo->getRealPath()."\n";
	echo "getPathname =   ".$splFileInfo->getPathname()."\n";
	echo "isLink =        ".$splFileInfo->isLink()."\n";
	if ($splFileInfo->isLink()) {
		echo "getLinkTarget = ".$splFileInfo->getLinkTarget()."\n";
		} else {
		echo "getLinkTarget = N/A\n";
		}
	echo "getType =       ".$splFileInfo->getType()." (".filetype($splFileInfo->getPathname()).")\n";
	echo "isDir =         ".$splFileInfo->isDir()."\n";
	echo "isFile =        ".$splFileInfo->isFile()."\n";
	echo "-----------------------------------------------------------------------\n";
	echo "getPath =       ".$splFileInfo->getPath()."\n";
	echo "getFilename =   ".$splFileInfo->getFilename()."\n";
	echo "getBasename =   ".$splFileInfo->getBasename()." (".basename($splFileInfo->getPathname()).")\n";
	echo "getExtension =  ".$splFileInfo->getExtension()."\n";
	echo "-----------------------------------------------------------------------\n";
	if (!$splFileInfo->isLink()) {
		echo "getATime =      ".$splFileInfo->getAtime()." (".fileatime($splFileInfo->getPathname()).")\n";
		echo "getMTime =      ".$splFileInfo->getMtime()." (".filemtime($splFileInfo->getPathname()).")\n";
		echo "getCTime =      ".$splFileInfo->getCtime()." (".filectime($splFileInfo->getPathname()).")\n";
		echo "getInode =      ".$splFileInfo->getInode()." (".fileinode($splFileInfo->getPathname()).")\n";
		echo "getPerms =      ".$splFileInfo->getPerms()." (".fileperms($splFileInfo->getPathname()).")\n";
		echo "getGroup =      ".$splFileInfo->getGroup()." (".filegroup($splFileInfo->getPathname()).")\n";
		echo "getOwner =      ".$splFileInfo->getOwner()." (".fileowner($splFileInfo->getPathname()).")\n";
		echo "getSize =       ".$splFileInfo->getSize()." (".filesize($splFileInfo->getPathname()).")\n";
		}
	}
die;
*/

// Check perms

if (posix_getuid()) {
	
	echo bashcolor("You are not root. Checking file readability: ", "red");
	
	echo "\n";
	
	$oops = 0;
	foreach ($files as $splFileInfo) {
		$path = $splFileInfo->getRealPath();
		if (!is_readable($path)) {
			$oops = 1;
			echo "x";
			} else {
			echo ".";
			}
		}
		
	echo "\n\n";
		
	if ($oops) {
		echo "Some files could not be read. Continue? (Y/n)";
		$line = trim(fgets(fopen("php://stdin","r")));
		$line = $line ?: "y";
		if($line != "y"){
			echo "Exiting!\n"; die;
			}
		}
	
	} else {
	
	echo bashcolor("Running as root. Some QuickLook plugins may not be available.", "red");
	echo "\n";
	
	}

$fixatimes = 0;
if ($wopt_paranoid) {

	echo bashcolor("\nFilesystem is writable. You can choose:\n(c) Preserve ctimes (default)\n(a) Preserve atimes\n", "purple");
	$line = trim(fgets(fopen("php://stdin","r"))) ?: "c";
	if ($line == "a") { $fixatimes = 1; }
	
	}
	
// Filetypes for special handling

$m_files = array(	"mkv",
					"ogg",
					"avi",
					"wav",
					"mpeg",
					"mpg",
					"vob",
					"mp4",
					"m2v",
					"mp3",
					"asf",
					"wma",
					"wmv",
					"qt",
					"mov",
					"rm",
					"ifo",
					"ac3",
					"dts",
					"aac",
					"ape",
					"flac",
					"aiff", 
					"m2ts"	);

$e_files = array(	"ai",
					"aiff",
					"ape",
					"asf",
					"avi",
					"bmp",
					"divx",
					"dng",
					"doc",
					"docx",
					"eps",
					"epub",
					"exe",
					"exif",
					"fla",
					"flac",
					"flv",
					"gif",
					"icc",
					"iso",
					"jpg",
					"jpeg",
					"m2ts",
					"m4a",
					"m4b",
					"m4v",
					"mkv",
					"mobi",
					"azw",
					"azw3",
					"mov",
					"qt",
					"mp3",
					"mp4",
					"mpeg",
					"mpg",
					"m2v",
					"nef",
					"numbers",
					"ogg",
					"pages",
					"pdf",
					"pict",
					"png",
					"ppm",
					"ppt",
					"psd",
					"psb",
					"qif",
					"raw",
					"rtf",
					"sr2",
					"srf",
					"svg",
					"swf",
					"tiff",
					"tif",
					"torrent",
					"vcf",
					"vob",
					"wav",
					"webm",
					"wma",
					"wmv",
					"xls",
					"xlsx",
					"xmp",
					"zip"	);

foreach ($e_files as $ext) { $e_files[] = strtoupper($ext); }
foreach ($m_files as $ext) { $m_files[] = strtoupper($ext); }

// Inserts

foreach ($files as $splFileInfo) {

	$type = $splFileInfo->getType();
	if ($type == "dir") {
		foreach ($wopt_bundles as $bundle) {
			$check = ".".$bundle;
			if (substr($splFileInfo->getFilename(), -(strlen($check)), strlen($check)) == $check) { $type = "bundle"; }
			}	
		}
	
	if ($type != "link") { $atime = $splFileInfo->getATime(); }
	$pathname = $splFileInfo->getPathname();
	$path = $splFileInfo->getPath();
	$filename = $splFileInfo->getFilename();
	$extension = $splFileInfo->getExtension();
	$shellpath = escapeshellarg($pathname);
	
	echo bashcolor("\n".$pathname."\n","blue");
	
	if (!$type) { echo "\nBREAK: can't determine type of ".$pathname; die; }
	
	if ($type != "link") {
		$stat = chop(@shell_exec("stat -x ".$shellpath." 2>&1"));	
		} else {
		$stat = null;
		}
	
	if ($type != "link" && $wopt_paranoid) {
		$pre_access = null;
		$pre_modify = null;
		$pre_change = null;
		foreach (explode("\n", $stat) as $line) {
			$check = substr($line, 0, 6);
			if ($check == "Access") { $pre_access = $line; }
			if ($check == "Modify") { $pre_modify = $line; }
			if ($check == "Change") { $pre_change = $line; }
			}
		}

	// Determine ID of parent dir by querying database

	$parent = $dbo->query("SELECT id FROM files WHERE (Pathname='".str_replace("'", "''", $path)."')")->fetch()['id'];
	stringPrint("parent");

	// Gather file attributes
	
	$stmt = $dbo->prepare("INSERT INTO files VALUES (:id, :parent, :Pathname, :Path, :Filename, :Extension, :Type, :items, :newest, :stat, :LinkTarget, :RealPath, :Inode, :Size, :Perms, :Owner, :ATime, :MTime, :CTime, :gfi_type, :gfi_attr, :gfi_created, :hash, :tinfo)");
	
	if ($type == "dir") {
		$size = trim(shell_exec("du -ks ".$shellpath." | cut -f1"))*1024;
		} elseif ($type == "file" || $type == "bundle") {
		$size = $splFileInfo->getSize();
		} else {
		$size = null;
		}
	$stmt->BindValue(":Size",@$size);
	stringPrint(floor($size/1024)."k");
	
	if ($parent) {
		$stmt->BindValue(":parent",$parent);
		} else {
		$stmt->BindValue(":parent",0);
		}
	
	stringPrint("items");
	
	if ($type == "dir" || $type == "bundle" ) {
		$items = chop(@shell_exec("find ".$shellpath." \( ! -regex '.*/\..*' \) | wc -l 2>&1"))-1;
		} else {
		$items = null;
		}
	
	$stmt->BindValue(":items",@$items);
	
	stringPrint("newest");
	
	if ($type == "dir") {
		$newest = filemtime(chop(shell_exec("find ".$shellpath." -type f -not -path '*/\.*' -print0 | xargs -0 stat -f \"%m %N\" | sort -rn | head -1 | cut -f2- -d\" \"")));
		} else {
		$newest = null;
		}
	
	$stmt->BindValue(":newest",@$newest);
	
	$stmt->BindValue(":stat",@$stat);
	
	$stmt->BindValue(":Pathname",$pathname);
	$stmt->BindValue(":Path",$path);
	$stmt->BindValue(":Filename",$filename);
	$stmt->BindValue(":Extension",$extension);
	$stmt->BindValue(":Type",$type);
	
	if ($type == "link") {
	
		$stmt->BindValue(":LinkTarget",$splFileInfo->getLinkTarget());
		$stmt->BindValue(":RealPath",$splFileInfo->getRealPath());
	
		} else {
		
		$stmt->BindValue(":Inode",$splFileInfo->getInode());
		$stmt->BindValue(":Perms",$splFileInfo->getPerms());
		$stmt->BindValue(":Owner",$splFileInfo->getOwner().":".$splFileInfo->getGroup());
		
		$stmt->BindValue(":ATime",$atime);
		$stmt->BindValue(":CTime",$splFileInfo->getCTime());
		$stmt->BindValue(":MTime",$splFileInfo->getMTime());
	
		}
		
	stringPrint("attr");

	$gfiparts = explode("\n", chop(shell_exec($bin_gfi." -P ".$shellpath." 2>&1")));
	if (is_array($gfiparts)) {
		foreach ($gfiparts as $line) {
			list($label, $value) = explode(": ", $line);
			$gfi[$label] = isset($value) ? trim($value,"\"") : null;
			}
		}
	
	$writegfitype = @$gfi['type'].":".@$gfi['creator'];
	if ($writegfitype == "\\0\\0\\0\\0:\\0\\0\\0\\0" || $writegfitype == ":") { $writegfitype = null; }
	
	$stmt->BindValue("gfi_type",$writegfitype);
	$stmt->BindValue("gfi_attr",@$gfi['attributes']);
	$stmt->BindValue("gfi_created",strtotime($gfi['created']));
	
	stringPrint("gfi");
	
	if ($wopt_hash && $type != "link") {
		$stmt->BindValue(":hash",md5_file($pathname));
		StringPrint("hash");
		} else {
		$stmt->BindValue(":hash",null);
		StringPrint("(x)hash");
		}

	$thumb = $wopt_tmpdir.$filename.".png";
	/*
	// insane workaround for insane apple bug
	if (@pathinfo($pathname)['extension'] == "mp3" | "MP3" | "m4a" | "M4A" | "m4b" | "M4B" ) {
		$zprefix = "sudo qlmanage";
		} else {
		$zprefix = "qlmanage";
		}
	*/
	$zprefix = "sudo qlmanage"; // prevent hang
	@exec($zprefix." -t -f ".$wopt_thumb_factor." -o ".$wopt_tmpdir." ".$shellpath." 2>&1");
	stringPrint("thumb");
	if ($size && !file_exists($thumb) && (in_array($extension, $m_files) || in_array($extension, $e_files))) {
		@exec("ffmpegthumbnailer -i ".$shellpath." -o \"".$thumb."\" -s ".$wopt_thumb_size." -c png 2>&1");
		stringPrint("fthumb");
		} else {
		stringPrint("(x)fthumb");
		}
	if (file_exists($thumb) && filesize($thumb)) {
		$stmt->BindValue(":tinfo",serialize(getimagesize($thumb)));
		}
		
	$stmt->execute();
	stringPrint("->files");
	
	// Gather spotlight metadata
	
	$stmt = $dbo->prepare("INSERT INTO mdls VALUES (:id, :hasmeta, :DateAdded, :ContentType, :Creator, :Kind, :UserTags, :FSInvisible, :PixelWidth, :PixelHeight, :spotlight)");
	$mdls = shell_exec("mdls -plist - ".$shellpath." 2>&1");
	stringPrint("mdls");
	
	if ($mdls != $pathname.": could not find ".$pathname.".\n") {
	
		$stmt->BindValue(":hasmeta",1);
		$parser = new plistParser();
		$spotlight = $parser->parseString($mdls);
		
		$stmt->BindValue(":DateAdded",@strtotime($spotlight['kMDItemDateAdded']));
		$stmt->BindValue(":ContentType",@$spotlight['kMDItemContentType']);
		$stmt->BindValue(":Creator",@$spotlight['kMDItemCreator']);
		$stmt->BindValue(":Kind",@$spotlight['kMDItemKind']);
		if (isset($spotlight['kMDItemUserTags'])) {
			$stmt->BindValue(":UserTags",serialize($spotlight['kMDItemUserTags']));
			}
		$stmt->BindValue(":FSInvisible",@$spotlight['kMDItemFSInvisible']);
		$stmt->BindValue(":PixelWidth",@$spotlight['kMDItemPixelWidth']);
		$stmt->BindValue(":PixelHeight",@$spotlight['kMDItemPixelHeight']);
		$stmt->BindValue(":spotlight",serialize($spotlight));
		
		} else {
		$stmt->BindValue(":hasmeta",0);
		}
	$stmt->execute();
	stringPrint("->mdls");
		
	// Gather external metadata
	
	$stmt = $dbo->prepare("INSERT INTO metadata VALUES (:id, :duration, :mediainfo, :exiftool)");
	
	if ($type != "dir" && in_array($extension, $m_files)) {
		$minfo = parseMediaInfo(shell_exec($bin_mediainfo." --Output=OLDXML ".$shellpath." 2>&1"));
		if ($minfo['file']['general']['duration'][0]) {
			$stmt->BindValue(":duration",$minfo['file']['general']['duration'][0]);
			} else {
			$stmt->BindValue(":duration",null);
			}
		$stmt->BindValue(":mediainfo",serialize($minfo));
		stringPrint("minfo");
		} else {
		$stmt->BindValue(":duration",null);
		$stmt->BindValue(":mediainfo",null);
		stringPrint("(x)info");
		}
	
	if ($type != "dir" && $type != "link" && in_array($extension, $e_files)) {
		$stmt->BindValue(":exiftool",serialize(eval("return ".`$bin_exiftool -php $shellpath`)));
		stringPrint("etool");
		} else {
		$stmt->BindValue(":exiftool",null);
		stringPrint("(x)etool");
		}
	$stmt->execute();
	stringPrint("->meta");
	
	// Gather thumbnail
		
	$stmt = $dbo->prepare("INSERT INTO thumbs VALUES (:id, :thumb)");
	if (file_exists($thumb) && filesize($thumb)) {
		$stmt->BindValue(":thumb",file_get_contents($thumb));
		} else {
		$stmt->BindValue(":thumb",null);
		}
	$stmt->execute();
	stringPrint("->thumb");
	
	// Set fileatime back to original value
	
	if ($type != "link" && is_writable($pathname) && $fixatimes) {
		@exec("touch -at `date -r ".$atime." +%Y%m%d%H%M.%S` ".$shellpath." 2>&1");
		stringPrint("touch");
		}
	
	echo "\n";
	
	// Double check stat for file against pre-run value
	
	if ($type != "link" && $wopt_paranoid) {
		
		$restat = chop(@shell_exec("stat -x ".$shellpath." 2>&1"));
		$post_access = null;
		$post_modify = null;
		$post_change = null;
	
		foreach (explode("\n", $restat) as $line) {
			$check = substr($line, 0, 6);
			if ($check == "Access") { $post_access = $line; }
			if ($check == "Modify") { $post_modify = $line; }
			if ($check == "Change") { $post_change = $line; }
			}
		
		$message = array();
		if ($pre_access != $post_access) {
			$message[] = "ATIME CHANGED";
			}
		if ($pre_modify != $post_modify) {
			$message[] = "MTIME CHANGED";
			}
		if ($pre_change != $post_change) {
			$message[] = "CTIME CHANGED";
			}
		
		if (count($message)) { echo bashcolor(str_repeat(" ",99).implode(" ~ ", $message), "purple"); }

		}
	
	}

echo "\n\n\n";

// Footer

$seconds = floor($time = microtime(true)-$_SERVER["REQUEST_TIME_FLOAT"]);
$fbanner = "Finished in ".$seconds." seconds";
echo str_repeat("-", strlen($fbanner))."\n".$fbanner."\n";
echo "Files ignored: ".$ignored." / Files nodescended: ".$nodescended."\n";

// Write app summary values

$dbo->exec("UPDATE _walkwalk SET nodescended=".$nodescended.", ignored=".$ignored.", status='completed_in_".$seconds."'");

?>