Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for shell icons for files on Windows #63

Merged
merged 8 commits into from
Oct 21, 2023

Conversation

iksi4prs
Copy link
Contributor

@iksi4prs iksi4prs commented Oct 1, 2023

Support for shell icons for files on Windows.

  1. Added support for shell icons on Windows.
  2. It is planned to add support to Linux, so common components, like cache, were implemented as non-platform specific.
  3. For images cache, since System.Drawing is Windows specific, and Avalonia is not referenced in Abstractions module, an addition of abstract class for image model was needed.
  4. On Windows, the shell icons are implemented differently for files (data files eg .docx, etc, and exe files) and for folders (folder can also include desktop.ini, etc), so this feature relates only to files, not folders (hence also name in settings)
  5. It is planned also to add support for folders later.

See attached images.

image

image

Copy link
Owner

@IngvarX IngvarX left a comment

Choose a reason for hiding this comment

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

Hey @iksi4prs ! Thanks for your PR. Please see my file specific comments below. Some general comments here:

  • Please make sure you follow current code formatting for consistency
  • Please add tests where possible/exclude some classes from tests coverage if needed

private readonly IconsSettingsModel _builtin;
private readonly IUnitOfWorkFactory _unitOfWorkFactory;
private readonly Platform _platform;
private IconsSettingsModel _cachedValue = null;
Copy link
Owner

Choose a reason for hiding this comment

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

We don't really need to cache values here because calling DB is rare and doesn't take too much time

Copy link
Contributor Author

Choose a reason for hiding this comment

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

every time we draw an icon for a file, we need to check if to use shell-icon or bulit-in, so this value should be kept somewhere, no point in checking db every time

Copy link
Owner

Choose a reason for hiding this comment

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

Please see my comment about loading it on startup


public IconsSettingsModel GetIconsSettings()
{
if (_platform != Platform.Windows)
Copy link
Owner

Choose a reason for hiding this comment

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

I would recommend to avoid platform-specific code here. Just allow to read/save as usual. See my comment regarding settings for more info

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 kept this as lower level possible, so implementers of feature for linux/mac, will have less to do.

@@ -30,15 +31,18 @@ public int SelectedIndex

public SettingsDialogViewModel(
ISettingsViewModel generalSettingsViewModel,
ISettingsViewModel terminalSettingsViewModel)
ISettingsViewModel terminalSettingsViewModel,
ISettingsViewModel iconsSettingsViewModel)
Copy link
Owner

Choose a reason for hiding this comment

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

Let's simply don't show icon VM for non-windows environments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I wrote before, I kept this as lower level possible, so implementers of feature for linux/mac, will have less to do.

Copy link
Owner

Choose a reason for hiding this comment

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

ok, if Linux/Mac PRs are incoming I'm ok with keeping it as is

private long _size;
private Bitmap _shellIcon = null;
Copy link
Owner

Choose a reason for hiding this comment

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

= null is redundant

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Habit. used to newer projects with Bitmap?.


