commit d7b6477d688c32add7d1110b0fc1fe9700b29ab5 Author: MymsMan Date: Thu Apr 2 14:58:52 2020 +0100 version 1 - handle conflicts and schedule changes diff --git a/CONTROL/control b/CONTROL/control new file mode 100644 index 0000000..bdb9b1e --- /dev/null +++ b/CONTROL/control @@ -0,0 +1,11 @@ +Package: schedchk +Priority: optional +Section: misc +Version: 0.1.0-0 +Architecture: mipsel +Maintainer: mymsman +Depends: webif(>=1.4.8-1), +Description: Check scheduled recordings against EPG and reschedule, if possible, entries that don't match the EPG +or cause conflicts + +Tags: http://wiki.hummy.tv/wiki/schedchk diff --git a/webif/plugin/schedchk/schedchk.jim b/webif/plugin/schedchk/schedchk.jim new file mode 100755 index 0000000..bf4210d --- /dev/null +++ b/webif/plugin/schedchk/schedchk.jim @@ -0,0 +1,464 @@ +#!/mod/bin/jimsh +# "schedchk = Check for recording schedule issues and attempt to fix them" +# author MymsMan based on webif functions by af123 + +source /mod/webif/lib/setup +require rsv.class epg.class system.class + +proc log {msg {level 1}} { + if {$level > $::loglevel} return + puts $::logfd "[\ + clock format [clock seconds] -format "%d/%m/%Y %H:%M:%S"\ + ] SC([pid])- $msg" + flush $::logfd +} + + +# Parse command options and apply defaults +proc ::schedchk::checkopts {argv} { + set settings [settings] + set parmerror 0 + + set ::autologlevel [$settings _nval_setting "autolog"] + set ::loglevel $::autologlevel + if {[info exists ::auto::logfd]} { + set logfd ::auto::logfd + } + + # List of options with default values + set optarray { + d 0 + debug 0 + alert 0 + test 0 + noconflict 0 + } + + # Override default from settings DB + foreach {key defvalue} [array get optarray] { + set ::opts($key) [$settings _nval_setting "schedchk_$key"] + if {$::opts($key)==0} {set ::opts($key) $defvalue} + } + + # Handle text setting for oher options + set otheropts [$settings _tval_setting "schedchk_otheropts"] + if {$otheropts == 0} {set otheropts ""} + + # Parse argument lists + foreach argl [list $otheropts $argv] { + set ::optlist "" + log "arg list $argl" 2 + for {set ix 0} {$ix < [llength $argl]} {incr ix} { + set arg [lindex $argl $ix] + + #check if option in optarray list + if {[string range $arg 0 0] == "-"} { + set argx [string tolower [string range $arg 1 end]] + if {[dict exists $optarray $argx]} { + incr ix + set val [lindex $argl $ix] + set nval $val + if {$val eq "y"} {set nval 1} + if {$val eq "n"} {set nval 0} + if {![string is double -strict $nval]} { + if {[string length $nval] == 0 || + [string range $nval 0 0] == "-"} { + # Value omitted assume true + set nval 1 + set val "y" + incr ix -1 + } else { + log "Option $arg value ($val) is not y, n or numeric" 0 + incr ix -1 + set parmerror 1 + continue + } + } + lappend ::optlist $arg + lappend ::optlist $val + set ::opts($argx) $nval + continue + } + } + + # check other options + switch -- $arg { + -rsv - + --help - + -h { + set ::opt $arg + } + -hmt { + set ::opt $arg + incr ix + set file [lindex $argl $ix] + set ::file $file + + if {[file isfile $file]} { + set ::ts [ts fetch $file] + if {$::ts == 0} { + log "Cannot process ($file) file is not valid recording" 0 + set parmerror 1 + continue + } + } else { + log "Cannot process ($file) file does not exist" 0 + set parmerror 1 + continue + } + } + default { + log "Unrecognized option: $arg" 0 + set parmerror 1 + continue + } + } + + } + } + + if {$::opts(debug) || $::opts(d)} { + set ll $::loglevel + if {$::opts(debug) > $ll} {set ll $::opts(debug)} + if {$::opts(d) > $ll} {set ll $::opts(d)} + set ::debug 1 + set ::loglevel $ll + set ::auto::loglevel $ll + } + + if {$parmerror} { + log "Parameter errors found" + exit + } + +} + + +proc ::schedchk::svcmap {} { + # establish rsv <-> epg service_id mapping + global svcmap + set svcmap {} + lmap i \ + [$::channeldb query {select hSvc, usSvcid from TBL_SVC}] \ + { + set svcmap([lindex $i 1]) [lindex $i 3] + } + set svckeys [array names svcmap] +} +proc ::schedchk::conflicts {} { + global conflicts + if {$::opts(noconflict)} { + log "-noconflict Bypassing automatic conflict resolution" + set conflicts {} + return + } + + set conflicts [rsv newconflicts [system tuners] "xlist"] + if {[llength $conflicts] > 1} { + log "++++ [llength $conflicts] Conflicts exist +++" 1 + log "$conflicts" 2 + } +} + +proc ::schedchk::rsvscan {} { + global svcmap conflicts + ::schedchk::svcmap + ::schedchk::conflicts + + + set events [rsv list] + + # for each reservation + foreach event $events { + set name [$event name] + set s [$event start] + set ds "[clock format $s -format {%a %d %b %Y %H:%M}]" + set d [$event get nduration] + set e $($s + $d) + set now [clock seconds] + lassign [$event padded 1] sp ep + + # Ignore manual recordings & reminders + if {[$event get ersvtype] != 3} { continue} + # Has event passed + if {$now > $e + $ep} { + set ended 1 + incr num_ended + } else { + set ended 0 + } + + set devent "$ds [clock format $d -format {%H:%M}] === $name === [$event channel_name]" + + if {!$ended} { + log "Reservation - $devent" 2 + set elist [$event aul] + set enum -1 + set ecrids [split [$event get szEventToRecord] "|"] + set ecrid [$event get szCRID] + + if {[llength $elist] > 0} { + # check each episode scheduled + foreach epsd $elist { + lassign $epsd service_id start end event_id + incr enum + + set svc $svcmap($service_id) + # Retrieve epg record for the episode + set record [lindex [\ + epg dbfetch dump -service $svc -event $event_id -sort ""] 0] + log "$service_id $start $end $svc $event_id $record" 3 + if {$record==""} { + log "+++ No matching epg entry ++++ $devent" 0 + # look for an alternate showing + set ecrid [lindex $ecrids $enum] + set ecrid [string range $ecrid [string first "/" $ecrid] end] + set others [epg dbfetch dump -crid $ecrid -nocase 1 -debug 1] + #param "collate nocase" + log "$ecrid $ecrids $others" 0 + foreach other $others { + set ostart [$other get start] + set odur [$other get duration] + set oend $($ostart+$odur) + set oname [$other get name] + set ocname [$other get channel_name] + set dother "[clock format $ostart -format {%a %d %b %Y %H:%M}] [clock format $odur -format {%H:%M}] === $name === $ocname" + if {$ostart <= $now} {continue} + ## should favour same definition + log "Alternate Episode $dother" 2 + set oconflicts [rsv checkconflict \ + $ostart $odur \ + [system tuners]] + # Should ignore conflicts with self + if {[llength $oconflicts]} { + # Alternate has Conflicts + log "Alternate conflicts $oconflicts" 2 + continue + } + # attempt to schedule the alternate + if {[::schedchk::schedule $other $dother]} { + if {![$event isseries]} { + # delete single recording + ::schedchk::cancel $event $deps + } else { + # refresh since can't skip without epg entry + ::schedchk::refresh $event $deps + } + ::schedchk::conflicts + break + } + } + + } else { + # Check that details match + set dur $($end-$start) + set epgname [$record get name] + set deps "[clock format $start -format {%a %d %b %Y %H:%M}] [clock format $dur -format {%H:%M}] === $epgname === [$record get channel_name]" + + log "Episode $deps" 2 + set ok 1 + if {$start != [$record get start]} { + set ok 0 + log "+++ Start Mismatch: $start != [$record get start] +++ $deps" 1 + } + if {$dur != [$record get duration]} { + set ok 0 + log "+++ Duration Mismatch: $dur != [$record get duration] +++ $deps" 1 + } + # Refresh event since we cant update individual entry + if {!$ok} { + ::schedchk::refresh $event $deps + ::schedchk::conflicts + } + + # Check for conflicts + if {"[$event get ulslot]$end" in $conflicts} { + set ok 0 + log "+++ Confict exists +++ $deps" 1 + set others [$record othertimes] + foreach other $others { + set ostart [$other get start] + set odur [$other get duration] + set oend $($ostart+$odur) + set oname [$other get name] + set ocname [$other get channel_name] + set dother "[clock format $ostart -format {%a %d %b %Y %H:%M}] [clock format $odur -format {%H:%M}] === $name === $ocname" + if {$ostart == $start} {continue} + if {$ostart <= $now} {continue} + ## should favour same definition + log "Alternate Episode $dother" 2 + set oconflicts [rsv checkconflict \ + $ostart $odur \ + [system tuners]] + # Should ignore conflicts with self + if {[llength $oconflicts]} { + # Alternate has Conflicts + log "Alternate conflicts $oconflicts" 2 + continue + } + # attempt to schedule the alternate + if {[::schedchk::schedule $other $dother]} { + if {![$event isseries]} { + # delete single recording + ::schedchk::cancel $event $deps + } else { + # skip conflicted epidode + ::schedchk::skip $event $svc $event_id $deps + } + ::schedchk::conflicts + break + } + } + + } + } + } + } else { + log "+++ No episodes scheduled +++ $devent" 0 + } + } else { + log "Completed - $devent" 2 + # check for new series in same time slot + } + } + if {[llength $conflicts] > 1} { + log "++++ [llength $conflicts] Unresolved conflicts remain +++" 0 + } + log "===============================================" 1 + epg cleanup +} + +# Create a new reservation +proc ::schedchk::schedule {event desc} { + if {[$event scheduled]} { + log "*** Already scheduled *** $desc" 0 + return 1 + } + if {$::opts(test)} { + log "*** Test mode =scheduled *** $desc" 0 + return 1 + } + set type 1 + set r [rsv construct $event $type] + if {[catch {$r insert pending} msg]} { + log "+++ Error encountered while scheduling: $msg ++++ $desc" 0 + return 0 + } else { + log "*** Successfully scheduled *** $desc" 0 + system restartpending + rsv commit + return 1 + } +} + +# Refresh a reservation +proc ::schedchk::refresh {event desc} { + if {$::opts(test)} { + log "*** Test mode =refreshed *** $desc" 0 + return 1 + } + set type 1 + if {[catch { + $event clear_ulslot + $event set_refresh + $event insert + } msg]} { + log "+++ Error encountered while refreshing: $msg ++++ $desc" 0 + return 0 + } else { + log "*** Successfully refreshed *** $desc" 0 + system restartpending + rsv commit + return 1 + } +} + +# Cancel reservation +proc ::schedchk::cancel {event desc} { + if {$::opts(test)} { + log "*** Test mode =cancelled *** $desc" 0 + return 1 + } + set type 1 + if {[catch { + $event clear_ulslot + $event set_delete + $event insert pending 0 1 + } msg]} { + log "+++ Error encountered while cancelling: $msg ++++ $desc" 0 + return 0 + } else { + log "*** Successfully cancelled *** $desc" 0 + system restartpending + rsv commit + return 1 + } +} + +# Skip an episode +proc ::schedchk::skip {event xservice xevent desc} { + if {$::opts(test)} { + log "*** Test mode =skipped *** $desc" 0 + return 1 + } + if {[catch { + $event apply_skip $xservice $xevent + } msg]} { + log "+++ Error encountered while skipping: $msg ++++ $desc" 0 + return 0 + } else { + log "*** Successfully skipped *** $desc" 0 + system restartpending + rsv commit + return 1 + } + +} + +#---------------------------------------------------------------------------------------------------------- +# Start of mainline + +set ::optlist "" +set ::opt "-h" +set ::debug 0 +set logfd stdout + +# validate parameters +::schedchk::checkopts $argv + +# Open log file / use auto logfile +if {[info exists ::auto::logfile]} { + set logfile $::auto::logfile +} else { + set logfile "/mod/tmp/schedchk.log" +} +if {![info exists ::auto::logfd]} { + if {$debug} { + set logfd stdout + puts "DEBUG ON level $::loglevel" + } else { + set logfd [open $logfile "a+"] + } +} + +# process command +switch -- $opt { + -rsv { # "scan the recording schedule" + ::schedchk::rsvscan + } + -h - + --help - + default { # Help + puts "schedchk = Check for recording schedule issues and attempt to fix them" + puts " " + puts "schedchk -h = produce this help" + puts "schedchk -rsv = scan the recording schedule" + + puts "" + puts "Option Default (unless changed in Settings) " + puts "-d 0 = produce detailed debug on Stdout (0, 1, 2) " + puts "-test n = produce detailed debug on Stdout (0, 1, 2) " + + } + +}