This commit is contained in:
prpr 2022-03-26 00:25:28 +00:00
commit 6c9a18fa14
21 changed files with 645 additions and 370 deletions

View File

@ -1,7 +1,7 @@
Package: webif
Priority: optional
Section: web
Version: 1.4.9-6
Version: 1.4.9-7
Architecture: mipsel
Maintainer: af123@hpkg.tv
Depends: tcpfix,webif-channelicons(>=1.1.27),lighttpd(>=1.4.39-1),jim(>=0.79),jim-pack(>=0.79),jim-oo(>=0.77),jim-sqlite3(>=0.77),jim-cgi(>=0.7-2),jim-binary(>=0.76),service-control(>=2.3),busybox(>=1.20.2-1),lsof(>=4.87),epg(>=1.2.8),hmt(>=2.0.10),ssmtp,cron-daemon(>=1.18.3-3),at(>=3.1.18),anacron,trm(>=1.1),openssl-command,nicesplice,id3v2,file,rsvsync(>=1.1.13),webif-charts(>=1.2-1),stripts(>=1.4.2),tmenu(>=1.21-2),ffmpeg(>=2.8),id3v2,multienv(>=1.6),tcpping(>=1.1),e2fsprogs,wireless-tools(>=29-1),dbupdate,recmon(>=2.0.7),hwctl,nugget(>=0.98),sqlite3(>=3.15.1),jim-xconv,zip(>=3.0-1),wget

View File

