diff --git a/CONTROL/conffiles b/CONTROL/conffiles
deleted file mode 100644
index 941459f..0000000
--- a/CONTROL/conffiles
+++ /dev/null
@@ -1 +0,0 @@
-etc/sweeper.conf
diff --git a/CONTROL/control b/CONTROL/control
index d9545f2..ed42be4 100644
--- a/CONTROL/control
+++ b/CONTROL/control
@@ -1,9 +1,9 @@
Package: sweeper
Priority: optional
Section: misc
-Version: 1.0.19
+Version: 2.0.0
Architecture: mipsel
Maintainer: af123@hummypkg.org.uk
-Depends: webif(>=1.0.14-8)
-Description: Sweeper is a package for managing recordings in a variety of ways using custom rules [Add delete action. Some online help.]
+Depends: webif(>=1.0.14-10)
+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/
diff --git a/webif/plugin/sweeper/auto.hook b/webif/plugin/sweeper/auto.hook
index d7097a3..e610af8 100644
--- a/webif/plugin/sweeper/auto.hook
+++ b/webif/plugin/sweeper/auto.hook
@@ -69,67 +69,135 @@ proc ::sweeper::strcontains {ref val} {
>= 0]
}
-######################################################################
-# Rule criteria
+proc ::sweeper::moveset {ts dst {op rename}} {
+ 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]
}
-proc ::sweeper::lcn {ts num} {
+proc ::sweeper::lcn {ts num folder} {
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]
}
-proc ::sweeper::hour {ts str} {
+proc ::sweeper::hour {ts str folder} {
set hour [clock format [$ts get start] -format "%H"]
return [::sweeper::intcomp $hour $str]
}
-proc ::sweeper::schedduration {ts dur} {
+proc ::sweeper::schedduration {ts dur folder} {
return [::sweeper::intcomp [$ts get scheddur] $dur]
}
-proc ::sweeper::size {ts size} {
+proc ::sweeper::size {ts size folder} {
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)
log " ... Recording age: $recage" 2
return [::sweeper::intcomp $recage $age]
}
-proc ::sweeper::wage {ts 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::definition {ts def} {
+proc ::sweeper::definition {ts def folder} {
return [::sweeper::strcontains [$ts get definition] $def]
}
-proc ::sweeper::filename {ts str} {
+proc ::sweeper::filename {ts str folder} {
return [::sweeper::strcontains [$ts bfile] $str]
}
-proc ::sweeper::title {ts str} {
+proc ::sweeper::title {ts str folder} {
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]
}
-proc ::sweeper::guidance {ts str} {
+proc ::sweeper::guidance {ts str folder} {
return [::sweeper::strcontains [$ts get guidance] $str]
}
-proc ::sweeper::genre {ts genre} {
+proc ::sweeper::genre {ts genre folder} {
set glist [ts genrelist]
set tsg [$ts get genre]
@@ -139,7 +207,7 @@ proc ::sweeper::genre {ts genre} {
return 0
}
-proc ::sweeper::lock {ts g} {
+proc ::sweeper::lock {ts g folder} {
if {$g} {
if {![$ts flag Locked]} {
log "Locked recording." 0
@@ -154,13 +222,13 @@ proc ::sweeper::lock {ts g} {
return 1
}
-proc ::sweeper::folder_fflag {ts flag} {
+proc ::sweeper::folder_fflag {ts flag folder} {
return [file exists "[file dirname [$ts get file]]/.$flag"]
}
######################################################################
-proc ::sweeper::action {ts cmds} {
+proc ::sweeper::action {ts cmds folder} {
global root
lassign $cmds cmd rest
@@ -171,12 +239,19 @@ proc ::sweeper::action {ts cmds} {
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 {$cmd eq "move"} {
+ if {!$create} {
log " ... No such directory $dir" 2
return 1
}
@@ -190,14 +265,11 @@ proc ::sweeper::action {ts cmds} {
}
}
}
- log "Moving [$ts get file] to $rest" 0
- foreach f [$ts fileset] {
- log " ....... $f"
- if {!$::sweeper::dryrun} {
- file rename $f "$dir/[file tail $f]"
- if {"$dir" ni $::sweeper::recalc} {
- lappend ::sweeper::recalc $dir
- }
+ 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
@@ -291,7 +363,8 @@ proc ::sweeper::folder_apply {dir callback} {
proc ::sweeper:folder_merge {src dst {op rename}} {
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] {
if {![string match {*.ts} $e]} continue
set entry "$src/$e"
@@ -313,12 +386,7 @@ proc ::sweeper:folder_merge {src dst {op rename}} {
continue
}
- foreach f [$ts fileset] {
- if {$op eq "copy" \
- && [file exists "$dst/[file tail $f]"]} continue
- log " ....... $f"
- file $op $f "$dst/[file tail $f]"
- }
+ ::sweeper::moveset $ts $dst $op
}
if {$op eq "rename" && ![system rmdir_if_empty $src]} {
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
lassign $cmds cmd rest
@@ -345,12 +413,19 @@ proc ::sweeper::folder_action {ts cmds} {
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 {$cmd eq "move"} {
+ if {!$create} {
log " ... No such directory $dir" 2
return 1
}
@@ -365,7 +440,7 @@ proc ::sweeper::folder_action {ts cmds} {
}
}
if {!$::sweeper::dryrun} {
- ::sweeper:folder_merge $folder $dir
+ ::sweeper:folder_merge $folder $dir $cmd
}
return 1
}
@@ -450,6 +525,64 @@ proc ::sweeper::folder_action {ts cmds} {
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} {
log "Processing \[$rule]" 2
@@ -462,25 +595,9 @@ proc ::sweeper::runrule {ts rule} {
}
while {[llength $rule] > 1} {
set rule [lassign $rule cmd arg]
- 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]
- } else {
- set ret [::sweeper::$cmd $ts $arg]
- }
+ set ret [::sweeper::clause $folder $cmd $arg $ts]
if {$cmd eq "action"} { return $ret }
- if {$negate} { set ret $(!$ret) }
- if {!$ret} {
- log " Nomatch" 2
- break
- }
- log " MATCH" 2
+ if {!$ret} break
}
return 0
@@ -498,12 +615,18 @@ proc ::sweeper::apply {dir cf} {
$fp close
set runfolder 0
+ set nrules 0
foreach rule $rules {
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 "--- SWEEP SCAN STARTING FOR $dir ---" 2
+ log "--- SWEEP SCAN STARTING FOR $dir ($nrules) ---" 2
log "" 2
foreach e [readdir -nocomplain $dir] {
@@ -530,7 +653,7 @@ proc ::sweeper::apply {dir cf} {
foreach rule $rules {
if {[lindex $rule 0] eq "folder"} continue
- if {[string index $rule 0] eq "#" || \
+ if {[string index $rule 0] eq "#" ||
[llength $rule] < 2} continue
if {[::sweeper::runrule $ts $rule]} break
}
@@ -608,7 +731,7 @@ proc ::sweeper::apply {dir cf} {
}
foreach rule $rules {
- if {[string index $rule 0] eq "#" || \
+ if {[string index $rule 0] eq "#" ||
[llength $rule] < 2} continue
if {[lindex $rule 0] ne "folder"} continue
if {[::sweeper::runrule $ts $rule]} break
diff --git a/webif/plugin/sweeper/edit.jim b/webif/plugin/sweeper/edit.jim
index 883babb..45887a2 100755
--- a/webif/plugin/sweeper/edit.jim
+++ b/webif/plugin/sweeper/edit.jim
@@ -74,30 +74,29 @@ Add pre-defined ruleset:
+ title="Delete Rule">
+ title="Duplicate Rule">
+ title="Move Rule Up">
+ title="Move Rule Down">
-
+
diff --git a/webif/plugin/sweeper/harness.jim b/webif/plugin/sweeper/harness.jim
new file mode 100755
index 0000000..5d86ab7
--- /dev/null
+++ b/webif/plugin/sweeper/harness.jim
@@ -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
+
diff --git a/webif/plugin/sweeper/img/plus.png b/webif/plugin/sweeper/img/plus.png
new file mode 100644
index 0000000..57a31a3
Binary files /dev/null and b/webif/plugin/sweeper/img/plus.png differ
diff --git a/webif/plugin/sweeper/rules_json.jim b/webif/plugin/sweeper/rules_json.jim
index bddd378..f5acc9c 100755
--- a/webif/plugin/sweeper/rules_json.jim
+++ b/webif/plugin/sweeper/rules_json.jim
@@ -6,6 +6,10 @@ require system.class
set dir [cgi_get dir ""]
+if {$dir eq "" && [llength $argv]} {
+ set dir [lindex $argv 0]
+}
+
set root [system mediaroot]
if {$dir eq "" || $dir eq $root} {
@@ -45,8 +49,43 @@ proc quot {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} {
- global clausedescr lcomment
+ global lcomment lockfound
set enabled 1
if {[lindex $rule 0] eq "##"} {
@@ -76,22 +115,11 @@ proc rule {id rule} {
set lockfound -1
while {[llength $rule] > 1} {
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 "lock"} { set lockfound $arg }
if {$c} { add_json ",\n" }
incr c
- add_json " {\n"
- add_json " \"negate\": $negate,\n"
- add_json " \"cmd\": \"[quot $cmd]\",\n"
- add_json " \"arg\": \"[quot $arg]\"\n"
- add_json " }"
+ clause $cmd $arg
}
add_json "\n"
if {$cmd ne "action"} {
diff --git a/webif/plugin/sweeper/schema.js b/webif/plugin/sweeper/schema.js
index 024d023..f4411d0 100644
--- a/webif/plugin/sweeper/schema.js
+++ b/webif/plugin/sweeper/schema.js
@@ -4,42 +4,49 @@ var schema = {
'class': 'all',
type: 'int',
desc: 'Logical Channel Number',
+ negate: true,
def: "0"
},
duration: {
'class': 'all',
type: 'int',
desc: 'Recording duration (in minutes)',
+ negate: true,
def: "0"
},
schedduration: {
'class': 'all',
type: 'int',
desc: 'Scheduled duration (in minutes)',
+ negate: true,
def: "0"
},
size: {
'class': 'all',
type: 'int',
desc: 'Recording size (in bytes)',
+ negate: true,
def: "0"
},
hour: {
'class': 'all',
type: 'int',
desc: 'Hour in which the recording started',
+ negate: true,
def: "0"
},
age: {
'class': 'all',
type: 'int',
desc: 'Age (in hours)',
+ negate: true,
def: "0"
},
wage: {
'class': 'all',
type: 'int',
desc: 'Time since last watched, or recorded (in hours)',
+ negate: true,
def: "0"
},
title: {
@@ -47,6 +54,7 @@ var schema = {
type: 'substr',
desc: 'Recording Title contains',
idesc: 'Recording Title does not contain',
+ negate: true,
def: 'Enter text here...'
},
synopsis: {
@@ -54,6 +62,7 @@ var schema = {
type: 'substr',
desc: 'Synopsis contains',
idesc: 'Synopsis does not contain',
+ negate: true,
def: 'Enter text here...'
},
guidance: {
@@ -61,12 +70,14 @@ var schema = {
type: 'substr',
desc: 'Guidance Text contains',
idesc: 'Guidance Text does not contain',
+ negate: true,
def: 'Enter text here...'
},
genre: {
'class': 'all',
type: 'select',
desc: 'Recording Genre',
+ negate: true,
select: {
Unclassified: 'Unclassified',
Film: 'Film',
@@ -84,6 +95,7 @@ var schema = {
'class': 'all',
type: 'select',
desc: 'Recording Definition',
+ negate: true,
select: {
SD: 'Standard Definition',
HD: 'High Definition'
@@ -95,6 +107,7 @@ var schema = {
type: 'select',
desc: 'Recording Flagged as',
idesc: 'Recording not Flagged as',
+ negate: true,
select: {
Locked: 'Locked',
New: 'New',
@@ -110,6 +123,7 @@ var schema = {
'class': 'all',
type: 'select',
desc: 'Change Recording Lock',
+ negate: true,
select: {
1: 'Lock Recording',
0: 'Unlock Recording'
@@ -121,6 +135,8 @@ var schema = {
'class': 'all',
type: 'substr',
desc: 'Filename contains',
+ idesc: 'Filename does not contain',
+ negate: true,
def: 'Enter text here...'
},
fflag: {
@@ -128,7 +144,24 @@ var schema = {
type: 'string',
desc: 'Folder flagged as',
idesc: 'Folder not flagged as',
+ negate: true,
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: {
@@ -150,24 +183,24 @@ var schema = {
desc: 'Move recording to folder...',
continues: false
},
- lock: {
- 'class': 'all',
- argtype: 'none',
- desc: 'Lock recordings',
- continues: true
- },
- unlock: {
- 'class': 'all',
- argtype: 'none',
- desc: 'Unlock recordings',
- continues: true
- },
movecreate: {
'class': 'all',
argtype: 'folder',
desc: 'Move recording to folder (creating if necessary)',
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: {
'class': 'folder',
argtype: 'folder',
@@ -187,6 +220,18 @@ var schema = {
desc: 'Rename recording files to...',
continues: true
},
+ lock: {
+ 'class': 'all',
+ argtype: 'none',
+ desc: 'Lock recordings',
+ continues: true
+ },
+ unlock: {
+ 'class': 'all',
+ argtype: 'none',
+ desc: 'Unlock recordings',
+ continues: true
+ },
delete: {
'class': 'all',
argtype: 'none',
@@ -216,47 +261,38 @@ var macros = {
example: {
desc: 'Example rules',
rules: [
- {
- "raw": "lcn {>= 70} lcn {<= 79} duration {>= 90} action {move Children/Films}",
- "name": "Move any Children's films (by length)",
+ {
+ "raw": "lcn {>= 70} lcn {<= 79} or {duration {>= 90} genre Film } action {move Children/Films}",
+ "name": "Move any Children's films",
"type": "file",
"enabled": "0",
"criteria": [
{
+ "negate": 0,
"cmd": "lcn",
"arg": ">= 70"
},
{
+ "negate": 0,
"cmd": "lcn",
"arg": "<= 79"
},
{
- "cmd": "duration",
- "arg": ">= 90"
- }
- ],
- "action": {
- "cmd": "move",
- "arg": "Children/Films"
- }
- },
- {
- "raw": "lcn {>= 70} lcn {<= 79} genre Film action {move Children/Films}",
- "name": "Move any Children's films (by genre)",
- "type": "file",
- "enabled": "0",
- "criteria": [
- {
- "cmd": "lcn",
- "arg": ">= 70"
- },
- {
- "cmd": "lcn",
- "arg": "<= 79"
- },
- {
- "cmd": "genre",
- "arg": "Film"
+ "negate": 0,
+ "cmd": "or",
+ "arg": "duration {>= 90} genre Film ",
+ "criteria": [
+ {
+ "negate": 0,
+ "cmd": "duration",
+ "arg": ">= 90"
+ },
+ {
+ "negate": 0,
+ "cmd": "genre",
+ "arg": "Film"
+ }
+ ]
}
],
"action": {
diff --git a/webif/plugin/sweeper/script.js b/webif/plugin/sweeper/script.js
index d8b859b..d33397a 100644
--- a/webif/plugin/sweeper/script.js
+++ b/webif/plugin/sweeper/script.js
@@ -90,16 +90,20 @@ var getters = {
}
};
-function ruleconf(rule)
+function clauseconf(clause)
{
var s = '';
- if (rule.hasClass('ruledisabled'))
- s += '## ';
- if (rule.attr('type') == 'folder')
- s += 'folder ';
-
- rule.find('tr.clause').each(function(i) {
+ clause.find('tbody:first > tr.clause,tbody:first > tr.compositeclause')
+ .each(function() {
+ if ($(this).hasClass('compositeclause'))
+ {
+ cc = clauseconf($(this).find('table:first'));
+ cmd = $(this).attr('cmd');
+ if (cc && cc.length)
+ s += cmd + ' {' + cc + '} ';
+ return;
+ }
cmd = $(this).find('th.cmd').attr('cmd');
negate = $(this).find('th.cmd').attr('negate');
c = schema.criterion[cmd];
@@ -111,6 +115,19 @@ function ruleconf(rule)
s += '!';
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');
arg = $.trim(rule.find('tr.action td.val').text());
@@ -144,18 +161,23 @@ function rulerefresh(rule)
if (!showraw)
rule.find('.raw').hide();
rule.find('.raw').html(ruleconf(rule));
- rule.find('tr.clause th.title').text('And:').first().text('If:');
- if (rule.find('tr.clause').length < 1)
+ rule.find('tr.clause th.title,tr.compositeclause th.title')
+ .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.otherwise').hide();
- rule.find('div.criteria,div.arrow').hide();
+ rule.find('tr.otherwise,div.criteria,div.arrow').hide();
}
else
{
rule.find('tr.action th.title').text('Then:');
- rule.find('tr.otherwise').show();
- rule.find('div.criteria,div.arrow').show();
+ rule.find('tr.otherwise,div.criteria,div.arrow').show();
}
}
@@ -172,7 +194,34 @@ function critdesc(cmd, negate)
return c.desc;
}
-function criterion(data)
+function composite_criterion(c, data)
+{
+ s = '' +
+ 'And: | ' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ ' ' +
+ '' +
+ '' +
+ ' ' +
+ c.desc +
+ ' | ';
+
+ $.each(data.criteria, function(key, val) {
+ s += criterion(val, 'comp');
+ });
+
+ s += ' |
';
+
+ return s;
+}
+
+function criterion(data, classes)
{
var c = schema.criterion[data.cmd];
var s;
@@ -182,7 +231,12 @@ function criterion(data)
alert('Unknown Criterion (' + data.cmd + ')');
return;
}
- s = 'And: | ' +
+ if (c.type == 'composite')
+ return composite_criterion(c, data);
+
+ s = '
---|
And: | ' +
'' +
critdesc(data.cmd, data.negate) + ' | ';
@@ -192,10 +246,10 @@ function criterion(data)
s += 'UNKNOWN (' + data.arg + ')';
s += ' | ' +
- '' +
+ '' +
'' +
' ' +
- '' +
+ '' +
'' +
' |
';
return s;
@@ -264,8 +318,12 @@ function addrule(id, data)
$.each(data.criteria, function(key, val) {
if (val.cmd == 'lock' && (
data.action.cmd == 'lock' || data.action.cmd == 'unlock'))
+ {
+ alert('Removed legacy lock/unlock - ' +
+ 'check rules before saving.');
return;
- $c.find('tbody').append(criterion(val));
+ }
+ $c.find('tbody:first').append(criterion(val));
});
$c.find('th.title:first').text('If:');
@@ -599,7 +657,7 @@ $('#macros').on('click', '#b_macro', function(e) {
changed(0);
-function addcriterion(rule)
+function addcriterion(rule, target)
{
var type = rule.attr('type');
@@ -616,6 +674,7 @@ function addcriterion(rule)
);
});
$('#newcondition_negate').prop('checked', false);
+ $('#newcondition_cmd').trigger('change');
$('#newcondition').dialog({
height: 'auto', width: 'auto',
@@ -624,21 +683,22 @@ function addcriterion(rule)
position: {
my: 'bottom left',
at: 'top right',
- of: rule
+ of: target
},
buttons: {
"Add Condition": function() {
$(this).dialog('close');
- var id = rule.attr('id');
var val = $('#newcondition_cmd').val();
var negate = $('#newcondition_negate')
.prop('checked') ? '1' : '0';
- rule.find('table.criteria tbody')
- .append(criterion({
+ var obj = {
cmd: val,
negate: negate,
- arg: schema.criterion[val].def
- }));
+ arg: schema.criterion[val].def,
+ criteria: []
+ };
+ target.find('tbody:first')
+ .append(criterion(obj));
changed(1);
rulerefresh(rule);
},
@@ -656,7 +716,8 @@ $('#ruleset')
})
.on('click', 'a.addcriterion', function(e) {
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) {
e.preventDefault();
@@ -827,6 +888,32 @@ $('#ruleset')
.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) {
@@ -837,7 +924,17 @@ $('#ruleset')
$('#edit_action_arg').enable();
$('.edit_action_help').hide();
$('#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
$.each(macros, function(key, val) {
@@ -854,5 +951,7 @@ $('#ruleset')
});
loadrules($('span.dir').text());
+
+ $(document).tooltip();
});
diff --git a/webif/plugin/sweeper/style.css b/webif/plugin/sweeper/style.css
index d4aa70e..445992c 100644
--- a/webif/plugin/sweeper/style.css
+++ b/webif/plugin/sweeper/style.css
@@ -62,6 +62,11 @@ div.raw
right: 12px;
}
+div.rawvis
+{
+ background: #ccc;
+}
+
div#buttons
{
padding-top: 1em;
@@ -107,6 +112,7 @@ span.legendright
table
{
border: 0;
+ line-height: 1em;
}
table th
diff --git a/webif/plugin/sweeper/test b/webif/plugin/sweeper/test
index 0372fc2..8c4a863 100755
--- a/webif/plugin/sweeper/test
+++ b/webif/plugin/sweeper/test
@@ -1,50 +1,12 @@
#!/mod/bin/jimsh
-source /mod/webif/lib/setup
-require lock system.class ts.class tdelete pretty_size browse.class \
- safe_delete settings.class plugin
+source /mod/webif/plugin/sweeper/harness.jim
-proc log {msg {level 1}} {
- puts "[\
- clock format [clock seconds] -format "%d/%m/%Y %H:%M"\
- ] - $msg"
+if {![llength $argv]} {
+ ::sweeper::scan 0
+} else {
+ 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
-
diff --git a/webif/plugin/sweeper/test.jim b/webif/plugin/sweeper/test.jim
index 9e26b21..f4dafdc 100755
--- a/webif/plugin/sweeper/test.jim
+++ b/webif/plugin/sweeper/test.jim
@@ -1,14 +1,11 @@
#!/mod/bin/jimsh
package require cgi
-source /mod/webif/lib/setup
-require lock system.class ts.class tdelete pretty_size browse.class \
- safe_delete settings.class plugin
+source /mod/webif/plugin/sweeper/harness.jim
httpheader
set dir [cgi_get dir ""]
-set root [system mediaroot]
set cf "/tmp/sweepertest.cf"
@@ -31,30 +28,6 @@ if {[catch {set fp [open $cf w]} msg]} {
$fp puts -nonewline $data
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
::sweeper::apply $dir $cf