From b3ed9b6cab0913e7d6605cd92e4f022b6f89a1c1 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sat, 27 Jul 2024 23:37:41 -0500 Subject: [PATCH] Visually indicate to users that they should click Refresh --- ConsoleUI/ModListScreen.cs | 29 +++++++-------------- ConsoleUI/Properties/Resources.resx | 4 +-- Core/Repositories/RepositoryDataManager.cs | 23 ++++++++++++++++ GUI/Controls/ManageMods.cs | 24 +++++++++++++++++ GUI/Properties/EmbeddedImages.cs | 4 ++- GUI/Properties/Resources.resx | 6 +++++ GUI/Resources/refreshStale.png | Bin 0 -> 1974 bytes GUI/Resources/refreshVeryStale.png | Bin 0 -> 1963 bytes 8 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 GUI/Resources/refreshStale.png create mode 100644 GUI/Resources/refreshVeryStale.png 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 0000000000000000000000000000000000000000..4ecd94f83e228b0afffc6f83b2d328944f8bbf71 GIT binary patch literal 1974 zcmV;n2TAyeP)3&28HS&G?=1Fsi&t!n@q*0~JS=t^pdebYw1ldLB!xx$bIK-%}+AF=f=bSlb z-sgVbx#xcHKfa{@)E~KA>G6tg`H7HN0XTsazys7sf)5Of{*KG#_Wf4?cDy`(4T#-y z=024sg(=ok9L8ydE3s;L(yxMf7g8HOPSk?XDoSp*%MF3 z&n)gIG43LYbRMB!K#^-nO6Vd#KZSzAH>b|}&OA5*>(9Rq<_3`BNBvz0 z14HcFk-O@Av2*dkC5z_}YyBI-?E3Ot%b5hV?{eZ~1vOX7*KON*`@PWto;~Aq7A9SJ z`$vTgswSE#?#o$9jKb5qs?3e4YkHo0?!}DkjPeJXM?-YFm-43ryL(N=Ug&bQ_TQg^ zngiY4IjiH`UVVO1PCV{_&S2QY8&NM+)SKjS08s{T zKftPwG8G%AcdjiW(P2S11!j~KpsmYCinS{)EzN-teGla?gopHW2Ot%X4j{8-V_6=_ z$uaZ?b%ZIf$si-p4HFrtZRy7+>6Eh8;~8W0zsa=QjYUca#X|8`dt#qsOnK8G=Ns z!l4hFIDW2e&z^&ylWdbvgE~I1zd=IOxZMFXEtzmwj9HqNjN2UmMBPY$ZynCh&4?{a zPKm`EGO)?e(Cp?^<;}hP^?RFdw7Yk`bF_~3&H)hm8IX=OHMd}xc?5&zm}QuGG&Q#X z(gz~}EWxscGse*$(9v~+I0gRrehujQuK@UP?bdTuHx}KOC{idQFsIEG8r`wZ{ zs%H=gnqMhZPsiPtPj%I?UQ@BXHxfYA78PVC;S1_0Kzn;XgM)!Xe7vrCc+My5PxcLX zQz`%HWtYqC6B3*2YHN_KE70|E1Ow)mO4r9BTUSz7TZ@p`Di z(9m)R4E5xFyZGZDdxo0ZcicXBwYJ< zmDSYOeB#=^uB! zWoCBvG9F2b!D^MzG=bL_qVakg7ps2l=?qQXf2eN9X};NM;j~L1`7M~YyLZL2i&HAw z0~BV&(tPd`op(Hs@sFQfe3*K`lvybWih&eENCRE|i3#oMY@@Poo}TE4r}f%R$;cWX z&ym%}$Osr}mk5PZWC5yb5@%Py(9*|TkktJq$dT1=0&flv&fT@)@sh%QdRDG%i&L$| zSvIcMG&B#HkK6;g+8}~{VioWOG{&VRfNIPdll!W}ihQF{r}Wh=lvqE9F4~s>RZTkC}^sIN4JrillnN$tuKBhfExNn zZcaK`Q;H~lbm9i4|Kcs1EWbYOYcpu;4q%la-DYyN_Bs^Sy+1m@XGel&DELP3Rx7Cv zD>r*WOrKMNMHsu8w&}rrj|gd7 zIda4p9RM)H+TOD#&Qp+Rf?;yaZLqX#5ms3~%A^;5II4NU#)(M@cBdaZ?*1^fH+@Kx zW^rxMMt5I`1e-#(HOQsP8fa{NXE26#&17^}JZ@|;{?Wx6+>GXv0npR4j`GmcF zD+evp=966Nqp#}P@S5bu);MPteP_UuOz)tIs+o8LI>E4k!>TYdJBG^R<@o*nAK*D~ z`N04Ho~qA$6eSC9@7&3c7SZ7e(;bi~oIaKLC3%zDn%lo0)@Qy;u*wS{CzfuSTHBlhX0}RfJ)VP$;BRG@}?KeG9qk@<;PhqKaK9(zgIsF?i{ePtQ?0 z8=DrCOwQnX1HUIzs07*qo IM6N<$f@Q|ZwEzGB literal 0 HcmV?d00001 diff --git a/GUI/Resources/refreshVeryStale.png b/GUI/Resources/refreshVeryStale.png new file mode 100644 index 0000000000000000000000000000000000000000..b98d46d8262dc37889b7f781a37c8806a7fcce24 GIT binary patch literal 1963 zcmV;c2UPfpP)@~#DkQ9_k6dFP$H$6B)AwjgM#6L+0Y6AkPP8$>y z3TYJtO_Nrr91>WN5(U!~aaC$K42Iw}U}KCIV;6Sq^|8m>eb@Altc{JgV5C&-NMF5~ zc{B6-%x{kOd+KAU zk}Uk_*B8vSyPxlT`K@2(PEI+eSM^-r=v`to_w#dvHhfJ zUXT+ujOj;@*ZH{L6T>tF`XI>&Iyx2Ht*1nBVIed&KG~*eY^1ob5J`wifZ+g(kB+t2 zIsKaz#iS=&(M^FlNEX0*FGO~zJ;jxj1f88v2H^MOayS6W5(CJrf332B%*-Tu6dhp- z>@vs*bi+gjJS{!g^jNY}k{}X!G5|%vDa!!a!vQJ}FD%VXdEH%Jj41fvFa85*1Y zoT|OEgTMZG-R(C2mX8iM(ALolLO&xU>C@(x7POKQ6d^_|t)zsTEiIUmbYv)iRjGWg zd<;Di9bGp_vA~}{sRv#EJpiAr*zm>0+uzx7@CrnOyL3(4U3civeP3A_YHltOc&uXM z$K!LmsXKJ2OV_l$LjhE6-o)`4gcKbM(AL&NUteTDpRa5lX!G~gC%b!t*&P4*l`04c zA=Y_39>goJpgjgc%gu#Vt7!0ekc3!U1w8=Pfx!)Ja#E5FU(_HY$%3clJ{aoBVY~SA zp9T7}n+#*$=9-!!0aYc-mXYM~pfog~`~8@Tg6wo+b-8esmJ)NjslRlI>oql18x3R6 zfa4$RKg}gsWTL7@hQq?$_5i4A^W#p{MqPjVoufyauh-Q4_NgUH#^x?vO1i^=O_I=r zAQX+#awbz7i)eEF`u3wEAH>9BITN`DxDv&}u!wnZq)anfO*KEvVYc#@V@5-`Gs`663+WH{b!a zQ7B=Y76?T&#*9t_)tE6N>x&OsYU_<~nJqsrl<}ml!$?sxB-UVRBre73J7(F*&BoT_J zWIJWrTRXrsP9_FOZu+$GS_@;7EoiDiK(~{hm;E&E4R1acK#hJoKhMRu$;FgSo3Mt` zw|-}r<@ct3vmCE4f=vRK-Q>FG78Esnk{IA@wPHq5Ni3y8^2)wlQ_> zqdj4Yg|>_=YMXn>o>E3Z!MNAwF8CIWeuJ>0GcMghyXP7rs%@aU+DHrlm@#d~KTe!u zV!8>2$qm23g35W=Wce_Y-dL5;_{7&HWTZLVVI25-VtDR^kS5LGMxPyDca$`{h4D6p z%eD2;)VlTI?n5J@?(@B7#*APj(Ea?BvJyH1F@mOz3U?u~VE2oC)~R#IoEf6~;*Ehm znX{I=rx$;(*P2OJpNguP1S2|1%plojVfy$aYL6W!91g!sVE2_r0{{dro()l)DS}C51wrdgFMU0|8|eB&d;I)aKwmCr zxCn+-gkcU*G^$fvUIvoBp8S=CiELR01-nwbYz1th@A9dTo@eQ3x;eL^B!^qI=kZ*) z@F&q(_eTuB9Y|%&H|FIQOkOtg*_k-3GI^t&LzI)@pfEp&QK@#k*P0Cxsy{U-5|;)W z?!Oi7fD=fKD~06gPmxmiLzFxJBvkih98!)yC9)T-mh4&cANl`hh7Ov&-kn4*U+@1i z0|p?Zhi2~pl7Y0izJmCw-WG@K2e1x;7k~-qgPt0o#tnvnAkZ7n$Kq{-^w56jpIQG> xSO=BmL6RQw-e3rGP~I3MEpD{GEcky&{|0m+!%u^!_CNpt002ovPDHLkV1jEk%Q^r6 literal 0 HcmV?d00001