Skip to content

Conversation

@dcflachs
Copy link
Contributor

@dcflachs dcflachs commented Aug 5, 2022

In 6.10.1+ the dockerman icon caching mechanism does not work correctly for icons specified by the net.unraid.docker.icon container label on containers without a dockerman template. For containers created with this label outside of dockerman the caching mechanism initially downloads and displays the correct icon. If the icon url is changed however the new icon is never downloaded because the old icon remains cached and there is no mechanism in place for invalidating the icon in the absence of a dockerman template. To fix this i propose the following changes to dockerman.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of container icon files to prevent accumulation of outdated or unused icons, ensuring only the current icon is retained for each container.
  • Refactor

    • Streamlined internal logic for managing and cleaning up container icon files, resulting in more efficient icon file management.

global $docroot, $dockerManPaths;
$imgUrl = $this->getTemplateValue($Repository, 'Icon','all',$contName);
if (!$imgUrl) $imgUrl = $tmpIconUrl;
$imgUrl = $iconUrl ?: $this->getTemplateValue($Repository, 'Icon','all',$contName);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer iconUrl specified by container label if it exists.

$iconRAM = sprintf('%s/%s-%s.png', $dockerManPaths['images-ram'], $contName, 'icon');
$icon = sprintf('%s/%s-%s.png', $dockerManPaths['images'], $contName, 'icon');
$imgUrlHash = sha1($imgUrl);
$iconFile = sprintf('%s-%s.png', 'icon', $imgUrlHash);
Copy link
Contributor Author

@dcflachs dcflachs Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of storing the icons by container name only, this change uses a sha1 hash of the imgUrl. This way a change to the icon url will automatically invalidate the cached icon.

$iconFile = sprintf('%s-%s.png', 'icon', $imgUrlHash);

$iconRAM = sprintf('%s/%s-%s', $dockerManPaths['images-ram'], $contName, $iconFile);
$icon = sprintf('%s/%s', $dockerManPaths['images'], $iconFile);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In images-ram the icon file name contains both the container name and the urlHash so that it is unique for each container.
In images the icons are store by urlHash alone. This has the benefit of storing only a single copy of identical icons.

return (is_file($iconRAM)) ? str_replace($docroot, '', $iconRAM) : '';
}

public function purgeUnusedIconFiles($contName, $keepIcon='') {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new function better handles the more complex removal process for this caching mechanism.

if ( ($keepIcon === '') || !(strpos($filename, $keepIcon) !== false) ) {
@unlink($filename);
}
}
Copy link
Contributor Author

@dcflachs dcflachs Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first block removes all icons associated with contName in images-ram except the one specified by keepIcon.

@unlink($filename);
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next block removes all icons stored in the old style from the images directory. Ideally this would only need to be run once when the caching mechanism was first upgraded to purge all old icons. Unfortunately i could not think of a good way to make that happen so i settled for always running this check.

@unlink($filename);
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The final block removes any of the shared icons in images that share the same hash as icons removed in the first block that are not referenced by another icon in images-ram.

$imgUrl = $iconUrl ?: $this->getTemplateValue($Repository, 'Icon','all',$contName);
if (!$imgUrl || trim($imgUrl) == "/plugins/dynamix.docker.manager/images/question.png") return '';

$imageName = $contName ?: $name;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this line specifically because i could not figure out what it was doing. So far as i can tell $name is never set anywhere.

$iconPath = $DockerTemplates->getIcon($Repository,$Name);
@unlink("$docroot/$iconPath");
@unlink("{$dockerManPaths['images']}/".basename($iconPath));
$DockerTemplates->purgeUnusedIconFiles($Name);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since removing an icon from the cache is a bit more complex I use a new function specifically for that purpose.

@dcflachs
Copy link
Contributor Author

dcflachs commented Aug 5, 2022

My unRAID server has ~42 containers running with about ~30 unique icons. In my testing the changes to the caching mechanism do not seem to greatly effect the execution time of the getAllInfo function.