private IconsType GetUserSelectedType()
{
var model = _iconsSettingsService.GetIconsSettings();
Copy link
Owner

Choose a reason for hiding this comment

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

I'm ok with settings applying only after restart so I think we can just inject this value from factory (where we can read it once) instead of doing this call every time

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As a user, very confusing. I applied the theme, and didn't understand why nothing happens.

Copy link
Owner

Choose a reason for hiding this comment

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

True but it's a tradeoff between code complexity and functionality. In the future I'm planning to add automatic restart to avoid that issue. Loading and storing this value on start saves us a lot of effort of loading it during runtime

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 think Avalonia v11 has different way of handling theme, that support it (at least for themes)


namespace Camelot.Services.AllPlatforms;

public class ShellIconsCacheService : IShellIconsCacheService
Copy link
Owner

Choose a reason for hiding this comment

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

I think we should leave "Cache" part of the name for this class out because we don't really want to expose to consumers such details as using cache internally. It seems that we use ShellIconsService only here so moving all code to the same file sounds fine to me

Copy link
Contributor Author

@iksi4prs iksi4prs Oct 4, 2023

Choose a reason for hiding this comment

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

ShellIconsService is per platform, just asking platform for icon, based on file extension or application.
ShellIconsCacheService is higher level, shared to all platform, that checks if it already has icon for extension.
eg for file1.txt and file2.txt, ShellIconsCacheService will call ShellIconsService only on 1st time.
on 2nd time, it will use cache, as it already has icon for ".txt".

Copy link
Owner

Choose a reason for hiding this comment

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

I got the idea but I don't like the naming, we should not let consumers of this class know that there is some cache being used. I recommend to implement this caching logic as a decorator or put it in some base class and for platforms just add some platform-specific implementation

{
private readonly IShellLinksService _shellLinksService;
private readonly IShellIconsService _shellIconsService;
private readonly Dictionary<string, Bitmap> _cache = new();
Copy link
Owner

Choose a reason for hiding this comment

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

What happens if the cache grows too large? Should we enforce some size limits?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. Cache is limited to number of extensions and apps.
I guess for normal user, if he has 30 extensions and some 100 apps, then it will not grow more than 150-200 icons.
But its a good idea to add some limit.

{
case IShellIconsService.ShellIconType.Extension:
{
var ext = Path.GetExtension(path);
Copy link
Owner

Choose a reason for hiding this comment

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

Please use IPathService

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.


namespace Camelot.Services.Abstractions;

public interface IShellIconsService
Copy link
Owner

Choose a reason for hiding this comment

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

These class & interface should be merged with IShellService with some methods being private

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see notes before about IShellIconsService as per platform.


public class ConcreteImage : ImageModel
{
public Bitmap Bitmap { get; }
Copy link
Owner

Choose a reason for hiding this comment

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

Is there a way to follow one of approaches mentioned here? https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only Current solution looks hacky. Alternative approach would be to get rid of Camelot.Images and create separate projects for platform specific stuff instead of touching services/abstractions projects that designed for a business logic and not UI stuff

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 is the trickiest thing on this PR.
I understand the separation of logic and UI.
But here, I think although its "image", it is part of business logic.
At first I thought as a solution to use Skia, or something like that, but it is actually not necessary.
Avalonia provides all that is necessary, just need some Image class in the abstractions layer.

Copy link
Owner

Choose a reason for hiding this comment

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

I see. My recommendation is:

  1. Create Camelot.UI.AllPlatforms and corresponding platform specific projects if needed
  2. Move this logic there. Services was designed for a business logic and not rendering stuff. Feel free to use Avalonia in new projects instead. It's totally ok to have a different logic for rendering on different platforms (surprisingly it didn't happen before).
  3. Camelot.Images can be removed in this case and all casts won't be necessary anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

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

a. "Create Camelot.UI.AllPlatforms" - can u send picture when do u want it in solution ?
b. "Move this logic there" - do u mean all the icons services ?

Copy link
Owner

Choose a reason for hiding this comment

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

I will actually try to build and push this change today in the evening

@codecov-commenter
Copy link

codecov-commenter commented Oct 6, 2023

Codecov Report

Attention: 181 lines in your changes are missing coverage. Please review.

Comparison is base (379a713) 97.68% compared to head (c7b4e2e) 92.89%.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #63      +/-   ##
==========================================
- Coverage   97.68%   92.89%   -4.79%     
==========================================
  Files         177      185       +8     
  Lines        4619     4830     +211     
  Branches        0      388     +388     
==========================================
- Hits         4512     4487      -25     
- Misses        107      288     +181     
- Partials        0       55      +55     
Files Coverage Δ
.../Implementations/FileSystemNodeViewModelFactory.cs 100.00% <100.00%> (ø)
...Implementations/Dialogs/SettingsDialogViewModel.cs 100.00% <100.00%> (ø)
...iewModels/Services/Interfaces/Models/ImageModel.cs 0.00% <0.00%> (ø)
....ViewModels.Windows/ShellIcons/WindowsIconTypes.cs 0.00% <0.00%> (ø)
...tions/MainWindow/FilePanels/Nodes/FileViewModel.cs 56.66% <20.00%> (-43.34%) ⬇️
src/Camelot.Services/IconsSettingsService.cs 40.90% <40.90%> (ø)
...els.Windows/ShellIcons/WindowsShellLinksService.cs 0.00% <0.00%> (ø)
...Implementations/Settings/IconsSettingsViewModel.cs 23.52% <23.52%> (ø)
...ShellIcons/SystemImageToAvaloniaBitmapConverter.cs 0.00% <0.00%> (ø)
...els.Windows/ShellIcons/WindowsShellIconsService.cs 0.00% <0.00%> (ø)
... and 1 more

... and 30 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@IngvarX
Copy link
Owner

IngvarX commented Oct 6, 2023

Pushed some changes to avoid casts, unfortunately tests are still broken. Next step will be to address tests and do refactoring

@IngvarX
Copy link
Owner

IngvarX commented Oct 6, 2023

Fixed tests in the follow up commit. So just refactoring/addressing PR comments is left

return true; // icon in indexed resource

var ext = Path.GetExtension(filename).ToLower();
var extensionsOfIcoFiles = new List<string>() { ".ico", ".exe", ".dll" , ".cpl", ".appref-ms", ".msc" };
Copy link
Owner

Choose a reason for hiding this comment

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

I suspect that this list should be in sync with WindowsShellIconsService and this code might be refactored to avoid this class creation at all

@IngvarX IngvarX merged commit 1a513c0 into IngvarX:master Oct 21, 2023
3 checks passed
@IngvarX
Copy link
Owner

IngvarX commented Oct 21, 2023

Thanks, merged!

@iksi4prs iksi4prs deleted the shell-icons-windows branch October 22, 2023 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants