#!/mod/bin/jimsh

source /mod/webif/lib/setup
require system.class settings.class ts.class rsv.class browse.class \
    queue.class \
    lock plugin safe_delete pretty_size

source /mod/webif/lib/auto/util.jim

set ::auto::settings [settings]
set ::auto::loglevel [$::auto::settings autolog]

set ::auto::prelocked 0
set ::auto::earlyexit 0
set ::auto::force 0

while {[llength $argv]} {
	switch -- [lindex $argv 0] {
		-d {
			incr ::auto::loglevel
			set ::auto::logfd stdout
		}
		-f {
			set ::auto::force 1
		}
		-prelocked {
			set ::auto::prelocked 1
		}
		-logprefix {
			set argv [lrange $argv 1 end]
			if {[llength $argv]} {
				set ::auto::logprefix [lindex $argv 0]
			}
		}
		-test {
			set ::auto::earlyexit 1
		}
		default {
			# Pass to rest of script.
			break
		}
	}
	set argv [lrange $argv 1 end]
}

# Acquire lock
if {$::auto::logfd ne "unset"} {
	puts $::auto::logfd "Acquiring lock..."
}
if {!$::auto::prelocked && ![acquire_lock webif_autoscan]} {
	if {$::auto::loglevel > 1} {
		system plog auto "Could not acquire lock."
	}
	puts "Could not acquire exclusive lock, terminating."
	exit
}

::auto::loginit

######################################################################
# Determine if it's time to run

if {[system uptime] < 180} {
	::auto::log "Aborting, system has just booted." 2
	exit
}

::auto::dsc
::auto::oktorun
if {!$::auto::force} {
	set autofreq [$::auto::settings autofreq]
	if {$autofreq == 0} { set autofreq 10 }

	set timesincelast $(([clock seconds] - [$::auto::settings autolast]) \
	    / 60)
	if {$timesincelast < $autofreq} {
		::auto::log "Aborting, not yet time to run." 2
		::auto::log "  elapsed (minutes): $timesincelast (<$autofreq)" 2
		exit
	}
}

if {$::auto::earlyexit} {
	if {!$::auto::force} {
		$::auto::settings autolast [clock seconds]
	}
	puts "Early exit."
	exit
}

#########################################################################
# Initialisation

set scanstart [clock milliseconds]
::auto::log "Auto processing starting" 2

::auto::tmpdir "webif_auto"

if {[system pkginst undelete]} {
	set ::auto::dustbin "[system dustbin]"
} else {
	set ::auto::dustbin ""
}

set ::auto::root [system mediaroot]
file stat "$::auto::root/" rootstat
set ::auto::rootdev $rootstat(dev)

#########################################################################
# Utility functions

set ::auto::flagdb {}
set ::auto::activeflags {}
set ::auto::extraflags {}

proc ::auto::buildflagdb {dir {active {}}} {{seen {}} {indent 0}} {
	variable flagdb
	variable activeflags
	variable extraflags
	variable dustbin
	variable rootdev

	incr indent 2
	log "[string repeat " " $indent]\[$dir]" 3

	if {$dir eq $dustbin} {
		log "[string repeat " " $indent]Dustbin, skipping." 3
		incr indent -2
		return
	}

	file stat "$dir/" st

	if {[specialdir $dir]} {
		# Special folder
		if {$st(dev) != $rootdev} {
			log "[string repeat " " $indent\
			    ]Special folder on different device, skipping." 3
			incr indent -2
			return
		}
		if {[llength $active]} {
			set active {}
			log "[string repeat " " $indent\
			    ]Special folder, overriding recursion." 3
		}
	}

	# Already seen
	set key "$st(dev):$st(ino)"
	if {$key in $seen} {
		log "[string repeat " " $indent]Already seen, skipping." 3
		incr indent -2
		return
	}
	lappend seen $key

	set flags [lmap i \
	    [glob -nocomplain -directory $dir -tails ".auto*"] \
	    { string range $i 5 end }]

	foreach x $extraflags {
		foreach f [lmap i \
		    [glob -nocomplain -directory $dir -tails ".$x"] \
		    { string range $i 1 end }] {
			ladd flags $f
		}
	}

	foreach f $flags {
		# If recursive flag found, add to active set.
		if {[string index $f end] eq "R"} {
			ladd active [string range $f 0 end-1]
		}
	}

	# Add any active flags to the set.
	foreach f $active {
		ladd flags $f
	}

	if {[llength $flags]} {
		lappend flagdb $dir $flags
		log "[string repeat " " $indent]    $flags" 3
		foreach f $flags { ladd activeflags $f }
	}

	foreach entry [readdir -nocomplain $dir] {
		if {[file isdirectory "$dir/$entry"]} {
			buildflagdb "$dir/$entry" $active
		}
	}

	set active {}

	incr indent -2
}

set ::auto::recalcdirs {}

proc ::auto::recalcdir {dir} {
	variable recalcdirs
	ladd recalcdirs $dir
}

proc ::auto::direntries {dir callback} {
	foreach entry [readdir -nocomplain $dir] {
		if {![string match {*.ts} $entry]} continue
		if {[catch {set ts [ts fetch "$dir/$entry"]}]} continue
		if {$ts == 0} continue
		if {[$callback $ts] eq "STOP"} break
	}
}

