Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ffbd8fd
Add logic for IcoPath's that are web URL's
Garulf Aug 30, 2022
03e319e
Remove whitespace
Garulf Aug 30, 2022
fda155a
DOn't prepend pluginpath if IcoPath is a web URL
Garulf Aug 30, 2022
b5c2125
Merge branch 'dev' into handle-icon-urls
Garulf Aug 30, 2022
d62d710
Use URI class to detect web URL
Garulf Sep 21, 2022
301882d
Switch from WebClient to HTTP
Garulf Sep 21, 2022
5c33b0d
Only convert relative paths if its a valid path
Garulf Oct 7, 2022
b7900b2
// Only convert relative paths if its a valid path
Garulf Oct 7, 2022
57d63cf
Add logic for IcoPath's that are web URL's
Garulf Aug 30, 2022
ed107cc
Remove whitespace
Garulf Aug 30, 2022
7f1b9d0
DOn't prepend pluginpath if IcoPath is a web URL
Garulf Aug 30, 2022
416c44f
Use URI class to detect web URL
Garulf Sep 21, 2022
ec1a061
Switch from WebClient to HTTP
Garulf Sep 21, 2022
ebd6f17
Only convert relative paths if its a valid path
Garulf Oct 7, 2022
eb3f723
// Only convert relative paths if its a valid path
Garulf Oct 7, 2022
ef575bb
Adjust Http Code
taooceros Oct 13, 2022
3708489
Adjust Google Code a bit due to http change
taooceros Oct 13, 2022
191c6af
Adjust Bing Code a bit due to http change
taooceros Oct 13, 2022
957c4e2
Adjust Http Design to avoid memory leak
taooceros Oct 13, 2022
eb5e33a
Make ImageLoader Async
taooceros Oct 13, 2022
c39a727
Revert back some unnecessary change
taooceros Oct 13, 2022
99fd8d1
Dispose Stream (though it maybe already disposed by disposed the resp…
taooceros Oct 27, 2022
2507d1a
remove testing code
taooceros Oct 30, 2022
ba0aee1
resize bitmapimage when not loading full image
taooceros Oct 30, 2022
cae0b7b
Merge branch 'handle-icon-urls' of https://github.com/Flow-Launcher/F…
Garulf Oct 30, 2022
e9bf62e
Revert "Merge branch 'handle-icon-urls' of https://github.com/Flow-La…
taooceros Oct 30, 2022
0fd127a
Refactor code
taooceros Oct 30, 2022
cf9dd4a
Respect loadFullImage Option in ImageCache.cs
taooceros Oct 30, 2022
ffa40b0
Respect loadFullImage Option in ImageCache.cs
taooceros Oct 30, 2022
ff2ebc8
Merge branch 'dev' into handle-icon-urls
taooceros Oct 30, 2022
e86a2f1
slightly refactor
taooceros Oct 30, 2022
28256a7
Merge remote-tracking branch 'origin/handle-icon-urls' into handle-ic…
taooceros Oct 30, 2022
101593a
edit signature for CacheContainImage
taooceros Oct 31, 2022
169857b
Merge remote-tracking branch 'origin/dev' into handle-icon-urls
taooceros Nov 11, 2022
26668b4
Merge branch 'dev' into handle-icon-urls
Garulf Nov 17, 2022
ce52919
Merge branch 'dev' into handle-icon-urls
Garulf Nov 17, 2022
414e55c
Merge branch 'dev' into handle-icon-urls
onesounds Nov 18, 2022
622b130
Merge branch 'handle-icon-urls' of https://github.com/Flow-Launcher/F…
Garulf Nov 18, 2022
e5948a7
Merge branch 'dev' into handle-icon-urls
jjw24 Nov 22, 2022
67443fd
Merge branch 'handle-icon-urls' of https://github.com/Flow-Launcher/F…
Garulf Nov 23, 2022
3665bd9
Wrap non async part in task
Garulf Nov 23, 2022
83ec809
remove PluginDirectory, ImageLoader path checking, update IcoPath logic
jjw24 Nov 24, 2022
303d3b9
remove task.run outside
taooceros Nov 24, 2022
fb3a23f
remove unnecessary local variable
taooceros Nov 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static async Task UpdateManifestAsync(CancellationToken token = default)
var request = new HttpRequestMessage(HttpMethod.Get, manifestFileUrl);
request.Headers.Add("If-None-Match", latestEtag);

var response = await Http.SendAsync(request, token).ConfigureAwait(false);
using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
Comment thread
jjw24 marked this conversation as resolved.

if (response.StatusCode == HttpStatusCode.OK)
{
Expand All @@ -56,4 +56,4 @@ public static async Task UpdateManifestAsync(CancellationToken token = default)
}
}
}
}
}
8 changes: 4 additions & 4 deletions Flow.Launcher.Core/Updater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public async Task UpdateAppAsync(IPublicAPI api, bool silentUpdate = true)
await updateManager.CreateUninstallerRegistryEntry().ConfigureAwait(false);
}

