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(" =_('cannot check')?>");
+ });
+}
+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(" =_('checking')?>...");
+ }
+ }
+ });
+ 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||=$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) ) {