fix<Avatar>: fix crash when more than one thread wants to access same avatar file

This commit is contained in:
leo 2021-04-01 10:54:05 +08:00
parent 6f08d03d04
commit c22ea8f4cf
3 changed files with 49 additions and 46 deletions

View file

@ -103,6 +103,9 @@ namespace SourceGit {
Setting = JsonSerializer.Deserialize<Preference>(File.ReadAllText(settingFile)); Setting = JsonSerializer.Deserialize<Preference>(File.ReadAllText(settingFile));
} }
// Make sure avatar cache folder exists
if (!Directory.Exists(Helpers.Avatar.CACHE_PATH)) Directory.CreateDirectory(Helpers.Avatar.CACHE_PATH);
// Try auto configure git via registry. // Try auto configure git via registry.
if (Setting == null || !IsGitConfigured) { if (Setting == null || !IsGitConfigured) {
var root = RegistryKey.OpenBaseKey( var root = RegistryKey.OpenBaseKey(

View file

@ -2,8 +2,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Net;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
@ -36,6 +38,14 @@ namespace SourceGit.Helpers {
Brushes.DarkViolet Brushes.DarkViolet
}; };
/// <summary>
/// Path to cache downloaded avatars
/// </summary>
public static readonly string CACHE_PATH = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"SourceGit",
"avatars");
/// <summary> /// <summary>
/// User property definition. /// User property definition.
/// </summary> /// </summary>
@ -53,26 +63,15 @@ namespace SourceGit.Helpers {
set { SetValue(UserProperty, value); } set { SetValue(UserProperty, value); }
} }
/// <summary>
/// Loading request
/// </summary>
private class Request {
public BitmapImage img = null;
public List<Avatar> targets = new List<Avatar>();
}
/// <summary>
/// Path to cache downloaded avatars
/// </summary>
private static readonly string CACHE_PATH = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"SourceGit",
"avatars");
/// <summary> /// <summary>
/// Current requests. /// Current requests.
/// </summary> /// </summary>
private static Dictionary<string, Request> requesting = new Dictionary<string, Request>(); private static Dictionary<string, List<Avatar>> requesting = new Dictionary<string, List<Avatar>>();
/// <summary>
/// Loaded images.
/// </summary>
private static Dictionary<string, BitmapImage> loaded = new Dictionary<string, BitmapImage>();
/// <summary> /// <summary>
/// Render implementation. /// Render implementation.
@ -112,10 +111,10 @@ namespace SourceGit.Helpers {
/// </summary> /// </summary>
private void ReloadImage(Git.User oldUser) { private void ReloadImage(Git.User oldUser) {
if (oldUser != null && requesting.ContainsKey(oldUser.Email)) { if (oldUser != null && requesting.ContainsKey(oldUser.Email)) {
if (requesting[oldUser.Email].targets.Count <= 1) { if (requesting[oldUser.Email].Count <= 1) {
requesting.Remove(oldUser.Email); requesting.Remove(oldUser.Email);
} else { } else {
requesting[oldUser.Email].targets.Remove(this); requesting[oldUser.Email].Remove(this);
} }
} }
@ -125,8 +124,13 @@ namespace SourceGit.Helpers {
if (User == null) return; if (User == null) return;
var email = User.Email; var email = User.Email;
if (loaded.ContainsKey(email)) {
Source = loaded[email];
return;
}
if (requesting.ContainsKey(email)) { if (requesting.ContainsKey(email)) {
requesting[email].targets.Add(this); requesting[email].Add(this);
return; return;
} }
@ -137,36 +141,32 @@ namespace SourceGit.Helpers {
string filePath = Path.Combine(CACHE_PATH, md5); string filePath = Path.Combine(CACHE_PATH, md5);
if (File.Exists(filePath)) { if (File.Exists(filePath)) {
Source = new BitmapImage(new Uri(filePath)); var img = new BitmapImage(new Uri(filePath));
loaded.Add(email, img);
Source = img;
return; return;
} }
requesting.Add(email, new Request()); requesting.Add(email, new List<Avatar>());
requesting[email].targets.Add(this); requesting[email].Add(this);
BitmapImage downloading = new BitmapImage(new Uri("https://www.gravatar.com/avatar/" + md5 + "?d=404")); Task.Run(() => {
requesting[email].img = downloading; try {
downloading.DownloadCompleted += (o, e) => { var agent = new WebClient();
var owner = o as BitmapImage; var data = agent.DownloadData("https://www.gravatar.com/avatar/" + md5 + "?d=404");
if (owner != null) { //var data = agent.DownloadData("https://cdn.s.loli.top/avatar/" + md5 + "?d=404");
if (!Directory.Exists(CACHE_PATH)) Directory.CreateDirectory(CACHE_PATH); File.WriteAllBytes(filePath, data);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(owner));
using (var fs = new FileStream(filePath, FileMode.Create)) {
encoder.Save(fs);
}
if (requesting.ContainsKey(email)) { if (requesting.ContainsKey(email)) {
BitmapImage exists = new BitmapImage(new Uri(filePath)); Dispatcher.Invoke(() => {
foreach (var one in requesting[email].targets) one.Source = exists; var img = new BitmapImage(new Uri(filePath));
loaded[email] = img;
foreach (var one in requesting[email]) one.Source = img;
requesting.Remove(email); requesting.Remove(email);
});
} }
} } catch {}
}; });
downloading.DownloadFailed += (o, e) => {
requesting.Remove(email);
};
} }
/// <summary> /// <summary>

View file

@ -73,7 +73,7 @@
</Style> </Style>
</DataGrid.Resources> </DataGrid.Resources>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTemplateColumn Width="*" IsReadOnly="True"> <DataGridTemplateColumn x:Name="commitGraphColumn" Width="*" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal" Margin="{Binding GraphOffset, Converter={StaticResource CommitTitleMargin}}"> <StackPanel Orientation="Horizontal" Margin="{Binding GraphOffset, Converter={StaticResource CommitTitleMargin}}">
@ -144,7 +144,7 @@
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn Width="84" IsReadOnly="True" Binding="{Binding Committer.Name}" ElementStyle="{StaticResource Style.DataGridText.NoPadding}"/> <DataGridTextColumn Width="Auto" IsReadOnly="True" Binding="{Binding Committer.Name}" ElementStyle="{StaticResource Style.DataGridText.NoPadding}"/>
<DataGridTextColumn Width="84" IsReadOnly="True" Binding="{Binding ShortSHA}" ElementStyle="{StaticResource Style.DataGridText}"/> <DataGridTextColumn Width="84" IsReadOnly="True" Binding="{Binding ShortSHA}" ElementStyle="{StaticResource Style.DataGridText}"/>
<DataGridTextColumn Width="128" IsReadOnly="True" Binding="{Binding Committer.Time}" ElementStyle="{StaticResource Style.DataGridText.NoPadding}"/> <DataGridTextColumn Width="128" IsReadOnly="True" Binding="{Binding Committer.Time}" ElementStyle="{StaticResource Style.DataGridText.NoPadding}"/>
</DataGrid.Columns> </DataGrid.Columns>
@ -162,7 +162,7 @@
</DataGrid> </DataGrid>
<!-- Commit Graph --> <!-- Commit Graph -->
<Border x:Name="commitGraphContainer" Grid.Row="1" Margin="0,0,342,0" ClipToBounds="True" IsHitTestVisible="False"> <Border x:Name="commitGraphContainer" Grid.Row="1" Width="{Binding ElementName=commitGraphColumn, Path=ActualWidth}" ClipToBounds="True" IsHitTestVisible="False" HorizontalAlignment="Left">
<helpers:CommitGraph <helpers:CommitGraph
x:Name="commitGraph" x:Name="commitGraph"
Width="{Binding ElementName=commitGraphContainer, Path=ActualWidth}" Width="{Binding ElementName=commitGraphContainer, Path=ActualWidth}"