if ($ct['Icon']) $tmp['icon'] = $ct['Icon'];
if ( ! $communityApplications ) {
if (!is_file($tmp['icon']) || $reload) $tmp['icon'] = $this->getIcon($image,$name,$tmp['icon']);
if (!is_file($tmp['icon']) || $reload) $tmp['icon'] = $this->getIcon($image,$name,$ct['Icon']);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this change is not made then subsequent calls to getAllInfo will invalidate the cache by passing in the icon file path via tmp['icon'] instead of the icon url.

@dcflachs
Copy link
Contributor Author

dcflachs commented Sep 3, 2022

Any chance you would have time to look this pull request over and provide feedback?
@limetech @bergware

@teambvd
Copy link

teambvd commented Dec 27, 2022

It'd be great to get some eyes on this whenever someone has time - I've got between 50 and 60 containers on each of my unraid servers, and loading the docker tab's gotten to feeling pretty clunky at this point, so any performance improvement would be great!

@dcflachs dcflachs force-pushed the dockerman_icon_patch branch from 0365a1e to a5bd646 Compare December 1, 2023 01:50
@dcflachs
Copy link
Contributor Author

dcflachs commented Dec 1, 2023

Rebased changes onto latest rev

@dcflachs
Copy link
Contributor Author

I would love to get some feedback on this issue and proposed fix.

@Squidly271
Copy link
Contributor

Not my call, but since this is from 2022 you might want to retest the base system on 7.x and resubmit (even though there are no merge conflicts). Quite probable that since it is a very old PR that it's not going to get looked at since there have been numerous changes in DockerClient.

@ljm42 ljm42 added the TBD label Apr 29, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 29, 2025

Walkthrough

The changes refactor the handling of Docker container icon files within the container management logic. The update centralizes the logic for cleaning up unused icon files by introducing a new method, purgeUnusedIconFiles, in the DockerTemplates class. The getIcon method is updated to generate icon filenames using a SHA1 hash of the icon URL and now calls the new cleanup method to remove stale icons. The container creation logic is simplified to use this new cleanup method instead of manually deleting specific icon files. No changes are made to public interfaces except for the updated and new methods in DockerTemplates.

Changes

File(s) Change Summary
emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php Simplified icon file cleanup during container creation by replacing manual deletions with a call to purgeUnusedIconFiles.
emhttp/plugins/dynamix.docker.manager/include/DockerClient.php Refactored getIcon to use SHA1-hashed filenames, updated parameter name, and added purgeUnusedIconFiles for icon cleanup.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CreateDocker
    participant DockerTemplates

    User->>CreateDocker: Submit container creation/update
    CreateDocker->>DockerTemplates: getIcon(repository, name, iconUrl)
    DockerTemplates->>DockerTemplates: Generate SHA1 icon filename
    DockerTemplates->>DockerTemplates: Download/copy icon if needed
    DockerTemplates->>DockerTemplates: purgeUnusedIconFiles(name, keepIcon)
    DockerTemplates->>CreateDocker: Return icon path
    CreateDocker->>DockerTemplates: purgeUnusedIconFiles(name)
    CreateDocker->>User: Complete container creation/update
Loading

Poem

In the warren of Docker, a cleanup began,
Old icons were hopping, but now there’s a plan.
With hashes and purges, the clutter is gone,
The containers look dapper, their icons live on.
🐇✨

A tidy-up burrow, so neat and so bright—
CodeRabbit’s changes, just perfectly right!

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
emhttp/plugins/dynamix.docker.manager/include/DockerClient.php (1)

347-376: 🛠️ Refactor suggestion

Avoid “only variables should be passed by reference” notice

Inside purgeUnusedIconFiles() the suffix is extracted with:

$suffix = end(explode('-', $ramFile));

end() expects its argument by reference; passing the result of explode() triggers
Only variables should be passed by reference on PHP 7+ (and still emits a notice on earlier versions).
Assign to a variable first:

- $suffix = end(explode('-', $ramFile));
+ $parts  = explode('-', $ramFile);
+ $suffix = end($parts);

This eliminates the notice and is forward-compatible with PHP 8’s stricter reference handling.

🧹 Nitpick comments (3)
emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php (1)

85-91: Centralised icon-cleanup call looks correct, but consider preserving the current icon when possible

Replacing the hand-written unlink() sequence with $DockerTemplates->purgeUnusedIconFiles($Name) is a nice consolidation and keeps the template layer in charge of file housekeeping.
One small follow-up: at this point you already know the hash-based filename that getIcon() will eventually keep (it’s icon-<sha1>.png) once the new icon is downloaded. If you pass that value as the second argument you avoid a brief window where both the RAM and persistent copies are removed, forcing a re-download on the next page refresh:

$keep = sprintf('icon-%s.png', sha1($_POST['contIcon']));
$DockerTemplates->purgeUnusedIconFiles($Name, $keep);

Not strictly required, but it shaves an extra HTTP request for users who reopen the Docker tab immediately after clicking Apply.

emhttp/plugins/dynamix.docker.manager/include/DockerClient.php (2)

355-364: Directory creation race – use @mkdir with the recursive flag

mkdir(dirname($iconRAM), 0755, true) is fine when multiple concurrent calls are unlikely, but if two getIcon() invocations land at the same time the second one will throw a warning because the directory now exists.
You’re already suppressing IO warnings elsewhere, so consider the same pattern here:

- if (!is_dir(dirname($iconRAM))) mkdir(dirname($iconRAM), 0755, true);
- if (!is_dir(dirname($icon)))   mkdir(dirname($icon),    0755, true);
+@mkdir(dirname($iconRAM), 0755, true);
+@mkdir(dirname($icon),    0755, true);

It keeps the intent while removing a harmless but noisy warning in the logs.


378-406: Pattern mismatch may leave orphaned persistent icons

purgeUnusedIconFiles() deletes persistent icons using the glob pattern
%s/%s*.png (i.e. <contName>*.png).
Since persistent icons are now stored as icon-<sha1>.png without the container name, they are never caught by this first pass and rely solely on the suffix-cleanup logic below.

That works, but it’s indirect and means the initial glob() does unnecessary work.
You could simplify (and speed up) the method by deleting from the persistent directory based on the computed $keepIcon instead:

- $icon_glob = sprintf('%s/%s*.png', $dockerManPaths['images'], $contName);
+ $icon_glob = sprintf('%s/icon-*.png', $dockerManPaths['images']);

and keep the “suffix” reconciliation for safety.

Not a correctness bug, but trimming the extra filesystem pass reduces IO when many containers share the same icon.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 988e005 and a5bd646.

📒 Files selected for processing (2)
  • emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php (1 hunks)
  • emhttp/plugins/dynamix.docker.manager/include/DockerClient.php (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php (1)
emhttp/plugins/dynamix.docker.manager/include/DockerClient.php (1)
  • purgeUnusedIconFiles (378-406)

@richard-lee-863
Copy link

Bump. Really need this.

@joyboy-za
Copy link

I agree.

I didn't know what was happening when I added a second container of the same image and they had 2 different icons but came from the same registry.

Even if there was a way to only do it via the command line, that would work for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants