source /mod/webif/lib/setup if {![exists -command class]} { package require oo } if {![exists -command sqlite3.open]} { package require sqlite3 } if {![exists -command binary]} { package require binary } require settings.class system.class plugin svc.class set binaryfields aulEventToRecordInfo class rsv { ulslot -1 ersvtype 0 hsvc 0 nsttime 0 szsttime "00000000000000" nduration 0 erepeat 0 usevtid 0 szevtname {} ulPreOffset 0 ulPostOffset 0 ulProgramId 0 ulSeriesId 0 ucVolume 0 ucInputMode 0 usChNum 0 ucRecKind 0 ucCRIDType 0 szCRID {} szFPBRecPath {} szRecordedProgCrid {} szEventToRecord {} aulEventToRecordInfo {} bRecomRsv 0 usLastRecordedEvtId 0 eReady 0 szSvcName {} usLcn 0 sort 0 action 0 _table "" _origstart 0 } proc {rsv dbhandle} {args} { if {"-close" in $args} { if {[info exists ::rsv::db]} { catch {$::rsv::db close} unset ::rsv::db return 1 } return 0 } if {[info exists ::rsv::db]} { return $::rsv::db } set ::rsv::db [sqlite3.open /var/lib/humaxtv/rsv.db] $::rsv::db query {attach database '/var/lib/humaxtv/channel.db' as channel} if {![file exists /var/lib/humaxtv/rsvp.db]} { file copy /var/lib/humaxtv/rsv.db /var/lib/humaxtv/rsvp.db set tdb [sqlite3.open /var/lib/humaxtv/rsvp.db] $tdb query {drop table TBL_VERSION} $tdb query {alter table TBL_RESERVATION rename to pending} $tdb query {alter table pending add column action int} $tdb query {delete from pending} $tdb close } $::rsv::db query {attach database '/var/lib/humaxtv/rsvp.db' as pending} # These are required to upgrade old tables. catch { $::rsv::db query { alter table pending add column action int} } # Add the skip table if missing catch { $::rsv::db query { create table if not exists pending.skip ( ulslot int, state text, service_id int, event_id int, hSvc int, start int, ucCRIDType int, szCRID text, szSkipCRID text ); }} catch { $::rsv::db query { create unique index pending.skipidx on skip( ulslot, state, service_id, event_id, ucCRIDType, szCRID, szSkipCRID ); } } return $::rsv::db } alias {rsv cleanup} rsv dbhandle -close rsv method status {} { switch -- [system nugget schedule.timers.status $ulslot] { "0,0" { return "idle" } "1,1" { return "ready" } "2,1" { return "arwatch" } "2,2" { return "recording" } default { return "unknown" } } } rsv method aul {{filter false}} { set aul {} set xaul {} if {$filter} { set xaul [$self skiplist] } for {set i 0} {$i < [string length $aulEventToRecordInfo]} { incr i 16} { binary scan [string range $aulEventToRecordInfo $i $($i + 15)]\ iiii service start end event_id if {"$service:$event_id" ni $xaul} { catch {lappend aul [list $service $start $end $event_id]} } } return $aul } proc {rsv mkaul} {e} { $e get_channel_info return [binary format iiii \ [$e get channel_hsvc] \ [$e get start] \ [$e end] \ [$e get event_id] \ ] } proc {rsv buildaul} {auls} { set newaul {} foreach aul $auls { lassign $aul _hsvc _start _end _eid append newaul [binary format iiii \ $_hsvc $_start $_end $_eid] } return $newaul } rsv method setaul {s} { set aulEventToRecordInfo $s } rsv method clear_ulslot {} { set ulslot -1 } rsv method isseries {} { if {$ucRecKind == 4} { return 1 } else { return 0 } } rsv method issplit {} { if {$ucRecKind == 2} { return 1 } else { return 0 } } rsv method _strip {str} { return [system strip $str] } rsv method folder {} { return [$self _strip $szFPBRecPath] } rsv method name {} { set name [$self _strip $szevtname] if {$name == ""} { switch $ersvtype { 1 { set name "--- Unnamed reminder ---" } 2 { set name "--- Unnamed manual reminder ---" } 3 { set name "--- Unnamed recording ---" } 4 { set name "--- Unnamed manual recording ---" } 5 { set name "--- Wake-up ---" } 6 { set name "--- Sleep ---" } 7 { set name "--- Auto Update ---" } 11 { set name "--- DSO Event ---" } default { set name "--- Unknown event type $ersvtype ---" } } } return $name } rsv method padded {{l 0}} { if {$ulPreOffset > 0 || $ulPostOffset > 0} { if {$l} { return [list $ulPreOffset $ulPostOffset] } else { return 1 } } else { if {$l} { return {0 0} } else { return 0 } } } rsv method channel_name {} { return [string range $szSvcName 1 end] } rsv method szsttime_stamp {} { set spaced [regsub {^(....)(..)(..)(..)(..)(..).*} \ $szsttime {\1 \2 \3 \4 \5 \6}] if {[catch { set stamp [clock scan $spaced -format "%Y %m %d %H %M %S"] }]} { return 0 } return $stamp } rsv method start {} { if {[string range $szsttime 0 3] eq "0000"} { return $nsttime } set tm [$self szsttime_stamp] if {$tm > 0} { return $tm } return $nsttime } rsv method end {} { return $([$self start] + $nduration) } rsv method showing {} { set now [clock seconds] if {$nsttime - $ulPreOffset <= $now && $nsttime + $nduration + $ulPostOffset >= $now} { return 1 } return 0 } rsv method icon {} { set rsvicon "" switch $ersvtype { 1 - 2 { set rsvicon "175_1_00_Reservation_Watch.png" } 3 - 4 { set rsvicon "175_1_11_Reservation_Record.png" } 5 { set rsvicon "745_1_10_Video_2Live.png" } 6 { set rsvicon "745_1_11_Video_1REC.png" } 7 { set rsvicon "345_6_08_ST_Ad_Hoc.png" } } return $rsvicon } rsv method RKIcon {} { switch $ucRecKind { 2 { set RKIcon "178_1_26_Icon_Split.png" } 4 { set RKIcon "175_1_11_Series_Record.png" } default { switch $erepeat { 1 {set RKIcon "521_1_00_RP_Daily_C.png"} 2 {set RKIcon "521_1_00_RP_Weekly_C.png"} 3 {set RKIcon "521_1_00_RP_Weekdays_C.png"} 4 {set RKIcon "521_1_00_RP_Weekend_C.png"} default {set RKIcon ""} } } } return $RKIcon } rsv method pendingicon {{width 30}} { switch $action { 0 { set icon "add" } 1 { set icon "close" } 2 { set icon "ar" } 3 { set icon "pad" } 4 { set icon "folder" } 5 { set icon "skip" } 6 { set icon "refresh" } } return "" } rsv method iconset {{height 20}} { set iconlist {} set icon [$self icon] if {$icon ne ""} { lappend iconlist "" if {$ersvtype == 3} { if {[$self padded]} { set padding "<- [expr $ulPreOffset / 60], [expr $ulPostOffset / 60] ->" lappend iconlist \ "\"$padding\"" } elseif {[$self status] eq "arwatch"} { lappend iconlist \ "" } else { lappend iconlist \ "" } } } set icon [$self RKIcon] if {$icon ne ""} { lappend iconlist "" } return $iconlist } rsv method set {ivName val} { set $ivName $val } rsv method setorigstart {o} { set _origstart $o } rsv method set_delete {} { set action 1 } rsv method set_unpad {} { set action 2 } rsv method set_folder {name} { set action 4 set szFPBRecPath $name } rsv method set_pad {{pre 60} {post 60}} { set action 3 set ulPreOffset $pre set ulPostOffset $post } rsv method set_refresh {} { set action 6 set aulEventToRecordInfo "" } rsv method update_aul {new_event old_aul} { # Add new_event (optional) to aulEventToRecordInfo and szEventToRecord # remove old_aul (optional) from " " # Maintain list in time order set curauls [$self aul] set ecrids [split $szEventToRecord "|"] set evtaul {} set insert 1 set start 0 # Do we have new event to add? if {$new_event != 0 && $new_event !={}} { set service_id [$new_event get channel_hsvc] set start [$new_event get start] set end [$new_event end] set evtid [$new_event get event_id] set evtaul [list $service_id $start $end $evtid] set evtcrid [string toupper \ "1[$new_event get channel_crid][$new_event get event_crid]"] set insert 0 } # rebuild aul and events list with new event in correct order lmap aul $curauls ecrid $ecrids { #puts "$aul $ecrid" if {$aul == $old_aul} {continue; #remove current entry} if {$aul == ""} {continue; #null entry} if {!$insert && $start <= [lindex $aul 1]} { set insert 1 lappend newaul $evtaul append newevnts "$evtcrid|" } lappend newaul $aul append newevnts "$ecrid|" } # add to end if not inserted if {!$insert} { set insert 1 lappend newaul $evtaul append newevnts "$evtcrid|" } set action 5 $self setaul [$self buildaul $newaul] set szEventToRecord $newevnts } rsv method set_next_event {} { # Update reservation next event info in ucVolume if {[string length $aulEventToRecordInfo] >0} { binary scan [string range $aulEventToRecordInfo 0 15] \ iiii service_id start end event_id set dur $($end-$start) if {$hsvc != $service_id || $nsttime != $start || $nduration != $dur || $usevtid != $event_id} { set nduration $dur set ucVolume "$service_id:$event_id:$start" } } else { # The next event is not yet in the EPG set time $([clock seconds] - 86400) set ucVolume "$hsvc:$usLastRecordedEvtId:$time" } } rsv method reset_next_event {} { # Ensure our copy matches the updated reservation if {$ucVolume != 0} { lassign [split $ucVolume ":"] service_id event_id start set hsvc $service_id set nsttime $start set usevtid $event_id set ucVolume 0 } } rsv method set_skip {event} { set action 5 $event get_channel_info set event_hsvc [$event get channel_hsvc] set service_id [$event get service_id] set event_id [$event get event_id] set crid [string toupper \ "[$event get channel_crid][$event get event_crid]"] if {"1$crid" ni [split $szRecordedProgCrid "|"]} { set szRecordedProgCrid "1$crid|$szRecordedProgCrid" } set szEventToRecord [string map "1$crid| {}" $szEventToRecord] catch { [rsv dbhandle] query { insert or ignore into skip (ulslot, state, ucCRIDType, szCRID, szSkipCRID, service_id, event_id, start, hSvc) values('%s', 'pending', '%s', '%s', '%s', %s, %s, %s, %s) } -1 $ucCRIDType $szCRID $crid $service_id $event_id [\ $event get start] $event_hsvc } set newaul "" set next_event -1 foreach aul [$self aul] { lassign $aul _hsvc _start _end _eid if {$_hsvc == $event_hsvc && $_eid == $event_id} continue # Track the next event which should be recorded. if {$next_event == -1} { set next_event $_eid set next_hsvc $_hsvc set next_time $_start } append newaul [binary format iiii \ $_hsvc $_start $_end $_eid] } set aulEventToRecordInfo $newaul set nsttime [$event get start] set nduration [$event get duration] # Handle skipping the next up-coming instance. if {$event_id eq $usevtid} { # (mis)use some fields to hold the next event which # should be recorded. if {$next_event == -1} { # The next event is not yet in the EPG set time $([clock seconds] - 86400) set ucVolume "$hsvc:$usLastRecordedEvtId:$time" } else { set ucVolume "$next_hsvc:$next_event:$next_time" } } } rsv method replace_skip {skiplist {debug false}} { set action 5 set szRecordedProgCrid "1[join $skiplist |1]|" # Remove episodes to be skipped from both szEventToRecord # and aulEventToRecordInfo set aul [$self aul] if {![llength $aul]} return set rec [lmap i \ [lrange [split $szEventToRecord |] 0 end-1] { # Remove the initial CRID type string range $i 1 end }] if {$debug} { puts "_AUL: [llength $aul] $aul" puts "_REC: [llength $rec] $rec" } # Should not happen but in this case the Humax software will # sort it out in the background once it sees the new skiplist. if {[llength $aul] != [llength $rec]} return set skipnext 0 set i 0 foreach crid $rec { if {$crid ni $skiplist} { incr i continue } if {$i == 0} { incr skipnext } set aul [lreplace $aul $i $i] set rec [lreplace $rec $i $i] if {$debug} { puts " -- Removing $crid" } incr i } if {$debug} { puts " AUL: [llength $aul] $aul" puts " REC: [llength $rec] $rec" } if {[llength $rec]} { set szEventToRecord "1[join $rec |1]|" set aulEventToRecordInfo [rsv buildaul $aul] } else { set szEventToRecord "" set aulEventToRecordInfo "" } # Is the next scheduled recording to be skipped? if {!$skipnext} return if {$debug} { puts "Skip next event." } if {[llength $aul]} { lassign [lindex $aul 0] _hsvc _start _end _eid set ucVolume "$_hsvc:$_eid:$_start" } else { # The next event is not yet in the EPG set time $([clock seconds] - 86400) set ucVolume "$hsvc:$usLastRecordedEvtId:$time" } } rsv method apply_skip {service event} { require epg.class lassign [epg dbfetch dump -service $service -event $event] epg if {$epg eq ""} { error "Cannot find event in EPG." } $epg get_channel_info # First check to see if there is already a pending skip for this # event and, if so, update that one. set crid [string toupper \ "[$epg get channel_crid][$epg get series_crid]"] set table pending set ev [rsv fetch $table $ersvtype $hsvc 0 $usevtid $crid] if {$ev == 0} { set table TBL_RESERVATION set ev $self $ev clear_ulslot } $ev set_skip $epg if {[catch {$ev insert_deferred} msg]} { error "Error during insert." } } rsv method remove_pending {} { [rsv dbhandle] query "delete from pending where ulslot = $ulslot" } rsv method fix_hsvc {} { set c [svc channel $szSvcName] if {$c == 0} { set c [svc load usLcn $usLcn] } if {$c == 0} return set hsvc [$c get hSvc] } proc {rsv find_hsvc} {lcn channel} { set c [svc channel $channel] if {$c == 0} { set c [svc load usLcn $lcn] } if {$c == 0} { return 0 } return [$c get hSvc] } rsv method cleanvars {} { return [lsort [lmap i [$self vars] { if {[string index $i 0] eq "_"} continue function $i }]] } rsv method insert {{table pending} {force 0} {defer 0}} { set rsvdb [rsv dbhandle] set now [clock seconds] # In-progress check if {!$force && $nsttime - $ulPreOffset <= $now} { # Start time in the past if {$nsttime + $nduration + $ulPostOffset >= $now} { # Still showing switch $action { 0 { # Add # Ok if real-time scheduling in use. if {![rsv rtsched]} { throw 20 "Event already in progress." return } } default { throw 20 "Event already in progress." return } } } elseif {$action == 0} { throw 20 "Event has finished." return } } # Duplicate check - all tables if {!$force && $action == 0 && $usevtid} { foreach tab {pending TBL_RESERVATION} { set rec [$rsvdb query " select ulslot from $tab where usevtid = '%s' and hsvc = '%s' " $usevtid $hsvc] if {[llength $rec] > 0} { throw 20 "Duplicate reservation." return } } } # Find a spare slot. if {$ulslot < 0} { set slotlist [$rsvdb query " select ulslot FROM $table order by ulslot; "] if {[llength $slotlist] > 0} { set slots [lmap i $slotlist {lindex $i 1}] set max [lindex $i end] for {set i 0} {$i < $max} {incr i} { if {$i ni $slots} { set ulslot $i break } } if {$ulslot < 0} { set ulslot $($max + 1) } } if {$ulslot < 0} { set ulslot 0 } } set fields [$self cleanvars] foreach field {szSvcName usLcn sort} { set df [lsearch $fields $field] set fields [lreplace $fields $df $df] } if {$table ne "pending"} { set df [lsearch $fields "action"] set fields [lreplace $fields $df $df] } set vals {} set bvals {} foreach field $fields { set f [$self get $field] if {$field in $::binaryfields && [string bytelength $f] > 1} { binary scan $f H* fx lappend bvals $fx lappend vals "X'%s'" } else { lappend bvals $f lappend vals "'%s'" } } set query "insert into ${table}(" append query [join $fields ","] append query ") values(" append query [join $vals ","] append query ");" #puts $query #puts $bvals $rsvdb query "delete from ${table} where ulslot = $ulslot;" $rsvdb query $query {*}$bvals system plog activity "Scheduled [$self name] @ $nsttime" if {$table eq "pending" && !$defer} { rsv commit } } rsv method insert_deferred {{table pending}} { return [$self insert $table 0 1] } proc {rsv list} {{table tbl_reservation} {extra ""} {class rsv}} { set qstring " select $table.*, channel.TBL_SVC.szSvcName, channel.TBL_SVC.usLcn, case when ersvtype > 3 then 1 else 0 end as sort1, case when nsttime + nduration < [clock seconds] then 0 else 1 end as sort2 from $table left join channel.TBL_SVC on $table.hSvc = channel.TBL_SVC.hSvc " if {$extra ne ""} { append qstring $extra } append qstring " order by sort1, sort2 desc, nsttime " #puts "QSTRING: ($qstring)" set res [[$class dbhandle] query $qstring] set records {} foreach rec $res { lappend rec _table $table lappend records [$class new $rec] } return $records } proc {rsv count} {{table tbl_reservation}} { return [llength [rsv list $table]] } proc {rsv lookuptab} {} { set records {} foreach tab {tbl_reservation pending} { set res [[rsv dbhandle] query " select usSvcId, usevtid, ucCRIDType, szCRID, ucRecKind, aulEventToRecordInfo, szRecordedProgCrid from $tab left join channel.TBL_SVC on $tab.hSvc = channel.TBL_SVC.hSvc where ersvtype <= 3 "] foreach rec $res { if {$rec(ucRecKind) == 4} { set p "S" } else { set p "E" } set records([\ string tolower "$rec(usSvcId):$rec(usevtid)"]) $p if {$rec(szCRID) eq ""} continue if {$rec(ucCRIDType) == 49} { set p "E" } elseif {$rec(ucCRIDType) == 50} { set p "S" } else { continue } foreach r [split $rec(szRecordedProgCrid) "|"] { if {$r != {}} { set records([string tolower "\ $rec(szCRID):\ [string range $r 1 end]"]) "R" } } set aul $rec(aulEventToRecordInfo) if {[string length $aul]} { for {set i 0} { $i < [string length $aul]} {incr i 16} { binary scan [string range \ $aul $i $($i + 15)] \ iiii service start end event_id set records([ string tolower \ "$service:$event_id"]) $p } } else { set records([ string tolower \ "$rec(usSvcId):$rec(szCRID)"]) $p } } } # Add all skipped episodes for all series set skiplist [rsv _skiplist] foreach skip $skiplist { set records($skip) "X" } return $records } proc {rsv xlookuptab} {} { set records {} foreach tab {tbl_reservation pending} { set res [[rsv dbhandle] query " select $tab.szCRID, channel.TBL_SVC.hSvc from $tab left join channel.TBL_SVC on $tab.hSvc = channel.TBL_SVC.hSvc where ersvtype <= 3 "] foreach rec $res { lappend records "$rec(hSvc)/[file tail $rec(szCRID)]" } } return $records } proc {rsv entry} {{table TBL_RESERVATION} crid svc} { set res [[rsv dbhandle] query " select $table.*, channel.TBL_SVC.szSvcName, channel.TBL_SVC.usLcn from $table left join channel.TBL_SVC on $table.hSvc = channel.TBL_SVC.hSvc where szCRID like '%%%s' and $table.hsvc = '%s' " $crid $svc] if {[llength $res] > 0} { return [rsv new [lindex $res 0]] } return 0 } proc {rsv fetch} {table ersvtype hsvc nsttime usevtid {crid ""} {extra ""}} { set q " select $table.*, channel.TBL_SVC.szSvcName, channel.TBL_SVC.usLcn from $table left join channel.TBL_SVC on $table.hSvc = channel.TBL_SVC.hSvc where $table.ersvtype = '%s' and $table.hsvc = '%s' and $table.usevtid = '%s' " set params "$ersvtype $hsvc $usevtid" if {$nsttime > 0} { append q " and $table.nsttime = '%s' " lappend params $nsttime } if {$crid ne ""} { append q " and szCRID = '%s' collate nocase " lappend params $crid } if {$extra ne ""} { append q " $extra " } set res [[rsv dbhandle] query $q {*}$params] if {[llength $res] > 0} { return [rsv new [lindex $res 0]] } return 0 } proc {rsv slot} {{table TBL_RESERVATION} slot} { set res [[rsv dbhandle] query " select $table.*, channel.TBL_SVC.szSvcName, channel.TBL_SVC.usLcn from $table left join channel.TBL_SVC on $table.hSvc = channel.TBL_SVC.hSvc where ulslot = %s" $slot] if {[llength $res] > 0} { return [rsv new [lindex $res 0]] } return 0 } proc {rsv rtsched} {} { if {![system nuggeted]} { return 0 } if {![[settings] rtschedule]} { return 0 } return 1 } proc {rsv commit} {{plugins 1}} { if {![rsv rtsched]} return rsv dbhandle -close if {[catch { exec /mod/boot/rsvsync -realtime >> /mod/tmp/rsvsync.log} msg]} { puts "Scheduling: $msg" } else { system restartpending 0 if {$plugins} { eval_plugins rts } } } proc {rsv construct} {event type} { global ccrid $event get_channel_info set args {} set args(ersvtype) 3 set args(hsvc) [$event get channel_hsvc] set args(nsttime) [$event get start] set args(nduration) [$event get duration] set args(usevtid) [$event get event_id] set args(szevtname) "\025[xconv [$event get name]]" set args(eReady) 30 lassign [system padding] args(ulPreOffset) args(ulPostOffset) set ccrid [string toupper [$event get channel_crid]] # Fallback from series to event if there is no series CRID. if {$type == 2 && [$event get series_crid] eq ""} { set type 1 } if {$type == 1} { # Event set args(ucCRIDType) 49 set args(ucRecKind) 1 set ecrid [$event get event_crid] if {$ecrid ne ""} { set args(szCRID) "$ccrid$ecrid" set args(szEventToRecord) "1$args(szCRID)|" set args(aulEventToRecordInfo) [rsv mkaul $event] # Handle split events if {[string match {*#*} $args(szCRID)]} { set args(ucRecKind) 2 lassign [split $args(szCRID) "#"] crid imi set args(szCRID) $crid # TODO - check to see how many parts there # are... append args(szEventToRecord) \ $args(szEventToRecord) } } } elseif {$type == 3} { # Reminder set args(ersvtype) 2 set args(szsttime) [clock format $args(nsttime) \ -format {%Y%m%d%H%M%S}] } else { # Series set args(ucCRIDType) 50 set args(ucRecKind) 4 set args(szCRID) "$ccrid[$event get series_crid]" set args(szFPBRecPath) "$args(szevtname)" set events {} set seen {} set now [clock seconds] set progs [lmap i [\ epg dbfetch dump -scrid [$event get series_crid] \ -service [$event get service_id] \ -sort start] { if {[set ecrid [$i get event_crid]] eq ""} continue if {[$i end] < $now} { set args(usLastRecordedEvtId) [$i get event_id] continue } if {$ecrid in $seen} continue lappend seen $ecrid lappend events [rsv mkaul $i] list "1$::ccrid$ecrid" }] # set 1st event to record binary scan [string range [lindex $events 0] 0 15] \ iiii service_id start end event_id #puts "$service_id $start [clock format $end] $event_id" set args(hsvc) $service_id set args(nsttime) $start set args(usevtid) $event_id set args(nduration) $($end-$start) set args(szEventToRecord) "[join $progs "|"]|" set args(aulEventToRecordInfo) [join $events ""] } return [rsv new $args] } proc {rsv manual} {start end lcn type repeat {title ""}} { set args {} set args(ersvtype) $type set args(erepeat) $repeat set args(nsttime) $start set args(szsttime) [clock format $args(nsttime) \ -format {%Y%m%d%H%M%S}] set args(nduration) $($end - $start) set c [svc load usLcn $lcn] set args(hsvc) [$c get hSvc] if {$title eq ""} { set title [$c get szSvcName] } set args(szevtname) $title set args(ucRecKind) 0 set args(usevtid) 0 set args(eReady) 30 return [rsv new $args] } proc {rsv backup} {file} { set rsvdb [rsv dbhandle] require epg.class if {[catch { set fd [open $file w] } msg]} { error "Error creating backup file. - $msg" } puts "Backing up scheduled recordings and events..." set events [rsv list] set fields [[rsv] cleanvars] puts $fd "# version 2" puts $fd "#\n# Schedule\n# [join $fields "\t"]" foreach event $events { puts " Backing up scheduled event '[$event name]'" puts -nonewline $fd "event\t" foreach f $fields { set ret [$event get $f] if {$f in $::binaryfields} { binary scan $ret H* ret } puts -nonewline $fd "$ret\t" } puts $fd "" } puts "Done." puts "Backing up channel favourites..." puts " Names:" puts $fd "#\n# Favourites" # Favourite names set favnames {} loop i 1 6 { set favname [system strip \ [system param FAV_CUSTOM_STR0$i Text]] set idx [expr 1 << ($i - 1)] set favnames($idx) $favname puts $fd "favname\t$i\t$idx\t$favname" puts " $favname" } set grp 0 foreach res [$rsvdb query { select eFavGroup, TBL_FAV.eSvcType, substr(szSvcName, 2) as szSvcName, favIdx from TBL_FAV join TBL_SVC using (hSvc) order by eFavGroup, favIdx }] { if {$res(eFavGroup) != $grp} { set grp $res(eFavGroup) if {[dict exists $favnames $grp]} { puts " Group $favnames($grp)" } else { puts " Group '$grp':" } } puts " $res(szSvcName)" puts $fd "fav\t$res(eFavGroup)\t$res(eSvcType)\t$res(szSvcName)\t$res(favIdx)" } puts "Done." puts "Backing up skiplist..." set skiplist [$rsvdb query { select * from skip order by ulslot, start }] if {[llength $skiplist]} { # Add key line set keys "#\n# Skiplist\n#" foreach {k v} [lindex $skiplist 0] { append keys " $k," } puts $fd $keys foreach skip $skiplist { puts -nonewline $fd "skip\t" foreach {k v} $skip { puts -nonewline $fd "$v\t" } puts $fd "" } } puts "Done." puts "Backing up channel list..." puts $fd "#\n# Channels by hSvc" foreach channel [epg channellist hSvc] { lassign $channel name hsvc puts $fd "hsvc\t$hsvc\t$name" } puts $fd "#\n# Channels by LCN" foreach channel [epg channellist usLcn] { lassign $channel name uslcn puts $fd "lcn\t$uslcn\t$name" } puts "Done." close $fd } proc {rsv restore} {file} { set rsvdb [rsv dbhandle] if {![file exists $file]} { error "Backup file $file does not exist." } if {[catch { set fd [open $file r] } msg]} { error "Error opening $file - $msg" } set data [split [read $fd] "\n"] set ver 1 set hsvcmap {} set lcnmap {} set favmap {} # Check version, build maps for later. foreach line $data { if {[string match "# version *" $line]} { set ver [lindex [split $line " "] 2] puts "Backup version $ver" } lassign [split $line "\t"] tag f1 f2 f3 f4 switch -- $tag { hsvc { set hsvcmap($f1) $f2 } lcn { set lcnmap($f2) $f1 } favname { set favmap($f2) $f3 } } } set chanmap [lreverse $hsvcmap] puts "Restoring scheduled events from $file..." # Disable RTS until next restart. system nugget quit # Clear tables foreach tab {TBL_RESERVATION pending skip} { $rsvdb query {delete from %s;} $tab } set fields [[rsv] cleanvars] ###################################################################### # Restore events foreach line $data { set vals [split $line "\t"] if {[lindex $vals 0] ne "event"} continue set vars {} set i 0 foreach f $fields { if {$ver < 2 && $f eq "aulEventToRecordInfo"} { continue } incr i set val [lindex $vals $i] if {$f in $::binaryfields} { set val [binary format H* $val] } lappend vars $f $val } # Don't restore DSO events. if {$vars(ersvtype) == 11} continue set rsv [rsv new $vars] # Need to fix up channel and CRID mappings in case something # has changed during a channel scan. puts " Restoring [$rsv name] ([$rsv get szSvcName])" set bad 0 # First, the service number set ohsvc [$rsv get hsvc] if {$ohsvc > 0} { set hsvc [$rsv fix_hsvc] if {$hsvc == 0} { puts " Cannot find channel, restore failed." set bad 1 } elseif {$hsvc != $ohsvc} { puts -nonewline " Service number has " puts "changed $ohsvc -> $hsvc, fixing." } else { puts " No change in channel service." } } if {!$bad} { # Need to fix up the AUL table service IDs too. set newaul "" foreach aul [$rsv aul] { # {service start end event_id} lassign $aul ohsvc start end eid if {![dict exists $hsvcmap $ohsvc]} { # Should not happen puts " Losing AUL entry ($ohsvc)." continue } set lcn 0 set channame $hsvcmap($ohsvc) if {[dict exists $lcnmap $channame]} { set lcn $lcnmap($channame) } # Find the new hsvc if possible. set _hsvc [rsv find_hsvc $lcn $channame] if {$_hsvc eq ""} continue puts -nonewline " AUL $channame ($ohsvc" if {$ohsvc != $_hsvc} { puts -nonewline " -> $_hsvc" } puts ")" append newaul [binary format iiii \ $_hsvc $start $end $eid] } $rsv setaul $newaul } if {!$bad} { if {[catch {$rsv insert pending 1 1} msg]} { puts " Error inserting event, $msg" } } puts "" } ###################################################################### # Restore skiplist puts "Restoring skiplist..." set fields "ulslot state service_id event_id hSvc start ucCRIDType szCRID szSkipCRID" foreach line $data { set vals [lrange [lassign [split $line "\t"] key] 0 end-1] if {$key ne "skip"} continue lassign $vals {*}$fields # Map old hSvc to new if {![dict exists $hsvcmap $hSvc]} { # Should not happen puts " Losing skip entry ($hSvc)" continue } set chan $hsvcmap($hSvc) puts " Restoring skip for $chan - $szSkipCRID" # Fetch new hSvc and service_id set c [svc channel $chan] if {$c == 0} { puts " Cannot find channel" continue } set _hsvc [$c get hSvc] set _service_id [$c get usSvcId] if {$_hsvc eq "" || $_service_id eq ""} { puts " Cannot map channel name to service." continue } if {$hSvc != $_hsvc || $service_id != $_service_id} { puts -nonewline " Service number has changed " puts "$hSvc -> $_hsvc, fixing." lset vals [lsearch $fields hSvc] $_hsvc lset vals [lsearch $fields service_id] $_service_id } catch {$rsvdb query " insert into skip([join $fields ,]) values ( [join [lrepeat [llength $fields] "'%s'"] ,] ); " {*}$vals} } ###################################################################### # Restore favourites puts "Restoring favourite channels..." $rsvdb query {delete from channel.TBL_FAV} $rsvdb query {drop table if exists pending.fav} $rsvdb query {create table pending.fav ( favIdx integer primary key unique, hSvc integer(4), eFavGroup integer(4), [eSvcType] integer(4) )} $rsvdb query {drop table if exists pending.favnames} $rsvdb query {create table pending.favnames ( idx integer primary key unique, name string )} set grp 0 set inc 0 set lastidx 1 foreach line $data { set vals [split $line "\t"] if {[lindex $vals 0] eq "favname"} { lassign $vals x num x name $rsvdb query { insert into pending.favnames(idx, name) values(%s, '%s') } $num $name continue } if {[lindex $vals 0] ne "fav"} { continue } lassign $vals x group type chan idx if {$idx eq ""} { # This is to support old format backup files where # the favourite index is not present. set idx 0 } else { if {$idx eq "-"} { set idx $lastidx incr inc } set lastidx $idx incr idx $inc } if {$grp != $group} { set grp $group if {[dict exists $favmap $grp]} { puts " Group $favmap($grp)" } else { puts " Group $grp" } } puts " $chan" set c [svc channel $chan] if {$c == 0} { puts " Cannot map channel name to service." continue } set hsvc [$c get hSvc] if {$hsvc eq ""} { puts " Cannot map channel name to service." continue } $rsvdb query { insert into pending.fav(favIdx, hSvc, eFavGroup, eSvcType) values(%s, %s, %s, %s); } $idx $hsvc $group $type } system restartpending close $fd } # Returns an array of expanded events from the schedule. # Array keys: # 0: start # 1: end # 2: hSvc # 3: event_id # 4: Schedule ID (sid) # 5: ucRecKind # 6: class (live, pending) proc {rsv allevents} {{xota 0}} { set events [rsv list] set pending [rsv list pending] if {[llength $pending]} { lappend events {*}$pending } set today [midnight] set xevents {} foreach e $events { set seen 0 if {[$e get ersvtype] > 4} continue if {[$e get action] ne "0"} continue if {[$e start] < $today} continue if {$xota && [$e get szevtname] eq "Disable OTA"} continue set trailer [list \ [$e get ulslot] \ [$e get ucRecKind] \ [$e get _table] \ ] # Expand the events encoded in the AUL data. foreach a [$e aul] { lassign $a service start end event_id if {$start == [$e start] && \ $end == [$e end]} { incr seen } lappend xevents [list $start $end $service $event_id \ {*}$trailer] } if {$seen} continue set start [$e start] set end [$e end] lappend xevents [list $start $end \ [$e get hsvc] [$e get usevtid] {*}$trailer] set repeat [$e get erepeat] switch $repeat { 1 - 3 - 4 { # 1 Daily # 3 Weekends # 4 Weekdays for {set i 0} {$i < 8} {incr i} { incr start 86400 incr end 86400 # Sun == 0 set day [clock format $start -format "%w"] # Weekdays Only if {$repeat == 3 && ($day == 0 || $day == 6)} { continue } # Weekends Only if {$repeat == 4 && $day != 0 && $day != 6} { continue } lappend xevents [list \ $start $end \ [$e get hsvc] [$e get usevtid] \ {*}$trailer] } } 2 { # Weekly lappend xevents [list \ $($start + 7 * 86400) \ $($end + 7 * 86400) \ [$e get hsvc] [$e get usevtid] \ {*}$trailer] } } } return $xevents } proc {rsv evaluate_conflicts} {events type thresh {debug 0}} { set conflicts {} set slots {0 0} foreach ev $events { lassign $ev start end hsvc eid sid if {$debug} { puts "\nSLOTS: $slots" puts $ev } # Close off any open slots that have now finished. for {set i 0} {$i < 2} {incr i} { set v [lindex $slots $i] if {$v eq "0"} { if {$debug} { puts "\[$i] empty." } continue } lassign $v xsid xend if {$start >= $xend} { lset slots $i 0 if {$debug} { puts "\[$i] finished $xend." } } else { if {$debug} { puts "\[$i] running $xend." } } } # Find slot for recording if {[lindex $slots 0] eq "0"} { set uslot 0 } elseif {$thresh > 1 && [lindex $slots 1] eq "0"} { set uslot 1 } else { # Conflict detected if {$debug} { puts " Conflict." } if {$type eq "xlist"} { set c "$sid$end" for {set i 0} {$i < 2} {incr i} { set v [lindex $slots $i] if {$v eq "0"} continue lassign $v xsid xend lappend c "$xsid$xend" } } else { set c "$sid" for {set i 0} {$i < 2} {incr i} { set v [lindex $slots $i] if {$v eq "0"} continue lassign $v xsid xend lappend c "$xsid" } } foreach x $c { if {![dict exists $conflicts $x]} { set conflicts($x) $c } } # If this events ends later than the # existing one in slot 0, then replace that # with this one. lassign [lindex $slots 0] xsid xend if {$xend >= $end} continue if {$debug} { puts " Replacing slot 0.\n" } set uslot 0 } if {$debug} { puts " -> into slot $uslot" } # Insert event into slot lset slots $uslot [list $sid $end] } return $conflicts } proc {rsv newconflicts} {{thresh 1} {type "list"} {debug 0}} { set events [lsort -index 0 -integer [rsv allevents]] set conflicts [rsv evaluate_conflicts $events $type $thresh $debug] if {$type eq "map"} { return $conflicts } return [dict keys $conflicts] } proc {rsv checkconflict} {s d thresh {debug 0}} { set events [rsv allevents] lappend events [list $s $($s + $d) 0 0 0] set events [lsort -index 0 -integer $events] set conflicts [rsv evaluate_conflicts $events 'list' $thresh $debug] set ret {} if {![dict exists $conflicts "0"]} { return $ret } foreach c [dict get $conflicts 0] { if {$c eq "0"} continue set s [rsv slot $c] set s [$s name] lappend ret $s } return $ret } proc {rsv clearskip} {slot {start 0}} { set q "delete from skip where ulslot = $slot " if {$start} { append q "and start = $start" } catch { [rsv dbhandle] query $q } } proc {rsv _skiplist} {{ulslot -1}} { set dbh [rsv dbhandle] set q "select group_concat(hSvc || ':' || event_id, ' ') as list from skip where state = 'live'" if {$ulslot >= 0} { append q " and ulslot = $ulslot" } lassign [$dbh query $q] ret return $ret(list) } rsv method skiplist {} { return [rsv _skiplist $ulslot] } proc {rsv reassert_skips} {{debug false}} { set rsvdb [rsv dbhandle] set changes 0 # Remove any expired skips $rsvdb query "delete from skip where start < [clock seconds]" foreach s [$rsvdb query { select ulslot, szCRID, group_concat(szSkipCRID) as skiplist from skip where state = 'live' and ulslot >= 0 group by 1, 2 order by ulslot, start }] { if {$debug} { puts "\n--> SKIP: $s" } if {![dict exists $s ulslot]} continue if {[catch { set r [rsv slot $s(ulslot)] }]} continue if {$r eq "0"} { if {$debug} { puts "Slot no longer in schedule." } rsv clearskip $s(ulslot) continue } if {[$r get szCRID] ne $s(szCRID)} { if {$debug} { puts "Slot now for different series." } rsv clearskip $s(ulslot) continue } #if {$debug} { require classdump; classdump $r } set toskip [split $s(skiplist) ,] set rsvskip [lmap i \ [lrange [split [$r get szRecordedProgCrid] |] 0 end-1] { # Remove the initial CRID type string range $i 1 end }] # Build list of events in rsvskip which are not in toskip. # These are entries which have been added as episodes have # recorded. set recskip [lmap i $rsvskip { if {$i in $toskip} continue function $i }] # Build a new skip list consisting of: # Upcoming skipped events (in start order) # Other events from existing list (recorded events) # Always allow space for two of the "other events" if they # are present. set others [llength $recskip] if {$others > 2} { set others 2 } set skiplist [lrange $toskip 0 $(5 - $others - 1)] lappend skiplist {*}$recskip # Truncate to 5 entries set skiplist [lrange $skiplist 0 4] if {$debug} { puts "TOSKIP: $toskip" puts "RSVSKIP: $rsvskip" puts "RECSKIP: $recskip" puts "SKIPLIST: $skiplist" } if {$skiplist eq $rsvskip} { puts "Skip list already correct, nothing to do." continue } set ev [rsv fetch pending [$r get ersvtype] \ [$r get hsvc] 0 [$r get usevtid] $s(szCRID)] if {$ev ne "0"} { if {$debug} { puts "Found pending entry, skipping." } continue } # Apply the new skiplist. $r clear_ulslot $r replace_skip $skiplist #if {$debug} { require classdump; classdump $r } $r insert_deferred puts "Applied new skip list." incr changes } if {$changes} { if {$debug} { puts "Committing changes..." } rsv commit } }