sweeper 2.0.0 + add and/or, tooltips, moveset

This commit is contained in:
hummypkg 2014-06-12 17:45:11 +00:00
parent 270701873d
commit d5319e6f0d
12 changed files with 494 additions and 224 deletions

View File

@ -1 +0,0 @@
etc/sweeper.conf

View File

@ -1,9 +1,9 @@
Package: sweeper Package: sweeper
Priority: optional Priority: optional
Section: misc Section: misc
Version: 1.0.19 Version: 2.0.0
Architecture: mipsel Architecture: mipsel
Maintainer: af123@hummypkg.org.uk Maintainer: af123@hummypkg.org.uk
Depends: webif(>=1.0.14-8) Depends: webif(>=1.0.14-10)
Description: Sweeper is a package for managing recordings in a variety of ways using custom rules [Add delete action. Some online help.] Description: Sweeper is a package for managing recordings in a variety of ways using custom rules [Add and/or clauses. See forum.]
Tags: http://hummy.tv/forum/threads/5138/ Tags: http://hummy.tv/forum/threads/5138/

View File

@ -69,67 +69,135 @@ proc ::sweeper::strcontains {ref val} {
>= 0] >= 0]
} }
###################################################################### proc ::sweeper::moveset {ts dst {op rename}} {
# Rule criteria set file [$ts get file]
log "${op}set($file) -> $dst" 0
proc ::sweeper::flag {ts flag} { # 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]]
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 {[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
}
}
return
}
# Otherwise - copy or local FS move.
foreach f $fset {
set tail [file tail $f]
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
}
}
}
######################################################################
# Rule conditions
proc ::sweeper::flag {ts flag folder} {
return [$ts flag $flag] return [$ts flag $flag]
} }
proc ::sweeper::lcn {ts num} { proc ::sweeper::lcn {ts num folder} {
return [::sweeper::intcomp [$ts get channel_num] $num] return [::sweeper::intcomp [$ts get channel_num] $num]
} }
proc ::sweeper::duration {ts dur} { proc ::sweeper::duration {ts dur folder} {
return [::sweeper::intcomp [$ts duration] $dur] return [::sweeper::intcomp [$ts duration] $dur]
} }
proc ::sweeper::hour {ts str} { proc ::sweeper::hour {ts str folder} {
set hour [clock format [$ts get start] -format "%H"] set hour [clock format [$ts get start] -format "%H"]
return [::sweeper::intcomp $hour $str] return [::sweeper::intcomp $hour $str]
} }
proc ::sweeper::schedduration {ts dur} { proc ::sweeper::schedduration {ts dur folder} {
return [::sweeper::intcomp [$ts get scheddur] $dur] return [::sweeper::intcomp [$ts get scheddur] $dur]
} }
proc ::sweeper::size {ts size} { proc ::sweeper::size {ts size folder} {
return [::sweeper::intcomp [$ts size] $size] return [::sweeper::intcomp [$ts size] $size]
} }
proc ::sweeper::age {ts age} { proc ::sweeper::age {ts age folder} {
set recage $(([clock seconds] - [$ts get end]) / 3600) set recage $(([clock seconds] - [$ts get end]) / 3600)
log " ... Recording age: $recage" 2 log " ... Recording age: $recage" 2
return [::sweeper::intcomp $recage $age] return [::sweeper::intcomp $recage $age]
} }
proc ::sweeper::wage {ts age} { proc ::sweeper::wage {ts age folder} {
set recage $(([clock seconds] - [$ts lastmod]) / 3600) set recage $(([clock seconds] - [$ts lastmod]) / 3600)
log " ... Watched age: $recage" 2 log " ... Watched age: $recage" 2
return [::sweeper::intcomp $recage $age] return [::sweeper::intcomp $recage $age]
} }
proc ::sweeper::definition {ts def} { proc ::sweeper::definition {ts def folder} {
return [::sweeper::strcontains [$ts get definition] $def] return [::sweeper::strcontains [$ts get definition] $def]
} }
proc ::sweeper::filename {ts str} { proc ::sweeper::filename {ts str folder} {
return [::sweeper::strcontains [$ts bfile] $str] return [::sweeper::strcontains [$ts bfile] $str]
} }
proc ::sweeper::title {ts str} { proc ::sweeper::title {ts str folder} {
return [::sweeper::strcontains [$ts get title] $str] return [::sweeper::strcontains [$ts get title] $str]
} }
proc ::sweeper::synopsis {ts str} { proc ::sweeper::synopsis {ts str folder} {
return [::sweeper::strcontains [$ts get synopsis] $str] return [::sweeper::strcontains [$ts get synopsis] $str]
} }
proc ::sweeper::guidance {ts str} { proc ::sweeper::guidance {ts str folder} {
return [::sweeper::strcontains [$ts get guidance] $str] return [::sweeper::strcontains [$ts get guidance] $str]
} }
proc ::sweeper::genre {ts genre} { proc ::sweeper::genre {ts genre folder} {
set glist [ts genrelist] set glist [ts genrelist]
set tsg [$ts get genre] set tsg [$ts get genre]
@ -139,7 +207,7 @@ proc ::sweeper::genre {ts genre} {
return 0 return 0
} }
proc ::sweeper::lock {ts g} { proc ::sweeper::lock {ts g folder} {
if {$g} { if {$g} {
if {![$ts flag Locked]} { if {![$ts flag Locked]} {
log "Locked recording." 0 log "Locked recording." 0
@ -154,13 +222,13 @@ proc ::sweeper::lock {ts g} {
return 1 return 1
} }
proc ::sweeper::folder_fflag {ts flag} { proc ::sweeper::folder_fflag {ts flag folder} {
return [file exists "[file dirname [$ts get file]]/.$flag"] return [file exists "[file dirname [$ts get file]]/.$flag"]
} }
###################################################################### ######################################################################
proc ::sweeper::action {ts cmds} { proc ::sweeper::action {ts cmds folder} {
global root global root
lassign $cmds cmd rest lassign $cmds cmd rest
@ -171,12 +239,19 @@ proc ::sweeper::action {ts cmds} {
continue { return 0 } continue { return 0 }
stop { return 1 } stop { return 1 }
preserve { return 1 } preserve { return 1 }
copy -
copycreate -
move - move -
movecreate { movecreate {
set rest [::sweeper::expand $ts $rest] set rest [::sweeper::expand $ts $rest]
set dir [::sweeper::resolvedir $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 {![file isdirectory $dir]} {
if {$cmd eq "move"} { if {!$create} {
log " ... No such directory $dir" 2 log " ... No such directory $dir" 2
return 1 return 1
} }
@ -190,14 +265,11 @@ proc ::sweeper::action {ts cmds} {
} }
} }
} }
log "Moving [$ts get file] to $rest" 0 log "$cmd [$ts get file] to $rest" 0
foreach f [$ts fileset] { if {!$::sweeper::dryrun} {
log " ....... $f" ::sweeper::moveset $ts $dir $cmd
if {!$::sweeper::dryrun} { if {"$dir" ni $::sweeper::recalc} {
file rename $f "$dir/[file tail $f]" lappend ::sweeper::recalc $dir
if {"$dir" ni $::sweeper::recalc} {
lappend ::sweeper::recalc $dir
}
} }
} }
return 1 return 1
@ -291,7 +363,8 @@ proc ::sweeper::folder_apply {dir callback} {
proc ::sweeper:folder_merge {src dst {op rename}} { proc ::sweeper:folder_merge {src dst {op rename}} {
if {$src eq $dst} return if {$src eq $dst} return
log "Moving recordings from $src to $dst" 0 if {$op eq "move"} { set op "rename" }
log "$op recordings from $src to $dst" 0
foreach e [readdir -nocomplain $src] { foreach e [readdir -nocomplain $src] {
if {![string match {*.ts} $e]} continue if {![string match {*.ts} $e]} continue
set entry "$src/$e" set entry "$src/$e"
@ -313,12 +386,7 @@ proc ::sweeper:folder_merge {src dst {op rename}} {
continue continue
} }
foreach f [$ts fileset] { ::sweeper::moveset $ts $dst $op
if {$op eq "copy" \
&& [file exists "$dst/[file tail $f]"]} continue
log " ....... $f"
file $op $f "$dst/[file tail $f]"
}
} }
if {$op eq "rename" && ![system rmdir_if_empty $src]} { if {$op eq "rename" && ![system rmdir_if_empty $src]} {
log "Failed to remove directory" 0 log "Failed to remove directory" 0
@ -331,7 +399,7 @@ proc ::sweeper:folder_merge {src dst {op rename}} {
} }
} }
proc ::sweeper::folder_action {ts cmds} { proc ::sweeper::folder_action {ts cmds folder} {
global root global root
lassign $cmds cmd rest lassign $cmds cmd rest
@ -345,12 +413,19 @@ proc ::sweeper::folder_action {ts cmds} {
continue { return 0 } continue { return 0 }
stop { return 1 } stop { return 1 }
preserve { return 1 } preserve { return 1 }
copycreate -
copy -
movecreate - movecreate -
move { move {
set rest [::sweeper::expand $ts $rest] set rest [::sweeper::expand $ts $rest]
set dir [::sweeper::resolvedir $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 {![file isdirectory $dir]} {
if {$cmd eq "move"} { if {!$create} {
log " ... No such directory $dir" 2 log " ... No such directory $dir" 2
return 1 return 1
} }
@ -365,7 +440,7 @@ proc ::sweeper::folder_action {ts cmds} {
} }
} }
if {!$::sweeper::dryrun} { if {!$::sweeper::dryrun} {
::sweeper:folder_merge $folder $dir ::sweeper:folder_merge $folder $dir $cmd
} }
return 1 return 1
} }
@ -450,6 +525,64 @@ proc ::sweeper::folder_action {ts cmds} {
return 0 return 0
} }
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
}
if {$folder && [exists -proc ::sweeper::folder_$cmd]} {
set ret [::sweeper::folder_$cmd $ts $arg $folder]
} else {
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} { proc ::sweeper::runrule {ts rule} {
log "Processing \[$rule]" 2 log "Processing \[$rule]" 2
@ -462,25 +595,9 @@ proc ::sweeper::runrule {ts rule} {
} }
while {[llength $rule] > 1} { while {[llength $rule] > 1} {
set rule [lassign $rule cmd arg] set rule [lassign $rule cmd arg]
log " $cmd\($arg)" 2 set ret [::sweeper::clause $folder $cmd $arg $ts]
if {[string index $cmd 0] eq "!"} {
set negate 1
set cmd [string range $cmd 1 end]
} else {
set negate 0
}
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 {$cmd eq "action"} { return $ret }
if {$negate} { set ret $(!$ret) } if {!$ret} break
if {!$ret} {
log " Nomatch" 2
break
}
log " MATCH" 2
} }
return 0 return 0
@ -498,12 +615,18 @@ proc ::sweeper::apply {dir cf} {
$fp close $fp close
set runfolder 0 set runfolder 0
set nrules 0
foreach rule $rules { foreach rule $rules {
if {[lindex $rule 0] eq "folder"} { incr runfolder } if {[lindex $rule 0] eq "folder"} { incr runfolder }
if {[string index $rule 0] ne "#" && [llength $rule] > 1} {
incr nrules
}
} }
if {!$nrules} return
log "" 2 log "" 2
log "--- SWEEP SCAN STARTING FOR $dir ---" 2 log "--- SWEEP SCAN STARTING FOR $dir ($nrules) ---" 2
log "" 2 log "" 2
foreach e [readdir -nocomplain $dir] { foreach e [readdir -nocomplain $dir] {
@ -530,7 +653,7 @@ proc ::sweeper::apply {dir cf} {
foreach rule $rules { foreach rule $rules {
if {[lindex $rule 0] eq "folder"} continue if {[lindex $rule 0] eq "folder"} continue
if {[string index $rule 0] eq "#" || \ if {[string index $rule 0] eq "#" ||
[llength $rule] < 2} continue [llength $rule] < 2} continue
if {[::sweeper::runrule $ts $rule]} break if {[::sweeper::runrule $ts $rule]} break
} }
@ -608,7 +731,7 @@ proc ::sweeper::apply {dir cf} {
} }
foreach rule $rules { foreach rule $rules {
if {[string index $rule 0] eq "#" || \ if {[string index $rule 0] eq "#" ||
[llength $rule] < 2} continue [llength $rule] < 2} continue
if {[lindex $rule 0] ne "folder"} continue if {[lindex $rule 0] ne "folder"} continue
if {[::sweeper::runrule $ts $rule]} break if {[::sweeper::runrule $ts $rule]} break

View File

@ -74,30 +74,29 @@ Add pre-defined ruleset:
</span> </span>
<a class=enadisrule href=#> <a class=enadisrule href=#>
<img src=img/disable.png <img src=img/disable.png
alt="Enable/Disable Rule"
title="Enable/Disable Rule"> title="Enable/Disable Rule">
</a> </a>
<a class=delrule href=#> <a class=delrule href=#>
<img src=/img/context/delete.png <img src=/img/context/delete.png
alt="Delete Rule" title="Delete Rule"> title="Delete Rule">
</a> </a>
<a class=cprule href=#> <a class=cprule href=#>
<img src=/img/context/page_white_copy.png <img src=/img/context/page_white_copy.png
alt="Duplicate Rule" title="Duplicate Rule"> title="Duplicate Rule">
</a> </a>
<a class=uprule href=#> <a class=uprule href=#>
<img src=/img/nav/up.png <img src=/img/nav/up.png
alt="Move Rule Up" title="Move Rule Up"> title="Move Rule Up">
</a> </a>
<a class=downrule href=#> <a class=downrule href=#>
<img src=/img/nav/down.png <img src=/img/nav/down.png
alt="Move Rule Down" title="Move Rule Down"> title="Move Rule Down">
</a> </a>
</span> </span>
</legend> </legend>
<a href=# class=addcriterion> <a href=# class=addcriterion>
<img src=/img/context/plus.png <img src=img/plus.png
alt="Add condition" title="Add condition"> title="Add condition">
</a> </a>
<div class=criteria> <div class=criteria>
<table class="criteria"><tbody></tbody></table> <table class="criteria"><tbody></tbody></table>
@ -153,7 +152,7 @@ Add pre-defined ruleset:
<select id=newcondition_cmd name=newcondition_cmd></select> <select id=newcondition_cmd name=newcondition_cmd></select>
</td></tr><tr><th>Negate?</th><td> </td></tr><tr><th>Negate?</th><td>
<input id=newcondition_negate name=newcondition_negate type=checkbox <input id=newcondition_negate name=newcondition_negate type=checkbox
value='1' /> value='1' title="Invert the sense of the condition."/>
</td></tr> </td></tr>
</table> </table>
</div> </div>

View File

@ -0,0 +1,45 @@
source /mod/webif/lib/setup
require lock system.class ts.class pretty_size browse.class \
safe_delete settings.class plugin
proc log {msg {level 1}} {
puts "[\
clock format [clock seconds] -format "%d/%m/%Y %H:%M"\
] - $msg"
}
proc elapsed {start} {
return $(([clock milliseconds] - $start) / 1000.0)
}
proc startclock {} {
set ::startclock_s [clock milliseconds]
}
proc endclock {size} {
set el [elapsed $::startclock_s]
set rate $($size / $el)
return "[pretty_size $size] in $el seconds - [pretty_size $rate]/s"
}
proc register {args} {}
proc scan_run {dir flag callback} {
global dustbin
if {[string match {\[*} [file tail $dir]]} return
if {[file exists "$dir/.$flag"]} { $callback $dir }
foreach entry [readdir -nocomplain $dir] {
if {[file isdirectory "$dir/$entry"]} {
scan_run "$dir/$entry" $flag $callback
}
}
}
set root [system mediaroot]
source auto.hook

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -6,6 +6,10 @@ require system.class
set dir [cgi_get dir ""] set dir [cgi_get dir ""]
if {$dir eq "" && [llength $argv]} {
set dir [lindex $argv 0]
}
set root [system mediaroot] set root [system mediaroot]
if {$dir eq "" || $dir eq $root} { if {$dir eq "" || $dir eq $root} {
@ -45,8 +49,43 @@ proc quot {str} {
return [cgi_quote_html $str] return [cgi_quote_html $str]
} }
set composite {and or}
proc clause {cmd arg {indent 6}} {
global lockfound composite
set istr [string repeat " " $indent]
if {[string index $cmd 0] eq "!"} {
set negate 1
set cmd [string range $cmd 1 end]
} else {
set negate 0
}
if {$cmd eq "lock"} { set lockfound $arg }
add_json "$istr{\n"
add_json "$istr \"negate\": $negate,\n"
add_json "$istr \"cmd\": \"[quot $cmd]\",\n"
add_json "$istr \"arg\": \"[quot $arg]\""
if {$cmd in $composite} {
add_json ",\n"
add_json "$istr \"criteria\": \[\n"
set c 0
while {[llength $arg] > 1} {
set arg [lassign $arg subcmd subarg]
if {$c} { add_json ",\n" }
incr c
clause $subcmd $subarg $($indent + 6)
}
add_json "\n$istr ]"
}
add_json "\n"
add_json "$istr}"
}
proc rule {id rule} { proc rule {id rule} {
global clausedescr lcomment global lcomment lockfound
set enabled 1 set enabled 1
if {[lindex $rule 0] eq "##"} { if {[lindex $rule 0] eq "##"} {
@ -76,22 +115,11 @@ proc rule {id rule} {
set lockfound -1 set lockfound -1
while {[llength $rule] > 1} { while {[llength $rule] > 1} {
set rule [lassign $rule cmd arg] set rule [lassign $rule cmd arg]
if {[string index $cmd 0] eq "!"} {
set negate 1
set cmd [string range $cmd 1 end]
} else {
set negate 0
}
if {$cmd eq "action"} break if {$cmd eq "action"} break
if {$cmd eq "lock"} { set lockfound $arg }
if {$c} { add_json ",\n" } if {$c} { add_json ",\n" }
incr c incr c
add_json " {\n" clause $cmd $arg
add_json " \"negate\": $negate,\n"
add_json " \"cmd\": \"[quot $cmd]\",\n"
add_json " \"arg\": \"[quot $arg]\"\n"
add_json " }"
} }
add_json "\n" add_json "\n"
if {$cmd ne "action"} { if {$cmd ne "action"} {

View File

@ -4,42 +4,49 @@ var schema = {
'class': 'all', 'class': 'all',
type: 'int', type: 'int',
desc: 'Logical Channel Number', desc: 'Logical Channel Number',
negate: true,
def: "0" def: "0"
}, },
duration: { duration: {
'class': 'all', 'class': 'all',
type: 'int', type: 'int',
desc: 'Recording duration (in minutes)', desc: 'Recording duration (in minutes)',
negate: true,
def: "0" def: "0"
}, },
schedduration: { schedduration: {
'class': 'all', 'class': 'all',
type: 'int', type: 'int',
desc: 'Scheduled duration (in minutes)', desc: 'Scheduled duration (in minutes)',
negate: true,
def: "0" def: "0"
}, },
size: { size: {
'class': 'all', 'class': 'all',
type: 'int', type: 'int',
desc: 'Recording size (in bytes)', desc: 'Recording size (in bytes)',
negate: true,
def: "0" def: "0"
}, },
hour: { hour: {
'class': 'all', 'class': 'all',
type: 'int', type: 'int',
desc: 'Hour in which the recording started', desc: 'Hour in which the recording started',
negate: true,
def: "0" def: "0"
}, },
age: { age: {
'class': 'all', 'class': 'all',
type: 'int', type: 'int',
desc: 'Age (in hours)', desc: 'Age (in hours)',
negate: true,
def: "0" def: "0"
}, },
wage: { wage: {
'class': 'all', 'class': 'all',
type: 'int', type: 'int',
desc: 'Time since last watched, or recorded (in hours)', desc: 'Time since last watched, or recorded (in hours)',
negate: true,
def: "0" def: "0"
}, },
title: { title: {
@ -47,6 +54,7 @@ var schema = {
type: 'substr', type: 'substr',
desc: 'Recording Title contains', desc: 'Recording Title contains',
idesc: 'Recording Title does not contain', idesc: 'Recording Title does not contain',
negate: true,
def: 'Enter text here...' def: 'Enter text here...'
}, },
synopsis: { synopsis: {
@ -54,6 +62,7 @@ var schema = {
type: 'substr', type: 'substr',
desc: 'Synopsis contains', desc: 'Synopsis contains',
idesc: 'Synopsis does not contain', idesc: 'Synopsis does not contain',
negate: true,
def: 'Enter text here...' def: 'Enter text here...'
}, },
guidance: { guidance: {
@ -61,12 +70,14 @@ var schema = {
type: 'substr', type: 'substr',
desc: 'Guidance Text contains', desc: 'Guidance Text contains',
idesc: 'Guidance Text does not contain', idesc: 'Guidance Text does not contain',
negate: true,
def: 'Enter text here...' def: 'Enter text here...'
}, },
genre: { genre: {
'class': 'all', 'class': 'all',
type: 'select', type: 'select',
desc: 'Recording Genre', desc: 'Recording Genre',
negate: true,
select: { select: {
Unclassified: 'Unclassified', Unclassified: 'Unclassified',
Film: 'Film', Film: 'Film',
@ -84,6 +95,7 @@ var schema = {
'class': 'all', 'class': 'all',
type: 'select', type: 'select',
desc: 'Recording Definition', desc: 'Recording Definition',
negate: true,
select: { select: {
SD: 'Standard Definition', SD: 'Standard Definition',
HD: 'High Definition' HD: 'High Definition'
@ -95,6 +107,7 @@ var schema = {
type: 'select', type: 'select',
desc: 'Recording Flagged as', desc: 'Recording Flagged as',
idesc: 'Recording not Flagged as', idesc: 'Recording not Flagged as',
negate: true,
select: { select: {
Locked: 'Locked', Locked: 'Locked',
New: 'New', New: 'New',
@ -110,6 +123,7 @@ var schema = {
'class': 'all', 'class': 'all',
type: 'select', type: 'select',
desc: 'Change Recording Lock', desc: 'Change Recording Lock',
negate: true,
select: { select: {
1: 'Lock Recording', 1: 'Lock Recording',
0: 'Unlock Recording' 0: 'Unlock Recording'
@ -121,6 +135,8 @@ var schema = {
'class': 'all', 'class': 'all',
type: 'substr', type: 'substr',
desc: 'Filename contains', desc: 'Filename contains',
idesc: 'Filename does not contain',
negate: true,
def: 'Enter text here...' def: 'Enter text here...'
}, },
fflag: { fflag: {
@ -128,7 +144,24 @@ var schema = {
type: 'string', type: 'string',
desc: 'Folder flagged as', desc: 'Folder flagged as',
idesc: 'Folder not flagged as', idesc: 'Folder not flagged as',
negate: true,
def: 'nosweep' def: 'nosweep'
},
'or': {
'class': 'all',
type: 'composite',
label: 'Or',
desc: 'If any of...',
negate: false,
def: ''
},
'and': {
'class': 'all',
type: 'composite',
label: 'And',
desc: 'If all of...',
negate: false,
def: ''
} }
}, },
action: { action: {
@ -150,24 +183,24 @@ var schema = {
desc: 'Move recording to folder...', desc: 'Move recording to folder...',
continues: false continues: false
}, },
lock: {
'class': 'all',
argtype: 'none',
desc: 'Lock recordings',
continues: true
},
unlock: {
'class': 'all',
argtype: 'none',
desc: 'Unlock recordings',
continues: true
},
movecreate: { movecreate: {
'class': 'all', 'class': 'all',
argtype: 'folder', argtype: 'folder',
desc: 'Move recording to folder (creating if necessary)', desc: 'Move recording to folder (creating if necessary)',
continues: false continues: false
}, },
copy: {
'class': 'all',
argtype: 'folder',
desc: 'Copy recording to folder...',
continues: false
},
copycreate: {
'class': 'all',
argtype: 'folder',
desc: 'Copy recording to folder (creating if necessary)',
continues: false
},
fileunder: { fileunder: {
'class': 'folder', 'class': 'folder',
argtype: 'folder', argtype: 'folder',
@ -187,6 +220,18 @@ var schema = {
desc: 'Rename recording files to...', desc: 'Rename recording files to...',
continues: true continues: true
}, },
lock: {
'class': 'all',
argtype: 'none',
desc: 'Lock recordings',
continues: true
},
unlock: {
'class': 'all',
argtype: 'none',
desc: 'Unlock recordings',
continues: true
},
delete: { delete: {
'class': 'all', 'class': 'all',
argtype: 'none', argtype: 'none',
@ -216,47 +261,38 @@ var macros = {
example: { example: {
desc: 'Example rules', desc: 'Example rules',
rules: [ rules: [
{ {
"raw": "lcn {&gt;= 70} lcn {&lt;= 79} duration {&gt;= 90} action {move Children/Films}", "raw": "lcn {&gt;= 70} lcn {&lt;= 79} or {duration {&gt;= 90} genre Film } action {move Children/Films}",
"name": "Move any Children's films (by length)", "name": "Move any Children's films",
"type": "file", "type": "file",
"enabled": "0", "enabled": "0",
"criteria": [ "criteria": [
{ {
"negate": 0,
"cmd": "lcn", "cmd": "lcn",
"arg": "&gt;= 70" "arg": "&gt;= 70"
}, },
{ {
"negate": 0,
"cmd": "lcn", "cmd": "lcn",
"arg": "&lt;= 79" "arg": "&lt;= 79"
}, },
{ {
"cmd": "duration", "negate": 0,
"arg": "&gt;= 90" "cmd": "or",
} "arg": "duration {&gt;= 90} genre Film ",
], "criteria": [
"action": { {
"cmd": "move", "negate": 0,
"arg": "Children/Films" "cmd": "duration",
} "arg": "&gt;= 90"
}, },
{ {
"raw": "lcn {&gt;= 70} lcn {&lt;= 79} genre Film action {move Children/Films}", "negate": 0,
"name": "Move any Children's films (by genre)", "cmd": "genre",
"type": "file", "arg": "Film"
"enabled": "0", }
"criteria": [ ]
{
"cmd": "lcn",
"arg": "&gt;= 70"
},
{
"cmd": "lcn",
"arg": "&lt;= 79"
},
{
"cmd": "genre",
"arg": "Film"
} }
], ],
"action": { "action": {

View File

@ -90,16 +90,20 @@ var getters = {
} }
}; };
function ruleconf(rule) function clauseconf(clause)
{ {
var s = ''; var s = '';
if (rule.hasClass('ruledisabled')) clause.find('tbody:first > tr.clause,tbody:first > tr.compositeclause')
s += '## '; .each(function() {
if (rule.attr('type') == 'folder') if ($(this).hasClass('compositeclause'))
s += 'folder '; {
cc = clauseconf($(this).find('table:first'));
rule.find('tr.clause').each(function(i) { cmd = $(this).attr('cmd');
if (cc && cc.length)
s += cmd + ' {' + cc + '} ';
return;
}
cmd = $(this).find('th.cmd').attr('cmd'); cmd = $(this).find('th.cmd').attr('cmd');
negate = $(this).find('th.cmd').attr('negate'); negate = $(this).find('th.cmd').attr('negate');
c = schema.criterion[cmd]; c = schema.criterion[cmd];
@ -111,6 +115,19 @@ function ruleconf(rule)
s += '!'; s += '!';
s += cmd + ' ' + quot(val) + ' '; s += cmd + ' ' + quot(val) + ' ';
}); });
return s;
}
function ruleconf(rule)
{
var s = '';
if (rule.hasClass('ruledisabled'))
s += '## ';
if (rule.attr('type') == 'folder')
s += 'folder ';
s += clauseconf(rule);
act = rule.find('tr.action th.cmd').attr('cmd'); act = rule.find('tr.action th.cmd').attr('cmd');
arg = $.trim(rule.find('tr.action td.val').text()); arg = $.trim(rule.find('tr.action td.val').text());
@ -144,18 +161,23 @@ function rulerefresh(rule)
if (!showraw) if (!showraw)
rule.find('.raw').hide(); rule.find('.raw').hide();
rule.find('.raw').html(ruleconf(rule)); rule.find('.raw').html(ruleconf(rule));
rule.find('tr.clause th.title').text('And:').first().text('If:'); rule.find('tr.clause th.title,tr.compositeclause th.title')
if (rule.find('tr.clause').length < 1) .text('And:').first().text('If:');
rule.find('tr.compositeclause').each(function(e) {
cmd = $(this).attr('cmd');
label = schema.criterion[cmd].label;
$(this).find('table th.title').text(label + ':')
.first().text('If:');
});
if (rule.find('tr.clause,tr.compositeclause').length < 1)
{ {
rule.find('tr.action th.title').text('Always:'); rule.find('tr.action th.title').text('Always:');
rule.find('tr.otherwise').hide(); rule.find('tr.otherwise,div.criteria,div.arrow').hide();
rule.find('div.criteria,div.arrow').hide();
} }
else else
{ {
rule.find('tr.action th.title').text('Then:'); rule.find('tr.action th.title').text('Then:');
rule.find('tr.otherwise').show(); rule.find('tr.otherwise,div.criteria,div.arrow').show();
rule.find('div.criteria,div.arrow').show();
} }
} }
@ -172,7 +194,34 @@ function critdesc(cmd, negate)
return c.desc; return c.desc;
} }
function criterion(data) function composite_criterion(c, data)
{
s = '<tr class="compositeclause" cmd=' + data.cmd + '>' +
'<th class=title>And:</th>' +
'<td colspan=3>' +
'<table class=compositeclause>' +
'<tr><td colspan=4>' +
'<a href=# class=addsubcriterion>' +
'<img src=img/plus.png height=15 ' +
'title="Add condition to sub-clause"></a>' +
'&nbsp;' +
'<a class=delsubclause href=# ' +
'title="Delete sub-clause and the conditions within it">' +
'<img src=/img/context/delete.png></a>' +
'&nbsp;' +
c.desc +
'</td></tr>';
$.each(data.criteria, function(key, val) {
s += criterion(val, 'comp');
});
s += '</table></td></tr>';
return s;
}
function criterion(data, classes)
{ {
var c = schema.criterion[data.cmd]; var c = schema.criterion[data.cmd];
var s; var s;
@ -182,7 +231,12 @@ function criterion(data)
alert('Unknown Criterion (' + data.cmd + ')'); alert('Unknown Criterion (' + data.cmd + ')');
return; return;
} }
s = '<tr class=clause><th class=title>And:</th>' + if (c.type == 'composite')
return composite_criterion(c, data);
s = '<tr class="clause';
if (classes) s += ' ' + classes;
s += '"><th class=title>And:</th>' +
'<th class=cmd negate=' + data.negate + ' cmd=' + data.cmd + '>' + '<th class=cmd negate=' + data.negate + ' cmd=' + data.cmd + '>' +
critdesc(data.cmd, data.negate) + '</th><td class=val>'; critdesc(data.cmd, data.negate) + '</th><td class=val>';
@ -192,10 +246,10 @@ function criterion(data)
s += 'UNKNOWN (' + data.arg + ')'; s += 'UNKNOWN (' + data.arg + ')';
s += '</td><td>' + s += '</td><td>' +
'<a class=editclause href=#>' + '<a class=editclause href=# title="Edit condition">' +
'<img src=/img/context/edit.png></a>' + '<img src=/img/context/edit.png></a>' +
'&nbsp;' + '&nbsp;' +
'<a class=delclause href=#>' + '<a class=delclause href=# title="Delete condition">' +
'<img src=/img/context/delete.png></a>' + '<img src=/img/context/delete.png></a>' +
'</td></tr>'; '</td></tr>';
return s; return s;
@ -264,8 +318,12 @@ function addrule(id, data)
$.each(data.criteria, function(key, val) { $.each(data.criteria, function(key, val) {
if (val.cmd == 'lock' && ( if (val.cmd == 'lock' && (
data.action.cmd == 'lock' || data.action.cmd == 'unlock')) data.action.cmd == 'lock' || data.action.cmd == 'unlock'))
{
alert('Removed legacy lock/unlock - ' +
'check rules before saving.');
return; return;
$c.find('tbody').append(criterion(val)); }
$c.find('tbody:first').append(criterion(val));
}); });
$c.find('th.title:first').text('If:'); $c.find('th.title:first').text('If:');
@ -599,7 +657,7 @@ $('#macros').on('click', '#b_macro', function(e) {
changed(0); changed(0);
function addcriterion(rule) function addcriterion(rule, target)
{ {
var type = rule.attr('type'); var type = rule.attr('type');
@ -616,6 +674,7 @@ function addcriterion(rule)
); );
}); });
$('#newcondition_negate').prop('checked', false); $('#newcondition_negate').prop('checked', false);
$('#newcondition_cmd').trigger('change');
$('#newcondition').dialog({ $('#newcondition').dialog({
height: 'auto', width: 'auto', height: 'auto', width: 'auto',
@ -624,21 +683,22 @@ function addcriterion(rule)
position: { position: {
my: 'bottom left', my: 'bottom left',
at: 'top right', at: 'top right',
of: rule of: target
}, },
buttons: { buttons: {
"Add Condition": function() { "Add Condition": function() {
$(this).dialog('close'); $(this).dialog('close');
var id = rule.attr('id');
var val = $('#newcondition_cmd').val(); var val = $('#newcondition_cmd').val();
var negate = $('#newcondition_negate') var negate = $('#newcondition_negate')
.prop('checked') ? '1' : '0'; .prop('checked') ? '1' : '0';
rule.find('table.criteria tbody') var obj = {
.append(criterion({
cmd: val, cmd: val,
negate: negate, negate: negate,
arg: schema.criterion[val].def arg: schema.criterion[val].def,
})); criteria: []
};
target.find('tbody:first')
.append(criterion(obj));
changed(1); changed(1);
rulerefresh(rule); rulerefresh(rule);
}, },
@ -656,7 +716,8 @@ $('#ruleset')
}) })
.on('click', 'a.addcriterion', function(e) { .on('click', 'a.addcriterion', function(e) {
e.preventDefault(); e.preventDefault();
addcriterion($(this).closest('div.rule')); var rule = $(this).closest('div.rule');
addcriterion(rule, rule.find('table.criteria'));
}) })
.on('click', 'a.editcomment', function(e) { .on('click', 'a.editcomment', function(e) {
e.preventDefault(); e.preventDefault();
@ -827,6 +888,32 @@ $('#ruleset')
.html($('#empty_rulebase').html()); .html($('#empty_rulebase').html());
}); });
}); });
})
.on('click', 'a.delsubclause', function(e) {
e.preventDefault();
$(this).dojConfirmAction({
question: 'Delete sub-condition?',
yesAnswer: 'Yes',
cancelAnswer: 'No'
}, function(el) {
var rule = $(el).closest('div.rule');
$(el).closest('tr.compositeclause')
.fadeOut('slow', function() {
$(this).remove();
rulerefresh(rule);
changed(1);
});
});
})
.on('click', 'a.addsubcriterion', function(e) {
e.preventDefault();
var rule = $(this).closest('div.rule');
var clause = $(this).closest('table.compositeclause');
addcriterion(rule, clause);
})
.on('dblclick', 'div.raw', function(e) {
e.preventDefault();
$(this).toggleClass("rawvis");
}); });
$('#edit_action_act').on('change', function(e) { $('#edit_action_act').on('change', function(e) {
@ -837,7 +924,17 @@ $('#ruleset')
$('#edit_action_arg').enable(); $('#edit_action_arg').enable();
$('.edit_action_help').hide(); $('.edit_action_help').hide();
$('#edit_action_help_' + schema.action[cmd].argtype).show(); $('#edit_action_help_' + schema.action[cmd].argtype).show();
}) });
$('#newcondition_cmd').on('change', function(e) {
var cmd = $(this).val();
if (schema.criterion[cmd].negate)
$('#newcondition_negate').enable();
else
$('#newcondition_negate')
.prop('checked', false)
.disable();
});
// Set up macros // Set up macros
$.each(macros, function(key, val) { $.each(macros, function(key, val) {
@ -854,5 +951,7 @@ $('#ruleset')
}); });
loadrules($('span.dir').text()); loadrules($('span.dir').text());
$(document).tooltip();
}); });

View File

@ -62,6 +62,11 @@ div.raw
right: 12px; right: 12px;
} }
div.rawvis
{
background: #ccc;
}
div#buttons div#buttons
{ {
padding-top: 1em; padding-top: 1em;
@ -107,6 +112,7 @@ span.legendright
table table
{ {
border: 0; border: 0;
line-height: 1em;
} }
table th table th

View File

@ -1,50 +1,12 @@
#!/mod/bin/jimsh #!/mod/bin/jimsh
source /mod/webif/lib/setup source /mod/webif/plugin/sweeper/harness.jim
require lock system.class ts.class tdelete pretty_size browse.class \
safe_delete settings.class plugin
proc log {msg {level 1}} { if {![llength $argv]} {
puts "[\ ::sweeper::scan 0
clock format [clock seconds] -format "%d/%m/%Y %H:%M"\ } else {
] - $msg" scan_run [lindex $argv 0] sweeper ::sweeper::sweep
} }
proc elapsed {start} {
return $(([clock milliseconds] - $start) / 1000.0)
}
proc startclock {} {
set ::startclock_s [clock milliseconds]
}
proc endclock {size} {
set el [elapsed $::startclock_s]
set rate $($size / $el)
return "[pretty_size $size] in $el seconds - [pretty_size $rate]/s"
}
proc register {args} {}
proc scan_run {dir flag callback} {
global dustbin
if {[string match {\[*} [file tail $dir]]} return
if {[file exists "$dir/.$flag"]} { $callback $dir }
foreach entry [readdir -nocomplain $dir] {
if {[file isdirectory "$dir/$entry"]} {
scan_run "$dir/$entry" $flag $callback
}
}
}
set root [system mediaroot]
source auto.hook
::sweeper::scan 0
#::sweeper::apply $::root $::sweeper::cf #::sweeper::apply $::root $::sweeper::cf

View File

@ -1,14 +1,11 @@
#!/mod/bin/jimsh #!/mod/bin/jimsh
package require cgi package require cgi
source /mod/webif/lib/setup source /mod/webif/plugin/sweeper/harness.jim
require lock system.class ts.class tdelete pretty_size browse.class \
safe_delete settings.class plugin
httpheader httpheader
set dir [cgi_get dir ""] set dir [cgi_get dir ""]
set root [system mediaroot]
set cf "/tmp/sweepertest.cf" set cf "/tmp/sweepertest.cf"
@ -31,30 +28,6 @@ if {[catch {set fp [open $cf w]} msg]} {
$fp puts -nonewline $data $fp puts -nonewline $data
close $fp close $fp
proc log {msg {level 1}} {
puts $msg
}
proc elapsed {start} {
return $(([clock milliseconds] - $start) / 1000.0)
}
proc startclock {} {
set ::startclock_s [clock milliseconds]
}
proc endclock {size} {
set el [elapsed $::startclock_s]
set rate $($size / $el)
return "[pretty_size $size] in $el seconds - [pretty_size $rate]/s"
}
proc register {args} {}
set root [system mediaroot]
source auto.hook
set ::sweeper::dryrun 1 set ::sweeper::dryrun 1
::sweeper::apply $dir $cf ::sweeper::apply $dir $cf