var newVersionTips = NewVersinoTips(newReleaseVersion.ToString());
var newVersionTips = NewVersionTips(newReleaseVersion.ToString());

Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");

Expand Down Expand Up @@ -137,10 +137,10 @@ private async Task<UpdateManager> GitHubUpdateManagerAsync(string repository)
return manager;
}

public string NewVersinoTips(string version)
public string NewVersionTips(string version)
{
var translater = InternationalizationManager.Instance;
var tips = string.Format(translater.GetTranslation("newVersionTips"), version);
var translator = InternationalizationManager.Instance;
var tips = string.Format(translator.GetTranslation("newVersionTips"), version);

return tips;
}
Expand Down
53 changes: 37 additions & 16 deletions Flow.Launcher.Infrastructure/Http/Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public static void UpdateProxy(ProxyProperty property)
var userName when string.IsNullOrEmpty(userName) =>
(new Uri($"http://{Proxy.Server}:{Proxy.Port}"), null),
_ => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"),
new NetworkCredential(Proxy.UserName, Proxy.Password))
new NetworkCredential(Proxy.UserName, Proxy.Password))
},
_ => (null, null)
},
Expand All @@ -79,7 +79,7 @@ var userName when string.IsNullOrEmpty(userName) =>
_ => throw new ArgumentOutOfRangeException()
};
}
catch(UriFormatException e)
catch (UriFormatException e)
{
API.ShowMsg("Please try again", "Unable to parse Http Proxy");
Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
Expand All @@ -94,7 +94,7 @@ public static async Task DownloadAsync([NotNull] string url, [NotNull] string fi
if (response.StatusCode == HttpStatusCode.OK)
{
await using var fileStream = new FileStream(filePath, FileMode.CreateNew);
await response.Content.CopyToAsync(fileStream);
await response.Content.CopyToAsync(fileStream, token);
}
else
{
Expand All @@ -117,7 +117,7 @@ public static async Task DownloadAsync([NotNull] string url, [NotNull] string fi
public static Task<string> GetAsync([NotNull] string url, CancellationToken token = default)
{
Log.Debug($"|Http.Get|Url <{url}>");
return GetAsync(new Uri(url.Replace("#", "%23")), token);
return GetAsync(new Uri(url), token);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this change intended?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah I don't think that's needed since it is creating a url.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what do you mean? the old code is also creating a url too no?

}

/// <summary>
Expand All @@ -130,36 +130,57 @@ public static async Task<string> GetAsync([NotNull] Uri url, CancellationToken t
{
Log.Debug($"|Http.Get|Url <{url}>");
using var response = await client.GetAsync(url, token);
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.OK)
{
return content;
}
else
var content = await response.Content.ReadAsStringAsync(token);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new HttpRequestException(
$"Error code <{response.StatusCode}> with content <{content}> returned from <{url}>");
}

return content;
}

/// <summary>
/// Asynchrously get the result as stream from url.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I revised some of the Http design to align with the HttpClient design. Their design is much better

/// Send a GET request to the specified Uri with an HTTP completion option and a cancellation token as an asynchronous operation.
/// </summary>
/// <param name="url">The Uri the request is sent to.</param>
/// <param name="completionOption">An HTTP completion option value that indicates when the operation should be considered completed.</param>
/// <param name="token">A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
/// <returns></returns>
public static Task<Stream> GetStreamAsync([NotNull] string url,
CancellationToken token = default) => GetStreamAsync(new Uri(url), token);


/// <summary>
/// Send a GET request to the specified Uri with an HTTP completion option and a cancellation token as an asynchronous operation.
/// </summary>
/// <param name="url"></param>
/// <param name="token"></param>
/// <returns></returns>
public static async Task<Stream> GetStreamAsync([NotNull] string url, CancellationToken token = default)
public static async Task<Stream> GetStreamAsync([NotNull] Uri url,
CancellationToken token = default)
{
Log.Debug($"|Http.Get|Url <{url}>");
return await client.GetStreamAsync(url, token);
}

public static async Task<HttpResponseMessage> GetResponseAsync(string url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
CancellationToken token = default)
=> await GetResponseAsync(new Uri(url), completionOption, token);

public static async Task<HttpResponseMessage> GetResponseAsync([NotNull] Uri url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
CancellationToken token = default)
{
Log.Debug($"|Http.Get|Url <{url}>");
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
return await response.Content.ReadAsStreamAsync();
return await client.GetAsync(url, completionOption, token);
}

/// <summary>
/// Asynchrously send an HTTP request.
/// </summary>
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token = default)
public static async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken token = default)
{
return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
return await client.SendAsync(request, completionOption, token);
}
}
}
55 changes: 38 additions & 17 deletions Flow.Launcher.Infrastructure/Image/ImageLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using static Flow.Launcher.Infrastructure.Http.Http;