@ -21,7 +21,6 @@ set limit $(1-$start)
puts "
<script type=text/javascript>
var initbookmarks = '[$ts bookmarks]';
var len = [$ts duration 1];
var file = '$erfile';
var dir = '$dir';
@ -29,24 +28,29 @@ var dir = '$dir';
<fieldset class=cleft>
<legend>Manage Bookmarks</legend>
<table class=keyval cellpadding=5>
<table class=keyval>
<tr><th>File:</th><td>$rfile</td></tr>
<tr><th>Length:</th><td>[clock format $len -format %T]</td></tr>
<tr><th>Size:</th><td>[pretty_size [$ts size]] ([$ts get definition])</td></tr>
<tr><th>Bookmarks<br><span class=footnote>(enter in seconds)</span></th><td>
<input id=bookmarks size=80 maxlength=255 init=\"[$ts bookmarks]\"
value=\"\" />
<input id=bookmarks size=80 maxlength=255 value=\"[$ts bookmarks]\" />
<button id=update>Update</button>
<br>
<span id=bookmarkstime></span>
</td></tr><tr><td align=right>
</td></tr><tr><td class=cell-align-right>
<button class=left id=addbmark>Add Bookmark</button>
<button class=left id=delbmark>Remove Bookmark</button>
</td><td><div id=slider></div></td></tr>
<tr><td align=right>
<tr><td class=cell-align-right>
<span class=left>Current: <span id=curbmk></span></span><br><br><br>
"
if {![$ts flag "ODEncrypted"]} {
puts "
<button id=genthumbs title=\"Generate Thumbnails\"
class=left>Generate Thumbnails</button>
"
}
puts "
</td><td>
<div id=thumbs class=hidden>
<table><tr>
@ -55,13 +59,13 @@ set times {}
loop v $start $limit $incr {
lappend times $v
puts "
<th style=\"text-align: center\"><span class=mark>
<th class=mark><span class=mark>
[format "%+d" $v]</span>s</th>
"
}
puts "</tr><tr>"
foreach v $times {
puts "<td><img class=bmp src=/img/generating.png pos=$v></td>"
puts "<td><img class=bmp data-pos=$v alt=\"thumbnail @ $v s\"></td>"
}
puts "
</tr></table>
@ -77,5 +81,8 @@ if {[system pkginst nicesplice]} {
puts "
</div>
<div id=results class=\"hidden blood\"></div>
</fieldset>
"
footer

View File

@ -1,238 +1,225 @@
var curval = 0;
var $slider;
var values;
/* using var len defined in inline script, index.jim */
/* using var file defined in inline script, index.jim */
/* using var dir defined in inline script, index.jim */
function
toTimeStr(tval)
{
return new Date(null, null, null, null, null, tval)
.toTimeString().match(/\d{2}:\d{2}:\d{2}/)[0] + ' ';
.toTimeString().match(/\d{2}:\d{2}:\d{2}/)[0];
}
function
valarray(valstr)
{
return valstr.trim().split(/ +/);
}
function
setvals()
{
var nvalues;
values = valarray($('#bookmarks').val());
if (values.length > 0 && values[0] != '')
{
refreshtimes();
return;
}
nvalues = [];
$.each(values, function(k, v) {
if (v > len)
v = len;
if (v < 0)
v = 0;
nvalues.push(v);
});
values = nvalues;
$('#bookmarks').val(values.join(' '));
values = valarray($('#bookmarks').val());
sortmarks();
refreshtimes();
}
function
draw_slider()
{
if ($slider)
$slider.slider('destroy');
else
$slider = $('#slider');
setvals();
if (!values.length || values[0] == '')
{
$slider = null;
return;
}
$slider.slider({
min: 0,
max: len,
step: 1,
values: values,
start: function(event, ui) {
curval = ui.value;
},
stop: function(event, ui) {
curval = ui.value;
sortmarks();
refreshtimes();
},
slide: function(event, ui) {
var marks = '';
for (var i = 0; i < ui.values.length; ++i)
marks += ui.values[i] + ' ';
$('#bookmarks').val(marks.trim());
setvals();
}
});
refreshtimes();
};
function
regenthumbs(curbmkstr)
{
$('#curbmk').html(curbmkstr);
$('#thumbs').hide();
$('#genthumbs')
.button('enable')
.button('option', 'icon', 'ui-icon-zoomin');
}
function
update_slider()
{
setvals();
/* slider values are strings */
var curvalstr = "" + curval;
if (!values.includes(curvalstr)) {
/* try to map current selected bmk to new bmk */
var ovalues = $slider.slider("option", "values");
var nn = ovalues.indexOf(curvalstr);
if (nn < 0) {
curval = 0;
} else {
if (nn >= values.length)
nn = values.length - 1;
curval = nn >= 0 ? values[nn] : 0;
}
regenthumbs(toTimeStr(curval));
}
$('#slider .ui-slider-handle').each(function(i) {
if (i >= values.length)
$(this).hide();
else
$(this).show();
});
$slider.slider("option", "values", values);
};
function
refreshtimes()
{
var t = '';
if (!values.length || values[0] == '')
{
$.each(values, function(k, v) {
t += toTimeStr(v);
});
$('#slider .ui-slider-handle').each(function(i) {
$(this).attr('title', toTimeStr(values[i]));
});
}
$('#bookmarkstime').text(t);
}
function
sortmarks()
{
values.sort(function(a, b){return a - b});
$('#bookmarks').val(values.join(" "));
return valstr.trim().split(/\s+/);
}
$(function() {
var curval = 0;
var $slider;
var values;
$('#bookmarks').val($('#bookmarks').attr('init'));
draw_slider();
$('#curbmk').html(toTimeStr(curval));
function
sortmarks()
{
values.sort(function(a, b){return a - b});
$('#bookmarks').val(values.join(" "));
}
$('#addbmark').button({icons: {primary: "ui-icon-plus"}, text: false})
.on('click', function() {
$('#bookmarks').val('0 ' + $('#bookmarks').val());
curval = 0;
update_slider();
});
$('#delbmark').button({icons: {primary: "ui-icon-minus"}, text: false})
.on('click', function() {
var cur = $('#bookmarks').val();
cur = cur.replace(
new RegExp('(^| )' + curval + '( |$)', ''), ' ').trim();
$('#bookmarks').val(cur);
update_slider();
});
$('#save').button({icons: {primary: "ui-icon-disk"}})
.on('click', function() {
$.post('save.jim', {
file: file,
bookmarks: $('#bookmarks').val()
}, function(data) {
$('#results').html(data)
.slideDown('slow').delay(5000).slideUp('slow');
});
});
$('#back').button({icons: {primary: "ui-icon-arrowreturnthick-1-w"}})
.on('click', function() {
window.location = '/go/browse?dir=' + encodeURIComponent(dir);
});
$('#crop').button({icons: {primary: "ui-icon-arrowreturnthick-1-e"}})
.on('click', function() {
window.location =
window.location.href.replace('/bookmarks/?','/crop/crop.jim?');
});
$('#update').button()
.on('click', function() {
update_slider();
});
$('#slider')
.on('slidechange', function(evt, ui) {
var tstr = toTimeStr(curval);
if (tstr != $('#curbmk').html())
regenthumbs(tstr);
});
$('#genthumbs').button({icons: {primary: "ui-icon-zoomin"}, disabled: false})
.on('click', function() {
var start;
var incr = -1;
var last;
$(this)
.button('disable')
.button('option', 'icon', 'ui-icon-refresh');
$('img.bmp').each(function(i) {
if (start === undefined) {
start = $(this).attr('pos') | 0;
} else {
last = $(this).attr('pos') | 0;
function
refreshtimes()
{
var t = '';
if (values.length > 0 && values[0] != '')
{
$.each(values, function(k, v) {
if (t != '') t += ', ';
t += toTimeStr(v);
});
$('#slider .ui-slider-handle').each(function(i) {
if (values[i]|0 > 0)
$(this).attr('title', toTimeStr(values[i]))
else $(this).attr('title', '');
});
$('#curbmk').html(toTimeStr(curval));
$('#genthumbs').button('enable');
}
incr++;
});
else
{
$('#genthumbs').button('disable');
$('#curbmk').html('');
}
$('#bookmarkstime').text(t);
}
incr = (last - start) / incr;
$.get('/browse/thumbnail/mkrange.jim', {
'file': file,
's': start+curval,
'e': last+curval,
'i': incr
}, function() {
$('#thumbs').show();
$('img.bmp').each(function(i) {
/* cast to "int" */
var pos = $(this).attr('pos')|0;
$(this).attr('src',
'/browse/thumbnail/fetch.jim?' +
'file=' + encodeURIComponent(file) +
'&pos=' + (curval+pos).toFixed(1));
function
setvals()
{
var nvalues;
values = valarray($('#bookmarks').val());
nvalues = new Set();
$.each(values, function(k, v) {
if (v > len)
v = len;
if (v < 0)
v = 0;
nvalues.add(v);
});
$('#genthumbs').button('option', 'icon', 'ui-icon-zoomin');
values = Array.from(nvalues);
sortmarks();
}
function
regenthumbs(curbmkstr)
{
$('#curbmk').html(curbmkstr);
$('#thumbs').hide();
$('#genthumbs')
.button(curbmkstr ? 'enable': 'disable')
.button('option', 'icon', 'ui-icon-zoomin');
}
function
draw_slider()
{
if ($slider)
$slider.slider('destroy');
else
$slider = $('#slider');
setvals();
$slider.slider({
min: 0,
max: len,
step: 1,
disabled: values.length <= 0 || values[0] == '',
values: values,
start: function(event, ui) {
curval = ui.value;
},
stop: function(event, ui) {
curval = ui.value;
sortmarks();
refreshtimes();
},
slide: function(event, ui) {
var marks = '';
for (var i = 0; i < ui.values.length; ++i)
marks += ui.values[i] + ' ';
$('#bookmarks').val(marks.trim());
setvals();
}
});
refreshtimes();
/* slider values are strings */
var curvalstr = "" + curval;
if (!values.includes(curvalstr)) {
/* try to map current selected bmk to new bmk */
var ovalues = $slider.slider("option", "values");
var nn = ovalues.indexOf(curvalstr);
if (nn < 0) {
curval = 0;
regenthumbs('');
} else {
if (nn >= values.length)
nn = values.length - 1;
if (nn >= 0) {
regenthumbs(toTimeStr(curval = values[nn]));
} else {
curval = 0;
regenthumbs('');
}
}
}
};
$('#genthumbs').button({icons: {primary: "ui-icon-zoomin"}, disabled: true})
.on('click', function() {
var start;
var incr = -1;
var last;
$(this)
.button('disable')
.button('option', 'icon', 'ui-icon-refresh');
$('img.bmp').each(function(i) {
if (start === undefined) {
start = $(this).attr('data-pos') | 0;
} else {
last = $(this).attr('data-pos') | 0;
}
incr++;
$(this).attr('src', '/img/generating.png');
});
$('#thumbs').show();
incr = (last - start) / incr;
$.get('/browse/thumbnail/mkrange.jim', {
'file': file,
's': start+curval,
'e': last+curval,
'i': incr
}, function() {
$('img.bmp').each(function(i) {
/* cast to "int" */
var pos = $(this).attr('data-pos')|0;
$(this).attr('src',
'/browse/thumbnail/fetch.jim?' +
'file=' + encodeURIComponent(file) +
'&pos=' + (curval+pos).toFixed(1));
});
$('#genthumbs').button('option', 'icon', 'ui-icon-zoomin');
});
});
});
draw_slider();
});
$('#addbmark').button({icons: {primary: "ui-icon-plus"}, text: false})
.on('click', function() {
$('#bookmarks').val('0 ' + $('#bookmarks').val());
curval = 0;
draw_slider();
});
$('#delbmark').button({icons: {primary: "ui-icon-minus"}, text: false})
.on('click', function() {
var cur = $('#bookmarks').val();
cur = cur.replace(
new RegExp('(^| )' + curval + '( |$)', ''), ' ').trim();
$('#bookmarks').val(cur);
draw_slider();
});
$('#save').button({icons: {primary: "ui-icon-disk"}})
.on('click', function() {
$.post('save.jim', {
file: file,
bookmarks: $('#bookmarks').val()
}, function(data) {
$('#results').html(data)
.slideDown('slow').delay(5000).slideUp('slow');
});
});
$('#back').button({icons: {primary: "ui-icon-arrowreturnthick-1-w"}})
.on('click', function() {
window.location = '/go/browse?dir=' + encodeURIComponent(dir);
});
$('#crop').button({icons: {primary: "ui-icon-arrowreturnthick-1-e"}})
.on('click', function() {
window.location =
window.location.href.replace('/bookmarks/?','/crop/crop.jim?');
});
$('#update').button()
.on('click', draw_slider);
$('#slider')
.on('slidechange', function(evt, ui) {
var tstr = toTimeStr(curval);
if (tstr != $('#curbmk').html())
regenthumbs(tstr);
});
});

View File

@ -14,3 +14,20 @@
margin-top: 5px;
}
/* <table class=keyval ... cellpadding=5> */
.keyval th, .keyval td
{
padding: 5px;
}
/* <td align=right> */
.cell-align-right
{
text-align: right;
}
/* <th style="text-align: center"> */
th.mark
{
text-align: center;
}

View File

@ -70,10 +70,6 @@ set newname "$shname-[clock seconds]"
puts "Renaming file group to $newname"
puts "<span class=hidden id=fileparams file=\"$dir/$newname.ts\"></span>"
ts renamegroup "$dir/$shname.ts" $newname
exec /mod/bin/hmt "+setfilename=$newname" "$dir/$newname.hmt"
# New nicesplice shrinks whilst cropping.
# No longer required - nicesplice now sets this flag.
#exec /mod/bin/hmt "+shrunk" "$dir/$newname.hmt"
set croptime [expr [expr [clock milliseconds] - $cropstart] / 1000.0]
puts "Time taken: $croptime"

View File

@ -10,7 +10,7 @@ set urlbase [cgi_get base ""]
# Default to just downloading the raw file.
set url $file
set mime "video/ts"
set mime "video/mp2t"
if {[string match {*.ts} $file]} {
if {![catch {set ts [ts fetch $file]}]} {

View File

@ -13,10 +13,14 @@ if {$file == 0} exit
set sz [pretty_size [file size $file]]
set flags {}
set url ""
# assumption: the type is only ts if fetch has already been checked
if {$type eq "ts"} {
require epg.class ts.class
set ts [ts fetch $file]
set ts [ts fetch $file 1]
# Causes other series information to be automatically populated
set epname [$ts episode_name]
@ -199,7 +203,7 @@ eval_plugins browsetsfile
puts "<tr>
<th>Flags</th>
<td>[$ts get flags]</td>
<td>[set flags [$ts get flags]]</td>
</tr>
"
@ -215,9 +219,6 @@ if {[$ts get bookmarks]} {
"
}
puts "
</table>
"
puts "<div class=hidden id=file>$file</div>"
puts {
<script type=text/javascript>
@ -256,12 +257,12 @@ $('img.rollimg').hover(
</script>
}
exit
}
# Otherwise, for a general file.
puts "
if {$type ne "ts"} {
puts "
<table class=keyval>
<tr>
<th>File</th>
@ -269,20 +270,33 @@ puts "
</tr><tr>
<th>Size</th>
<td>$sz</td>
</tr><tr>
</tr>"
}
set hasffmpeg 0
if {$type ne "ts" || ("ODEncrypted" ni $flags && $url eq "") } {
puts "<tr>
<th>Info</th>
<td class=pre id=ffmpeg>
<img src=/img/spin.gif><i>Loading...</i>
</td>
</tr>
</table>
"
set url "/browse/ffmpeg.jim?file=[cgi_quote_url $file]"
puts { <script type="text/javascript"> }
puts "var url = \"$url\";"
puts {
$('#ffmpeg').load(url);
</script>
</tr>"
set hasffmpeg 1
} elseif {$type eq "ts" && $url ne ""} {
puts [format {
<script type="text/javascript">
$('#playDL').attr('href','%s').enable();
</script> } $url]
}
puts "
</table>
"
if {$hasffmpeg} {
set url "/browse/ffmpeg.jim?file=[cgi_quote_url $file]"
puts [format {
<script type="text/javascript">
var url = "%s";
$('#ffmpeg').load(url, function() { $('#play, #playDL').enable(); });
</script> } $url]
}

View File

@ -21,7 +21,7 @@ header
set nicesplice [system pkginst nicesplice]
set ignore {.nts .thm .hmi}
set include {.ts .avi .mpg .mpeg .wmv .mkv .mp3 .mp4 .mov .hmt .m4v .m4a}
set include {.ts .avi .mpg .mpeg .wmv .mkv .mp3 .mp4 .mov .hmt .m4v .m4a .webm}
if {![dict exists $env SCRIPT_NAME]} {
set env(SCRIPT_NAME) ""
@ -139,6 +139,14 @@ proc entry {file} {{i 0}} {
set img Video_Failed
}
set omenu opt
if {[$ts get definition] eq ""} {
set type gen
set ts 0
set img Video_Other
set omenu oopt
} else {
set omenu opt
}
if {[file exists "${base}.thm"]} { set thmok 1 }
} elseif {$ext eq ".hmt"} {
if {[file exists "${base}.ts"]} { return }

62
webif/html/browse/play.jim Executable file
View File

@ -0,0 +1,62 @@
#!/mod/bin/jimsh
package require cgi
source /mod/webif/lib/setup
require system.class
require ts.class
set file [cgi_get file]
set urlbase [cgi_get base ""]
set duration [cgi_get duration 1]
set fmts [split [cgi_get fmts ""] ","]
set vc [cgi_get vc ""]
# Default to just downloading the raw file.
set url $file
# Prefer to use DLNA server ... (necessary if encrypted)
set dlna [system dlnaurl [file normalize $url] $urlbase]
if {[llength $dlna]} {
set url [lindex $dlna 0]
} elseif {[regexp {^(https?://(.+:.*@)?[[:alnum:].]+(:[[:digit:]]+)?)/} $urlbase x y]} {
set url "$y$url"
} else {
set url "http://[system ip]$url"
}
if {[file extension $file] in {.ts .TS}} {
if {![catch {set ts [ts fetch $file]}] && $ts != 0} {
set duration [$ts duration 1]
}
}
set file [file tail $file]
set playlist [file tempfile "[env "TMPDIR" [env "TMP" "/tmp"]]/playXXXXXX"]
set pl ""
try {
set pl [open $playlist w]
$pl puts "#EXTM3U"
$pl puts "#EXTINF:$duration,$file"
$pl puts "#PLAYLIST:$file"
$pl puts $url
} finally {
if {$pl ne ""} {
$pl close
}
}
httpheader "application/x-mpegurl" 0 [list \
"Content-Disposition" "attachment; filename=\"[file rootname $file].m3u\"" \
"Content-Length" "[file size $playlist]" \
]
set pl ""
try {
set pl [open $playlist r]
$pl copyto stdout
} finally {
if {$pl ne ""} {
$pl close
}
}
catch {file delete $playlist}

View File

@ -10,7 +10,7 @@ var plugins = {
dmenu_prepare: {}
};
// pattern matches directory path prefix
// pattern matches directory path prefix and suffix
var pathre = /.*\/|\.[^.]*$/g;
// IDs of size, img elements for folders use RFC4648 s5 encoding of name
@ -787,12 +787,14 @@ $('img.doopt').contextMenu(
// Disable items which are not yet implemented.
$('#optmenu').disableContextMenuItems('#title');
var $buttons = {
"Close" : function() {$(this).dialog('close');}
};
var $buttonsp = $.extend(
{"Play" : function() { doplay(); }},
$buttons);
var $buttons = [
{ id: 'close',
text: 'Close',
click: function() {$(this).dialog('close');}},
{ id: 'play',
text: 'Play',
click: function() { doplay(this); }}
];
// Create reusable dialogue.
var $dialog = $('#dialogue').dialog({
@ -806,16 +808,56 @@ var $dialog = $('#dialogue').dialog({
'<img src="/img/spin.gif">Retrieving data...'); }
});
function doplay()
/* insert button-like Download link before Play */
$('#play').before(function(i){
var dl = document.createElement('a');
dl.setAttribute('class', this.className);
dl.id = this.id + 'DL';
dl.innerHTML = 'Download';
return dl;
});
function doplay(it)
{
var file = $dialog.attr('file');
var type = $dialog.attr('type');
disableall();
var duration = 0;
var fmts = "";
var vc = ""
var ff = $('#ffmpeg')[0];
if (ff) {
/* extract duration, container and video codec from ffmpeg output */
ff = ff.innerHTML;
var match = /Duration:\s+([0-9.:]+),/.exec(ff);
if (match && match[1])
duration = (new Date('1970-01-01T' + match[1] + 'Z')).getTime()/1000;
match = /Input #0,\s+([-A-Za-z0-9_,]+),\s/.exec(ff);
if (match && match[1]) fmts = match[1];
match = /Stream #.+\sVideo:\s+([-A-Za-z0-9_]+)\s/.exec(ff);
if (match && match[1]) vc = match[1];
}
window.location = '/play/play.jim?' +
'dir=' + encodeURIComponent(dir) +
'&file=' + encodeURIComponent(file);
fmts = /mp4|webm/.exec(fmts);
if (fmts && fmts[0])
vc = /h264|av1|vp9/.exec(vc);
else
vc = null;
if (vc && vc[0]) {
/* base on page address to handle client on external network, etc */
var hh = new URL(file, window.location.href);
window.open(hh.href, 'WebIf_Player');
} else {
window.location = '/browse/play.jim?' +
'dir=' + encodeURIComponent(dir) +
'&base=' + encodeURI(window.location.hostname) +
'&duration=' + duration +
'&file=' + encodeURIComponent(file);
}
$(it).dialog('close');
}
// Bind dialogue open to filenames.
@ -834,11 +876,24 @@ $('a.bf').click(function(e) {
$dialog.attr('file', file);
$dialog.attr('type', type);
if (type == 'ts' &&
(opt.attr('odencd') == 0 || opt.attr('dlna') == 1))
$dialog.dialog("option", "buttons", $buttonsp);
else
$dialog.dialog("option", "buttons", $buttons);
$('#playDL').attr('download', file.replace(/.*\//, ''));
if (type == 'ts') {
if (opt.attr('odencd') != 0) {
/* encrypted: link to be enabled once populated */
$('#playDL').disable();
/* ... but if no DLNA never Play */
if (opt.attr('dlna') != 1) $('#play').disable();
} else {
/* link unencrypted file directly */
$('#playDL').attr('href', file);
}
} else {
/* generic: enable Play once media file is parsed */
$('#play').disable();
$('#playDL').attr('href', file);
}
$dialog.dialog('open');
});
@ -991,7 +1046,7 @@ $('#join').button({icons: {primary: "ui-icon-video"}})
.click(function() {
var files = new Array();
var els = $('input.fsts:checked + a').each(function() {
files.push($(this).attr('file'));
files.push(encodeURIComponent($(this).attr('file')));
});
//console.log("%o", files);
window.location.href = '/browse/join/join.jim?files=' +

View File

@ -35,3 +35,13 @@ input.uint8_t
width: 6ch;
}
/* a link that looks like a button */
button + a.ui-button
{
margin-right: 0.4em;
}
.ui-button:link
{
background-color: rgb(254, 206, 47) !important;
}

View File

@ -362,7 +362,7 @@ $('button.manual_rsv').button({icons:{primary:"ui-icon-clock"}})
$('#mrform input.time').timepicker({
showDuration: true,
timeFormat: 'g:ia',
timeFormat: 'H:i',
step:5
});

View File

@ -13,16 +13,30 @@ if {[cgi_get act] eq "xtelnet"} {
if {[cgi_get act] eq "cryptokey"} {
set val [cgi_get cryptokey ""]
if {[string length "$val"] == 0} {
set val [system encryptionkey]
puts "Using native encryption key.<br>"
set nkey [system encryptionkey]
if {$val eq "" || [string equal -nocase $val $nkey]} {
if {[system customencryptionkey] ne ""} {
system customencryptionkey $nkey
system nugget cryptokey -init
puts "Using native encryption key."
} else {
puts "Native key unchanged."
}
set cryptokey ""
} elseif {[string length $val] != 32} {
puts "Encryption key is too short."
exit
puts "Encryption key must be 32 hexadecimal digits."
} else {
if {[string equal -nocase $val [system customencryptionkey]]} {
puts "Custom key unchanged."
} elseif {[set customkey [system customencryptionkey $val]] ne ""} {
set cryptokey $customkey
system nugget cryptokey -init
puts "Installed new encryption key."
} else {
puts "Failed to install encryption key $val"
}
file write "/mod/boot/cryptokey" [binary format H* $val]
system nugget cryptokey -init
puts "Installed new encryption key."
}
puts "<br>"
exit
}
@ -33,15 +47,7 @@ set logsize [$settings logsize]
set logkeep [$settings logkeep]
set logage [$settings logage]
set cryptokey [system encryptionkey]
if {![catch {set ck_fd [open "/mod/boot/cryptokey"]}]} {
set ck_bytes [$ck_fd read 16]
$ck_fd close
binary scan $ck_bytes H* ck_key
if {[string length $ck_key] == 32} {
set cryptokey $ck_key
}
}
set cryptokey [system customencryptionkey]
handle_int_update pkgdev $pkgdev "Development Package Display"
handle_int_update rtschedule $rtschedule "Real-time scheduling"

View File

@ -60,7 +60,7 @@ puts "
<table>
<tr><th class=key>
Native encryption key
</th><td>[system encryptionkey]</td></tr>
</th><td><span id=nativekey>[system encryptionkey]</span></td></tr>
<tr>
<form class=auto id=cryptokey method=post action=$env(SCRIPT_NAME)>
<input type=hidden name=act value=cryptokey>
@ -71,7 +71,26 @@ puts "
<small><input value=set type=submit></small>
<div id=cryptokey_output></div>
</td>
</form>
</form>"
# Script that clears the custom key input field when the native key
# is restored by entering its full value (as shown in #nativekey).
# We rely on magical knowledge that the form holding this <input> is
# submitted with an Ajax call, and so we hook all successful Ajax calls,
# but filter by the text returned (init.hook) to avoid unnecessary updates.
puts {
<script>
$(document).ajaxSuccess(
function(ev, jqXHR) {
var ip;
if ((jqXHR.responseText.indexOf("ative ") >= 0) &&
($("#nativekey").html() == (ip = $("input[name=cryptokey]")).prop("value"))) {
ip.prop("value", "");
}
}
);
</script>
}
puts "
</tr><tr>
<td></td>
<td class=blood>

View File

@ -2,77 +2,119 @@
source /mod/webif/lib/setup
require system.class
if {[system model] eq "HDR"} {
require settings.class
set smartattrs {realloc pending offline spinretry}
set smartattribs(SMART_status) "Unknown"
foreach sa $smartattrs {
set smartattribs(SMART_$sa) 0
set smartattribs(SMART_ack_$sa) 0
}
foreach line [[settings] smartdata] {
lassign $line x name x n x t
if {$name eq "SMART_status"} {
set smartattribs($name) $t
} else {
set smartattribs($name) $n
proc {system disksmart} {} {
set smartmsg ""
set smartattrs {realloc pending offline spinretry}
set smartattribs(SMART_status) "Unknown"
foreach sa $smartattrs {
set smartattribs(SMART_$sa) 0
set smartattribs(SMART_ack_$sa) 0
}
}
# (SMART_ack_status 0 SMART_ack_pending 0 SMART_status PASSED SMART_pending 7 SMART_ack_realloc 0 SMART_ack_offline 0 SMART_realloc 0 SMART_offline 7)
set smartmsg ""
if {$smartattribs(SMART_status) ne "PASSED"} {
append smartmsg \
"Disk overall health assessment is: $smartattribs(SMART_status)\n"
}
foreach sa $smartattrs {
if {$smartattribs(SMART_$sa) != $smartattribs(SMART_ack_$sa)} {
append smartmsg \
"Disk $sa sector count is: $smartattribs(SMART_$sa)"
if {$smartattribs(SMART_ack_$sa) > 0} {
append smartmsg " (was $smartattribs(SMART_ack_$sa))"
foreach line [[settings] smartdata] {
lassign $line x name x n x t
if {$name eq "SMART_status"} {
set smartattribs($name) $t
} else {
set smartattribs($name) $n
}
append smartmsg "\n"
}
# (SMART_ack_status 0 SMART_ack_pending 0 SMART_status PASSED SMART_pending 7 SMART_ack_realloc 0 SMART_ack_offline 0 SMART_realloc 0 SMART_offline 7)
if {$smartattribs(SMART_status) ne "PASSED"} {
append smartmsg \
"Disk overall health assessment is: $smartattribs(SMART_status)\n"
}
foreach sa $smartattrs {
if {$smartattribs(SMART_$sa) != $smartattribs(SMART_ack_$sa)} {
append smartmsg \
"Disk $sa sector count is: $smartattribs(SMART_$sa)"
if {$smartattribs(SMART_ack_$sa) > 0} {
append smartmsg " (was $smartattribs(SMART_ack_$sa))"
}
append smartmsg "\n"
}
}
return $smartmsg
}
if {$smartmsg ne ""} {
proc {system diskro} {} {
if {[system model] eq "HDR"} {
set dev {/mnt/hd[1-3]}
} else {
set dev [system diskpart]
}
# Python re: no POSIX character classes
# Jim TCL regex: no \x character classes
# Why not have both ...
set s {[[:blank:]]}
set a {[[:alnum:]]}
set nc {[^,]}
set roparts [lsearch -all -inline -regexp \
[split [file read /proc/mounts] "\n\r"] \
"${s}${dev}${s}+${a}+${s}*(${s}|${nc}+,)ro(,|$|${s})"]
return [join [lmap line $roparts \
{format "Filesystem %s on %s is read-only" [lindex $line 1] [lindex $line 0]}] "\n"]
}
set smartmsg [system disksmart]
set romsg [system diskro]
if {$smartmsg ne "" || $romsg ne ""} {
if {![dict exists $env SCRIPT_NAME]} { set env(SCRIPT_NAME) "" }
puts "
<div id=smartwarning class=warningbox><center>
puts {
<div id=smartwarning class=warningbox style="width: 80%; left: 1%"><center>
!! WARNING !!
<br><br>
There are potential hardware problems with the internal hard disk on
this device.
<br><br>
<br><br>}
set problems " "
if {$smartmsg ne ""} {
append problems "potential hardware"
}
if {$romsg ne ""} {
if {[string index $problems end] ne " "} {
append problems " and/or "
}
append problems "filesystem"
}
if {[string index $problems end] ne " "} {
append problems " "
}
puts [format "
There are %sproblems with the hard disk on
this system.
<br><br>" $problems]
if {$smartmsg ne ""} {
puts "
[string map {"\n" "<br>"} $smartmsg]
"
<br>
"
}
if {$romsg ne ""} {
puts "
[string map {"\n" "<br>"} $romsg]
<br><br>
"
}
if {$env(SCRIPT_NAME) ne "/diag/disk.jim"} {
puts "
<br>
<a href=/diag/disk.jim>Go to disk diagnostics</a>
"
Go to <a href=/diag/disk.jim>Disk Diagnostics</a>
"
} else {
puts "
<br>
Don't panic; for help, visit
<a target=_blank
href=http://wiki.hummy.tv/wiki/Disk_Problem>
wiki.hummy.tv
</a>
"
<br>
Don't panic; for help, visit
<a target=_blank href=\"http://wiki.hummy.tv/wiki/Disk_Problem\">wiki.hummy.tv</a>
"
}
puts "
</center></div>
"
}
"
}

View File

@ -20,7 +20,10 @@ puts "<br>Loader Version: [system loaderver]"
puts "<br>System ID: [system systemid]"
puts "<br>Serial Number: [system serialno]"
if {$mws::pagetag eq "Diagnostics"} {
puts "<br>Encryption Key: [system encryptionkey]"
puts "<br>Native Encryption Key: [system encryptionkey]"
if {[set customkey [system customencryptionkey]] ne ""} {
puts "<br>Custom Encryption Key: $customkey"
}
}
puts "<br>Last Boot Reason: [system lastbootreason]"

View File

@ -5,7 +5,7 @@ if {[file exists /mod/tmp/notify.log]} {
source /mod/webif/lib/setup
puts {
<div id=sysnotify class=warningbox style="width: 90%"><center>
<div id=sysnotify class=warningbox style="width: 90%;left: 1%"><center>
!! WARNING !!
<br><br>
You have pending system notifications:

View File

@ -146,7 +146,7 @@ proc {queue dbqueryl} {query_list {txn_mode ""}} {
proc {queue startup} {{days 7}} {
if {$days == 0} { set days 7 }
return [queue dbqueryl { { {
return [queue dbqueryl [list { {
update queue
set status = 'INTERRUPTED',
log = 'Job will be retried automatically.',
@ -163,12 +163,12 @@ proc {queue startup} {{days 7}} {
update queue
set status = 'PENDING'
where status = 'DEFER'
} } { {
} } [list {
delete from queue
where status in ('COMPLETE', 'FAILED')
and submitted < %s
} [expr [clock seconds] - 86400 * $days]
} } ]
] ] ]
}
proc {queue fetch} {file action} {

View File

@ -33,16 +33,24 @@ if {![exists -proc require]} {
exit
}
proc httpheader {{type "text/html"} {cache 0} {extra ""}} {{done 0}} {
proc httpheader {{type "text/html"} {cache 0} {extra {}}} {{done 0}} {
if {$done} return
if {!$cache} {
puts -nonewline "Content-Type: $type; charset=\"UTF-8\"; no-cache\r\n"
puts -nonewline "Expires: -1\r\n"
puts -nonewline "Connection: close\r\n"
puts -nonewline "Pragma: no-cache\r\n"
puts -nonewline "Cache-Control: no-cache\r\n"
set hdr [dict create \
"Content-Type" "$type; charset=\"UTF-8\"; no-cache" \
"Expires" "-1" \
"Connection" "close" \
"Pragma" "no-cache" \
"Cache-Control" "no-cache"]
} else {
puts -nonewline "Content-Type: $type; charset=\"UTF-8\"\r\n"
set hdr [dict create "Content-Type" "$type; charset=\"UTF-8\""]
}
if {![catch {dict size $extra}]} {
set hdr [dict merge $hdr $extra]
set extra ""
}
dict for {k v} $hdr {
puts -nonewline "$k: $v\r\n"
}
if {$extra ne ""} { puts -nonewline "$extra" }
puts -nonewline "\r\n"

View File

@ -144,6 +144,13 @@ proc {system serialno} {} {{serial ""}} {
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 }
@ -153,8 +160,46 @@ proc {system encryptionkey} {} {{key ""}} {
$fd seek 0xcb800
append bytes [$fd read 10]
$fd close
binary scan $bytes H* key
return $key
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 ""}} {
@ -270,7 +315,7 @@ proc {system dlnadb} {} {
}
proc {system _dlnaurl} {file urlbase} {
set mime "video/ts"
set mime "video/mp2t"
if {[catch {set db [sqlite3.open [system dlnadb]]}]} {
return {}
}
@ -279,7 +324,10 @@ proc {system _dlnaurl} {file urlbase} {
from tblresource join tblmedia using (mediaid)
where localurl = '%s'} $file]
if {[llength $muri]} {
lassign [lindex $muri 0] x mime x xuri
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 {
@ -298,7 +346,7 @@ proc {system _dlnaurl} {file urlbase} {
return [list $url $mime]
}
proc {system dlnaurl} {file {urlbase ""}} {
proc {system dlnaurl} {file {urlbase "127.0.0.1"}} {
if {$urlbase eq ""} { set urlbase [system ip] }
set retries 5
set ret {}

View File

@ -863,16 +863,9 @@ ts method getkey {mode} {
}
if { $mode ne "dlna" } {
# also try other keys, such as this - same as active?
try {
set fd [open "/mod/boot/cryptokey"]
set bytes [$fd read 16]
binary scan $bytes H* key
if {[string length $key] == 32} {
ladd keys $key
}
} on error {} {
} finally {
catch {$fd close}
set key [system customencryptionkey]
if {$key ne ""} {
ladd keys $key
}
# the native key