refactor: delay starting background tasks

* start background task only it is needed
* solve the problem that we can not use `ViewModels.Preference.Instance` until resource ready
* remove avatar sever settings
This commit is contained in:
leo 2024-08-18 23:09:55 +08:00
parent e7921db339
commit 7fe1df20cc
No known key found for this signature in database
13 changed files with 178 additions and 190 deletions

View file

@ -528,6 +528,8 @@ namespace SourceGit
private void TryLaunchedAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
{
Native.OS.SetupEnternalTools();
Models.AvatarManager.Instance.Start();
Models.AutoFetchManager.Instance.Start();
string startupRepo = null;
if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0]))

View file

@ -26,7 +26,7 @@ namespace SourceGit.Commands
Args += remote;
AutoFetch.MarkFetched(repo);
Models.AutoFetchManager.Instance.MarkFetched(repo);
}
public Fetch(string repo, string remote, string localBranch, string remoteBranch, Action<string> outputHandler)
@ -46,110 +46,4 @@ namespace SourceGit.Commands
private readonly Action<string> _outputHandler;
}
public class AutoFetch
{
public static bool IsEnabled
{
get;
set;
} = false;
public static int Interval
{
get => _interval;
set
{
if (value < 1)
return;
_interval = value;
lock (_lock)
{
foreach (var job in _jobs)
{
job.Value.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(_interval));
}
}
}
}
class Job
{
public Fetch Cmd = null;
public DateTime NextRunTimepoint = DateTime.MinValue;
}
static AutoFetch()
{
Task.Run(() =>
{
while (true)
{
if (!IsEnabled)
{
Thread.Sleep(10000);
continue;
}
var now = DateTime.Now;
var uptodate = new List<Job>();
lock (_lock)
{
foreach (var job in _jobs)
{
if (job.Value.NextRunTimepoint.Subtract(now).TotalSeconds <= 0)
{
uptodate.Add(job.Value);
}
}
}
foreach (var job in uptodate)
{
job.Cmd.Exec();
job.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
Thread.Sleep(2000);
}
});
}
public static void AddRepository(string repo)
{
var job = new Job
{
Cmd = new Fetch(repo, "--all", true, false, null) { RaiseError = false },
NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
};
lock (_lock)
{
_jobs[repo] = job;
}
}
public static void RemoveRepository(string repo)
{
lock (_lock)
{
_jobs.Remove(repo);
}
}
public static void MarkFetched(string repo)
{
lock (_lock)
{
if (_jobs.TryGetValue(repo, out var value))
{
value.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
}
}
private static readonly Dictionary<string, Job> _jobs = new Dictionary<string, Job>();
private static readonly object _lock = new object();
private static int _interval = 10;
}
}