namespace Flow.Launcher.Infrastructure.Image
{
Expand All @@ -23,13 +26,7 @@ public static class ImageLoader

private static readonly string[] ImageExtensions =
{
".png",
".jpg",
".jpeg",
".gif",
".bmp",
".tiff",
".ico"
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"
};

public static void Initialize()
Expand All @@ -39,21 +36,24 @@ public static void Initialize()

var usage = LoadStorageToConcurrentDictionary();

foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
foreach (var icon in new[]
{
Constant.DefaultIcon, Constant.MissingImgIcon
})
{
ImageSource img = new BitmapImage(new Uri(icon));
img.Freeze();
ImageCache[icon] = img;
}

_ = Task.Run(() =>
_ = Task.Run(async () =>
{
Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () =>
await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () =>
{
ImageCache.Data.AsParallel().ForAll(x =>
foreach (var imageUsage in ImageCache.Data)
{
Load(x.Key);
});
await LoadAsync(imageUsage.Key);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Due to the ImageLoader change to async (not always async so ValueTask). I wonder whether we should use Task to wrap the nonasync part so we can always just await it.

}
});
Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
});
Expand Down Expand Up @@ -99,7 +99,7 @@ private enum ImageType
Cache
}

private static ImageResult LoadInternal(string path, bool loadFullImage = false)
private static async ValueTask<ImageResult> LoadInternalAsync(string path, bool loadFullImage = false)
{
ImageResult imageResult;

Expand All @@ -113,7 +113,28 @@ private static ImageResult LoadInternal(string path, bool loadFullImage = false)
{
return new ImageResult(ImageCache[path], ImageType.Cache);
}

if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps))
{
// Download image from url
await using var resp = await GetStreamAsync(uriResult);
if (resp == null)
{
return new ImageResult(ImageCache[Constant.MissingImgIcon], ImageType.Error);
}
await using var buffer = new MemoryStream();
await resp.CopyToAsync(buffer);
buffer.Seek(0, SeekOrigin.Begin);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = buffer;
image.EndInit();
image.StreamSource = null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

to make the streamsource collectable

image.Freeze();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Freeze is needed because the BitmapImage is not created in the ui thread.

ImageCache[path] = image;
return new ImageResult(image, ImageType.ImageFile);
}
if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
{
var imageSource = new BitmapImage(new Uri(path));
Expand Down Expand Up @@ -218,9 +239,9 @@ public static bool CacheContainImage(string path)
return ImageCache.ContainsKey(path) && ImageCache[path] != null;
}

