diff --git a/emhttp/plugins/dynamix.plugin.manager/Plugins.page b/emhttp/plugins/dynamix.plugin.manager/Plugins.page index e4a30d1776..d5ade9a962 100755 --- a/emhttp/plugins/dynamix.plugin.manager/Plugins.page +++ b/emhttp/plugins/dynamix.plugin.manager/Plugins.page @@ -108,10 +108,71 @@ function initlist() { $('#plugin_table').tablesorter({sortList:[[4,0],[1,0]],sortAppend:[[1,0]],headers:{0:{sorter:false},5:{sorter:false}},textAttribute:'data'}); $('.desc_readmore').readmore({maxHeight:80,moreLink:"",lessLink:""}); $('div.spinner.fixed').hide('slow'); - loadlist(); + checkPlugins(); } }); } +// Update check, parallel + non-blocking. Instead of one serial sweep that +// blocks on the slowest/unreachable plugin, fire one bounded-concurrency +// request per installed plugin. Each row resolves independently; a slow or +// failed check only affects its own row (never the page), and times out into a +// definite "cannot check" state instead of spinning forever. +var pluginUpdates = 0; +function checkPlugin(plg) { + var name = plg.replace(/\.plg$/,''); + return $.ajax({url:'/plugins/dynamix.plugin.manager/include/ShowPlugins.php',data:{one:name},timeout:20000}) + .done(function(data){ + data = (data||'').split('\0'); + updateInfo(data[0]); + pluginUpdates += (parseInt(data[1],10) || 0); + }) + .fail(function(){ + var id = name.replace(/\./g,'-'); + $('#sid-'+id).attr('data','4').html(" "); + }); +} +function checkPlugins(reset) { + var queue = [], seen = {}; + // class 'remove' is on both the checkbox and the button for each plugin, so + // dedupe by data attribute to avoid checking every plugin twice + $('input.remove').each(function(){ + var d=$(this).attr('data'); + if (d && !seen[d]) { + seen[d]=1; + queue.push(d); + if (reset) { + var id = d.replace(/\.plg$/,'').replace(/\./g,'-'); + $('#sid-'+id).attr('data','0').html(" ..."); + } + } + }); + pluginUpdates = 0; + $('#updateall').hide(); + $('#checkall').find('input').prop('disabled',true); + var total = queue.length, done = 0, idx = 0, active = 0, MAX = 6; + if (!total) { $('#checkall').find('input').prop('disabled',false); return; } + function pump() { + while (active < MAX && idx < total) { + active++; + checkPlugin(queue[idx++]).always(function(){ + active--; done++; + // Refresh the sort cache in place (do NOT 'update', which re-applies + // sortList and re-sorts by Status). Each check changes a row's Status + // rank, so 'update' would make rows jump around as results land. Rows + // stay in their initial order; a later header click still sorts on the + // current data because the cache is kept fresh. + $('#plugin_table').trigger('updateCache'); + if (done == total) { + if (pluginUpdates > 1) $('#updateall').show(); else $('#updateall').hide(); + $('#checkall').find('input').prop('disabled',false); + } else { + pump(); + } + }); + } + } + pump(); +} function loadlist(id,check) { if (id) timers.plugins = setTimeout(function(){$('div.spinner.fixed').show('slow');},500); $.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{audit:id,check:check||},function(data) { @@ -145,7 +206,7 @@ function loadlist(id,check) { } $(function() { initlist(); - $('.tabs-container').append(""); + $('.tabs-container').append(""); $('.tabs-container').append(""); $('.tabs-container').append(""); }); diff --git a/emhttp/plugins/dynamix.plugin.manager/include/ShowPlugins.php b/emhttp/plugins/dynamix.plugin.manager/include/ShowPlugins.php index c0830e5467..1430e124cc 100644 --- a/emhttp/plugins/dynamix.plugin.manager/include/ShowPlugins.php +++ b/emhttp/plugins/dynamix.plugin.manager/include/ShowPlugins.php @@ -25,6 +25,7 @@ $check = unscript(_var($_GET,'check')); $cmd = unscript(_var($_GET,'cmd')); $init = unscript(_var($_GET,'init')); +$one = unscript(_var($_GET,'one')); // single-plugin update check (fired per row, in parallel) $empty = true; $install = false; $updates = 0; @@ -56,6 +57,13 @@ } } +// Restrict to a single installed plugin so its (network) update check can run +// in parallel with the others, instead of the legacy serial sweep that blocks +// the page on the slowest/unreachable plugin. Goes through the normal update +// check path below (no $audit, $check stays falsy) and returns just this +// plugin's vid-/sid- line. +if ($one) $plugins = "/var/log/plugins/".basename($one,'.plg').".plg"; + delete_file($alerts); foreach (glob($plugins,GLOB_NOSORT) as $plugin_link) { //only consider symlinks diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index add045ddde..56d767265a 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -262,7 +262,10 @@ function download($url, $name, &$error, $write=true) { if (($perror = pclose($file)) != 0) { $error = "$plg download failure: ".error_desc($perror); return false; - } elseif (filesize($name) == 0) { + } + clearstatcache(true, $name); + if (filesize($name) == 0) { + $error = "$plg download failure: zero-length file"; if ($write) write("plugin: download failure: zero-length file\r","\n"); return false; } else { @@ -330,7 +333,11 @@ function plugin($method, $plugin_file, &$error) { // validate plugin download without installation if ($method == 'validate') { - $name = '/tmp/validate-plugin.tmp'; + $name = tempnam('/tmp', 'validate-plugin-'); + if ($name === false) { + $error = "unable to create validation temporary file"; + return false; + } foreach ($xml->FILE as $file) if ($file->URL) { if (!$file->SHA256 and !$file->MD5) continue; if ( (download($file->URL, $name, $error, false) === false) && (download(filter_url($file->URL), $name, $error, false) === false) ) {