From d6fa31c52d9b56552c0c1b1d299c656ff2977b9a Mon Sep 17 00:00:00 2001 From: dcflachs Date: Thu, 4 Aug 2022 21:41:16 -0400 Subject: [PATCH] fix(docker): refresh label icon cache by URL - Purpose: update the Dockerman icon cache patch so label-driven icons refresh when the label URL changes. - Before: the 7.3 Docker tab cache guard could keep serving an existing cached icon even after a third-party container changed its net.unraid.docker.icon label. - Problem: that preserved the fast path but meant the rebased PR no longer fully fixed the original stale icon case. - New behavior: non-Dockerman containers track the last label icon URL, refresh icons when that URL changes, and clear cached label icons when the label is removed. - Implementation: keep Dockerman-managed containers on template-first icon resolution, prefer label URLs only for non-Dockerman containers, share persistent icons by SHA1 URL filename, and purge unused cached icon files. --- .../include/CreateDocker.php | 5 +- .../include/DockerClient.php | 74 +++++++++++++++++-- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php b/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php index cb32fc1923..f8bf67b3ab 100755 --- a/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php +++ b/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php @@ -151,10 +151,7 @@ function dockerUIBlockerScript($enable) { if (is_file($filename)) { $oldXML = simplexml_load_file($filename); if ($oldXML->Icon != $_POST['contIcon']) { - if (!strpos($Repository,":")) $Repository .= ":latest"; - $iconPath = $DockerTemplates->getIcon($Repository,$Name); - @unlink("$docroot/$iconPath"); - @unlink("{$dockerManPaths['images']}/".basename($iconPath)); + $DockerTemplates->purgeUnusedIconFiles($Name); } } file_put_contents($filename, $postXML); diff --git a/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php b/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php index fc51e1ae2a..b4337abaa0 100755 --- a/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php +++ b/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php @@ -337,13 +337,25 @@ public function getAllInfo($reload=false,$com=true,$communityApplications=false) // looking for a match. Bad. $labelIconUrl = $ct['Icon'] ?? null; if (!$communityApplications) { + $isDockerman = ($ct['Manager'] ?? '') === 'dockerman'; + $preferLabelIcon = !empty($labelIconUrl) && !$isDockerman; + $labelIconChanged = $preferLabelIcon + && ($tmp['icon-url'] ?? '') !== $labelIconUrl; + $labelIconRemoved = !$isDockerman && !empty($tmp['icon-url']) && empty($labelIconUrl); $iconExists = !empty($tmp['icon']) && (is_file($tmp['icon']) || is_file($docroot . $tmp['icon'])); - if (!$iconExists || $reload) { - $tmp['icon'] = $this->getIcon($image, $name, $labelIconUrl ?: ($tmp['icon'] ?? '')); + if ($labelIconRemoved) { + $this->purgeUnusedIconFiles($name); + $tmp['icon'] = '/plugins/dynamix.docker.manager/images/question.png'; + unset($tmp['icon-url']); + } elseif (!$iconExists || $reload || $labelIconChanged) { + $tmp['icon'] = $this->getIcon($image, $name, $labelIconUrl ?: ($tmp['icon'] ?? ''), $preferLabelIcon); + if ($preferLabelIcon) $tmp['icon-url'] = $labelIconUrl; // Explicitly return the fallback asset, so that subsequent polls see // the local file instead of rerunning the expensive template scan if (empty($tmp['icon'])) $tmp['icon'] = '/plugins/dynamix.docker.manager/images/question.png'; + } elseif (!$preferLabelIcon) { + unset($tmp['icon-url']); } } if ($ct['Running']) { @@ -430,14 +442,28 @@ public function getAllInfo($reload=false,$com=true,$communityApplications=false) return $info; } - public function getIcon($Repository,$contName,$tmpIconUrl='') { + private function getIconFile($imgUrl) { + return sprintf('%s-%s.png', 'icon', sha1($imgUrl)); + } + + private function getIconRamPath($contName, $imgUrl) { + global $dockerManPaths; + return sprintf('%s/%s-%s', $dockerManPaths['images-ram'], $contName, $this->getIconFile($imgUrl)); + } + + public function getIcon($Repository,$contName,$iconUrl='',$preferIconUrl=false) { global $docroot, $dockerManPaths; - $imgUrl = $this->getTemplateValue($Repository, 'Icon','all',$contName); - if (!$imgUrl) $imgUrl = $tmpIconUrl; + if ($preferIconUrl) { + $imgUrl = $iconUrl ?: $this->getTemplateValue($Repository, 'Icon','all',$contName); + } else { + $imgUrl = $this->getTemplateValue($Repository, 'Icon','all',$contName); + if (!$imgUrl) $imgUrl = $iconUrl; + } if (!$imgUrl || trim($imgUrl) == "/plugins/dynamix.docker.manager/images/question.png") return ''; - $iconRAM = sprintf('%s/%s-%s.png', $dockerManPaths['images-ram'], $contName, 'icon'); - $icon = sprintf('%s/%s-%s.png', $dockerManPaths['images'], $contName, 'icon'); + $iconFile = $this->getIconFile($imgUrl); + $iconRAM = $this->getIconRamPath($contName, $imgUrl); + $icon = sprintf('%s/%s', $dockerManPaths['images'], $iconFile); if (!is_dir(dirname($iconRAM))) mkdir(dirname($iconRAM), 0755, true); if (!is_dir(dirname($icon))) mkdir(dirname($icon), 0755, true); @@ -447,14 +473,46 @@ public function getIcon($Repository,$contName,$tmpIconUrl='') { @copy($icon, $iconRAM); } if (!is_file($icon) && is_file($iconRAM)) { - @copy($iconRAM,$icon); + @copy($iconRAM, $icon); } if (!is_file($iconRAM)) { my_logger("$contName: Could not download icon $imgUrl"); } + else { + $this->purgeUnusedIconFiles($contName, $iconFile); + } return (is_file($iconRAM)) ? str_replace($docroot, '', $iconRAM) : ''; } + + public function purgeUnusedIconFiles($contName, $keepIcon='') { + global $docroot, $dockerManPaths; + + $icon_glob = sprintf('%s/%s-*.png', $dockerManPaths['images-ram'], $contName); + $ramFiles = glob($icon_glob); + foreach ($ramFiles as $filename) { + if ( ($keepIcon === '') || !(strpos($filename, $keepIcon) !== false) ) { + @unlink($filename); + } + } + + $icon_glob = sprintf('%s/%s*.png', $dockerManPaths['images'], $contName); + foreach (glob($icon_glob) as $filename) { + if ( ($keepIcon === '') || !(strpos($filename, $keepIcon) !== false) ) { + @unlink($filename); + } + } + + foreach ($ramFiles as $ramFile) { + if ( strpos($ramFile, '-icon-') !== false ) { + $suffix = substr($ramFile, strrpos($ramFile, '-icon-') + 6); + if ( $suffix && !glob($dockerManPaths['images-ram'].'/*icon-'.$suffix) ) { + $filename = sprintf('%s/icon-%s', $dockerManPaths['images'], $suffix); + @unlink($filename); + } + } + } + } } ####################################