public static ImageSource Load(string path, bool loadFullImage = false)
public static async ValueTask<ImageSource> LoadAsync(string path, bool loadFullImage = false)
{
var imageResult = LoadInternal(path, loadFullImage);
var imageResult = await LoadInternalAsync(path, loadFullImage);

var img = imageResult.ImageSource;
if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache)
Expand Down
18 changes: 15 additions & 3 deletions Flow.Launcher.Plugin/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ public string IcoPath
{
if (!string.IsNullOrEmpty(PluginDirectory) && !Path.IsPathRooted(value))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@taooceros & @Garulf not related to this PR but I am trying to figure out why we need to check PluginDirectory is not an empty string? I cant seem to hit this condition.

{
_icoPath = Path.Combine(value, IcoPath);
string absPath = Path.Combine(value, IcoPath);
// Only convert relative paths if its a valid path
if (File.Exists(absPath))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there another way to check this path without using File.Exists? Doesn't seem necessary to use it here.

@Garulf Garulf Nov 22, 2022

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This way allows us to improve how Flow auto appends the plugins path to icon paths that are relative and still accept URI such as http, https, ftp, etc. instead of checking every URI under the sun to see if it's valid let's just detect if that file even exists and leave it alone if it doesn't.

Edit: original code just tacked on the plugins path to any string it didn't detect a root file path on.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I mean this is an unnecessary call because we already checking that the path exists or not at

private static ImageResult GetThumbnailResult(ref string path, bool loadFullImage = false)
{
ImageSource image;
ImageType type = ImageType.Error;
if (Directory.Exists(path))
{
/* Directories can also have thumbnails instead of shell icons.
* Generating thumbnails for a bunch of folder results while scrolling
* could have a big impact on performance and Flow.Launcher responsibility.
* - Solution: just load the icon
*/
type = ImageType.Folder;
image = GetThumbnail(path, ThumbnailOptions.IconOnly);
}
else if (File.Exists(path))
{
var extension = Path.GetExtension(path).ToLower();
if (ImageExtensions.Contains(extension))
{

(GetThumbnailResult is called by LoadInternalAsync)

We just need to determine that it is not a URI.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We're not really concerned with if the path exists or not. What we're really concerned with is the path really a relative path or not.

The built-in method for detecting relative paths isnt very smart.

@jjw24 jjw24 Nov 24, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The built-in method for detecting relative paths isnt very smart

Agree, using Uri class is not working for our cases. Just doing a simple check for http and https seems more effective to determine that it is Url, then consider everything else as local absolute/relative path.

@Garulf @taooceros I reworked IcoPath, PluginDirectory and ImageLoader 83ec809, let me know if ok.

{
_icoPath = Path.Combine(value, IcoPath);
}

}
else
{
Expand Down Expand Up @@ -135,9 +141,15 @@ public string PluginDirectory
set
{
_pluginDirectory = value;
if (!string.IsNullOrEmpty(IcoPath) && !Path.IsPathRooted(IcoPath))
if (!string.IsNullOrEmpty(IcoPath) && !Path.IsPathRooted(IcoPath))
{
IcoPath = Path.Combine(value, IcoPath);
Comment thread
jjw24 marked this conversation as resolved.
string absPath = Path.Combine(value, IcoPath);
// Only convert relative paths if its a valid path
if (File.Exists(absPath))
{
IcoPath = absPath;
}

}
}
}
Expand Down
16 changes: 11 additions & 5 deletions Flow.Launcher/Msg.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,16 @@ public Msg()
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(TopProperty));
fadeOutStoryboard.Children.Add(fadeOutAnimation);

imgClose.Source = ImageLoader.Load(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\close.png"));
_ = LoadImageAsync();

imgClose.MouseUp += imgClose_MouseUp;
}

private async System.Threading.Tasks.Task LoadImageAsync()
{
imgClose.Source = await ImageLoader.LoadAsync(Path.Combine(Infrastructure.Constant.ProgramDirectory, "Images\\close.png"));
}

void imgClose_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!closing)
Expand All @@ -56,7 +62,7 @@ private void fadeOutStoryboard_Completed(object sender, EventArgs e)
Close();
}

public void Show(string title, string subTitle, string iconPath)
public async void Show(string title, string subTitle, string iconPath)
{
tbTitle.Text = title;
tbSubTitle.Text = subTitle;
Expand All @@ -66,15 +72,15 @@ public void Show(string title, string subTitle, string iconPath)
}
if (!File.Exists(iconPath))
{
imgIco.Source = ImageLoader.Load(Path.Combine(Constant.ProgramDirectory, "Images\\app.png"));
imgIco.Source = await ImageLoader.LoadAsync(Path.Combine(Constant.ProgramDirectory, "Images\\app.png"));
}
else {
imgIco.Source = ImageLoader.Load(iconPath);
imgIco.Source = await ImageLoader.LoadAsync(iconPath);
}

Show();

Dispatcher.InvokeAsync(async () =>
await Dispatcher.InvokeAsync(async () =>
{
if (!closing)
{
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher/SettingWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,7 @@
Height="32"
Margin="32,0,0,0"
FlowDirection="LeftToRight"
Source="{Binding Image, IsAsync=True}" />
Source="{Binding Image, Mode=OneWay}" />
</StackPanel>
<StackPanel Grid.Column="1" Margin="12,0,14,0">
<TextBlock
Expand Down
22 changes: 20 additions & 2 deletions Flow.Launcher/ViewModel/PluginViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Windows;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure.Image;
Expand All @@ -24,7 +25,23 @@ public PluginPair PluginPair
}
}

public ImageSource Image => ImageLoader.Load(PluginPair.Metadata.IcoPath);

private async void LoadIconAsync()
{
Image = await ImageLoader.LoadAsync(PluginPair.Metadata.IcoPath);
}

public ImageSource Image
{
get
{
if (_image == ImageLoader.DefaultImage)
LoadIconAsync();

return _image;
}
set => _image = value;
}
public bool PluginState
{
get => !PluginPair.Metadata.Disabled;
Expand All @@ -50,6 +67,7 @@ public Control SettingControl
? new Control()
: settingProvider.CreateSettingPanel()
: null;
private ImageSource _image = ImageLoader.DefaultImage;

public Visibility ActionKeywordsVisibility => PluginPair.Metadata.ActionKeywords.Count == 1 ? Visibility.Visible : Visibility.Collapsed;
public string InitilizaTime => PluginPair.Metadata.InitTime + "ms";
Expand Down
Loading