set ::sweeper::cf "/mod/etc/sweeper.conf" set ::sweeper::dryrun 0 set ::sweeper::lastruleresult 0 set ::sweeper::stack {} proc ::sweeper::unknown {cmd args} { log "Unknown sweeper rule clause '$cmd'" 0 return 0 } ###################################################################### # Utility functions # Perform an integer comparison. proc ::sweeper::intcomp {ref val} { lassign $val op num if {$num eq ""} { set num $op set op "==" } return [expr $ref $op $num] } # Substring/pattern check proc ::sweeper::strcontains {ref val} { if {[string first "*" $val] > -1} { return [string match -nocase $val $ref] } return [expr \ [string first [string tolower $val] [string tolower $ref]] \ >= 0] } set ::sweeper::expand_fns { replace {2} regsub {2} asfilename {1 {inline}} } proc ::sweeper::expand_replace {ts &ret search replace} { set ret [string map [list $search $replace] $ret] } proc ::sweeper::expand_regsub {ts &ret search replace} { if {[catch { regsub -all -- $search $ret $replace ret } msg]} { log "Error. %regsub - $msg" } } proc ::sweeper::expand_asfilename {ts &ret arg} { return [system filename $arg] } set ::sweeper::brmap "( ) \[ ] \\{ \\}" # Expand a string containing tokens proc ::sweeper::expand {ts str {orig ""}} { 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 start [$ts get start] set timestamp [clock format $start -format "%Y%m%d%H%M%S"] set yyyymmdd [string range $timestamp 0 7] set hhmm [string range $timestamp 8 11] set hh [string range $hhmm 0 1] set mm [string range $hhmm 2 3] set end [$ts get end] set etimestamp [clock format $end -format "%Y%m%d%H%M%S"] set eyyyymmdd [string range $etimestamp 0 7] set ehhmm [string range $etimestamp 8 11] set ehh [string range $ehhmm 0 1] set emm [string range $ehhmm 2 3] set map [list \ "%orig" $orig \ "%title" [$ts get title] \ "%genre" $genre \ "%definition" [$ts get definition] \ "%synopsis" [$ts get synopsis] \ "%lcn" [$ts get channel_num] \ "%channel" [$ts get channel_name] \ "%duration" [$ts duration] \ \ "%filename" [$ts get file] \ "%basename" [$ts bfile] \ "%folder" [$ts dir] \ \ %epname [$ts episode_name] \ %series [$ts get seriesnum] \ %episodes [$ts get episodetot] \ %episode [$ts get episodenum] \ %epdescr [$ts epstr] \ \ "%timestamp" $timestamp \ "%yyyymmdd" $yyyymmdd \ "%hhmm" $hhmm \ "%hh" $hh \ "%mm" $mm \ "%year" [clock format $start -format "%Y"] \ "%month" $(0 + [clock format $start -format "%m"]) \ "%date" $(0 + [clock format $start -format "%d"]) \ "%2digityear" [clock format $start -format "%y"] \ "%2digitmonth" [clock format $start -format "%m"] \ "%2digitdate" [clock format $start -format "%d"] \ "%shortday" [clock format $start -format "%a"] \ "%longday" [clock format $start -format "%A"] \ "%shortmonth" [clock format $start -format "%b"] \ "%longmonth" [clock format $start -format "%B"] \ \ "%etimestamp" $etimestamp \ "%eyyyymmdd" $eyyyymmdd \ "%ehhmm" $ehhmm \ "%ehh" $ehh \ "%emm" $emm \ ] # log $map 2 set ret [string map $map $str] log " Expanded \[$str] -> \[$ret]" 2 foreach {fn params} $::sweeper::expand_fns { # log " Looking for %$fn" 2 lassign $params numargs flags set li -1 while {[set i [string first "%$fn" $ret]] >= 0} { if {$i <= $li} break set li $i # Fetch the delimiter set chpos $($i + [string length $fn] + 1) set ch [string index $ret $chpos] # log " - found at $i (delim\[$ch])" 2 # Extract the arguments set pos $chpos set fnargs {} while {[llength $fnargs] < $numargs} { incr pos set e [string first $ch $ret $pos] if {$e == -1 && [ \ dict exists $::sweeper::brmap $ch]} { set bch $::sweeper::brmap($ch) set e [string first $bch $ret $pos] } if {$e == -1} { log "Error. %$fn - [llength $fnargs]/$numargs parameters found." break } lappend fnargs [string range $ret \ $pos $($e - 1)] set pos $e } # log "FNARGS: ($fnargs)" 2 if {[llength $fnargs] == $numargs} { log " - Calling expand_$fn\($fnargs)" 2 set fnret [ ::sweeper::expand_$fn $ts ret {*}$fnargs] log " - Result: ($ret) ($fnret)" 2 } if {"inline" in $flags} { set nret [string range $ret 0 $($i - 1)] append nret $fnret append nret [string range $ret $($pos + 1) end] set ret $nret } else { set ret [string replace $ret $($i - 1) $pos] } log " FN expanded \[$str] -> \[$ret]" 2 } } return $ret } proc ::sweeper::resolvedir {dir} { global root if {$dir eq ""} { return $root } if {[string index $dir 0] eq "/"} { return $dir } return "$root/$dir" } # returns true if the arguments are actually the same file proc ::sweeper::samefile {a b} { if {![file exists $a] || ![file exists $b]} { return 0 } if {[file stat $a] eq [file stat $b]} { return 1 } return 0 } # Move/copy the set of files which make up a recording proc ::sweeper::moveset {ts dst {op rename}} { set file [$ts get file] log "${op}set($file) -> $dst" 0 # Handle alias for rename if {$op eq "move"} { set op "rename" } # Determine whether this is a cross-filesystem move. file stat [$ts get file] sts file stat $dst std set xfs 0 if {$sts(dev) ne $std(dev)} { log " Cross-filesystem - will copy then delete." 0 set xfs 1 } set fset [lsort [$ts fileset]] # Handle cross-filesystem (xfs) moves if {$xfs && $op eq "rename"} { # For cross-filesystem moves, copy the whole file set # and then delete the originals if all the copies were # successful. foreach f $fset { set tail [file tail $f] if {[::sweeper::samefile $f "$dst/$tail"]} { log " Destination is same as source." 0 return } if {[catch {file copy $f "$dst/$tail"} msg]} { log " ....... $f: XFS copy failed, $msg." 0 file delete -force "$dst/$tail" log " .... Leaving originals intact." 0 return } log " ....... $f: OK" 0 } log " Now deleting original files." 0 foreach f $fset { set tail [file tail $f] if {[file exists "$dst/$tail"] && [file size "$dst/$tail"] == [file size $f]} { file tdelete $f log " ....... $f: OK" 0 } else { log " ....... $f: ERROR, sizes differ." 0 return } } if {$op eq "rename"} { set ::sweeper::renames($file) "$dst/[file tail $file]" } return } # Otherwise - copy or local FS move. foreach f $fset { set tail [file tail $f] if {[::sweeper::samefile $f "$dst/$tail"]} { log " Destination is same as source." 0 return } if {$op eq "copy" && [file exists "$dst/$tail"]} { if {[file size "$dst/$tail"] == [file size $f]} { log " ....... $f: Already copied." 2 continue } log "Deleting truncated copy $dst/$tail" 0 file tdelete "$dst/$tail" } log " ....... $f" if {[catch {file $op $f "$dst/[file tail $f]"} msg]} { log "$op failed, $msg." 0 return } } if {$op eq "rename"} { set ::sweeper::renames($file) "$dst/[file tail $file]" } } # Search for a folder with name 'target' under 'root', skipping 'orig' 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 {\[*} [string trimleft $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 "" } # Apply a function to all recordings in a directory. proc ::sweeper::folder_apply {dir callback args} { 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 {*}$args } } proc ::sweeper::rmdir_if_empty {dir} { if {$dir eq $::root} return if {[file exists "$dir/.sweeper"]} return if {![system rmdir_if_empty $dir]} { log "Failed to remove directory" 0 foreach l [system rmdir_if_empty $dir 1] { log "Blocking file: $l" 2 } } } proc ::sweeper::qrecalc {args} { foreach dst $args { if {$dst ni $::sweeper::recalc} { lappend ::sweeper::recalc $dst } } } ###################################################################### # Rule conditions # # Parameters: # ts - instance of the ts class for the recording being processed. # arg - arguments provided for the action. # folder - true if the action is being applied to a folder. # # Return values: # # 0 - condition does not match. # 1 - condition matched. proc ::sweeper::lastrule {ts flag folder} { return $::sweeper::lastruleresult } proc ::sweeper::flag {ts flag folder} { return [$ts flag $flag] } proc ::sweeper::lcn {ts num folder} { return [::sweeper::intcomp [$ts get channel_num] $num] } proc ::sweeper::duration {ts dur folder} { return [::sweeper::intcomp [$ts duration] $dur] } proc ::sweeper::hour {ts str folder} { set hour [clock format [$ts get start] -format "%H"] return [::sweeper::intcomp $hour $str] } proc ::sweeper::now {ts str folder} { set now [clock format [clock seconds] -format "%H%M"] return [::sweeper::intcomp $now $str] } proc ::sweeper::schedduration {ts dur folder} { return [::sweeper::intcomp [expr [$ts get scheddur] / 60] $dur] } proc ::sweeper::size {ts size folder} { return [::sweeper::intcomp [$ts size] $size] } proc ::sweeper::age {ts age folder} { set recage $(([clock seconds] - [$ts get end]) / 3600) log " ... Recording age: $recage" 2 return [::sweeper::intcomp $recage $age] } proc ::sweeper::wage {ts age folder} { set recage $(([clock seconds] - [$ts lastmod]) / 3600) log " ... Watched age: $recage" 2 return [::sweeper::intcomp $recage $age] } proc ::sweeper::bookmarks {ts bookmarks folder} { return [::sweeper::intcomp [$ts get bookmarks] $bookmarks] } proc ::sweeper::definition {ts def folder} { return [::sweeper::strcontains [$ts get definition] $def] } proc ::sweeper::filename {ts str folder} { return [::sweeper::strcontains [$ts bfile] $str] } proc ::sweeper::title {ts str folder} { return [::sweeper::strcontains [$ts get title] $str] } proc ::sweeper::synopsis {ts str folder} { return [::sweeper::strcontains [$ts get synopsis] $str] } proc ::sweeper::guidance {ts str folder} { return [::sweeper::strcontains [$ts get guidance] $str] } proc ::sweeper::genre {ts genre folder} { 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::fflag {ts flag folder} { return [file exists "[$ts dir]/.$flag"] } proc ::sweeper::foldername {ts str folder} { return [::sweeper::strcontains [$ts dir] $str] } proc ::sweeper::fileexists {ts str folder} { if {[string index $str 0] ne "/"} { set str "[$ts dir]/[::sweeper::expand $ts $str]" } else { set str [::sweeper::expand $ts $str] } log " FILEEXISTS($str)" 2 return [file isfile $str] } proc ::sweeper::direxists {ts str folder} { if {[string index $str 0] ne "/"} { set str "[$ts dir]/[::sweeper::expand $ts $str]" } else { set str [::sweeper::expand $ts $str] } log " DIREXISTS($str)" 2 return [file isdirectory $str] } proc ::sweeper::series {ts flag folder} { if {!$folder} { return 0 } set dir [$ts dir] if {![file exists "$dir/.series"]} { log "Not series folder (nofile)." 2 return 0 } # Check if the .series entry is a real one versus one created # by ts resetnew set fd [open "$dir/.series"] set bytes [read $fd] close $fd set sbytes [unpack $bytes -uintle 160 32] if {$sbytes == 0} { log "Not series folder." 2 return 0 } return 1 } proc ::sweeper::textmatch {ts str folder} { if {![regexp -- {^([^~]+)~~(.*)$} $str x target pattern]} { log "No pattern in textmatch." 1 return 0 } log "Textmatch ($target) against ($pattern)" 2 return [::sweeper::strcontains [::sweeper::expand $ts $target] $pattern] } proc ::sweeper::intmatch {ts str folder} { if {![regexp -- {^([^~]+)~~(.*)$} $str x target pattern]} { log "No pattern in intmatch." 1 return 0 } if {[llength [split $pattern " "]] != 2} { log "Invalid pattern in numeric comparison." 0 return 0 } log "Intmatch ($target) against ($pattern)" 2 return [::sweeper::intcomp [::sweeper::expand $ts $target] $pattern] } ######################## # Deprecated conditions proc ::sweeper::lock {ts g folder} { if {$g} { if {![$ts flag Locked]} { log "Locked recording." 0 $ts lock } } else { if {[$ts flag Locked]} { log "Unlocked recording." 0 $ts unlock } } # Always matches return 1 } ###################################################################### # Rule actions. # # Parameters: # ts - instance of the ts class for the recording being processed. # cmd - name of action being processed. # arg - arguments provided for the action. # folder - true if the action is being applied to a folder. # # Return values: # # 0 - continue to next rule # 1 - stop processing proc ::sweeper::action_continue {ts cmd arg folder} { return 0 } proc ::sweeper::action_stop {ts cmd arg folder} { return 1 } alias ::sweeper::action_preserve ::sweeper::action_stop proc ::sweeper::action_move {ts cmd arg folder} { set dir [::sweeper::resolvedir [::sweeper::expand $ts $arg]] set create 0 set ocmd $cmd if {[string range $cmd end-5 end] eq "create"} { set create 1 set cmd [string range $cmd 0 end-6] } if {$folder} { ::sweeper::folder_apply [$ts dir] \ ::sweeper::action_move $ocmd $arg 0 if {$cmd eq "move"} { ::sweeper::rmdir_if_empty [$ts dir] } return 1 } if {![file isdirectory $dir]} { if {!$create} { log " ... No such directory $dir" 2 return 1 } if {!$::sweeper::dryrun} { system mkdir_p $dir if {![file isdirectory $dir]} { log "Error creating $dir" 1 return 1 } else { log " ... created $dir" 2 } } } log "$cmd [$ts get file] to $arg" 0 if {!$::sweeper::dryrun} { ::sweeper::moveset $ts $dir $cmd ::sweeper::qrecalc $dir [$ts dir] if {$cmd eq "move"} { ::sweeper::rmdir_if_empty [$ts dir] } } return 1 } alias ::sweeper::action_movecreate ::sweeper::action_move alias ::sweeper::action_copy ::sweeper::action_move alias ::sweeper::action_copycreate ::sweeper::action_move proc ::sweeper::action_fileunder {ts cmd arg folder} { if {!$folder} { log "$cmd action can only be applied to folders." return 1 } set dir [::sweeper::resolvedir $arg] if {![file isdirectory $dir]} { log " ... No such directory $dir" 2 return 1 } set folder [$ts dir] set lfolder [file tail $folder] log " - searching for $lfolder under $dir" 2 set target [::sweeper::find $dir $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 "$dir/$lfolder" if {$target eq $folder} { log "Skipping merge to self." 2 return 1 } log "Creating $target" 0 if {!$::sweeper::dryrun} { system mkdir_p $target } } if {$::sweeper::dryrun} { return 1 } ::sweeper::folder_apply $folder [ lambda {ts target} { ::sweeper::moveset $ts $target rename }] $target ::sweeper::rmdir_if_empty $folder ::sweeper::qrecalc $folder $target return 1 } alias ::sweeper::action_fileundercreate ::sweeper::action_fileunder proc ::sweeper::action_renamefile {ts cmd arg folder} { set dir [$ts dir] if {$folder} { ::sweeper::folder_apply $dir \ ::sweeper::action_renamefile $cmd $arg 0 return 0 } set arg [system filename [::sweeper::expand $ts $arg [$ts bfile]]] set file [$ts get file] log "Renaming $file to $arg" 0 if {[file exists "$dir/$arg.ts"]} { log "... ERROR Target already exists" 0 return 0 } if {[::sweeper::samefile $file "$dir/$arg.ts"]} { log "... ERROR Target is the same as source" 0 return 0 } if {!$::sweeper::dryrun} { ts renamegroup $file $arg if {[file exists "$dir/$arg.ts"]} { $ts setfile "$dir/$arg.ts" set ::sweeper::renames($file) "$dir/$arg.ts" } else { log "... ERROR renamefile somehow failed" 0 } } return 0 } proc ::sweeper::action_settitle {ts cmd arg folder} { if {$folder} { ::sweeper::folder_apply [$ts dir] \ ::sweeper::action_settitle $cmd $arg 0 return 0 } set arg [::sweeper::expand $ts $arg [$ts get title]] log "Setting title for [$ts get file] to $arg" 0 if {!$::sweeper::dryrun} { $ts settitle $arg } return 0 } proc ::sweeper::action_setguidance {ts cmd arg folder} { if {$folder} { ::sweeper::folder_apply [$ts dir] \ ::sweeper::action_setguidance $cmd $arg 0 return 0 } set arg [::sweeper::expand $ts $arg [$ts get guidance]] log "Setting guidance for [$ts get file] to $arg" 0 if {!$::sweeper::dryrun} { $ts setguidance $arg } return 0 } proc ::sweeper::action_lock {ts cmd arg folder} { if {$folder} { ::sweeper::folder_apply [$ts dir] \ ::sweeper::action_lock $cmd 0 0 return 0 } if {![$ts flag Locked]} { log "Locked [$ts get file]" 0 if {!$::sweeper::dryrun} { $ts lock } } return 0 } proc ::sweeper::action_unlock {ts cmd arg folder} { if {$folder} { ::sweeper::folder_apply [$ts dir] \ ::sweeper::action_unlock $cmd 0 0 return 0 } if {[$ts flag Locked]} { log "Unlocked [$ts get file]" 0 if {!$::sweeper::dryrun} { $ts unlock } } return 0 } proc ::sweeper::action_delete {ts cmd arg folder} { if {$folder} { ::sweeper::folder_apply [$ts dir] \ ::sweeper::action_delete $cmd 0 0 return } log "Deleting [$ts get file]" 0 if {!$::sweeper::dryrun} { safe_delete [$ts get file] sweeper } return 1 } proc ::sweeper::action_flag {ts cmd arg folder} { log "Flagged [$ts get file] as $arg" 0 if {!$::sweeper::dryrun} { $ts setflag $arg } return 0 } proc ::sweeper::action_unflag {ts cmd arg folder} { log "Unflagged [$ts get file] as $arg" 0 if {!$::sweeper::dryrun} { $ts unsetflag $arg } return 0 } eval_plugins sweeper ###################################################################### # Handle action proc ::sweeper::action {ts cmds folder} { lassign $cmds cmd rest log "ACTION: $cmd\($rest) \[$folder]" 2 return [::sweeper::action_$cmd $ts $cmd $rest $folder] } ###################################################################### # Handle clauses proc ::sweeper::or {ts clause folder} { log " --> OR:" 2 set ret 0 while {[llength $clause] > 1} { set clause [lassign $clause cmd arg] set ret [::sweeper::clause $folder $cmd $arg $ts] if {$ret} { log " <-- OR true." 2 break } } if {!$ret} { log " <-- OR false." 2 } return $ret } proc ::sweeper::and {ts clause folder} { log " --> AND:" 2 set ret 0 while {[llength $clause] > 1} { set clause [lassign $clause cmd arg] set ret [::sweeper::clause $folder $cmd $arg $ts] if {!$ret} { log " <-- AND false." 2 break } } if {$ret} { log " <-- AND true." 2 } return $ret } proc ::sweeper::clause {folder cmd arg ts} { log " $cmd\($arg)" 2 if {[string index $cmd 0] eq "!"} { set negate 1 set cmd [string range $cmd 1 end] } else { set negate 0 } set ret [::sweeper::$cmd $ts $arg $folder] if {$cmd eq "action"} { return $ret } if {$negate} { set ret $(!$ret) } if {!$ret} { log " Nomatch" 2 } else { log " MATCH" 2 } return $ret } ###################################################################### proc ::sweeper::runrule {ts rule folder} { log "Processing \[$rule]" 2 if {[string index $rule 0] eq "#" || [llength $rule] < 2} { return 0 } while {[llength $rule] > 1} { set rule [lassign $rule cmd arg] set ret [::sweeper::clause $folder $cmd $arg $ts] if {$cmd eq "action"} { set ::sweeper::lastruleresult 1 return $ret } if {!$ret} break } set ::sweeper::lastruleresult 0 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 set nrules 0 foreach rule $rules { switch -- [lindex $rule 0] { folder - global { incr runfolder } } if {[string index $rule 0] ne "#" && [llength $rule] > 1} { incr nrules } } if {!$nrules} return log "" 2 log "--- SWEEP SCAN STARTING FOR $dir ($nrules) ---" 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 {[string index $rule 0] eq "#" || [llength $rule] < 2} continue switch -- [lindex $rule 0] { folder continue global { set rule [lrange $rule 1 end] } } if {[::sweeper::runrule $ts $rule 0]} 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 {\[*} [string trimleft $e]]} continue if {$e eq $dustbin} continue log "" 2 log "==== folder $entry ====" 2 log "" 2 if {[file exists "$entry/.nosweep"]} { log "No-sweep folder." 2 continue } set ts 0 foreach de [lsort -command [lambda {a b} { upvar entry e return $([file mtime "$e/$b"] - [file mtime "$e/$a"]) }] [lsearch -glob -all -inline \ [readdir -nocomplain $entry] {*.ts}]] { set dentry "$entry/$de" 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] < 3} continue switch -- [lindex $rule 0] { folder - global { set rule [lrange $rule 1 end] } default continue } if {[::sweeper::runrule $ts $rule 1]} break } } } foreach dir $::sweeper::recalc { log "Resetting unwatched recording flag for $dir" 0 ts resetnew $dir } } # Callback function which is called from scan_run proc ::sweeper::sweep {dir} { if {$dir eq $::root} return ::sweeper::apply $dir "$dir/.sweeper" } proc ::sweeper::scan {&files} { log "::sweeper::scan files - ($files)" 2 set ::sweeper::renames {} ::sweeper::apply $::root $::sweeper::cf scan_run $::root sweeper ::sweeper::sweep log "::sweeper::scan renames - ($::sweeper::renames)" 2 if {[llength $::sweeper::renames]} { foreach k [array names ::sweeper::renames] { set pos [lsearch $files $k] log "$k = pos $pos" 2 if {$pos == -1} continue set files [lreplace $files $pos $pos \ $::sweeper::renames($k)] } log "::sweeper::scan files - ($files)" 2 } } proc ::sweeper::scansingledir {dir} { if {$dir eq $::root} { ::sweeper::apply $::root $::sweeper::cf } elseif {[file exists "$dir/.sweeper"]} { ::sweeper::apply $dir "$dir/.sweeper" } } if {[file exists $::sweeper::cf]} { register postdecryptscan ::sweeper::scan register postdecryptsingledir ::sweeper::scansingledir }