View file

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace SourceGit.Models
{
public class AutoFetchManager
{
public static AutoFetchManager Instance
{
get
{
if (_instance == null)
_instance = new AutoFetchManager();
return _instance;
}
}
public class Job
{
public Commands.Fetch Cmd = null;
public DateTime NextRunTimepoint = DateTime.MinValue;
}
public bool IsEnabled
{
get;
set;
} = false;
public int Interval
{
get => _interval;
set
{
_interval = Math.Max(1, value);
lock (_lock)
{
foreach (var job in _jobs)
job.Value.NextRunTimepoint = DateTime.Now.AddMinutes(_interval * 1.0);
}
}
}
private static AutoFetchManager _instance = null;
private Dictionary<string, Job> _jobs = new Dictionary<string, Job>();
private object _lock = new object();
private int _interval = 10;
public void Start()
{
Task.Run(() =>
{
while (true)
{
if (!IsEnabled)
{
Thread.Sleep(10000);
continue;
}
var now = DateTime.Now;
var uptodate = new List<Job>();
lock (_lock)
{
foreach (var job in _jobs)
{
if (job.Value.NextRunTimepoint.Subtract(now).TotalSeconds <= 0)
uptodate.Add(job.Value);
}
}
foreach (var job in uptodate)
{
job.Cmd.Exec();
job.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
Thread.Sleep(2000);
}
// ReSharper disable once FunctionNeverReturns
});
}
public void AddRepository(string repo)
{
var job = new Job
{
Cmd = new Commands.Fetch(repo, "--all", true, false, null) { RaiseError = false },
NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
};
lock (_lock)
{
_jobs[repo] = job;
}
}
public void RemoveRepository(string repo)
{
lock (_lock)
{
_jobs.Remove(repo);
}
}
public void MarkFetched(string repo)
{
lock (_lock)
{
if (_jobs.TryGetValue(repo, out var value))
value.NextRunTimepoint = DateTime.Now.AddMinutes(Interval * 1.0);
}
}
}
}

View file

@ -20,15 +20,31 @@ namespace SourceGit.Models
void OnAvatarResourceChanged(string email);
}
public static partial class AvatarManager
public partial class AvatarManager
{
public static string SelectedServer
public static AvatarManager Instance
{
get;
set;
} = "https://www.gravatar.com/avatar/";
get
{
if (_instance == null)
_instance = new AvatarManager();
static AvatarManager()
return _instance;
}
}
private static AvatarManager _instance = null;
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")]
private static partial Regex REG_GITHUB_USER_EMAIL();
private object _synclock = new object();
private string _storePath;
private List<IAvatarHost> _avatars = new List<IAvatarHost>();
private Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
private HashSet<string> _requesting = new HashSet<string>();
public void Start()
{
_storePath = Path.Combine(Native.OS.DataDir, "avatars");
if (!Directory.Exists(_storePath))
@ -62,7 +78,7 @@ namespace SourceGit.Models
var matchGithubUser = REG_GITHUB_USER_EMAIL().Match(email);
var url = matchGithubUser.Success ?
$"https://avatars.githubusercontent.com/{matchGithubUser.Groups[2].Value}" :
$"{SelectedServer}{md5}?d=404";
$"https://www.gravatar.com/avatar/{md5}?d=404";
var localFile = Path.Combine(_storePath, md5);
var img = null as Bitmap;
@ -105,20 +121,22 @@ namespace SourceGit.Models
NotifyResourceChanged(email);
});
}
// ReSharper disable once FunctionNeverReturns
});
}
public static void Subscribe(IAvatarHost host)
public void Subscribe(IAvatarHost host)
{
_avatars.Add(host);
}
public static void Unsubscribe(IAvatarHost host)
public void Unsubscribe(IAvatarHost host)
{
_avatars.Remove(host);
}
public static Bitmap Request(string email, bool forceRefetch)
public Bitmap Request(string email, bool forceRefetch)
{
if (forceRefetch)
{
@ -167,7 +185,7 @@ namespace SourceGit.Models
return null;
}
private static string GetEmailHash(string email)
private string GetEmailHash(string email)
{
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered));
@ -177,21 +195,12 @@ namespace SourceGit.Models
return builder.ToString();
}
private static void NotifyResourceChanged(string email)
private void NotifyResourceChanged(string email)
{
foreach (var avatar in _avatars)
{
avatar.OnAvatarResourceChanged(email);
}
}
private static readonly object _synclock = new object();
private static readonly string _storePath;
private static readonly List<IAvatarHost> _avatars = new List<IAvatarHost>();
private static readonly Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
private static readonly HashSet<string> _requesting = new HashSet<string>();
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")]
private static partial Regex REG_GITHUB_USER_EMAIL();
}
}

View file

@ -378,7 +378,6 @@
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Design</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Design-Anpassungen</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">ALLGEMEIN</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">Avatar Server</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Beim Starten nach Updates suchen</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Sprache</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">Commit-Historie</x:String>

View file

@ -380,7 +380,6 @@
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Theme</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Theme Overrides</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">GENERAL</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">Avatar Server</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Check for updates on startup</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Language</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">History Commits</x:String>

