rework action framework

This commit is contained in:
hummypkg 2014-06-19 19:11:33 +00:00
parent 1328068b2a
commit 93929ca84b
3 changed files with 366 additions and 313 deletions

View File

@ -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"

View File

@ -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',

View File

@ -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 += '</td><td>' +
'<a class=editclause href=# title="Edit condition">' +
'<img src=/img/context/edit.png></a>' +
'&nbsp;' +
'<a class=delclause href=# title="Delete condition">' +
s += '</td><td>';
if (c.type != 'noarg')
s += '<a class=editclause href=# title="Edit condition">' +
'<img src=/img/context/edit.png></a>' +
'&nbsp;';
else
s += '<img src=/img/blank.gif width=16>' +
'&nbsp;';
s += '<a class=delclause href=# title="Delete condition">' +
'<img src=/img/context/delete.png></a>' +
'</td></tr>';
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 + ')');