diff --git a/webif/plugin/sweeper/auto.hook b/webif/plugin/sweeper/auto.hook index a46cdad..0584bd6 100644 --- a/webif/plugin/sweeper/auto.hook +++ b/webif/plugin/sweeper/auto.hook @@ -1,6 +1,7 @@ set ::sweeper::cf "/mod/etc/sweeper.conf" set ::sweeper::dryrun 0 +set ::sweeper::lastruleresult 0 proc ::sweeper::unknown {cmd args} { log "Unknown sweeper rule clause '$cmd'" 0 @@ -8,9 +9,29 @@ proc ::sweeper::unknown {cmd args} { } ###################################################################### -# Helper functions +# Utility functions -proc ::sweeper::expand {ts str} { +# 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 check +proc ::sweeper::strcontains {ref val} { + return [expr \ + [string first [string tolower $val] [string tolower $ref]] \ + >= 0] +} + +# Expand a string containing tokens +proc ::sweeper::expand {ts str {orig ""}} { if {[string first "%" $str] == -1} { return $str } @@ -37,6 +58,7 @@ proc ::sweeper::expand {ts str} { "%timestamp" $timestamp \ "%yyyymmdd" $yyyymmdd \ "%hhmm" $hhmm \ + "%orig" $orig \ ] set ret [string map $map $str] @@ -52,29 +74,14 @@ proc ::sweeper::resolvedir {dir} { return "$root/$dir" } -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] -} - +# 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 @@ -93,6 +100,8 @@ proc ::sweeper::moveset {ts dst {op rename}} { 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 @@ -151,8 +160,83 @@ proc ::sweeper::moveset {ts dst {op rename}} { } } +# 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 {\[*} $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 {src} { + 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 + } + } +} + +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] @@ -221,6 +305,17 @@ proc ::sweeper::genre {ts genre folder} { 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] +} + +######################## +# Deprecated conditions + proc ::sweeper::lock {ts g folder} { if {$g} { if {![$ts flag Locked]} { @@ -233,312 +328,228 @@ proc ::sweeper::lock {ts g folder} { $ts unlock } } + # Always matches return 1 } -proc ::sweeper::folder_fflag {ts flag folder} { - return [file exists "[file dirname [$ts get file]]/.$flag"] -} - ###################################################################### +# 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 {ts cmds folder} { - global root - - lassign $cmds cmd rest - - log "ACTION: $cmd\($rest)" 2 - - switch $cmd { - continue { return 0 } - stop { return 1 } - preserve { return 1 } - copy - - copycreate - - move - - movecreate { - set rest [::sweeper::expand $ts $rest] - set dir [::sweeper::resolvedir $rest] - set create 0 - if {[string range $cmd end-5 end] eq "create"} { - set create 1 - set cmd [string range $cmd 0 end-6] - } - 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 $rest" 0 - if {!$::sweeper::dryrun} { - ::sweeper::moveset $ts $dir $cmd - if {"$dir" ni $::sweeper::recalc} { - lappend ::sweeper::recalc $dir - } - } - return 1 - } - renamefile { - set rest [system filename [::sweeper::expand $ts $rest]] - set file [$ts get file] - log "Renaming [$ts get file] to $rest" 0 - if {[file exists "[file dirname $file]/$rest.ts"]} { - log "... ERROR Target already exists" 0 - return 0 - } - if {!$::sweeper::dryrun} { - ts renamegroup $file $rest - } - return 0 - } - lock { - if {![$ts flag Locked]} { - log "Locked [$ts get file]" 0 - if {!$::sweeper::dryrun} { - $ts lock - } - } - return 0 - } - unlock { - if {[$ts flag Locked]} { - log "Unlocked [$ts get file]" 0 - if {!$::sweeper::dryrun} { - $ts unlock - } - } - return 0 - } - delete { - log "Deleting [$ts get file]" 0 - if {!$::sweeper::dryrun} { - safe_delete [$ts get file] sweeper - } - return 1 - } - default { - log "Unknown action '$cmd'" 0 - } - } +proc ::sweeper::action_continue {ts cmd arg folder} { 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::action_stop {ts cmd arg folder} { + return 1 } -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" +alias ::sweeper::action_preserve ::sweeper::action_stop - 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::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] } -} - -proc ::sweeper:folder_merge {src dst {op rename}} { - if {$src eq $dst} return - if {$op eq "move"} { set op "rename" } - log "$op 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 - } - - ::sweeper::moveset $ts $dst $op - } - if {$op eq "rename" && ![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 folder} { - 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 } - copycreate - - copy - - movecreate - - move { - set rest [::sweeper::expand $ts $rest] - set dir [::sweeper::resolvedir $rest] - set create 0 - if {[string range $cmd end-5 end] eq "create"} { - set create 1 - set cmd [string range $cmd 0 end-6] - } - 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 - } - } - } - if {!$::sweeper::dryrun} { - ::sweeper:folder_merge $folder $dir $cmd + if {$folder} { + ::sweeper::folder_apply [$ts dir] \ + ::sweeper::action_move $ocmd $arg 0 + if {$cmd eq "move"} { + ::sweeper::rmdir_if_empty $dir } return 1 - } - fileundercreate - - fileunder { - set dir [::sweeper::resolvedir $rest] - if {![file isdirectory $dir]} { + } + if {![file isdirectory $dir]} { + if {!$create} { log " ... No such directory $dir" 2 return 1 } - 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} { - ::sweeper:folder_merge $folder $target - } - return 1 - } - renamefile { - ::sweeper::folder_apply $folder [lambda {ts} { - set rest [system filename \ - [::sweeper::expand $ts $rest]] - set file [$ts get file] - log "Renaming [$ts get file] to $rest" 0 - if {[file exists "[file dirname $file]/$rest.ts"]} { - log "... ERROR Target already exists" 0 - return 0 - } - if {!$::sweeper::dryrun} { - ts renamegroup $file $rest + system mkdir_p $dir + if {![file isdirectory $dir]} { + log "Error creating $dir" 1 + return 1 + } else { + log " ... created $dir" 2 } } - return 0 - } - lock { - if {$::sweeper::dryrun} { return 0 } - ::sweeper::folder_apply $folder [lambda {ts} { - if {![$ts flag Locked]} { - log "Locked [$ts get file]" 0 - $ts lock - } - }] - return 0 - } - unlock { - if {$::sweeper::dryrun} { return 0 } - ::sweeper::folder_apply $folder [lambda {ts} { - if {[$ts flag Locked]} { - log "Unlocked [$ts get file]" 0 - $ts unlock - } - }] - return 0 - } - delete { - ::sweeper::folder_apply $folder [lambda {ts} { - log "Deleting [$ts get file]" 0 - if {!$::sweeper::dryrun} { - safe_delete [$ts get file] sweeper - } - }] + } + log "$cmd [$ts get file] to $arg" 0 + if {!$::sweeper::dryrun} { + ::sweeper::moveset $ts $dir $cmd + ::sweeper::qrecalc $dir [$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 - } - default { - log "Unknown action '$cmd'" 0 - } + } + 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} { + if {$folder} { + ::sweeper::folder_apply [$ts 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 "[$ts dir]/$arg.ts"]} { + log "... ERROR Target already exists" 0 + return 0 + } + if {!$::sweeper::dryrun} { + ts renamegroup $file $arg + if {[file exists "$dir/$arg.ts"]} { + $ts setfile "$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 +} + +###################################################################### +# 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 @@ -582,11 +593,7 @@ proc ::sweeper::clause {folder cmd arg ts} { } else { set negate 0 } - if {$folder && [exists -proc ::sweeper::folder_$cmd]} { - set ret [::sweeper::folder_$cmd $ts $arg $folder] - } else { - set ret [::sweeper::$cmd $ts $arg $folder] - } + set ret [::sweeper::$cmd $ts $arg $folder] if {$cmd eq "action"} { return $ret } if {$negate} { set ret $(!$ret) } if {!$ret} { @@ -597,6 +604,8 @@ proc ::sweeper::clause {folder cmd arg ts} { return $ret } +###################################################################### + proc ::sweeper::runrule {ts rule} { log "Processing \[$rule]" 2 @@ -610,10 +619,13 @@ proc ::sweeper::runrule {ts rule} { while {[llength $rule] > 1} { set rule [lassign $rule cmd arg] set ret [::sweeper::clause $folder $cmd $arg $ts] - if {$cmd eq "action"} { return $ret } + if {$cmd eq "action"} { + set ::sweeper::lastruleresult 1 + return $ret + } if {!$ret} break } - + set ::sweeper::lastruleresult 0 return 0 } @@ -760,6 +772,7 @@ proc ::sweeper::apply {dir cf} { } } +# Callback function which is called from scan_run proc ::sweeper::sweep {dir} { if {$dir eq $::root} return ::sweeper::apply $dir "$dir/.sweeper" diff --git a/webif/plugin/sweeper/schema.js b/webif/plugin/sweeper/schema.js index 446cbad..8ab95ae 100644 --- a/webif/plugin/sweeper/schema.js +++ b/webif/plugin/sweeper/schema.js @@ -1,5 +1,12 @@ var schema = { criterion: { + lastrule: { + 'class': 'all', + type: 'noarg', + desc: 'Last Rule Matched', + negate: true, + def: "" + }, lcn: { 'class': 'all', type: 'int', @@ -140,13 +147,21 @@ var schema = { def: 'Enter text here...' }, fflag: { - 'class': 'folder', + 'class': 'all', type: 'string', desc: 'Folder flagged as', idesc: 'Folder not flagged as', negate: true, def: 'nosweep' }, + foldername: { + 'class': 'all', + type: 'string', + desc: 'Folder name contains', + idesc: 'Folder name does not contain', + negate: true, + def: 'Enter text here...' + }, 'or': { 'class': 'all', type: 'composite', @@ -220,6 +235,18 @@ var schema = { desc: 'Rename recording files to...', continues: true }, + settitle: { + 'class': 'all', + argtype: 'string', + desc: 'Set recording title to...', + continues: true + }, + setguidance: { + 'class': 'all', + argtype: 'string', + desc: 'Set recording guidance to...', + continues: true + }, lock: { 'class': 'all', argtype: 'none', diff --git a/webif/plugin/sweeper/script.js b/webif/plugin/sweeper/script.js index d33397a..ddb61d6 100644 --- a/webif/plugin/sweeper/script.js +++ b/webif/plugin/sweeper/script.js @@ -40,6 +40,9 @@ function changed(c) } var setters = { + noarg: function(cmd, a) { + return ''; + }, int: function(cmd, a) { var b = a.split(" "); if (b.length == 1) @@ -66,6 +69,9 @@ var setters = { }; var getters = { + noarg: function(cmd, a) { + return ''; + }, int: function(cmd, a) { var b = a.split(" "); if (b.length != 2) @@ -245,11 +251,15 @@ function criterion(data, classes) else s += 'UNKNOWN (' + data.arg + ')'; - s += '' + - '' + - '' + - ' ' + - '' + + s += ''; + if (c.type != 'noarg') + s += '' + + '' + + ' '; + else + s += '' + + ' '; + s += '' + '' + ''; return s; @@ -483,6 +493,9 @@ function edit_clause(obj) changed(1); }); break; + case 'noarg': + alert('This condition cannot be edited.'); + break; default: alert('Unhandled clause type (' + cmd + ')');