set ::sweeper::cf "/mod/etc/sweeper.conf" proc ::sweeper::unknown {cmd args} { log "Unknown sweeper rule clause '$cmd'" 0 return 0 } ###################################################################### # Helper functions proc ::sweeper::expand {ts str} { if {[string first "%" $str] == -1} { return $str } set glist [ts genrelist] set tsg [$ts get genre] if {![dict exists $glist $tsg]} { set genre "Unknown" } else { set genre [lindex $glist($tsg) 0] } set timestamp [clock format [$ts get start] -format "%Y%m%d%H%M%S"] set map [list \ "%title" [$ts get title] \ "%genre" $genre \ "%definition" [$ts get definition] \ "%lcn" [$ts get channel_num] \ "%channel" [$ts get channel_name] \ "%duration" [$ts duration] \ "%timestamp" $timestamp \ ] set ret [string map $map $str] log " Expanded to \[$ret]" 2 return $ret } proc ::sweeper::intcomp {ref val} { lassign $val op num if {$num eq ""} { set num $op set op "==" } return [expr $ref $op $num] } proc ::sweeper::strcontains {ref val} { return [expr \ [string first [string tolower $val] [string tolower $ref]] \ >= 0] } ###################################################################### # Rule criteria proc ::sweeper::flag {ts flag} { return [$ts flag $flag] } proc ::sweeper::lcn {ts num} { return [::sweeper::intcomp [$ts get channel_num] $num] } proc ::sweeper::duration {ts dur} { return [::sweeper::intcomp [$ts duration] $dur] } proc ::sweeper::hour {ts str} { set hour [clock format [$ts get start] -format "%H"] return [::sweeper::intcomp $hour $str] } proc ::sweeper::schedduration {ts dur} { return [::sweeper::intcomp [$ts get scheddur] $dur] } proc ::sweeper::size {ts size} { return [::sweeper::intcomp [$ts size] $size] } proc ::sweeper::age {ts age} { set recage $(([clock seconds] - [$ts get end]) / 3600) log " ... Recording age: $recage" 2 return [::sweeper::intcomp $recage $age] } proc ::sweeper::wage {ts age} { set recage $(([clock seconds] - [$ts lastmod]) / 3600) log " ... Watched age: $recage" 2 return [::sweeper::intcomp $recage $age] } proc ::sweeper::definition {ts def} { return [::sweeper::strcontains [$ts get definition] $def] } proc ::sweeper::title {ts str} { return [::sweeper::strcontains [$ts get title] $str] } proc ::sweeper::synopsis {ts str} { return [::sweeper::strcontains [$ts get synopsis] $str] } proc ::sweeper::guidance {ts str} { return [::sweeper::strcontains [$ts get guidance] $str] } proc ::sweeper::genre {ts genre} { set glist [ts genrelist] set tsg [$ts get genre] if {![dict exists $glist $tsg]} { return 0 } if {[lindex $glist($tsg) 0] eq $genre} { return 1 } return 0 } proc ::sweeper::lock {ts g} { if {$g} { if {![$ts flag Locked]} { log "Locked recording." 0 $ts lock } } else { if {[$ts flag Locked]} { log "Unlocked recording." 0 $ts unlock } } return 1 } proc ::sweeper::folder_fflag {ts flag} { return [file exists "[file dirname [$ts get file]]/.$flag"] } ###################################################################### proc ::sweeper::action {ts cmds} { global root lassign $cmds cmd rest log "ACTION: $cmd\($rest)" 2 switch $cmd { continue { return 0 } stop { return 1 } preserve { return 1 } move - movecreate { set rest [::sweeper::expand $ts $rest] if {![file isdirectory "$root/$rest"]} { if {$cmd eq "move"} { log " ... No such directory $root/$rest" 2 return 1 } system mkdir_p "$root/$rest" if {![file isdirectory "$root/$rest"]} { log "Error creating directory $root/$rest" 1 return 1 } else { log " ... created directory $root/$rest" 2 } } log "Moving [$ts get file] to $rest" 0 foreach f [$ts fileset] { log " ....... $f" file rename $f "$root/$rest/[file tail $f]" if {"$root/$rest" ni $::sweeper::recalc} { lappend ::sweeper::recalc "$root/$rest" } } return 1 } lock { if {![$ts flag Locked]} { log "Locked [$ts get file]" 0 $ts lock } return 0 } unlock { if {[$ts flag Locked]} { log "Unlocked [$ts get file]" 0 $ts unlock } return 0 } default { log "Unknown action '$cmd'" 0 } } return 0 } proc ::sweeper::find {root target orig} { set dustbin [system dustbin 1] foreach e [readdir -nocomplain $root] { regsub -all -- {//} "$root/$e" "/" entry if {![file isdirectory $entry]} continue if {[string match {\[*} $e]} continue if {$e eq $dustbin} continue if {$entry eq $orig} continue if {$e eq $target} { return $entry } set ret [::sweeper::find $entry $target $orig] if {$ret ne ""} { return $ret } } return "" } proc ::sweeper::folder_apply {dir callback} { log "Applying action to recordings in $dir" 2 foreach e [readdir -nocomplain $dir] { if {![string match {*.ts} $e]} continue set entry "$dir/$e" log "+ folder_apply processing $entry" 2 if {[catch {set ts [ts fetch $entry]} msg} { log "Error reading TS file, $msg" 0 continue } if {$ts == "0"} { log "Invalid TS file." 2 continue } if {[$ts inuse]} { log "Recording in use." 2 continue } $callback $ts } } proc ::sweeper:folder_merge {src dst} { if {$src eq $dst} return log "Moving recordings from $src to $dst" 0 foreach e [readdir -nocomplain $src] { if {![string match {*.ts} $e]} continue set entry "$src/$e" log "+ Sweeper processing $entry" 2 if {[catch {set ts [ts fetch $entry]} msg} { log "Error reading TS file, $msg" 0 continue } if {$ts == "0"} { log "Invalid TS file." 2 continue } if {[$ts inuse]} { log "Recording in use." 2 continue } foreach f [$ts fileset] { log " ....... $f" file rename $f "$dst/[file tail $f]" } } if {![system rmdir_if_empty $src]} { log "Failed to remove directory" 0 foreach l [system rmdir_if_empty $src 1] { log "Blocking file: $l" 0 } } if {$dst ni $::sweeper::recalc} { lappend ::sweeper::recalc $dst } } proc ::sweeper::folder_action {ts cmds} { global root lassign $cmds cmd rest set folder [file dirname [$ts get file]] set lfolder [file tail $folder] log "FOLDER ACTION: $cmd\($rest) @\[$folder]" 2 switch $cmd { continue { return 0 } stop { return 1 } preserve { return 1 } movecreate - move { set rest [::sweeper::expand $ts $rest] if {![file isdirectory "$root/$rest"]} { if {$cmd eq "move"} { log " ... No such directory $root/$rest" 2 return 1 } system mkdir_p "$root/$rest" if {![file isdirectory "$root/$rest"]} { log "Error creating directory $root/$rest" 1 return 1 } else { log " ... created directory $root/$rest" 2 } } ::sweeper:folder_merge $folder "$root/$rest" return 1 } fileundercreate - fileunder { if {![file isdirectory "$root/$rest"]} { log " ... No such directory $root/$rest" 2 return 1 } log " - searching for $lfolder under $root/$rest" 2 set target [::sweeper::find $root/$rest $lfolder $folder] log " = $target" 2 if {$target eq "" || ![file isdirectory $target]} { log "Did not find directory." 2 if {$cmd ne "fileundercreate"} { return 1 } set target "$root/$rest/$lfolder" log "Creating $target" 0 system mkdir_p $target } ::sweeper:folder_merge $folder $target return 1 } lock { ::sweeper::folder_apply $folder [lambda {ts} { if {![$ts flag Locked]} { log "Locked [$ts get file]" 0 $ts lock } }] return 0 } unlock { ::sweeper::folder_apply $folder [lambda {ts} { if {[$ts flag Locked]} { log "Unlocked [$ts get file]" 0 $ts unlock } }] return 0 } default { log "Unknown action '$cmd'" 0 } } return 0 } proc ::sweeper::runrule {ts rule} { log "Processing \[$rule]" 2 if {[string index $rule 0] eq "#" || [llength $rule] < 2} { return 0 } set folder 0 if {[lindex $rule 0] eq "folder"} { set folder 1 set rule [lrange $rule 1 end] } while {[llength $rule] > 1} { set rule [lassign $rule cmd arg] log " $cmd\($arg)" 2 if {$folder && [exists -proc ::sweeper::folder_$cmd]} { set ret [::sweeper::folder_$cmd $ts $arg] } else { set ret [::sweeper::$cmd $ts $arg] } if {$cmd eq "action"} { return $ret } if {!$ret} { log " Nomatch" 2 break } log " MATCH" 2 } return 0 } proc ::sweeper::apply {dir cf} { if {[catch {set fp [open $cf r]} msg]} { log "Error opening sweeper ruleset ($cf), $msg" 0 return } set ::sweeper::recalc {} set rules [split [read $fp] "\n"] $fp close set runfolder 0 foreach rule $rules { if {[lindex $rule 0] eq "folder"} { incr runfolder } } log "" 2 log "--- SWEEP SCAN STARTING FOR $dir ---" 2 log "" 2 foreach e [readdir -nocomplain $dir] { set entry "$dir/$e" if {[file isdirectory $entry]} continue if {![string match {*.ts} $entry]} continue log "+ Sweeper processing $entry" 2 if {[catch {set ts [ts fetch $entry]} msg} { log "Error reading TS file, $msg" 0 continue } if {$ts == "0"} { log "Invalid TS file." 2 continue } if {[$ts inuse]} { log "Recording in use." 2 continue } foreach rule $rules { if {[lindex $rule 0] eq "folder"} continue if {[string index $rule 0] eq "#" || \ [llength $rule] < 2} continue if {[::sweeper::runrule $ts $rule]} break } } if {$runfolder > 0} { log "" 2 log " -- FOLDER RULES --" 2 log "" 2 set dustbin [system dustbin 1] foreach e [readdir -nocomplain $dir] { set entry "$dir/$e" if {![file isdirectory $entry]} continue if {[string match {\[*} $e]} continue if {$e eq $dustbin} continue log "" 2 log "==== folder $entry ====" 2 log "" 2 if {![file exists "$entry/.series"]} { log "Not series folder." 2 continue } set ts 0 foreach de [readdir -nocomplain $entry] { set dentry "$entry/$de" if {[file isdirectory $dentry]} { set ts 0 break } if {$ts ne "0"} continue if {![string match {*.ts} $dentry]} continue log " --- Considering $dentry" 2 if {[catch {set ts [ts fetch $dentry]} msg} { log "Error reading TS file, $msg" 2 continue } if {$ts == "0"} { log "Invalid TS file." 2 continue } if {[$ts inuse]} { log "Recording in use." 2 set ts 0 continue } break } if {$ts == "0"} { log "No usable recordings in folder." 2 continue } foreach rule $rules { if {[string index $rule 0] eq "#" || \ [llength $rule] < 2} continue if {[lindex $rule 0] ne "folder"} continue if {[::sweeper::runrule $ts $rule]} break } } } foreach dir $::sweeper::recalc { log "Resetting unwatched recording flag for $dir" 0 ts resetnew $dir } } proc ::sweeper::sweep {dir} { if {$dir eq $::root} return ::sweeper::apply $dir "$dir/.sweeper" } proc ::sweeper::scan {args} { ::sweeper::apply $::root $::sweeper::cf scan_run $::root sweeper ::sweeper::sweep } if {[file exists $::sweeper::cf]} { register postdecryptscan ::sweeper::scan }