diff --git a/ConsoleUI/ModListScreen.cs b/ConsoleUI/ModListScreen.cs index f6b128a99..e633f34d2 100644 --- a/ConsoleUI/ModListScreen.cs +++ b/ConsoleUI/ModListScreen.cs @@ -265,17 +265,16 @@ public ModListScreen(GameInstanceManager mgr, RepositoryDataManager repoData, Re AddObject(new ConsoleLabel( -searchWidth, -1, -2, () => { - int days = daysSinceUpdated(registryFilePath()); + var days = Math.Round(timeSinceUpdate.TotalDays); return days < 1 ? "" : days == 1 ? string.Format(Properties.Resources.ModListUpdatedDayAgo, days) : string.Format(Properties.Resources.ModListUpdatedDaysAgo, days); }, null, (ConsoleTheme th) => { - int daysSince = daysSinceUpdated(registryFilePath()); - return daysSince < daysTillStale ? th.RegistryUpToDate - : daysSince < daystillVeryStale ? th.RegistryStale - : th.RegistryVeryStale; + return timeSinceUpdate < RepositoryDataManager.TimeTillStale ? th.RegistryUpToDate + : timeSinceUpdate < RepositoryDataManager.TimeTillVeryStale ? th.RegistryStale + : th.RegistryVeryStale; } )); @@ -442,16 +441,6 @@ private bool ViewSuggestions(ConsoleTheme theme) return true; } - private string registryFilePath() - { - return Path.Combine(manager.CurrentInstance.CkanDir(), "registry.json"); - } - - private int daysSinceUpdated(string filename) - { - return (DateTime.Now - File.GetLastWriteTime(filename)).Days; - } - private bool PlayGame() => PlayGame(manager.CurrentInstance .game @@ -591,6 +580,7 @@ private void RefreshList(ConsoleTheme theme) private List GetAllMods(ConsoleTheme theme, bool force = false) { + timeSinceUpdate = repoData.LastUpdate(registry.Repositories.Values); ScanForMods(); if (allMods == null || force) { if (!registry?.HasAnyAvailable() ?? false) @@ -722,12 +712,13 @@ private long totalInstalledDownloadSize() return total; } - private readonly GameInstanceManager manager; - private RegistryManager regMgr; - private Registry registry; + private readonly GameInstanceManager manager; + private RegistryManager regMgr; + private Registry registry; private readonly RepositoryDataManager repoData; private Dictionary> upgradeableGroups; private readonly bool debug; + private TimeSpan timeSinceUpdate = TimeSpan.Zero; private readonly ConsoleField searchBox; private readonly ConsoleListBox moduleList; @@ -739,8 +730,6 @@ private long totalInstalledDownloadSize() Properties.Resources.ModListSearchFocusedGhostText.Length, Properties.Resources.ModListSearchUnfocusedGhostText.Length )); - private const int daysTillStale = 7; - private const int daystillVeryStale = 30; private static readonly string unavailable = "!"; private static readonly string notinstalled = " "; diff --git a/ConsoleUI/Properties/Resources.resx b/ConsoleUI/Properties/Resources.resx index 5e37fb506..7944f2cab 100644 --- a/ConsoleUI/Properties/Resources.resx +++ b/ConsoleUI/Properties/Resources.resx @@ -327,8 +327,8 @@ If you uninstall it, CKAN will not be able to re-install it. Mark auto-installed Mark user-selected Apply changes - Updated at least {0} day ago - Updated at least {0} days ago + Updated {0} day ago + Updated {0} days ago Play game Launch the game Sort... diff --git a/Core/Repositories/RepositoryDataManager.cs b/Core/Repositories/RepositoryDataManager.cs index b264c2a93..e4d14be9b 100644 --- a/Core/Repositories/RepositoryDataManager.cs +++ b/Core/Repositories/RepositoryDataManager.cs @@ -107,6 +107,17 @@ public void Prepopulate(List repos, IProgress percentProgress) } } + public TimeSpan LastUpdate(IEnumerable repos) + => repos.Distinct().Where(r => repoDataStale(r)) + .Select(r => RepoUpdateTimestamp(r)) + .OfType() + .Select(dt => DateTime.Now - dt) + .DefaultIfEmpty(TimeSpan.Zero) + .Min(); + + public static readonly TimeSpan TimeTillStale = TimeSpan.FromDays(3); + public static readonly TimeSpan TimeTillVeryStale = TimeSpan.FromDays(14); + /// /// Values to describe the result of an attempted repository update. /// Failure is actually handled by throwing exceptions, so I'm not sure we need that. @@ -150,6 +161,11 @@ public UpdateResult Update(Repository[] repos, .ToArray(); if (toUpdate.Length < 1) { + // Update timestamp for already up to date repos + foreach (var f in repos.Select(GetRepoDataPath)) + { + File.SetLastWriteTimeUtc(f, DateTime.UtcNow); + } user.RaiseProgress(Properties.Resources.NetRepoAlreadyUpToDate, 100); user.RaiseMessage(Properties.Resources.NetRepoNoChanges); return UpdateResult.NoChanges; @@ -307,6 +323,13 @@ private IEnumerable GetRepoDatas(IEnumerable repos) .Where(data => data != null) ?? Enumerable.Empty(); + private DateTime? RepoUpdateTimestamp(Repository repo) + => FileTimestamp(GetRepoDataPath(repo)); + + private static DateTime? FileTimestamp(string path) + => File.Exists(path) ? (DateTime?)File.GetLastWriteTime(path) + : null; + private string etagsPath => Path.Combine(reposDir, "etags.json"); private Dictionary etags = new Dictionary(); diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index 55d428b06..357e9d6d5 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -1455,6 +1455,30 @@ private bool _UpdateModsList(Dictionary old_modules = null) UpdateHiddenTagsAndLabels(); + var timeSinceUpdate = guiConfig.RefreshOnStartup ? TimeSpan.Zero + : repoData.LastUpdate(registry.Repositories.Values); + Util.Invoke(this, () => + { + if (timeSinceUpdate < RepositoryDataManager.TimeTillStale) + { + RefreshToolButton.Image = EmbeddedImages.refresh; + RefreshToolButton.ToolTipText = new SingleAssemblyComponentResourceManager(typeof(ManageMods)) + .GetString($"{RefreshToolButton.Name}.ToolTipText"); + } + else if (timeSinceUpdate < RepositoryDataManager.TimeTillVeryStale) + { + RefreshToolButton.Image = EmbeddedImages.refreshStale; + RefreshToolButton.ToolTipText = string.Format(Properties.Resources.ManageModsRefreshStaleToolTip, + Math.Round(timeSinceUpdate.TotalDays)); + } + else + { + RefreshToolButton.Image = EmbeddedImages.refreshVeryStale; + RefreshToolButton.ToolTipText = string.Format(Properties.Resources.ManageModsRefreshVeryStaleToolTip, + Math.Round(timeSinceUpdate.TotalDays)); + } + }); + ClearStatusBar?.Invoke(); Util.Invoke(this, () => ModGrid.Focus()); return true; diff --git a/GUI/Properties/EmbeddedImages.cs b/GUI/Properties/EmbeddedImages.cs index 428714f98..c4875a6c5 100644 --- a/GUI/Properties/EmbeddedImages.cs +++ b/GUI/Properties/EmbeddedImages.cs @@ -13,7 +13,7 @@ namespace CKAN.GUI #if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] #endif - public class EmbeddedImages + public static class EmbeddedImages { public static readonly Icon AppIcon = new Icon( Assembly.GetExecutingAssembly() @@ -36,6 +36,8 @@ public class EmbeddedImages public static Bitmap info => get("info"); public static Bitmap ksp => get("ksp"); public static Bitmap refresh => get("refresh"); + public static Bitmap refreshStale => get("refreshStale"); + public static Bitmap refreshVeryStale => get("refreshVeryStale"); public static Bitmap resetCollapse => get("resetCollapse"); public static Bitmap search => get("search"); public static Bitmap settings => get("settings"); diff --git a/GUI/Properties/Resources.resx b/GUI/Properties/Resources.resx index 81dc61781..5644a4ddf 100644 --- a/GUI/Properties/Resources.resx +++ b/GUI/Properties/Resources.resx @@ -453,6 +453,12 @@ Are you sure you want to skip this change? Hidden labels: Hidden tags: Hidden labels and tags: + Your mod list was last updated {0} days ago. +Click Refresh regularly to keep it up to date. +Or choose "Update repositories on launch" in the settings. + Your mod list was last updated {0} days ago! +Click Refresh to update! +Or choose "Update repositories on launch" in the settings. Total play time: {0} hours None Install diff --git a/GUI/Resources/refreshStale.png b/GUI/Resources/refreshStale.png new file mode 100644 index 000000000..4ecd94f83 Binary files /dev/null and b/GUI/Resources/refreshStale.png differ diff --git a/GUI/Resources/refreshVeryStale.png b/GUI/Resources/refreshVeryStale.png new file mode 100644 index 000000000..b98d46d82 Binary files /dev/null and b/GUI/Resources/refreshVeryStale.png differ