View file

@ -383,7 +383,6 @@
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Tema</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Sobrescrever Tema</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">GERAL</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">Servidor de Avatar</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Verificar atualizações na inicialização</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Idioma</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">Commits do Histórico</x:String>

View file

@ -383,7 +383,6 @@
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">主题</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">主题自定义</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">通用配置</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">头像服务</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">启动时检测软件更新</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">显示语言</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">最大历史提交数</x:String>

View file

@ -383,7 +383,6 @@
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">主題</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">主題自訂</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">通用配置</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">頭像服務</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">啟動時檢測軟體更新</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">顯示語言</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">最大歷史提交數</x:String>

View file

@ -141,7 +141,7 @@ namespace SourceGit.ViewModels
var last = Pages[0];
if (last.Data is Repository repo)
{
Commands.AutoFetch.RemoveRepository(repo.FullPath);
Models.AutoFetchManager.Instance.RemoveRepository(repo.FullPath);
repo.Close();
last.Node = new RepositoryNode() { Id = Guid.NewGuid().ToString() };
@ -245,7 +245,7 @@ namespace SourceGit.ViewModels
};
repo.Open();
Commands.AutoFetch.AddRepository(repo.FullPath);
Models.AutoFetchManager.Instance.AddRepository(repo.FullPath);
if (page == null)
{
@ -371,7 +371,7 @@ namespace SourceGit.ViewModels
{
if (page.Data is Repository repo)
{
Commands.AutoFetch.RemoveRepository(repo.FullPath);
Models.AutoFetchManager.Instance.RemoveRepository(repo.FullPath);
repo.Close();
}

View file

@ -128,19 +128,6 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _layout, value);
}
public string AvatarServer
{
get => Models.AvatarManager.SelectedServer;
set
{
if (Models.AvatarManager.SelectedServer != value)
{
Models.AvatarManager.SelectedServer = value;
OnPropertyChanged();
}
}
}
public int MaxHistoryCommits
{
get => _maxHistoryCommits;
@ -262,11 +249,9 @@ namespace SourceGit.ViewModels
set
{
if (Native.OS.SetShell(value))
{
OnPropertyChanged();
}
}
}
public string GitDefaultCloneDir
{
@ -276,12 +261,12 @@ namespace SourceGit.ViewModels
public bool GitAutoFetch
{
get => Commands.AutoFetch.IsEnabled;
get => Models.AutoFetchManager.Instance.IsEnabled;
set
{
if (Commands.AutoFetch.IsEnabled != value)
if (Models.AutoFetchManager.Instance.IsEnabled != value)
{
Commands.AutoFetch.IsEnabled = value;
Models.AutoFetchManager.Instance.IsEnabled = value;
OnPropertyChanged();
}
}
@ -289,15 +274,15 @@ namespace SourceGit.ViewModels
public int? GitAutoFetchInterval
{
get => Commands.AutoFetch.Interval;
get => Models.AutoFetchManager.Instance.Interval;
set
{
if (value is null or < 1)
if (value is null || value < 1)
return;
if (Commands.AutoFetch.Interval != value)
if (Models.AutoFetchManager.Instance.Interval != value)
{
Commands.AutoFetch.Interval = (int)value;
Models.AutoFetchManager.Instance.Interval = (int)value;
OnPropertyChanged();
}
}
@ -336,7 +321,7 @@ namespace SourceGit.ViewModels
{
get;
set;
} = new List<string>();
} = [];
public int LastActiveTabIdx
{

View file

@ -39,7 +39,7 @@ namespace SourceGit.Views
refetch.Click += (_, _) =>
{
if (User != null)
Models.AvatarManager.Request(User.Email, true);
Models.AvatarManager.Instance.Request(User.Email, true);
};
ContextMenu = new ContextMenu();
@ -54,7 +54,7 @@ namespace SourceGit.Views
return;
var corner = (float)Math.Max(2, Bounds.Width / 16);
var img = Models.AvatarManager.Request(User.Email, false);
var img = Models.AvatarManager.Instance.Request(User.Email, false);
if (img != null)
{
var rect = new Rect(0, 0, Bounds.Width, Bounds.Height);
@ -72,21 +72,19 @@ namespace SourceGit.Views
public void OnAvatarResourceChanged(string email)
{
if (User.Email.Equals(email, StringComparison.Ordinal))
{
InvalidateVisual();
}
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
Models.AvatarManager.Subscribe(this);
Models.AvatarManager.Instance.Subscribe(this);
}
protected override void OnUnloaded(RoutedEventArgs e)
{
base.OnUnloaded(e);
Models.AvatarManager.Unsubscribe(this);
Models.AvatarManager.Instance.Unsubscribe(this);
}
private static void OnUserPropertyChanged(Avatar avatar, AvaloniaPropertyChangedEventArgs e)

View file

@ -57,7 +57,7 @@
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.General}"/>
</TabItem.Header>
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32" ColumnDefinitions="Auto,*">
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.Locale}"
HorizontalAlignment="Right"
@ -71,25 +71,10 @@
SelectedItem="{Binding Locale, Mode=TwoWay, Converter={x:Static c:StringConverters.ToLocale}}"/>
<TextBlock Grid.Row="1" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.AvatarServer}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<ComboBox Grid.Row="1" Grid.Column="1"
MinHeight="28"
Padding="8,0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding AvatarServer, Mode=TwoWay}">
<ComboBox.Items>
<sys:String>https://www.gravatar.com/avatar/</sys:String>
<sys:String>https://cravatar.cn/avatar/</sys:String>
</ComboBox.Items>
</ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.VisibleDiffContextLines}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<NumericUpDown Grid.Row="2" Grid.Column="1"
<NumericUpDown Grid.Row="1" Grid.Column="1"
Minimum="4" Maximum="10000" Increment="1"
Height="28"
Padding="4"
@ -98,11 +83,11 @@
CornerRadius="3"
Value="{Binding DiffViewVisualLineNumbers, Mode=TwoWay}"/>
<TextBlock Grid.Row="3" Grid.Column="0"
<TextBlock Grid.Row="2" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.SubjectGuideLength}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<NumericUpDown Grid.Row="3" Grid.Column="1"
<NumericUpDown Grid.Row="2" Grid.Column="1"
Minimum="50" Maximum="1000" Increment="1"
Height="28"
Padding="4"
@ -111,11 +96,11 @@
CornerRadius="3"
Value="{Binding SubjectGuideLength, Mode=TwoWay}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
<TextBlock Grid.Row="3" Grid.Column="0"
Text="{DynamicResource Text.Preference.General.MaxHistoryCommits}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<Grid Grid.Row="4" Grid.Column="1" ColumnDefinitions="*,64">
<Grid Grid.Row="3" Grid.Column="1" ColumnDefinitions="*,64">
<Slider Grid.Column="0"
Minimum="20000" Maximum="100000"
TickPlacement="BottomRight" TickFrequency="5000"
@ -130,16 +115,16 @@
Text="{Binding MaxHistoryCommits}"/>
</Grid>
<CheckBox Grid.Row="5" Grid.Column="1"
<CheckBox Grid.Row="4" Grid.Column="1"
Content="{DynamicResource Text.Preference.General.RestoreTabs}"
IsChecked="{Binding RestoreTabs, Mode=TwoWay}"/>
<CheckBox Grid.Row="6" Grid.Column="1"
<CheckBox Grid.Row="5" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preference.General.UseFixedTabWidth}"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFixedTabWidth, Mode=TwoWay}"/>
<CheckBox Grid.Row="7" Grid.Column="1"
<CheckBox Grid.Row="6" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preference.General.Check4UpdatesOnStartup}"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=Check4UpdatesOnStartup, Mode=TwoWay}"/>