diff --git a/webif/html/browse/index.jim b/webif/html/browse/index.jim index 587f67eb..8e6f27c7 100755 --- a/webif/html/browse/index.jim +++ b/webif/html/browse/index.jim @@ -215,8 +215,8 @@ proc entry {file} {{i 0}} { # Indexed set dlna 0 - if {$::dlnaok && $::model eq "HDR" && - [llength [system dlnaurl $file]]} { + if {$::dlnaok && $::model eq "HDR" && [llength [ + system dlnaurl [file normalize $file]]]} { icon "/img/dlna.png" "Indexed by DLNA Server" set dlna 1 } diff --git a/webif/lib/system.class b/webif/lib/system.class index 9da71279..cb19d188 100644 --- a/webif/lib/system.class +++ b/webif/lib/system.class @@ -316,10 +316,6 @@ proc {system dlnadb} {} { proc {system _dlnaurl} {file urlbase} { set mime "video/mp2t" - set nfile $file - if {![catch {set nfile [file normalize $file]}]} { - set file $nfile - } if {[catch {set db [sqlite3.open [system dlnadb]]}]} { return {} } diff --git a/webif/lib/system.class.orig b/webif/lib/system.class.orig new file mode 100644 index 00000000..9da71279 --- /dev/null +++ b/webif/lib/system.class.orig @@ -0,0 +1,921 @@ + +source /mod/webif/lib/setup + +package require xconv + +if {![exists -command class]} { package require oo } +if {![exists -command sqlite3.open]} { package require sqlite3 } +if {![exists -command binary]} { package require binary } + +class system {} + +proc {system model} {} {{model ""}} { + if {$model ne ""} { return $model } + if {[catch {set fp [open /etc/model r]}]} { + set model {HD[R]} + } else { + set model [string trim [read $fp]] + close $fp + } + return $model +} + +proc {system hostname} {} { + if {[catch {set hostname [string trim [exec hostname]]}]} { + set hostname "humax" + } + return $hostname +} + +proc {system ip} {} { + if {[catch {set fp [open /etc/hosts r]}]} { + set ip "127.0.0.1" + } else { + set ipl [lindex [split [$fp read] "\n"] 1] + regsub -- {[[:space:]].*} $ipl "" ip + $fp close + } + return $ip +} + +proc {system modversion} {{short 0}} {{modver ""}} { + if {$modver eq ""} { + if {[catch {set fp [open /etc/modversion r]}]} { + set modver "102" + } else { + set modver [string trim [read $fp]] + close $fp + } + } + if {$short} { return $modver } + lassign [split $modver ""] a b c + return [format "%d.%d%d" $a $b $c] +} + +proc {system modbuild} {} {{modbuild ""}} { + if {$modbuild ne ""} { return $modbuild } + if {[catch {set modbuild [string trim [file read /etc/modbuild]]}]} { + set modbuild 0 + } + return $modbuild +} + +proc {system lastbootreason} {{descr 1}} {{lbr -1}} { + if {$lbr == -1} { + set lbr 0 + if {[file readable /tmp/.lbr]} { + set lbr [file read /tmp/.lbr] + } + } + if {!$descr} { return $lbr } + switch $lbr { + 1 { return "Front panel button" } + 2 { return "Remote control handset" } + 3 { return "Scheduled event" } + 4 { return "Power cycle" } + } + return "Unknown ($lbr)" +} + +proc {system idletime} {} { + if {[file exists "/tmp/.lastir"]} { + return $([clock seconds] - [file mtime "/tmp/.lastir"]) + } + return 0 +} + +proc {system fhtcpversion} {} {{ver ""}} { + if {$ver ne ""} { return $ver } + set file "/etc/fhtcpversion" + if {![file exists $file]} { set file "/root/fhtcpversion" } + if {[catch {set fp [open $file r]}]} { + set ver "?.??.??" + } else { + set ver [string trim [read $fp]] + close $fp + } + return $ver +} + +# :c0400 System ID +# 4:c8400 read 1128 bytes +# 3:c8c00 MAC address +# 2:c9000 +# 1:cb400 DTCP key material +# 0:cb800 Serial Number +# 5:cbc00 + +proc {system systemid} {} {{id ""}} { + if {$id ne ""} { return $id } + set fd [open /dev/mtd3 r] + + $fd seek 0xc0400 + set bytes [$fd read 4] + $fd close + binary scan $bytes H* hex + set id [format "%s.%s" [string range $hex 0 3] [string range $hex 4 end]] + return $id +} + +proc {system macaddr} {} {{mac ""}} { + if {$mac ne ""} { return $mac } + set fd [open /dev/mtd3 r] + + $fd seek 0xc8c00 + set bytes [$fd read 6] + $fd close + set tmac "" + foreach b [split $bytes ""] { + binary scan $b H* hex + if {$tmac ne ""} { append tmac : } + append tmac $hex + } + return [set mac $tmac] +} + +proc {system serialno} {} {{serial ""}} { + if {$serial ne ""} { return $serial } + set fd [open /dev/mtd3 r] + + $fd seek 0xcb800 + set bytes [$fd read 14] + $fd close + set serial "[string range $bytes 0 1] [string range $bytes 2 8] [ + string range $bytes 9 end]" + return $serial +} +proc {system keybytestostring} {key_bytes} { + binary scan $key_bytes H* key_str + if {[string length $key_str] == 32} { + return $key_str + } + return {} +} + +proc {system encryptionkey} {} {{key ""}} { + if {$key ne ""} { return $key } + set fd [open /dev/mtd3 r] + $fd seek 0xc8c00 + set bytes [$fd read 6] + $fd seek 0xcb800 + append bytes [$fd read 10] + $fd close + return [system keybytestostring $bytes] +} + +proc {system customencryptionkey} {{key ""}} {{keyfile "/mod/boot/cryptokey"}} { + + set ck_fd {} + try { + if {$key eq ""} { + set ck_fd [open $keyfile r] + set ck_bytes [$ck_fd read 16] + return [system keybytestostring $ck_bytes] + } elseif {[string equal -nocase $key [system encryptionkey]]} { + file delete -force $keyfile + return $key + } else { + set ck_bytes [binary format H* $key] + set test [system keybytestostring $ck_bytes] + if {![string equal -nocase $test $key]} { + throw 1 "Invalid custom key" + } + if {[file exists $keyfile]} { + # attempt not to truncate on update until written + set access r+ + } else { + set access w + } + set ck_fd [open $keyfile $access] + $ck_fd seek 0 + $ck_fd puts -nonewline $ck_bytes + $ck_fd close + set ck_fd {} + return $key + } + } on error {msg opts} { + return {} + } finally { + if {$ck_fd ne {}} { + $ck_fd close + } + } +} + +proc {system loaderver} {} {{ver ""}} { + if {$ver ne ""} { return $ver } + set fd [open /dev/mtd3 r] + $fd seek 0x20006 + set bytes [$fd read 2] + $fd close + set ver [switch $bytes { + "\x04\x5f" { format "a7.30" } + "\xec\xe5" { format "a7.31" } + "\x59\x9d" { format "a7.33" } + "\x72\x5c" { format "a7.34" } + "\xe5\xe3" { format "L7.26" } + "\x8f\x7c" { format "L7.27" } + default { + binary scan $bytes H* hex + format "Unknown - $hex" + } + }] + return $ver +} + +proc {system kernelver} {} {{ver ""}} { + if {$ver ne ""} { return $ver } + #1 SMP Sun Mar 25 18:30:38 KST 2012 + set rver [string range [exec uname -v] 11 end] + set ver [switch $rver { + # HDR + "Sep 16 20:17:56 KST 2010" { format "HDR_1.01.05" } + "Oct 11 21:11:05 KST 2010" { format "HDR_1.01.09" } + "Jan 21 15:44:39 KST 2011" { format "HDR_1.02.07" } + "Jun 11 00:54:19 KST 2011" { format "HDR_1.02.20" } + "Mar 25 18:30:38 KST 2012" { format "HDR_1.02.27" } + "May 27 00:19:34 KST 2012" { format "HDR_1.02.28" } + "Jul 5 11:11:28 KST 2012" { format "HDR_1.02.29" } + "Jan 12 16:49:05 KST 2013" { format "HDR_1.02.32" } + "Mar 6 07:27:02 KST 2013" { format "HDR_1.03.06(a)" } + "May 8 14:32:30 KST 2013" { format "HDR_1.03.06(b)" } + "Dec 10 14:36:54 KST 2013" { format "HDR_1.03.11" } + "Feb 7 14:15:02 KST 2014" { format "HDR_1.03.12" } + "May 19 22:39:27 BST 2014" { format "HDR_CFW_3.00" } + "Feb 19 20:58:57 GMT 2015" { format "HDR_CFW_3.03" } + "Mar 11 23:34:27 GMT 2016" { format "HDR_CFW_3.10" } + "Mar 11 23:37:22 GMT 2016" { format "HDR_CFW_3.10d" } + "Jan 9 04:46:49 GMT 2017" { format "HDR_CFW_3.11" } + "Jan 9 04:50:13 GMT 2017" { format "HDR_CFW_3.11d" } + "Mar 23 19:36:14 GMT 2017" { format "HDR_CFW_3.12" } + "Mar 23 19:39:44 GMT 2017" { format "HDR_CFW_3.12d" } + "Apr 18 22:42:30 BST 2017" { format "HDR_CFW_3.13" } + "Apr 18 22:45:34 BST 2017" { format "HDR_CFW_3.13d" } + "Mar 29 13:48:27 BST 2020" { format "HDR_CFW_3.14" } + "Mar 29 13:52:16 BST 2020" { format "HDR_CFW_3.14d" } + + # HD + "Oct 11 21:14:31 KST 2010" { format "HD_1.01.12" } + "May 17 14:16:20 KST 2011" { format "HD_1.02.18" } + "Jun 11 00:54:19 KST 2011" { format "HD_1.02.20" } + "Mar 25 07:09:19 KST 2012" { format "HD_1.02.27" } + "May 27 00:19:40 KST 2012" { format "HD_1.02.28" } + "Jul 5 19:41:17 KST 2012" { format "HD_1.02.29" } + "Oct 13 12:48:09 KST 2012" { format "HD_1.02.31" } + "Dec 9 14:15:07 KST 2014" { format "HD_1.03.02" } + + default { format "Unknown - $rver" } + }] + return $ver +} + +# Newer version below is over 100 times faster +#proc {system pkgver} {{pkg webif}} { +# return [lrange [split [exec opkg list-installed $pkg] " "] 2 end] +#} + +proc {system pkgver} {{pkg webif}} { + if {[catch {set fp [open /mod/var/opkg/info/$pkg.control r]}]} { + return 0 + } + + set v 0 + foreach line [split [$fp read] "\n"] { + if {[string equal -length 9 $line "Version: "]} { + lassign $line x v + break + } + } + $fp close + return $v +} + +proc {system pkginst} {pkg} { + return [file exists "/mod/var/opkg/info/$pkg.control"] +} + +proc {system mediaroot} {} { + switch [system model] { + HDR { return "/media/My Video" } + HD { return "/media/drive1/Video" } + } + return "" +} + +proc {system dlnastatus} {} { + return [system is_listening 9000] +} + +proc {system dlnadb} {} { + switch [system model] { + HDR { return "/mnt/hd2/dms_cds.db" } + HD { return "/media/drive1/dms_cds.db" } + } + return "" +} + +proc {system _dlnaurl} {file urlbase} { + set mime "video/mp2t" + set nfile $file + if {![catch {set nfile [file normalize $file]}]} { + set file $nfile + } + if {[catch {set db [sqlite3.open [system dlnadb]]}]} { + return {} + } + set muri [$db query { + select tblresource.mimetype, contenturi + from tblresource join tblmedia using (mediaid) + where localurl = '%s'} $file] + if {[llength $muri]} { + lassign [lindex $muri 0] x maybemime x xuri + if {$maybemime ne "video/ts"} { + set mime $maybemime + } + } else { + # Try for partially linked entry + set muri [$db query { + select mediaid from tblmedia + where localurl = '%s' + } $file] + if {![llength $muri]} { return {} } + lassign [lindex $muri 0] x mediaid + set xuri "media/$mediaid.TS" + } + + $db close + + set url "http://${urlbase}:9000/web/$xuri" + + return [list $url $mime] +} + +proc {system dlnaurl} {file {urlbase "127.0.0.1"}} { + if {$urlbase eq ""} { set urlbase [system ip] } + set retries 5 + set ret {} + while {$retries > 0 && [\ + catch {set ret [system _dlnaurl $file $urlbase]}]} { + incr retries -1 + sleep 1 + } + return $ret +} + +proc {system dlnahelper} {file {urlbase "127.0.0.1"}} { + set dir /mnt/hd2/mod/.dlnahelper + require lock + + if {$file eq "-release"} { + release_lock dlnahelper + return {} + } + + if {![acquire_lock dlnahelper 2]} { return {} } + + if {![file isdirectory $dir]} { file mkdir $dir } + + set base [file rootname $file] + foreach ext {hmt ts} { + file delete "$dir/helper.$ext" + file link -symbolic "$dir/helper.$ext" "$base.$ext" + } + + set ret [system dlnaurl "$dir/helper.ts" $urlbase] + if {![llength $ret]} { + # Manually import the helper file. + if {![catch {set db [sqlite3.open [system dlnadb]]}]} { + $db query { + insert into tblmedia + (localurl,folder,mediatype,refcount) + values('%s','%s',0,1) + } "$dir/helper.ts" $dir + $db close + set ret [system dlnaurl "$dir/helper.ts" $urlbase] + } + } + if {![llength $ret]} { + release_lock dlnahelper + } + return $ret +} + +proc {system dustbin} {{short 0}} { + set dustbin "\[Deleted Items\]" + if {![catch {set fd [open "/mod/boot/dustbin.name" r]}]} { + set dustbin [lindex [split [read $fd] "\n"] 0] + $fd close + } + if {$short} { return $dustbin } + return "[system mediaroot]/$dustbin" +} + +proc {system dustbinsize} {} { + set bin [system dustbin] + set ret 0 + if {[file isdirectory $bin]} { + if {[catch { + lassign [exec /mod/bin/busybox/du -s $bin] ret + }]} { + set ret 0 + } + } + return $($ret * 1024) +} + +proc {system diskpart} {} { + switch [system model] { + HDR { return "/mnt/hd2" } + HD { return "/media/drive1" } + } + return "" +} + +proc {system diskdev} {} { + return [lindex [lsearch -regexp -inline \ + [split [file read /proc/mounts] "\n\r"] " [system diskpart] "] 0] +} + +proc {system disk} {} { + return [string range [system diskdev] 0 end-1] +} + +proc {system disktemp} {} { + if {[catch { + set smart [exec /bin/smartctl -A [system disk] | grep ^194] + regsub -all -- {[[:space:]]+} $smart " " smart + set temp [lindex [split $smart] 9] + }]} { + set temp 0 + } + return $($temp + 0) +} + +proc {system tsrdir} {} { + switch [system model] { + HDR { + set tsrdir "/mnt/hd2/Tsr" + } + HD { + set tsrdir "/media/drive1/.tsr" + } + } + return $tsrdir +} + +proc {system tsr} {} { + return [file join [system tsrdir] "0.ts"] +} + +require pretty_size + +proc {system diskspace} {{raw 0}} { + set part [system diskpart] + + lassign [exec /mod/bin/busybox/stat -f -c {%S %b %f} $part] \ + bsize blocks fblocks + + set size $($bsize * $blocks) + set free $($bsize * $fblocks) + set used $($size - $free) + set perc $($used * 100 / $size) + set fperc $(100 - $perc) + + set tsrdir [system tsrdir] + switch [system model] { + HDR { + set tsrok [file isdirectory $tsrdir] + } + HD { + set tsrok [file exists "$tsrdir/0.ts"] + } + } + + if {$tsrok} { + set tsrbuf 21474836480 + if {[catch { + lassign [exec du -ks $tsrdir] tsrused + }]} { + set tsrused 0 + set tsrbuf 0 + } else { + set tsrused $($tsrused * 1024) + } + } else { + set tsrbuf 0 + set tsrused 0 + } + + if {!$raw} { + set size [pretty_size $size] + set free [pretty_size $free] + set used [pretty_size $used] + set tsrbuf [pretty_size $tsrbuf] + set tsrused [pretty_size $tsrused] + } + + return [list $size $used $perc $free $fperc $tsrbuf $tsrused] +} + +proc {system diskfree} {} { + lassign [exec /mod/bin/busybox/stat -f -c {%S %f} [system diskpart]] \ + bsize fblocks + return $($bsize * $fblocks) +} + +proc {system busy} {} { + # Is humaxtv doing anything important? + if {[catch {set pid [exec /mod/bin/busybox/pgrep -n humaxtv]}]} { + return 0 + } + set c 0 + foreach line [split [\ + exec /mod/webif/lib/bin/lsof -X -Fn -p $pid] "\n"] { + if {[string match {*Video*.ts} $line]} { incr c } + } + if {$c > 0} { return 1 } + return 0 +} + +proc {system inuse} {file} { + if {![file exists $file]} { return 0 } + set op [exec /mod/webif/lib/bin/lsof -X -Fn $file] + switch -glob -- $op { + "" - + "*status error*" { return 0 } + } + return 1 +} + +proc {system dirinuse} {dir} { + set files {} + foreach line [split [\ + exec /mod/webif/lib/bin/lsof -X -Fn +d $dir] "\n"] { + if {[string index $line 0] ne "n"} continue + lappend files [file tail $line] + } + return $files +} + +proc {system reboot} {{fast 0}} { + if {$fast} { + exec /mod/webif/lib/bin/reboot -f + } else { + exec /mod/webif/lib/bin/reboot + } +} + +proc {system restartpending} {{mode 1}} { + if {$mode} { + close [open /tmp/.restartpending w] + } else { + file delete /tmp/.restartpending + } +} + +proc {system param} {param {type Value} {tbl MENUCONFIG}} { + if {[catch {set db [sqlite3.open /var/lib/humaxtv/setup.db]} msg]} { + return 0 + } + set val 0 + set ret [$db query " + select item$type + from TBL_$tbl + where itemName = '$param' + "] + if {[llength $ret] == 1} { + lassign [lindex $ret 0] x val + } + $db close + return $val +} + +proc {system padding} {} { + return [list \ + [system param START_PADDING_TIME] \ + [system param STOP_PADDING_TIME] \ + ] +} + +proc {system instandby} {} { + return [system param LAST_STANDBY Value USERCONFIG] +} + +proc {system mkdir_p} {dir} { + exec /mod/bin/busybox/mkdir -p $dir +} + +proc {system dirblockers} {dir {permitted {".*"}}} { + set fl {} + foreach e [readdir -nocomplain $dir] { + if {$e in {. ..}} continue + if {[file isdirectory "$dir/$e"]} { + lappend fl $e + continue + } + set flag 1 + foreach p $permitted { + if {[string match -nocase $p $e]} { + set flag 0 + break + } + } + if {$flag} { + lappend fl $e + } + } + return $fl +} + +proc {system rmdir_if_empty} {dir {permitted {".*"}}} { + set fl [system dirblockers $dir $permitted] + + if {![llength $fl]} { + file delete -force $dir + return 1 + } + return 0 +} + +proc {system listening} {{mport 0}} { + set ret {} + foreach line [split [exec /mod/bin/busybox/netstat -lntpw] "\n"] { + # Remove repeated spaces + regsub -all -- {[[:space:]]+} $line " " line +# tcp 0 0 0.0.0.0:9955 0.0.0.0:* LISTEN 169/humaxtv + lassign [split $line " "] x x x port x type app + if {$type ne "LISTEN"} { continue } + lassign [split $port ":"] x port + + if {$mport && $mport != $port} { continue } + lappend ret [list $port [split $app "/"]] + } + return $ret +} + +proc {system is_listening} {mport} { + return [llength [system listening $mport]] +} + +proc {system logtimestamp} {} { + return [clock format [clock seconds] -format "%d/%m/%Y %H:%M:%S"] +} + +proc {system _log} {log msg} { + set logfd [open $log "a+"] + puts $logfd "[system logtimestamp] - $msg" + $logfd close +} + +proc {system log} {log msg} { + system _log "/var/log/$log.log" $msg +} + +proc {system plog} {log msg} { + system _log "/mod/tmp/$log.log" $msg +} + +proc {system notify} {msg} { + system plog notify $msg +} + +proc {system display} {hdr hd} { + if {[system model] eq "HDR"} { + exec /sbin/display $hdr + } else { + exec /sbin/display "\$$hd" + } +} + +proc {system tuners} {} { + if {[system model] eq "HDR"} { + return 2 + } else { + return 1 + } +} + +proc {system uptime} {} { + set fd [open /proc/uptime r] + set uptime [lindex [split [read $fd]] 0] + $fd close + return $uptime +} + +proc {system filename} {str {extra ""}} { + # Humax TV replaces these characters. + # "%*./:<>?\| + set chars "\"%*\./:<>?\\\\|$extra" + return [regsub -all -- "\[$chars]" $str "_"] +} + +proc {system connectivity} {{site "hpkg.tv"} {port 80} {ret "0"}} { + set extra [lassign [exec /mod/bin/tcpping $site $port] result] + if {$ret ne "0"} { upvar $ret err } + switch $result { + OK { return 1 } + default { + set err [list $result $extra] + return 0 + } + } +} + +proc {system nuggeted} {} { + if {![system pkginst nugget]} { return 0 } + if {[system nugget ping] eq "PONG"} { + return 1 + } + return 0 +} + +proc {system nugget} {args} { + if {[catch {set ret [exec /mod/bin/nugget {*}$args]} msg]} { + return "" + } + return $ret +} + +proc {system strip} {str} { + if {[string range $str 1 2] eq "i7"} { + # ISO6937, convert to UTF-8 + return [xconv [string range $str 3 end]] + } + if {[string first "\025" $str] == 0} { + # UTF-8 + return [string range $str 1 end] + } + return [xconv $str] +} + +proc {system usbdisks} {{full 0}} { + set disks [lmap i [glob -nocomplain \ + -directory /sys/bus/usb/drivers/usb-storage *:*] { + file tail $i + }] + if {!$full} { + return [llength $disks] + } + if {![llength $disks]} { return {} } + + # Build list of device to device ID + set map {} + foreach block [glob -nocomplain -directory /sys/block sd?] { + set link [file readlink $block/device] + foreach disk $disks { + if {[string first "/$disk/" $link] >= 0} { + set map([file tail $block]) $disk + } + } + + } + return $map +} + +proc {system usbmounts} {{full 0}} { + if {!$full} { + set num 0 + catch { + set num [ + exec grep -cE {^/dev/sd[a-z][1-9] /media/[^/]+ } \ + /proc/mounts] + } + return $num + } + set ret {} + if {[catch { + set lines [exec grep -E {^/dev/sd[a-z][1-9] /media/[^/]+ } \ + /proc/mounts] + }]} { return $ret } + foreach line [split $lines "\n"] { + lassign $line dev mp + set rec "DEV {$dev} MP {$mp} LABEL {NO NAME} TYPE Unknown" + + set rec(VENDOR) Unknown + set rec(MODEL) Unknown + + if {[regexp {/dev/(sd[a-z])} $dev x rdev]} { + set rec(RDEV) $rdev + if {[file readable [ + set f /sys/block/$rdev/device/vendor]]} { + set rec(VENDOR) [string trim [file read $f]] + } + if {[file readable [ + set f /sys/block/$rdev/device/model]]} { + set rec(MODEL) [string trim [file read $f]] + } + } + + lassign [exec stat -f -c {%S %b %f} $mp] blocks blockc blockf + set rec(SIZE) $($blockc * $blocks) + set rec(USED) $(100 - $blockf * 100 / $blockc) + + # /dev/sda1: LABEL="SAN" UUID="DBC1-1CF8" TYPE="vfat" + catch { + foreach field [ + split [exec /mod/sbin/blkid -c /dev/null $dev] "\n"] { + while {[regexp { *([A-Z]+)="?([^"]+)"?(.*)} \ + $field x k v field]} { + set rec($k) $v + } + } + } + # Test for EXFAT + if {$rec(TYPE) eq "Unknown" && [ + file exists /mod/sbin/exfatlabel]} { + catch { + set label [exec /mod/sbin/exfatlabel $dev 2>/dev/null] + if {$label ne ""} { + set rec(TYPE) "EXFAT" + set rec(LABEL) $label + } + } + } + + lappend ret $rec + } + + return $ret +} + +proc {system has} {comp} { + switch $comp { + wifi_dongle { + if {[catch {exec /mod/bin/iwgetid}]} { return 0 } + return 1 + } + tvdb { + require settings.class + return [[settings] _nval_setting tvdb] + } + } + return 0 +} + +# Note that {system checkop} can be called from processes other than that +# which started the operation. Hence no visilibty into the ::system::ops +# dictionary. +proc {system checkop} {op} { + if {![regexp -nocase -- {^[.a-z0-9]+$} $op]} { return 0 } + if {[catch {set fp [open "/tmp/.bgop.$op" "a"]}]} { + return 0 + } + set ret 1 + if {[$fp lock]} { + $fp unlock + set ret 0 + } + $fp close + return $ret +} + +set ::system::ops {} + +proc {system startop} {args op file} { + if {![regexp -nocase -- {^[a-z0-9]+$} $op]} { return 0 } + if {"-multiple" in $args} { + set i 0 + append op "[pid]." + while {[dict exists $::system::ops "$op$i"]} { incr i } + append op $i + } elseif {[dict exists $::system::ops $op]} { + system endop $op + } + if {[catch { + set fp [open "/tmp/.bgop.$op" "a"] + if {[$fp lock]} { + $fp puts "$file\n[pid]" + $fp flush + set ::system::ops($op) $fp + } else { + $fp close + error "Could not lock op file." + } + }]} { return 0 } + return $op +} + +proc {system endop} {op} { + if {![regexp -nocase -- {^[.a-z0-9]+$} $op]} { return 0 } + if {[dict exists $::system::ops $op]} { + catch { + set fp $::system::ops($op) + $fp unlock + $fp close + } + dict unset ::system::ops $op + } + if {[file exists "/tmp/.bgop.$op"]} { + file delete "/tmp/.bgop.$op" + return 1 + } + return 0 +} + +proc {system specialdir} {dir} { + if {[string match {\[*} [string trimleft [file tail $dir]]]} { + return 1 + } + return 0 +} + diff --git a/webif/lib/ts.class b/webif/lib/ts.class index 05fd9d86..526814a5 100644 --- a/webif/lib/ts.class +++ b/webif/lib/ts.class @@ -339,7 +339,7 @@ ts method setgenre {newgenre} { } ts method dlnaloc {{urlbase ""}} { - return [system dlnaurl $file $urlbase] + return [system dlnaurl [file normalize $file] $urlbase] } ts method cleanbmp {} { diff --git a/webif/lib/ts.class.orig b/webif/lib/ts.class.orig new file mode 100644 index 00000000..05fd9d86 --- /dev/null +++ b/webif/lib/ts.class.orig @@ -0,0 +1,899 @@ + +if {![exists -command class]} { package require oo } +if {![exists -command pack]} { package require pack } +if {![exists -command xconv]} { package require xconv } +if {![exists -command binary]} { package require binary } + +source /mod/webif/lib/setup +require system.class tvdb.class classdump + +set tsgroup {ts nts hmt thm} + +class ts { + file "" + base "" + title "" + synopsis "" + definition "" + channel_num 0 + channel_name "" + start 0 + end 0 + flags "" + error "" + guidance "" + bookmarks 0 + schedstart 0 + scheddur 0 + genre 0 + resume 0 + status "" + series "" + + seriescached 1 + seriesnum 0 + episodenum 0 + episodetot 0 + episodename "" + tvdb_method "" + tvdb_series {} + tvdb_data {} +} + +ts method bfile {} { + return [file tail [file rootname $file]] +} + +ts method dir {} { + return [file dirname $file] +} + +ts method duration {{raw 0}} { + set d [expr $end - $start] + if {!$raw} { set d $($d / 60) } + return $d +} + +ts method size {} { + return [file size $file] +} + +ts method _parse {line} { + set vars [split $line "\t"] + + lassign [split $line "\t"] \ + title synopsis definition channel_num channel_name \ + start end flags_list guidance bookmarks schedstart scheddur \ + genre resume status seriesnum episodenum episodetot + + set synopsis [xconv $synopsis] + + set flags [split [string range $flags_list 0 end-1] ,] +} + +ts method lastmod {} { + return [file mtime "[file rootname $file].hmt"] +} + +ts method inuse {} { + return [system inuse $file] +} + +ts method bookmarks {{aslist 0}} { + set marks [split [string trim [exec /mod/bin/hmt -bookmarks $file]]] + if {$aslist} { return $marks } + return [join $marks " "] +} + +ts method setbookmarks {marks} { + exec /mod/bin/hmt +setbookmarks=[join $marks :] $file +} + +ts method storeepisode {{data {}}} { + if {[llength $data]} { + set d [join $data ","] + } else { + set d "$seriesnum,$episodenum,$episodetot" + } + exec /mod/bin/hmt +setseries=$d $file +} + +ts method clearepdata {} { + set seriesnum 0 + set episodenum 0 + set episodetot 0 +} + +ts method flag {f} { + if {$f in $flags} {return 1} else {return 0} +} + +ts method unflag {f} { + lremove flags $f +} + +ts method unlock {} { + set cmd [list /mod/bin/hmt -lock $file] + exec {*}$cmd + lremove flags "Locked" + return 1 +} + +ts method lock {} { + set cmd [list /mod/bin/hmt +lock $file] + exec {*}$cmd + ladd flags "Locked" + return 1 +} + +set ::ts::flagmap { + detectads Addetection + dedup Deduped + encrypted ODEncrypted + protect Encrypted +} + +ts method setflag {flag {dummy 0}} { + if {!$dummy} { + set cmd [list /mod/bin/hmt +$flag $file] + if {[catch {exec {*}$cmd}]} { + throw 20 "Unknown flag." + } + } + if {[dict exists $::ts::flagmap $flag]} { + ladd flags $::ts::flagmap($flag) + } else { + ladd flags [string totitle $flag] + } + return 1 +} + +ts method unsetflag {flag {dummy 0}} { + if {!$dummy} { + set cmd [list /mod/bin/hmt -$flag $file] + if {[catch {exec {*}$cmd}]} { + throw 20 "Unknown flag." + } + } + if {[dict exists $::ts::flagmap $flag]} { + lremove flags $::ts::flagmap($flag) + } else { + lremove flags [string totitle $flag] + } + return 1 +} + +ts method set_shrunk {} { + set cmd [list /mod/bin/hmt +shrunk $file] + exec {*}$cmd + ladd flags "Shrunk" + return 1 +} + +ts method set_deduped {} { + set cmd [list /mod/bin/hmt +dedup $file] + exec {*}$cmd + ladd flags "Deduped" + return 1 +} + +ts method unset_deduped {} { + set cmd [list /mod/bin/hmt -dedup $file] + exec {*}$cmd + lremove flags "Deduped" + return 1 +} + +ts method unenc {} { + set cmd [list /mod/bin/hmt -protect $file] + exec {*}$cmd + lremove flags "Encrypted" + return 1 +} + +ts method enc {} { + set cmd [list /mod/bin/hmt +protect $file] + exec {*}$cmd + ladd flags "Encrypted" + return 1 +} + +ts method set_new {} { + set cmd [list /mod/bin/hmt +new $file] + exec {*}$cmd + ladd flags "New" + return 1 +} + +ts method set_watched {} { + set cmd [list /mod/bin/hmt -new $file] + exec {*}$cmd + lremove flags "New" + return 1 +} + +ts method setfile {f} { set file $f } + +proc {ts parse} {file line} { + set e [ts new] + $e setfile $file + $e _parse $line + return $e +} + +proc {ts exec} {file} { + set raw 0 + set cmd [list /mod/bin/hmt] + lappend cmd "-p" + lappend cmd $file + + #puts "CMD -$cmd-" + + return [exec {*}$cmd] +} + +ts method fileset {} { + global tsgroup + + set root [file rootname $file] + set fset {} + foreach ext $tsgroup { + if {[file exists "$root.$ext"]} { + lappend fset "$root.$ext" + } + } + return $fset +} + +proc {ts fetch_or_error} {file {checked 0}} { + if {[catch {set ts [ts fetch $file $checked]}] || $ts == 0} { + print "Could not load ts file $file" + return 0 + } + return $ts +} + +proc {ts fetch} {file {checked 0}} { + # Check that this is a .ts file which has at least one sidecar + # file (.hmt) + if {!$checked} { + if {[file extension $file] ne ".ts"} { return 0 } + if {![file exists "[file rootname $file].hmt"]} { return 0 } + } + + if {[file extension $file] ne ".ts"} { + set file "[file rootname $file].ts" + } + + return [ts parse $file [ts exec $file]] +} + +ts method delete {} { + foreach f [$self fileset] { + file tdelete $f + puts "Removed $f
" + } + return 1 +} + +ts method move {dst {touch 0} {force 0}} { + foreach f [$self fileset] { + set nf "$dst/[file tail $f]" + while {[file exists $nf]} { + set nf "$dst/_[file tail $nf]" + } + file rename $f $nf + if {$touch} { + exec /mod/bin/busybox/touch $nf + } + } + return 1 +} + +ts method copy {dst} { + foreach f [$self fileset] { + file copy $f "$dst/[file tail $f]" + } + return 1 +} + +ts method settitle {newtitle} { + if {[string length $newtitle] > 48} { return } + + exec /mod/bin/hmt "+settitle=${newtitle}" $file + set title $newtitle +} + +ts method setsynopsis {newsynopsis} { + if {[string length $newsynopsis] > 252} { return } + + exec /mod/bin/hmt "+setsynopsis=${newsynopsis}" $file + set synopsis $newsynopsis +} + +ts method setguidance {newguidance} { + if {[string length $newguidance] > 74} { return } + + if {$newguidance eq ""} { + exec /mod/bin/hmt "-guidance" $file + } else { + exec /mod/bin/hmt "+setguidance=${newguidance}" $file + } + set guidance $newguidance +} + +proc {ts genre} {genre} { + if {![string is integer $genre] || $genre < 0} { + set genre 0 + } + if {$genre <= 15} { + set genre $($genre << 4) + } + return $genre +} + +ts method setgenre {newgenre} { + set newgenre [ts genre $newgenre] + exec /mod/bin/hmt "+setgenre=-${newgenre}" $file + set genre $newgenre +} + +ts method dlnaloc {{urlbase ""}} { + return [system dlnaurl $file $urlbase] +} + +ts method cleanbmp {} { + set bfile [file rootname $file] + foreach f [glob -nocomplain "${bfile}*.bmp"] { + file delete $f + } +} + +ts method mkbmps {{offset 0}} { + set bfile [file rootname $file] + if {[catch { + exec /mod/bin/ffmpeg -loglevel fatal -ss $offset -i $file \ + -vf fps=fps=2 -frames 5 \ + -pix_fmt argb -vf vflip -s 140x78 "${bfile}%d.bmp" + } msg]} { + puts "ERROR: $msg" + return 0 + } + return 1 +} + +ts method mkbmp {{offset 0} {ext ""}} { + set bfile [file rootname $file] + set bmpfile "$bfile$ext.bmp" + set cmd [list /mod/bin/ffmpeg -loglevel fatal -ss $offset -i $file \ + -frames 1 -pix_fmt argb -vf vflip -s 140x78 -y $bmpfile] + catch { exec {*}$cmd } + if {![catch { file stat $bmpfile stbmp }]} { + if {[dict get $stbmp size] != 0} { return 1 } + } + return 0 +} + +ts method mkthm {{offset 0}} { + if {![$self mkbmp $offset]} { return 0 } + set bfile [file rootname $file] + # Trim the bitmap header from the start of the file + if {[catch { + exec /bin/dd if=$bfile.bmp of=$bfile.thm~ bs=54 skip=1 + } msg]} { + puts "ERROR: $msg" + return 0 + } + exec /bin/echo -n " " >> $bfile.thm~ + file rename -force $bfile.thm~ $bfile.thm + file tdelete $bfile.bmp + $self setflag thumbnail + return 1 +} + +# From MontysEvilTwin +# - https://hummy.tv/forum/threads/7787/page-2#post-106826 +# ffmpeg -i "File 1.ts" -c:a mp3 -b:a 128k "File 1.mp3" +# ffmpeg -i "File 1.ts" -c:a copy "File 1.mp2" +# ffmpeg -i "File 1.ts" -c:a copy "File 1.loas" +ts method mkmp3 {{slow false} {tmp ""} {v 0} {br 128}} { + set rfile [file rootname $file] + + if {$slow} { + set opts [list -c:a mp3 -b:a ${br}k] + set ext mp3 + } else { + set opts [list -c:a copy] + if {$definition eq "HD"} { + set ext loas + } else { + set ext mp2 + } + } + set cmd [list /mod/bin/ffmpeg \ + -y -benchmark -vn -v $v \ + -i $file {*}$opts \ + ] + + if {$tmp eq ""} { + lappend cmd "${rfile}.$ext" + } else { + lappend cmd "$tmp.$ext" + } + + set output [exec {*}$cmd] + + if {$tmp ne ""} { + file rename "$tmp.$ext" "${rfile}.mp3" + } elseif {$ext ne "mp3"} { + file rename "${rfile}.$ext" "${rfile}.mp3" + } + + exec /mod/bin/id3v2 \ + --song $title \ + --comment $synopsis \ + --album $channel_name \ + --year "[clock format $start -format {%Y}]" \ + "${rfile}.mp3" + + return $output +} + +ts method mkmpg {{tmp ""}} { + set rfile [file rootname $file] + + set cmd [list /mod/bin/ffmpeg \ + -y -benchmark -v 0 \ + -i $file \ + -map 0:0 -map 0:1 \ + -vcodec copy -acodec copy] + + if {$tmp eq ""} { + lappend cmd "${rfile}.mpg" + } else { + lappend cmd "$tmp.mpg" + } + + set output [exec {*}$cmd] + + if {$tmp ne ""} { + file rename "$tmp.mpg" "${rfile}.mpg" + } + + return $output +} + +proc {ts renamegroup} {from to} { + global tsgroup + + set dir [file dirname $from] + set root [file rootname $from] + + # Catch from string without a . character in it + if {$root eq $from} { return } + + foreach ext $tsgroup { + set f "$root.$ext" + if {![file exists $f]} continue + file rename $f "${dir}/${to}.${ext}" + } + + exec /mod/bin/hmt "+setfilename=$to" "${dir}/${to}.hmt" + +# set ndir [file normalize $dir] +# +# if {![catch {set db [sqlite3.open $::dmsfile]}]} { +# catch { +# set x [lindex [$db query {select mediaid from tblMedia +# where localUrl = '%s'} [file normalize $from]] 0] +# lassign $x key mediaid +# if {$mediaid ne ""} { +# $db query {update tblMedia set localUrl = '%s' +# where mediaid = %s} "${ndir}/{$to}.ts" $mediaid +# $db query {update tblMedia set title = '%s' +# where mediaid = %s} "{$to}.ts" $mediaid +# } +# } +# $db close +# } +} + +proc {ts touchgroup} {target ref} { + global tsgroup + + set dir [file dirname $target] + set root [file rootname $target] + + # Catch from string without a . character in it + if {$root eq $target} { return } + + foreach ext $tsgroup { + set f "$root.$ext" + if {![file exists $f]} continue + file touch $f $ref + } +} + +proc {ts resetnew} {dir} { + if {![file isdirectory $dir]} return + if {![file exists "$dir/.series"]} { + set fd [open "$dir/.series" "w"] + puts -nonewline $fd [string repeat "\x0" 276] + close $fd + } + + set tot 0 + set watched 0 + foreach file [readdir -nocomplain $dir] { + if {![string match {*.ts} $file]} { continue } + incr tot + if {[set ts [ts fetch "$dir/$file"]] != 0} { + if {![$ts flag "New"]} { incr watched } + } + } + + if {!$tot} { + file delete "$dir/.series" + return + } + + set fd [open "$dir/.series"] + set bytes [read $fd] + close $fd + set recs [unpack $bytes -uintle 0 32] + set played [unpack $bytes -uintle 32 32] + + #puts "Current: $played/$recs" + #hexdump $bytes + #puts "Calculated: $watched/$tot" + + pack bytes $tot -intle 32 0 + pack bytes $watched -intle 32 32 + + #hexdump $bytes + + set fd [open "$dir/.series" "w"] + puts -nonewline $fd $bytes + close $fd +} + +proc {ts iterate} {callback {verbose 0} {dir ""} {nospecial 0}} {{rootdev 0}} { + require system.class + if {$dir eq ""} { + set dir [system mediaroot] + file stat "$dir/" rootstat + set rootdev $rootstat(dev) + } + + if {$verbose} { puts "Scanning directory ($dir)" } + + if {$rootdev != 0} { + file stat "$dir/" st + if {$st(dev) != $rootdev} return + } + + if {$nospecial && [system specialdir $dir]} return + + foreach entry [readdir -nocomplain $dir] { + if {[file isdirectory "$dir/$entry"]} { + ts iterate $callback $verbose "$dir/$entry" $nospecial + continue + } + if {![string match {*.ts} $entry]} continue + if {[catch {set ts [ts fetch "$dir/$entry"]}]} continue + if {$ts == 0} continue + $callback $ts + } +} + +# +# Attempt to extract the series/episode names using a variety of techniques +# + +ts method series_name {} { + + # For recorded series, use the folder name + set dir [file dirname $file] + if {[file exists "$dir/.series"]} { + set s [file tail $dir] + } else { + set s $title + } + + foreach x { + {^new: *} + } { + regsub -nocase -all -- $x $s "" s + } + + return $s +} + +set ::ts::episode_prefixes { + {^new series\.* *} + {^cbeebies\.* *} + {^cbbc\.* *} + {^t4: *} + {^brand new series *[-:]* *} + {^\.+} + { *\(Part [0-9] of [0-9]\) *} + {, Part [0-9]} +} + +ts method _tvdb_resolve {seriesid} { + # See if we can find a TVDB series for this recording. + + set dir [file dirname $file] + + set tvdb_series [set v [tvdb series "" $seriesid]] + if {[$v get seriesid] == 0} { return } + + # Got one. + + # Easiest case - we can explicitly request the episode. + if {$seriesnum && $episodenum} { + if {$seriescached} { + set tvdb_method "cached values" + } else { + set tvdb_method "series and episode number" + } + return [$v episodebynum $seriesnum $episodenum] + } + + # Now try to find the episode using the current episode name + # (using series or episode number if available) + set k [$v episodebyname $episodename $seriesnum $episodenum] + if {[llength $k]} { + set tvdb_method "episode name ($episodename)" + return $k + } + + # More problematic but can at least narrow the list of candidates + # using the episode or series numbers if we have them. + + if {$episodenum} { + set tvdb_method "episode number" + return [$v episodebyepnum $episodenum $synopsis] + } + + if {$seriesnum} { + set tvdb_method "series and synopsis" + return [$v episodebyseries $seriesnum $synopsis] + } + + # Most difficult - try and match based on synopsis alone + set tvdb_method "synopsis text" + return [$v episodebysynopsis $synopsis] +} + +proc {ts serieslist} {dir} { + set idfile "$dir/.tvdbseriesid" + if {![file exists $idfile]} { return {} } + return [lmap i [split [file read $idfile] "\n"] { + string trim $i + }] +} + +ts method extract_numbers {} { + + ###################################################################### + # Check for embedded Series/Episode number. + # Thank you broadcasters for the variation! + # Least trustworthy first. + + # Episode 5 + regexp -nocase -- {Episode (\d+)} $synopsis x episodenum + + # ^23/27. + regexp -nocase -- {^\s*(\d+)/(\d+)} $synopsis x episodenum episodetot + + # (8/8) + regexp -nocase -- {\((\d+)/(\d+)\)} $synopsis x episodenum episodetot + + # (Episode 5/10) + # (Ep5/10) + # (Ep 3 of 3) + # (Ep3) + regexp -nocase -- {Epi?s?o?d?e?\s*(\d+)\s*(of|/)?\s*(\d+)?} $synopsis \ + x episodenum x episodetot + + # (S2 Ep1) + # S.02 Ep.002 + # S01 Ep52 + # (S4 Ep 7) + # (S1, ep 2) + # (S8, Ep2) + # (S4 Ep22/24) + regexp -nocase -- {S\.*(\d+),?\s*Ep\.?\s*(\d+)(/(\d+))?} $synopsis \ + x seriesnum episodenum x episodetot + + # (Part 5/10) + # (Pt. 5/10) + # (Part 5 of 10) + # (Pt. 5 of 10) + # (Pt5) + regexp -nocase -- {P(art|t\.?)\s*(\d+)\s*(of|/)?\s*(\d+)?} $synopsis \ + x x episodenum x episodetot + + foreach v {seriesnum episodenum episodetot} { + if {[set $v] eq ""} { + set $v 0 + } else { + incr $v 0 + } + } + +} + +ts method episode_name {} { + set s $synopsis + + ###################################################################### + # Attempt to determine the episode name from the synopsis + + # Strip common prefixes + foreach prefix $::ts::episode_prefixes { + regsub -nocase -all -- $prefix $s "" s + } + + # Strip anything following a colon. + regsub -all -- { *[:].*$} $s "" s + + # If the resulting string is longer than 40 characters then + # split around . and take the left hand side if appropriate. + if {[string length $s] > 40} { + lassign [split $s "."] v w + set s $v + if {[string length $s] < 6 && [string length $w] < 6} { + append s "_$w" + } + } + + # Shorten if too long. + if {[string length $s] > 40} { set s [string range $s 0 39] } + + set episodename $s + + if {$episodenum == 0} { + $self extract_numbers + set seriescached 0 + } + + # Now see if TVDB has anything to add + + set fbase "[file dirname $file]/.tvdb" + + if {![system has tvdb] || ![file exists "${fbase}seriesid"]} { + return $s + } + + if {!$seriesnum && [file exists "${fbase}series"]} { + set seriesnum [string trim [file read "${fbase}series"]] + } + foreach seriesid [ts serieslist [$self dir]] { + set tvdb_data [$self _tvdb_resolve $seriesid] + if {![dict exists $tvdb_data name]} continue + set flag 0 + if {!$seriesnum} { + set seriesnum $tvdb_data(series) + incr flag + } + if {!$episodenum} { + set episodenum $tvdb_data(episode) + incr flag + } + if {$flag} { $self storeepisode } + + return $tvdb_data(name) + } + + return $s +} + +ts method epstr {{format "s%se%E/%n"}} { + set map {} + if {$seriesnum eq 0} { + set map(%s) "?" + set map(%S) "??" + } else { + set map(%s) $seriesnum + set map(%S) [format "%02d" $seriesnum] + } + if {$episodenum eq 0} { + set map(%e) "?" + set map(%E) "??" + } else { + set map(%e) $episodenum + set map(%E) [format "%02d" $episodenum] + } + if {$episodetot eq 0} { + set map(%n) "?" + set map(%N) "??" + } else { + set map(%n) $episodetot + set map(%N) [format "%02d" $episodetot] + } + return [string map $map $format] +} + +ts method tsr {} { + set fd [open "[file rootname $file].nts"] + set bytes [read $fd 0x20] + close $fd + set tsr [unpack $bytes -uintle $(8 * 0x1f) 8] + return $tsr +} + +ts method genrenib {} { + if {[catch {set v $($genre >> 4)}]} { set v 0 } + return $v +} + +ts method genre_info {} { + set g [$self genrenib] + lassign $::epg::genrelist($g) txt img + if {$img eq "Unclassified"} { + set img "/images/173_3_26_G3_$img.png" + } else { + set img "/images/173_3_00_G3_$img.png" + } + return [list $txt $img] +} + +proc {ts genrelist} {} { + require epg.class + set glist {} + foreach {k v} $::epg::genrelist { + lappend glist $($k << 4) $v + } + return $glist +} + +# return the key that will decrypt the file in the mode, or nothing +ts method getkey {mode} { + # mode: dlna (active key), direct + + set rfile [file rootname $file] + + set keys {} + # the active key + set key [string range [system nugget cryptokey -key] 0 31] + if {$key ne ""} { + lappend keys $key + } + if { $mode ne "dlna" } { + # also try other keys, such as this - same as active? + set key [system customencryptionkey] + if {$key ne ""} { + ladd keys $key + } + + # the native key + if {![catch {set key [system encryptionkey]}]} { + ladd keys $key + } + + # support a file listing other keys in hex, one-per-line + # eg, for recordings imported from a broken box + try { + set fd [open "/mod/etc/keys" r] + foreach key [split [$fd read -nonewline] "\n"] { + ladd keys $key + } + } on error {} { + } finally { + catch {$fd close} + } + } + + foreach key $keys { + if {[catch { + set ret [exec /mod/bin/stripts -q/ $key $rfile] + }]} continue + if {$ret eq "1"} { + return $key + } + } + return +} +