proc ::auto::flagscan {root flag callback {recurse 1}} {
	variable flagdb
	variable activeflags

	if {$flag ni $activeflags} {
		log "No $flag flags in filesystem, suppressing scan."
		return
	}

	if {!$recurse} {
		# Just this exact directory
		if {[dict exists $flagdb $root] && $flag in $flagdb($root)} {
			log "[string toupper $flag]: \[$root]" 2
			$callback $root
		}
		return
	}

	set rootl [string length $root]

	foreach {dir flags} $flagdb {
		if  {![string equal -length $rootl $dir $root]} continue
		if {$flag in $flags} {
			oktorun
			dsc
			log "[string toupper $flag]: \[$dir]" 2
			if {[$callback $dir] eq "STOP"} break
		}
	}
}

alias ::auto::autoflagscan ::auto::flagscan

######################################################################
# Plugin registration

set ::auto::plugins {}

proc ::auto::register {plugin {priority 500} {fn ""}} {
	variable plugins

	if {$fn eq ""} { set fn "::${plugin}::run" }
	lappend plugins [list $plugin $fn $priority]
	log "Registered $plugin with priority $priority ($fn)" 2
}

proc ::auto::register_flag {plugin flag} {
	variable extraflags
	ladd extraflags $flag
	log "Registered flag '$flag' for $plugin" 2
}

# Backwards compatibility with legacy plugins
set ::auto::legacy {}
proc register {type fn {priority 50}} {
	set module [lindex [split $fn :] 2]

	switch -- $type {
		predecryptscan { incr priority 600 }
		postdecryptscan { incr priority 500 }
		prededupscan { incr priority 800 }
		postdedupscan { incr priority 700 }
		preshrinkscan { incr priority 400 }
		postshrinkscan { incr priority 300 }
		prempgscan { incr priority 300 }
		postmpgscan { incr priority 200 }
		premp3scan { incr priority 300 }
		postmp3scan { incr priority 200 }
		preexpirescan { incr priority 900 }
		postexpirescan { incr priority 800 }
		postdecryptsingledir {
			::auto::log \
			    "Mapping ::${module}::rundir on to $fn" 1
			alias ::${module}::rundir $fn
			return
		}
		default {
			::auto::log \
			    "Legacy registration ignored for $type $fn" 1
			return
		}
	}
	::auto::register $module $priority $fn
	lappend ::auto::legacy $module
}

######################################################################
# Load plugins

# Bundled
eval_plugins auto 1 "" /mod/webif/lib/auto/plugin

# Third party

# temporary for legacy plugins
set settings $::auto::settings
set root $::auto::root
eval_plugins auto 1
unset settings root

######################################################################
# Set expected variables for legacy plugins

if {[llength $::auto::legacy]} {
	::auto::log "Legacy plugins in use - $::auto::legacy" 1

	set settings $::auto::settings
	set root $::auto::root
	alias log ::auto::log
	alias scan_run ::auto::flagscan
	alias scanup ::auto::autoflagscanup
}

######################################################################
# Run media flag database

::auto::log "Scanning media for flags..."
set st [clock milliseconds]
::auto::buildflagdb $::auto::root
::auto::log "Scan completed ([::auto::elapsed $st] seconds)"
::auto::log "Active flags: $::auto::activeflags"
if {$::auto::loglevel > 1} {
	foreach {dir flags} $::auto::flagdb {
		::auto::log "[format %-80s $dir] - $flags" 2
	}
}

######################################################################
# Run plugins

set ::auto::orderedplugins [\
    lsort -index end -decreasing -integer $::auto::plugins]

set __dummy ""
proc ::auto::runplugin {fn {_plugin ""} args} {
	variable orderedplugins
	variable legacy

	foreach p $orderedplugins {
		lassign $p plugin xfn priority

		if {$_plugin ne "" && $plugin ne $_plugin} continue

		if {$fn eq "run"} {
			set rfn $xfn
		} else {
			set rfn "::${plugin}::$fn"
		}

		if {[exists -proc $rfn] || [exists -alias $rfn]} {
			set st [clock milliseconds]
			log [string repeat * 56] 2
			log "*********> $rfn (Priority $priority)" 2
			if {$plugin in $legacy && $fn ne "rundir"} {
				set call [list $rfn __dummy]
			} else {
				set call [list $rfn $args]
			}
			if {[catch {uplevel 1 {*}$call} msg]} {
				log "$rfn: $msg" 0
				lassign [info stacktrace] p f l
				log "    $f:$l @ $p" 0
			}
			log "<********* $rfn ([elapsed $st] seconds)" 2
		}
	}
}

set ::auto::passes {init run cleanup}
if {[lindex $argv 0] eq "-singledir"} {
	foreach dir [lrange $argv 1 end] {
		::auto::runplugin rundir "" $dir
	}
} elseif {[llength $argv] > 0} {
	foreach pass $::auto::passes {
		foreach arg $argv { ::auto::runplugin $pass $arg }
	}
} else {
	foreach pass $::auto::passes {
		::auto::runplugin $pass
	}
	$::auto::settings autolast [clock seconds]
}

if {!$::auto::prelocked} { release_lock webif_autoscan }

foreach dir $::auto::recalcdirs {
	::auto::log "Running unwatched recalculation for $dir" 2
	ts resetnew $dir
}

::auto::log "Auto processing completed in [::auto::elapsed $scanstart] seconds."

