From bc13c816261cf858b2b18e383e166a6c596675e3 Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 6 Jul 2023 16:24:25 +0200 Subject: [PATCH 001/101] Set version to '1.26-alpha' --- version.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.json b/version.json index b5c6fe26..0237c8ad 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { - "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.25-alpha", + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", + "version": "1.26-alpha", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/v\\d+(?:\\.\\d+)?$", From d3b1fda05f0a8d9747f9eec750c538e79e348e22 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 20:24:47 +0100 Subject: [PATCH 002/101] Bump automapper --- src/IronyModManager.DI/IronyModManager.DI.csproj | 2 +- src/IronyModManager.Models/IronyModManager.Models.csproj | 2 +- src/IronyModManager.Services/IronyModManager.Services.csproj | 2 +- src/IronyModManager.Shared/IronyModManager.Shared.csproj | 2 +- src/IronyModManager.Storage/IronyModManager.Storage.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/IronyModManager.DI/IronyModManager.DI.csproj b/src/IronyModManager.DI/IronyModManager.DI.csproj index 0707df5f..4676696b 100644 --- a/src/IronyModManager.DI/IronyModManager.DI.csproj +++ b/src/IronyModManager.DI/IronyModManager.DI.csproj @@ -44,7 +44,7 @@ - + diff --git a/src/IronyModManager.Models/IronyModManager.Models.csproj b/src/IronyModManager.Models/IronyModManager.Models.csproj index ce9b4987..7f5d2e12 100644 --- a/src/IronyModManager.Models/IronyModManager.Models.csproj +++ b/src/IronyModManager.Models/IronyModManager.Models.csproj @@ -46,7 +46,7 @@ - + all diff --git a/src/IronyModManager.Services/IronyModManager.Services.csproj b/src/IronyModManager.Services/IronyModManager.Services.csproj index e0f95c0f..4dd49eb6 100644 --- a/src/IronyModManager.Services/IronyModManager.Services.csproj +++ b/src/IronyModManager.Services/IronyModManager.Services.csproj @@ -45,7 +45,7 @@ - + diff --git a/src/IronyModManager.Shared/IronyModManager.Shared.csproj b/src/IronyModManager.Shared/IronyModManager.Shared.csproj index 47a8e496..2a808414 100644 --- a/src/IronyModManager.Shared/IronyModManager.Shared.csproj +++ b/src/IronyModManager.Shared/IronyModManager.Shared.csproj @@ -40,7 +40,7 @@ - + diff --git a/src/IronyModManager.Storage/IronyModManager.Storage.csproj b/src/IronyModManager.Storage/IronyModManager.Storage.csproj index 503fc035..29c7e882 100644 --- a/src/IronyModManager.Storage/IronyModManager.Storage.csproj +++ b/src/IronyModManager.Storage/IronyModManager.Storage.csproj @@ -47,7 +47,7 @@ - + From a7d01f87cf7f6ff6ade9495f6f43c62884a25ab0 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 20:28:03 +0100 Subject: [PATCH 003/101] Upgrade nuget --- src/IronyModManager.Storage/IronyModManager.Storage.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager.Storage/IronyModManager.Storage.csproj b/src/IronyModManager.Storage/IronyModManager.Storage.csproj index 29c7e882..59a8b55c 100644 --- a/src/IronyModManager.Storage/IronyModManager.Storage.csproj +++ b/src/IronyModManager.Storage/IronyModManager.Storage.csproj @@ -48,7 +48,7 @@ - + From 8ffd2a816fe64f9bbfee6eff7e11a48ad808e375 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 20:36:32 +0100 Subject: [PATCH 004/101] Bump difflpex --- src/IronyModManager.Tests/IronyModManager.Tests.csproj | 4 ++-- src/IronyModManager/IronyModManager.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/IronyModManager.Tests/IronyModManager.Tests.csproj b/src/IronyModManager.Tests/IronyModManager.Tests.csproj index 30462ba5..65d5aca0 100644 --- a/src/IronyModManager.Tests/IronyModManager.Tests.csproj +++ b/src/IronyModManager.Tests/IronyModManager.Tests.csproj @@ -27,7 +27,7 @@ - + @@ -37,7 +37,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index 3d320e06..7656ab7c 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -129,7 +129,7 @@ - + From 162e5f298a665950407efae1e4309b4c938893ab Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 20:42:13 +0100 Subject: [PATCH 005/101] Bump test sdk Bump xunit Bump moq --- .../IronyModManager.IO.Tests.csproj | 10 +++++----- .../IronyModManager.Localization.Tests.csproj | 10 +++++----- .../IronyModManager.Model.Tests.csproj | 10 +++++----- .../IronyModManager.Parser.Tests.csproj | 10 +++++----- .../IronyModManager.Services.Tests.csproj | 10 +++++----- .../IronyModManager.Storage.Tests.csproj | 10 +++++----- .../IronyModManager.Tests.Common.csproj | 8 ++++---- src/IronyModManager.Tests/IronyModManager.Tests.csproj | 8 ++++---- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj b/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj index b46cfc67..0acb6759 100644 --- a/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj +++ b/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj @@ -27,17 +27,17 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj b/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj index 8fb9fe46..e76c44f8 100644 --- a/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj +++ b/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj @@ -27,16 +27,16 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj b/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj index 650431e4..8edbd9f3 100644 --- a/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj +++ b/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj @@ -26,16 +26,16 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj b/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj index 85ebd037..51054880 100644 --- a/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj +++ b/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj @@ -43,20 +43,20 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj b/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj index 0d535aae..c47809fd 100644 --- a/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj +++ b/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj @@ -43,16 +43,16 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj b/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj index edfc50ec..7d71ac99 100644 --- a/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj +++ b/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj @@ -43,16 +43,16 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj b/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj index 1cfb1b8c..ff05204f 100644 --- a/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj +++ b/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj @@ -44,16 +44,16 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Tests/IronyModManager.Tests.csproj b/src/IronyModManager.Tests/IronyModManager.Tests.csproj index 65d5aca0..37a7ef2a 100644 --- a/src/IronyModManager.Tests/IronyModManager.Tests.csproj +++ b/src/IronyModManager.Tests/IronyModManager.Tests.csproj @@ -28,15 +28,15 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From fbc9ec5fa6500ac856970cce55d37b3e6dd7d775 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 21:47:52 +0100 Subject: [PATCH 006/101] I really tried few things to make xunit runner vs work, but to no avail. Needs mor time than I'm willing to spend. --- Directory.Build.props | 2 +- Test.runsettings | 2 +- .../IronyModManager.Model.Tests.csproj | 5 ----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3556203c..913dac82 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,7 +27,7 @@ [0.10.22] [0.10.12.2] [2.1.16] - + [2.4.5] [1.0.4] diff --git a/Test.runsettings b/Test.runsettings index 310535c6..ed3765e7 100644 --- a/Test.runsettings +++ b/Test.runsettings @@ -4,7 +4,7 @@ 2 x64 .\TestResults - .NETCoreApp,Version=v3.1 + net7 diff --git a/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj b/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj index 8edbd9f3..d513f22f 100644 --- a/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj +++ b/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj @@ -57,11 +57,6 @@ True - - - True - - PreserveNewest From fb33a3602b2c11be48a90a2bc57c4b60f2a0a446 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 21:56:25 +0100 Subject: [PATCH 007/101] Bump fsharp core --- src/IronyModManager.Parser/IronyModManager.Parser.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager.Parser/IronyModManager.Parser.csproj b/src/IronyModManager.Parser/IronyModManager.Parser.csproj index 40d1ed8e..6ac8655a 100644 --- a/src/IronyModManager.Parser/IronyModManager.Parser.csproj +++ b/src/IronyModManager.Parser/IronyModManager.Parser.csproj @@ -41,7 +41,7 @@ - + all From b98484a185158e8de2fcf5bb00c7d5e54ea4956d Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:16:19 +0100 Subject: [PATCH 008/101] Bump cwtools (compiled from forked source) --- .../IronyModManager.Parser.Common.csproj | 2 +- src/IronyModManager.Parser/IronyModManager.Parser.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IronyModManager.Parser.Common/IronyModManager.Parser.Common.csproj b/src/IronyModManager.Parser.Common/IronyModManager.Parser.Common.csproj index 6237c9e8..1f9d4a07 100644 --- a/src/IronyModManager.Parser.Common/IronyModManager.Parser.Common.csproj +++ b/src/IronyModManager.Parser.Common/IronyModManager.Parser.Common.csproj @@ -40,7 +40,7 @@ - + all diff --git a/src/IronyModManager.Parser/IronyModManager.Parser.csproj b/src/IronyModManager.Parser/IronyModManager.Parser.csproj index 6ac8655a..c8c98de1 100644 --- a/src/IronyModManager.Parser/IronyModManager.Parser.csproj +++ b/src/IronyModManager.Parser/IronyModManager.Parser.csproj @@ -40,7 +40,7 @@ - + From 1774e71e2fd4fbf518d6bfcafd1df9095d862d69 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:23:49 +0100 Subject: [PATCH 009/101] Add recompiled latest version --- References/Direct/LiteDB.dll | Bin 488960 -> 488960 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/References/Direct/LiteDB.dll b/References/Direct/LiteDB.dll index daa25ae785d42ec14aea028596d073cc3ad41c15..6ee13bbfadd4e6dd231344a5c86e80e8b85d6cf3 100644 GIT binary patch literal 488960 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

oT>4rg-eU?Lb+&8D1Su|1G$!gchaR#JH|^ zM1xT--SuW^`@=La~*v(kp@!pXIVxnI-%*vPx>=uDRlvrUT(;-CGQxhfq^iyy zP1L+l@U=LmwYm=#gMz1r=zta>7Jsc@p7&LGT6G-I)hW7*tuCnj5;F0Cs;+lO~vN#EmD5PiAZIWU)BlvD)_#2Rxw4OHqhpH+8(w?u=X7 z#JR;Zc;wLT#4##5qT#?x{XnQ+Qh}PPI!v{IrUdRs+z#4E64U7x&9^ZI6e2c40~Aj& zMmy}5Le=QfL^Pr(jbfV|i8}eC^dnM3>s(#moHYEb|L`|T)A1huMS%+q+5FHDYCxr5 zYG9{46Vl-GRu$wIp-!y`{b~ioEKF(P!Uz1342Sjj+!I4If1ByktY+?@YHr{R+;RGI;B?#@p=vI}<~@^vY+iA|<6FLm zI1bR!YAyF?D1&a%43uBlVmjM>48Hl}15*>wk_g*M%iV|J4cmISw!;QVag^oWhKlK^ zv7kiEv2qhc)1BqszNsh5?Vr2*6WdH>8+z`bVGqnpcOVLmus#IV&_f9GACi_c3d$!l zgQqe1>>x{8h+EDJ%42}ux^8EH|8ByU)lh7=p`A=>q$E8l*IjF&e?0~6gCtO4dEc)s zDZV(^%RDY9D^rV_~Uf-^cMBe9IQbb)o4UW1p zM&`!DE(>d*{|;R8&^IPUq_*>1qHhk)uSWP?I)D7Wl)tds_xr!SlUFAO&zCk#^Hf3S z8Gf(B{V?wIyB+ECgfLVkMh?Oc?HFmp{aS?Mv~z?y75lE>e)wUc7d`aw9q5|_k_f6UvNs%?ES}D!l79VKMxmbBeca^6X)0unk-2753CNE0EP?w1wzMouWd1WTr6vm zvEG;w!-qlvU1XfMugp1{rh61Zp&V;cV))1~wG+uDz02x`51z!}8t?9{h7Yb|usRAm zT&X@qGv;c&{k|e=FCthsi|W={QFU3|JlE=VOMah0rx)#PVeUOwlI;jhVoKa|Y9&AD z1J32@ymK*gh8g%>VFCG!^p;d)uV-uer@bFf9zNRWVUua_mVZSuAD~m_>b-X=8n@>q z_?H!5vOCHf`U*bRlTV}fW+k6v>`wozSN9c3 zLcM4t*W_J0efXM4Gq_n>^DzzK*Y962B6BU?(u(@^gLw!(H`;q<%J9r_&OMSF0~>3c z)7@uTn$WQ7xdg$meTJ_9eukEVR+q6Ul=aU{4xeqXYnIW#0+q}#_?V-)J-3;)O7cyWq9L;UP{F4gF7Z1KeA>An$fUBhASyI#l8%6+&|RG< zuE^AWO{RBKk=#2eDV(l36sDg5J<&e9>B|l1oW9tAL#OXyz?|u`0fIt{e1?wtVNhnj z1Ac}qYu5YE{0%|4U)bGon14PJxoKBKP>y==HyY!)Nu9Iex&1r406S;*+=_Vd++m%w z+Rl(ehNj{ z{t$Uj*IBaL@5=HFHwgvA_fKi7HBtwO_IYH(ZjGSg#hHW=CUMkMXXT!9dSkuuUiB=q zJ>vcuaiQxqH@Rzsm76v>0au^BC|Sr`x$V1PS!?d(6kL6<0ZrdVD|b-WNGo^PWJEbg z`|>j^^zrHlpC>Vf0-c~rrfutpwl@~4Wq9MOA8#2ZCVbMSWV7!djDOB+kZDMgP5$v`?uxZ=fYdY zesu(&eMUZJU%gQDaaw~=ToXl2wnC@=;>_|5Ro)fInwa);s}n|SyogFX?*U=yZ%7_;b`Y{A2>Fa5XWCJ*m26jwNtSA{_*7l` z4BrtcTgmw@6}j{|$z@*Ik(GfzorQamOfh2(NOhQ8m6pK1ht9H3B2{HdAtX$IQA-Da zo`6IOAPtV`&n2~>{-gbWMOFs!2^4ItWiD;$SR+U z=p!Vh-9f>+W(&R#zWqN&ar*DWW$=7Z6~}@X8I%@-&@Me8*T-;mUk9~adJ;}NCK#I1 z{fOJHb0F+hi}nqEM^a@rN_!wyu^B{ym8PYLF+PuY@6x`C+APlr-Yr@r^;md--i7tYbeY>df*fZT4};p+Sm{dlhP7Wwpxv zV&JQZ(VKpX+}iGbK(J&fc~;p}dG3Fa0mv(L;BtTEQ{)-no*^5_LW5K4h67!kMU9pI z1zN50_77o|w~Vj!+{Plg_DB*?`0;r#%x`=Jzkh&V2X>b>;r>cFcb|91mWnqTf>HEH zU|M$M0;C1$2lJWAeNEwjnB+bcAK-UzL$oW1k^HznlH=AYwyLTy_W|~^&p;P%8vGnL zl>*EQA&VIALAxhPAF&c>zE$b|y)vwkJw#agGwFQ&zlq55ewFuA?WS%B3nuAabQ^X^ zl@ZL&>KXk&-1!$RJ%WBy6=It?&t?1P!FIz;e_dzN9hv{4aZG~pzhR6 zRn+`{2U8KW??Zi76x#oxRSSKXI=8Pu`hP{8Mzb5L8fE&6%IwF^F2j(sUoa+vieFe( z@lavzHQcACPYg7s8XsU)yAQI`i5=x36QC29&K|Gm?7F&d2v@hGy0z|mosk32S9Q#} z>S(Dw)L#?l>mZZNaghCJQ+Ic`$v#BAGP^ei*_AG$SAstGA>_9m>)J%SEa`inHKnVc}82N^CUu}Q^A5=!CA zI)G5HFBDNeiOb%ghe1@*hm0M&V(i$Z-_vj* zK92pE4S%a{_$!cUs~`Iy$-yh>^iA4cE#TiIRqK8~^740-zyC%pA3-r4%x(@sA6?Xm z4j#i>^&qs*MJ;DQJO}9K{v9mP4rCsKEOZkko+pu9=_y>=R{eu>!1?IWv=XogUTT1~ zt>S#`7U9vJzbFbbW8SNNv7^~;%Z3HkIikRIeAY^Q&T zM3SftPHB!TGlMLEGr;m;iN=!oFRtEESy*60041h2cb0gNG_wA3&vOVoDMPDf4bl}e z5MKRHbRSi{!C%kj8gC9$)|TgcS&ESwz|tD9!zYw+ErD5M|<3I$cHX?(oypBz|7Ltnd#n#ryx%Ull@pL-AGrak7KZi>Q;%hH@WM zr07^Tuwy-ol9Zmq#Tg(y$_$q;b!zGyVM84sM z$UoQz1{mhg3$vZd3i7N6bV(gOlB>Ms#!J;zmG?m4g}H$oVJ8F@5A`fc51ElOQxdAv z)vj;0Tn^LqNf_shso7Em<&}NRI*5kY!<~4lKbJX?d+>PpPsuZfW$9O_up#KRgb12# zAA-Wm=muvktMhqwjMWTr2Qh(J12QYf71p%-tbwZ#U2Gp;oOHIU0aw+hh7K=hb`6=m zD9jk*qK_AbpeQq(;I*WANoW}2o=h4j&kWOCXJ{DWt_F?co`NfPD+@HKa&jzwDb{+H zD^VXU{RRxZ)PuU_(@|CRQ}9YcC}5?OoxfvF#yb^6q7Y%FPlYw%gNK5cMQi$v^isJ7fHtiZH(4EsBUiK=bW)#;d!ElN8uW!Zog5OcKf z^6l$5UB8CB#CnEFE#C4ktGugLJF>ij)|pmghr99=wGxUvjKgR%WL<; z{y;bM@`zDW=6A7+I*&R z!ROy!%?Gb`l3_MtSH_COBcrsP8egLY+UiI|Xe}SbX>e6g)@V2>e z@EYbZI(RK__TY7-U+*vPCA`5(mQf6lT%Kn5v@BpMX(S%kCC+pFQ%TcamNl>raqM1e zfJCxOvB6?fUlugAPcleK@MZ4p-a+0}P$+J9k4m?Q;TvS8BZ0S#EGtZNRb~sXBgqza z*x628|0$y^mVQ^*FT`0T)mKyP^u?5=cDslXD7oe{wRZkpoMhQ+N1fidhyk#%p0wg_ z9qP7S6}KT(P720e!jzsaMRwv&sJb)Bo%uOH|gM!a;*xQb5WZKwQ%fOrF%KFDp z6ba=**H#J}8efx-xa>aoHB-oGY86?mT$k3PTU%Mb>c}e;%AJ3Z{nRX-x9b4J|dxgfNp7z9>?p~GEY#z=U zInEKPm9CrY#y#25D3>3KbR$)M8%Dz;U~;GZwe?nd3q?!f6o_iR)@aatSV++%?RG4q zX#AF2U^Q-V<|0=A+qks2XL33h&mHOcdtAo3*z?w&v+4PcP@WZd?o3a-3I*l2>51&K z9DI-cIP1@CLeiM5d4t+4-e6vdx34wQ&Uv8~Jh1q}o~-3IBSO}lk94d7&O06GU+8Lg zpohs`^$wt|3hFIaS5|Q_<7xVc7zM``bDgfIkzfz7ITCQU1Ak>Ndl~pyhF_|TU+34h zgBX-Y0x6xqc)0w51gA7bETgFWmN-6~L+N3VwEgazU%p9b5H!znTaa_h9gnLrATRab zfc!s*y0f+IddDBbT2_Tz8Nprw+J_O7%3M57D#^?D8r-w^ZI0h0{P^kv4-S#N@!KCi zZhpW3Tm=0kauI$v;Ma2_E<@L2MCuWPVeYG=ujJFmsX;G6>Dv!;ptuUjzU#qsy_ynwS|HOtBUSKq`|`*jWmww z6f+4(UeQuF-0@-}p00M^LoCeiT$o~$M#hUz(cdNG%;X3Cb`*J<`PmNnc{DU`On&-5 zu`qNBX{!-R)R<~*JW;~)9o87(-Sw`|jIsiGWi1p}$|h}$C-6~-wx>Ofo0)cW4>|C( zHAWa-V-F6awm5l=#1;^!(pq=J)LalJSSJ*IR|g5I%hg#JA0g5L+2!? zsdIB@bmu6ixijXpblRChr>er1zQP3M%JvrUvV|kPP@k(SjL4-5$#I2POCg%Y{!&dW zGbL`t+L&CKb)p+h$d>bfQ4J>8-DpzA90$yc}z|EiPOS2SJGFU(cRNk zM7;K7xf`NZLPj?XPvQ(!I@;1%+k@?f2--&aQo6dI<6dSHpO6|8hj(V|JZ9W|cN4$K5ALk%jl2R%3^s*!~zp@$h#sx84l`aN0{PU-yK z%d_w>g2MyJm2;Ia(bi$L=SxkH$SC~XjBd*NExgYS34v}AsX_Uu+Qn%cyb%Nt{%#`O zBjv)M1P$;SNPPlC>XQhne;T=usVTYddvH>0vn+D?x}s;xTEZNGo>f1WY?vK{qLf z6PvWVB7t-_1HI#G%Td~2&1qz#YQpKwl>SK}7s8DzCCfXq=%vcI#xpu%C%QjO|{=A_^x$wM-)PwrglvaV6-zN5)^G%E*WF=c<6a+|HDKCI};j*PA5 zM*)eERc=3eVxh`T@_|7%XQxU9Qg&?A^#`i`bixqO*!SDPU&+X(esh$~g0`RB@xRpX z;-`}ZwyYDRc zf8OVPo_F#|SKU)pr%s(ZwVygw17ukx9G-N@U0wRj%I5JEVVP z*EC!$uKDdh$?pV3<=B&QYK_6s{Q1lmV6d^5wZ=keL2*1*VV`&xg2MekptS>!BD0+r ztb8PqIEftVFW;tSMYa2>xcq34S-(~Mk9E7JvRMT(8h6A=~%>&yW_@(vn!)) z9&X=bNp8n==^81Y+lBc>i9GYs_GF?-&;j#QBsS|_%Ks++Id@_;lgr)A&sMlv7HAgP zyX&v9Lwc!geF97Oo>~ zWYk+cEpS&0+}8yz$p+@5)r3(K==+Yhm6H26@Rp;V}8k&wfMV?jliTA#B04ussx(;OU zCLbEaGLw%h(@8{(uxNYfLvjl7hvm%8#~;DfmY_nzQF;&hf}$-!%94AX$SEqjM9eOE zu${%`^B~53yJRkgT{Bn|jQdPOoLw< zmG#2#_kg{&sMEYcZn1TSie02JE?FMr*k6`Fv!)OM<30}?yR`PQbodnMD~TS+abtT1 zsdqhCmBXTiH6tfO1n;`vPO74v0cM*hDlN-}pW)94q5vd=0UOq*XN?Ev=B;;aHf7$M zK-J;oAM|mIBQ-5!cpAe2dta4$XTs;lw?G=T@==`8+q||u&pVOUJ&tameY}ztGp2Ia zd>K=@%|q;Wy?#ICwUuZbW+|TEYMG1}Fbi@_gm)Tyj>6Shm5X2p(_NlzH-%?H>iFX# z8zI7wp-xbc-c;hNL$qc-h z2e32;zKsZ8uqF7Qw#cOzR&V`L2|os0e3okpTsA-Ob3>G^SNPjP&AHB>h%FUQA*$7) z!{HCx5WBi?PFo3f{rsHVdeZh;T1AdtC;^*;cnMSV7b2oEhB^NHPe64`7esQk8!pRNGfzN|pMftR{`K;(kKmL~e=u@*iJc%Y{0ux7MF@^n| z*ePt3`EA4+m1EcxnS$MZ;xmjbb5+iv`L=@*e~z5J@UNPh=3|k;++WUl$3t+1RyLr5 z{{sZpImDk*6i08DkS%$738kuSuv6{g*ti$I0v^UHpbM(I-9?M$g(neM8=7xhl8s$E z)O4dq|0K)$vLJjfZRP@pqMK}yisyyn#ECy{S?ISOEd$`S63qMz&lv4}s{7P?$ zC98#%hL%ROg-W!d6TJj223iSdb1y5}BIB=CZx6u`A=KVOCH{mmO8|_8m6(IMlx0?d zV%UO&+_nO1EoK^(_%oS!s?I6#gthMCeOB>|NkdB=Gl!O1P4yMWpYH<7wesa>IPJpn z2>2njm4B1r@J5A%m(y$YSc32{pi?my$96B*g)CeIlUXw;bBBEp5?$n_ z&(N+MB@R%tifruVwN<7m#PIT*JN$FRe7H-DLX4R4Al%fgQ75yBCWHjYI+;W^TU(%* z-GzdeVWf5kytbs4lQ20cE2y-9N()@2@m1ifkbKRha6)fwam(V-+`cE%tI{U_%72~p z3^7US#nqqSxAU_I_j|Zm_=osry`B462F_=W){?st7@JACx%dZZJzBGzszCl2`j&Ev zFsFGYUAO6Ec~Q5~I&IY(335<-K)`FOt(g^hm%#Y#=L=T#(vvs`(H=uLfQL)00<9m+ z+a0X`)jW6k%Kwn3L_&E=AVwc&cGJ_5jNx&Aq)`afA9`(xurb?JR^qjW!&EcrXFc2c zoJaqYRBY0bAYumOPSlF7%9w$>{&H@vTks#g6*%x6FVr5bG|I&qh8A*j9;E=1rQyD@0?k2zy^V4?3G5B>6aD#; z_PGwN77kt2SIuuTfsY@%GPrK(?`a~|NH^XN77gxyRfp=wU*MB|^^1bi8SAst&D)1{ z^9%%2VyI))X3>|FOcxQ&lcu-gGl(gDqQ8~~sVdaZduF4*YU7!rWe!5~V++v+q`boE zua{z-`6gI2_If4D%7NTzPNL1FjZ{}3<=k`ja`mC}De3Plm@OYipTm^2ZzDJr--H=z z1D5BsQ`t1H-S_Y2brN~?TVCQ8mx=E{c-@|x!cfw}f-_2Ys4$zl4Q%l{-x@~sO?6s4s!H)XQeYo8zNaBmK1>wS z5F5s)4)2!SBqAXo?H@GFQD7n=nNVxV{miHDqMQTpGe5Q+A~7!^Oqn1T%wP<+eM&gI zwu-wKTOJ%6y_%k9%|>}$v4+RTN5W@F+59JU5?+QJqARtfh6_cWH)q@{S`=5R18y#Pw4n0Bz5TczzjNWoI%G2I|v;eubN87tz9}4(xKy~PO=Og zC_ttBkhep}%`@ou&(Zf+4jmusB+Jl2Ri^xqw?oHmGwAsE3_3p1LFnkVjRowdmeoj}M%w+Tf)_{+&iF4j&z13lzk$ikT{!MYP$%Qa z!rahqHZ8QgjwG*|jbn;s%S`JidM%^HpzXsaUDLU^(O41z-xIrv!=m@uw_)7UV0v@U8ILoxNc_!R?S;0f^DlA38MLk%dC&zFEjA zF2LOYa)K6K$!GW8jTEaQZCti|C3>5XG(mENA@PTfGPjlQ1ilB1hN1w7XhNHUiUJ+w za)NjL%jcMli-qE0_;ygM>fcN6dxoR^@Pzi4hu3R>yGogJRxmVSnP#E32g9l|CtPGV z1sUfF=}E4p+&4)T zcjzQn7>30M)XX%EH4lLAr2z8)_&06$Iv z=COLr=iZ5{KQH_oM2;MEc+dEa8`OOmLkWQ3^150zc>$xnn zk)$(YhAcKK?B^L~7wm<161CPkKv_2Tq}8?19;jTP_VzxbP&9s?P|+xNCfffCG@=`z z(ub!|>$4C#gA>+2NJB+`+yG7JmHYn-y$MglQ#W-(+RwI<~P#DeGdizlXBzhe; z^(HS$hmuJ=TY^t(C>fvw8{VjpkeZPKme!H#yBE=RTi+y(t`EGG2y9vM#yq<2`UqbF zEj)_G>@0m0t^KfadJg>}2cT~y_yTQf-l<#$$t8Si7yswLzgYOcm*9W55a;9fxNI0S z$wsg=n-}hqW)rj(DTULkKYR8Lg-hTJ1C3SPSMzoa0F$E80UVpcts&l6ra(J2D#`mwU9iCtGXc2OTe?h*3YO8NZ6 z^2x^^6lO2JNsb@iET^sC+451`HSCb(g0{TTb#!m&gj|$--oU4WRaTvug}s}A=GIW> zI*8{dVK*xN$NAy#!66hRku%*IjcmaAEWnWdo29oSt$x`W8qaX8xGB7khBKOggXkg3 zAd*EayZD5Q;2Vo!@15!<+c@NwxTO?3hzp4&{k?#e`7~@ z9VlY^Js6+g+=)WFz&bU7hzO4DO{+UnIDlxte*BFm|G4`5%XW zygnt#T9RbrnC+5?BdyBV)QB(1L=@4{N1>2Isi3WbpD@;2YhFtfH;qXOF|FRD5RG7M zmS9b9hth}NM!bF`;q|Rxh1VaGlaFtc(-tgcVpe_?n!>Zp6})*4taWk|gcqfK!K1U$ za3VAeiH4shG<;mSdhsXZKL=Yusl6HMS*%p&qu4{6hE;GTNxw8o$K`e9)+b_i^~&8pA(aF%`YW{2)Ss zH9%$9=b|?Wf@qx(0zX1-DTrR6@Lpp_5MC`t%q`@jZRuxv273KyUp&znYBURxw7$km z%F!Y`It`EBM;LQX>%Y70>I)!@cA{_ZFEr}x{Iadfqp)*HPz{QuC?pY^!tOl&(W(2x z;3o3;wVNyDv7qcG@$5fxvhJZtu*2hS3)o}!a5{$~R7>vQO!j_sy_&6!4?2Y3R(1BJ zq|RQi>dD7%z@c61EBvAO(H0~;TTC#;{8@*HFRP-mqDdY_vwN@|dH7+>J_|!lx58D| z7j@e1K*i6rpAo-x2W(z2hY7{;C_9XxCHM%W440yjGJ{z(%Mr z(ZXu|NcHg`KckYF*CR9ALq{?$><)t~18lTBLc-|Pe6i`i8Gk+cIr(zVYq%~UIgu0) z{RDWFhlV8PLW{}eJ6i96MD|?Cg-hs?w0cO+iKkEvMz7+-`D;j z-dc?0L`(Sb?R8&2{3q0nk!h!NwsYY}f_E1c?lM1Wjz0m3KUQ}n_|e~q<+XpV5X|Pn@+hx0Dl>W9pH+Al_veo6fve++Wzd?MP8) z$fC9wy@pg<7TcDV3C*5R^H8LatcJzgYeURLw-SmUcJougLjKve^&De&Pi6ay>emtkudhm1E;(Yghs7 z!MB#m6GU4p&vIj^T8a1j20JPEcee?*pLvP_zH$)Ia@;@+3t z!-HawJj6@Q;Mlwb*)TD_kgqL^U6Tl+WBGb$bl(frFXVER-xP1o!p)u$eUk4n{{M6u zT#7oI`OiH*x2cS)zxn+C7ymiCwmLc>sV~9NchGz=*N0z!I#W6icUXPQSFMln;y0;} z@#8nk$;X%CcsS3~JJOE+z)uJ1{W{UqXP?yhN;32FtsUr&T`J>w z-t2s^c7dLxo#w2a4%eesKy`GcezqrkL&rVhzrLT3`c`(+Po;v@{ zPU`4o;6=GN2!Bayk_xZZs4Jco*H(c`F%U%Ok}}(L68&GD8|mu9Xor?epA$qoPz3sh zB*Wg#gCjFBZCQ*mzV^D*dIomL4XVv(efDC3`17qDyUcxSx7q$g8x*w_D_no>2A%1_ z)+ryuNR)I07gH9KQQve$8qnVQmdU;!Un!yY;0i5yVMlA z6Ef&mSx&?qQ$F9ed&_iZjhz^=Z0=YN+RBhb z*O91N#`9i0t&nV@o%kW54K4RDw3HZHk}*Wt(Q}W#0dPhoA!w^4mygeLXroaujC=J_ z2d6{4M`dUL4X=j!$xEh|@OFic5jwn4At5_ijn+8deGVT8zN;w9gy3BShS%`ZpD#x{ z0~tr1<#OewO1Ohkl%r+*YCP?))cSiGyH+dJo@@Gh^VQyn_F0Ker#}1pm_GLbSpme= zr6$%cVGq}zd#XLv%H?OQ{A!&}bB@iIy*azCo@2wImwO<0;q#qeZT=F*v1MxWsi0=x zjhFJAgr@Z!L~_`Rz9Pq&#Tm{=-{h;}HLs&vs)r{tkLn4$=0`hU7JF%KpX3z2*IQI? zt%PLFBTBVWsv=@)=P3U)ZzpBAH;YQUN$5T~S+0x~OGCR~vCXoeRJ)?Ss#NPQJp5f6 zVUljqv!`278rtQGtylF_QN#0LTwjAO_>bLAwDlwfkm^d49h|dkwvihB2-rxKn$YwT zq4~Z0wkUS!Hzk^#@s-bOGsHzDvd9E(UYM1$DY{F=a20$ABJJv-mW;nXs=1=8tAFIy zzc8(-+B-^irsYF&AER8ApCDy4aUCgvLKhIYR$ux+ATza)>SiB-O@~iq_tjAG(qsf zKZ0U2dUo~bX?#+f%I&L3EPs3OANk0g$^Qy|jlUdt3xh5FM%N~(yl@jS7-PI#RSEg? zYKCt!SIf|wk)*qIq!!Gp$__yYQ#@yyk5Nf&bya z-c)dFKJ&-$Q4y8awQ9B1H#>OiF1WRA>L1-}XiMwov{=lHeU8>_-%q&jFQ?yscHdu1 zzn^j6ccU}^LQiiGA&_-9HX_fLuYm&E;B z;{GFX|DL#im236&wY0v>)}~W={S7&!{X_gqeQ+bANLKN$`N)rN=e6Z&*6!kjT5}+~ zybvx#TvF}D{1_WR&lZVIep6b_hu_zw7#HuVWmAw{OD~fm^RSXglb!`#>wXY$H8%bg z^yo^A#aY^+OSdti`C678Lq#ojjC>MCVh`>q@l)Ut2mYl4|5{+la*%snA(!K5v+5gf zjsd-xVLIU}yn#{onFc00T*j`pIEdB`ZPuxwdTkLnwzXf|RYn>0;kihI(f24qY%DiO z>k+;ycGY*dxtfv$(PKc|XoiX{8Y~AI^rzFH6SKxWYgXuM4Epma71r~OVTNuAt{YzM51opGIHj2vC z^3a}k6|%olWyQIsTCx+bYPnXeL|!Kz|=Lt-pEA@%!9aF*@%R32YBxqiPHSKEI zof{n2DQT(sW#jtK;EWw+I|h`R_ZWDpG|5n~6@z`By~(+@qF!vOq^@D>#uC$ZUH4AS z_YBS7yUM1}>J1D>87?l$aPh^)#mA=bi#hb_?SUtf{glx%RRa6bxR6-&_sLMPQ58gr z(myw-SLaS7g!VT7CCo)@3$wS7*|0k<^fu>z`~O4Y#sAxhgKA%W{ZQ{Qt`NnhxRc>= zuj6sDW+*n*LR~Nlz`@?%z^wA>zaeEAjxWw|{5lKTLDY35wAMeban65W_i%G1@uzn$ z{Tx9U&#Tq>&4VypA5^PGbGD%>qYUx$9r5#MV)Lr=W)LsiQH{MalrQO0K1Oe2xlB(r zJx|%7vEE%>isc<131>7n(!6&PcZKHTvi(wN9EYN#My#GpuBJY-WLddpqg?OiBj_!Z zwJV73-_>RiAzjd7Q=MqHGUm_m2UlGem{}LJa@j*mbn3gdl7igJn{EqJ>pCB?UkPpX z%RazFZn);Zr?r+Yv(nm~-=5V!hIDRAGvM`NByw4FInkeONubi~C zb0B#w17(iWn?04RJWZ-1ZnLmg+&GytA+6YlP57-OHneoh)L&>yv}X+pbWLC{@Q;@R ze3p~$5u~8Nfyl#F^>1TUpSc|pM0=C8w&!AmpqT2cpPxJDH*QN<`?cp&1pRqSb4!yI zYWzD5k%{F-r-SfEjnA^?_B5U?!S0OOi4KB7CjT@wcn<~dL(an+6%xJ&_QsD3DaS1j zF-n=C*Z8+UqzNZ<(Xx34w3e4|!$kpo}>oU{UY5RB> zsge)(#zIwcxHN@1!co@i2%al~QSf<^NTye09ss{j0ptiW?EvNhfTpqomK;rc7{X-mOjd|$qUy4S?`7W$zfM^gBR}8iNhsLebgrwA43rMswqD609cR$ z%mbi51(*lGKngGqfWL`($b$97p&PWJ3APw$c9PR?h=DEJ? z4b4KU!%z!DB091iWO29y5LW^RSz>WqVH|`z#7Y)g!4goPOJ@a4KyzIzD`f&&;I!!x80Z%t=6t9mp7!fVOfV<5>dQ#^Dn05`O&Iu>%oO<8{#- zAGn`%DBWao-N^BCC2^Ka;qWd%;6Rq?y9(52On3Oa@IZm)PKotgffkrbVH#_Hf%>P! zlB5rD|D58cu_Wq43&UMApgjayJO#9uKwC`#sSylqlfjkB@0(H{)I$@e+W$?FHCY{6 zYV9`q7Zbj;Tr*{r-KX3x^tGIOTz8f1Qt;41T6k;hnXT;H(iyI?e9!`@XPMQ1``aNRtu03x?aYH6My2q zq=&Jh>(jat*GSmho=6yz4le7R%$?lHJ;~K~qT=?<4y6CxRWfaX&ms~3hlLeWGTq*y zR?L<=kA^^f{giT-*C*vR3eC7Qw@bx9GcjG7D7y=mrSgOm3MM>d+KrggnCCh1DZ?%6 zi>RY=Bq!nq8}>XNCWlxwGQKq|El7Vd()as2fvy_>nb7X!_S4MyZsM5 zr5$uCCu z?z&oW`Sp0!o@s+XwOm|wJUDui2+*eNOo^!2( zJ!`BTsgYJ1BY{zt43}<%ludFvN^8dUYV7?L+P1pD756!pP}>@xaO;$QEl||=d8Vtk z*5sr!2r!C0(P5yWA%pG&mkS8iyRNS^b`6{~OrfT?G`4u=kWEaR;UV*SnD-l5gva|s zOIOZkBX_Bm4Ypp`l4n_Ps$W~t?gMO=sapS=y;MA-h4e(;O05HoOCecE*@GQ-9cmXY)N$KMvCVKp-XRKn^wO~`N zHIE~n9(qiaSw%^?7%c|!9_t5}tbDvUv`;bW0kS??!0$*g+7_o*o9f#YTOZ+EkNHnq z#bQK}RtL_Zlr-H(FRgqqYY!-~WqfMxgs-RjUcSBQx`(xGkv`6GrW8Gs(I`uJOEw;5 z>v#br7q!K!qSb)R@)*PhL;Ik)3}Ah#W zz96~<>iKv9ALEa2Ubw*9QsYKMax<{IdzVm0{mkJE*UO!oTYR~@Bk1LJ%$;|wE7yT8 zYp4HnBq*&d`(`_CPwOW|nRMY^NT77#2DHCqV!N$DNPLhdub+5fMi!z=WY^{E8COavWm1SuqZF9cyyJiL|cuqN(EQgv8A zy{r^0y`DXom1qYPA7D?~X$N#;jV(*E;mj~w?vuOaR!CBA%u3x~k1YxD`7y^8X3L0 zpI>aPT77_TXY~81-u&+($JWX_1OSe)4J9+WMlR zD&BL5=kP?I0cIMOd6I-La#*`iF4KON7=H9QejNh#-@>DaXJhIqZcN1%qZ?EG+)8j; zKb4F=?@NoPhv2=*#Sjv_eWf4VpSfvbi zDK{#bI>ceQ+5$8WfRPkn9soNgF~c;#Jl2Q40^0P44W_!W)w73VVkKci>{aB>hF4pT z*V0Yna8iEj>+H0VN&mj|bq=!4(C(SN3opEoxI_DgdnkgPe8fA6gx94t?ottBXJWWK z!&j5W*?zhmy4G2n)I2?_1{JR8WI43EX}(mlN1qriPL4h~+_kiOxIHT?P6FD;fn3on zme+dE)B-O3G88P`hn8dW_MWsqFDCst_D2SyVbWN!vTE5ZqDEEHq-|f5n|D*UA7=2? z``5|((hJWQm-d*lr*wfp`#6v(i8!L8>&VP&3-uVUwW?^-XmnjSam46)WQGs3XCet< zozB(uJ;&tcZ*cUfEgS@WrnBUp1+>YOozbLE3ns)&n=Q|HJ z1YM6f;zxCo()A?OV|~cZQ}eMy7y8g_eEZ;lphoI6@mVGrE?(U%cdoDVZ}IwV)=%rx z7SX3Ym^OMhmsqr{v?$xBE&3n&wENUiL8tE1EXK7yZEhjkrO_kL&A0AX#3>!pD; zO&b$yY#5`~XZS|i>jfSRd?#O%TmpBa44cx7t`}>RS#N`xLI$LP)u8WVJbQ;Sm^*ax z=vtp0iMI>$3Kj6cX8-pze>CLhb51_G&hzUQolX5!qIy?9dOuOe&>uOhmgIo#rcO^JbB`_LSL*f3^ zck3$RvAm4;A_%q1z2&s^o2@T&ecwU6)-hCa_&(L{H=0+A4H@c|7n@%K)F?C`$uLNT zbyNRMoT3(0uBS^a)0U|w7KxEk_i#vQ9S_k5PPO6Szi!7C!cduArC@=fv1hzs7U zXN}wZ`ZRDtpskRyA`3w5IAU~iO6x%j@Do&XrZ#3ar-Qw=Qll+O8;Xvl-sn48FTsuX z2F_D7t}6U^FPF*5ql;U&(hx6@3f_&5^~Z|YD;AfgV zUdq|roUCT4MR69BqvMqGy5^6G9CBT(5njvGr}G4nAMYb#^6|cM3h_G8@*dxm_-%q0 zJ_PyA{14Olqzj6^C@Hsj|5{|AX=98q`O)^o_u4{dD>fv%Eu=Qequ$+1|KF1xPdP$Q z#`uM~+A=qWcDl)UKaxV-{y4f~Wuwh>#{HjWLW#_c>07uAyatiT5a=3E+TwFU~zsuFv zYg|*gMAfD~;42Qy0HGFift-ANVH)}w7us^6XS>j*LhmQ-11|Kp<3aO7~ZIm@cpz1GwR*f*s2_jutn9+S~u^kdgslmx6Oi*D}@hjUediU>^Cn* z;_6+Ev}Dg<`J8aV-5Gv|*E&HB{$Pnqs{5f^jPdgX$;XG_jBfJU`c%&;*D0O+G5J_X zY;VBgkFKq?9(F?UPH1Pk5q?KP%JT-AxP)KR2Qm=kJ`bm!E1CAgZvd?1BM+&LPK1`e zMn9&kPr}EJb5PBPcPav)mjW7pjODezrN-URWH%unl}I_ZgNK11`Jz7(J@NhtZ*3`i zwikup5)#F!5GTBkFZ4T-uLt<*#Z(X>wH1Br;L8`@0ONtq$8RWhR*1gB4=ALR@Nyux zbQia#S9AKgD?PflYEGmiapu`9p$Z zH9`)iBosQ8oyO1S(WdyH;b$I7jh}9_Li}XUxD52N!;yPrL9LKcIlNe(lny*A_j|%aA&5o`J(_ zi|*FO&bXGhJBx?dIIyZr-){yr8Z!(6w8f2L^oUl);X}|@igx5KK!wM%Er`{Kpght^ zjEYm*9%x;pd^Am&=U9Iqgt_swkYh{F{=60!cPsG(_#Nx>(RakJp62!RnynWTcmeCA zqZFQxUnGZWI9jf6mxo(_CF9|O-QO>?KFLS)PeHu4A{(-dH@XR$T1zB|ef)5P>|t_? z2A%&W@>iYzLHYaf$1euY=r_FZ5zuj*>McG6T#*XLOA};o9c-Z~MPQ-F(&zpG4R1&Jm;!dN?$@D_Y1p zX8E&q^ARCBoqk;#-9j+=BP7O#pQOAAYokps*4Q z#F)Kc;v?ya{((fS{g`St0n8;?_;a8=#j)o&STXuN-zM%U;CBK%!%qr$QhzIGD^|a3hQDu6Gw(LC|bKKhEEW~$ZzbFNqHWk z$&-hE^K0px+XdD#$RZWY-hHd<`6w&=zrbcn{>itM-B)F^rB`uAauVw6(Hznz@ic5E zvb!Z%s%k$C#)tGZjp#yB+T53SV7y%J2=RSeHLQGbF%tuC6w@I@f-lz~q#C8zTw;KJ7Tx*zcyOltgi;7JkgD8Hjc)ROhHOvps~GEkXrs4PET`O_uUeVh&Ek{ zw(HP>>?p|Tg4`@Ne~2Hfzcd7Su5piB07r7RYwaHT&=aP~&RyD`*!Q?~j8k0h8 z)zjFadb57J4w)Z;p5gJGMg-k{8noY_J4}K0b4h%xR4umt07XnZ;pM&@dLT--`Z>JXZEAAro(UqH(+t%Zq4Rs`c8pP^IxB)OC`#x`WadHb)`QIs(jqG{(~oN^@m5MlKAE>%viLE4@9` z$SXC&W7M7XNSeqOBaD#nI%NW~xpo?^NwEu0hVixQrKMu_Uo#_lx`$EC-SVY$EcZ=L z%IuB!Hai)s*O~`Eh!qiIqxE?19MO+H28H98-?5Q;(JClW&*K|iqX~HNs;zzNL5Qc0 z0Aq4Cy+~#mqkhP?LvR6m&g)65&FEti_gJ~j&IDM&Ot}=w1lR(J{E{T{afyq;C_{3B zTxv#pFF(UQO8x6HIIlb6KUJOB!j?(wqxuNif<>EPTPzf0B#g`;RkrwN1iF4O`Z(WX z&vo#g;!yv3^p{+-lP!T7|CjQgDZ|j9C2MRI4Aq=(0UE^4SK`*4`8-y@GO;eZH#4O{Q08r=D)gC$gJIE;-EZXfx%5TL)5byh61ab$&JqAEoK zH^jOe96H|yR>MS`4liiKfo$F6u+>y;EjJz(;-AA3e`x;t1CW#)Ckgg)sjN61&Hpp` zzmfk}@t+Pn_eK8Ouj0w*)Ix8E*P>#gLi@fCw^xc>stk1DFTEh7@2P z0B5EE^8k2x3NR0VSEK;*08jx9sd)fIDZo4c&Q1a5`J5MO^Jcu?_wsBe)$!A$THh0E zrh>jJkey*1g)|?0SK-M#*}F8mUED{}cy``fX+0Q++1U#@i44XD(3!DGpStwc zSBTNu=%;hgcAyKfJwb+H+0Vb185#TA)#T2fOig&<-$>NGIXc|DpSJ9U1EQTvhg$D?$aWJC zHnPRnLjf2H!8N)Y4HG_ zyCLk(gG1Z<(h9J)Vr|g8)^ZVS@|RN)KSzjPVTctHemq5-;L#|aOiaM@8N(ykwRW!tS=HigV6n?!caMw(gB?ET5K;wh{#OEoA&BG6aG5RxeJ)XVE>ql>*C>Zf-?gJ6d zuCW)I-C1C3WkYt-j?y!BG6~q!iR|V&?#N6y7}7^0ZS-LBTw`L=YgM>|5s2&Pz)ku82DxZF7%eNS#ebgebI zy!dyb%XfMg@o)8E2Y&Gr@pI3s{(!^_m)!tY`R(cx-c(oo}e zboV55{8JfWeHADX)(gaAMA=;AGRV<|=8hrXp`|t}Vl(}_AYwhI6w#;2p1lS0?+aWH z_?$`3dWK%wj!7!ZG>9%I1hb%>AkELRLF^aQSY^w?SM&pZs)ZUCm+kZWe3na2w7x~o zJQ}?l*ve`62Na&B{h&VJujtpfek}C6qxW%6W{#liU~#$AL$seaSo;Y_NdOn4n{Zcb zl`W6Wildj4RS?}wsLrRb1sCpw-{!zv_RYNn{B!g9lsCnt&&=b(`{L$xWMTg9sdkd1 zaV+pJR8gWs!NtOeUyxueLu~40vTcSSb%xtFz)7AFSI400>g~vqYamOKvX`T~iBm0@ z0ZB6)$=#ejGyZo|lG`^xLu;3CL}$Up;qk#lucpr}AM)Yr#nAe)dNtpd zlJFK*w4R-8k6lt_9#&t!{J2uJaDN(ZbX$EpXt%zJC6p*o5%x@dLJC=CYA9w_vvWob1cAX7dTv{(Ok_2vd%s@XasGEhIU9k54BErRW~~ zNnJf3ukEX7?_?O+(*28iW%7LSQJgkJHNI#A$$J`G zc>!8+UjuMWxHG?AV1;+lgYlobecjWb{rD`NOe{7&IxX6e_BPgXvSJkz(Tk65?G@S2 z=w`vT!kr>OOlx?>is!y9%bw!+9m!s@R(>atryqTth+Ot)Od#fW;SZefszg12B+wa9 zZ;{D%aDNu=;k-Xn`6=!t24)QH9$d%27zH+9c{%~Td@oiJIN;4fqQc!gmUF`5tc4m#5BtqBjtT>UJ*^K1Qx!R3C zI)OaUN!}JW{^TTp`S{;94AGzFb6F93pu? zn;Jh%>FF)a=uG;`bMvc;#oF={3S%#kAA_9sY;)m+v29#*XT)<}@*x}S<6en{by?2z zEGpeB`}3$lNVR6oxomh9A*xv0`O$&VWvzQ?u^ZKPM{o9Ot+SEs_(HxCgM8=cJGSpb zsbr>YZAE1pSCOJ7!6-Glj}{-JbzLX0uyghKmrNKI;M|wj7LrJh=S1fTL#?^$Vdl79 zaENA1Z~+3jWci%j7~Ip}Y^aQaF&?|KEdL8+oBff^n5Lbt6iae_Va^|9nxT5r{U7}$ zk&Slt<}9UXXD6|F2y`UZ=T&NdvZCtO&4)6cEy0B5HLoP{@J5A1=i|-N{VKjuYSW69 z7xy~F4AJ|s^5_hE8)(?o=AxDbdb}4XRnZSo0Kz;~_$N3$x+O!IGchkhGdQERQq`I_ z&c?dBjs0HkWme)&nN`fOf?qqbo>YaaL9Tvws;ss`;XJKiAZ%`3sBE&ZC#Nu; zwh$vFT0^VhbWd@j3-HIH!D|~%uPs!qtrW##&nsXr4bO_X5iJQdsHOGtSJn!;a_@kZ@yyo$S}(iw+gypOY$;nYQ zondQ9>FTIpo98bR{iv(k%LQEIac}Vya#xVo$%N5R#LVad3a|y!0&|-F=A;$+_@{!JjDnp!M~p3j4jASOYS2kdQ?b>GfP7|wIo6G22!}18;-U> zEEAN8=ovgoO6Zb&4Km3*#dxnuj&iKql;0bb-;rKh!VEjkF) zssDNSMMd#N93~vk2mQ2YE_D#pyX5xe>qiz!FDg3N0J&Ij@AK6OhBC0 zfz~6L^%zYCs#|#GE0r5B$7~~a8hPdq8C+HNLM^~5jh(&xA))4x9L@=&&pqewr|7U` z1W1y#=FucyBvf;q`>5m7t&Szx16$@~E_+@~w;67rFSg6B%~vXEmHK_wD6ZTfY5pdS zTSL$!+%gAPW(a;-gQydCU2_>+_CmTAlYV`MT=C&RmkH4Fgx(kbG@;kUB4igr?BR@R zVK90QAHdY7@M3~>0X>aof>G_M?kqmYIHcubdkuP3ZpBEgc@xN|!@~`dV2YmFwY7A^ z);O`BCOgNvB2?A9P|_5k*2I+1`oU-pO(ckT&Y-dxQ_4KWVnf+O#UMn75j8+3T`Jqc zWeZ$$3=*K)r1Ls14v)_rNrF^5-l?{Z^8ow~>?9=HjCJ&zG-O+!xc=?r+>=Mn=|NmZ-}XdNfGu^h}!Hf;!`z@Cy{i#T;k1 zDC88U!DWF=yuii#lHvunN>AI%DIi6kB+dT{a!c|@I(sCdyqzN&#``(O)(wO&gM>`J zqD+coeWh_GVGi1uujygw1Q+kC-OShqverXhYNL>0f4*b?O0W;EnxA}0oCa-@+5C4Z zYc#B7JE=Y00Z!MQB(bdyR*}R|xolj}U?i~f=MOQ#bRXGBCf19WrFlybxC^A7<%FLl za-PrRM29cdr=?M!EZrpS<0Y}IEX&aG>=tb%(bi(rQf3fsjcKr(UNL8;S0+}vO#1vG z89G-TmQpPQMJUfy10iU zks`ggsECIKtMQ@inT_mf+DlK}tECULsif;6rV*R;2L=_f$#<6%b<4r?S)%anusJ<$@s4hl4I)mkr#<2^6@KSk;UbSdLYkvt_tD?Wk9Kz;As>=*#37*CT z$5rf*xXXWuWH8uDJ_>Ej6qYcBZJE&Q|XllzIH2QY~M_0|&>-q=_aG{k=SB zpw`6e1-*_OW;U~u)B7ys^kM8YOHQ?CaMkr9QH0uq2yaYkc1!Rg_}SgKS3mA_o&P(x z^IgtHq{nUf=ux+S;N_mqoqmkdb$(s*Cy1k47bNrc%PF+m3B630^mb*?&=SQN%4V(e zMt$)3j;(dx!iUy8Z|*gQOkasF0AI6<$?r z8})4?zHPL(MLADO4Rb@H;3o#gO%V2rSs1%f8&x*v&qPKUNgZMU{! zzv;ZGG?uIgq5ghKLoBYS-2W=%En<$#%`S=mV=pYr#YQv?Ct|ui8 zxr@z1D9$I=a8LAJ!Wv8dLtvU=5pw~mc`k~I59d>^mdT>%)~m0Bzw8VkZR2!wf(U$( z*H$tp#{NbVNJ5E8O03xi!&js9S<(OYm~_ zayLNG@J5A%-=LR6E#Q>`e*kd!O#qYIOilC(h2Ch16cXM^oCw8Cz4_(m(A&%KPIZ1P zymJz8DXIuq(qNL2>}DJDma*Nf!bd$cgs}z|XT-_cifz5N3AeS;B#O4Xq7RaHbP;hf z&2l=kB`43$?0RhN-Syw+%zpRT={K^qzYyI7O*Pr=Ja0C- zR=*akkRv(ZhK+=~zKY!sRZp>Aj}8DZIW(2cXLZ@E5XWW|jFVc`t7w*N>C|LVHcV%9d}9gPJE{AOztohPpeD6LbCKf-_g%8;W~I4?a%V9 zuQ9(IvHP9b>rZYn|9Z+Ti{#$5Kf=?_oxS>mvYVz-rXeZfqGI4JGKutcn&-M*J(ilx zZO_TQ`e|3kUhcp-TfNlHZ9QHzIQCNK?_XZFJ9Lr`-^Lc-1{^17Zt!;J4cX>Y`Z@R|Wqz|4l!_iFP#>D-gTx8GBogiZP z^V&i&Xa`wHzK}$>g3fCzm*`{i)?%f@5gM;8+*4jR7NE9ap*``t85Rj+q?0aQzjA$B zU+#b--Fk4PccGAlV=~P^{-Wibxb()={CHoXc2s9mpVr`LyA4#(CW3Uv@NquJo{Lf% zZcit6Zy|qFV<+v2qiyg>g8Ep6eF`_fyuXlmkHY;lpx#EFgX9NF{rSyH3#KI9%@tT$ z1f7^ZRLv*j;MTd9ztNRJG+zhJud=qA<)7npeZn}Ek3Iu}3JQhMXYtV3RDxprAN(%x ztQg}9Tb~m>*`QK%J0La@#ODnBiJ*RDs-UXYe7B|1AwK25-S62h_u^Nj zGTN7W?UiXcf*j`Mau=vS{UX>awGB|I>RcG+Eef9$0%zlTBSx-j(&k8j?FEa z6{ZanJ*yd-b7f~?on3q^pl?sYGRDzETsY6g`(pSwiP(!jYe6yk68VhvRP_kOm+>}g zg15eccQ^q>UsaToun4Q5=nlR@4YOruy}rKcK>rBa&F@E5OxB{x&N6GsKGVHY@FICr`H8-QD$+fNi z9lN-L5a?QsHK4sAS?@i1Z8gAXcZkPQho9Ef%)&}1bz(bvPHFnCCSGuRGh5vc~ z>-cFBdw@w9GOc;mT+(&o&bjhxmyhz+iE=BnXWcz`?f$5;Fr0Rq|Lb%+I$<=bjJODl{z<+sz8w}Fv!)^jh zA9k}st)F-bT4QKMwPf6%Tm@R2Lt<8vIpjqeExsl(+Ncn(t>odim=rb|{v_(0*~pXK z$O=g!-$mqFCE_X!i{CSts7U$?>^^)Qv_ZIzA}1fU@Lr16S57tgehwFA)#MAx6BS|j zhGE!Kyj+6KMX&uKsH2~dlRO*#maMvI5+gR&q-QnfJ?Pr-r~I)&kHzby&`>bbGx0~k zgW*!_EY9Wp%09jD&4iE8$*P_Ygm;rBuga1qkL$7OLo%wg5hJYjj?B+suPax@);FO# zzLnsV;Ppmu-E`14!b6l@*NC%zyc%cpK!;#>+#4AUSu&aW^QTDfbX1Rq2K{-hJB9cY zLTm$8-dgK%n)K}a>vYoN$G0g$J{AI2nxD3>Q#Qn(l*el;wAOlwiu-ZAS{=)cj#s|B zx(`1(A^B2fe)KK!b^6pJ(1Fe@^J|6Bgzh3pCO^Fk4S!p?ZU{fmFjaB;-37a$bdmAU zGCB^_73{QjHmI=Jx+?I?R+wWpOxO!CT=-BIFR!d?uZjz-5;Nn@p}74ZKWb#3l}j0QF4V^5Sgz`|zAe5h$ZLHC z;TW#RKjI@+%1*QUj6U-5=j9aQFUawu%ZVL-PJRqRy@!w1ZE&Xbaem@20d_IMl33ye zHLtC>t-n$7_>1`5diFKYvC;S^5~8~yrqbAnrNDRau^zb$yv69X{MI(Cq-I2k`;M*z zNW^=HIDU`YWjQ3XXjLA@=MYUK_#%>`PeI%mq*#F*h%3aE=n8NpeR^+h*LCUI!o7d_ zYSG8evse;dOU#ItQ>m=xARYe!>KeSZm|{xlNjcv@JZ^A$ZDU&7sCgqWR}H1;yO5pK zkN7f2t{uqdE)?P0ir&7n+SQGVbpiYnB)>gZwE{~E3(Q5 zGNZ49#?2Yl?q$PTR>9oKR)6kW4>{SAnTxAkt3FK4S^9pLyYJ_^R(^n-e0)$EdYucM zaG@`9p)Xb_@@SO573w69UxsYCUrF4rChi@H`?bXVdg6XVt_BJ7kno!+z&rr%OabOu z>4mzW7@kZGIl1?8^xyVJU#B=72^TF$xJW_5MTvH>Ly{+5Xv^ zXUAz4m}5iiJHKJ~qd_XuWs&$T8$V)E9Js9fNd0a20o88e|Df~Bs&ja=^UDCVqlp!y z3i?&l8{;lruSn>^ipg~Zn_*@A*Ju3VPsG?502hkX|9<)Ra_}4E-^cl-4VwC;teN0! zi!)~8dGuh<^xr*qS@c%ojBZ+(xuG&qpRh0A?yK-spIE{_Os`L1Sg>^;;vauUd`_1w zcj+S^enOb2VtvZ}wpvs<0?Y$ISQPVniTnM;{XrVhJVf}R0PsqCS)K7H&HjD@ zk%$N6_;x&&Z*7K_SZKXM&E~eTaC?P%2nLi=2=)qF6{5e;*KM`lY`?}yCd%WKR9^nq z@?gi*Fd~o226gzFJt>6Zc|FwhVb;7-od(&;wd8FT0UMW=qOD?e&&3Nlmdp0nj2LVe zU#6{!VQ$*Vmhq)h#@{qt_rlNWYZF16hOt_%+aXC9rbs`}*CuCOFMVcg?GWZWzYcuK zF$<5GP*wbrT;={Maetk-ze(K36Zf}rRk`LN!S7Olc>p|-0?Y&8F#(QN0W}RfimVqN zv*ehG{J|5WlXcHod+YI{gQWqicX#U2BR`+73A>Z=14=KwfR5M+ANBqVIaBIgWl+8E z0Ow;BMD=bTDveiIXhIOHC|+S}ClTL*B#hCE7nj-9hVM)Vd>a7foVmJm7rvk{)a$rr z>EEw3UT7-roscCH-7sgI#_+HFf>-duuYx38DGH6XZoYT#|GP3GZ&p0|KP%bXqAQfs zGPfelEo6$5eyAwkT$i&6t6sJ;VRfG3LX$zLypxz)+XqG8#tZIusHKqK`Ou{vE8g6~ z%7+Vc*(|fNY2uxVxaKp`Q_^2je*>}ApL-e-v%@y)u)5^Kny*!3&6Tj`3TU~{C}S;C z#KdnUUU2&W^&ep!@XC(#h8KIwaCbm}Ve%@8IVagBn`qple2CMQU z2-SyZ?ykRQ!zWpp9UpSzx_}JE3s^q!#DPT5w`&zV%3&DXN3&V{4IAa*9fVtpXYqp9 zSsp#rGgXU0y@&HBh%{XoJ3iC-yXMpvGk@}LkPI{9`6E#R`(1?*TqSV#4=lx`3wO6% z(r|at#ln}-nf-*ESrv>PKU5v6^N6SrPMA-~CPw$7+YpS=TmW0^6mrbca+hcxXG;C! zySa4JpN?p5HCG1$O4q{?=KTyt3zJtR!b>!ZXs&`0=^T0=aOy9)sT54UkIQMJ$t6)e zWa~2NUauXtyUGrK7dm?2pz+M)a^usY`EgX%Z0;XQaOsBrtO9lRwc@|%vljo=Ie#-} zL;QDK8RylvUg+5mTHR^Jg#v{SP#m}R3vxF@_iJq4EnFSUNWp~ygqY;0aH;wVP*7<~ zK3T(BxWWw=dtC~DFX25LFll3_*IOL^9di4cp9Z>hI)=1GyXkN+h6EZp+M@f?55u@k z!;6ZWhB40Xk$%(gk_BG#xsOBi_n>ss@K)tZH!6}FRn&)hW@rDkR*zaj4fl){ikF`; zUZ_1fwchLcJGkxD_htq}eS z3dUC?6o|13x0g&{?9s5nN#lizZ}pLg;E4oj5dD|%Xpl4yfPbd|^8nbA0?Y&8nG|3i z06AU*FdXIqFee3=CmqQN@X`SD0Ps_Qc``BcX@Gf%Sr7mhBW~sv(+K7vpp*j41E8D& z%%i=Eov4S`o4i&CV~Ne}kMAQT=VE|&Nw0UIprvBSu`deE#Z`~EHadH{AO5Fw2lL`7 zxNN_|kA6hdQC=hM9;7Yr_d?UbTTZ)BObn`UiRTv2ohGTQUh6@U#VRg~1{D%VMRS7_ zX6BpcO3JtAbdKtquaJCP#c_KgD=F(6By;wtZx|Ad1+;rQSmuUgy}tEhvS^f1t{B7& z-;!cDk65+`^AWfHdZ}A~ovek|)5*#Hb4WAXd~{z9j2G)9@!t>ojh?pRp}Z1p3zD%Cc7{hpL9O|AQU=XNnF6ZIbh=Hp=dhY`84fIr z&w`^|#Da#*FFqR*$-%MUV2%!N-nBD1h&So|ewlO`9`8d>`+k+^T)LDCu>BkT7J9-r z(4X1m|8OUgn6+QtlD-`(%{Jt?S44N--fCx=+KfqK)ngp}jN|Azj-zLbqqg_>OZLg% z1W)q+iIyusmC%xJuKr!_?3&pFaAd53Ff^}WK{*TYbmyQ}jQ zW$P!@rL@9J;Y0XZ59_nU`}T-v5Z+DOLZ3Z9c6<$$!b-F?!2Z6*PSw7MQNGeYx7sU| zy|opnJ(io3rY77yYo9}v?(SYUY2Lqe=FrZ zud%#3FWOq(`RFyyx2F~QtJNN5)RW1`t5$hqtxnYv-!l0onam@Tc}XUTg|&al!dh>& zOh`EiDb!Xd>&@h{xn3<1Qi>ko1G{LjBAUmR+(Xp@?ORi={hR;0@*gcscW8oN zaPExf_QZYXH296cdVt+F4X*Q|abS1zzm8vjn#=Cb_nX!JqwQgFB{*On0fpp0%G`EQ zOzv|2%T2?ckN<4`>!Oh0Xir<%>fhkWH{ZHD(yodaWp8c|i5_ztrb?CQN`6A750y^v zUIh2DN!<-zO2Q<#xpJNCc{LXN#~e5jd0EQahb-`#;9q zo0xVdvjvs%G#f=41*XtvBKHlGBSn42dno;-@Uz9mDl{~sTPR6SgC5H~#i7cwKNW}i zmi@jsxT=88=D=uiXuDOv1oK%m)9HnH5Skhy;XgJh6(G3zV7;=*!4pf zcKRV~3FJ_Z{m=c-2SC;7ho1Muto_i70WY?Rsq0HL(9j1xTlYloGh2r?d+*aYb9&b^ zzCWEww&UTFOgYo>(D+*D(*(oVYn4-qP+SP;kBZI1nWjfSMe-WGe5rf1ud4bg)LjCy zvQFUAnN58$O5dc@>zImZ>vv7wT0gdu=!VkPFFg(~_wL;JgYGRw51V8kSvSX; zE#vtqa4sFs2Q+}@<3ZJ-%yazORonYqwQW+hX&3c-4*Yrtexsax{APv9n)jnFlnq;* zq+I1fuaQ%Tua(2<{A%3M!@XuZlR0MJU9Nv{2yRwfKfXy(uW|DaziayibHr~XaqBI# zQJEuNsMIW=Tv75d@8#Hx&ERa`>O1-wd>=iYO>{w<4=eMFN$-Sf#PbJCc#?U;skFV4 z_EiSLbC9O7BfMyTxzF{Y-Eehw57J<4SDtM-8_)RMzNqQRxxRg1B zXX{RvU4Ar8=jBJe(NjCyH$R8SL}~Fc(W~>*CD5`=w6qn9xrKi(%cJa6$w`~=7l1mt z3Dum}pDPlbaXz2hn)Y=$?aQTOum24c7Xt%xi}l?%5~o&q_+L0o@+?=jxxf{XQziZt zK5yvX7O4gIBiQ6PuLJ$Sbgj;l2UQCWsC2VB+{ij*yb zF0xUp#TBeGyOLv@_kmy9V`AAOf)H=`;GFe zKD2opKA~)uwRWLL-8kZ8+xB2b5A>VwX6&^2g-s5L^(2uu-bQ+*)MftBgMHskg?y^X z;$|f7_e4)5?kQy>*~!YB!l%jH9{|lNb6)Osl>4n#Zl!}=2#;j0RID#s2jkC#pO#}u zx9ckL)5MY-2!^dseDy^&BKjE{= z)wz)BHjZ7Dsy;1$%SiQU8K(8A>@xLfpH`o{f#Zr!xmX4Pa>To~R;m+AQtoWnB=vjVPN0c!R5dDo5+~lqC>HL*}(Wb9W`N}wxV$Dk$X!Ns> z;8q5DX+~>^)=H)cqr39@F`F-y7I`4u{ap4KfOdL1=4G0VXzxgq5$$=n=>Jgk9Tgo+ znqRg4sUVxv9Y|XJ`FhT|GPW|=C0d8MaYu@?Os4n2hHYx&BD!57K}TIQGe>8AtKZqP z_ucL`^A&ZeP2ubcast$B$pJN}w|?Y!^Ah?0Eh05f+7|k*q;9ZyrNu?Kg#eZEH4K;R zwZ20z_4KrZd*`Yj-oQsa{Hp^QkDD#}>Ln3YJ}aYda#i3*=qp#cp@t%fax^c}5IEN3 zN6(Pi<=WS)jZ%>pxU|hzfllWq6`6;fgWHJ#Kh&{l^^NzBiMP{I*{~qCe}g))$j8gU zR68$FRN&Wo!cM#1l~-AiB-EuuH($@QFZ7_gZT~i}eK64WjMZ0ILx{>Z9>h5?R-S zJSYS0L`2NAvcbV=Bl0(Fuk&DZ8B|^Kd~_%s+`gp=4^K87 zJZmlvaaW`mdy2Wo-~o-cTZgTuR2|rPPHVEqKX(VMytbhU_pZK*_@#)6E;@Z{7(a7G ztHrX_Vt+ncjogn^Jz{)qW<^d+sYpFLyk7Y|0|UyLn%u5hNUHJ}f?de8s?=3|R;3t9 zw+YCJ(pDHKoku@4$IN(Xg02yV5z6T;?3lap9_yxU-nNeU2l7gPy&t@ERlh4fG$z@B z=EgV`VWdfble#e$Vegv_(x7+skBuXtam3_j&7DBX*3S-i<)=SCJgWfp7p6!MG`sy4 zERgM0HZg*as%M9>D+*(^vQ=|=nlN#CbBjQ?J4P40KYPTw&VwFABOv8r8(cQ_x~7@EuUEE_BQbUN4rT|a0= zHmmQQ5Z&nPQBU+V2s%*pLB8E}VW?x?-Ajx%$~_egLcwGopQh%gOtu7b`E5NzB@J&> zNXWJbo4{isQwK}tCrBRsov4-{-O%p<_--;R3ZUlyq3uh+qbjntZ*F%w z-C>ac=?;S8La<3Eh@cpvBBHpUV-r_x!jm>9Ti+aTu>ar1#wqYaNqYC z#bw+@XWZ9u9~X@O`@VIz?gV|l@BjaPp1!wEol~c(&N+3eZr!T7>wtx#!_uJV*bgi) z_5-T8=Ri55cX76h(=IAqW(PhB<1;sCZ#@xR+pxjF7=eo^v*fC6(L1RZb+Ct_lG{%x zKaWG3!y(nM1Z$Vy!3os~2QPy@zdPnUjeG~UoI);B<6I8VtgPR&h7+`5CK{(S!oD$o zg*MY2ML+kb+%TNnz#xug2Cv$N>_9H}I)mL0+DK(>rN{VG{&@%;efQXegJD0Yk79W) zM6E*pKygTh)Z z#QAs=k#n8|fQ93?;MQ;0IzjAH00$Q4XZmlrzH=ADMF@(^WoEGl3xRx3$LETLVZF?A z1*;uBSEyckuDBT1!{-WC-Qz`$XAxp!;yVfPzW7`OAwKyeuBp0A`H%qen~j?l?H_IE39B=X=6UM6BI0*_NPnWo+TOtB#@IQ7(9pS*Ko9Xu~H z8$T0GF5$TL|mX9F)&op&OdO_6^aDyFs0GXoh+jnD<)BBebSUomxOJ@QH`IGnTt|3hECdh*3MG^&>qto?LA0|aH27oi~8b=NPiIn>Q5O4`eSy1{xFA4 za84U68vUW^Hx`GoH0_HDirSYrt9?Cy0F|v-P5^EOfsBK4lo#a8dtm@3{T3jgNk4BsLQp0#Xdwfs z4N)5slq}WoJQjQ*VZ<*(QeFOfW%oslQy z5Xn=;z}wzz%ah2H^1KLmk42X}pF=Es)C;(^U5v=EeGN4)F7%OJ=&qc5H8h`ZhF{L9 zFzM#y!#SbGaaZf5NGx+Dm-f`lU}%P{ZN%hTV6OQWaN!geUd2}CPo*T zY2E?(C9O?=H^095tvcrz_y@?R+?os?{9%VD3gO%UMf9DE2Fum9?q!Uci!l~g+ZyOx zL74Ow?mR(#eCT{Z>-x|Igz_)45pF~`&#~kG|NLHJej78t$k$ztYWPjy3It1h9kC_8 z?k%XMQ~OU@_{fxXmF$V>(6sR#rMRy<7_$5){uoF6La|nRq)X>-fne+RNxOGr0EWXa z5QhzHx?Zj8wYb(cqz1OGgH>(m{8b=#pb>2U&UpAx1ltZ7!4bl(1#BDVBAj+t{ttM< zwt1uv=S%1ruOn-;Me?R+B&=Qy(sGMv3oQ-EBrB4@lMv4obk!elieS`8#)$zM+ z?AQ3~3%T)MVL-iqiCED`D(j#znd=0}?1;R@irb5*I-vt-6|MzffTZYK6{bOu`d0Ei-_o0dW`&3&K zBO~j(32A)mgAsHbyAWUEe?O)CI`h8?erw&jM)ha9E)`=aUjy=TBbAoU4l19(%z zBEtrlxbYH_nQX=)oBCsdOdA1t%gHk9-GLjHm%9R8LKnc+^?t$RTI1)sdcU9^*artP z>`3Iz04DvpM-9CTnUfWkZJVqOlRg2Ho<0*}pdVAf?7EBI5pE5c@gKKQBS2P*HY(W! zHcHvVY*b*_=Uf}=$k-@VAK9q5s?YfdC83Sp2E0ej$mYI8Y|Mo2M7(c8D-i71hm)Yh zhQ4JLJF_9%x)SwXs`4=%`f!||JNPgd4PH*tXq2715;*f@Jq;oiv?YzE2QOR=<=q1c zngi(z1?)3p3NDU<`c>cRStfP@7We~|y`AC*O`LcWvc#jo>TpwnbHIiI^E0*hHi1+3C1cDvy zoCGC(b+U3Tc8>ES(3WpdS(y&LGI$`l3Zl*d36sYgHk!P0cAo^9vnwP$0&{mzhTsu| znZkCTCVn@<>f7CCL-?}@PfcMv7*PzapRa|^=u7{BDCq1t-1r3?fi9tibbJSa*2ByU zb;i=b-7Y3mzihTBp*SWmfMEf}Er;UBtw8V2GMeo3!!ZIg-Td>6L`x-OcJqWyH?IdE zWn1HCYr1(O5YYwQJSB~s7-yQPP0_=P#{2nwAy59}J~ZORHY=V%n`O>1GZUyby9eri zW2if0XS9jP&cs!AhBp9ev(EtUvHp<%-x;P75Pphb>QXkB26e10uR^?Ub1x#;(dI}{ z;#<41;+^}}%TVsS^^Sn)eBXL74DB?dOl9Z31f21$Q=l?I@~u@c0;92if`S?&ePN*O z5)OOi7l_OW7r0f!S5(p$rW;sF=!Wdp5?AS{5_!|Fz6`v_3QHGNh{f2dH*RgOATmC-8puIB##Wp7_hQ&z0mJRU zKbw_hw{S^WH0*i@?0mici<}|;;yDfyl<0MR3aB%^mRt8B{mrT?)A@Qm8G60O=vCR- z?!SRF3j7oyDv*f6!B`4K4Cjk*kjncA=jP(k)aFyxhk%xt8}>+lOgeZ;PDj%f)o+w> z=z{9kg$sEXNGP5>1H~(2!(EDBVCPv4D4sbQ#rI>qqW*OG@<{Jda_U`$4D`;-VtN-S zy&s4w-xA8g=$$f-^e(Q_dtYQty?+F}$C@Ji3B#->!tXImsUiFU!<3C|VGLzh z`}+I>!9bsQx8|>*BLVceX8+u9uA*BHU+!8nn6DA+)m7H&?sh&;DcJcbm(OHyl0sG7cqAKHDX4WO*rPVKPNgTEG%JcK6A{cB0CP1?@!DlASaM6*f5&sgedp#B#=r zNFc`%w4vKV`5GIdULzY)jL$$Ti`tk4B_t?rqmGr55rUg!e28?Ny~=nkuvlt z*qiqw5O@{(6K)RP3S$|C9iorG&L@NZB|`Hg#yG_qL-FfcD3C?Fq4+IE5i$T855z1% z8E$?FBBzHE*an@5&~`})l7^yuEfmNiWhg30ffv%+oEXgc0G&4IOlRoH*g|B`U2w}! z!~i0HIIJ?P0SLMpf^KUi=xzwsA;BS?ap++Ps@6)-(-8C`!Q{?3^fm<5ofF{u6>D*#-L!S(5YgK*Y0xW2>%1`_otc&-8LMmY4wD4(LHNu8_E_ML zp^#M_`x9n-owXW!KgYAhX}Ryd41uBXOM@+YNZ^+mExRf5OXI-ccC{Pk6&uqwlQEF+ zPnh(le^P1P{vb2!O)7xO5@q6sL$BzIuw4pUWPkhZvWnC7=$P`Yck-B0AaZv zI42L!cXT|&VTk$!at%m*Lirxd2(WH&FHGEs*W^|)_cez5;W6%9{<1OIhCO2~Aoxh$ zaCo%43GV6|^@WXH*(nadiX8QheL0b@veo!y<2csj5--?!cAld43ugS@ILaP=FFuaN zLJ5oi_&C-Glog_zlT_s9_*}X+944b#s+bEDsD5-Rq_RBJrSXBZtH=k6t9;4AIL#}s7uEgUVX%lY#JMlgAEYA1}7YjwPg`F?? zv4()J{`+q@3=1>THO`#?@UlNfP#A^p1?De?Gx8dd7%hN2^kBUfX8neBE8<|X=re3Z zx{u+n=WH^I8Hd2&JrLH1z^9PCu?yW(*&2};JvgAvu~I5}yZiS*|E5Yu7Y#P>sE(au`XNR*fDZVHUq zj(~xd-k1|nsJCrb5$jw zT3e&BOq8DOm}#_X55jVpTTC4S)mFDhz3!4enA+%r@{05!u4=0|RzmN+x4_+FWzi0Y zGt7EPCtD&A(?KJ^z>JCMpaT+==%5Kvo$4TArQx?$sH{vE>m!Z><2Ug~NAMwT&Nk1w{%2(wEBr#0Y9KU~F7Kh(x%xoRx-?b|bv&W{A5XCj;5 z7Wa%d3RftO44}A^0%o?$^w=TITd=7^%++z=@r5;mE*a-%vsowEtv zDU)U)mcEmhSoL0l&2H<@JqX*~J;j<$If2XF+jbA2i*^sh6<>UM8GTA!r;(8}p2!3e zk9RPFa0JD^nD`Pua1q>mr+y%5ac=V-$)D+9=7-|uuXXy4CRF)0wiM6}C|@1$F?SjI zS`UM69$$#jd$MM5I_+M7my(54+Qn{+q<5G>@}`3fy~7y+Um-4dm}Rp3d#J8Ag&3Fk z>@*!U*r;hR#W;8hs2y zQ_yM~3qgT#-6GuDjzDDC@7E>#(^tysMx)8>1>+4PIFeHY)?VY#4M2MXjom9gMIz*) zGM8Jkapx}?uxMVy6{bC)>7>q3!FpL_t|0Wb(N_U7Csi1%jzIK-*g92o_M|kf1934= zehgR{I-T?)7E#6MBO6hcujC_-1#Nx}%0V$X0AvmbPiI)i(zr9kkI0nsEKE;=(-xD- zSnY~v7td8mtaE5V*x4QD%R!>oQ=-QqB9ujzAGoZ`CUUqAZn>HGnQ=TeTVKYJh}#3%)Z3k!Bc%@T~oq8sXvdk5QR{7s4IPRJ?{6x_RZ%`u>1}s z3Knudj3Vl5O<2gy_>_~zJEC0O5ebK%?!srb5Q!bHxaOS2R8rt?&H9r4z&kCQI9r70 z;F@0zI&8AolX6Yurq!kN;=UoDZJzQCcMZ(}(Hxwh0GCI4XA~-u%5MWdWR#fF?l}r5 z%#3ZAXDgVzuto-j!rGWHuU(i|&LqSafXH=gMCI|Wtg! z7D37RAk(!QJihJS!wLT&o^1)U>(+y)Ps=J85juG9GL#D6ufY{N)Lx)v4Tg2*Ux7`s z5eI1=VwxBIG?y#QLX+ktrYSJZtA3gwzuObezK|)_I)T#J55Zx%gtHk){iq3u(hhQL zLWm_ICBB5hL`=^_r1YbhzBG}s>zSyV+6?Sq5mFmFpjBKELIXpiz?v7tN_B?7IUS9s zdNAgDNS>bySBWi+Q>2|M5fe1OS6E?qQ|_Z+e9-)M$DAdz^|_q|ZegutOZUcXX)y?M zaXY0gEkWcT*b-LcfG9AdeM`)!%6K1bifg?Jy^z_T-^;L$nO8$UKdK7ZUJZ^gyH{X# z#YVTdqtO|g+r>KU#$ub}`+%EJzlZ&{at(MI`PWL~m9=UP7+_ty)=ILOWN}u~R7j2! zT}xS0F2O>B*%meBo1tE*G}eEo@U@bmPz=w#sUN@-U!DZy48R3PO~@xZ4FF8z;^E9e zJ6c&>C4J-9)M=cx+uw@o#CB9|+R=mYcEq}3ZHV=VU)LK@PNU19t~d*QEw$_-F?5C0 zZ({9^L{+IY_Y^s4RN%t*}!aV>H^Y>{O7(`k#@I-E>QFh+)r)=aSpWx)f4)cNFE z7J@B~V7?SLXOyJYxEjjA);sK8!f8Pik|ee<5@vPuIKO-7Gv+*%gHI==dS!a|s_fkh zN@KaIu&Te4YB_M%=7nspt~mOqt5H+83aF`@pAAm<$g_mqtGYBKtGeT?pzb`(h-Vpr zUjB_o|8#|a>+1WrF7^Fzcu_|VqvRF2UxjTUWUhwqg@7r92 z9lrSP$d>GW4x-5q!=<))__sLhYZNX~q_>jHa^i*g{|8WnfVIz9CXb_CSYJ%zDD$-Y z7Do~Lqo?!kQ)$+i((Gr?CY(P27`VlFagzB15gFy|7iVlI#i{X&vwA3}{3Qg#b|N@5 zT#lm+Sr$dLS5Lz8x_QETpL)O(UVPqvUkH-_;Q5Yo`(h}majr_-!ep8dpC&wlTrjC`n}>OEf6?|z2Z*c{URi1$tPK?Kbl zQrc?8oY9lYhXfd7JcJwO!MQ9AS~gQiM6H?yEt(1T^*D-rjhEMXIS2ym8dpn&yP8i) z&d2cDaotRSS}NT4sO3lCU2zP8j+OHb>F5ai6tHnrt56zb@_XmzfC*!M7e=TPOHg-2 zsM$nKjg0m=p-d?`32{`M<;Nki#_52xzGEGj(s=_3ozGcEKD_z0*hu(yh@S;l7f0lxM&D2LWTA=4zlL#aOD3w*^~l0+b$2N@RsT&^-=d~s)qlAJUsi(u zyafMSiTJGVL-C4#0_mN@P_%d^{Yr@Jl1v&)@a;vJ_=?&cM1G}?6#c)bvwfi$=kSvJ znt|_sc)SD)qZAWLRQXhd+OG(ue7y%CV7NxMC{4aHpm|avV!l9N%ojWrwo&5?*oGrt zKrZGz&lkQgU^Di3kuUhR*cUuZax;baD1z+cIENUmlOcint+lO_`3VfMPUc76{&1bl zMZ~*rM(M-cQFk+Lb4&t6 z+~O^e9V5mIocoAJFE!EpLP0$Gp=U(Rj}^pS-1{&aFyZ$JB17{YVcBvj8YU+94~iL- zkfXibH6=-Xpd(dXRLb^h%y;A5EQv+K!q;$$q|q1%8ErbaAE?6OeHim0QU2ron=~VC z?BB#y{hQYI7$h?%QqmI=^O>wc%&Y|l{o6C4O&FV|Ek-sirt06mM-kb-(QG_kWYfPQ zHrBsAjd2&Jn~uM=VzGGUCrL31=!}pJyyARcZP$&1^>Hj|4It zru_gz@>3pKWK6Zq=^sMW{1(gJ{_I!qhEYMjzG zf>Dgcf4q$-TXADEkT%r}rUi=jbI&sOa1hAy$6AQMXbkxG(8f#~p>0QPL^-OB{N}e2 znxV&w+DHWo9&0135bw8<7ZEgLK(lW3AIgUW7z4h9o4*#~B)vmqe1&EUons(Su2g>+ zQJ8`MCvJtKux6K_OX#YjP`l6^X*_spIF_1>S$N46|94= zc?0A$L1G&Vq_kbe8DH}zv9`;bGnLI5a~zf4Ta1OTQ5tNRM6bvweQq^Wu5lQYPhufaL8GLo9U7To=TKV=xt-QARM8%F~0jJYyLI+UWe4$Y6*0~}m zTPV+u<#>NeXjiYYGC9=+E@GdcUVfF-^4C*0O0~eGRQcYXBx58cn#biGs58(|fW|xY zq!RfOF#e2)vxRt;SCM~^cEpeVK^lnqP=jR7L`v2{AIjp#3|nB(hpr0k!q_$KF|unh zm0ee%khE)>j>n7ax({Mw{^4!J`*!^ff`MK4h5F6AZxX<+<7ber&)Rpk9=?J$YrUZI zF&%34E^fZvo(>%B7R%*nx5~JK-Tn*Yv|C~uCz8@-l*~4?LH2hUr?ajuh&|{%fL@@b89CLq5tDx%PjdO!@H#0@*qpC~hLNG^R@gMI8rA?`drYw~Orp(M^rYtb@(JzL!Vr-c<7}>J8 z%9i`09JFPclgEo}c_YMz?;FlY=(8ny5r7>Rpow7f@+6wXdkOVlxeB&>A79#q0 zeR&Mgb6!$C$rs}J5I4U~OaTsU;#S_E%s46-y zNypG84ZIb6TsKT(R>0vk-x`o*oy`&7{9FL*>kBwwrYf1Q1flkAJLk~Ne}YVC0nDti zk{QotRDKSSeASQ==Ux>yj%9m4WXEN}W*P9OO25XW$37o4QAU|L@N~-n$fQ@MDpQqi zUK6DDsEw+OQA%SFt0Go!H4QA%og+<2Dyu4+cPvV@?x-!02+;$c4Wz{KRb|@osTDMb zb2z?y0}Ubg=E9c1VhX;CIc2fwzwmBVU7CjmsjJmi%!{Kw={QDZ%x56M0*>=<-f2jX zUn4nU{{Gw%*OOV-Z14PRMq%a3N2qM`)0o~dhio-hb<-;fqdo5Z%gwzGv%Iz0{u3AR zrX1|5Zr=M; zlg?jeo8OxqAT{nQOKpC8wRzey+Pu_iEMYDzq3Oe4`RqswH}=t9Jq&|9;}teRth&FV z{tq82wD=GF0_Q=MrMR|dncfV6^#mD}hAu-U6u1nQJ?1h5n(^`W5cbPqTQWX_EjaQS zVk@695T&HgU~}Wy%3L4wF~d}l{KjBV#QMU|5bt}1FA(h57m}c)FC0cubk-N*8-1|R zf2yoZhdO?Vo8Ruc0*7`_GlrCiUrW3H4^SQ3Jtiq|jDFboxrWMbonM2Vt(zNtR&G`?LCD-ITeG%<_&T?X-5^h14#IRplndfMY&nRG+ zCo5r$Z!=1~bnNvf2$KK6_BcNu@uPuB3W0$!_n3hRRG&oqdL`7Eu`k*}WMASc`x=6R z(7vcvj~9f$Vwkd#eQgN}tOjbrt?e5`hJ97IzJ`Gobl#n;q=|Z~ACT0y%OA-FWh`F* zM}iW&+>TP}%r27_9!0HJRY#_SXnw-Yx69|i8+M5|v9wENjCFpW!W6I#3S`c49MfR2 zaKT#x5Kn`@;?`)9-ert)7_^ITJR!KCTO&`Nu+dk&#RTn4fSpjJIu2C%w;B5zW3!%B zj)%$<@aZRlE+Ku>4TjDxtOv-=J6jK@E6c!DHkVLu^Ws(~ll+&-b1FDqL$xt`GzQV; zDH2$c7?>>Zgvp!?VDdiqsBLG`?s}VonbK~K?7Ja~MbA`^sw07_hz3!`m7}b#iRruT zckrslQn#fJis#w*?vDSsoklgGow9i0L;i8y&l1UuRr2}~QZNO4> z3IpcC9E~OHOzF^Paoq)vX8GwSV*>P35d-R9#SHuuvy16pV5t8$LYW% z37J#>6uZX@!W9g&_R|0Epor-|gLq&6IP=z@GfG(8=8Dy0&?5;jXVeupf6i!k%^5Mi zFuB5>-34h=2}GIs+)TfYcg=p;jl5X5NM6cM^4imv7pv#-0(o_3n3+mm z`xeU!D_sM5^+eFju_de}$n$OGNdm|VXRQYEnj(2Iz9QcUd2p#jMSc`Q{&y*{5Uu$* zJoK3lBWAtcAT8`wuK9+|2J;Sh1qk_(p^+c!8p%%?Nq!T2`LVhlFOu28l-b~x zLvd@vS&&g*$o0m@qgQeG@n`KLBb2`-4jEk3BjTjUK;nJLtE4|9y2L*niF`ZtPYLbe z$J<-fjp@(?`{4%v#N%&HgmbzAdPFl-p6UDq$lhr}L-*DL866A}h5fLh4|it-Fx@iJ z5RdnyiqL;iN4;=@r@(T3i@Bi|r{Qc0W*PIL*LBG)CuV_XQI3W!2Si-4z-pioFe~F( zj$pky-@_jzr(aR&0>8q{V%jFC52l8DS@8DFmxs|cr5x#6oTck2$ezB1BIEg@M%PCp zHs()oOlY934G`?upOBzLUo%**&ia#t&WyzCLX?l`P_+$l^Ys-wu1LA2-9dCY)f{(j zLZxj4S}KiL9=AeRXFhhrW+I)7i5s3&D$VB$PkKVr~*kHFw#0mKdrb zL!%mpRe;kEeEt@~7M)`HQRMKNFc#{uF`7i{#&mSop!e;MSIBHo@{c zteEHe#F%dCfcAA$PfApz7uL#sgz?k|Dgz17uWW`}m(&=JEAaSj?Swxj81($9Zbp1; zw}I@q%M$N(hM`sX3re|_67jrh(Y6V4KPV;6wCYI2M5#3#YB~(1K&QiG-Y{GW+Z#Z8 z1hl5U=^PeF6ee;nw8Dmyf<(%orZfZcbNEf??0L5!wfPM47^E?!kI;HNa0;NT?W!uI zm5HgEA$JA>>l}+*NAQ~rV8Vim_9w?~=__ndNEgAKAtkD^6*2*WfF+38ud)2PSY9Pz#_V$W*m zC!#r162MQa{hXEazz}n$=koRkbEZj)Gjy2>ng;2X&d*c7dj+nVNwHn*&O{q%BFQC))e2&78%AQ@ zj7)ge3SqOFSkIbU5S7u-foR;>8W6X3=TW(VE<#Fo0yVaaeOG zomijo<(HAaGR#R!e4OO8bP{2`9aRBf1>h}0)B8O+~5bXgi(HQ6L_duppg$06YaT0W@A8D z;ch{b;S1_qMgBr6oy+UJoq>a=;EkJILCs)O2F$>SJ71GA7xydkibc}JHGNzXxf}gn zAx<`$F+Jlk<6yOgoMz$*_+WRG4JC!K{%{nwVV$%obzf&D#wGZyX8@wAmG#F}xLbk> z-ySmeEr2${-JXS}Gn8=ku)qww?QnqwQ#SJPMM5G))<|ZG^C~?lovkkuBAdSC{Ba;t zO35cLp>FKYt|C??Zn$cyE{T-n%sf(5`TuLl;j9HCxlAA`BRPN+jrrEqh_fPvu?yA* zTwh0^VN9t={dA_%YZYnDAB(g^Mn*hMuW;@NEyty1Vr^Dn&}K0khc-)HnKsKt5w%(A zP;K^Nzs<5)aPKtR=?)Cj91z}-VKxP|)kSQxQKK7+c)!u@hF~!EXhGxpO!<=lZSGIF zq0N>1ZH{xWwP=|~+r`dtwC+81B`WR)3WMW#XK7&m1su#4 zrE{(c;yvuzVt%}N`-vrsH)2;=IU`1lLXg7_0x=JArfe^Y)FA12uwsN~_iY8J5(G(-cv?YD((WNzr4?gy~Fb9aPr%PH6#UEUJA}Ky>deO`(2Ae8YEi zygYP54MXBFl044|j!M|4xP*mWt1w@~zGBL-W8+E1Y+^h{%*GZmTep}Qa2+(&d3CwF zM_z%}5c3KGqj4bCC&Dk#LX2NvyN&#UEJuFfZ@yn()Ae|PFW7@&76;+I7-qAOU$~~& zFJM<<;1>=+u;cS02}+(9uLp~@JTG#b_l3&MbQrlDh?_q*%)NEdI8Pa8y@|kMb?`{E zaqal=4q^lz9*J)}MK-}G?E%8eSkh~Zhgo`tv7w8%tR`KYt}ueMyDga27tGzDGtPp35`r77!i@c973tm{iS10wOKADt{@{ex$*eH~)Vc~4(^UyL0&F@~<2*_L<2**2 zao%+l$y&yF5ep9G#3~rdF~gVv2n?S~zYOJQ^iTan`WIK}e<||jSdOChc#)oOMl7C7 zZ^NzaU}h6Mmtv3i4s0v{N$6PLFo59$FdwHb9+RW*w?)jX6N$}cflb3Be^gg#My$IKUdTY z&ZJA~!jmb?!JHc-ptHx;#j$9| zkWWr*f_V$&WfK-mY! z`d@`|F!n*|M)o1DvX7O>oc2LcdAuMzoneXx;bw*@1li4lppK0jjz_$&y_pDh^baH` z(cfdNZRh$c$2j6^m7D2M-4k*1{R7U)pnt%03{|L%XL~>!B5w+Vismi8MYb&;`_SEgDvX-gT zwh@!x7h?mS6H{CNpSbDEiA%}PSG;5#Jqm{x~nj-x|d64^OTi-@QczU|Ui_g0{rs z#@dp=uq}NP+N5bqwDqVhiL2Vu!zel163y1*1z~77>Cv1JhP;vTe9hw7e&o9_$vMngIkc!)%~+U_YpnrV>J5u&{l zQP|pc32Pdp8#>}0;n6eBgD^y|jdVGYLBEYblC(R9Uf&$~ptN#(E9^YW3OzH<==oU| zo^CJndzV}i`8^d(xl>o-6zGas$8;r7y4oJRz6KWutT~RC^F9@H? zFhwE#ya0-re$GI=ub;mn80aUO6C(liv-UZ$S6N{@{|aw^I43p&w)?&6LcS2eIk@@0 z@B`EnDy#tsR7iZwU0}+u(wzmsV<(I-Q+Vu&akvR%+EXh))dH_snO}{D&*c{GP8{L5 z=K^m!xt2>f*13?WUF04$@i ztCuNX*;RbKHrLVfANL1Q?Pz1<5L>Yp-SE$dSeQ|u)tGs8q9&Y(c z{N3P{xJl@fIB4+t9HKI=`TdMd9`%?RQeLv4GyTW|{79;`qPIta!j2tki&a0Sy66XK zsHVPsem~oe^GSI!l&ZjSrCj9T~g@-=RVE zM*Qj-2i4v+EL%=RgE37Gidh(9PCCF*Fd*o~6|YD^J2fe4f)ttnkQ_{%G^FjP2>=Qk zcE_q3yZeIDhuv)s2|xeSdH?Ip5Dag{xOj^fcCsdkSSgs*3qnnd_IhAC*-za$mtP>w@s@46`B#-@!0Nf$-f7vxL&kFU7jK z2l2jc?nAJnuOLAOUx8<=wfl++*k-%x#I!KShjEMf3ib(iQm>V7V~j~(p+ea1{h)K? zCLTcuT6vHWeD?GnWdz&=DI43E%4JmWjLnoq&)A$!(iBWdlfIXHa$*xSX|jx(G;{7% zUR#AvESn~+6g5E#HE9*sTcOS(TM<{;%CEkyQ1l)zu!DyfrbrQffMH5aHjzZpW9{`Z#QQe! z1cDuHf&?9GBJ+DT(E~OC$A-4fw5amaxW#PZJfI)OA2#t3De*Zg5a82VxW@YTZZL{x z5M`|7?;;^wq%nS_7tN(wH3Z_Gb`1F-aINbgax_mQ_;yq4cd|3-O*vBDZy3pk_ zPvi2-jeRHuK8j^hXa%q-y7JLz@*F!);lMZ;XN;qQ(kdQju-)hn0q{pViCEE2#4@lG zW*oB)qLSG@&!$ZIzqpP~8`B!+4Zy#kPmkj)pvxWjdRGgM+;nfv1KT zi4RcNE+d>%pnlVTKTmv4Xed*vQF=pn8t5AE0ScA~s$p8AnjS1-tlh@XgpblwAIdM# z2Xl+*L!k7rC3Kkx>t*yoc}4mVSLtINK-qWdls?b{5y~2Ti+bX_ZW}l8{EM;GvoQvsPe|B zo{pdM$oL^9eih@RC(&=nNha)l$HJL;gv(&-N!dE_7}I@^o4+SuoCb1?FHEg)XTrSf z**^f%#sCLzW(wLEpr~kLz>lCn)8@@=4EUB2Igfla@KZs20&NT+12kwQbP2>Z2ArwU z7+J)cszCGJbbFK^?-*czWNX?gs~5FZWv8|}(r>G*qQ?sq|6higr&PKF+ifJWpAheh z>=y*hIJjJ6{FL$}0c6&Wn}7b=#VC)&rnJ~n1ypV@X2RUx5Yjq?JeT1Vs)8skK~R*3 zgSV1}Nj4A+VW9#O>y2`RH@Il|0T&^W+QjgsbvK>m#Meg*@Go@J5fRj3Z!gT)8+ zr%BjM6ce_uqFBxkR!Sf601BU)B_j#|vbQgU6B{#endyjA#8!Q!17_4hIY5 zovKkbR~rRQa~7)r4cF&gFG1Ua5Nd^K{ClBpeIttc1DRz ziiXD}5~hoZL||lpnXpVoCML1gGb z)@{LilgK%ZOgKC)!*;uX2pvLXvfU8jo9sGdP(0s6Y)SuqF!JiOe=oPDV0?r9 zhwif{Zm}`oD(HjXq{0|*YuwP=EBBWD!P2V+t?im?4}?7yHSL}bFVes-R#h@Z+Wjj# ztv1XU8(Oz3sA?NF7#JiagZ9(`;Mj5s<_PhiMv6@S-|#-p4t&zc0_LECc^7uWC&F-d z;1=|)0cQxrlpDMb9u63)QU6&e%Qg?dT$S0HgYQdqCySgoVBFCXaPxU-NVwF_Q&MnK z%FpeEB=sZH?wKfm{cv;lb;2S`F~inJ1xa8ss0lLQ$4ppSGkTdw1$3C(o(S>qAD=Uj zbX7l&WmL~#EW;Ya3{+s)X1jzsH15l`R$R6^ z8Pu@VDY&)e5EzuxMdm{^o=v9OE^4kh9~jIf*2a@rXH~5{yQW- zDn4Xl|JZ2y$5)sqPPDh(I)t1t$PysjZUhEBF;k5;soss3l<{Lt{6~x*+~Tn|o5g9k zLl_6!Rq9I&3Pi1Sa}fHibvV&r?E|nA+k+XQnuL! zxzaW%3Xd0r8yRNBWRGWn0tz^b3fLNvp*_M1PD8zE1%DWfRI?pe_^R2S+)&nHUrK@! zUwQ%Z?$nniEo{ZJx~r~ChvvI8ZvMO5DR76{YEkVeSFG{`(!6ln1tIz)AzVV5v9I3| z%Rd3+yw@Rfe54)cV2f0-`9-L&H=0RvN*O#&87@qysr3eYq@87kS7T!1kIrKyOM49|rw*jnWCp78y)Y+(T%W3Qg+la}($8PX+z+DU5rJXYn z=$4tyGRWnaY$CYAy`Oc``YrCN!LmUx8CHR{9V~p>9H+fvb!ECfmDYRHw>s7fi#|ac z)vGiUZ%?583bM9M=R>@xAjR1_61Z4(&WWl+O#XdTM@7-6$w~x@wVXv6#{@bg;JC0ouX@++uV17*bhWbEkG79=5v=Nc57Q4+`wTviVg|Y~Kmq z{=mROLIQ7ZM&vvul!Y)*z}c1|MJWrsGTIk3wJ!Y_mU!}JyX2dhP~qMKXfyLXW1`lQ zV@+|6etsIbooX`3YJy+=5z83U1HniSTxZuSl#kH^gd#<+UC;~djs?m9ims;e7<6@Wb8O_)C_2?j*u}Q{)wMNv=oQGm^q$!EYE&PXvbij z@73bVN7u@pR{mYwebzS%!R7WP2p*Q3C^;*)ws|GV-KU}EsC+Ra#s{|oBR-TOxu3yG%@)P>6xY=1W!{%o;vFI2u9d0Z`tNy8Ed*ZvDll+_}Pcynm-dhxgn7)bn#&< z9>HphI1&pPR7xI`UPR^{Nj7QkDBK!PKqqFqV(c=aXR>E`&y<77_d++{FnBOm#^ILq zNSL)cc?;igKy0sc&vbjX4SYJB-}n~vP*ols>sj0U6=Jvo@W#%_t)D<%l*`3N$fnujw*`nh4;^TmK=3D+GR!$B`s4xfXej&i__l7v}Y?VG5Jr6(#EU_pCGAd z{&))tiBC&T88+kP4+)w+j)e0jAw66)+lO4V8j4KVU*bpW>V?%fm2k%l+p;&?r8UM( zHGd9@v;9#xWlUa`Yy+=~&Q75GmpQwmI&81Tr?NRmK2>FqPrV5hW#7go?eT)};S94e z$&W4v1^nn;xV23~WavlnfXKaeG52va(0(6yEGbbQj^U&AeI&pbe(me~?n5qE-*+!> zf3Uu<+!~A~npHi?7sKNjxcTdIKI7Q3@@+isaGpd3a~+_YFWnSYp2umC!K^*&P}{KH zz-;pqu%&dq7P#hj5!A<)&4$&tbA;6Li>G3+HW6ZL zc^u4Z+la{@46AkKz-;VHpIo1oxd?g3kr^IvBoeHK2Z-}FaF%Li%ellKFZ>!`WX#$* z5Ah8JJn;zUvJO4d`5IJS-&VqW70K`+o>!4IzPa;Hd}K&25{ndz5>=W+IP9|D9|1s#jWiGM8?~rc9%rY z)F%S%x5rte^xNYY+y`R~JXTpF7VYtu{cDCNQ@LS7@#*eg_RkH+s;9yB>H&lXn{bV_ zy4ub^LkZgXr_Jpt-pJE@H>tt0H#K6ktqrZ};X$~URkw`^bDh)-w96XFf_X!N9&Ub{ zIbmVgI|Y~FG8kqToC#WWkZR2*&Gwt|~+r5!EQ_{3~kJ-IJG7lno z50QabSQ;W#qhOYT^@%kKfv}mNUGxcU!L$q7Y1A&1soKTks0G^v&Bfyd;n@sR$7&bP zfdcK~1>D;Hg2=F4bR6$TE#x#1`Yq%Prcr;zHC3j45RLZn^ZsKh5!WU5hv=-)u|MGe zUuUfzY3E<1+}VVNbOndOec710uC7hhph@*XsIRrU2AfbtwYs~Ve}f@A{~B+2|F3m! zO_sfN(2gcq_T(Tu&9aXU!aG=-wwN%#3wAqv*}9S`%MN9ni5uP_BvC%05$#~~5677Tuu%f^v?5{D(c&8JY z6Aoty){(VO>J!R(v7&&YfsKk;+RZZ43)uI}756pb(0D&eY0qXyWZ7ou|LaTsO3U}DZzByK@iBVtt}okbY0)-R@7g;=%Bl6qCFCaBn6 zEEpQFgk~h5ieYk(+6*e@GTUBhyDE)w9m80Jj%(j-58y;ctdJak<}qJsoP>8iN&O04 zf(qf=qhaEOy)r!cm(On)xbpRQpr@!-97)D&^|PrJJWZ%9e66pli&0k}zgDYT?f!-` zt}A#Nnq^QWQ-h-Fn9|`8$nj?SMrG~oNh?Nq*b`9MG)()HXxm1m?bRn>CdN3ZeAZYHB^^xdevut9`>Z zX8abtN94DZrTo^*s2u$ko1w=G!e=o|Q%3k4hS|vE!&ZYjHlDr!@xFJOkD!@TDz|3A z0Q)I_65x6K65Raf@jk%md0c7T3xTN~W@dCQLKs^#aCPp5wRwvWYODo)g_b z!Jf9W09qRue6WlUx<0zIr(GY#hqP`z6Ki?mu?DepL15feCyPrJ<)UBNo+^kIB`8F*A4>>#glpwrhkG;v-2J_D!pT11HEh zW*q|2T7xyK>xKF;_Cftc_MuY9KHl~1gDUiRflVx6n0i8ZA;Xje!k075!XkVz!z`@q z=EGvUxdQRN-CT)aFz$=iosppAUCL)*(doOCa%(tjWPR0#>Cp79!p$GE{ReeMKcd4) zsy$`wE(W@pHlydPZfO^%$(acpt~iZP+~9~mztho_(J4r%ffW1i_SB0gl0RicOzWw| z=$D8#Q%1z(HD$!kB-1-T1CLG(UHKW<@_c{vUWF=rSCa?9_f`};w3z$98p+vJvG^R4 z5jI0I9_sG>jp&@P!HXSJQPBlMG71oml5xhbeL{(rK5sNYmi)*4TEvV#NKAv#6SIz4 znZR)D)IZdTu`%j8vN3U$jeU%Q&<9a%9xn(lVVH`N{rm?MF~4&S;(hyBieN|kAwh}# ze8<{$wr;W9!bH8bfy&BssN(gw`S!!JB4Iy#1q&~(jNR*iE`B=ihNtt1KL=lNhehS6 z<`ygqPW5h?gee{Eb8!wu$h!flsZ8=JjKzw4+?{z)y%~dZy>mlve+KWj!JLNEd{BYk z&DO4lYsny|^ntofxy0#0re9{~SzoA|aYo(WPyiZd2k-ITddsMp>r^8S)Uh}PI%a+` z9SaO~ykRIeqhsnL(y_Qo$6q6N>X@SSctLm>!xW};{39r0I=&I{zK(B3u%nJiP@>~s zS;fwEj2^|>NM&U@RPh$vpktm1IuiQK8jn|9v@w8Y%7nt3%jXkQBsm#9j%T!jA0yNS$l;uUC485*u?sEh18YXQwM zC!@JvSS@HS(A({|M|z9WQ*SC=pf~0g)0;r)jq6!A4rO8VMj1zX6Ibc&XXH)2QCuD` z(p4GOIK*^?J#Ll0t}a2aqpnC$qN__0)v2x$+FLL{s9x~fAwbwzRUxW`KA>S_dHx>^Ap`ntLs!H&8jL5Z%` zfx0`@Rnj^deZ@ePkLgggdvNo8=SILmUAy82brIk4Ay*me%}9PF9F_AN;heJ?H+anj zIPkk34-TsU8p!dWE$9^U7+5TJF|L{X>s;P)FSt>)xV9I4zSyYlEW9XzY| z5~Sv4L-H#5;@cJEJ{4i9$7iV|9l10atm2rc8X+sLbeg){in>N*k6p9@t#(EhO z>qb2k?>XS9o&3l9>xdg|O=1abjd=&QhDQj2p-Luj07ciR>S&r=D(Bbqw^{s)1g`q;?`*&O}uYs{eHn1uM<1F z4i*sE8KJ<=mLZ&WugBH5vxmTqc1Es+QP_inyNs-PD41&=oR-#l6_!mK<@d$*L+^ej z%PCdhL&(8TuReOYoo9WaVDdBy&Qbtt?V}?O)T>Go=#}}!^eQma>!46>Mz7RIq*rm3 zUaOHi^-9rtyhyM85F69$!-)6w`Y3{dUZZy?B!FJcJCr`d*25Ij8ciwLtE((dByGe> z+W8H6`@?rASSN2`rUw3xd{OPkaEr|s;dP`n7WU8Aq3|`oa#9E1paoIdeF@8n)9%Z- zx_e_r!hGWruU^Y?e(YX|^}i`=J}KM8BdUDlI3SxiEQquC2RxIrjLm%vyi&xP@jko< zv%=$6%6R~EX2|vk$^&y?u`u&Ap|p_tKB%93PbR+xK6_x={U?fEkD-O~`4bSPPR@at zSV3ibfDeWmVJZ9|3+@NQvb4O7he>BOEm_e_6U_ z@IekgBTpmTpr}!yYLUBhDGP<&dZumx(e5Rc$buA4L}-adx2m*cr4OE73xQ#FTD$%p zXH_*D_&CLmNfah&yhEJ?qx?rFUq)n7>+pl5z0#ZubM8VghBIGOU8)Q!jI8;@%x|%p�b)F3!Bhi{qnexhgY9hfQ0nUtj}*+feP) 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 literal 488960 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�G2&-% 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

^>B+sAPv3)j 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�Ca1uwrC*ZLqMzVj-^aFOg#Px|O4 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

hsSMRM0<%c?##`#o$cP@5 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 From 21f3897a861fe97cb0515a71fc7fbf206b15a82a Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:27:34 +0100 Subject: [PATCH 010/101] Lock material avalonia icons --- Directory.Build.props | 1 + src/IronyModManager/IronyModManager.csproj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 913dac82..b989fb2c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -26,6 +26,7 @@ [2.1.0] [0.10.22] [0.10.12.2] + [1.1.10] [2.1.16] [2.4.5] diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index 7656ab7c..05cbf109 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -132,7 +132,7 @@ - + From c1d9e99dc9db72517931f00cc5568740b73a527f Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:34:15 +0100 Subject: [PATCH 011/101] Bump magick net --- src/IronyModManager.IO/IronyModManager.IO.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager.IO/IronyModManager.IO.csproj b/src/IronyModManager.IO/IronyModManager.IO.csproj index 39e3f044..25b5b72e 100644 --- a/src/IronyModManager.IO/IronyModManager.IO.csproj +++ b/src/IronyModManager.IO/IronyModManager.IO.csproj @@ -43,7 +43,7 @@ - + From 4a6e82240ad5626b2aea861a784067be287d547a Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:39:24 +0100 Subject: [PATCH 012/101] Update netsparkle --- src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj | 2 +- src/IronyModManager/IronyModManager.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj b/src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj index 55dabd05..381789f9 100644 --- a/src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj +++ b/src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj @@ -28,7 +28,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index 05cbf109..ff78d7f1 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -147,7 +147,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 82d654af1dd895ec0d134433b67390661179ba51 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:41:29 +0100 Subject: [PATCH 013/101] Update nlog --- .../IronyModManager.GameHandler.csproj | 2 +- .../IronyModManager.Tests.Common.csproj | 2 +- src/IronyModManager/IronyModManager.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/IronyModManager.GameHandler/IronyModManager.GameHandler.csproj b/src/IronyModManager.GameHandler/IronyModManager.GameHandler.csproj index 79928b56..3f98c613 100644 --- a/src/IronyModManager.GameHandler/IronyModManager.GameHandler.csproj +++ b/src/IronyModManager.GameHandler/IronyModManager.GameHandler.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj b/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj index ff05204f..6240e953 100644 --- a/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj +++ b/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj @@ -50,7 +50,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index ff78d7f1..97594a6a 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -148,7 +148,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From daa23ea8e795d45c860b4808b3712a36095f1b18 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:44:20 +0100 Subject: [PATCH 014/101] Bump splat nlog adapter --- src/IronyModManager/IronyModManager.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index 97594a6a..99045321 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -154,7 +154,7 @@ - + From 998cde9772a7a7aed91bc24dd582b14100382ce0 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:43:40 +0100 Subject: [PATCH 015/101] Update SimpleInjector --- src/IronyModManager.DI/IronyModManager.DI.csproj | 2 +- .../IronyModManager.Localization.csproj | 2 +- src/IronyModManager/IronyModManager.csproj | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/IronyModManager.DI/IronyModManager.DI.csproj b/src/IronyModManager.DI/IronyModManager.DI.csproj index 4676696b..c5775aa1 100644 --- a/src/IronyModManager.DI/IronyModManager.DI.csproj +++ b/src/IronyModManager.DI/IronyModManager.DI.csproj @@ -53,7 +53,7 @@ - + diff --git a/src/IronyModManager.Localization/IronyModManager.Localization.csproj b/src/IronyModManager.Localization/IronyModManager.Localization.csproj index e614c608..581f2223 100644 --- a/src/IronyModManager.Localization/IronyModManager.Localization.csproj +++ b/src/IronyModManager.Localization/IronyModManager.Localization.csproj @@ -49,7 +49,7 @@ - + diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index 99045321..d966bc95 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -151,11 +151,11 @@ - + - + From e46f76ce63e08ae8d64c68ef6dfc70a13ba08494 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 23:23:50 +0100 Subject: [PATCH 016/101] Bump mbus Fix expected refactor mess --- .../MessageBus/MessageBusMemoryProvider.cs | 26 +++----- .../MessageBus/MessageBusValidationService.cs | 65 +++++++++++++++++++ .../IronyModManager.Shared.csproj | 2 +- 3 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 src/IronyModManager.DI/MessageBus/MessageBusValidationService.cs diff --git a/src/IronyModManager.DI/MessageBus/MessageBusMemoryProvider.cs b/src/IronyModManager.DI/MessageBus/MessageBusMemoryProvider.cs index f2081b95..f9262532 100644 --- a/src/IronyModManager.DI/MessageBus/MessageBusMemoryProvider.cs +++ b/src/IronyModManager.DI/MessageBus/MessageBusMemoryProvider.cs @@ -5,7 +5,7 @@ // Created : 06-25-2023 // // Last Modified By : Mario -// Last Modified On : 06-25-2023 +// Last Modified On : 02-09-2024 // *********************************************************************** // // Mario @@ -18,6 +18,7 @@ using System.Threading.Tasks; using SlimMessageBus.Host; using SlimMessageBus.Host.Memory; +using SlimMessageBus.Host.Services; namespace IronyModManager.DI.MessageBus { @@ -32,7 +33,7 @@ internal class MessageBusMemoryProvider : MemoryMessageBus #region Constructors ///

- /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The settings. public MessageBusMemoryProvider(MessageBusSettings settings) : base(settings, new MemoryMessageBusSettings() { EnableMessageSerialization = false }) @@ -41,24 +42,15 @@ internal class MessageBusMemoryProvider : MemoryMessageBus #endregion Constructors - #region Methods + #region Properties /// - /// Asserts the settings. + /// Gets the validation service. /// - protected override void AssertSettings() - { - // Sigh, let's fix the validation mess - foreach (var consumerSettings in Settings.Consumers) - { - if (consumerSettings.ConsumerMethodInfo != null && consumerSettings.ConsumerMethod == null) - { - consumerSettings.ConsumerMethod = ReflectionUtils.GenerateMethodCallToFunc>(consumerSettings.ConsumerMethodInfo, consumerSettings.ConsumerType, typeof(Task), consumerSettings.MessageType); - } - } - base.AssertSettings(); - } + /// The validation service. + // Will you make up your mind where you want AssertSettings to be + protected override IMessageBusSettingsValidationService ValidationService => new MessageBusValidationService(Settings); - #endregion Methods + #endregion Properties } } diff --git a/src/IronyModManager.DI/MessageBus/MessageBusValidationService.cs b/src/IronyModManager.DI/MessageBus/MessageBusValidationService.cs new file mode 100644 index 00000000..2069a258 --- /dev/null +++ b/src/IronyModManager.DI/MessageBus/MessageBusValidationService.cs @@ -0,0 +1,65 @@ + +// *********************************************************************** +// Assembly : IronyModManager.DI +// Author : Mario +// Created : 02-09-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-09-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SlimMessageBus.Host; +using SlimMessageBus.Host.Services; + +namespace IronyModManager.DI.MessageBus +{ + + /// + /// Class MessageBusValidationService. + /// Implements the + /// + /// + internal class MessageBusValidationService : DefaultMessageBusSettingsValidationService + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The settings. + public MessageBusValidationService(MessageBusSettings settings) : base(settings) + { + } + + #endregion Constructors + + #region Methods + + /// + /// Asserts the settings. + /// + public override void AssertSettings() + { + // Sigh, let's fix the validation mess + foreach (var consumerSettings in Settings.Consumers) + { + if (consumerSettings.ConsumerMethodInfo != null && consumerSettings.ConsumerMethod == null) + { + consumerSettings.ConsumerMethod = ReflectionUtils.GenerateMethodCallToFunc>(consumerSettings.ConsumerMethodInfo, consumerSettings.ConsumerType, typeof(Task), consumerSettings.MessageType); + } + } + base.AssertSettings(); + } + + #endregion Methods + } +} diff --git a/src/IronyModManager.Shared/IronyModManager.Shared.csproj b/src/IronyModManager.Shared/IronyModManager.Shared.csproj index 2a808414..340fd764 100644 --- a/src/IronyModManager.Shared/IronyModManager.Shared.csproj +++ b/src/IronyModManager.Shared/IronyModManager.Shared.csproj @@ -57,7 +57,7 @@ - + From f3785167c2f4d68467051f2a0cb3654f24e2d518 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 22:57:16 +0100 Subject: [PATCH 017/101] Bump reactiveui --- src/IronyModManager.Common/IronyModManager.Common.csproj | 2 +- src/IronyModManager/IronyModManager.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IronyModManager.Common/IronyModManager.Common.csproj b/src/IronyModManager.Common/IronyModManager.Common.csproj index 8a2262dc..29e2be85 100644 --- a/src/IronyModManager.Common/IronyModManager.Common.csproj +++ b/src/IronyModManager.Common/IronyModManager.Common.csproj @@ -50,7 +50,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index d966bc95..fe7764cd 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -149,7 +149,7 @@ - + From 853d313ad106fd08a5787087d05441a3c0373f01 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 23:08:27 +0100 Subject: [PATCH 018/101] Update dependency --- src/IronyModManager.Shared/IronyModManager.Shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager.Shared/IronyModManager.Shared.csproj b/src/IronyModManager.Shared/IronyModManager.Shared.csproj index 340fd764..3287aab4 100644 --- a/src/IronyModManager.Shared/IronyModManager.Shared.csproj +++ b/src/IronyModManager.Shared/IronyModManager.Shared.csproj @@ -55,7 +55,7 @@ - + From fa87952e7ec70b4092314127f50fe402a616d47e Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 23:02:18 +0100 Subject: [PATCH 019/101] Update skia sharp --- src/IronyModManager.Common/IronyModManager.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager.Common/IronyModManager.Common.csproj b/src/IronyModManager.Common/IronyModManager.Common.csproj index 29e2be85..fd6a4e31 100644 --- a/src/IronyModManager.Common/IronyModManager.Common.csproj +++ b/src/IronyModManager.Common/IronyModManager.Common.csproj @@ -52,7 +52,7 @@ - + From 2818a64b6bf62a19c75c11482a3d473792f82ca6 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 23:04:30 +0100 Subject: [PATCH 020/101] Missed commit --- src/IronyModManager.Platform/IronyModManager.Platform.csproj | 2 +- src/IronyModManager/IronyModManager.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IronyModManager.Platform/IronyModManager.Platform.csproj b/src/IronyModManager.Platform/IronyModManager.Platform.csproj index 1add30a1..27c81b8a 100644 --- a/src/IronyModManager.Platform/IronyModManager.Platform.csproj +++ b/src/IronyModManager.Platform/IronyModManager.Platform.csproj @@ -64,7 +64,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index fe7764cd..58cb8c9b 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -153,7 +153,7 @@ - + From e16f704b031fb78a42f4b93db5cebbef6e88199e Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 9 Feb 2024 23:07:08 +0100 Subject: [PATCH 021/101] Bump SmartFormat --- src/IronyModManager.Shared/IronyModManager.Shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager.Shared/IronyModManager.Shared.csproj b/src/IronyModManager.Shared/IronyModManager.Shared.csproj index 3287aab4..71e62412 100644 --- a/src/IronyModManager.Shared/IronyModManager.Shared.csproj +++ b/src/IronyModManager.Shared/IronyModManager.Shared.csproj @@ -58,7 +58,7 @@ - + From 3571fed2ec4fbc83d7179f6e1c01e21a56f48c1e Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 03:47:59 +0100 Subject: [PATCH 022/101] Bump Jot --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index b989fb2c..4097c955 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,7 +27,7 @@ [0.10.22] [0.10.12.2] [1.1.10] - [2.1.16] + [2.1.17] [2.4.5] From 71fe2024eb1d2d382dc4b1dba292398646855540 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 03:43:15 +0100 Subject: [PATCH 023/101] Bump sharpcompress --- src/IronyModManager.IO/IronyModManager.IO.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager.IO/IronyModManager.IO.csproj b/src/IronyModManager.IO/IronyModManager.IO.csproj index 25b5b72e..79e7e4ea 100644 --- a/src/IronyModManager.IO/IronyModManager.IO.csproj +++ b/src/IronyModManager.IO/IronyModManager.IO.csproj @@ -53,7 +53,7 @@ - + From d28581ed839af9d99ada0f78350243c199c60d53 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 03:53:46 +0100 Subject: [PATCH 024/101] Update dir props --- Directory.Build.props | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 4097c955..e75b2c05 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -22,15 +22,17 @@ [7.0.3] [7.0.4] [7.0.0] + [2.1.17] + + [2.5.1] [2.1.0] [0.10.22] [0.10.12.2] [1.1.10] - [2.1.17] [2.4.5] - + [1.0.4] [1.0.0-beta13.15] [0.0.0-alpha.0.132] From 311c0303fbfbcb2478e0db95980799368c103d98 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 04:39:24 +0100 Subject: [PATCH 025/101] Upgrade all modules, paths... --- Directory.Build.props | 12 ++++++---- Test.runsettings | 2 +- .../LocalizationResourceGenerator.csproj | 2 +- cmd/build-tools.bat | 6 ++--- publish/publish-linux-x64.bat | 22 ++++++++--------- publish/publish-osx-x64.bat | 22 ++++++++--------- publish/publish-win-x64.bat | 24 +++++++++---------- publish/setup/win-installer.iss | 4 ++-- .../Irony.AppCastGenerator.csproj | 2 +- .../IronyModManager.Common.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.DI.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.GameHandler.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.IO.Common.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.IO.Tests.csproj | 4 ++-- .../IronyModManager.IO.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Localization.Tests.csproj | 2 +- .../IronyModManager.Localization.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Model.Tests.csproj | 2 +- .../IronyModManager.Models.Common.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Models.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Parser.Common.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Parser.Tests.csproj | 2 +- .../IronyModManager.Parser.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Platform.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Services.Common.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Services.Tests.csproj | 2 +- .../IronyModManager.Services.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Shared.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Storage.Common.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Storage.Tests.csproj | 2 +- .../IronyModManager.Storage.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- .../IronyModManager.Tests.Common.csproj | 4 ++-- .../IronyModManager.Tests.csproj | 2 +- .../IronyModManager.Updater.csproj | 2 +- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- src/IronyModManager/IronyModManager.csproj | 4 ++-- .../PublishProfiles/linux-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/osx-x64.pubxml | 4 ++-- .../Properties/PublishProfiles/win-x64.pubxml | 4 ++-- 89 files changed, 187 insertions(+), 183 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index e75b2c05..f5d251dc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,10 +18,10 @@ $(MSBuildProjectDirectory)=$(MSBuildProjectName) - [7.0.0] - [7.0.3] - [7.0.4] - [7.0.0] + [8.0.0] + [8.0.0-preview.7.23375.6] + [8.0.1] + [8.0.0] [2.1.17] @@ -43,4 +43,8 @@ all + + + + \ No newline at end of file diff --git a/Test.runsettings b/Test.runsettings index ed3765e7..a143ed74 100644 --- a/Test.runsettings +++ b/Test.runsettings @@ -4,7 +4,7 @@ 2 x64 .\TestResults - net7 + net8.0 diff --git a/Tools/LocalizationResourceGenerator/src/LocalizationResourceGenerator/LocalizationResourceGenerator.csproj b/Tools/LocalizationResourceGenerator/src/LocalizationResourceGenerator/LocalizationResourceGenerator.csproj index ae664eeb..73b7f337 100644 --- a/Tools/LocalizationResourceGenerator/src/LocalizationResourceGenerator/LocalizationResourceGenerator.csproj +++ b/Tools/LocalizationResourceGenerator/src/LocalizationResourceGenerator/LocalizationResourceGenerator.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 diff --git a/cmd/build-tools.bat b/cmd/build-tools.bat index b58fcb8d..a28eb303 100644 --- a/cmd/build-tools.bat +++ b/cmd/build-tools.bat @@ -5,6 +5,6 @@ dotnet build --configuration Release cd .. cd .. cd .. -xcopy "Tools\LocalizationResourceGenerator\src\LocalizationResourceGenerator\bin\Release\net7.0\*.dll" "Tools\LocalizationResourceGenerator\" /Y /S /D -xcopy "Tools\LocalizationResourceGenerator\src\LocalizationResourceGenerator\bin\Release\net7.0\*.exe" "Tools\LocalizationResourceGenerator\" /Y /S /D -xcopy "Tools\LocalizationResourceGenerator\src\LocalizationResourceGenerator\bin\Release\net7.0\*.json" "Tools\LocalizationResourceGenerator\" /Y /S /D \ No newline at end of file +xcopy "Tools\LocalizationResourceGenerator\src\LocalizationResourceGenerator\bin\Release\net8.0\*.dll" "Tools\LocalizationResourceGenerator\" /Y /S /D +xcopy "Tools\LocalizationResourceGenerator\src\LocalizationResourceGenerator\bin\Release\net8.0\*.exe" "Tools\LocalizationResourceGenerator\" /Y /S /D +xcopy "Tools\LocalizationResourceGenerator\src\LocalizationResourceGenerator\bin\Release\net8.0\*.json" "Tools\LocalizationResourceGenerator\" /Y /S /D \ No newline at end of file diff --git a/publish/publish-linux-x64.bat b/publish/publish-linux-x64.bat index ed588618..3781e206 100644 --- a/publish/publish-linux-x64.bat +++ b/publish/publish-linux-x64.bat @@ -17,16 +17,16 @@ dotnet publish src\IronyModManager.Common\IronyModManager.Common.csproj /p:Publ dotnet publish src\IronyModManager.Updater\IronyModManager.Updater.csproj /p:PublishProfile=src\IronyModManager.Updater\Properties\PublishProfiles\linux-x64.pubxml --configuration linux-x64 dotnet publish src\IronyModManager.GameHandler\IronyModManager.GameHandler.csproj /p:PublishProfile=src\IronyModManager.GameHandler\Properties\PublishProfiles\linux-x64.pubxml --configuration linux-x64 dotnet publish src\IronyModManager\IronyModManager.csproj /p:PublishProfile=src\IronyModManager\Properties\PublishProfiles\linux-x64.pubxml --configuration linux-x64 -xcopy "src\IronyModManager\bin\linux-x64\net7.0\linux-x64\*.dll" "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\" /Y /S /D -xcopy "src\IronyModManager\bin\linux-x64\net7.0\linux-x64\*.json" "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\" /Y /S /D -xcopy "src\IronyModManager\bin\linux-x64\net7.0\linux-x64\*.pdb" "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\" /Y /S /D -xcopy "src\IronyModManager.Updater\bin\x64\linux-x64\net7.0\publish\linux-x64\*.*" "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\" /Y /S /D -xcopy "src\IronyModManager.GameHandler\bin\x64\linux-x64\net7.0\publish\linux-x64\*.*" "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\" /Y /S /D -del "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\IronyModManager.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\IronyModManager.Updater.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\IronyModManager.GameHandler.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\steam_api64.dll" /S /Q -xcopy "References\CopyAll\*.*" "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\linux-x64\net8.0\linux-x64\*.dll" "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\linux-x64\net8.0\linux-x64\*.json" "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\linux-x64\net8.0\linux-x64\*.pdb" "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\" /Y /S /D +xcopy "src\IronyModManager.Updater\bin\x64\linux-x64\net8.0\publish\linux-x64\*.*" "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\" /Y /S /D +xcopy "src\IronyModManager.GameHandler\bin\x64\linux-x64\net8.0\publish\linux-x64\*.*" "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\" /Y /S /D +del "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\IronyModManager.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\IronyModManager.Updater.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\IronyModManager.GameHandler.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\steam_api64.dll" /S /Q +xcopy "References\CopyAll\*.*" "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\" /Y /S /D REM Why on earth cannot nuget include these? Also the documentation sucks in this regard -xcopy "References\Conditional\Steamworks\OSX-Linux-x64\libsteam_api.so" "src\IronyModManager\bin\x64\linux-x64\net7.0\publish\linux-x64\" /Y /S /D +xcopy "References\Conditional\Steamworks\OSX-Linux-x64\libsteam_api.so" "src\IronyModManager\bin\x64\linux-x64\net8.0\publish\linux-x64\" /Y /S /D cd publish \ No newline at end of file diff --git a/publish/publish-osx-x64.bat b/publish/publish-osx-x64.bat index 28cc408b..cb1dd7d6 100644 --- a/publish/publish-osx-x64.bat +++ b/publish/publish-osx-x64.bat @@ -17,16 +17,16 @@ dotnet publish src\IronyModManager.Common\IronyModManager.Common.csproj /p:Publ dotnet publish src\IronyModManager.Updater\IronyModManager.Updater.csproj /p:PublishProfile=src\IronyModManager.Updater\Properties\PublishProfiles\osx-x64.pubxml --configuration osx-x64 dotnet publish src\IronyModManager.GameHandler\IronyModManager.GameHandler.csproj /p:PublishProfile=src\IronyModManager.GameHandler\Properties\PublishProfiles\osx-x64.pubxml --configuration osx-x64 dotnet publish src\IronyModManager\IronyModManager.csproj /p:PublishProfile=src\IronyModManager\Properties\PublishProfiles\osx-x64.pubxml --configuration osx-x64 -xcopy "src\IronyModManager\bin\osx-x64\net7.0\osx-x64\*.dll" "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\" /Y /S /D -xcopy "src\IronyModManager\bin\osx-x64\net7.0\osx-x64\*.json" "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\" /Y /S /D -xcopy "src\IronyModManager\bin\osx-x64\net7.0\osx-x64\*.pdb" "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\" /Y /S /D -xcopy "src\IronyModManager.Updater\bin\x64\osx-x64\net7.0\publish\osx-x64\*.*" "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\" /Y /S /D -xcopy "src\IronyModManager.GameHandler\bin\x64\osx-x64\net7.0\publish\osx-x64\*.*" "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\" /Y /S /D -del "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\IronyModManager.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\IronyModManager.Updater.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\IronyModManager.GameHandler.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\steam_api64.dll" /S /Q -xcopy "References\CopyAll\*.*" "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\osx-x64\net8.0\osx-x64\*.dll" "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\osx-x64\net8.0\osx-x64\*.json" "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\osx-x64\net8.0\osx-x64\*.pdb" "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\" /Y /S /D +xcopy "src\IronyModManager.Updater\bin\x64\osx-x64\net8.0\publish\osx-x64\*.*" "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\" /Y /S /D +xcopy "src\IronyModManager.GameHandler\bin\x64\osx-x64\net8.0\publish\osx-x64\*.*" "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\" /Y /S /D +del "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\IronyModManager.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\IronyModManager.Updater.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\IronyModManager.GameHandler.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\steam_api64.dll" /S /Q +xcopy "References\CopyAll\*.*" "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\" /Y /S /D REM Why on earth cannot nuget include these? Also the documentation sucks in this regard -xcopy "References\Conditional\Steamworks\OSX-Linux-x64\steam_api.bundle\Contents\MacOS\*.*" "src\IronyModManager\bin\x64\osx-x64\net7.0\publish\osx-x64\" /Y /S /D +xcopy "References\Conditional\Steamworks\OSX-Linux-x64\steam_api.bundle\Contents\MacOS\*.*" "src\IronyModManager\bin\x64\osx-x64\net8.0\publish\osx-x64\" /Y /S /D cd publish \ No newline at end of file diff --git a/publish/publish-win-x64.bat b/publish/publish-win-x64.bat index 2b5e6bb8..66a6321f 100644 --- a/publish/publish-win-x64.bat +++ b/publish/publish-win-x64.bat @@ -17,17 +17,17 @@ dotnet publish src\IronyModManager.Common\IronyModManager.Common.csproj /p:Publ dotnet publish src\IronyModManager.Updater\IronyModManager.Updater.csproj /p:PublishProfile=src\IronyModManager.Updater\Properties\PublishProfiles\win-x64.pubxml --configuration win-x64 dotnet publish src\IronyModManager.GameHandler\IronyModManager.GameHandler.csproj /p:PublishProfile=src\IronyModManager.GameHandler\Properties\PublishProfiles\win-x64.pubxml --configuration win-x64 dotnet publish src\IronyModManager\IronyModManager.csproj /p:PublishProfile=src\IronyModManager\Properties\PublishProfiles\win-x64.pubxml --configuration win-x64 -xcopy "src\IronyModManager\bin\win-x64\net7.0\win-x64\*.dll" "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\" /Y /S /D -xcopy "src\IronyModManager\bin\win-x64\net7.0\win-x64\*.json" "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\" /Y /S /D -xcopy "src\IronyModManager\bin\win-x64\net7.0\win-x64\*.pdb" "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\" /Y /S /D -xcopy "src\IronyModManager.Updater\bin\x64\win-x64\net7.0\publish\win-x64\*.*" "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\" /Y /S /D -xcopy "src\IronyModManager.GameHandler\bin\x64\win-x64\net7.0\publish\win-x64\*.*" "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\win-x64\net8.0\win-x64\*.dll" "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\win-x64\net8.0\win-x64\*.json" "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\" /Y /S /D +xcopy "src\IronyModManager\bin\win-x64\net8.0\win-x64\*.pdb" "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\" /Y /S /D +xcopy "src\IronyModManager.Updater\bin\x64\win-x64\net8.0\publish\win-x64\*.*" "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\" /Y /S /D +xcopy "src\IronyModManager.GameHandler\bin\x64\win-x64\net8.0\publish\win-x64\*.*" "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\" /Y /S /D REM Temp fix due to avalonia bug -xcopy "%userprofile%\.nuget\packages\avalonia.angle.windows.natives\2.1.0.2020091801\runtimes\win7-x64\native\av_libglesv2.dll" "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\" /Y /S /D -del "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\IronyModManager.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\IronyModManager.Updater.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\IronyModManager.GameHandler.runtimeconfig.dev.json" /S /Q -del "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\steam_api64.dll" /S /Q -xcopy "References\CopyAll\*.*" "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\" /Y /S /D -xcopy "References\Conditional\Steamworks\Windows-x64\*.*" "src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64\" /Y /S /D +xcopy "%userprofile%\.nuget\packages\avalonia.angle.windows.natives\2.1.0.2020091801\runtimes\win7-x64\native\av_libglesv2.dll" "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\" /Y /S /D +del "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\IronyModManager.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\IronyModManager.Updater.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\IronyModManager.GameHandler.runtimeconfig.dev.json" /S /Q +del "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\steam_api64.dll" /S /Q +xcopy "References\CopyAll\*.*" "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\" /Y /S /D +xcopy "References\Conditional\Steamworks\Windows-x64\*.*" "src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64\" /Y /S /D cd publish \ No newline at end of file diff --git a/publish/setup/win-installer.iss b/publish/setup/win-installer.iss index b2aeda10..1b85a535 100644 --- a/publish/setup/win-installer.iss +++ b/publish/setup/win-installer.iss @@ -5,8 +5,8 @@ #define MyAppPublisher "Mario" #define MyAppURL "https://bcssov.github.io/IronyModManager/" #define MyAppExeName "IronyModManager.exe" -#define PublishPath "..\..\src\IronyModManager\bin\x64\win-x64\net7.0\publish" -#define SourcePath "..\..\src\IronyModManager\bin\x64\win-x64\net7.0\publish\win-x64" +#define PublishPath "..\..\src\IronyModManager\bin\x64\win-x64\net8.0\publish" +#define SourcePath "..\..\src\IronyModManager\bin\x64\win-x64\net8.0\publish\win-x64" #define MyAppVersion GetStringFileInfo(SourcePath + "\" + MyAppExeName, "ProductVersion") [Code] diff --git a/src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj b/src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj index 381789f9..3dd74293 100644 --- a/src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj +++ b/src/Irony.AppCastGenerator/Irony.AppCastGenerator.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 ../IronyModManager/Assets/logo.ico Irony App Cast Generator Component LICENSE diff --git a/src/IronyModManager.Common/IronyModManager.Common.csproj b/src/IronyModManager.Common/IronyModManager.Common.csproj index fd6a4e31..8493abf9 100644 --- a/src/IronyModManager.Common/IronyModManager.Common.csproj +++ b/src/IronyModManager.Common/IronyModManager.Common.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Irony Mod Manager Common Component LICENSE logo.png diff --git a/src/IronyModManager.Common/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Common/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Common/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Common/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Common/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Common/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Common/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Common/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Common/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Common/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Common/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Common/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.DI/IronyModManager.DI.csproj b/src/IronyModManager.DI/IronyModManager.DI.csproj index c5775aa1..dfe8e574 100644 --- a/src/IronyModManager.DI/IronyModManager.DI.csproj +++ b/src/IronyModManager.DI/IronyModManager.DI.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Mario Mario Irony Mod Manager DI Component diff --git a/src/IronyModManager.DI/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.DI/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.DI/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.DI/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.DI/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.DI/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.DI/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.DI/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.DI/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.DI/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.DI/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.DI/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.GameHandler/IronyModManager.GameHandler.csproj b/src/IronyModManager.GameHandler/IronyModManager.GameHandler.csproj index 3f98c613..43338297 100644 --- a/src/IronyModManager.GameHandler/IronyModManager.GameHandler.csproj +++ b/src/IronyModManager.GameHandler/IronyModManager.GameHandler.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 ../IronyModManager/Assets/logo.ico IronyModManager Game Launcher Component LICENSE diff --git a/src/IronyModManager.GameHandler/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.GameHandler/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.GameHandler/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.GameHandler/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.GameHandler/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.GameHandler/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.GameHandler/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.GameHandler/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.GameHandler/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.GameHandler/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.GameHandler/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.GameHandler/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.IO.Common/IronyModManager.IO.Common.csproj b/src/IronyModManager.IO.Common/IronyModManager.IO.Common.csproj index a1b6970e..1c4bb3cb 100644 --- a/src/IronyModManager.IO.Common/IronyModManager.IO.Common.csproj +++ b/src/IronyModManager.IO.Common/IronyModManager.IO.Common.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 true ../../keys/Irony-Main.snk true diff --git a/src/IronyModManager.IO.Common/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.IO.Common/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.IO.Common/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.IO.Common/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.IO.Common/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.IO.Common/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.IO.Common/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.IO.Common/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.IO.Common/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.IO.Common/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.IO.Common/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.IO.Common/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj b/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj index 0acb6759..495298c6 100644 --- a/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj +++ b/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 true ../../keys/Irony-Main.snk Debug;Release;Functional_Test;osx-x64;linux-x64;win-x64 @@ -28,7 +28,7 @@ - + diff --git a/src/IronyModManager.IO/IronyModManager.IO.csproj b/src/IronyModManager.IO/IronyModManager.IO.csproj index 79e7e4ea..244790fb 100644 --- a/src/IronyModManager.IO/IronyModManager.IO.csproj +++ b/src/IronyModManager.IO/IronyModManager.IO.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 true ../../keys/Irony-Main.snk true diff --git a/src/IronyModManager.IO/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.IO/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.IO/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.IO/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.IO/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.IO/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.IO/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.IO/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.IO/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.IO/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.IO/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.IO/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj b/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj index e76c44f8..2b665fe7 100644 --- a/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj +++ b/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 true ../../keys/Irony-Main.snk IronyModManager.Localization.Tests diff --git a/src/IronyModManager.Localization/IronyModManager.Localization.csproj b/src/IronyModManager.Localization/IronyModManager.Localization.csproj index 581f2223..130a7c2b 100644 --- a/src/IronyModManager.Localization/IronyModManager.Localization.csproj +++ b/src/IronyModManager.Localization/IronyModManager.Localization.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Irony Mod Manager Localization Component Irony Mod Manager LICENSE diff --git a/src/IronyModManager.Localization/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Localization/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Localization/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Localization/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Localization/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Localization/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Localization/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Localization/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Localization/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Localization/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Localization/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Localization/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj b/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj index d513f22f..807e9e79 100644 --- a/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj +++ b/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 IronyModManager.Model.Tests LICENSE logo.png diff --git a/src/IronyModManager.Models.Common/IronyModManager.Models.Common.csproj b/src/IronyModManager.Models.Common/IronyModManager.Models.Common.csproj index c0511172..b9b8a5d0 100644 --- a/src/IronyModManager.Models.Common/IronyModManager.Models.Common.csproj +++ b/src/IronyModManager.Models.Common/IronyModManager.Models.Common.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 true ../../keys/Irony-Main.snk Irony Mod Manager Models Common Component diff --git a/src/IronyModManager.Models.Common/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Models.Common/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Models.Common/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Models.Common/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Models.Common/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Models.Common/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Models.Common/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Models.Common/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Models.Common/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Models.Common/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Models.Common/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Models.Common/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Models/IronyModManager.Models.csproj b/src/IronyModManager.Models/IronyModManager.Models.csproj index 7f5d2e12..af51cef6 100644 --- a/src/IronyModManager.Models/IronyModManager.Models.csproj +++ b/src/IronyModManager.Models/IronyModManager.Models.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Mario Irony Mod Manager Models Component Mario diff --git a/src/IronyModManager.Models/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Models/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Models/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Models/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Models/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Models/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Models/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Models/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Models/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Models/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Models/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Models/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Parser.Common/IronyModManager.Parser.Common.csproj b/src/IronyModManager.Parser.Common/IronyModManager.Parser.Common.csproj index 1f9d4a07..d6788dd8 100644 --- a/src/IronyModManager.Parser.Common/IronyModManager.Parser.Common.csproj +++ b/src/IronyModManager.Parser.Common/IronyModManager.Parser.Common.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 true ../../keys/Irony-Main.snk true diff --git a/src/IronyModManager.Parser.Common/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Parser.Common/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Parser.Common/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Parser.Common/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Parser.Common/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Parser.Common/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Parser.Common/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Parser.Common/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Parser.Common/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Parser.Common/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Parser.Common/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Parser.Common/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj b/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj index 51054880..784c82ce 100644 --- a/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj +++ b/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 true ../../keys/Irony-Main.snk IronyModManager.Parser.Tests diff --git a/src/IronyModManager.Parser/IronyModManager.Parser.csproj b/src/IronyModManager.Parser/IronyModManager.Parser.csproj index c8c98de1..e1b22897 100644 --- a/src/IronyModManager.Parser/IronyModManager.Parser.csproj +++ b/src/IronyModManager.Parser/IronyModManager.Parser.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 true ../../keys/Irony-Main.snk true diff --git a/src/IronyModManager.Parser/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Parser/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Parser/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Parser/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Parser/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Parser/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Parser/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Parser/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Parser/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Parser/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Parser/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Parser/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Platform/IronyModManager.Platform.csproj b/src/IronyModManager.Platform/IronyModManager.Platform.csproj index 27c81b8a..b0983338 100644 --- a/src/IronyModManager.Platform/IronyModManager.Platform.csproj +++ b/src/IronyModManager.Platform/IronyModManager.Platform.csproj @@ -1,6 +1,6 @@  - net7.0 + net8.0 true ../../keys/Irony-Main.snk true diff --git a/src/IronyModManager.Platform/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Platform/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Platform/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Platform/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Platform/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Platform/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Platform/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Platform/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Platform/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Platform/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Platform/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Platform/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Services.Common/IronyModManager.Services.Common.csproj b/src/IronyModManager.Services.Common/IronyModManager.Services.Common.csproj index 1f2fd020..32e5cf8a 100644 --- a/src/IronyModManager.Services.Common/IronyModManager.Services.Common.csproj +++ b/src/IronyModManager.Services.Common/IronyModManager.Services.Common.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 true ../../keys/Irony-Main.snk Irony Mod Manager Services Common Component diff --git a/src/IronyModManager.Services.Common/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Services.Common/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Services.Common/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Services.Common/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Services.Common/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Services.Common/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Services.Common/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Services.Common/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Services.Common/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Services.Common/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Services.Common/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Services.Common/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj b/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj index c47809fd..7f163f65 100644 --- a/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj +++ b/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 IronyModManager.Services.Tests LICENSE logo.png diff --git a/src/IronyModManager.Services/IronyModManager.Services.csproj b/src/IronyModManager.Services/IronyModManager.Services.csproj index 4dd49eb6..e5290045 100644 --- a/src/IronyModManager.Services/IronyModManager.Services.csproj +++ b/src/IronyModManager.Services/IronyModManager.Services.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Irony Mod Manager Services Component Mario LICENSE diff --git a/src/IronyModManager.Services/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Services/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Services/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Services/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Services/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Services/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Services/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Services/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Services/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Services/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Services/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Services/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Shared/IronyModManager.Shared.csproj b/src/IronyModManager.Shared/IronyModManager.Shared.csproj index 71e62412..03022d95 100644 --- a/src/IronyModManager.Shared/IronyModManager.Shared.csproj +++ b/src/IronyModManager.Shared/IronyModManager.Shared.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Irony Mod Manager Shared Component LICENSE logo.png diff --git a/src/IronyModManager.Shared/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Shared/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Shared/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Shared/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Shared/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Shared/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Shared/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Shared/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Shared/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Shared/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Shared/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Shared/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Storage.Common/IronyModManager.Storage.Common.csproj b/src/IronyModManager.Storage.Common/IronyModManager.Storage.Common.csproj index 2a65469a..c25c6c2b 100644 --- a/src/IronyModManager.Storage.Common/IronyModManager.Storage.Common.csproj +++ b/src/IronyModManager.Storage.Common/IronyModManager.Storage.Common.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 Irony Mod Manager Storage Common Component LICENSE logo.png diff --git a/src/IronyModManager.Storage.Common/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Storage.Common/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Storage.Common/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Storage.Common/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Storage.Common/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Storage.Common/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Storage.Common/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Storage.Common/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Storage.Common/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Storage.Common/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Storage.Common/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Storage.Common/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj b/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj index 7d71ac99..1b97aa4f 100644 --- a/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj +++ b/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 IronyModManager.Storage.Tests LICENSE logo.png diff --git a/src/IronyModManager.Storage/IronyModManager.Storage.csproj b/src/IronyModManager.Storage/IronyModManager.Storage.csproj index 59a8b55c..718083a4 100644 --- a/src/IronyModManager.Storage/IronyModManager.Storage.csproj +++ b/src/IronyModManager.Storage/IronyModManager.Storage.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Mario Mario Irony Mod Manager Storage Component diff --git a/src/IronyModManager.Storage/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Storage/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Storage/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Storage/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Storage/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Storage/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Storage/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Storage/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Storage/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Storage/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Storage/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Storage/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj b/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj index 6240e953..0770d44e 100644 --- a/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj +++ b/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 IronyModManager.Tests.Common LICENSE logo.png @@ -43,7 +43,7 @@ - + diff --git a/src/IronyModManager.Tests/IronyModManager.Tests.csproj b/src/IronyModManager.Tests/IronyModManager.Tests.csproj index 37a7ef2a..a8d3ff53 100644 --- a/src/IronyModManager.Tests/IronyModManager.Tests.csproj +++ b/src/IronyModManager.Tests/IronyModManager.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 IronyModManager.Tests LICENSE logo.png diff --git a/src/IronyModManager.Updater/IronyModManager.Updater.csproj b/src/IronyModManager.Updater/IronyModManager.Updater.csproj index 25cb781d..fa49a52f 100644 --- a/src/IronyModManager.Updater/IronyModManager.Updater.csproj +++ b/src/IronyModManager.Updater/IronyModManager.Updater.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 ../IronyModManager/Assets/logo.ico IronyModManager Updater Component LICENSE diff --git a/src/IronyModManager.Updater/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager.Updater/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager.Updater/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager.Updater/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager.Updater/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager.Updater/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager.Updater/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager.Updater/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager.Updater/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager.Updater/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager.Updater/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager.Updater/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False diff --git a/src/IronyModManager/IronyModManager.csproj b/src/IronyModManager/IronyModManager.csproj index 58cb8c9b..c9b29ea0 100644 --- a/src/IronyModManager/IronyModManager.csproj +++ b/src/IronyModManager/IronyModManager.csproj @@ -1,7 +1,7 @@  WinExe - net7.0 + net8.0 Assets\logo.ico IronyModManager.Program Mario @@ -134,7 +134,7 @@ - + diff --git a/src/IronyModManager/Properties/PublishProfiles/linux-x64.pubxml b/src/IronyModManager/Properties/PublishProfiles/linux-x64.pubxml index 04545ee0..b8f7f6db 100644 --- a/src/IronyModManager/Properties/PublishProfiles/linux-x64.pubxml +++ b/src/IronyModManager/Properties/PublishProfiles/linux-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\linux-x64\net7.0\publish\linux-x64 + net8.0 + bin\x64\linux-x64\net8.0\publish\linux-x64 linux-x64 true False diff --git a/src/IronyModManager/Properties/PublishProfiles/osx-x64.pubxml b/src/IronyModManager/Properties/PublishProfiles/osx-x64.pubxml index df6bd9f9..ff92dad8 100644 --- a/src/IronyModManager/Properties/PublishProfiles/osx-x64.pubxml +++ b/src/IronyModManager/Properties/PublishProfiles/osx-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\osx-x64\net7.0\publish\osx-x64 + net8.0 + bin\x64\osx-x64\net8.0\publish\osx-x64 osx-x64 true False diff --git a/src/IronyModManager/Properties/PublishProfiles/win-x64.pubxml b/src/IronyModManager/Properties/PublishProfiles/win-x64.pubxml index 1cd12279..cbd6468d 100644 --- a/src/IronyModManager/Properties/PublishProfiles/win-x64.pubxml +++ b/src/IronyModManager/Properties/PublishProfiles/win-x64.pubxml @@ -7,8 +7,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. FileSystem Release x64 - net7.0 - bin\x64\win-x64\net7.0\publish\win-x64 + net8.0 + bin\x64\win-x64\net8.0\publish\win-x64 win-x64 true False From a4d5040a751ae27b1e8d14e691e1a9d0c51b335a Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 16:02:20 +0100 Subject: [PATCH 026/101] Add config option --- .../PlatformConfigurationOptions.cs | 28 +++++++++++++++++-- .../Config/PlatformConfiguration.cs | 7 ++--- src/IronyModManager/appSettings.json | 3 ++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/IronyModManager.Platform/Configuration/PlatformConfigurationOptions.cs b/src/IronyModManager.Platform/Configuration/PlatformConfigurationOptions.cs index 1340f942..d37ec651 100644 --- a/src/IronyModManager.Platform/Configuration/PlatformConfigurationOptions.cs +++ b/src/IronyModManager.Platform/Configuration/PlatformConfigurationOptions.cs @@ -1,10 +1,11 @@ -// *********************************************************************** + +// *********************************************************************** // Assembly : IronyModManager.Platform // Author : Mario // Created : 04-16-2021 // // Last Modified By : Mario -// Last Modified On : 05-12-2023 +// Last Modified On : 02-10-2024 // *********************************************************************** // // Mario @@ -17,6 +18,23 @@ namespace IronyModManager.Platform.Configuration { + + /// + /// Class App. + /// + public class App + { + #region Properties + + /// + /// Gets or sets a value indicating whether [single instance]. + /// + /// true if [single instance]; otherwise, false. + public bool SingleInstance { get; set; } + + #endregion Properties + } + /// /// Class ConflictSolver. /// @@ -118,6 +136,12 @@ public class PlatformConfigurationOptions { #region Properties + /// + /// Gets the application. + /// + /// The application. + public App App { get; } = new App(); + /// /// Gets the conflict solver. /// diff --git a/src/IronyModManager/Implementation/Config/PlatformConfiguration.cs b/src/IronyModManager/Implementation/Config/PlatformConfiguration.cs index ec34fcae..81f5f123 100644 --- a/src/IronyModManager/Implementation/Config/PlatformConfiguration.cs +++ b/src/IronyModManager/Implementation/Config/PlatformConfiguration.cs @@ -5,7 +5,7 @@ // Created : 04-16-2021 // // Last Modified By : Mario -// Last Modified On : 11-26-2023 +// Last Modified On : 02-10-2024 // *********************************************************************** // // Mario @@ -13,13 +13,11 @@ // // *********************************************************************** using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using Microsoft.Extensions.Configuration; using IronyModManager.Platform; using IronyModManager.Platform.Configuration; using IronyModManager.Shared.Configuration; +using Microsoft.Extensions.Configuration; namespace IronyModManager.Implementation.Config { @@ -138,6 +136,7 @@ private PlatformConfigurationOptions GetPlatformOptions() platformConfiguration.Updates.Disable = configuration.GetSection("Updates").GetSection("Disable").Get(); platformConfiguration.TitleBar.Native = configuration.GetSection("TitleBar").GetSection("Native").Get(); platformConfiguration.ConflictSolver.UseSubMenus = configuration.GetSection("ConflictSolver").GetSection("UseSubMenus").Get(); + platformConfiguration.App.SingleInstance = configuration.GetSection("App").GetSection("SingleInstance").Get(); } return platformConfiguration; } diff --git a/src/IronyModManager/appSettings.json b/src/IronyModManager/appSettings.json index b1d8d13a..b96bdd5e 100644 --- a/src/IronyModManager/appSettings.json +++ b/src/IronyModManager/appSettings.json @@ -39,5 +39,8 @@ "UseHybridMemory": true, "UseDiskSearch": true, "CompressIndexedDefinitions": false + }, + "App": { + "SingleInstance": true } } \ No newline at end of file From d912b225d382f2bfa41961fdb56fb3fa702c2a32 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 20:41:01 +0100 Subject: [PATCH 027/101] Add single instance implementation --- src/IronyModManager/App.xaml.cs | 13 +- .../Implementation/SingleInstance/Args.cs | 41 ++++ .../SingleInstance/SingleInstance.cs | 186 ++++++++++++++++++ src/IronyModManager/Program.cs | 35 +++- src/IronyModManager/StaticResources.cs | 39 +++- 5 files changed, 307 insertions(+), 7 deletions(-) create mode 100644 src/IronyModManager/Implementation/SingleInstance/Args.cs create mode 100644 src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs diff --git a/src/IronyModManager/App.xaml.cs b/src/IronyModManager/App.xaml.cs index 9e9968d2..4e1f3028 100644 --- a/src/IronyModManager/App.xaml.cs +++ b/src/IronyModManager/App.xaml.cs @@ -5,7 +5,7 @@ // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 10-29-2023 +// Last Modified On : 02-10-2024 // *********************************************************************** // // Mario @@ -70,6 +70,16 @@ public App() #endregion Constructors + #region Properties + + /// + /// Gets the main window. + /// + /// The main window. + public static MainWindow MainWindow { get; private set; } + + #endregion Properties + #region Methods /// @@ -232,6 +242,7 @@ private void InitApp(IClassicDesktopStyleApplicationLifetime desktop) mainWindow.DataContext = vm; mainWindow.EnsureTitlebarSpacing(); desktop.MainWindow = mainWindow; + MainWindow = mainWindow; } /// diff --git a/src/IronyModManager/Implementation/SingleInstance/Args.cs b/src/IronyModManager/Implementation/SingleInstance/Args.cs new file mode 100644 index 00000000..2043cbc4 --- /dev/null +++ b/src/IronyModManager/Implementation/SingleInstance/Args.cs @@ -0,0 +1,41 @@ + +// *********************************************************************** +// Assembly : IronyModManager +// Author : Mario +// Created : 02-10-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-10-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IronyModManager.Implementation.SingleInstance +{ + + /// + /// Class Args. + /// Implements the + /// + /// + public class Args + { + #region Properties + + /// + /// Gets or sets the command line arguments. + /// + /// The command line arguments. + public string[] CommandLineArgs { get; set; } + + #endregion Properties + } +} diff --git a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs new file mode 100644 index 00000000..e5b29741 --- /dev/null +++ b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs @@ -0,0 +1,186 @@ + +// *********************************************************************** +// Assembly : IronyModManager +// Author : Mario +// Created : 02-10-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-10-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** +using System; +using System.IO.Pipes; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace IronyModManager.Implementation.SingleInstance +{ + + /// + /// Class SingleInstance. + /// + internal static class SingleInstance + { + #region Fields + + /// + /// The delay + /// + private const int delay = 100; + + /// + /// The object lock + /// + private static readonly object objLock = new(); + + /// + /// The mutex + /// + private static Mutex mutex; + + /// + /// The mutex name + /// + private static string mutexName; + + #endregion Fields + + #region Delegates + + /// + /// Delegate ArgsDelegate + /// + /// The arguments. + public delegate void ArgsDelegate(Args args); + + #endregion Delegates + + #region Events + + /// + /// Occurs when [instance launched]. + /// + public static event ArgsDelegate InstanceLaunched; + + #endregion Events + + #region Methods + + /// + /// Initializes this instance. + /// + public static void Initialize() + { + lock (objLock) + { + var initial = false; + try + { + mutex = new Mutex(true, $"Global\\{GetMutexName()}", out initial); + if (initial) + { + Task.Run(Monitor); + } + else + { + var data = new Args() + { + CommandLineArgs = Environment.GetCommandLineArgs() + }; + var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)); + using var pipe = new NamedPipeClientStream(".", GetMutexName(), PipeDirection.Out, PipeOptions.CurrentUserOnly | PipeOptions.WriteThrough); + pipe.Connect(); + pipe.Write(bytes, 0, bytes.Length); + } + } + catch + { + } + if (!initial) + { + Environment.Exit(0); + } + } + } + + /// + /// Monitors this instance. + /// + public static async Task Monitor() + { + using var pipe = new NamedPipeServerStream(GetMutexName(), PipeDirection.In, maxNumberOfServerInstances: 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly | PipeOptions.WriteThrough); + while (mutex != null) + { + try + { + if (!pipe.IsConnected) + { + await pipe.WaitForConnectionAsync(); + } + var buffer = new byte[1024]; + var sb = new StringBuilder(); + while (true) + { + var readBytes = pipe.Read(buffer, 0, buffer.Length); + if (readBytes == 0) + { + break; + } + sb.Append(Encoding.UTF8.GetString(buffer)); + } + var args = JsonConvert.DeserializeObject(sb.ToString()); + pipe.Disconnect(); + if (args != null) + { + InstanceLaunched?.Invoke(args); + } + } + catch + { + await Task.Delay(delay); + } + } + } + + /// + /// Gets the name of the mutex. + /// + /// System.String. + private static string GetMutexName() + { + lock (objLock) + { + if (string.IsNullOrWhiteSpace(mutexName)) + { + var name = nameof(IronyModManager); + var sb = new StringBuilder(); + sb.Append(name, 0, Math.Min(name.Length, 31)); + sb.Append('.'); + var hash = new StringBuilder(); + hash.AppendLine(Environment.MachineName); + hash.AppendLine(Environment.UserName); + var data = SHA256.HashData(Encoding.UTF8.GetBytes(hash.ToString())); + foreach (var item in data) + { + if (sb.Length >= 64) + { + break; + } + sb.Append($"{item:X2}"); + } + mutexName = sb.ToString(); + } + return mutexName; + } + } + + #endregion Methods + } +} diff --git a/src/IronyModManager/Program.cs b/src/IronyModManager/Program.cs index bdcd4cb4..74af665b 100644 --- a/src/IronyModManager/Program.cs +++ b/src/IronyModManager/Program.cs @@ -5,7 +5,7 @@ // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 06-25-2023 +// Last Modified On : 02-10-2024 // *********************************************************************** // // Copyright (c) Mario. All rights reserved. @@ -13,17 +13,18 @@ // // *********************************************************************** using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; +using Avalonia.Threading; using CommandLine; +using IronyModManager.Common; using IronyModManager.Controls.Dialogs; using IronyModManager.DI; using IronyModManager.Implementation.Actions; using IronyModManager.Implementation.AvaloniaEdit; +using IronyModManager.Implementation.SingleInstance; using IronyModManager.Localization; using IronyModManager.Platform; using IronyModManager.Platform.Configuration; @@ -93,6 +94,10 @@ public static void Main(string[] args) try { ParseArguments(args); + if (StaticResources.CommandLineOptions != null && !StaticResources.CommandLineOptions.ShowFatalErrorNotification) + { + InitSingleInstance(); + } var app = BuildAvaloniaApp(); InitAvaloniaOptions(app); Bootstrap.PostStartup(); @@ -195,6 +200,30 @@ private static void InitLogging() LogManager.Setup().SetupExtensions(s => s.RegisterTarget("IronyFile", typeof(Log.IronyFileTarget))); } + /// + /// Initializes the single instance. + /// + private static void InitSingleInstance() + { + var configuration = DIResolver.Get().GetOptions().App; + if (configuration.SingleInstance) + { + SingleInstance.Initialize(); + SingleInstance.InstanceLaunched += (args) => + { + ParseArguments(args.CommandLineArgs); + Dispatcher.UIThread.SafeInvoke(() => + { + App.MainWindow.Show(); + App.MainWindow.Activate(); + var previousState = App.MainWindow.WindowState; + App.MainWindow.WindowState = WindowState.Minimized; + App.MainWindow.WindowState = previousState; + }); + }; + } + } + /// /// Logs the error. /// diff --git a/src/IronyModManager/StaticResources.cs b/src/IronyModManager/StaticResources.cs index bbaf843e..4ad520f1 100644 --- a/src/IronyModManager/StaticResources.cs +++ b/src/IronyModManager/StaticResources.cs @@ -1,10 +1,11 @@ -// *********************************************************************** + +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 05-07-2020 // // Last Modified By : Mario -// Last Modified On : 10-28-2022 +// Last Modified On : 02-10-2024 // *********************************************************************** // // Mario @@ -23,6 +24,7 @@ namespace IronyModManager { + /// /// Class StaticResources. /// @@ -31,6 +33,11 @@ public static class StaticResources { #region Fields + /// + /// The command line options + /// + private static CommandLineArgs commandLineOptions; + /// /// The application icon /// @@ -58,13 +65,39 @@ public static class StaticResources #endregion Fields + #region Delegates + + /// + /// Delegate CommandLineArgsChangedDelegate + /// + public delegate void CommandLineArgsChangedDelegate(); + + #endregion Delegates + + #region Events + + /// + /// Occurs when [command line arguments changed]. + /// + public static event CommandLineArgsChangedDelegate CommandLineArgsChanged; + + #endregion Events + #region Properties /// /// Gets or sets the command line options. /// /// The command line options. - public static CommandLineArgs CommandLineOptions { get; set; } + public static CommandLineArgs CommandLineOptions + { + get => commandLineOptions; + set + { + commandLineOptions = value; + CommandLineArgsChanged?.Invoke(); + } + } /// /// Gets or sets a value indicating whether this instance is verifying container. From b8d00ba2dc4231434479a77146a1019965dffa3c Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 21:02:05 +0100 Subject: [PATCH 028/101] Handle command line parameters --- src/IronyModManager/App.xaml.cs | 31 +++++++++++++------ .../SingleInstance/SingleInstance.cs | 2 +- src/IronyModManager/Program.cs | 4 +-- src/IronyModManager/StaticResources.cs | 9 +++++- .../Controls/ModHolderControlViewModel.cs | 12 +++++-- 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/IronyModManager/App.xaml.cs b/src/IronyModManager/App.xaml.cs index 4e1f3028..fd207501 100644 --- a/src/IronyModManager/App.xaml.cs +++ b/src/IronyModManager/App.xaml.cs @@ -87,7 +87,7 @@ public App() /// public override void Initialize() { - showFatalNotification = StaticResources.CommandLineOptions != null && StaticResources.CommandLineOptions.ShowFatalErrorNotification; + showFatalNotification = StaticResources.CommandLineOptions.ShowFatalErrorNotification; AvaloniaXamlLoader.Load(this); if (!Design.IsDesignMode) { @@ -128,16 +128,29 @@ public override void OnFrameworkInitializationCompleted() /// protected virtual void HandleCommandLine() { - if (StaticResources.CommandLineOptions != null && !string.IsNullOrWhiteSpace(StaticResources.CommandLineOptions.GameAbrv)) + static void setGame(bool raiseEvent = false) { - var gameService = DIResolver.Get(); - var games = gameService.Get(); - var game = games.FirstOrDefault(g => g.Abrv.Equals(StaticResources.CommandLineOptions.GameAbrv, StringComparison.OrdinalIgnoreCase)); - if (game != null) + if (!string.IsNullOrWhiteSpace(StaticResources.CommandLineOptions.GameAbrv)) { - gameService.SetSelected(games, game); + var gameService = DIResolver.Get(); + var games = gameService.Get(); + var game = games.FirstOrDefault(g => g.Abrv.Equals(StaticResources.CommandLineOptions.GameAbrv, StringComparison.OrdinalIgnoreCase)); + if (game != null) + { + gameService.SetSelected(games, game); + if (raiseEvent) + { + var mbus = DIResolver.Get(); + mbus.Publish(new ActiveGameRequestEvent(game)); + } + } } } + StaticResources.CommandLineArgsChanged += () => + { + setGame(true); + }; + setGame(); } /// @@ -201,7 +214,7 @@ protected virtual void SetAppTitle(IClassicDesktopStyleApplicationLifetime deskt if (File.Exists(Constants.TitleSuffixFilename)) { var suffix = File.ReadAllLines(Constants.TitleSuffixFilename); - if (suffix.Any()) + if (suffix.Length != 0) { appTitle = $"{appTitle} ({suffix.FirstOrDefault()})"; } @@ -219,7 +232,7 @@ protected virtual async Task VerifyWritePermissionsAsync() await Task.Delay(5000); var permissionService = DIResolver.Get(); var permissions = permissionService.VerifyPermissions(); - if (permissions.Any() && permissions.Any(p => !p.Valid)) + if (permissions.Count != 0 && permissions.Any(p => !p.Valid)) { var notificationAction = DIResolver.Get(); var locManager = DIResolver.Get(); diff --git a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs index e5b29741..c4e49798 100644 --- a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs +++ b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs @@ -33,7 +33,7 @@ internal static class SingleInstance /// /// The delay /// - private const int delay = 100; + private const int Delay = 100; /// /// The object lock diff --git a/src/IronyModManager/Program.cs b/src/IronyModManager/Program.cs index 74af665b..0dff6eb0 100644 --- a/src/IronyModManager/Program.cs +++ b/src/IronyModManager/Program.cs @@ -94,7 +94,7 @@ public static void Main(string[] args) try { ParseArguments(args); - if (StaticResources.CommandLineOptions != null && !StaticResources.CommandLineOptions.ShowFatalErrorNotification) + if (!StaticResources.CommandLineOptions.ShowFatalErrorNotification) { InitSingleInstance(); } @@ -235,7 +235,7 @@ private static async void LogError(Exception e) var logger = DIResolver.Get(); logger.Fatal(e); - var runFatalErrorProcess = StaticResources.CommandLineOptions == null || !StaticResources.CommandLineOptions.ShowFatalErrorNotification; + var runFatalErrorProcess = !StaticResources.CommandLineOptions.ShowFatalErrorNotification; if (runFatalErrorProcess && !ExternalNotificationShown) { var path = Environment.ProcessPath; diff --git a/src/IronyModManager/StaticResources.cs b/src/IronyModManager/StaticResources.cs index 4ad520f1..06f5fc37 100644 --- a/src/IronyModManager/StaticResources.cs +++ b/src/IronyModManager/StaticResources.cs @@ -91,7 +91,14 @@ public static class StaticResources /// The command line options. public static CommandLineArgs CommandLineOptions { - get => commandLineOptions; + get + { + if (commandLineOptions == null) + { + commandLineOptions = new CommandLineArgs(); + } + return commandLineOptions; + }; set { commandLineOptions = value; diff --git a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs index 4b28e004..2f0f15ee 100644 --- a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs @@ -5,7 +5,7 @@ // Created : 02-29-2020 // // Last Modified By : Mario -// Last Modified On : 11-27-2023 +// Last Modified On : 02-10-2024 // *********************************************************************** // // Mario @@ -260,10 +260,18 @@ public ModHolderControlViewModel(IExternalProcessHandlerService externalProcessH InstalledMods = installedModsControlViewModel; CollectionMods = collectionModsControlViewModel; UseSimpleLayout = !DIResolver.Get().GetOptions().ConflictSolver.UseSubMenus; - if (StaticResources.CommandLineOptions != null && StaticResources.CommandLineOptions.EnableResumeGameButton) + if (StaticResources.CommandLineOptions.EnableResumeGameButton) { forceEnableResumeButton = true; } + StaticResources.CommandLineArgsChanged += () => + { + if (StaticResources.CommandLineOptions.EnableResumeGameButton) + { + forceEnableResumeButton = true; + EvalResumeAvailability(); + } + }; } #endregion Constructors From 144ef1400ae91c8c48949288c98fe5b30c03228b Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 21:02:32 +0100 Subject: [PATCH 029/101] Fix mistakes --- .../Implementation/SingleInstance/SingleInstance.cs | 2 +- src/IronyModManager/StaticResources.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs index c4e49798..5f19d657 100644 --- a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs +++ b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs @@ -144,7 +144,7 @@ public static async Task Monitor() } catch { - await Task.Delay(delay); + await Task.Delay(Delay); } } } diff --git a/src/IronyModManager/StaticResources.cs b/src/IronyModManager/StaticResources.cs index 06f5fc37..2b6622aa 100644 --- a/src/IronyModManager/StaticResources.cs +++ b/src/IronyModManager/StaticResources.cs @@ -98,7 +98,7 @@ public static CommandLineArgs CommandLineOptions commandLineOptions = new CommandLineArgs(); } return commandLineOptions; - }; + } set { commandLineOptions = value; From 55d32d1b02109d139f642fd00aecd39a8172906b Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 21:13:56 +0100 Subject: [PATCH 030/101] Make suggested IDE changes --- src/IronyModManager/StaticResources.cs | 5 +- .../CollectionModsControlViewModel.cs | 56 +++++++++---------- .../Controls/GameControlViewModel.cs | 30 ++++------ .../Controls/ModHolderControlViewModel.cs | 2 +- 4 files changed, 40 insertions(+), 53 deletions(-) diff --git a/src/IronyModManager/StaticResources.cs b/src/IronyModManager/StaticResources.cs index 2b6622aa..d11347e1 100644 --- a/src/IronyModManager/StaticResources.cs +++ b/src/IronyModManager/StaticResources.cs @@ -93,10 +93,7 @@ public static CommandLineArgs CommandLineOptions { get { - if (commandLineOptions == null) - { - commandLineOptions = new CommandLineArgs(); - } + commandLineOptions ??= new CommandLineArgs(); return commandLineOptions; } set diff --git a/src/IronyModManager/ViewModels/Controls/CollectionModsControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/CollectionModsControlViewModel.cs index b0e45a04..0c619376 100644 --- a/src/IronyModManager/ViewModels/Controls/CollectionModsControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/CollectionModsControlViewModel.cs @@ -5,7 +5,7 @@ // Created : 03-03-2020 // // Last Modified By : Mario -// Last Modified On : 12-05-2023 +// Last Modified On : 02-10-2024 // *********************************************************************** // // Mario @@ -41,9 +41,9 @@ using IronyModManager.Models.Common; using IronyModManager.Services.Common; using IronyModManager.Shared; -using static IronyModManager.ViewModels.Controls.ModifyCollectionControlViewModel; using Nito.AsyncEx; using ReactiveUI; +using static IronyModManager.ViewModels.Controls.ModifyCollectionControlViewModel; namespace IronyModManager.ViewModels.Controls { @@ -284,7 +284,7 @@ public CollectionModsControlViewModel(IScrollState scrollState, ModExportProgres this.modExportProgressHandler = modExportProgressHandler; HashReportView = hashReportView; SearchMods.ShowArrows = true; - reorderQueue = new ConcurrentBag(); + reorderQueue = []; } #endregion Constructors @@ -954,7 +954,7 @@ async Task reorder() /// true if [is redo available]; otherwise, false. public virtual bool IsRedoAvailable() { - return redoStack.Any(); + return redoStack.Count != 0; } /// @@ -963,7 +963,7 @@ public virtual bool IsRedoAvailable() /// true if [is undo available]; otherwise, false. public virtual bool IsUndoAvailable() { - return undoStack.Any(); + return undoStack.Count != 0; } /// @@ -1191,8 +1191,8 @@ protected virtual void HandleModCollectionChange(bool resetStack) var missingMods = new List(); var hasModNames = existingCollection.ModNames != null && existingCollection.ModNames.Count() == existingCollection.Mods.Count(); var mods = existingCollection.Mods.ToList(); - var modPaths = existingCollection.ModPaths != null ? existingCollection.ModPaths.ToList() : new List(); - var modNames = hasModNames ? existingCollection.ModNames.ToList() : new List(); + var modPaths = existingCollection.ModPaths != null ? existingCollection.ModPaths.ToList() : []; + var modNames = hasModNames ? existingCollection.ModNames.ToList() : []; for (int i = 0; i < mods.Count; i++) { var item = mods[i]; @@ -1219,7 +1219,7 @@ protected virtual void HandleModCollectionChange(bool resetStack) } } } - if (missingMods.Any() && activeGame != null && existingCollection.Game.Equals(activeGame.Type)) + if (missingMods.Count != 0 && activeGame != null && existingCollection.Game.Equals(activeGame.Type)) { var title = localizationManager.GetResource(LocalizationResources.Collection_Mods.Prompts.ModsMissingTitle); var message = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.Prompts.ModsMissingMessage), new { Environment.NewLine, Mods = string.Join(Environment.NewLine, missingMods) }); @@ -1280,7 +1280,7 @@ Task importInstance(IModCollection importData) if (importData != null) { importData.IsSelected = true; - modNames = importData.ModNames != null ? importData.ModNames.ToList() : new List(); + modNames = importData.ModNames != null ? importData.ModNames.ToList() : []; if (modCollectionService.Save(importData)) { return Task.FromResult(importData); @@ -1333,13 +1333,13 @@ Task importInstance(IModCollection importData) var game = gameService.Get().FirstOrDefault(p => p.Type.Equals(result.Game, StringComparison.OrdinalIgnoreCase)); await MessageBus.PublishAsync(new ActiveGameRequestEvent(game)); await MessageBus.PublishAsync(new ModListInstallRefreshRequestEvent(true)); - var hasMods = (modNames?.Any()).GetValueOrDefault(); - var mods = game != null ? await modService.GetAvailableModsAsync(game) ?? new List() : new List(); + var hasMods = modNames != null && modNames.Count != 0; + var mods = game != null ? await modService.GetAvailableModsAsync(game) ?? [] : []; if (hasMods && !string.IsNullOrWhiteSpace(result.MergedFolderName)) { var importedMods = new List(); var descriptors = result.Mods.ToList(); - var paths = result.ModPaths != null ? result.ModPaths.ToList() : new List(); + var paths = result.ModPaths != null ? result.ModPaths.ToList() : []; for (int i = 0; i < descriptors.Count; i++) { var descriptor = descriptors[i]; @@ -1366,8 +1366,8 @@ Task importInstance(IModCollection importData) result.Mods = importedMods; modCollectionService.Save(result); } - var modDescriptorPaths = result.Mods != null ? result.Mods.ToList() : new List(); - var modPaths = result.ModPaths != null ? result.ModPaths.ToList() : new List(); + var modDescriptorPaths = result.Mods != null ? result.Mods.ToList() : []; + var modPaths = result.ModPaths != null ? result.ModPaths.ToList() : []; restoreCollectionSelection = result.Name; LoadModCollections(); var showImportNotification = true; @@ -1379,7 +1379,7 @@ Task importInstance(IModCollection importData) if (nonExistingModPaths.Any()) { var nonExistingModNames = new List(); - var hasModNames = modNames != null && modNames.Any() && modDescriptorPaths.Count == modNames.Count; + var hasModNames = modNames != null && modNames.Count != 0 && modDescriptorPaths.Count == modNames.Count; foreach (var item in nonExistingModPaths) { var index = modDescriptorPaths.IndexOf(item); @@ -1404,7 +1404,7 @@ Task importInstance(IModCollection importData) } } } - if (nonExistingModNames.Any()) + if (nonExistingModNames.Count != 0) { var notExistingModTitle = localizationManager.GetResource(LocalizationResources.Collection_Mods.ImportNonExistingMods.Title); var nonExistingModMessage = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Collection_Mods.ImportNonExistingMods.Message), new { Environment.NewLine, Mods = string.Join(Environment.NewLine, nonExistingModNames) }); @@ -1484,7 +1484,7 @@ protected override void OnActivated(CompositeDisposable disposables) { EvaluateHighlight(); EvalGameSpecificVisibility(); - SetSelectedModsState(Mods != null ? Mods.Where(p => p.IsSelected).ToObservableCollection() : new ObservableCollection()); + SetSelectedModsState(Mods != null ? Mods.Where(p => p.IsSelected).ToObservableCollection() : []); SubscribeToMods(); skipModSelectionSave = true; @@ -1543,7 +1543,7 @@ protected override void OnActivated(CompositeDisposable disposables) mod.IsSelected = false; } } - SetSelectedModsState(Mods != null ? Mods.Where(p => p.IsSelected).ToObservableCollection() : new ObservableCollection()); + SetSelectedModsState(Mods != null ? Mods.Where(p => p.IsSelected).ToObservableCollection() : []); AllModsEnabled = SelectedMods?.Count > 0 && SelectedMods.All(p => p.IsSelected); LoadModCollections(); SaveState(); @@ -1663,7 +1663,7 @@ await Task.Run(async () => mod.IsSelected = false; } } - SetSelectedModsState(Mods != null ? Mods.Where(p => p.IsSelected).ToObservableCollection() : new ObservableCollection()); + SetSelectedModsState(Mods != null ? Mods.Where(p => p.IsSelected).ToObservableCollection() : []); AllModsEnabled = SelectedMods?.Count > 0 && SelectedMods.All(p => p.IsSelected); LoadModCollections(); SaveState(); @@ -1767,7 +1767,7 @@ await Task.Run(async () => { item.IsSelected = false; } - SetSelectedModsState(new List()); + SetSelectedModsState([]); SaveSelectedCollection(); } skipModCollectionSave = false; @@ -1919,7 +1919,7 @@ void registerReportHandlers(long id, bool useImportOverlay = false) { reports.AddRange(gameReports); } - if (reports.Any()) + if (reports.Count != 0) { await TriggerOverlayAsync(id, false); HashReportView.SetParameters(reports); @@ -2082,7 +2082,7 @@ protected virtual async Task PerformModReorderAsync(bool instant, CancellationTo if (SelectedMods != null) { using var mutex = await reorderLock.LockAsync(cancellationToken); - if (reorderQueue.Any()) + if (!reorderQueue.IsEmpty) { scrollState.SetState(false); skipModSelectionSave = true; @@ -2132,7 +2132,7 @@ protected virtual async Task PerformModReorderAsync(bool instant, CancellationTo /// protected virtual void PerformRedo() { - if (!redoStack.Any()) + if (redoStack.Count == 0) { return; } @@ -2186,7 +2186,7 @@ protected virtual void PerformRedoUndoOrdering(IEnumerable descriptors) /// protected virtual void PerformUndo() { - if (!undoStack.Any()) + if (undoStack.Count == 0) { return; } @@ -2205,7 +2205,7 @@ protected virtual void RecognizeSortOrder(IModCollection modCollection) { var mods = new List(); var colMods = modCollection.Mods.ToList(); - var colPaths = modCollection.ModPaths != null ? modCollection.ModPaths.ToList() : new List(); + var colPaths = modCollection.ModPaths != null ? modCollection.ModPaths.ToList() : []; for (int i = 0; i < colMods.Count; i++) { var item = colMods[i]; @@ -2220,7 +2220,7 @@ protected virtual void RecognizeSortOrder(IModCollection modCollection) mods.Add(mod); } } - if (mods.Any()) + if (mods.Count != 0) { var ascending = mods.OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase).Select(p => p.DescriptorFile); var descending = mods.OrderByDescending(p => p.Name, StringComparer.OrdinalIgnoreCase).Select(p => p.DescriptorFile); @@ -2311,7 +2311,7 @@ protected virtual void SaveSelectedCollection() } collection.Game = game; collection.Name = SelectedModCollection.Name; - var selectedMods = SelectedMods != null ? SelectedMods.Where(p => p.IsSelected) : new List(); + var selectedMods = SelectedMods != null ? SelectedMods.Where(p => p.IsSelected) : []; collection.Mods = selectedMods.Select(p => p.DescriptorFile).ToList(); collection.ModPaths = selectedMods.Select(p => p.FullPath).ToList(); collection.IsSelected = true; @@ -2408,7 +2408,7 @@ protected virtual void SetSelectedModsState(IList selectedMods, bool canSh previousValidatedMods.AddOrUpdate(SelectedModCollection.Name, oldMods, (k, v) => oldMods); if (!ignoreStack && !prevMods.ListsSame(selectedMods)) { - undoStack.Push((prevMods ?? new List()).Select(p => p.DescriptorFile).ToList()); + undoStack.Push((prevMods ?? []).Select(p => p.DescriptorFile).ToList()); redoStack.Clear(); } } diff --git a/src/IronyModManager/ViewModels/Controls/GameControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/GameControlViewModel.cs index 92cbb293..d74ab532 100644 --- a/src/IronyModManager/ViewModels/Controls/GameControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/GameControlViewModel.cs @@ -1,10 +1,11 @@ -// *********************************************************************** + +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 02-12-2020 // // Last Modified By : Mario -// Last Modified On : 02-23-2021 +// Last Modified On : 02-10-2024 // *********************************************************************** // // Mario @@ -27,25 +28,29 @@ namespace IronyModManager.ViewModels.Controls { + /// /// Class GameControlViewModel. /// Implements the /// /// + /// The game service. + /// The active game request handler. + /// Initializes a new instance of the class. [ExcludeFromCoverage("This should be tested via functional testing.")] - public class GameControlViewModel : BaseViewModel + public class GameControlViewModel(IGameService gameService, ActiveGameRequestHandler activeGameRequestHandler) : BaseViewModel { #region Fields /// /// The active game request handler /// - private readonly ActiveGameRequestHandler activeGameRequestHandler; + private readonly ActiveGameRequestHandler activeGameRequestHandler = activeGameRequestHandler; /// /// The game service /// - private readonly IGameService gameService; + private readonly IGameService gameService = gameService; /// /// The previous game @@ -54,21 +59,6 @@ public class GameControlViewModel : BaseViewModel #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The game service. - /// The active game request handler. - public GameControlViewModel(IGameService gameService, ActiveGameRequestHandler activeGameRequestHandler) - { - this.gameService = gameService; - this.activeGameRequestHandler = activeGameRequestHandler; - } - - #endregion Constructors - #region Properties /// diff --git a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs index 2f0f15ee..0d596979 100644 --- a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs @@ -739,7 +739,7 @@ protected virtual async Task ApplyCollectionAsync(long id, bool showOverlay = tr var notificationType = NotificationType.Success; try { - var result = await modService.ExportModsAsync(CollectionMods.SelectedMods.ToList(), InstalledMods.AllMods.ToList(), CollectionMods.SelectedModCollection); + var result = await modService.ExportModsAsync([.. CollectionMods.SelectedMods], [.. InstalledMods.AllMods], CollectionMods.SelectedModCollection); string title; string message; if (result) From e94f13eebdae2e47f5f34aca81a6ebb7d8cdc88c Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 10 Feb 2024 21:23:37 +0100 Subject: [PATCH 031/101] Fix some issues Make activegame request change thread safe --- src/IronyModManager/App.xaml.cs | 9 ++++++--- .../ViewModels/Controls/GameControlViewModel.cs | 7 ++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/IronyModManager/App.xaml.cs b/src/IronyModManager/App.xaml.cs index fd207501..81342c78 100644 --- a/src/IronyModManager/App.xaml.cs +++ b/src/IronyModManager/App.xaml.cs @@ -128,7 +128,7 @@ public override void OnFrameworkInitializationCompleted() /// protected virtual void HandleCommandLine() { - static void setGame(bool raiseEvent = false) + static void setGame(bool raiseOnlyEvent = false) { if (!string.IsNullOrWhiteSpace(StaticResources.CommandLineOptions.GameAbrv)) { @@ -137,12 +137,15 @@ static void setGame(bool raiseEvent = false) var game = games.FirstOrDefault(g => g.Abrv.Equals(StaticResources.CommandLineOptions.GameAbrv, StringComparison.OrdinalIgnoreCase)); if (game != null) { - gameService.SetSelected(games, game); - if (raiseEvent) + if (raiseOnlyEvent) { var mbus = DIResolver.Get(); mbus.Publish(new ActiveGameRequestEvent(game)); } + else + { + gameService.SetSelected(games, game); + } } } } diff --git a/src/IronyModManager/ViewModels/Controls/GameControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/GameControlViewModel.cs index d74ab532..1f728bf0 100644 --- a/src/IronyModManager/ViewModels/Controls/GameControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/GameControlViewModel.cs @@ -17,6 +17,8 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; +using Avalonia.Threading; +using IronyModManager.Common; using IronyModManager.Common.Events; using IronyModManager.Common.ViewModels; using IronyModManager.Implementation.MessageBus; @@ -113,7 +115,10 @@ protected override void OnActivated(CompositeDisposable disposables) { if (m.Game != null) { - SelectedGame = Games.FirstOrDefault(p => p.Type.Equals(m.Game.Type, StringComparison.OrdinalIgnoreCase)); + Dispatcher.UIThread.SafeInvoke(() => + { + SelectedGame = Games.FirstOrDefault(p => p.Type.Equals(m.Game.Type, StringComparison.OrdinalIgnoreCase)); + }); } }).DisposeWith(disposables); From a818eb1125427dad41d85f47d236833048329eee Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 11 Feb 2024 03:56:45 +0100 Subject: [PATCH 032/101] Ensure thread safety when evaluation whether to force show resume button --- .../ViewModels/Controls/ModHolderControlViewModel.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs index 0d596979..94c5c925 100644 --- a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs @@ -5,7 +5,7 @@ // Created : 02-29-2020 // // Last Modified By : Mario -// Last Modified On : 02-10-2024 +// Last Modified On : 02-11-2024 // *********************************************************************** // // Mario @@ -21,6 +21,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; +using Avalonia.Threading; using IronyModManager.Common; using IronyModManager.Common.Events; using IronyModManager.Common.ViewModels; @@ -266,11 +267,11 @@ public ModHolderControlViewModel(IExternalProcessHandlerService externalProcessH } StaticResources.CommandLineArgsChanged += () => { - if (StaticResources.CommandLineOptions.EnableResumeGameButton) + Dispatcher.UIThread.SafeInvoke(() => { - forceEnableResumeButton = true; + forceEnableResumeButton = StaticResources.CommandLineOptions.EnableResumeGameButton; EvalResumeAvailability(); - } + }); }; } From ad43f371d07691a2320a89498c8b5841dde027a3 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 11 Feb 2024 04:00:30 +0100 Subject: [PATCH 033/101] Add disable flag --- .../Configuration/PlatformConfigurationOptions.cs | 15 ++++++++------- .../Config/PlatformConfiguration.cs | 3 ++- src/IronyModManager/appSettings.json | 3 ++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/IronyModManager.Platform/Configuration/PlatformConfigurationOptions.cs b/src/IronyModManager.Platform/Configuration/PlatformConfigurationOptions.cs index d37ec651..2ddea5bf 100644 --- a/src/IronyModManager.Platform/Configuration/PlatformConfigurationOptions.cs +++ b/src/IronyModManager.Platform/Configuration/PlatformConfigurationOptions.cs @@ -1,21 +1,16 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Platform // Author : Mario // Created : 04-16-2021 // // Last Modified By : Mario -// Last Modified On : 02-10-2024 +// Last Modified On : 02-11-2024 // *********************************************************************** // // Mario // // // *********************************************************************** -using System; -using System.Collections.Generic; -using System.Linq; - namespace IronyModManager.Platform.Configuration { @@ -232,6 +227,12 @@ public class Updates /// true if disable; otherwise, false. public bool Disable { get; set; } + /// + /// Gets or sets a value indicating whether [disable install only]. + /// + /// true if [disable install only]; otherwise, false. + public bool DisableInstallOnly { get; set; } + #endregion Properties } } diff --git a/src/IronyModManager/Implementation/Config/PlatformConfiguration.cs b/src/IronyModManager/Implementation/Config/PlatformConfiguration.cs index 81f5f123..f48df6a6 100644 --- a/src/IronyModManager/Implementation/Config/PlatformConfiguration.cs +++ b/src/IronyModManager/Implementation/Config/PlatformConfiguration.cs @@ -5,7 +5,7 @@ // Created : 04-16-2021 // // Last Modified By : Mario -// Last Modified On : 02-10-2024 +// Last Modified On : 02-11-2024 // *********************************************************************** // // Mario @@ -134,6 +134,7 @@ private PlatformConfigurationOptions GetPlatformOptions() platformConfiguration.Tooltips.Disable = configuration.GetSection("Tooltips").GetSection("Disable").Get(); platformConfiguration.Fonts.UseInbuiltFontsOnly = configuration.GetSection("Fonts").GetSection("UseInbuiltFontsOnly").Get(); platformConfiguration.Updates.Disable = configuration.GetSection("Updates").GetSection("Disable").Get(); + platformConfiguration.Updates.DisableInstallOnly = configuration.GetSection("Updates").GetSection("DisableInstallOnly").Get(); platformConfiguration.TitleBar.Native = configuration.GetSection("TitleBar").GetSection("Native").Get(); platformConfiguration.ConflictSolver.UseSubMenus = configuration.GetSection("ConflictSolver").GetSection("UseSubMenus").Get(); platformConfiguration.App.SingleInstance = configuration.GetSection("App").GetSection("SingleInstance").Get(); diff --git a/src/IronyModManager/appSettings.json b/src/IronyModManager/appSettings.json index b96bdd5e..1e9cde60 100644 --- a/src/IronyModManager/appSettings.json +++ b/src/IronyModManager/appSettings.json @@ -17,7 +17,8 @@ "UseDeferredRendering": null }, "Updates": { - "Disable": false + "Disable": false, + "DisableInstallOnly": false }, "Steam": { "UseLegacyLaunchMethod": false, From 031f809120a52e5ed2772dedd2df6d64ed7797f7 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 11 Feb 2024 04:03:11 +0100 Subject: [PATCH 034/101] Add option to hide installing updates --- .../ViewModels/Controls/OptionsControlViewModel.cs | 13 +++++++++++-- .../Views/Controls/OptionsControlView.xaml | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs index f2c3a995..f4d3ff10 100644 --- a/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs @@ -1,10 +1,11 @@ -// *********************************************************************** + +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 05-30-2020 // // Last Modified By : Mario -// Last Modified On : 11-08-2022 +// Last Modified On : 02-11-2024 // *********************************************************************** // // Mario @@ -37,6 +38,7 @@ namespace IronyModManager.ViewModels.Controls { + /// /// Class OptionsControlViewModel. /// Implements the @@ -210,6 +212,7 @@ public OptionsControlViewModel(IAppAction appAction, IPlatformConfiguration plat this.modService = modService; this.appAction = appAction; UpdatesAllowed = !platformConfiguration.GetOptions().Updates.Disable; + InstallingUpdatesAllowed = !platformConfiguration.GetOptions().Updates.DisableInstallOnly; LeftMargin = new Thickness(20, 0, 0, 0); LeftChildMargin = new Thickness(20, 10, 0, 0); } @@ -365,6 +368,12 @@ public OptionsControlViewModel(IAppAction appAction, IPlatformConfiguration plat [StaticLocalization(LocalizationResources.Options.Game.Title)] public virtual string GameOptions { get; protected set; } + /// + /// Gets or sets a value indicating whether [installing updates allowed]. + /// + /// true if [installing updates allowed]; otherwise, false. + public virtual bool InstallingUpdatesAllowed { get; protected set; } + /// /// Gets or sets the install updates. /// diff --git a/src/IronyModManager/Views/Controls/OptionsControlView.xaml b/src/IronyModManager/Views/Controls/OptionsControlView.xaml index cd77e5fd..b39b03d0 100644 --- a/src/IronyModManager/Views/Controls/OptionsControlView.xaml +++ b/src/IronyModManager/Views/Controls/OptionsControlView.xaml @@ -109,7 +109,7 @@ - From c896c00c477ad0c2596c8bb0fc509f95699cef91 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 11 Feb 2024 04:05:15 +0100 Subject: [PATCH 035/101] Disable update installation of osx --- src/IronyModManager/appSettings.osx-x64.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/IronyModManager/appSettings.osx-x64.json b/src/IronyModManager/appSettings.osx-x64.json index 5e7c4f82..ab3ec0a6 100644 --- a/src/IronyModManager/appSettings.osx-x64.json +++ b/src/IronyModManager/appSettings.osx-x64.json @@ -10,5 +10,13 @@ "@jdt.value": false } ] + }, + "Updates": { + "@jdt.replace": [ + { + "@jdt.path": "$.DisableInstallOnly", + "@jdt.value": true + } + ] } } From a98e16f2a4047050ec420a4c7d578df65bc4bf67 Mon Sep 17 00:00:00 2001 From: bcssov Date: Wed, 14 Feb 2024 16:00:21 +0100 Subject: [PATCH 036/101] Bump Fsharp core --- src/IronyModManager.Parser/IronyModManager.Parser.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager.Parser/IronyModManager.Parser.csproj b/src/IronyModManager.Parser/IronyModManager.Parser.csproj index e1b22897..29ba83ee 100644 --- a/src/IronyModManager.Parser/IronyModManager.Parser.csproj +++ b/src/IronyModManager.Parser/IronyModManager.Parser.csproj @@ -41,7 +41,7 @@ - + all From ecb35003eee243bc87c2ab46196bd3e7318da509 Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 15 Feb 2024 20:20:22 +0100 Subject: [PATCH 037/101] Remove static mainwindow and switch to helper class --- src/IronyModManager/App.xaml.cs | 48 +++++++++---------- src/IronyModManager/Program.cs | 43 ++++++++++------- src/IronyModManager/Views/MainWindow.xaml.cs | 49 ++++++++++---------- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/src/IronyModManager/App.xaml.cs b/src/IronyModManager/App.xaml.cs index 81342c78..b252cb3c 100644 --- a/src/IronyModManager/App.xaml.cs +++ b/src/IronyModManager/App.xaml.cs @@ -1,18 +1,19 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 02-10-2024 +// Last Modified On : 02-15-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -40,7 +41,6 @@ namespace IronyModManager { - /// /// Class App. /// Implements the @@ -54,7 +54,7 @@ public class App : Application /// /// The show fatal notification /// - private bool showFatalNotification = false; + private bool showFatalNotification; #endregion Fields @@ -70,16 +70,6 @@ public App() #endregion Constructors - #region Properties - - /// - /// Gets the main window. - /// - /// The main window. - public static MainWindow MainWindow { get; private set; } - - #endregion Properties - #region Methods /// @@ -149,6 +139,7 @@ static void setGame(bool raiseOnlyEvent = false) } } } + StaticResources.CommandLineArgsChanged += () => { setGame(true); @@ -168,8 +159,10 @@ static double validateSize(double minValue, double value) { return minValue; } + return value; } + var stateService = DIResolver.Get(); desktop.MainWindow.Height = validateSize(desktop.MainWindow.MinHeight, desktop.MainWindow.Height) + desktop.MainWindow.ExtendClientAreaTitleBarHeightHint; desktop.MainWindow.Width = validateSize(desktop.MainWindow.MinWidth, desktop.MainWindow.Width); @@ -188,7 +181,7 @@ protected virtual void InitAppTitle(IClassicDesktopStyleApplicationLifetime desk { SetAppTitle(desktop); var listener = MessageBus.Current.Listen(); - listener.SubscribeObservable(x => + listener.SubscribeObservable(_ => { SetAppTitle(desktop); }); @@ -210,10 +203,7 @@ protected virtual void InitCulture() protected virtual void SetAppTitle(IClassicDesktopStyleApplicationLifetime desktop) { var appTitle = IronyFormatter.Format(DIResolver.Get().GetResource(LocalizationResources.App.Title), - new - { - AppVersion = FileVersionInfo.GetVersionInfo(GetType().Assembly.Location).ProductVersion.Split("+")[0] - }); + new { AppVersion = FileVersionInfo.GetVersionInfo(GetType().Assembly.Location).ProductVersion!.Split("+")[0] }); if (File.Exists(Constants.TitleSuffixFilename)) { var suffix = File.ReadAllLines(Constants.TitleSuffixFilename); @@ -222,6 +212,7 @@ protected virtual void SetAppTitle(IClassicDesktopStyleApplicationLifetime deskt appTitle = $"{appTitle} ({suffix.FirstOrDefault()})"; } } + desktop.MainWindow.Title = appTitle; } @@ -240,7 +231,8 @@ protected virtual async Task VerifyWritePermissionsAsync() var notificationAction = DIResolver.Get(); var locManager = DIResolver.Get(); var title = locManager.GetResource(LocalizationResources.UnableToWriteError.Title); - var message = IronyFormatter.Format(locManager.GetResource(LocalizationResources.UnableToWriteError.Message), new { Environment.NewLine, Paths = string.Join(Environment.NewLine, permissions.Where(p => !p.Valid).Select(p => p.Path).ToList()) }); + var message = IronyFormatter.Format(locManager.GetResource(LocalizationResources.UnableToWriteError.Message), + new { Environment.NewLine, Paths = string.Join(Environment.NewLine, permissions.Where(p => !p.Valid).Select(p => p.Path).ToList()) }); await notificationAction.ShowPromptAsync(title, title, message, NotificationType.Error, PromptType.OK); } } @@ -258,7 +250,6 @@ private void InitApp(IClassicDesktopStyleApplicationLifetime desktop) mainWindow.DataContext = vm; mainWindow.EnsureTitlebarSpacing(); desktop.MainWindow = mainWindow; - MainWindow = mainWindow; } /// @@ -276,11 +267,14 @@ await Dispatcher.UIThread.SafeInvokeAsync(async () => await appAction.ExitAppAsync(); }); } + var logger = DIResolver.Get(); var lastException = logger.GetLastFatalExceptionMessage(); var locManager = DIResolver.Get(); var title = locManager.GetResource(LocalizationResources.FatalError.Title); - var message = string.IsNullOrWhiteSpace(lastException) ? locManager.GetResource(LocalizationResources.FatalError.Message) : locManager.GetResource(LocalizationResources.FatalError.MessageWithLastError).FormatIronySmart(new { Environment.NewLine, Message = string.Join(Environment.NewLine, lastException.SplitOnNewLine()) }); + var message = string.IsNullOrWhiteSpace(lastException) + ? locManager.GetResource(LocalizationResources.FatalError.Message) + : locManager.GetResource(LocalizationResources.FatalError.MessageWithLastError).FormatIronySmart(new { Environment.NewLine, Message = string.Join(Environment.NewLine, lastException.SplitOnNewLine()) }); var header = locManager.GetResource(LocalizationResources.FatalError.Header); var messageBox = MessageBoxes.GetFatalErrorWindow(title, header, message); @@ -292,6 +286,7 @@ await Dispatcher.UIThread.SafeInvokeAsync(async () => { desktop.MainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen; } + if (string.IsNullOrWhiteSpace(lastException)) { close().ConfigureAwait(false); @@ -308,7 +303,7 @@ private void InitThemes() themeManager.ApplyTheme(currentTheme.Type); var themeListener = MessageBus.Current.Listen(); - themeListener.SubscribeObservable(x => + themeListener.SubscribeObservable(_ => { OnThemeChanged().ConfigureAwait(true); }); @@ -318,7 +313,7 @@ private void InitThemes() { var window = (MainWindow)Helpers.GetMainWindow(); var id = idGenerator.GetNextId(); - window.ViewModel.TriggerManualOverlay(id, true, string.Empty); + window.ViewModel!.TriggerManualOverlay(id, true, string.Empty); SetFontFamily(window, x.Locale); window.ViewModel.TriggerManualOverlay(id, false, string.Empty); }); @@ -363,8 +358,9 @@ private void SetFontFamily(Window mainWindow, string locale = Shared.Constants.E { language = langService.Get().FirstOrDefault(p => p.Abrv.Equals(locale)); } + var fontResolver = DIResolver.Get(); - var font = fontResolver.ResolveFontFamily(language.Font); + var font = fontResolver.ResolveFontFamily(language!.Font); mainWindow.FontFamily = font.GetFontFamily(); } diff --git a/src/IronyModManager/Program.cs b/src/IronyModManager/Program.cs index 0dff6eb0..0876ddaf 100644 --- a/src/IronyModManager/Program.cs +++ b/src/IronyModManager/Program.cs @@ -1,18 +1,20 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 02-10-2024 +// Last Modified On : 02-15-2024 // *********************************************************************** // // Copyright (c) Mario. All rights reserved. // // // *********************************************************************** + using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia; @@ -34,7 +36,6 @@ namespace IronyModManager { - /// /// Class Program. /// @@ -46,7 +47,7 @@ internal class Program /// /// The external notification shown /// - private static bool ExternalNotificationShown = false; + private static bool ExternalNotificationShown; #endregion Fields @@ -58,9 +59,10 @@ internal class Program /// /// AppBuilder. public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() + { + return AppBuilder.Configure() .UseIronyPlatformDetect() - .UseIronyManagedDialogs().AfterSetup((s) => + .UseIronyManagedDialogs().AfterSetup(_ => { if (!Design.IsDesignMode) { @@ -73,6 +75,7 @@ public static AppBuilder BuildAvaloniaApp() DIContainer.Finish(true); } }); + } // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized @@ -98,6 +101,7 @@ public static void Main(string[] args) { InitSingleInstance(); } + var app = BuildAvaloniaApp(); InitAvaloniaOptions(app); Bootstrap.PostStartup(); @@ -146,27 +150,33 @@ void configureLinux() { waylandOpts.AppId = configuration.WaylandAppId; } + if (configuration.UseGPU.HasValue) { x11Opts.UseGpu = configuration.UseGPU.GetValueOrDefault(); waylandOpts.UseGpu = configuration.UseGPU.GetValueOrDefault(); } + if (configuration.UseEGL.HasValue) { x11Opts.UseEGL = configuration.UseEGL.GetValueOrDefault(); } + if (configuration.UseDBusMenu.HasValue) { x11Opts.UseDBusMenu = configuration.UseDBusMenu.GetValueOrDefault(); } + if (configuration.UseDeferredRendering.HasValue) { x11Opts.UseDeferredRendering = configuration.UseDeferredRendering.GetValueOrDefault(); waylandOpts.UseDeferredRendering = configuration.UseDeferredRendering.GetValueOrDefault(); } + app.With(x11Opts); app.With(waylandOpts); } + configureLinux(); } @@ -185,11 +195,7 @@ private static void InitDefaultCulture() private static void InitDI() { Bootstrap.Setup( - new DIOptions() - { - Container = new SimpleInjector.Container(), - PluginPathAndName = Shared.Constants.PluginsPathAndName - }); + new DIOptions { Container = new SimpleInjector.Container(), PluginPathAndName = Shared.Constants.PluginsPathAndName }); } /// @@ -209,16 +215,17 @@ private static void InitSingleInstance() if (configuration.SingleInstance) { SingleInstance.Initialize(); - SingleInstance.InstanceLaunched += (args) => + SingleInstance.InstanceLaunched += args => { ParseArguments(args.CommandLineArgs); Dispatcher.UIThread.SafeInvoke(() => { - App.MainWindow.Show(); - App.MainWindow.Activate(); - var previousState = App.MainWindow.WindowState; - App.MainWindow.WindowState = WindowState.Minimized; - App.MainWindow.WindowState = previousState; + var mainWindow = Helpers.GetMainWindow(); + mainWindow.Show(); + mainWindow.Activate(); + var previousState = mainWindow.WindowState; + mainWindow.WindowState = WindowState.Minimized; + mainWindow.WindowState = previousState; }); }; } diff --git a/src/IronyModManager/Views/MainWindow.xaml.cs b/src/IronyModManager/Views/MainWindow.xaml.cs index b63c2237..76bd68d4 100644 --- a/src/IronyModManager/Views/MainWindow.xaml.cs +++ b/src/IronyModManager/Views/MainWindow.xaml.cs @@ -1,11 +1,10 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 06-25-2023 +// Last Modified On : 02-15-2024 // *********************************************************************** // // Mario @@ -39,7 +38,6 @@ namespace IronyModManager.Views { - /// /// Class MainWindow. /// Implements the @@ -61,17 +59,17 @@ public class MainWindow : BaseWindow private Dictionary hotkeyGestures; /// - /// The prevent shutdown + /// The prevention shutdown flag /// - private bool preventShutdown = false; + private bool preventShutdown; /// /// The shutdown requested /// - private bool shutdownRequested = false; + private bool shutdownRequested; /// - /// The shut down state + /// The shut-down state /// private IShutDownState shutDownState; @@ -110,12 +108,13 @@ protected override void OnClosing(CancelEventArgs e) var locManager = DIResolver.Get(); var message = locManager.GetResource(LocalizationResources.App.BackgroundOperationMessage); var id = DIResolver.Get().GetNextId(); - ViewModel.TriggerManualOverlay(id, true, message); + ViewModel!.TriggerManualOverlay(id, true, message); } else { SaveWindowState(); } + base.OnClosing(e); } @@ -134,6 +133,7 @@ protected virtual Dictionary GetEnterKeyGestures() enterKeyGestures.Add(item, KeyGesture.Parse(item)); } } + return enterKeyGestures; } @@ -152,6 +152,7 @@ protected virtual Dictionary GetHotKeyGestures() hotkeyGestures.Add(item, KeyGesture.Parse(item)); } } + return hotkeyGestures; } @@ -164,12 +165,7 @@ protected virtual void InitializeEnterHotKeys() foreach (var item in GetEnterKeyGestures()) { var vm = ViewModel; - KeyBindings.Add(new KeyBinding() - { - Command = vm.RegisterHotkeyCommand, - CommandParameter = item.Key, - Gesture = item.Value - }); + KeyBindings.Add(new KeyBinding { Command = vm!.RegisterHotkeyCommand, CommandParameter = item.Key, Gesture = item.Value }); } } @@ -182,12 +178,7 @@ protected virtual void InitializeHotKeys() foreach (var item in GetHotKeyGestures()) { var vm = ViewModel; - KeyBindings.Add(new KeyBinding() - { - Command = vm.RegisterHotkeyCommand, - CommandParameter = item.Key, - Gesture = item.Value - }); + KeyBindings.Add(new KeyBinding { Command = vm!.RegisterHotkeyCommand, CommandParameter = item.Key, Gesture = item.Value }); } } @@ -202,10 +193,12 @@ protected virtual void InitWindowSize() var oldPos = Position; var oldHeight = Height; var oldWidth = Width; + static bool isValid(int value) { return value > 0 && !double.IsInfinity(value) && !double.IsNaN(value); } + try { var state = service.Get(); @@ -213,10 +206,12 @@ static bool isValid(int value) { Height = state.Height.GetValueOrDefault(); } + if (isValid(state.Width.GetValueOrDefault())) { Width = state.Width.GetValueOrDefault(); } + WindowState = state.IsMaximized.GetValueOrDefault() ? WindowState.Maximized : WindowState.Normal; // Silly setup code isn't it? @@ -225,7 +220,9 @@ static bool isValid(int value) var activeScreen = Screens.ScreenFromPoint(pos); var totalScreenX = Screens.All.Sum(p => p.WorkingArea.Width); var locX = state.LocationX.GetValueOrDefault() + state.Width.GetValueOrDefault() > totalScreenX ? totalScreenX - state.Width.GetValueOrDefault() : state.LocationX.GetValueOrDefault(); - var locY = state.LocationY.GetValueOrDefault() + state.Height.GetValueOrDefault() > activeScreen.WorkingArea.Height ? activeScreen.WorkingArea.Height - state.Height.GetValueOrDefault() : state.LocationY.GetValueOrDefault(); + var locY = state.LocationY.GetValueOrDefault() + state.Height.GetValueOrDefault() > activeScreen!.WorkingArea.Height + ? activeScreen.WorkingArea.Height - state.Height.GetValueOrDefault() + : state.LocationY.GetValueOrDefault(); if (isValid(locX) && isValid(locY)) { pos = Position.WithX(locX); @@ -254,7 +251,6 @@ static bool isValid(int value) /// The hotkeys. protected virtual void KillHotkeys(Dictionary hotkeys) { - var enterHotKeys = GetEnterKeyGestures(); var enterBindings = KeyBindings.Where(p => hotkeys.ContainsValue(p.Gesture)).ToList(); foreach (var item in enterBindings) { @@ -278,12 +274,12 @@ protected override void OnActivated(CompositeDisposable disposables) { Dispatcher.UIThread.InvokeAsync(() => { - ((IClassicDesktopStyleApplicationLifetime)Application.Current.ApplicationLifetime).Shutdown(); + ((IClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!).Shutdown(); }); } }).DisposeWith(disposables); - this.WhenAnyValue(v => v.ViewModel.RegisterHotkeyCommand).Subscribe(s => + this.WhenAnyValue(v => v.ViewModel.RegisterHotkeyCommand).Subscribe(_ => { InitializeHotKeys(); }).DisposeWith(disposables); @@ -356,16 +352,19 @@ private void SaveWindowState() { locX = 0; } + if (locY < 0.0) { locY = 0; } + state.Height = Convert.ToInt32(ClientSize.Height) - Convert.ToInt32(ExtendClientAreaTitleBarHeightHint); state.Width = Convert.ToInt32(ClientSize.Width); state.IsMaximized = false; state.LocationX = Convert.ToInt32(locX); state.LocationY = Convert.ToInt32(locY); } + service.Save(state); } From 835432b8173a82acfb3876ef80dcbcc975c6d1c0 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 17 Feb 2024 22:29:14 +0100 Subject: [PATCH 038/101] Code cleanup and fixes --- .../IAppState.cs | 10 ++- src/IronyModManager.Models/AppState.cs | 12 ++- .../Extensions.DateTime.cs | 41 +++++++++++ .../LocalizationResources.cs | 6 ++ src/IronyModManager.Storage/Database.cs | 32 ++++---- src/IronyModManager/Localization/de.json | 4 + src/IronyModManager/Localization/en.json | 4 + src/IronyModManager/Localization/es.json | 4 + src/IronyModManager/Localization/fr.json | 4 + src/IronyModManager/Localization/hr.json | 4 + src/IronyModManager/Localization/ru.json | 4 + src/IronyModManager/Localization/zh.json | 4 + src/IronyModManager/PrankBootstrap.cs | 73 +++++++++++++++++++ 13 files changed, 184 insertions(+), 18 deletions(-) create mode 100644 src/IronyModManager.Shared/Extensions.DateTime.cs create mode 100644 src/IronyModManager/PrankBootstrap.cs diff --git a/src/IronyModManager.Models.Common/IAppState.cs b/src/IronyModManager.Models.Common/IAppState.cs index 3c497deb..e2850037 100644 --- a/src/IronyModManager.Models.Common/IAppState.cs +++ b/src/IronyModManager.Models.Common/IAppState.cs @@ -4,15 +4,17 @@ // Created : 03-03-2020 // // Last Modified By : Mario -// Last Modified On : 02-13-2021 +// Last Modified On : 02-17-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; namespace IronyModManager.Models.Common { @@ -67,6 +69,12 @@ public interface IAppState : IModel /// The installed mods sort mode. int InstalledModsSortMode { get; set; } + /// + /// Gets or sets a value representing the last prank check. + /// + /// The last prank check. + DateTime? LastPrankCheck { get; set; } + /// /// Gets or sets the last writable check. /// diff --git a/src/IronyModManager.Models/AppState.cs b/src/IronyModManager.Models/AppState.cs index caedef72..f9eb09c6 100644 --- a/src/IronyModManager.Models/AppState.cs +++ b/src/IronyModManager.Models/AppState.cs @@ -4,15 +4,17 @@ // Created : 03-03-2020 // // Last Modified By : Mario -// Last Modified On : 02-13-2021 +// Last Modified On : 02-17-2024 // *********************************************************************** // // Mario // // // *********************************************************************** -using System.Collections.Generic; + using System; +using System.Collections.Generic; +using System.Linq; using IronyModManager.Models.Common; namespace IronyModManager.Models @@ -70,6 +72,12 @@ public class AppState : BaseModel, IAppState /// The installed mods sort mode. public virtual int InstalledModsSortMode { get; set; } + /// + /// Gets or sets a value representing the last prank check. + /// + /// The last prank check. + public DateTime? LastPrankCheck { get; set; } + /// /// Gets or sets the last writable check. /// diff --git a/src/IronyModManager.Shared/Extensions.DateTime.cs b/src/IronyModManager.Shared/Extensions.DateTime.cs new file mode 100644 index 00000000..2e10e66c --- /dev/null +++ b/src/IronyModManager.Shared/Extensions.DateTime.cs @@ -0,0 +1,41 @@ +// *********************************************************************** +// Assembly : IronyModManager.Shared +// Author : Mario +// Created : 02-17-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-17-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace IronyModManager.Shared +{ + /// + /// The extensions. + /// + public partial class Extensions + { + #region Methods + + /// + /// Is date same. + /// + /// The date 1. + /// The date 2. + /// A bool. + public static bool IsDateSame(this DateTime date1, DateTime date2) + { + return date1.Date == date2.Date; + } + + #endregion Methods + } +} diff --git a/src/IronyModManager.Shared/LocalizationResources.cs b/src/IronyModManager.Shared/LocalizationResources.cs index 5906ed26..aa6c8068 100644 --- a/src/IronyModManager.Shared/LocalizationResources.cs +++ b/src/IronyModManager.Shared/LocalizationResources.cs @@ -27,6 +27,12 @@ public static class Actions public const string DLC = Prefix + "DLC"; } } + public static class JokeError + { + public const string Prefix = "JokeError."; + public const string Title = Prefix + "Title"; + public const string Message = Prefix + "Message"; + } public static class FatalError { public const string Prefix = "FatalError."; diff --git a/src/IronyModManager.Storage/Database.cs b/src/IronyModManager.Storage/Database.cs index d4ba8a4e..66c7e50a 100644 --- a/src/IronyModManager.Storage/Database.cs +++ b/src/IronyModManager.Storage/Database.cs @@ -4,15 +4,17 @@ // Created : 01-11-2020 // // Last Modified By : Mario -// Last Modified On : 03-16-2021 +// Last Modified On : 02-17-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.DI; using IronyModManager.Models.Common; using IronyModManager.Shared; @@ -28,7 +30,7 @@ namespace IronyModManager.Storage /// /// /// - public class Database : PropertyChangedModelBase, IDatabase + public sealed class Database : PropertyChangedModelBase, IDatabase { #region Constructors @@ -37,11 +39,11 @@ public class Database : PropertyChangedModelBase, IDatabase /// public Database() { - Themes = new List(); - Games = new List(); - ModCollection = new List(); - GameSettings = new List(); - NotificationPosition = new List(); + Themes = []; + Games = []; + ModCollection = []; + GameSettings = []; + NotificationPosition = []; } #endregion Constructors @@ -53,53 +55,53 @@ public Database() /// /// The state of the application. [Trackable] - public virtual IAppState AppState { get; set; } = DIResolver.Get(); + public IAppState AppState { get; set; } = DIResolver.Get(); /// /// Gets or sets the games. /// /// The games. - public virtual IList Games { get; set; } + public IList Games { get; set; } /// /// Gets or sets the game settings. /// /// The game settings. [Trackable] - public virtual IEnumerable GameSettings { get; set; } + public IEnumerable GameSettings { get; set; } /// /// Gets or sets the mod collection. /// /// The mod collection. [Trackable] - public virtual IEnumerable ModCollection { get; set; } + public IEnumerable ModCollection { get; set; } /// /// Gets or sets the notification position. /// /// The notification position. - public virtual IList NotificationPosition { get; set; } + public IList NotificationPosition { get; set; } /// /// Gets or sets the preferences. /// /// The preferences. [Trackable] - public virtual IPreferences Preferences { get; set; } = DIResolver.Get(); + public IPreferences Preferences { get; set; } = DIResolver.Get(); /// /// Gets or sets the themes. /// /// The themes. - public virtual IList Themes { get; set; } + public IList Themes { get; set; } /// /// Gets or sets the state of the window. /// /// The state of the window. [Trackable] - public virtual IWindowState WindowState { get; set; } = DIResolver.Get(); + public IWindowState WindowState { get; set; } = DIResolver.Get(); #endregion Properties } diff --git a/src/IronyModManager/Localization/de.json b/src/IronyModManager/Localization/de.json index 74a32935..e77c6f4a 100644 --- a/src/IronyModManager/Localization/de.json +++ b/src/IronyModManager/Localization/de.json @@ -14,6 +14,10 @@ "DLC": "DLC" } }, + "JokeError": { + "Title": "Unbehandelte Ei-Container-Speicherverletzung", + "Message": "Drücken Sie OK und tun Sie so, als wäre nichts passiert :)" + }, "FatalError": { "Message": "Ein nicht behandelter Fehler ist aufgetreten. Anwendung wird automatisch geschlossen.", "MessageWithLastError": "Ein nicht behandelter Fehler ist aufgetreten. Bitte verwenden Sie diese Informationen, um einen Fehlerbericht einzulegen:{NewLine}{NewLine}{Message}", diff --git a/src/IronyModManager/Localization/en.json b/src/IronyModManager/Localization/en.json index 173e5104..863d2156 100644 --- a/src/IronyModManager/Localization/en.json +++ b/src/IronyModManager/Localization/en.json @@ -14,6 +14,10 @@ "DLC": "DLC" } }, + "JokeError": { + "Title": "Unhandled egg-container memory violation", + "Message": "Press OK to pretend like nothing happened :)" + }, "FatalError": { "Message": "Unhandled error occurred. App will close automatically.", "MessageWithLastError": "Unhandled error occurred. Please use this information to file a bug report:{NewLine}{NewLine}{Message}", diff --git a/src/IronyModManager/Localization/es.json b/src/IronyModManager/Localization/es.json index a4e95dcc..96d596ae 100644 --- a/src/IronyModManager/Localization/es.json +++ b/src/IronyModManager/Localization/es.json @@ -14,6 +14,10 @@ "DLC": "DLC" } }, + "JokeError": { + "Title": "Violación de memoria del contenedor de huevos no controlada", + "Message": "Pulsa OK para fingir que no ha pasado nada :)" + }, "FatalError": { "Message": "Ocurrió un error no manejado. La aplicación se cerrará automáticamente.", "MessageWithLastError": "Ocurrió un error no manejado. Por favor, utilice esta información para presentar un informe de errores:{NewLine}{NewLine}{Message}", diff --git a/src/IronyModManager/Localization/fr.json b/src/IronyModManager/Localization/fr.json index faed6796..6ae9a720 100644 --- a/src/IronyModManager/Localization/fr.json +++ b/src/IronyModManager/Localization/fr.json @@ -14,6 +14,10 @@ "DLC": "DLC" } }, + "JokeError": { + "Title": "Violation de la mémoire d'un conteneur d'œufs non gérée", + "Message": "Appuyez sur OK pour faire comme si rien ne s'était passé :)" + }, "FatalError": { "Message": "Une erreur non gérée s'est produite. L'application se fermera automatiquement.", "MessageWithLastError": "Une erreur non gérée s'est produite. Veuillez utiliser ces informations pour déposer un rapport de bogue:{NewLine}{NewLine}{Message}", diff --git a/src/IronyModManager/Localization/hr.json b/src/IronyModManager/Localization/hr.json index b55e6a86..5648e27c 100644 --- a/src/IronyModManager/Localization/hr.json +++ b/src/IronyModManager/Localization/hr.json @@ -14,6 +14,10 @@ "DLC": "DLC" } }, + "JokeError": { + "Title": "Neobrađeno kršenje memorije spremnika jaja", + "Message": "Pritisnite OK da se pretvarate da se ništa nije dogodilo :)" + }, "FatalError": { "Message": "Došlo je do greške. Aplikacija će se automatski zatvoriti.", "MessageWithLastError": "Došlo je do greške. Koristite ovu informaciju da biste podnijeli izvješće o greški:{NewLine}{NewLine}{Message}", diff --git a/src/IronyModManager/Localization/ru.json b/src/IronyModManager/Localization/ru.json index 57afbd51..dceabd7c 100644 --- a/src/IronyModManager/Localization/ru.json +++ b/src/IronyModManager/Localization/ru.json @@ -14,6 +14,10 @@ "DLC": "DLC" } }, + "JokeError": { + "Title": "Необработанное нарушение памяти контейнера яйца", + "Message": "Нажмите OK, чтобы сделать вид, что ничего не произошло :)" + }, "FatalError": { "Message": "Возникла неизвестная ошибка, приложение будет закрыто.", "MessageWithLastError": "Возникла неизвестная ошибка, приложение будет закрыто. Пожалуйста, используйте эту информацию, чтобы подать сообщение об ошибке:{NewLine}{NewLine}{Message}", diff --git a/src/IronyModManager/Localization/zh.json b/src/IronyModManager/Localization/zh.json index 9eece08d..7bf30a60 100644 --- a/src/IronyModManager/Localization/zh.json +++ b/src/IronyModManager/Localization/zh.json @@ -14,6 +14,10 @@ "DLC": "DLC" } }, + "JokeError": { + "Title": "未处理的彩蛋容器内存违规", + "Message": "按 \"确定\",假装什么也没发生:)" + }, "FatalError": { "Message": "发生了无法处理的异常,程序即将关闭", "MessageWithLastError": "发生了无法处理的异常,请以此信息提交错误报告:{NewLine}{NewLine}{Message}", diff --git a/src/IronyModManager/PrankBootstrap.cs b/src/IronyModManager/PrankBootstrap.cs new file mode 100644 index 00000000..5d440ada --- /dev/null +++ b/src/IronyModManager/PrankBootstrap.cs @@ -0,0 +1,73 @@ +// *********************************************************************** +// Assembly : IronyModManager +// Author : Mario +// Created : 02-17-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-17-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Avalonia.Threading; +using IronyModManager.Common; +using IronyModManager.DI; +using IronyModManager.Implementation.Actions; +using IronyModManager.Localization; +using IronyModManager.Services.Common; +using IronyModManager.Shared; + +namespace IronyModManager +{ + /// + /// Class PrankBootstrap. + /// Implements the + /// + /// + public class PrankBootstrap : PostStartup + { + #region Methods + + /// + /// On post startup callback. + /// + public override void OnPostStartup() + { + InitPrankAsync().ConfigureAwait(false); + } + + /// + /// Initialize the prank async. + /// + /// A Task. + private static async Task InitPrankAsync() + { + await Task.Delay(10000); + var prankDate = new DateTime(DateTime.Today.Year, 4, 1); + var appState = DIResolver.Get(); + var state = appState.Get(); + if (DateTime.Today.IsDateSame(prankDate) && state.LastPrankCheck.GetValueOrDefault().Year != DateTime.Today.Year) + { + var notificationAction = DIResolver.Get(); + var locManager = DIResolver.Get(); + var title = locManager.GetResource(LocalizationResources.JokeError.Title); + var message = locManager.GetResource(LocalizationResources.JokeError.Message); + await Dispatcher.UIThread.SafeInvokeAsync(async () => + { + await notificationAction.ShowPromptAsync(title, title, message, NotificationType.Error, PromptType.OK); + }); + state.LastPrankCheck = DateTime.Now; + appState.Save(state); + } + } + + #endregion Methods + } +} From 4b539cdbf70edfdc6f491a4f59e50120327f2535 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 18 Feb 2024 15:48:19 +0100 Subject: [PATCH 039/101] Safety checks --- src/IronyModManager/PrankBootstrap.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/IronyModManager/PrankBootstrap.cs b/src/IronyModManager/PrankBootstrap.cs index 5d440ada..7229c127 100644 --- a/src/IronyModManager/PrankBootstrap.cs +++ b/src/IronyModManager/PrankBootstrap.cs @@ -4,7 +4,7 @@ // Created : 02-17-2024 // // Last Modified By : Mario -// Last Modified On : 02-17-2024 +// Last Modified On : 02-18-2024 // *********************************************************************** // // Mario @@ -40,7 +40,7 @@ public class PrankBootstrap : PostStartup /// public override void OnPostStartup() { - InitPrankAsync().ConfigureAwait(false); + Task.Run(InitPrankAsync); } /// @@ -49,12 +49,19 @@ public override void OnPostStartup() /// A Task. private static async Task InitPrankAsync() { - await Task.Delay(10000); var prankDate = new DateTime(DateTime.Today.Year, 4, 1); var appState = DIResolver.Get(); var state = appState.Get(); if (DateTime.Today.IsDateSame(prankDate) && state.LastPrankCheck.GetValueOrDefault().Year != DateTime.Today.Year) { + await Task.Delay(10000); + var main = Helpers.GetMainWindow(); + while (main == null) + { + main = Helpers.GetMainWindow(); + await Task.Delay(250); + } + var notificationAction = DIResolver.Get(); var locManager = DIResolver.Get(); var title = locManager.GetResource(LocalizationResources.JokeError.Title); From e5140a9a5045b3abf602d1e375935902fbb47ff3 Mon Sep 17 00:00:00 2001 From: bcssov Date: Wed, 21 Feb 2024 15:56:00 +0100 Subject: [PATCH 040/101] Prevent IPC when in conflict solver mode --- src/IronyModManager/Program.cs | 7 +- src/IronyModManager/StaticResources.cs | 36 ++++++---- .../ViewModels/MainControlViewModel.cs | 60 +++++++--------- .../ViewModels/MainWindowViewModel.cs | 72 +++++++++---------- 4 files changed, 90 insertions(+), 85 deletions(-) diff --git a/src/IronyModManager/Program.cs b/src/IronyModManager/Program.cs index 0876ddaf..db560e4d 100644 --- a/src/IronyModManager/Program.cs +++ b/src/IronyModManager/Program.cs @@ -4,7 +4,7 @@ // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 02-15-2024 +// Last Modified On : 02-21-2024 // *********************************************************************** // // Copyright (c) Mario. All rights reserved. @@ -217,6 +217,11 @@ private static void InitSingleInstance() SingleInstance.Initialize(); SingleInstance.InstanceLaunched += args => { + if (!StaticResources.AllowCommandLineChange) + { + return; + } + ParseArguments(args.CommandLineArgs); Dispatcher.UIThread.SafeInvoke(() => { diff --git a/src/IronyModManager/StaticResources.cs b/src/IronyModManager/StaticResources.cs index d11347e1..7e444bbf 100644 --- a/src/IronyModManager/StaticResources.cs +++ b/src/IronyModManager/StaticResources.cs @@ -1,11 +1,10 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 05-07-2020 // // Last Modified By : Mario -// Last Modified On : 02-10-2024 +// Last Modified On : 02-21-2024 // *********************************************************************** // // Mario @@ -24,7 +23,6 @@ namespace IronyModManager { - /// /// Class StaticResources. /// @@ -56,7 +54,7 @@ public static class StaticResources /// /// The updater path /// - private static string[] updaterPath = null; + private static string[] updaterPath; /// /// The window icon @@ -85,6 +83,12 @@ public static class StaticResources #region Properties + /// + /// Gets or sets a value indicating whether the allow command line change. + /// + /// true if allow command line change; otherwise, false. + public static bool AllowCommandLineChange { get; set; } = true; + /// /// Gets or sets the command line options. /// @@ -124,6 +128,7 @@ public static WindowIcon GetAppIcon() using var ms = GetAppIconStream(); windowIcon = new WindowIcon(ms); } + return windowIcon; } @@ -138,6 +143,7 @@ public static Bitmap GetAppIconBitmap() using var ms = GetAppIconStream(); iconBitmap = new Bitmap(ms); } + return iconBitmap; } @@ -151,20 +157,23 @@ public static string GetLastKnownLocationInfo() { return lastKnownLocation; } + var firstSegment = string.Empty; var secondSegment = string.Empty; var entryAssembly = Assembly.GetEntryAssembly(); - var companyAttribute = (AssemblyCompanyAttribute)Attribute.GetCustomAttribute(entryAssembly, typeof(AssemblyCompanyAttribute)); - if (!string.IsNullOrEmpty(companyAttribute.Company)) + var companyAttribute = (AssemblyCompanyAttribute)Attribute.GetCustomAttribute(entryAssembly!, typeof(AssemblyCompanyAttribute)); + if (!string.IsNullOrEmpty(companyAttribute!.Company)) { firstSegment = $"{companyAttribute.Company}{Path.DirectorySeparatorChar}"; } + var titleAttribute = (AssemblyTitleAttribute)Attribute.GetCustomAttribute(entryAssembly, typeof(AssemblyTitleAttribute)); - if (!string.IsNullOrEmpty(titleAttribute.Title)) + if (!string.IsNullOrEmpty(titleAttribute!.Title)) { secondSegment = $"{titleAttribute.Title}-Location{Path.DirectorySeparatorChar}"; } + lastKnownLocation = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), $@"{firstSegment}{secondSegment}"); return lastKnownLocation; } @@ -213,8 +222,8 @@ static string generatePath(bool useProperSeparator) var secondSegment = string.Empty; var entryAssembly = Assembly.GetEntryAssembly(); - var companyAttribute = (AssemblyCompanyAttribute)Attribute.GetCustomAttribute(entryAssembly, typeof(AssemblyCompanyAttribute)); - if (!string.IsNullOrEmpty(companyAttribute.Company)) + var companyAttribute = (AssemblyCompanyAttribute)Attribute.GetCustomAttribute(entryAssembly!, typeof(AssemblyCompanyAttribute)); + if (!string.IsNullOrEmpty(companyAttribute!.Company)) { if (!useProperSeparator) { @@ -225,8 +234,9 @@ static string generatePath(bool useProperSeparator) firstSegment = $"{companyAttribute.Company}{Path.DirectorySeparatorChar}"; } } + var titleAttribute = (AssemblyTitleAttribute)Attribute.GetCustomAttribute(entryAssembly, typeof(AssemblyTitleAttribute)); - if (!string.IsNullOrEmpty(titleAttribute.Title)) + if (!string.IsNullOrEmpty(titleAttribute!.Title)) { if (!useProperSeparator) { @@ -237,14 +247,16 @@ static string generatePath(bool useProperSeparator) secondSegment = $"{titleAttribute.Title}-Updater{Path.DirectorySeparatorChar}"; } } + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), $@"{firstSegment}{secondSegment}"); } if (updaterPath == null) { - var col = new List() { generatePath(true), generatePath(false) }; + var col = new List { generatePath(true), generatePath(false) }; updaterPath = col.Distinct().ToArray(); } + return updaterPath; } diff --git a/src/IronyModManager/ViewModels/MainControlViewModel.cs b/src/IronyModManager/ViewModels/MainControlViewModel.cs index 211e0065..f842fa07 100644 --- a/src/IronyModManager/ViewModels/MainControlViewModel.cs +++ b/src/IronyModManager/ViewModels/MainControlViewModel.cs @@ -4,15 +4,17 @@ // Created : 01-20-2020 // // Last Modified By : Mario -// Last Modified On : 02-14-2021 +// Last Modified On : 02-21-2024 // *********************************************************************** // // Mario // // // *********************************************************************** -using System.Collections.Generic; + using System; +using System.Collections.Generic; +using System.Linq; using IronyModManager.Common.ViewModels; using IronyModManager.Shared; using IronyModManager.ViewModels.Controls; @@ -24,73 +26,59 @@ namespace IronyModManager.ViewModels /// Implements the /// /// + /// The theme control. + /// The language control. + /// The game control. + /// The mod control. + /// The options. + /// The actions. + /// Initializes a new instance of the class. [ExcludeFromCoverage("This should be tested via functional testing.")] - public class MainControlViewModel : BaseViewModel + public class MainControlViewModel( + ThemeControlViewModel themeControl, + LanguageControlViewModel languageControl, + GameControlViewModel gameControl, + ModHolderControlViewModel modControl, + OptionsControlViewModel options, + ActionsControlViewModel actions) : BaseViewModel { - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The theme control. - /// The language control. - /// The game control. - /// The mod control. - /// The options. - /// The actions. - public MainControlViewModel(ThemeControlViewModel themeControl, - LanguageControlViewModel languageControl, - GameControlViewModel gameControl, - ModHolderControlViewModel modControl, - OptionsControlViewModel options, ActionsControlViewModel actions) - { - ThemeSelector = themeControl; - LanguageSelector = languageControl; - GameSelector = gameControl; - ModHolder = modControl; - Options = options; - Actions = actions; - } - - #endregion Constructors - #region Properties /// /// Gets or sets the actions. /// /// The actions. - public virtual ActionsControlViewModel Actions { get; protected set; } + public virtual ActionsControlViewModel Actions { get; protected set; } = actions; /// /// Gets or sets the game selector. /// /// The game selector. - public virtual GameControlViewModel GameSelector { get; protected set; } + public virtual GameControlViewModel GameSelector { get; protected set; } = gameControl; /// /// Gets or sets the language selector. /// /// The language selector. - public virtual LanguageControlViewModel LanguageSelector { get; protected set; } + public virtual LanguageControlViewModel LanguageSelector { get; protected set; } = languageControl; /// /// Gets or sets the mod holder. /// /// The mod holder. - public virtual ModHolderControlViewModel ModHolder { get; protected set; } + public virtual ModHolderControlViewModel ModHolder { get; protected set; } = modControl; /// /// Gets or sets the options. /// /// The options. - public virtual OptionsControlViewModel Options { get; protected set; } + public virtual OptionsControlViewModel Options { get; protected set; } = options; /// /// Gets the theme selector. /// /// The theme selector. - public virtual ThemeControlViewModel ThemeSelector { get; protected set; } + public virtual ThemeControlViewModel ThemeSelector { get; protected set; } = themeControl; #endregion Properties diff --git a/src/IronyModManager/ViewModels/MainWindowViewModel.cs b/src/IronyModManager/ViewModels/MainWindowViewModel.cs index 46bf6282..7a67998f 100644 --- a/src/IronyModManager/ViewModels/MainWindowViewModel.cs +++ b/src/IronyModManager/ViewModels/MainWindowViewModel.cs @@ -1,11 +1,10 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 06-23-2023 +// Last Modified On : 02-21-2024 // *********************************************************************** // // Mario @@ -15,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reactive; using System.Reactive.Disposables; @@ -29,7 +29,6 @@ namespace IronyModManager.ViewModels { - /// /// Class MainWindowViewModel. /// Implements the @@ -43,12 +42,12 @@ public class MainWindowViewModel : BaseViewModel /// /// The overlay stack /// - protected readonly List overlayStack = new(); + protected readonly List OverlayStack = []; /// /// The loop running /// - protected bool loopRunning = false; + protected bool LoopRunning; /// /// The hotkey manager @@ -78,7 +77,7 @@ public class MainWindowViewModel : BaseViewModel /// /// The last message identifier /// - private long lastMessageId = 0; + private long lastMessageId; /// /// The overlay disposable @@ -177,12 +176,7 @@ public MainWindowViewModel() /// The message. public void TriggerManualOverlay(long id, bool isVisible, string message) { - QueueOverlay(new OverlayProgressEvent() - { - Id = id, - IsVisible = isVisible, - Message = message - }); + QueueOverlay(new OverlayProgressEvent { Id = id, IsVisible = isVisible, Message = message }); } /// @@ -202,6 +196,7 @@ protected virtual async Task AnimateTransitionAsync(bool mainVisible) { opacity = 1; } + MainOpacity = opacity; await Task.Delay(5); } @@ -214,10 +209,7 @@ protected void BindOverlay() { InitOverlayLoop(); overlayDisposable?.Dispose(); - overlayDisposable = overlayProgressHandler.Subscribe(s => - { - QueueOverlay(s); - }); + overlayDisposable = overlayProgressHandler.Subscribe(QueueOverlay); if (Disposables != null) { overlayDisposable.DisposeWith(Disposables); @@ -244,9 +236,9 @@ protected async Task FreeMemoryAsync() /// protected void InitOverlayLoop() { - if (!loopRunning) + if (!LoopRunning) { - loopRunning = true; + LoopRunning = true; Task.Run(() => OverlayLoopAsync().ConfigureAwait(false)); } } @@ -313,41 +305,45 @@ void setOverlayProperties(OverlayProgressEvent e) { OverlayVisible = e.IsVisible; } + if (e.Message != OverlayMessage) { OverlayMessage = e.Message; } + if (e.MessageProgress != OverlayMessageProgress) { OverlayMessageProgress = e.MessageProgress; HasProgress = !string.IsNullOrWhiteSpace(e.MessageProgress); } } + while (true) { await Task.Delay(2); lock (queueLock) { var now = DateTime.Now; - if (overlayStack.Any(p => now >= p.DateAdded) && currentMessageId.HasValue) + if (OverlayStack.Any(p => now >= p.DateAdded) && currentMessageId.HasValue) { - var overlays = overlayStack.Where(p => now >= p.DateAdded && p.Event.Id == currentMessageId.GetValueOrDefault()).OrderBy(p => p.DateAdded).ToList(); + var overlays = OverlayStack.Where(p => now >= p.DateAdded && p.Event.Id == currentMessageId.GetValueOrDefault()).OrderBy(p => p.DateAdded).ToList(); if (overlays.Count > 0) { - OverlayQueue overlay = null; + OverlayQueue overlay; if (overlays.Any(p => p.Event.IsVisible != OverlayVisible)) { if (overlays.Any(p => p.Event.IsVisible == false)) { - overlayStack.RemoveAll(p => p.Event.Id <= currentMessageId.GetValueOrDefault()); - if (overlayStack.Count > 0) + OverlayStack.RemoveAll(p => p.Event.Id <= currentMessageId.GetValueOrDefault()); + if (OverlayStack.Count > 0) { - currentMessageId = overlayStack.OrderByDescending(p => p.Event.Id).FirstOrDefault().Event.Id; + currentMessageId = OverlayStack.OrderByDescending(p => p.Event.Id)!.FirstOrDefault()!.Event.Id; } else { currentMessageId = null; } + if (currentMessageId.HasValue) { lastMessageId = currentMessageId.GetValueOrDefault(); @@ -356,6 +352,7 @@ void setOverlayProperties(OverlayProgressEvent e) { lastMessageId++; } + overlay = overlays.FirstOrDefault(p => p.Event.IsVisible == false); } else @@ -363,7 +360,7 @@ void setOverlayProperties(OverlayProgressEvent e) overlay = overlays.FirstOrDefault(p => p.Event.IsVisible != OverlayVisible); if (overlay != null) { - overlayStack.Remove(overlay); + OverlayStack.Remove(overlay); } } } @@ -371,10 +368,12 @@ void setOverlayProperties(OverlayProgressEvent e) { foreach (var item in overlays) { - overlayStack.Remove(item); + OverlayStack.Remove(item); } + overlay = overlays.LastOrDefault(); } + if (overlay != null) { setOverlayProperties(overlay.Event); @@ -393,18 +392,19 @@ protected void QueueOverlay(OverlayProgressEvent e) { lock (queueLock) { - if (!overlayStack.Any(p => p.Event == e)) + if (OverlayStack.All(p => p.Event != e)) { - if (!currentMessageId.HasValue && e.Id < lastMessageId) + switch (currentMessageId) { - return; - } - if (!currentMessageId.HasValue && OverlayVisible != e.IsVisible && e.IsVisible) - { - currentMessageId = e.Id; - lastMessageId = e.Id; + case null when e.Id < lastMessageId: + return; + case null when OverlayVisible != e.IsVisible && e.IsVisible: + currentMessageId = e.Id; + lastMessageId = e.Id; + break; } - overlayStack.Add(new OverlayQueue() { Event = e, DateAdded = DateTime.Now }); + + OverlayStack.Add(new OverlayQueue { Event = e, DateAdded = DateTime.Now }); } } } From ac3d93818d581de8dcc46b607396aa9f62c098eb Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 19 Feb 2024 21:21:39 +0100 Subject: [PATCH 041/101] Add basic functionality to colorize avaloniaedit --- .../Implementation/AvaloniaEdit/Constants.cs | 71 ++++++++ .../AvaloniaEdit/DiffBackgroundRenderer.cs | 170 ++++++++++++++++++ .../Implementation/AvaloniaEdit/DiffMargin.cs | 129 +++++++++++++ .../AvaloniaEdit/ResourceLoader.cs | 35 ++-- 4 files changed, 384 insertions(+), 21 deletions(-) create mode 100644 src/IronyModManager/Implementation/AvaloniaEdit/Constants.cs create mode 100644 src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs create mode 100644 src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/Constants.cs b/src/IronyModManager/Implementation/AvaloniaEdit/Constants.cs new file mode 100644 index 00000000..7b81def5 --- /dev/null +++ b/src/IronyModManager/Implementation/AvaloniaEdit/Constants.cs @@ -0,0 +1,71 @@ +// *********************************************************************** +// Assembly : IronyModManager +// Author : Mario +// Created : 02-19-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-19-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Media; + +namespace IronyModManager.Implementation.AvaloniaEdit +{ + /// + /// Class Constants. + /// + public class Constants + { + #region Fields + + /// + /// A private static readonly SolidColorBrush named diffDeletedLine. + /// + public static readonly SolidColorBrush DiffDeletedLine = SolidColorBrush.Parse("#FF9999"); + + /// + /// A private static readonly SolidColorBrush named diffDeletedPieces. + /// + public static readonly SolidColorBrush DiffDeletedPieces = SolidColorBrush.Parse("#FF9999"); + + /// + /// A private static readonly SolidColorBrush named DiffImaginaryLine. + /// + public static readonly SolidColorBrush DiffImaginaryLine = SolidColorBrush.Parse("#FF808080"); + + /// + /// A private static readonly SolidColorBrush named diffInsertedLine. + /// + public static readonly SolidColorBrush DiffInsertedLine = SolidColorBrush.Parse("#66cc99"); + + /// + /// A private static readonly SolidColorBrush named diffInsertedPieces. + /// + public static readonly SolidColorBrush DiffInsertedPieces = SolidColorBrush.Parse("#66cc99"); + + /// + /// A private static readonly SolidColorBrush named diffModifiedPieces. + /// + public static readonly SolidColorBrush DiffModifiedPieces = SolidColorBrush.Parse("#ffe28b"); + + /// + /// A private static readonly SolidColorBrush named diffUnchangedPieces. + /// + public static readonly SolidColorBrush DiffUnchangedPieces = SolidColorBrush.Parse("#ffe28b"); + + /// + /// A public static readonly Pen named TransparentPen. + /// + public static readonly Pen TransparentPen = new(new SolidColorBrush(Brushes.Transparent.Color)); + + #endregion Fields + } +} diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs new file mode 100644 index 00000000..98ad2908 --- /dev/null +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs @@ -0,0 +1,170 @@ +// *********************************************************************** +// Assembly : IronyModManager +// Author : Mario +// Created : 02-19-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-19-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Media; +using AvaloniaEdit.Document; +using AvaloniaEdit.Rendering; +using IronyModManager.ViewModels.Controls; + +namespace IronyModManager.Implementation.AvaloniaEdit +{ + /// + /// The diff background renderer. + /// + /// + public class DiffBackgroundRenderer : IBackgroundRenderer + { + #region Properties + + /// + /// Gets a value representing the layer. + /// + /// The layer. + public KnownLayer Layer => KnownLayer.Background; + + /// + /// Gets or sets a value representing the lines. + /// + /// The lines. + public IList Lines { get; set; } + + #endregion Properties + + #region Methods + + /// + /// Draws the specified text view. + /// + /// The text view. + /// The drawing context. + /// + public void Draw(TextView textView, DrawingContext drawingContext) + { + if (Lines == null || Lines.Count == 0) + { + return; + } + + foreach (var line in textView.VisualLines) + { + var num = line.FirstDocumentLine.LineNumber - 1; + if (num >= Lines.Count) + { + continue; + } + + var diff = Lines[num]; + + Brush brush = diff.Type switch + { + DiffPlex.DiffBuilder.Model.ChangeType.Deleted => Constants.DiffDeletedLine, + DiffPlex.DiffBuilder.Model.ChangeType.Inserted => Constants.DiffInsertedLine, + DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => Constants.DiffImaginaryLine, + _ => default + }; + + if (brush != default(Brush)) + { + var rect = BackgroundGeometryBuilder.GetRectsFromVisualSegment(textView, line, 0, 1000); + foreach (var r in rect) + { + drawingContext.DrawRectangle(brush, Constants.TransparentPen, new Rect(0, r.Top, textView.Bounds.Width, r.Height)); + } + } + + var offset = 0; + var endOffset = 0; + foreach (var piece in diff.SubPieces) + { + var subPieceBrush = piece.Type switch + { + DiffPlex.DiffBuilder.Model.ChangeType.Deleted => Constants.DiffDeletedPieces, + DiffPlex.DiffBuilder.Model.ChangeType.Inserted => Constants.DiffInsertedPieces, + DiffPlex.DiffBuilder.Model.ChangeType.Modified => Constants.DiffModifiedPieces, + DiffPlex.DiffBuilder.Model.ChangeType.Unchanged => Constants.DiffUnchangedPieces, + _ => default(Brush) + }; + if (subPieceBrush != default(Brush)) + { + var builder = new BackgroundGeometryBuilder + { + AlignToWholePixels = true + }; + endOffset += piece.Text.Length; + var diffSegment = new DiffSegment(line.StartOffset + offset, piece.Text.Length, line.StartOffset + endOffset); + offset = piece.Text.Length; + builder.AddSegment(textView, diffSegment); + + var geo = builder.CreateGeometry(); + if (geo != null) + { + drawingContext.DrawGeometry(brush, null, geo); + } + } + } + } + } + + #endregion Methods + + #region Classes + + /// + /// The diff segment. + /// + /// + /// + /// Initializes a new instance of the class. + /// + /// The offset. + /// The length. + /// The end offset. + private class DiffSegment(int offset, int length, int endOffset) : ISegment + { + #region Properties + + /// + /// Gets a value representing the end offset. + /// + /// + /// The end offset. + /// + public int EndOffset { get; } = endOffset; + + /// + /// Gets a value representing the length. + /// + /// + /// The length. + /// + public int Length { get; } = length; + + /// + /// Gets a value representing the offset. + /// + /// + /// The offset. + /// + public int Offset { get; } = offset; + + #endregion Properties + } + + #endregion Classes + } +} diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs new file mode 100644 index 00000000..e29d4c3d --- /dev/null +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs @@ -0,0 +1,129 @@ +// *********************************************************************** +// Assembly : IronyModManager +// Author : Mario +// Created : 02-19-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-19-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using AvaloniaEdit.Editing; +using AvaloniaEdit.Rendering; +using IronyModManager.ViewModels.Controls; + +namespace IronyModManager.Implementation.AvaloniaEdit +{ + /// + /// The diff margin. + /// + /// + public class DiffMargin : AbstractMargin + { + #region Fields + + /// + /// A private const double named LineMargin. + /// + private const double LineMargin = 4d; + + #endregion Fields + + #region Properties + + /// + /// Gets or sets a value representing the lines. + /// + /// The lines. + public IList Lines { get; set; } + + #endregion Properties + + #region Methods + + /// + /// Render. + /// + /// The context. + public override void Render(DrawingContext context) + { + base.Render(context); + if (Lines == null || Lines.Count == 0) + { + return; + } + + var typeFace = CreateTypeface(); + + var visualLines = TextView.VisualLinesValid ? TextView.VisualLines : Enumerable.Empty(); + foreach (var line in visualLines) + { + var rect = BackgroundGeometryBuilder.GetRectsFromVisualSegment(TextView, line, 0, 1000); + var ln = line.FirstDocumentLine.LineNumber - 1; + if (ln >= Lines.Count) + { + continue; + } + + var diff = Lines[ln]; + + Brush brush = diff.Type switch + { + DiffPlex.DiffBuilder.Model.ChangeType.Deleted => Constants.DiffDeletedLine, + DiffPlex.DiffBuilder.Model.ChangeType.Inserted => Constants.DiffInsertedLine, + DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => Constants.DiffImaginaryLine, + _ => default + }; + + foreach (var r in rect) + { + context.DrawRectangle(brush, Constants.TransparentPen, new Rect(0, r.Top, Bounds.Width, r.Height)); + } + + var text = new FormattedText(diff.Index.ToString(), typeFace, TextView.GetValue(TextBlock.FontSizeProperty), TextAlignment.Left, TextWrapping.NoWrap, Size.Empty); + context.DrawText(TextView.GetValue(TextBlock.ForegroundProperty), new Point(LineMargin, rect.FirstOrDefault().Top), text); + } + } + + /// + /// Measure override. + /// + /// The available size. + /// A Size. + protected override Size MeasureOverride(Size availableSize) + { + if (Lines == null || Lines.Count == 0) + { + return new Size(0, 0); + } + + var text = Lines.LastOrDefault()!.Index.ToString(); + var typeFace = CreateTypeface(); + var lineText = new FormattedText(text, typeFace, TextView.GetValue(TextBlock.FontSizeProperty), TextAlignment.Left, TextWrapping.NoWrap, Size.Empty); + return new Size(lineText.Bounds.Width + LineMargin * 2, 0); + } + + /// + /// Create typeface. + /// + /// A Typeface. + private Typeface CreateTypeface() + { + return new Typeface(TextView.GetValue(TextBlock.FontFamilyProperty), + TextView.GetValue(TextBlock.FontStyleProperty), + TextView.GetValue(TextBlock.FontWeightProperty)); + } + + #endregion Methods + } +} diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/ResourceLoader.cs b/src/IronyModManager/Implementation/AvaloniaEdit/ResourceLoader.cs index 416620c2..bd252759 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/ResourceLoader.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/ResourceLoader.cs @@ -4,13 +4,14 @@ // Created : 06-14-2021 // // Last Modified By : Mario -// Last Modified On : 06-14-2021 +// Last Modified On : 02-19-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.IO; @@ -30,7 +31,12 @@ namespace IronyModManager.Implementation.AvaloniaEdit /// Implements the /// /// - public class ResourceLoader : IResourceLoader + /// + /// Initializes a new instance of the class. + /// + /// The theme service. + /// The theme manager. + public class ResourceLoader(IThemeService themeService, IThemeManager themeManager) : IResourceLoader { #region Fields @@ -47,30 +53,15 @@ public class ResourceLoader : IResourceLoader /// /// The theme manager /// - private readonly IThemeManager themeManager; + private readonly IThemeManager themeManager = themeManager; /// /// The theme service /// - private readonly IThemeService themeService; + private readonly IThemeService themeService = themeService; #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The theme service. - /// The theme manager. - public ResourceLoader(IThemeService themeService, IThemeManager themeManager) - { - this.themeManager = themeManager; - this.themeService = themeService; - } - - #endregion Constructors - #region Methods /// @@ -82,9 +73,10 @@ public IHighlightingDefinition GetPDXScriptDefinition() { if (pdxScriptHighlightingDefinition == null) { - var resourcePath = themeManager.IsLightTheme(themeService.GetSelected().Type) ? Constants.Resources.PDXScriptLight : Constants.Resources.PDXScriptDark; + var resourcePath = themeManager.IsLightTheme(themeService.GetSelected().Type) ? IronyModManager.Constants.Resources.PDXScriptLight : IronyModManager.Constants.Resources.PDXScriptDark; pdxScriptHighlightingDefinition = GetHighlightingDefinition(resourcePath); } + return pdxScriptHighlightingDefinition; } @@ -96,9 +88,10 @@ public IHighlightingDefinition GetYAMLDefinition() { if (yamlHighlightingDefinition == null) { - var resourcePath = themeManager.IsLightTheme(themeService.GetSelected().Type) ? Constants.Resources.YAMLLight : Constants.Resources.YAMLDark; + var resourcePath = themeManager.IsLightTheme(themeService.GetSelected().Type) ? IronyModManager.Constants.Resources.YAMLLight : IronyModManager.Constants.Resources.YAMLDark; yamlHighlightingDefinition = GetHighlightingDefinition(resourcePath); } + return yamlHighlightingDefinition; } From 0f63730c7c34a7c5d315cd9242114d9d8cf7a816 Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 20 Feb 2024 06:51:59 +0100 Subject: [PATCH 042/101] Update warnings in merge viewer control view (tech debt) Add advanced merge viewer flag --- .../IAppState.cs | 8 +- src/IronyModManager.Models/AppState.cs | 8 +- .../Controls/MergeViewerControlViewModel.cs | 269 +++++++++--------- 3 files changed, 150 insertions(+), 135 deletions(-) diff --git a/src/IronyModManager.Models.Common/IAppState.cs b/src/IronyModManager.Models.Common/IAppState.cs index e2850037..eb514c67 100644 --- a/src/IronyModManager.Models.Common/IAppState.cs +++ b/src/IronyModManager.Models.Common/IAppState.cs @@ -4,7 +4,7 @@ // Created : 03-03-2020 // // Last Modified By : Mario -// Last Modified On : 02-17-2024 +// Last Modified On : 02-20-2024 // *********************************************************************** // // Mario @@ -81,6 +81,12 @@ public interface IAppState : IModel /// The last writable check. DateTime? LastWritableCheck { get; set; } + /// + /// Gets or sets a value indicating whether the use advanced diff viewer. + /// + /// true if you use advanced diff viewer; otherwise, false. + bool UseAdvancedDiffViewer { get; set; } + #endregion Properties } } diff --git a/src/IronyModManager.Models/AppState.cs b/src/IronyModManager.Models/AppState.cs index f9eb09c6..0270f3d8 100644 --- a/src/IronyModManager.Models/AppState.cs +++ b/src/IronyModManager.Models/AppState.cs @@ -4,7 +4,7 @@ // Created : 03-03-2020 // // Last Modified By : Mario -// Last Modified On : 02-17-2024 +// Last Modified On : 02-20-2024 // *********************************************************************** // // Mario @@ -84,6 +84,12 @@ public class AppState : BaseModel, IAppState /// The last writable check. public virtual DateTime? LastWritableCheck { get; set; } + /// + /// Gets or sets a value indicating whether the use advanced diff viewer. + /// + /// true if you use advanced diff viewer; otherwise, false. + public bool UseAdvancedDiffViewer { get; set; } + #endregion Properties } } diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index fdc54f3d..b5b8a8ef 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -4,20 +4,20 @@ // Created : 03-20-2020 // // Last Modified By : Mario -// Last Modified On : 04-27-2023 +// Last Modified On : 02-20-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reactive; using System.Reactive.Disposables; -using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Threading; @@ -44,40 +44,55 @@ namespace IronyModManager.ViewModels.Controls /// Implements the /// /// + /// State of the scroll. + /// The mod patch collection service. + /// The hotkey pressed handler. + /// The application action. + /// The external editor service. + /// The notification action. + /// The localization manager. + /// Initializes a new instance of the class. [ExcludeFromCoverage("This should be tested via functional testing.")] - public class MergeViewerControlViewModel : BaseViewModel + public class MergeViewerControlViewModel( + IScrollState scrollState, + IModPatchCollectionService modPatchCollectionService, + ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler, + IAppAction appAction, + IExternalEditorService externalEditorService, + INotificationAction notificationAction, + ILocalizationManager localizationManager) : BaseViewModel { #region Fields /// /// The URL action /// - private readonly IAppAction appAction; + private readonly IAppAction appAction = appAction; /// /// The external editor service /// - private readonly IExternalEditorService externalEditorService; + private readonly IExternalEditorService externalEditorService = externalEditorService; /// /// The hotkey pressed handler /// - private readonly ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler; + private readonly ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler = hotkeyPressedHandler; /// /// The localization manager /// - private readonly ILocalizationManager localizationManager; + private readonly ILocalizationManager localizationManager = localizationManager; /// /// The mod patch collection service /// - private readonly IModPatchCollectionService modPatchCollectionService; + private readonly IModPatchCollectionService modPatchCollectionService = modPatchCollectionService; /// /// The notification action /// - private readonly INotificationAction notificationAction; + private readonly INotificationAction notificationAction = notificationAction; /// /// The redo stack @@ -87,7 +102,7 @@ public class MergeViewerControlViewModel : BaseViewModel /// /// The scroll state /// - private readonly IScrollState scrollState; + private readonly IScrollState scrollState = scrollState; /// /// The undo stack @@ -107,38 +122,10 @@ public class MergeViewerControlViewModel : BaseViewModel /// /// The syncing selection /// - private bool syncingSelection = false; + private bool syncingSelection; #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// State of the scroll. - /// The mod patch collection service. - /// The hotkey pressed handler. - /// The application action. - /// The external editor service. - /// The notification action. - /// The localization manager. - public MergeViewerControlViewModel(IScrollState scrollState, - IModPatchCollectionService modPatchCollectionService, ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler, - IAppAction appAction, IExternalEditorService externalEditorService, - INotificationAction notificationAction, ILocalizationManager localizationManager) - { - this.appAction = appAction; - this.externalEditorService = externalEditorService; - this.notificationAction = notificationAction; - this.localizationManager = localizationManager; - this.hotkeyPressedHandler = hotkeyPressedHandler; - this.modPatchCollectionService = modPatchCollectionService; - this.scrollState = scrollState; - } - - #endregion Constructors - #region Delegates /// @@ -614,7 +601,7 @@ public virtual void InitParameters() /// true if [is redo available]; otherwise, false. public virtual bool IsRedoAvailable() { - return redoStack.Any(); + return redoStack.Count != 0; } /// @@ -623,7 +610,7 @@ public virtual bool IsRedoAvailable() /// true if [is undo available]; otherwise, false. public virtual bool IsUndoAvailable() { - return undoStack.Any(); + return undoStack.Count != 0; } /// @@ -664,15 +651,12 @@ void evalStack(string text, string prevText) prevText ??= string.Empty; if (undoStack.Count == 0 && string.IsNullOrWhiteSpace(prevText)) { - return; } - else if (undoStack.Count > 0 && undoStack.FirstOrDefault().Equals(prevText)) + else if (undoStack.Count > 0 && undoStack.FirstOrDefault()!.Equals(prevText)) { - return; } else if (prevText.Equals(text)) { - return; } else { @@ -680,6 +664,7 @@ void evalStack(string text, string prevText) redoStack.Clear(); } } + scrollState.SetState(!lockScroll); var prevLeftSide = LeftSide; var prevRightSide = RightSide; @@ -706,6 +691,7 @@ void evalStack(string text, string prevText) evalStack(RightSide, prevRightSide); } } + scrollState.SetState(true); } @@ -731,7 +717,7 @@ protected virtual void Copy(IEnumerable selected, IList 0) { - int indx = 0; + var idx = 0; selected = CopyDiffPieceCollection(selected); source = CopyDiffPieceCollection(source); destination = CopyDiffPieceCollection(destination); @@ -740,8 +726,9 @@ protected virtual void Copy(IEnumerable selected, IList p.Text != null).Select(p => p.Text))); @@ -750,7 +737,8 @@ protected virtual void Copy(IEnumerable selected, IList p.Text != null).Select(p => p.Text)), RightSide); } - ConflictFound?.Invoke(indx); + + ConflictFound?.Invoke(idx); } } @@ -765,15 +753,12 @@ protected virtual void CopyAfterLines(IEnumerable selected, { if (selected?.Count() > 0) { - int indx = 0; + var idx = 0; selected = CopyDiffPieceCollection(selected); source = CopyDiffPieceCollection(source); destination = CopyDiffPieceCollection(destination); var ordered = OrderSelected(selected, source); - var grouped = ordered.Select((x, id) => - { - return Tuple.Create(x.Key, x.Value, id == 0 ? 0 : x.Key - ordered.ElementAt(id - 1).Key, id); - }).ToList(); + var grouped = ordered.Select((x, id) => Tuple.Create(x.Key, x.Value, id == 0 ? 0 : x.Key - ordered.ElementAt(id - 1).Key, id)).ToList(); Tuple initial = null; var appliedOffset = 0; foreach (var item in grouped) @@ -785,16 +770,15 @@ protected virtual void CopyAfterLines(IEnumerable selected, var groupCopy = grouped.Skip(item.Item4 + 1).TakeWhile(p => p.Item3 <= 1); if (groupCopy.Any()) { - if (groupCopy.Last() != initial) + if (groupCopy.Last() != null) { initial = groupCopy.Last(); } } - if (initial == null) - { - initial = item; - } + + initial ??= item; } + var index = initial.Item1 + item.Item4 + appliedOffset + 1; var count = destination.Count - 1; if (index < 0) @@ -805,6 +789,7 @@ protected virtual void CopyAfterLines(IEnumerable selected, { index = count; } + while (destination[index].Type == ChangeType.Imaginary) { if (index < 0) @@ -812,17 +797,21 @@ protected virtual void CopyAfterLines(IEnumerable selected, index = 0; break; } + else if (index > count) { index = count; break; } + index++; appliedOffset++; } + destination.Insert(index, item.Item2); - indx = index; + idx = index; } + if (leftSide) { SetText(LeftSide, string.Join(Environment.NewLine, destination.Where(p => p.Text != null).Select(p => p.Text))); @@ -831,7 +820,8 @@ protected virtual void CopyAfterLines(IEnumerable selected, { SetText(string.Join(Environment.NewLine, destination.Where(p => p.Text != null).Select(p => p.Text)), RightSide); } - ConflictFound?.Invoke(indx); + + ConflictFound?.Invoke(idx); } } @@ -846,15 +836,12 @@ protected virtual void CopyBeforeLines(IEnumerable selected, { if (selected?.Count() > 0) { - int indx = 0; + var indx = 0; selected = CopyDiffPieceCollection(selected); source = CopyDiffPieceCollection(source); destination = CopyDiffPieceCollection(destination); var ordered = OrderSelected(selected, source); - var grouped = ordered.Select((x, id) => - { - return Tuple.Create(x.Key, x.Value, id == 0 ? 0 : x.Key - ordered.ElementAt(id - 1).Key, id); - }).ToList(); + var grouped = ordered.Select((x, id) => { return Tuple.Create(x.Key, x.Value, id == 0 ? 0 : x.Key - ordered.ElementAt(id - 1).Key, id); }).ToList(); Tuple initial = null; var appliedOffset = 0; foreach (var item in grouped) @@ -864,6 +851,7 @@ protected virtual void CopyBeforeLines(IEnumerable selected, appliedOffset = 0; initial = item; } + var index = initial.Item1 + item.Item4 + appliedOffset; var count = destination.Count - 1; if (index < 0) @@ -874,6 +862,7 @@ protected virtual void CopyBeforeLines(IEnumerable selected, { index = count; } + while (destination[index].Type == ChangeType.Imaginary) { if (index < 0) @@ -881,17 +870,21 @@ protected virtual void CopyBeforeLines(IEnumerable selected, index = 0; break; } + else if (index > count) { index = count; break; } + index--; appliedOffset--; } + destination.Insert(index, item.Item2); indx = index; } + if (leftSide) { SetText(LeftSide, string.Join(Environment.NewLine, destination.Where(p => p.Text != null).Select(p => p.Text))); @@ -900,6 +893,7 @@ protected virtual void CopyBeforeLines(IEnumerable selected, { SetText(string.Join(Environment.NewLine, destination.Where(p => p.Text != null).Select(p => p.Text)), RightSide); } + ConflictFound?.Invoke(indx); } } @@ -930,7 +924,7 @@ protected async Task CopyTextAsync(bool leftSide) /// if set to true [left side]. protected virtual void DeleteLines(bool leftSide) { - int indx = 0; + var indx = 0; var selected = leftSide ? LeftSideSelected : RightSideSelected; var source = CopyDiffPieceCollection(leftSide ? LeftDiff : RightDiff); if (selected != null && source != null && selected.Count > 0 && selected.Count <= source.Count) @@ -940,6 +934,7 @@ protected virtual void DeleteLines(bool leftSide) source.Remove(item); indx = item.Index; } + if (leftSide) { SetText(string.Join(Environment.NewLine, source.Where(p => p.Text != null).Select(p => p.Text)), RightSide); @@ -948,6 +943,7 @@ protected virtual void DeleteLines(bool leftSide) { SetText(LeftSide, string.Join(Environment.NewLine, source.Where(p => p.Text != null).Select(p => p.Text))); } + ConflictFound?.Invoke(indx); } } @@ -973,11 +969,12 @@ protected virtual void FindConflict(bool leftSide, bool moveDown, bool skipImagi while (true) { idx++; - if (idx > (source.Count - 1)) + if (idx > source.Count - 1) { idx = source.Count - 1; break; } + var prevIdx = idx - 1; var type = source[prevIdx].Type; if (source[idx].Type == ChangeType.Unchanged || (type == ChangeType.Unchanged && source[idx].Type != ChangeType.Unchanged)) @@ -985,6 +982,7 @@ protected virtual void FindConflict(bool leftSide, bool moveDown, bool skipImagi break; } } + var line = source.Skip(idx).FirstOrDefault(p => p.SubPieces.Count > 0 || !(p.Type == ChangeType.Unchanged || (skipImaginary && p.Type == ChangeType.Imaginary))); if (line != null) { @@ -993,6 +991,7 @@ protected virtual void FindConflict(bool leftSide, bool moveDown, bool skipImagi { index = 0; } + line = source.Skip(index).TakeWhile(p => p.SubPieces.Count > 0 || !(p.Type == ChangeType.Unchanged || (skipImaginary && p.Type == ChangeType.Imaginary))).LastOrDefault(); if (line != null) { @@ -1007,22 +1006,25 @@ protected virtual void FindConflict(bool leftSide, bool moveDown, bool skipImagi while (true) { reverseIdx++; - if (reverseIdx > (reverseSrc.Count - 1)) + if (reverseIdx > reverseSrc.Count - 1) { reverseIdx = reverseSrc.Count - 1; break; } + var prevIdx = reverseIdx - 1; if (prevIdx < 0) { prevIdx = 0; } + var type = reverseSrc[prevIdx].Type; if (reverseSrc[reverseIdx].Type == ChangeType.Unchanged || (type == ChangeType.Unchanged && reverseSrc[reverseIdx].Type != ChangeType.Unchanged)) { break; } } + var line = reverseSrc.Skip(reverseIdx).FirstOrDefault(p => p.SubPieces.Count > 0 || !(p.Type == ChangeType.Unchanged || (skipImaginary && p.Type == ChangeType.Imaginary))); if (line != null) { @@ -1031,6 +1033,7 @@ protected virtual void FindConflict(bool leftSide, bool moveDown, bool skipImagi { index = 0; } + line = reverseSrc.Skip(index).TakeWhile(p => p.SubPieces.Count > 0 || !(p.Type == ChangeType.Unchanged || (skipImaginary && p.Type == ChangeType.Imaginary))).LastOrDefault(); if (line != null) { @@ -1038,6 +1041,7 @@ protected virtual void FindConflict(bool leftSide, bool moveDown, bool skipImagi } } } + if (matchIdx.HasValue) { ConflictFound?.Invoke(matchIdx.GetValueOrDefault()); @@ -1054,11 +1058,11 @@ protected virtual void FindConflict(bool leftSide, bool moveDown, bool skipImagi protected virtual List GetDiffPieceWithIndex(List lines) { var col = new List(); - int counter = 0; + var counter = 0; foreach (var item in lines) { counter++; - col.Add(new DiffPieceWithIndex() + col.Add(new DiffPieceWithIndex { Index = counter, Position = item.Position, @@ -1067,6 +1071,7 @@ protected virtual List GetDiffPieceWithIndex(List Type = item.Type }); } + return col; } @@ -1081,7 +1086,7 @@ protected virtual void Move(bool moveUp, IEnumerable selecte { if (selected?.Count() > 0) { - int indx = 0; + var indx = 0; selected = CopyDiffPieceCollection(selected); source = CopyDiffPieceCollection(source); var ordered = OrderSelected(selected, source); @@ -1097,6 +1102,7 @@ protected virtual void Move(bool moveUp, IEnumerable selecte { index = count; } + while (source[index].Type == ChangeType.Imaginary) { if (index < 0) @@ -1109,6 +1115,7 @@ protected virtual void Move(bool moveUp, IEnumerable selecte index = count; break; } + if (moveUp) { index--; @@ -1118,10 +1125,12 @@ protected virtual void Move(bool moveUp, IEnumerable selecte index++; } } + source.RemoveAt(item.Key); source.Insert(index, item.Value); indx = index; } + if (leftSide) { SetText(string.Join(Environment.NewLine, source.Where(p => p.Text != null).Select(p => p.Text)), RightSide); @@ -1130,6 +1139,7 @@ protected virtual void Move(bool moveUp, IEnumerable selecte { SetText(LeftSide, string.Join(Environment.NewLine, source.Where(p => p.Text != null).Select(p => p.Text))); } + ConflictFound?.Invoke(indx); } } @@ -1143,22 +1153,24 @@ protected override void OnActivated(CompositeDisposable disposables) LeftSideSelected = new AvaloniaList(); RightSideSelected = new AvaloniaList(); - LeftSideSelected.CollectionChanged += (sender, args) => + LeftSideSelected.CollectionChanged += (_, _) => { if (syncingSelection) { return; } + syncingSelection = true; SyncSelectionsAsync(true).ConfigureAwait(true); }; - RightSideSelected.CollectionChanged += (sender, args) => + RightSideSelected.CollectionChanged += (_, _) => { if (syncingSelection) { return; } + syncingSelection = true; SyncSelectionsAsync(false).ConfigureAwait(true); }; @@ -1167,11 +1179,11 @@ protected override void OnActivated(CompositeDisposable disposables) { if (leftSide) { - Copy(LeftDiff, LeftDiff, RightDiff, leftSide); + Copy(LeftDiff, LeftDiff, RightDiff, true); } else { - Copy(RightDiff, RightDiff, LeftDiff, leftSide); + Copy(RightDiff, RightDiff, LeftDiff, false); } }).DisposeWith(disposables); @@ -1179,11 +1191,11 @@ protected override void OnActivated(CompositeDisposable disposables) { if (leftSide) { - Copy(LeftSideSelected, LeftDiff, RightDiff, leftSide); + Copy(LeftSideSelected, LeftDiff, RightDiff, true); } else { - Copy(RightSideSelected, RightDiff, LeftDiff, leftSide); + Copy(RightSideSelected, RightDiff, LeftDiff, false); } }).DisposeWith(disposables); @@ -1191,11 +1203,11 @@ protected override void OnActivated(CompositeDisposable disposables) { if (leftSide) { - CopyBeforeLines(LeftSideSelected, LeftDiff, RightDiff, leftSide); + CopyBeforeLines(LeftSideSelected, LeftDiff, RightDiff, true); } else { - CopyBeforeLines(RightSideSelected, RightDiff, LeftDiff, leftSide); + CopyBeforeLines(RightSideSelected, RightDiff, LeftDiff, false); } }).DisposeWith(disposables); @@ -1203,11 +1215,11 @@ protected override void OnActivated(CompositeDisposable disposables) { if (leftSide) { - CopyAfterLines(LeftSideSelected, LeftDiff, RightDiff, leftSide); + CopyAfterLines(LeftSideSelected, LeftDiff, RightDiff, true); } else { - CopyAfterLines(RightSideSelected, LeftDiff, RightDiff, leftSide); + CopyAfterLines(RightSideSelected, LeftDiff, RightDiff, false); } }).DisposeWith(disposables); @@ -1215,11 +1227,11 @@ protected override void OnActivated(CompositeDisposable disposables) { if (leftSide) { - Move(true, LeftSideSelected, LeftDiff, leftSide); + Move(true, LeftSideSelected, LeftDiff, true); } else { - Move(true, RightSideSelected, RightDiff, leftSide); + Move(true, RightSideSelected, RightDiff, false); } }).DisposeWith(disposables); @@ -1227,11 +1239,11 @@ protected override void OnActivated(CompositeDisposable disposables) { if (leftSide) { - Move(false, LeftSideSelected, LeftDiff, leftSide); + Move(false, LeftSideSelected, LeftDiff, true); } else { - Move(false, RightSideSelected, RightDiff, leftSide); + Move(false, RightSideSelected, RightDiff, false); } }).DisposeWith(disposables); @@ -1241,66 +1253,37 @@ protected override void OnActivated(CompositeDisposable disposables) { if (EditingLeft) { - string merged = string.Join(Environment.NewLine, LeftDocument.Text.SplitOnNewLine()); + var merged = string.Join(Environment.NewLine, LeftDocument.Text.SplitOnNewLine()); SetText(merged, RightSide); } else { - string merged = string.Join(Environment.NewLine, RightDocument.Text.SplitOnNewLine()); + var merged = string.Join(Environment.NewLine, RightDocument.Text.SplitOnNewLine()); SetText(LeftSide, merged); } + ExitEditMode(); }, okEnabled).DisposeWith(disposables); - CancelCommand = ReactiveCommand.Create(() => - { - ExitEditMode(); - }).DisposeWith(disposables); + CancelCommand = ReactiveCommand.Create(() => { ExitEditMode(); }).DisposeWith(disposables); - EditThisCommand = ReactiveCommand.Create((bool leftSide) => - { - SetEditThis(leftSide); - }).DisposeWith(disposables); + EditThisCommand = ReactiveCommand.Create((bool leftSide) => { SetEditThis(leftSide); }).DisposeWith(disposables); - CopyTextCommand = ReactiveCommand.Create((bool leftSide) => - { - CopyTextAsync(leftSide).ConfigureAwait(true); - }).DisposeWith(disposables); + CopyTextCommand = ReactiveCommand.Create((bool leftSide) => { CopyTextAsync(leftSide).ConfigureAwait(true); }).DisposeWith(disposables); - NextConflictCommand = ReactiveCommand.Create((bool leftSide) => - { - FindConflict(leftSide, true, false); - }).DisposeWith(disposables); + NextConflictCommand = ReactiveCommand.Create((bool leftSide) => { FindConflict(leftSide, true, false); }).DisposeWith(disposables); - PrevConflictCommand = ReactiveCommand.Create((bool leftSide) => - { - FindConflict(leftSide, false, false); - }).DisposeWith(disposables); + PrevConflictCommand = ReactiveCommand.Create((bool leftSide) => { FindConflict(leftSide, false, false); }).DisposeWith(disposables); - DeleteTextCommand = ReactiveCommand.Create((bool leftSide) => - { - DeleteLines(leftSide); - }).DisposeWith(disposables); + DeleteTextCommand = ReactiveCommand.Create((bool leftSide) => { DeleteLines(leftSide); }).DisposeWith(disposables); - UndoCommand = ReactiveCommand.Create((bool leftSide) => - { - PerformUndo(leftSide); - }).DisposeWith(disposables); + UndoCommand = ReactiveCommand.Create((bool leftSide) => { PerformUndo(leftSide); }).DisposeWith(disposables); - RedoCommand = ReactiveCommand.Create((bool leftSide) => - { - PerformRedo(leftSide); - }).DisposeWith(disposables); + RedoCommand = ReactiveCommand.Create((bool leftSide) => { PerformRedo(leftSide); }).DisposeWith(disposables); - EditorCommand = ReactiveCommand.CreateFromTask((bool leftSide) => - { - return LaunchExternalEditor(leftSide); - }).DisposeWith(disposables); + EditorCommand = ReactiveCommand.CreateFromTask((bool leftSide) => { return LaunchExternalEditor(leftSide); }).DisposeWith(disposables); - ReadOnlyEditorCommand = ReactiveCommand.CreateFromTask((bool leftSide) => - { - return LaunchExternalEditor(leftSide, true); - }).DisposeWith(disposables); + ReadOnlyEditorCommand = ReactiveCommand.CreateFromTask((bool leftSide) => { return LaunchExternalEditor(leftSide, true); }).DisposeWith(disposables); var previousEditTextState = false; this.WhenAnyValue(v => v.EditingText).Subscribe(s => @@ -1323,6 +1306,7 @@ void performAction() { LeftSideSelected.Add(LeftDiff.FirstOrDefault()); } + FindConflict(true, false, false); break; @@ -1331,6 +1315,7 @@ void performAction() { LeftSideSelected.Add(LeftDiff.FirstOrDefault()); } + FindConflict(true, true, false); break; @@ -1339,6 +1324,7 @@ void performAction() { LeftSideSelected.Add(LeftDiff.FirstOrDefault()); } + FindConflict(true, false, true); break; @@ -1347,6 +1333,7 @@ void performAction() { LeftSideSelected.Add(LeftDiff.FirstOrDefault()); } + FindConflict(true, true, true); break; @@ -1355,6 +1342,7 @@ void performAction() { SetEditThis(LeftSidePatchMod); } + break; case Enums.HotKeys.Ctrl_Shift_T: @@ -1374,6 +1362,7 @@ void performAction() { Copy(LeftSideSelected, LeftDiff, RightDiff, true); } + break; case Enums.HotKeys.Ctrl_V: @@ -1385,6 +1374,7 @@ void performAction() { CopyBeforeLines(LeftSideSelected, LeftDiff, RightDiff, true); } + break; case Enums.HotKeys.Ctrl_B: @@ -1396,6 +1386,7 @@ void performAction() { CopyAfterLines(LeftSideSelected, LeftDiff, RightDiff, true); } + break; case Enums.HotKeys.Ctrl_Z: @@ -1408,6 +1399,7 @@ void performAction() PostFocusSide?.Invoke(LeftSidePatchMod); } } + break; case Enums.HotKeys.Ctrl_Y: @@ -1420,6 +1412,7 @@ void performAction() PostFocusSide?.Invoke(LeftSidePatchMod); } } + break; case Enums.HotKeys.Ctrl_X: @@ -1431,12 +1424,11 @@ void performAction() { LaunchExternalEditor(LeftSidePatchMod, true).ConfigureAwait(true); } - break; - default: break; } } + if (CanPerformHotKeyActions) { Dispatcher.UIThread.SafeInvoke(performAction); @@ -1460,6 +1452,7 @@ protected virtual Dictionary OrderSelected(IEnumerable< var idx = source.IndexOf(item); orderedSelected.Add(idx, item); } + return orderedSelected.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value); } @@ -1473,6 +1466,7 @@ protected virtual void PerformRedo(bool leftSide) { return; } + if (leftSide) { undoStack.Push(LeftSide); @@ -1495,6 +1489,7 @@ protected virtual void PerformUndo(bool leftSide) { return; } + if (leftSide) { redoStack.Push(LeftSide); @@ -1524,6 +1519,7 @@ protected virtual void SetEditThis(bool leftSide) { CurrentEditText = RightSide; } + LeftDocument = new TextDocument(LeftSide); RightDocument = new TextDocument(RightSide); } @@ -1540,7 +1536,7 @@ protected virtual async Task SyncSelectionsAsync(bool leftSide) var syncDiff = !leftSide ? LeftDiff : RightDiff; var sourceCol = leftSide ? LeftSideSelected : RightSideSelected; var syncCol = !leftSide ? LeftSideSelected : RightSideSelected; - bool clearCol = true; + var clearCol = true; if (sourceCol?.Count > 0) { var filtered = sourceCol.Where(p => p != null); // Must be an underlying bug? @@ -1554,10 +1550,12 @@ protected virtual async Task SyncSelectionsAsync(bool leftSide) } } } + if (clearCol) { syncCol.Clear(); } + syncingSelection = false; } @@ -1582,6 +1580,7 @@ private async Task LaunchExternalEditor(bool leftSide, bool noPatchModEdit = fal // Override if analyze mode only noPatchModEdit = true; } + if (await appAction.RunAsync(opts.ExternalEditorLocation, arguments)) { string title; @@ -1596,6 +1595,7 @@ private async Task LaunchExternalEditor(bool leftSide, bool noPatchModEdit = fal message = localizationManager.GetResource(LocalizationResources.Conflict_Solver.ReadonlyEditor.Message); title = localizationManager.GetResource(LocalizationResources.Conflict_Solver.ReadonlyEditor.Title); } + if (await notificationAction.ShowPromptAsync(title, title, message, NotificationType.Info, !noPatchModEdit ? PromptType.ConfirmCancel : PromptType.OK)) { if (!noPatchModEdit) @@ -1603,19 +1603,20 @@ private async Task LaunchExternalEditor(bool leftSide, bool noPatchModEdit = fal if (leftSide) { var text = files.LeftDiff.Text ?? string.Empty; - string merged = string.Join(Environment.NewLine, text.ReplaceTabs()); + var merged = string.Join(Environment.NewLine, text.ReplaceTabs()); SetText(merged, RightSide); } else { var text = files.RightDiff.Text ?? string.Empty; - string merged = string.Join(Environment.NewLine, text.ReplaceTabs()); + var merged = string.Join(Environment.NewLine, text.ReplaceTabs()); SetText(LeftSide, merged); } } } } - files?.Dispose(); + + files.Dispose(); } } @@ -1690,6 +1691,7 @@ public override bool Equals(object obj) { return Index.Equals(diffPieceWithIndex.Index); } + return result; } @@ -1710,6 +1712,7 @@ public bool IsMatch(string term) { return false; } + term ??= string.Empty; return Text.Trim().StartsWith(term, StringComparison.OrdinalIgnoreCase); } From c25084991a8fd4719948fec1cd3b29cfefa482b3 Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 20 Feb 2024 07:20:10 +0100 Subject: [PATCH 043/101] Modernize merge viewer control --- .../Extensions.Double.cs | 62 +++++ .../Controls/MergeViewerControlViewModel.cs | 18 +- .../Controls/MergeViewerControlView.xaml | 162 ++++++------ .../Controls/MergeViewerControlView.xaml.cs | 240 +++++++++--------- 4 files changed, 267 insertions(+), 215 deletions(-) create mode 100644 src/IronyModManager.Shared/Extensions.Double.cs diff --git a/src/IronyModManager.Shared/Extensions.Double.cs b/src/IronyModManager.Shared/Extensions.Double.cs new file mode 100644 index 00000000..4fae70c3 --- /dev/null +++ b/src/IronyModManager.Shared/Extensions.Double.cs @@ -0,0 +1,62 @@ +// *********************************************************************** +// Assembly : IronyModManager.Shared +// Author : Mario +// Created : 02-20-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-20-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace IronyModManager.Shared +{ + /// + /// Class Extensions. + /// + public static partial class Extensions + { + #region Fields + + /// + /// A private const double named defaultTolerance. + /// + private const double defaultTolerance = 0.01; + + #endregion Fields + + #region Methods + + /// + /// Is nearly equal. + /// + /// The a. + /// The b. + /// The tolerance. + /// A bool. + public static bool IsNearlyEqual(this double a, double b, double tolerance) + { + return Math.Abs(a - b) < tolerance; + } + + /// + /// Is nearly equal. + /// + /// The a. + /// The b. + /// A bool. + public static bool IsNearlyEqual(this double a, double b) + { + return IsNearlyEqual(a, b, defaultTolerance); + } + + #endregion Methods + } +} diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index b5b8a8ef..a0bf55e3 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -836,7 +836,7 @@ protected virtual void CopyBeforeLines(IEnumerable selected, { if (selected?.Count() > 0) { - var indx = 0; + var idx = 0; selected = CopyDiffPieceCollection(selected); source = CopyDiffPieceCollection(source); destination = CopyDiffPieceCollection(destination); @@ -882,7 +882,7 @@ protected virtual void CopyBeforeLines(IEnumerable selected, } destination.Insert(index, item.Item2); - indx = index; + idx = index; } if (leftSide) @@ -894,7 +894,7 @@ protected virtual void CopyBeforeLines(IEnumerable selected, SetText(string.Join(Environment.NewLine, destination.Where(p => p.Text != null).Select(p => p.Text)), RightSide); } - ConflictFound?.Invoke(indx); + ConflictFound?.Invoke(idx); } } @@ -924,7 +924,7 @@ protected async Task CopyTextAsync(bool leftSide) /// if set to true [left side]. protected virtual void DeleteLines(bool leftSide) { - var indx = 0; + var idx = 0; var selected = leftSide ? LeftSideSelected : RightSideSelected; var source = CopyDiffPieceCollection(leftSide ? LeftDiff : RightDiff); if (selected != null && source != null && selected.Count > 0 && selected.Count <= source.Count) @@ -932,7 +932,7 @@ protected virtual void DeleteLines(bool leftSide) foreach (var item in selected) { source.Remove(item); - indx = item.Index; + idx = item.Index; } if (leftSide) @@ -944,7 +944,7 @@ protected virtual void DeleteLines(bool leftSide) SetText(LeftSide, string.Join(Environment.NewLine, source.Where(p => p.Text != null).Select(p => p.Text))); } - ConflictFound?.Invoke(indx); + ConflictFound?.Invoke(idx); } } @@ -1086,7 +1086,7 @@ protected virtual void Move(bool moveUp, IEnumerable selecte { if (selected?.Count() > 0) { - var indx = 0; + var idx = 0; selected = CopyDiffPieceCollection(selected); source = CopyDiffPieceCollection(source); var ordered = OrderSelected(selected, source); @@ -1128,7 +1128,7 @@ protected virtual void Move(bool moveUp, IEnumerable selecte source.RemoveAt(item.Key); source.Insert(index, item.Value); - indx = index; + idx = index; } if (leftSide) @@ -1140,7 +1140,7 @@ protected virtual void Move(bool moveUp, IEnumerable selecte SetText(LeftSide, string.Join(Environment.NewLine, source.Where(p => p.Text != null).Select(p => p.Text))); } - ConflictFound?.Invoke(indx); + ConflictFound?.Invoke(idx); } } diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml index 66b04589..2fd5e2d5 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml @@ -6,91 +6,89 @@ xmlns:controls="clr-namespace:IronyModManager.Controls;assembly=IronyModManager" xmlns:converter="clr-namespace:IronyModManager.Converters;assembly=IronyModManager" x:Class="IronyModManager.Views.Controls.MergeViewerControlView"> - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + /// The instance containing the event data. - /// The this ListBox. - /// The other ListBox. + /// This ListBox. + /// Other ListBox. protected virtual void HandleListBoxPropertyChanged(AvaloniaPropertyChangedEventArgs args, ListBox thisListBox, ListBox otherListBox) { if (args.Property == ListBox.ScrollProperty && thisListBox.Scroll != null) { var scroll = (ScrollViewer)thisListBox.Scroll; - scroll.PropertyChanged += (scrollSender, scrollArgs) => + scroll.PropertyChanged += (_, scrollArgs) => { if (scrollArgs.Property == ScrollViewer.HorizontalScrollBarValueProperty || scrollArgs.Property == ScrollViewer.VerticalScrollBarValueProperty) { if (thisListBox.Scroll == null || otherListBox.Scroll == null || syncingScroll || - (otherListBox.Scroll.Offset.X == thisListBox.Scroll.Offset.X && otherListBox.Scroll.Offset.Y == thisListBox.Scroll.Offset.Y)) + (otherListBox.Scroll.Offset.X.IsNearlyEqual(thisListBox.Scroll.Offset.X) && otherListBox.Scroll.Offset.Y.IsNearlyEqual(thisListBox.Scroll.Offset.Y))) { return; } + syncingScroll = true; Dispatcher.UIThread.SafeInvoke(async () => { @@ -196,41 +199,26 @@ protected override void OnActivated(CompositeDisposable disposables) var leftSide = this.FindControl("leftSide"); var rightSide = this.FindControl("rightSide"); - leftSide.PropertyChanged += (sender, args) => - { - HandleListBoxPropertyChanged(args, leftSide, rightSide); - }; - rightSide.PropertyChanged += (sender, args) => - { - HandleListBoxPropertyChanged(args, rightSide, leftSide); - }; - leftSide.ContextMenuOpening += (item) => - { - HandleContextMenu(leftSide, item, true); - }; - rightSide.ContextMenuOpening += (item) => - { - HandleContextMenu(rightSide, item, false); - }; - ViewModel.ConflictFound += (line) => - { - FocusConflict(line, leftSide, rightSide); - }; + leftSide.PropertyChanged += (_, args) => { HandleListBoxPropertyChanged(args, leftSide, rightSide); }; + rightSide.PropertyChanged += (_, args) => { HandleListBoxPropertyChanged(args, rightSide, leftSide); }; + leftSide.ContextMenuOpening += item => { HandleContextMenu(leftSide, item, true); }; + rightSide.ContextMenuOpening += item => { HandleContextMenu(rightSide, item, false); }; + ViewModel!.ConflictFound += line => { FocusConflict(line, leftSide, rightSide); }; int? focusSideScrollItem = null; - int previousCount = 0; + var previousCount = 0; var autoScroll = leftSide.AutoScrollToSelectedItem; - ViewModel.PreFocusSide += (left) => + ViewModel.PreFocusSide += left => { leftSide.AutoScrollToSelectedItem = rightSide.AutoScrollToSelectedItem = false; var listBox = left ? leftSide : rightSide; var visibleItems = listBox.ItemContainerGenerator.Containers.ToList(); - if (visibleItems.Any()) + if (visibleItems.Count != 0) { - focusSideScrollItem = visibleItems.LastOrDefault().Index; + focusSideScrollItem = visibleItems.LastOrDefault()!.Index; previousCount = (left ? leftSide : rightSide).ItemCount; } }; - ViewModel.PostFocusSide += (left) => + ViewModel.PostFocusSide += left => { async Task delay() { @@ -244,6 +232,7 @@ async Task delay() { focusSideScrollItem -= Math.Abs(previousCount - listBox.ItemCount); } + FocusConflict(-1, leftSide, rightSide); if (focusSideScrollItem.GetValueOrDefault() < 0) { @@ -253,10 +242,13 @@ async Task delay() { focusSideScrollItem = listBox.ItemCount - 1; } + listBox.ScrollIntoView(focusSideScrollItem.GetValueOrDefault()); } + focusSideScrollItem = null; } + Dispatcher.UIThread.SafeInvoke(() => delay().ConfigureAwait(false)); }; @@ -265,43 +257,47 @@ async Task delay() DiffPieceWithIndex findItem(bool searchUp) { var visibleItems = leftSide.ItemContainerGenerator.Containers.ToList(); - if (visibleItems.Any()) + if (visibleItems.Count != 0) { if (leftSide.Items is IEnumerable items) { var itemsList = items.ToList(); if (searchUp) { - if (visibleItems.FirstOrDefault().Item is DiffPieceWithIndex visibleItem) + if (visibleItems.FirstOrDefault()!.Item is DiffPieceWithIndex visibleItem) { var index = itemsList.IndexOf(visibleItem) - 2; if (index < 0) { index = 0; } + return itemsList[index]; } } else { - if (visibleItems.LastOrDefault().Item is DiffPieceWithIndex visibleItem) + if (visibleItems.LastOrDefault()!.Item is DiffPieceWithIndex visibleItem) { var index = itemsList.IndexOf(visibleItem) + 2; if (index > leftSide.ItemCount - 1) { index = leftSide.ItemCount - 1; } + return itemsList[index]; } } } } + return null; } + void evalKey() { // Yeah, it sucks that we can't access a property from a different thread - if (ViewModel.CanPerformHotKeyActions) + if (ViewModel!.CanPerformHotKeyActions) { DiffPiece item = null; switch (hotkey.Hotkey) @@ -313,10 +309,8 @@ void evalKey() case Enums.HotKeys.Ctrl_Shift_Down: item = findItem(false); break; - - default: - break; } + if (item != null) { leftSide.ScrollIntoView(item); @@ -349,43 +343,33 @@ protected override void OnLocaleChanged(string newLocale, string oldLocale) /// if set to true [left side]. protected virtual void SetEditorOptions(TextEditor editor, bool leftSide) { - editor.Options = new TextEditorOptions() + editor.Options = new TextEditorOptions { ConvertTabsToSpaces = true, IndentationSize = 4 }; - ViewModel.WhenAnyValue(p => p.EditingYaml).Subscribe(s => - { - setEditMode(); - }).DisposeWith(Disposables); + ViewModel.WhenAnyValue(p => p.EditingYaml).Subscribe(_ => { setEditMode(); }).DisposeWith(Disposables); setEditMode(); void setEditMode() { - if (ViewModel.EditingYaml) - { - editor.SyntaxHighlighting = resourceLoader.GetYAMLDefinition(); - } - else - { - editor.SyntaxHighlighting = resourceLoader.GetPDXScriptDefinition(); - } + editor.SyntaxHighlighting = ViewModel!.EditingYaml ? resourceLoader.GetYAMLDefinition() : resourceLoader.GetPDXScriptDefinition(); } - editor.TextChanged += (sender, args) => + editor.TextChanged += (_, _) => { var lines = editor.Text.SplitOnNewLine().ToList(); - string text = string.Join(Environment.NewLine, lines); - ViewModel.CurrentEditText = text; + var text = string.Join(Environment.NewLine, lines); + ViewModel!.CurrentEditText = text; }; } /// /// Synchronizes the scroll asynchronous. /// - /// The this ListBox. - /// The other ListBox. + /// This ListBox. + /// Other ListBox. /// Task. protected virtual Task SyncScrollAsync(ListBox thisListBox, ListBox otherListBox) { @@ -394,15 +378,17 @@ protected virtual Task SyncScrollAsync(ListBox thisListBox, ListBox otherListBox var otherMaxX = Math.Abs(otherListBox.Scroll.Extent.Width - otherListBox.Scroll.Viewport.Width); var otherMaxY = Math.Abs(otherListBox.Scroll.Extent.Height - otherListBox.Scroll.Viewport.Height); var offset = thisListBox.Scroll.Offset; - if (thisListBox.Scroll.Offset.X > otherMaxX || thisListBox.Scroll.Offset.X == thisMaxX) + if (thisListBox.Scroll.Offset.X > otherMaxX || thisListBox.Scroll.Offset.X.IsNearlyEqual(thisMaxX)) { offset = offset.WithX(otherMaxX); } - if (thisListBox.Scroll.Offset.Y > otherMaxY || thisListBox.Scroll.Offset.Y == thisMaxY) + + if (thisListBox.Scroll.Offset.Y > otherMaxY || thisListBox.Scroll.Offset.Y.IsNearlyEqual(thisMaxY)) { offset = offset.WithY(otherMaxY); } - if (otherListBox.Scroll.Offset.X != offset.X || otherListBox.Scroll.Offset.Y != offset.Y) + + if (!otherListBox.Scroll.Offset.X.IsNearlyEqual(offset.X) || otherListBox.Scroll.Offset.Y.IsNearlyEqual(offset.Y)) { try { @@ -415,6 +401,7 @@ protected virtual Task SyncScrollAsync(ListBox thisListBox, ListBox otherListBox logger.Error(ex); } } + return Task.FromResult(true); } @@ -426,67 +413,67 @@ protected virtual Task SyncScrollAsync(ListBox thisListBox, ListBox otherListBox private List GetActionsMenuItems(bool leftSide) { var menuItems = new List(); - if (ViewModel.EditorAvailable) + if (ViewModel!.EditorAvailable) { - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = ViewModel.Editor, Command = ViewModel.EditorCommand, CommandParameter = !leftSide }); - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = "-" }); } - var mainEditingItems = new List() + var mainEditingItems = new List { - new MenuItem() + new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = "-" }, - new MenuItem() + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = "-" // Separator magic string, and it's documented... NOT really!!! }, - new MenuItem() + new() { Header = ViewModel.CopyAll, Command = ViewModel.CopyAllCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.CopyThis, Command = ViewModel.CopyThisCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.CopyThisBeforeLine, Command = ViewModel.CopyThisBeforeLineCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.CopyThisAfterLine, Command = ViewModel.CopyThisAfterLineCommand, @@ -506,72 +493,72 @@ private List GetActionsMenuItems(bool leftSide) private List GetEditableMenuItems(bool leftSide) { var menuItems = new List(); - if (ViewModel.EditorAvailable) + if (ViewModel!.EditorAvailable) { - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = ViewModel.Editor, Command = ViewModel.EditorCommand, CommandParameter = leftSide }); - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = "-" }); } - var mainEditingItems = new List() + var mainEditingItems = new List { - new MenuItem() + new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = "-" }, - new MenuItem() + new() { Header = ViewModel.EditThis, Command = ViewModel.EditThisCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = "-" }, - new MenuItem() + new() { Header = ViewModel.DeleteText, Command = ViewModel.DeleteTextCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.MoveUp, Command = ViewModel.MoveUpCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.MoveDown, Command = ViewModel.MoveDownCommand, CommandParameter = leftSide - }, + } }; menuItems.AddRange(mainEditingItems); @@ -579,22 +566,23 @@ private List GetEditableMenuItems(bool leftSide) var undoAvailable = ViewModel.IsUndoAvailable(); if (redoAvailable || undoAvailable) { - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = "-" }); if (undoAvailable) { - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = ViewModel.Undo, Command = ViewModel.UndoCommand, CommandParameter = leftSide }); } + if (redoAvailable) { - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = ViewModel.Redo, Command = ViewModel.RedoCommand, @@ -602,50 +590,51 @@ private List GetEditableMenuItems(bool leftSide) }); } } + return menuItems; } /// - /// Gets the non editable menu items. + /// Gets the non-editable menu items. /// /// The left side. /// System.Collections.Generic.List<Avalonia.Controls.MenuItem>. private List GetNonEditableMenuItems(bool leftSide) { var menuItems = new List(); - if (ViewModel.EditorAvailable) + if (ViewModel!.EditorAvailable) { - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = ViewModel.ReadOnlyEditor, Command = ViewModel.ReadOnlyEditorCommand, CommandParameter = leftSide }); - menuItems.Add(new MenuItem() + menuItems.Add(new MenuItem { Header = "-" }); } - var mainEditingItems = new List() + var mainEditingItems = new List { - new MenuItem() + new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, - new MenuItem() + new() { Header = "-" }, - new MenuItem() + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, @@ -669,12 +658,14 @@ private void HandleEditorFindOrReplace(IronyModManager.Controls.TextEditor textE { return; } + searchPanel.IsReplaceMode = isReplaceMode; searchPanel.Open(); if (!(textEditor.TextArea.Selection.IsEmpty || textEditor.TextArea.Selection.IsMultiline)) { searchPanel.SearchPattern = textEditor.TextArea.Selection.GetText(); } + Dispatcher.UIThread.Post(searchPanel.Reactivate, DispatcherPriority.Input); } @@ -697,63 +688,64 @@ private void SetEditorContextMenu(IronyModManager.Controls.TextEditor textEditor { return; } + var ctx = new MenuFlyout { - Items = new List() + Items = new List { - new MenuItem() + new() { - Header = ViewModel.EditorCopy, - Command = ReactiveCommand.Create(() => textEditor.Copy()).DisposeWith(Disposables) + Header = ViewModel!.EditorCopy, + Command = ReactiveCommand.Create(() => textEditor.Copy()).DisposeWith(Disposables) }, - new MenuItem() + new() { Header = ViewModel.EditorCut, - Command = ReactiveCommand.Create(() => textEditor.Cut()).DisposeWith(Disposables) + Command = ReactiveCommand.Create(() => textEditor.Cut()).DisposeWith(Disposables) }, - new MenuItem() + new() { Header = ViewModel.EditorPaste, - Command = ReactiveCommand.Create(() => textEditor.Paste()).DisposeWith(Disposables) + Command = ReactiveCommand.Create(() => textEditor.Paste()).DisposeWith(Disposables) }, - new MenuItem() + new() { Header = ViewModel.EditorDelete, - Command = ReactiveCommand.Create(() => textEditor.Delete()).DisposeWith(Disposables) + Command = ReactiveCommand.Create(() => textEditor.Delete()).DisposeWith(Disposables) }, - new MenuItem() + new() { Header = ViewModel.EditorSelectAll, - Command = ReactiveCommand.Create(() => textEditor.SelectAll()).DisposeWith(Disposables) + Command = ReactiveCommand.Create(() => textEditor.SelectAll()).DisposeWith(Disposables) }, - new MenuItem() + new() { Header = "-" }, - new MenuItem() + new() { Header = ViewModel.EditorUndo, - Command = ReactiveCommand.Create(() => textEditor.Undo()).DisposeWith(Disposables) + Command = ReactiveCommand.Create(() => textEditor.Undo()).DisposeWith(Disposables) }, - new MenuItem() + new() { Header = ViewModel.EditorRedo, - Command = ReactiveCommand.Create(() => textEditor.Redo()).DisposeWith(Disposables) + Command = ReactiveCommand.Create(() => textEditor.Redo()).DisposeWith(Disposables) }, - new MenuItem() + new() { Header = "-" }, - new MenuItem() + new() { Header = ViewModel.EditorFind, - Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(textEditor, searchPanel, false)).DisposeWith(Disposables) + Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(textEditor, searchPanel, false)).DisposeWith(Disposables) }, - new MenuItem() + new() { Header = ViewModel.EditorReplace, - Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(textEditor, searchPanel, true)).DisposeWith(Disposables) - }, + Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(textEditor, searchPanel, true)).DisposeWith(Disposables) + } } }; textEditor.ContextFlyout = ctx; From fa80a73cd722c6ffff11774799d91cd94345a018 Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 20 Feb 2024 15:36:54 +0100 Subject: [PATCH 044/101] Add flags to front end --- .../IAppState.cs | 6 +- src/IronyModManager.Models/AppState.cs | 6 +- .../Controls/MergeViewerControlViewModel.cs | 59 +++++++++++++++---- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/IronyModManager.Models.Common/IAppState.cs b/src/IronyModManager.Models.Common/IAppState.cs index eb514c67..23c01f44 100644 --- a/src/IronyModManager.Models.Common/IAppState.cs +++ b/src/IronyModManager.Models.Common/IAppState.cs @@ -82,10 +82,10 @@ public interface IAppState : IModel DateTime? LastWritableCheck { get; set; } /// - /// Gets or sets a value indicating whether the use advanced diff viewer. + /// Gets or sets a value indicating whether the use new diff viewer. /// - /// true if you use advanced diff viewer; otherwise, false. - bool UseAdvancedDiffViewer { get; set; } + /// true if use new diff viewer; otherwise, false. + bool UseNewDiffViewer { get; set; } #endregion Properties } diff --git a/src/IronyModManager.Models/AppState.cs b/src/IronyModManager.Models/AppState.cs index 0270f3d8..32002ae5 100644 --- a/src/IronyModManager.Models/AppState.cs +++ b/src/IronyModManager.Models/AppState.cs @@ -85,10 +85,10 @@ public class AppState : BaseModel, IAppState public virtual DateTime? LastWritableCheck { get; set; } /// - /// Gets or sets a value indicating whether the use advanced diff viewer. + /// Gets or sets a value indicating whether the use new diff viewer. /// - /// true if you use advanced diff viewer; otherwise, false. - public bool UseAdvancedDiffViewer { get; set; } + /// true if use new diff viewer; otherwise, false. + public bool UseNewDiffViewer { get; set; } #endregion Properties } diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index a0bf55e3..eff4c498 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -54,6 +54,7 @@ namespace IronyModManager.ViewModels.Controls /// Initializes a new instance of the class. [ExcludeFromCoverage("This should be tested via functional testing.")] public class MergeViewerControlViewModel( + IAppStateService appStateService, IScrollState scrollState, IModPatchCollectionService modPatchCollectionService, ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler, @@ -69,6 +70,11 @@ public class MergeViewerControlViewModel( /// private readonly IAppAction appAction = appAction; + /// + /// A private readonly IAppStateService named appStateService. + /// + private readonly IAppStateService appStateService = appStateService; + /// /// The external editor service /// @@ -229,9 +235,9 @@ public class MergeViewerControlViewModel( public virtual string CopyThisAfterLine { get; protected set; } /// - /// Gets or sets the take other then this command. + /// Gets or sets the copy this after line command. /// - /// The take other then this command. + /// The copy this after line command. public virtual ReactiveCommand CopyThisAfterLineCommand { get; protected set; } /// @@ -554,6 +560,12 @@ public class MergeViewerControlViewModel( /// The right side selected. public virtual IAvaloniaList RightSideSelected { get; set; } + /// + /// Gets or sets a value representing the toggle merge type command. + /// + /// The toggle merge type command. + public virtual ReactiveCommand ToggleMergeTypeCommand { get; protected set; } + /// /// Gets or sets the undo. /// @@ -567,6 +579,12 @@ public class MergeViewerControlViewModel( /// The undo command. public virtual ReactiveCommand UndoCommand { get; protected set; } + /// + /// Gets or sets a value indicating whether the using new merge type. + /// + /// true if using new merge type; otherwise, false. + public virtual bool UsingNewMergeType { get; protected set; } + #endregion Properties #region Methods @@ -593,6 +611,8 @@ public virtual void InitParameters() { EditorAvailable = true; } + + EvaluateMergeType(); } /// @@ -797,7 +817,6 @@ protected virtual void CopyAfterLines(IEnumerable selected, index = 0; break; } - else if (index > count) { index = count; @@ -870,7 +889,6 @@ protected virtual void CopyBeforeLines(IEnumerable selected, index = 0; break; } - else if (index > count) { index = count; @@ -905,7 +923,7 @@ protected virtual void CopyBeforeLines(IEnumerable selected, /// System.Collections.Generic.List<IronyModManager.ViewModels.Controls.MergeViewerControlViewModel.DiffPieceWithIndex>. protected virtual List CopyDiffPieceCollection(IEnumerable col) { - return new List(col); + return [.. col]; } /// @@ -948,6 +966,14 @@ protected virtual void DeleteLines(bool leftSide) } } + /// + /// Evaluates a merge type. + /// + protected virtual void EvaluateMergeType() + { + UsingNewMergeType = appStateService.Get().UseNewDiffViewer; + } + /// /// Finds the conflict. /// @@ -1281,9 +1307,17 @@ protected override void OnActivated(CompositeDisposable disposables) RedoCommand = ReactiveCommand.Create((bool leftSide) => { PerformRedo(leftSide); }).DisposeWith(disposables); - EditorCommand = ReactiveCommand.CreateFromTask((bool leftSide) => { return LaunchExternalEditor(leftSide); }).DisposeWith(disposables); + EditorCommand = ReactiveCommand.CreateFromTask((bool leftSide) => LaunchExternalEditorAsync(leftSide)).DisposeWith(disposables); + + ReadOnlyEditorCommand = ReactiveCommand.CreateFromTask((bool leftSide) => LaunchExternalEditorAsync(leftSide, true)).DisposeWith(disposables); - ReadOnlyEditorCommand = ReactiveCommand.CreateFromTask((bool leftSide) => { return LaunchExternalEditor(leftSide, true); }).DisposeWith(disposables); + ToggleMergeTypeCommand = ReactiveCommand.Create(() => + { + var state = appStateService.Get(); + state.UseNewDiffViewer = !state.UseNewDiffViewer; + appStateService.Save(state); + EvaluateMergeType(); + }).DisposeWith(disposables); var previousEditTextState = false; this.WhenAnyValue(v => v.EditingText).Subscribe(s => @@ -1418,11 +1452,11 @@ void performAction() case Enums.HotKeys.Ctrl_X: if (LeftSidePatchMod || RightSidePatchMod) { - LaunchExternalEditor(LeftSidePatchMod).ConfigureAwait(true); + LaunchExternalEditorAsync(LeftSidePatchMod).ConfigureAwait(true); } else { - LaunchExternalEditor(LeftSidePatchMod, true).ConfigureAwait(true); + LaunchExternalEditorAsync(LeftSidePatchMod, true).ConfigureAwait(true); } break; @@ -1564,7 +1598,8 @@ protected virtual async Task SyncSelectionsAsync(bool leftSide) /// /// if set to true [left side]. /// if set to true [no patch mod edit]. - private async Task LaunchExternalEditor(bool leftSide, bool noPatchModEdit = false) + /// A Task<System.Threading.Tasks.Task> representing the asynchronous operation. + private async Task LaunchExternalEditorAsync(bool leftSide, bool noPatchModEdit = false) { var opts = externalEditorService.Get(); var left = leftDefinition; @@ -1680,10 +1715,10 @@ public override bool Equals(object obj) } /// - /// Equalses the specified other. + /// Equals. /// /// The other. - /// bool. + /// A bool. public new bool Equals(DiffPiece other) { var result = base.Equals(other); From 18d2778c3e9d7052531b1f8b2d1660519797ec0e Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 20 Feb 2024 16:20:11 +0100 Subject: [PATCH 045/101] Main conflict solver resolve warnings --- src/IronyModManager.Common/Extensions.cs | 67 ++-- .../ViewModels/MainConflictSolverViewModel.cs | 328 +++++++++--------- .../Views/MainConflictSolverControlView.xaml | 2 +- 3 files changed, 207 insertions(+), 190 deletions(-) diff --git a/src/IronyModManager.Common/Extensions.cs b/src/IronyModManager.Common/Extensions.cs index 2659faaf..5490f2eb 100644 --- a/src/IronyModManager.Common/Extensions.cs +++ b/src/IronyModManager.Common/Extensions.cs @@ -4,17 +4,19 @@ // Created : 01-14-2020 // // Last Modified By : Mario -// Last Modified On : 11-29-2022 +// Last Modified On : 02-20-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Avalonia; @@ -25,6 +27,7 @@ using IronyModManager.DI; using IronyModManager.Platform.Configuration; using IronyModManager.Shared; +using ReactiveUI; namespace IronyModManager.Common { @@ -52,7 +55,23 @@ public static void EnsureTitlebarSpacing(this Window window) } /// - /// Safes the invoke. + /// Observables WhenAnyValue. + /// + /// The type of the TSender. + /// The type of the TRet. + /// The type of the T1. + /// The sender. + /// The Property. + /// The selector. + /// an IObservable with TRets . + public static IObservable ObservableWhenAnyValue(this TSender sender, Expression> property, Func selector) + { + var innerSelector = selector; + return sender.WhenAny(property, c1 => innerSelector(c1.Value)); + } + + /// + /// Safe invoke. /// /// The dispatcher. /// The action. @@ -64,12 +83,12 @@ public static void SafeInvoke(this Dispatcher dispatcher, Action action) } else { - dispatcher.InvokeAsync(() => action()); + dispatcher.InvokeAsync(action); } } /// - /// Safes the invoke asynchronous. + /// Safes invoke asynchronous. /// /// /// The dispatcher. @@ -83,12 +102,12 @@ public static Task SafeInvokeAsync(this Dispatcher dispatcher, Func action()); + return dispatcher.InvokeAsync(action); } } /// - /// Safes the invoke asynchronous. + /// Safes invoke asynchronous. /// /// The dispatcher. /// The action. @@ -101,7 +120,7 @@ public static async Task SafeInvokeAsync(this Dispatcher dispatcher, Action acti } else { - await dispatcher.InvokeAsync(() => action()); + await dispatcher.InvokeAsync(action); } } @@ -113,7 +132,7 @@ public static async Task SafeInvokeAsync(this Dispatcher dispatcher, Action acti /// IDisposable. public static IDisposable SubscribeObservable(this IObservable source) { - return ObservableExtensions.Subscribe(source); + return source.Subscribe(); } /// @@ -125,7 +144,7 @@ public static IDisposable SubscribeObservable(this IObservable source) /// IDisposable. public static IDisposable SubscribeObservable(this IObservable source, Action onNext) { - return ObservableExtensions.Subscribe(source, onNext); + return source.Subscribe(onNext); } /// @@ -139,7 +158,7 @@ public static IDisposable SubscribeObservable(this IObservable source, Act public static IDisposable SubscribeObservable(this IObservable source, Action onNext, Action onError) { // Seriously annoyed with conflicting namespaces - return ObservableExtensions.Subscribe(source, onNext, onError); + return source.Subscribe(onNext, onError); } /// @@ -153,7 +172,7 @@ public static IDisposable SubscribeObservable(this IObservable source, Act public static IDisposable SubscribeObservable(this IObservable source, Action onNext, Action onCompleted) { // Seriously annoyed with conflicting namespaces - return ObservableExtensions.Subscribe(source, onNext, onCompleted); + return source.Subscribe(onNext, onCompleted); } /// @@ -167,7 +186,7 @@ public static IDisposable SubscribeObservable(this IObservable source, Act /// IDisposable. public static IDisposable SubscribeObservable(this IObservable source, Action onNext, Action onError, Action onCompleted) { - return ObservableExtensions.Subscribe(source, onNext, onError, onCompleted); + return source.Subscribe(onNext, onError, onCompleted); } /// @@ -179,7 +198,7 @@ public static IDisposable SubscribeObservable(this IObservable source, Act /// The token. public static void SubscribeObservable(this IObservable source, IObserver observer, CancellationToken token) { - ObservableExtensions.Subscribe(source, observer, token); + source.Subscribe(observer, token); } /// @@ -190,7 +209,7 @@ public static void SubscribeObservable(this IObservable source, IObserver< /// The token. public static void SubscribeObservable(this IObservable source, CancellationToken token) { - ObservableExtensions.Subscribe(source, token); + source.Subscribe(token); } /// @@ -202,7 +221,7 @@ public static void SubscribeObservable(this IObservable source, Cancellati /// The token. public static void SubscribeObservable(this IObservable source, Action onNext, CancellationToken token) { - ObservableExtensions.Subscribe(source, onNext, token); + source.Subscribe(onNext, token); } /// @@ -215,7 +234,7 @@ public static void SubscribeObservable(this IObservable source, Action /// The token. public static void SubscribeObservable(this IObservable source, Action onNext, Action onError, CancellationToken token) { - ObservableExtensions.Subscribe(source, onNext, onError, token); + source.Subscribe(onNext, onError, token); } /// @@ -228,7 +247,7 @@ public static void SubscribeObservable(this IObservable source, Action /// The token. public static void SubscribeObservable(this IObservable source, Action onNext, Action onCompleted, CancellationToken token) { - ObservableExtensions.Subscribe(source, onNext, onCompleted, token); + source.Subscribe(onNext, onCompleted, token); } /// @@ -242,7 +261,7 @@ public static void SubscribeObservable(this IObservable source, Action /// The token. public static void SubscribeObservable(this IObservable source, Action onNext, Action onError, Action onCompleted, CancellationToken token) { - ObservableExtensions.Subscribe(source, onNext, onError, onCompleted, token); + source.Subscribe(onNext, onError, onCompleted, token); } /// @@ -254,11 +273,11 @@ public static void SubscribeObservable(this IObservable source, Action /// IDisposable. public static IDisposable SubscribeObservableSafe(this IObservable source, IObserver observer) { - return ObservableExtensions.SubscribeSafe(source, observer); + return source.SubscribeSafe(observer); } /// - /// Converts to avalonialist. + /// Converts to AvaloniaList. /// /// /// The col. @@ -269,7 +288,7 @@ public static AvaloniaList ToAvaloniaList(this IEnumerable col) } /// - /// Converts to localizedpercentage. + /// Converts to LocalizedPercentage. /// /// The number. /// System.String. @@ -279,7 +298,7 @@ public static string ToLocalizedPercentage(this int number) } /// - /// Converts to localizedpercentage. + /// Converts to LocalizedPercentage. /// /// The number. /// System.String. @@ -289,7 +308,7 @@ public static string ToLocalizedPercentage(this double number) } /// - /// Converts to observablecollection. + /// Converts to ObservableCollection. /// /// /// The col. @@ -300,7 +319,7 @@ public static ObservableCollection ToObservableCollection(this IEnumerable } /// - /// Converts to sourcelist. + /// Converts to SourceList. /// /// /// The col. diff --git a/src/IronyModManager/ViewModels/MainConflictSolverViewModel.cs b/src/IronyModManager/ViewModels/MainConflictSolverViewModel.cs index 6566eca4..931554d7 100644 --- a/src/IronyModManager/ViewModels/MainConflictSolverViewModel.cs +++ b/src/IronyModManager/ViewModels/MainConflictSolverViewModel.cs @@ -1,17 +1,17 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 03-18-2020 // // Last Modified By : Mario -// Last Modified On : 06-26-2023 +// Last Modified On : 02-20-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.IO; @@ -42,14 +42,46 @@ namespace IronyModManager.ViewModels { - /// /// Class MainConflictSolverControlViewModel. /// Implements the /// /// + /// The external process handler service. + /// The hotkey pressed handler. + /// The identifier generator. + /// The mod patch collection service. + /// The localization manager. + /// The merge viewer. + /// The binary merge viewer. + /// The mod compare selector. + /// The ignore conflicts rules. + /// The mod filter. + /// The reset conflicts. + /// The database search. + /// The custom conflicts. + /// The logger. + /// The notification action. + /// The application action. + /// Initializes a new instance of the class. [ExcludeFromCoverage("This should be tested via functional testing.")] - public class MainConflictSolverControlViewModel : BaseViewModel + public class MainConflictSolverControlViewModel( + IExternalProcessHandlerService externalProcessHandlerService, + ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler, + IIDGenerator idGenerator, + IModPatchCollectionService modPatchCollectionService, + ILocalizationManager localizationManager, + MergeViewerControlViewModel mergeViewer, + MergeViewerBinaryControlViewModel binaryMergeViewer, + ModCompareSelectorControlViewModel modCompareSelector, + ModConflictIgnoreControlViewModel ignoreConflictsRules, + ConflictSolverModFilterControlViewModel modFilter, + ConflictSolverResetConflictsControlViewModel resetConflicts, + ConflictSolverDBSearchControlViewModel dbSearch, + ConflictSolverCustomConflictsControlViewModel customConflicts, + ILogger logger, + INotificationAction notificationAction, + IAppAction appAction) : BaseViewModel { #region Fields @@ -61,47 +93,47 @@ public class MainConflictSolverControlViewModel : BaseViewModel /// /// The localization directory /// - private static readonly string LocalizationDirectory = $"{Shared.Constants.LocalizationDirectory}{Path.DirectorySeparatorChar}"; + private static readonly string localizationDirectory = $"{Shared.Constants.LocalizationDirectory}{Path.DirectorySeparatorChar}"; /// /// The application action /// - private readonly IAppAction appAction; + private readonly IAppAction appAction = appAction; /// /// The external process handler service /// - private readonly IExternalProcessHandlerService externalProcessHandlerService; + private readonly IExternalProcessHandlerService externalProcessHandlerService = externalProcessHandlerService; /// /// The hotkey pressed handler /// - private readonly ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler; + private readonly ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler = hotkeyPressedHandler; /// /// The identifier generator /// - private readonly IIDGenerator idGenerator; + private readonly IIDGenerator idGenerator = idGenerator; /// /// The localization manager /// - private readonly ILocalizationManager localizationManager; + private readonly ILocalizationManager localizationManager = localizationManager; /// /// The logger /// - private readonly ILogger logger; + private readonly ILogger logger = logger; /// /// The mod patch collection service /// - private readonly IModPatchCollectionService modPatchCollectionService; + private readonly IModPatchCollectionService modPatchCollectionService = modPatchCollectionService; /// /// The notification action /// - private readonly INotificationAction notificationAction; + private readonly INotificationAction notificationAction = notificationAction; /// /// The cached invalids @@ -111,7 +143,7 @@ public class MainConflictSolverControlViewModel : BaseViewModel /// /// The filtering conflicts /// - private bool filteringConflicts = false; + private bool filteringConflicts; /// /// The invalids checked @@ -121,60 +153,10 @@ public class MainConflictSolverControlViewModel : BaseViewModel /// /// The take left binary /// - private bool takeLeftBinary = false; + private bool takeLeftBinary; #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The external process handler service. - /// The hotkey pressed handler. - /// The identifier generator. - /// The mod patch collection service. - /// The localization manager. - /// The merge viewer. - /// The binary merge viewer. - /// The mod compare selector. - /// The ignore conflicts rules. - /// The mod filter. - /// The reset conflicts. - /// The database search. - /// The custom conflicts. - /// The logger. - /// The notification action. - /// The application action. - public MainConflictSolverControlViewModel(IExternalProcessHandlerService externalProcessHandlerService, - ConflictSolverViewHotkeyPressedHandler hotkeyPressedHandler, IIDGenerator idGenerator, - IModPatchCollectionService modPatchCollectionService, ILocalizationManager localizationManager, - MergeViewerControlViewModel mergeViewer, MergeViewerBinaryControlViewModel binaryMergeViewer, - ModCompareSelectorControlViewModel modCompareSelector, ModConflictIgnoreControlViewModel ignoreConflictsRules, - ConflictSolverModFilterControlViewModel modFilter, ConflictSolverResetConflictsControlViewModel resetConflicts, - ConflictSolverDBSearchControlViewModel dbSearch, ConflictSolverCustomConflictsControlViewModel customConflicts, - ILogger logger, INotificationAction notificationAction, IAppAction appAction) - { - this.idGenerator = idGenerator; - this.modPatchCollectionService = modPatchCollectionService; - this.localizationManager = localizationManager; - this.logger = logger; - this.notificationAction = notificationAction; - this.appAction = appAction; - this.hotkeyPressedHandler = hotkeyPressedHandler; - this.externalProcessHandlerService = externalProcessHandlerService; - MergeViewer = mergeViewer; - ModCompareSelector = modCompareSelector; - BinaryMergeViewer = binaryMergeViewer; - IgnoreConflictsRules = ignoreConflictsRules; - ModFilter = modFilter; - ResetConflicts = resetConflicts; - DatabaseSearch = dbSearch; - CustomConflicts = customConflicts; - } - - #endregion Constructors - #region Properties /// @@ -195,11 +177,12 @@ public MainConflictSolverControlViewModel(IExternalProcessHandlerService externa /// /// The back command. public virtual ReactiveCommand BackCommand { get; set; } + /// /// Gets or sets the binary merge viewer. /// /// The binary merge viewer. - public virtual MergeViewerBinaryControlViewModel BinaryMergeViewer { get; protected set; } + public virtual MergeViewerBinaryControlViewModel BinaryMergeViewer { get; protected set; } = binaryMergeViewer; /// /// Gets or sets the conflicted objects. @@ -224,13 +207,13 @@ public MainConflictSolverControlViewModel(IExternalProcessHandlerService externa /// Gets or sets the custom conflicts. /// /// The custom conflicts. - public virtual ConflictSolverCustomConflictsControlViewModel CustomConflicts { get; protected set; } + public virtual ConflictSolverCustomConflictsControlViewModel CustomConflicts { get; protected set; } = customConflicts; /// /// Gets or sets the database search. /// /// The database search. - public virtual ConflictSolverDBSearchControlViewModel DatabaseSearch { get; protected set; } + public virtual ConflictSolverDBSearchControlViewModel DatabaseSearch { get; protected set; } = dbSearch; /// /// Gets or sets a value indicating whether [editing ignore conflicts rules]. @@ -239,15 +222,15 @@ public MainConflictSolverControlViewModel(IExternalProcessHandlerService externa public virtual bool EditingIgnoreConflictsRules { get; protected set; } /// - /// Gets or sets the hierarchal conflicts. + /// Gets or sets the hierarchical conflicts. /// - /// The hierarchal conflicts. - public virtual AvaloniaList HierarchalConflicts { get; protected set; } + /// The hierarchical conflicts. + public virtual AvaloniaList HierarchicalConflicts { get; protected set; } /// - /// Gets or sets the ignore. + /// Gets or sets ignore. /// - /// The ignore. + /// ignore. [StaticLocalization(LocalizationResources.Conflict_Solver.Ignore)] public virtual string Ignore { get; protected set; } @@ -261,7 +244,7 @@ public MainConflictSolverControlViewModel(IExternalProcessHandlerService externa /// Gets or sets the ignore conflicts rules. /// /// The ignore conflicts rules. - public virtual ModConflictIgnoreControlViewModel IgnoreConflictsRules { get; protected set; } + public virtual ModConflictIgnoreControlViewModel IgnoreConflictsRules { get; protected set; } = ignoreConflictsRules; /// /// Gets or sets a value indicating whether [ignore enabled]. @@ -369,19 +352,19 @@ public MainConflictSolverControlViewModel(IExternalProcessHandlerService externa /// Gets or sets the merge viewer. /// /// The merge viewer. - public virtual MergeViewerControlViewModel MergeViewer { get; protected set; } + public virtual MergeViewerControlViewModel MergeViewer { get; protected set; } = mergeViewer; /// /// Gets or sets the mod compare selector. /// /// The mod compare selector. - public virtual ModCompareSelectorControlViewModel ModCompareSelector { get; protected set; } + public virtual ModCompareSelectorControlViewModel ModCompareSelector { get; protected set; } = modCompareSelector; /// /// Gets or sets the mod filter. /// /// The mod filter. - public virtual ConflictSolverModFilterControlViewModel ModFilter { get; protected set; } + public virtual ConflictSolverModFilterControlViewModel ModFilter { get; protected set; } = modFilter; /// /// Gets or sets the number of conflicts caption. @@ -405,7 +388,7 @@ public MainConflictSolverControlViewModel(IExternalProcessHandlerService externa /// Gets or sets the reset conflicts. /// /// The reset conflicts. - public virtual ConflictSolverResetConflictsControlViewModel ResetConflicts { get; protected set; } + public virtual ConflictSolverResetConflictsControlViewModel ResetConflicts { get; protected set; } = resetConflicts; /// /// Gets or sets the reset conflicts column. @@ -513,13 +496,11 @@ public async Task InitializeAsync(bool readOnly) case ResetType.Ignored: sbIgnored.AppendLine(IronyFormatter.Format(modListFormat, new { ParentDirectory = conflict.Name, child.Name })); break; - - default: - break; } } } - var msgFormat = string.Empty; + + string msgFormat; if (readOnly) { if (sbIgnored.Length > 0 && sbResolved.Length > 0) @@ -550,12 +531,8 @@ public async Task InitializeAsync(bool readOnly) msgFormat = localizationManager.GetResource(LocalizationResources.Conflict_Solver.ResetWarning.RegularModeIgnoredOnly); } } - var msg = IronyFormatter.Format(msgFormat, new - { - Environment.NewLine, - ListOfConflictsResolved = sbResolved.ToString(), - ListOfConflictsIgnored = sbIgnored.ToString() - }); + + var msg = IronyFormatter.Format(msgFormat, new { Environment.NewLine, ListOfConflictsResolved = sbResolved.ToString(), ListOfConflictsIgnored = sbIgnored.ToString() }); var title = localizationManager.GetResource(LocalizationResources.Conflict_Solver.ResetWarning.Title); Dispatcher.UIThread.SafeInvoke(() => notificationAction.ShowPromptAsync(title, title, msg, NotificationType.Warning, PromptType.OK)); } @@ -610,17 +587,22 @@ protected virtual async Task EvaluateDefinitionValidity() patchDefinition.UseSimpleValidation = true; } } + var validationResult = modPatchCollectionService.Validate(patchDefinition); if (!validationResult.IsValid) { var title = localizationManager.GetResource(LocalizationResources.Conflict_Solver.ResolutionSaveError.Title); - var message = localizationManager.GetResource(validationResult.ErrorLine.HasValue ? LocalizationResources.Conflict_Solver.ResolutionSaveError.MessageLine : LocalizationResources.Conflict_Solver.ResolutionSaveError.MessageNoLine); - await Dispatcher.UIThread.SafeInvokeAsync(async () => await notificationAction.ShowPromptAsync(title, title, message.FormatIronySmart(new { Environment.NewLine, validationResult.ErrorMessage, Line = validationResult.ErrorLine, Column = validationResult.ErrorColumn }), NotificationType.Error, PromptType.OK)); + var message = localizationManager.GetResource(validationResult.ErrorLine.HasValue + ? LocalizationResources.Conflict_Solver.ResolutionSaveError.MessageLine + : LocalizationResources.Conflict_Solver.ResolutionSaveError.MessageNoLine); + await Dispatcher.UIThread.SafeInvokeAsync(async () => await notificationAction.ShowPromptAsync(title, title, + message.FormatIronySmart(new { Environment.NewLine, validationResult.ErrorMessage, Line = validationResult.ErrorLine, Column = validationResult.ErrorColumn }), NotificationType.Error, PromptType.OK)); } else { await Dispatcher.UIThread.SafeInvokeAsync(async () => await ResolveConflictAsync(true).ConfigureAwait(true)); } + BackAllowed = true; } @@ -646,6 +628,7 @@ protected virtual async Task FilterHierarchalConflictsAsync(IConflictResult conf { await Task.Delay(25); } + filteringConflicts = true; var index = PreviousConflictIndex; PreviousConflictIndex = null; @@ -658,25 +641,29 @@ protected virtual async Task FilterHierarchalConflictsAsync(IConflictResult conf { resolved.AddRange(conflictResult.ResolvedConflicts.GetHierarchicalDefinitions()); } + if (conflictResult.IgnoredConflicts != null) { resolved.AddRange(conflictResult.IgnoredConflicts.GetHierarchicalDefinitions()); } + if (conflictResult.RuleIgnoredConflicts != null) { resolved.AddRange(conflictResult.RuleIgnoredConflicts.GetHierarchicalDefinitions()); } + foreach (var topLevelResolvedConflicts in resolved) { IEnumerable topLevelConflicts; - if (topLevelResolvedConflicts.Name.StartsWith(LocalizationDirectory, StringComparison.OrdinalIgnoreCase)) + if (topLevelResolvedConflicts.Name.StartsWith(localizationDirectory, StringComparison.OrdinalIgnoreCase)) { - topLevelConflicts = conflicts.Where(p => p.Name.StartsWith(LocalizationDirectory, StringComparison.OrdinalIgnoreCase)); + topLevelConflicts = conflicts.Where(p => p.Name.StartsWith(localizationDirectory, StringComparison.OrdinalIgnoreCase)); } else { topLevelConflicts = conflicts.Where(p => p.Name.Equals(topLevelResolvedConflicts.Name)); } + if (topLevelConflicts.Any()) { foreach (var topLevelConflict in topLevelConflicts) @@ -692,6 +679,7 @@ protected virtual async Task FilterHierarchalConflictsAsync(IConflictResult conf } } } + conflicts.RemoveAll(conflicts.Where(p => p.Children == null || p.Children.Count == 0).ToList()); if (!invalidsChecked) { @@ -707,9 +695,9 @@ protected virtual async Task FilterHierarchalConflictsAsync(IConflictResult conf { var invalidChild = DIResolver.Get(); invalidChild.Name = item.File; - var message = item.ErrorColumn.HasValue || item.ErrorLine.HasValue ? - localizationManager.GetResource(LocalizationResources.Conflict_Solver.InvalidConflicts.Error) : - localizationManager.GetResource(LocalizationResources.Conflict_Solver.InvalidConflicts.ErrorNoLine); + var message = item.ErrorColumn.HasValue || item.ErrorLine.HasValue + ? localizationManager.GetResource(LocalizationResources.Conflict_Solver.InvalidConflicts.Error) + : localizationManager.GetResource(LocalizationResources.Conflict_Solver.InvalidConflicts.ErrorNoLine); invalidChild.Key = IronyFormatter.Format(message, new { item.ModName, @@ -722,28 +710,33 @@ protected virtual async Task FilterHierarchalConflictsAsync(IConflictResult conf invalidChild.AdditionalData = item; children.Add(invalidChild); } + invalidDef.Children = children; cachedInvalids = invalidDef; conflicts.Add(invalidDef); } } + if (cachedInvalids != null && !conflicts.Any(p => p.Key == cachedInvalids.Key)) { conflicts.Add(cachedInvalids); } + var selectedParentConflict = SelectedParentConflict; - HierarchalConflicts = conflicts; - NumberOfConflictsCaption = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Conflict_Solver.ConflictCount), new { Count = conflicts.Where(p => p.Key != InvalidKey).SelectMany(p => p.Children).Count() }); + HierarchicalConflicts = conflicts; + NumberOfConflictsCaption = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Conflict_Solver.ConflictCount), + new { Count = conflicts.Where(p => p.Key != InvalidKey).SelectMany(p => p.Children).Count() }); int? previousConflictIndex = null; - if (HierarchalConflicts.Any() && selectedParentConflict == null) + if (HierarchicalConflicts.Count != 0 && selectedParentConflict == null) { - selectedParentConflict = HierarchalConflicts.FirstOrDefault(); + selectedParentConflict = HierarchicalConflicts.FirstOrDefault(); } + if (selectedParentConflict != null) { var conflictName = selectedParentConflict.Name; selectedParentConflict = null; - var newSelected = HierarchalConflicts.FirstOrDefault(p => p.Name.Equals(conflictName)); + var newSelected = HierarchicalConflicts.FirstOrDefault(p => p.Name.Equals(conflictName)); if (newSelected != null) { previousConflictIndex = index; @@ -755,20 +748,24 @@ protected virtual async Task FilterHierarchalConflictsAsync(IConflictResult conf previousConflictIndex = newSelected.Children.ToList().IndexOf(overrideMatch); } } - if (previousConflictIndex.GetValueOrDefault() > (newSelected.Children.Count - 1)) + + if (previousConflictIndex.GetValueOrDefault() > newSelected.Children.Count - 1) { previousConflictIndex = newSelected.Children.Count - 1; } + selectedParentConflict = newSelected; } } + PreviousConflictIndex = previousConflictIndex; SelectedParentConflict = selectedParentConflict; } else { - HierarchalConflicts = null; + HierarchicalConflicts = null; } + filteringConflicts = false; } @@ -779,7 +776,7 @@ protected virtual async Task FilterHierarchalConflictsAsync(IConflictResult conf protected override void OnActivated(CompositeDisposable disposables) { var resolvingEnabled = this.WhenAnyValue(v => v.ResolvingConflict, v => !v); - var backAllowed = this.WhenAnyValue(v => v.BackAllowed, v => v == true); + var backAllowed = this.ObservableWhenAnyValue(v => v.BackAllowed, v => v); BackCommand = ReactiveCommand.CreateFromTask(async () => { @@ -795,10 +792,7 @@ protected override void OnActivated(CompositeDisposable disposables) SelectedModCollection = null; cachedInvalids = null; invalidsChecked = false; - var args = new NavigationEventArgs() - { - State = NavigationState.Main - }; + var args = new NavigationEventArgs { State = NavigationState.Main }; ReactiveUI.MessageBus.Current.SendMessage(args); BackAllowed = true; await TriggerOverlayAsync(id, false); @@ -807,10 +801,7 @@ protected override void OnActivated(CompositeDisposable disposables) GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); }, backAllowed).DisposeWith(disposables); - ResolveCommand = ReactiveCommand.CreateFromTask(() => - { - return EvaluateDefinitionValidity(); - }, resolvingEnabled).DisposeWith(disposables); + ResolveCommand = ReactiveCommand.CreateFromTask(EvaluateDefinitionValidity, resolvingEnabled).DisposeWith(disposables); IgnoreCommand = ReactiveCommand.Create(() => { @@ -844,13 +835,13 @@ protected override void OnActivated(CompositeDisposable disposables) MergeViewer.InitParameters(); if (ModFilter.IsActivated) { - ModFilter.SetConflictResult(Conflicts, SelectedModsOrder.ToList(), SelectedModCollection.Name); + ModFilter.SetConflictResult(Conflicts, [.. SelectedModsOrder], SelectedModCollection.Name); } }).DisposeWith(disposables); this.WhenAnyValue(v => v.SelectedParentConflict).Subscribe(s => { - IsConflictSolverAvailable = !(s?.Key == InvalidKey); + IsConflictSolverAvailable = s?.Key != InvalidKey; EvalViewerVisibility(); }).DisposeWith(disposables); @@ -874,18 +865,19 @@ protected override void OnActivated(CompositeDisposable disposables) } else { - if (HierarchalConflicts == null || !HierarchalConflicts.Any()) + if (HierarchicalConflicts == null || HierarchicalConflicts.Count == 0) { ModCompareSelector.Reset(); BinaryMergeViewer.Reset(false); MergeViewer.SetText(string.Empty, string.Empty, true, lockScroll: true); } + PreviousConflictIndex = null; IgnoreEnabled = false; } }).DisposeWith(disposables); - this.WhenAnyValue(v => v.ModCompareSelector.IsActivated).Where(p => p).Subscribe(s => + this.WhenAnyValue(v => v.ModCompareSelector.IsActivated).Where(p => p).Subscribe(_ => { this.WhenAnyValue(v => v.ModCompareSelector.DefinitionSelection).Subscribe(s => { @@ -899,17 +891,17 @@ protected override void OnActivated(CompositeDisposable disposables) if (!IsBinaryConflict) { BinaryMergeViewer.EnableSelection = ResolveEnabled = s.LeftSelectedDefinition != null && - s.RightSelectedDefinition != null && - s.LeftSelectedDefinition != s.RightSelectedDefinition && - (modPatchCollectionService.IsPatchMod(s.LeftSelectedDefinition.ModName) || modPatchCollectionService.IsPatchMod(s.RightSelectedDefinition.ModName)); + s.RightSelectedDefinition != null && + s.LeftSelectedDefinition != s.RightSelectedDefinition && + (modPatchCollectionService.IsPatchMod(s.LeftSelectedDefinition.ModName) || modPatchCollectionService.IsPatchMod(s.RightSelectedDefinition.ModName)); } else { BinaryMergeViewer.Reset(false); ResolveEnabled = false; BinaryMergeViewer.EnableSelection = s.LeftSelectedDefinition != null && - s.RightSelectedDefinition != null && - s.LeftSelectedDefinition != s.RightSelectedDefinition; + s.RightSelectedDefinition != null && + s.LeftSelectedDefinition != s.RightSelectedDefinition; BinaryMergeViewer.SetLeft(s.LeftSelectedDefinition); BinaryMergeViewer.SetRight(s.RightSelectedDefinition); } @@ -922,16 +914,16 @@ protected override void OnActivated(CompositeDisposable disposables) }).DisposeWith(disposables); }).DisposeWith(disposables); - this.WhenAnyValue(v => v.BinaryMergeViewer.IsActivated).Where(p => p).Subscribe(s => + this.WhenAnyValue(v => v.BinaryMergeViewer.IsActivated).Where(p => p).Subscribe(_ => { - Observable.Merge(BinaryMergeViewer.TakeLeftCommand.Select(s => true), BinaryMergeViewer.TakeRightCommand.Select(s => false)).Subscribe(s => + BinaryMergeViewer.TakeLeftCommand.Select(_ => true).Merge(BinaryMergeViewer.TakeRightCommand.Select(_ => false)).Subscribe(s => { takeLeftBinary = s; ResolveEnabled = true; }).DisposeWith(disposables); }).DisposeWith(disposables); - this.WhenAnyValue(p => p.MergeViewer.LeftSide).Where(p => !string.IsNullOrWhiteSpace(p)).Subscribe(s => + this.WhenAnyValue(p => p.MergeViewer.LeftSide).Where(p => !string.IsNullOrWhiteSpace(p)).Subscribe(_ => { var virtualDefinitions = ModCompareSelector.VirtualDefinitions; if (MergeViewer.LeftSidePatchMod && virtualDefinitions != null) @@ -941,7 +933,7 @@ protected override void OnActivated(CompositeDisposable disposables) } }).DisposeWith(disposables); - this.WhenAnyValue(p => p.MergeViewer.RightSide).Where(p => !string.IsNullOrWhiteSpace(p)).Subscribe(s => + this.WhenAnyValue(p => p.MergeViewer.RightSide).Where(p => !string.IsNullOrWhiteSpace(p)).Subscribe(_ => { var virtualDefinitions = ModCompareSelector.VirtualDefinitions; if (MergeViewer.RightSidePatchMod && virtualDefinitions != null) @@ -951,9 +943,9 @@ protected override void OnActivated(CompositeDisposable disposables) } }).DisposeWith(disposables); - this.WhenAnyValue(v => v.IgnoreConflictsRules.IsActivated).Where(p => p).Subscribe(s => + this.WhenAnyValue(v => v.IgnoreConflictsRules.IsActivated).Where(p => p).Subscribe(_ => { - Observable.Merge(IgnoreConflictsRules.SaveCommand, IgnoreConflictsRules.CancelCommand).Subscribe(result => + IgnoreConflictsRules.SaveCommand.Merge(IgnoreConflictsRules.CancelCommand).Subscribe(result => { switch (result.State) { @@ -965,9 +957,6 @@ protected override void OnActivated(CompositeDisposable disposables) case Implementation.CommandState.NotExecuted: EditingIgnoreConflictsRules = false; break; - - default: - break; } }).DisposeWith(disposables); }).DisposeWith(disposables); @@ -985,16 +974,16 @@ protected override void OnActivated(CompositeDisposable disposables) } }).DisposeWith(disposables); - this.WhenAnyValue(p => p.ModFilter.IsActivated).Where(p => p).Subscribe(s => + this.WhenAnyValue(p => p.ModFilter.IsActivated).Where(p => p).Subscribe(_ => { - ModFilter.SetConflictResult(Conflicts, SelectedModsOrder.ToList(), SelectedModCollection.Name); - this.WhenAnyValue(p => p.ModFilter.HasSavedState).Where(p => p).Subscribe(s => + ModFilter.SetConflictResult(Conflicts, [.. SelectedModsOrder], SelectedModCollection.Name); + this.WhenAnyValue(p => p.ModFilter.HasSavedState).Where(p => p).Subscribe(_ => { FilterHierarchalConflictsAsync(Conflicts, SelectedConflict).ConfigureAwait(false); }).DisposeWith(disposables); }).DisposeWith(disposables); - this.WhenAnyValue(p => p.ResetConflicts.IsActivated).Where(p => p).Subscribe(s => + this.WhenAnyValue(p => p.ResetConflicts.IsActivated).Where(p => p).Subscribe(_ => { ResetConflicts.ResetCommand.Subscribe(s => { @@ -1005,28 +994,19 @@ protected override void OnActivated(CompositeDisposable disposables) }).DisposeWith(disposables); }).DisposeWith(disposables); - this.WhenAnyValue(p => p.CustomConflicts.Saved).Where(p => p).Subscribe(s => + this.WhenAnyValue(p => p.CustomConflicts.Saved).Where(p => p).Subscribe(_ => { ResetConflicts.Refresh(); }).DisposeWith(disposables); - var previousEditTextState = false; - this.WhenAnyValue(v => v.EditingIgnoreConflictsRules).Subscribe(s => - { - if (s != previousEditTextState) - { - previousEditTextState = s; - } - }).DisposeWith(disposables); - hotkeyPressedHandler.Subscribe(m => { async Task performModSelectionAction() { - if (!filteringConflicts && (SelectedParentConflict?.Children.Any()).GetValueOrDefault()) + if (!filteringConflicts && SelectedParentConflict?.Children.Count != 0) { int? newSelectedConflict = null; - var col = SelectedParentConflict != null ? SelectedParentConflict.Children.ToList() : new List(); + var col = SelectedParentConflict != null ? [.. SelectedParentConflict.Children] : new List(); switch (m.Hotkey) { case Enums.HotKeys.Shift_Up: @@ -1042,8 +1022,10 @@ async Task performModSelectionAction() { index = 0; } + newSelectedConflict = index; } + break; case Enums.HotKeys.Shift_Down: @@ -1059,13 +1041,13 @@ async Task performModSelectionAction() { index = col.Count - 1; } + newSelectedConflict = index; } - break; - default: break; } + if (newSelectedConflict != null) { await Dispatcher.UIThread.SafeInvokeAsync(() => @@ -1075,12 +1057,13 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => } } } + async Task performModParentSelectionAction() { - if (!filteringConflicts && (HierarchalConflicts?.Any()).GetValueOrDefault()) + if (!filteringConflicts && HierarchicalConflicts?.Count != 0) { IHierarchicalDefinitions parent = null; - var col = HierarchalConflicts.ToList(); + var col = HierarchicalConflicts!.ToList(); switch (m.Hotkey) { case Enums.HotKeys.Ctrl_Shift_P: @@ -1096,8 +1079,10 @@ async Task performModParentSelectionAction() { index = 0; } + parent = col[index]; } + break; case Enums.HotKeys.Ctrl_Shift_N: @@ -1113,13 +1098,13 @@ async Task performModParentSelectionAction() { index = col.Count - 1; } + parent = col[index]; } - break; - default: break; } + if (parent != null) { await Dispatcher.UIThread.SafeInvokeAsync(() => @@ -1129,6 +1114,7 @@ await Dispatcher.UIThread.SafeInvokeAsync(() => } } } + if (m.Hotkey == Enums.HotKeys.Shift_Down || m.Hotkey == Enums.HotKeys.Shift_Up) { performModSelectionAction().ConfigureAwait(false); @@ -1168,6 +1154,7 @@ protected virtual async Task ResolveConflictAsync(bool resolve) { return; } + if (await externalProcessHandlerService.IsParadoxLauncherRunningAsync()) { var title = localizationManager.GetResource(LocalizationResources.Notifications.ParadoxLauncherRunning.Title); @@ -1175,13 +1162,14 @@ protected virtual async Task ResolveConflictAsync(bool resolve) notificationAction.ShowNotification(title, message, NotificationType.Error, 30); return; } + ResolvingConflict = true; if (ModCompareSelector.VirtualDefinitions != null && ModCompareSelector.VirtualDefinitions.Any()) { IHierarchicalDefinitions conflictParent = null; int? conflictParentIdx = null; - int parentIdx = HierarchalConflicts.ToList().IndexOf(SelectedParentConflict); - foreach (var item in HierarchalConflicts) + var parentIdx = HierarchicalConflicts.ToList().IndexOf(SelectedParentConflict); + foreach (var item in HierarchicalConflicts) { if (item.Children.Contains(SelectedConflict)) { @@ -1191,9 +1179,10 @@ protected virtual async Task ResolveConflictAsync(bool resolve) break; } } + var id = idGenerator.GetNextId(); await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Conflict_Solver.OverlayResolve)); - IDefinition patchDefinition = null; + IDefinition patchDefinition; if (!IsBinaryConflict) { patchDefinition = ModCompareSelector.VirtualDefinitions.FirstOrDefault(p => modPatchCollectionService.IsPatchMod(p.ModName)); @@ -1209,6 +1198,7 @@ protected virtual async Task ResolveConflictAsync(bool resolve) patchDefinition = ModCompareSelector.Definitions.FirstOrDefault(); } } + if (patchDefinition != null) { var generatedFileNames = patchDefinition.GeneratedFileNames; @@ -1219,23 +1209,25 @@ protected virtual async Task ResolveConflictAsync(bool resolve) generatedFileNames.Add(item); } } + patchDefinition.GeneratedFileNames = generatedFileNames; SyncCode(patchDefinition); try { - if (await Task.Run(async () => resolve ? - await modPatchCollectionService.ApplyModPatchAsync(Conflicts, patchDefinition, SelectedModCollection.Name) : - await modPatchCollectionService.IgnoreModPatchAsync(Conflicts, patchDefinition, SelectedModCollection.Name))) + if (await Task.Run(async () => + resolve + ? await modPatchCollectionService.ApplyModPatchAsync(Conflicts, patchDefinition, SelectedModCollection.Name) + : await modPatchCollectionService.IgnoreModPatchAsync(Conflicts, patchDefinition, SelectedModCollection.Name))) { await FilterHierarchalConflictsAsync(Conflicts); IHierarchicalDefinitions selectedConflict = null; - if (conflictParentIdx.HasValue && HierarchalConflicts.Any()) + if (conflictParentIdx.HasValue && HierarchicalConflicts.Count != 0) { - foreach (var item in HierarchalConflicts) + foreach (var item in HierarchicalConflicts) { if (item.Name.Equals(conflictParent.Name)) { - if (conflictParentIdx.Value > (item.Children.Count - 1)) + if (conflictParentIdx.Value > item.Children.Count - 1) { conflictParentIdx = item.Children.Count - 1; } @@ -1243,11 +1235,13 @@ await modPatchCollectionService.IgnoreModPatchAsync(Conflicts, patchDefinition, { conflictParentIdx = 0; } + selectedConflict = item.Children.Select(p => p).ToList()[conflictParentIdx.GetValueOrDefault()]; break; } } } + SelectedConflict = selectedConflict; ResetConflicts.Refresh(); } @@ -1260,25 +1254,29 @@ await modPatchCollectionService.IgnoreModPatchAsync(Conflicts, patchDefinition, notificationAction.ShowNotification(title, message, NotificationType.Error, 30); } } + if (SelectedConflict == null) { - if (parentIdx > (HierarchalConflicts.Count - 1)) + if (parentIdx > HierarchicalConflicts.Count - 1) { - parentIdx = HierarchalConflicts.Count - 1; + parentIdx = HierarchicalConflicts.Count - 1; } else if (parentIdx < 0) { parentIdx = 0; } - if (HierarchalConflicts.Any()) + + if (HierarchicalConflicts.Count != 0) { // Force a refresh of the UI SelectedParentConflict = null; - SelectedParentConflict = HierarchalConflicts.ElementAt(parentIdx); + SelectedParentConflict = HierarchicalConflicts.ElementAt(parentIdx); } } + await TriggerOverlayAsync(id, false); } + ResolvingConflict = false; BackAllowed = true; } diff --git a/src/IronyModManager/Views/MainConflictSolverControlView.xaml b/src/IronyModManager/Views/MainConflictSolverControlView.xaml index 842a075e..f35b5e56 100644 --- a/src/IronyModManager/Views/MainConflictSolverControlView.xaml +++ b/src/IronyModManager/Views/MainConflictSolverControlView.xaml @@ -30,7 +30,7 @@ - From 541b657410f2ca22cd72d81d28c14e1de59e414a Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 20 Feb 2024 16:22:35 +0100 Subject: [PATCH 046/101] Resolve main conflict solver view warnings --- .../MainConflictSolverControlView.xaml.cs | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/IronyModManager/Views/MainConflictSolverControlView.xaml.cs b/src/IronyModManager/Views/MainConflictSolverControlView.xaml.cs index 733a9afa..e500979c 100644 --- a/src/IronyModManager/Views/MainConflictSolverControlView.xaml.cs +++ b/src/IronyModManager/Views/MainConflictSolverControlView.xaml.cs @@ -4,7 +4,7 @@ // Created : 03-18-2020 // // Last Modified By : Mario -// Last Modified On : 06-27-2022 +// Last Modified On : 02-20-2024 // *********************************************************************** // // Mario @@ -58,21 +58,21 @@ public MainConflictSolverControlView() protected override void OnActivated(CompositeDisposable disposables) { var conflictList = this.FindControl("conflictList"); - conflictList.SelectionChanged += (sender, args) => + conflictList.SelectionChanged += (_, _) => { Dispatcher.UIThread.SafeInvoke(() => { - if (conflictList?.SelectedIndex > -1 && ViewModel.SelectedParentConflict != null) + if (conflictList.SelectedIndex > -1 && ViewModel!.SelectedParentConflict != null) { ViewModel.SelectedConflict = ViewModel.SelectedParentConflict.Children.ElementAt(conflictList.SelectedIndex); } else { - ViewModel.SelectedConflict = null; + ViewModel!.SelectedConflict = null; } }); }; - this.WhenAnyValue(p => p.IsActivated).Where(v => v).Subscribe(s => + this.WhenAnyValue(p => p.IsActivated).Where(v => v).Subscribe(_ => { this.WhenAnyValue(v => v.ViewModel.SelectedParentConflict).Subscribe(s => { @@ -80,7 +80,7 @@ protected override void OnActivated(CompositeDisposable disposables) { Dispatcher.UIThread.SafeInvoke(() => { - if (ViewModel.PreviousConflictIndex.HasValue) + if (ViewModel!.PreviousConflictIndex.HasValue) { if (conflictList.ItemCount > 0) { @@ -122,46 +122,33 @@ protected override void OnActivated(CompositeDisposable disposables) }).DisposeWith(disposables); }).DisposeWith(disposables); - conflictList.ContextMenuOpening += (item) => + conflictList.ContextMenuOpening += item => { List menuItems = null; if (item != null) { - ViewModel.SetParameters(item.Content as IHierarchicalDefinitions); + ViewModel!.SetParameters(item.Content as IHierarchicalDefinitions); if (!string.IsNullOrWhiteSpace(ViewModel.InvalidConflictPath)) { - menuItems = new List(); + menuItems = []; if (!ViewModel.ReadOnly) { - menuItems = new List() - { - new MenuItem() - { - Header = ViewModel.InvalidCustomPatch, - Command = ViewModel.InvalidCustomPatchCommand - }, - new MenuItem() - { - Header = "-" - } - }; + menuItems = + [ + new MenuItem { Header = ViewModel.InvalidCustomPatch, Command = ViewModel.InvalidCustomPatchCommand }, + new MenuItem { Header = "-" } + ]; } - menuItems.Add(new MenuItem() - { - Header = ViewModel.InvalidOpenFile, - Command = ViewModel.InvalidOpenFileCommand - }); + + menuItems.Add(new MenuItem { Header = ViewModel.InvalidOpenFile, Command = ViewModel.InvalidOpenFileCommand }); if (!ViewModel.InvalidConflictPath.EndsWith(Shared.Constants.ZipExtension, StringComparison.OrdinalIgnoreCase) && !ViewModel.InvalidConflictPath.EndsWith(Shared.Constants.BinExtension, StringComparison.OrdinalIgnoreCase)) { - menuItems.Add(new MenuItem() - { - Header = ViewModel.InvalidOpenDirectory, - Command = ViewModel.InvalidOpenDirectoryCommand - }); + menuItems.Add(new MenuItem { Header = ViewModel.InvalidOpenDirectory, Command = ViewModel.InvalidOpenDirectoryCommand }); } } } + conflictList.SetContextMenuItems(menuItems); }; From f9cc4631f9ebe1194cbd370efae305996b7795b0 Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 20 Feb 2024 16:26:49 +0100 Subject: [PATCH 047/101] Add new localization --- src/IronyModManager.Shared/LocalizationResources.cs | 1 + src/IronyModManager/Localization/de.json | 3 ++- src/IronyModManager/Localization/en.json | 3 ++- src/IronyModManager/Localization/es.json | 3 ++- src/IronyModManager/Localization/fr.json | 3 ++- src/IronyModManager/Localization/hr.json | 3 ++- src/IronyModManager/Localization/ru.json | 3 ++- src/IronyModManager/Localization/zh.json | 3 ++- 8 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/IronyModManager.Shared/LocalizationResources.cs b/src/IronyModManager.Shared/LocalizationResources.cs index aa6c8068..c3af581b 100644 --- a/src/IronyModManager.Shared/LocalizationResources.cs +++ b/src/IronyModManager.Shared/LocalizationResources.cs @@ -439,6 +439,7 @@ public static class ContextMenu public const string Redo = Prefix + "Redo"; public const string Editor = Prefix + "Editor"; public const string ReadonlyEditor = Prefix + "ReadonlyEditor"; + public const string UseNewCompare = Prefix + "UseNewCompare"; } public static class EditorContextMenu { diff --git a/src/IronyModManager/Localization/de.json b/src/IronyModManager/Localization/de.json index e77c6f4a..4d64cf21 100644 --- a/src/IronyModManager/Localization/de.json +++ b/src/IronyModManager/Localization/de.json @@ -332,7 +332,8 @@ "Undo": "Rückgängig Machen", "Redo": "Wiederholen", "Editor": "Externe Zusammenführung", - "ReadonlyEditor": "Externer Vergleich" + "ReadonlyEditor": "Externer Vergleich", + "UseNewCompare": "Neuen Diff-Vergleich verwenden" }, "EditorContextMenu": { "Copy": "Kopieren", diff --git a/src/IronyModManager/Localization/en.json b/src/IronyModManager/Localization/en.json index 863d2156..74ba1166 100644 --- a/src/IronyModManager/Localization/en.json +++ b/src/IronyModManager/Localization/en.json @@ -332,7 +332,8 @@ "Undo": "Undo", "Redo": "Redo", "Editor": "External Merge", - "ReadonlyEditor": "External Compare" + "ReadonlyEditor": "External Compare", + "UseNewCompare": "Use New Compare" }, "EditorContextMenu": { "Copy": "Copy", diff --git a/src/IronyModManager/Localization/es.json b/src/IronyModManager/Localization/es.json index 96d596ae..2591b2d1 100644 --- a/src/IronyModManager/Localization/es.json +++ b/src/IronyModManager/Localization/es.json @@ -332,7 +332,8 @@ "Undo": "Deshacer", "Redo": "Rehacer", "Editor": "Fusión Externa", - "ReadonlyEditor": "Comparación externa" + "ReadonlyEditor": "Comparación externa", + "UseNewCompare": "Utilizar la nueva comparación de diferencias" }, "EditorContextMenu": { "Copy": "Copiar", diff --git a/src/IronyModManager/Localization/fr.json b/src/IronyModManager/Localization/fr.json index 6ae9a720..07917855 100644 --- a/src/IronyModManager/Localization/fr.json +++ b/src/IronyModManager/Localization/fr.json @@ -332,7 +332,8 @@ "Undo": "annuler", "Redo": "Refaire", "Editor": "Fusion externe", - "ReadonlyEditor": "Comparaison externe" + "ReadonlyEditor": "Comparaison externe", + "UseNewCompare": "Utiliser New Diff Compare" }, "EditorContextMenu": { "Copy": "Copier", diff --git a/src/IronyModManager/Localization/hr.json b/src/IronyModManager/Localization/hr.json index 5648e27c..6058790e 100644 --- a/src/IronyModManager/Localization/hr.json +++ b/src/IronyModManager/Localization/hr.json @@ -332,7 +332,8 @@ "Undo": "Poništi", "Redo": "Ponovi", "Editor": "Vanjsko spajanje", - "ReadonlyEditor": "Vanjska usporedba" + "ReadonlyEditor": "Vanjska usporedba", + "UseNewCompare": "Koristite novu usporedbu" }, "EditorContextMenu": { "Copy": "Kopiraj", diff --git a/src/IronyModManager/Localization/ru.json b/src/IronyModManager/Localization/ru.json index dceabd7c..88310363 100644 --- a/src/IronyModManager/Localization/ru.json +++ b/src/IronyModManager/Localization/ru.json @@ -332,7 +332,8 @@ "Undo": "Отменить", "Redo": "Вернуть", "Editor": "Внешнее объединение", - "ReadonlyEditor": "Внешнее сравнение" + "ReadonlyEditor": "Внешнее сравнение", + "UseNewCompare": "Использование нового сравнения Diff" }, "EditorContextMenu": { "Copy": "Копировать", diff --git a/src/IronyModManager/Localization/zh.json b/src/IronyModManager/Localization/zh.json index 7bf30a60..7eff659c 100644 --- a/src/IronyModManager/Localization/zh.json +++ b/src/IronyModManager/Localization/zh.json @@ -332,7 +332,8 @@ "Undo": "撤销", "Redo": "重做", "Editor": "外部合并", - "ReadonlyEditor": "外部比较" + "ReadonlyEditor": "外部比较", + "UseNewCompare": "使用新差异比较" }, "EditorContextMenu": { "Copy": "复制", From 879bb33c23157fa5a34c13d05667dc76394364fc Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 20 Feb 2024 16:39:26 +0100 Subject: [PATCH 048/101] Add context menu toggle to swap modes --- .../LocalizationResources.cs | 1 + src/IronyModManager/Localization/de.json | 3 +- src/IronyModManager/Localization/en.json | 3 +- src/IronyModManager/Localization/es.json | 3 +- src/IronyModManager/Localization/fr.json | 3 +- src/IronyModManager/Localization/hr.json | 3 +- src/IronyModManager/Localization/ru.json | 3 +- src/IronyModManager/Localization/zh.json | 3 +- .../Controls/MergeViewerControlViewModel.cs | 18 ++ .../Controls/MergeViewerControlView.xaml.cs | 270 +++--------------- 10 files changed, 80 insertions(+), 230 deletions(-) diff --git a/src/IronyModManager.Shared/LocalizationResources.cs b/src/IronyModManager.Shared/LocalizationResources.cs index c3af581b..d3bc8ec7 100644 --- a/src/IronyModManager.Shared/LocalizationResources.cs +++ b/src/IronyModManager.Shared/LocalizationResources.cs @@ -440,6 +440,7 @@ public static class ContextMenu public const string Editor = Prefix + "Editor"; public const string ReadonlyEditor = Prefix + "ReadonlyEditor"; public const string UseNewCompare = Prefix + "UseNewCompare"; + public const string UseOldCompare = Prefix + "UseOldCompare"; } public static class EditorContextMenu { diff --git a/src/IronyModManager/Localization/de.json b/src/IronyModManager/Localization/de.json index 4d64cf21..50b2dd0e 100644 --- a/src/IronyModManager/Localization/de.json +++ b/src/IronyModManager/Localization/de.json @@ -333,7 +333,8 @@ "Redo": "Wiederholen", "Editor": "Externe Zusammenführung", "ReadonlyEditor": "Externer Vergleich", - "UseNewCompare": "Neuen Diff-Vergleich verwenden" + "UseNewCompare": "Neuen Diff-Vergleich verwenden", + "UseOldCompare": "Alten Diff-Vergleich verwenden" }, "EditorContextMenu": { "Copy": "Kopieren", diff --git a/src/IronyModManager/Localization/en.json b/src/IronyModManager/Localization/en.json index 74ba1166..fb87c60d 100644 --- a/src/IronyModManager/Localization/en.json +++ b/src/IronyModManager/Localization/en.json @@ -333,7 +333,8 @@ "Redo": "Redo", "Editor": "External Merge", "ReadonlyEditor": "External Compare", - "UseNewCompare": "Use New Compare" + "UseNewCompare": "Use New Diff Compare", + "UseOldCompare": "Use Old Diff Compare" }, "EditorContextMenu": { "Copy": "Copy", diff --git a/src/IronyModManager/Localization/es.json b/src/IronyModManager/Localization/es.json index 2591b2d1..e2e8d650 100644 --- a/src/IronyModManager/Localization/es.json +++ b/src/IronyModManager/Localization/es.json @@ -333,7 +333,8 @@ "Redo": "Rehacer", "Editor": "Fusión Externa", "ReadonlyEditor": "Comparación externa", - "UseNewCompare": "Utilizar la nueva comparación de diferencias" + "UseNewCompare": "Utilizar la nueva comparación de diferencias", + "UseOldCompare": "Utilizar la antigua comparación de diferencias" }, "EditorContextMenu": { "Copy": "Copiar", diff --git a/src/IronyModManager/Localization/fr.json b/src/IronyModManager/Localization/fr.json index 07917855..c14ef49b 100644 --- a/src/IronyModManager/Localization/fr.json +++ b/src/IronyModManager/Localization/fr.json @@ -333,7 +333,8 @@ "Redo": "Refaire", "Editor": "Fusion externe", "ReadonlyEditor": "Comparaison externe", - "UseNewCompare": "Utiliser New Diff Compare" + "UseNewCompare": "Utiliser New Diff Compare", + "UseOldCompare": "Utiliser l'ancien Diff Comparez" }, "EditorContextMenu": { "Copy": "Copier", diff --git a/src/IronyModManager/Localization/hr.json b/src/IronyModManager/Localization/hr.json index 6058790e..a6899dea 100644 --- a/src/IronyModManager/Localization/hr.json +++ b/src/IronyModManager/Localization/hr.json @@ -333,7 +333,8 @@ "Redo": "Ponovi", "Editor": "Vanjsko spajanje", "ReadonlyEditor": "Vanjska usporedba", - "UseNewCompare": "Koristite novu usporedbu" + "UseNewCompare": "Koristite novu usporedbu razlika", + "UseOldCompare": "Koristite staru usporedbu razlika" }, "EditorContextMenu": { "Copy": "Kopiraj", diff --git a/src/IronyModManager/Localization/ru.json b/src/IronyModManager/Localization/ru.json index 88310363..ffedf39d 100644 --- a/src/IronyModManager/Localization/ru.json +++ b/src/IronyModManager/Localization/ru.json @@ -333,7 +333,8 @@ "Redo": "Вернуть", "Editor": "Внешнее объединение", "ReadonlyEditor": "Внешнее сравнение", - "UseNewCompare": "Использование нового сравнения Diff" + "UseNewCompare": "Использовать новое сравнение различий", + "UseOldCompare": "Используйте старое сравнение различий" }, "EditorContextMenu": { "Copy": "Копировать", diff --git a/src/IronyModManager/Localization/zh.json b/src/IronyModManager/Localization/zh.json index 7eff659c..0435aa23 100644 --- a/src/IronyModManager/Localization/zh.json +++ b/src/IronyModManager/Localization/zh.json @@ -333,7 +333,8 @@ "Redo": "重做", "Editor": "外部合并", "ReadonlyEditor": "外部比较", - "UseNewCompare": "使用新差异比较" + "UseNewCompare": "使用新差异比较", + "UseOldCompare": "使用旧 Diff 比较" }, "EditorContextMenu": { "Copy": "复制", diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index eff4c498..4e17651c 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -579,6 +579,24 @@ public class MergeViewerControlViewModel( /// The undo command. public virtual ReactiveCommand UndoCommand { get; protected set; } + /// + /// Gets or sets a value representing the use new merge type caption. + /// + /// + /// The use new merge type caption. + /// + [StaticLocalization(LocalizationResources.Conflict_Solver.ContextMenu.UseNewCompare)] + public virtual string UseNewMergeTypeCaption { get; protected set; } + + /// + /// Gets or sets a value representing the use old merge type caption. + /// + /// + /// The use old merge type caption. + /// + [StaticLocalization(LocalizationResources.Conflict_Solver.ContextMenu.UseOldCompare)] + public virtual string UseOldMergeTypeCaption { get; protected set; } + /// /// Gets or sets a value indicating whether the using new merge type. /// diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 7dfd22bb..84a93707 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -343,11 +343,7 @@ protected override void OnLocaleChanged(string newLocale, string oldLocale) /// if set to true [left side]. protected virtual void SetEditorOptions(TextEditor editor, bool leftSide) { - editor.Options = new TextEditorOptions - { - ConvertTabsToSpaces = true, - IndentationSize = 4 - }; + editor.Options = new TextEditorOptions { ConvertTabsToSpaces = true, IndentationSize = 4 }; ViewModel.WhenAnyValue(p => p.EditingYaml).Subscribe(_ => { setEditMode(); }).DisposeWith(Disposables); setEditMode(); @@ -415,70 +411,26 @@ private List GetActionsMenuItems(bool leftSide) var menuItems = new List(); if (ViewModel!.EditorAvailable) { - menuItems.Add(new MenuItem - { - Header = ViewModel.Editor, - Command = ViewModel.EditorCommand, - CommandParameter = !leftSide - }); - menuItems.Add(new MenuItem - { - Header = "-" - }); + menuItems.Add(new MenuItem { Header = ViewModel.Editor, Command = ViewModel.EditorCommand, CommandParameter = !leftSide }); + menuItems.Add(new MenuItem { Header = "-" }); } var mainEditingItems = new List { - new() - { - Header = ViewModel.NextConflict, - Command = ViewModel.NextConflictCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.PrevConflict, - Command = ViewModel.PrevConflictCommand, - CommandParameter = leftSide - }, - new() - { - Header = "-" - }, - new() - { - Header = ViewModel.CopyText, - Command = ViewModel.CopyTextCommand, - CommandParameter = leftSide - }, + new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, + new() { Header = "-" }, + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide }, new() { Header = "-" // Separator magic string, and it's documented... NOT really!!! }, - new() - { - Header = ViewModel.CopyAll, - Command = ViewModel.CopyAllCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.CopyThis, - Command = ViewModel.CopyThisCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.CopyThisBeforeLine, - Command = ViewModel.CopyThisBeforeLineCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.CopyThisAfterLine, - Command = ViewModel.CopyThisAfterLineCommand, - CommandParameter = leftSide - } + new() { Header = ViewModel.CopyAll, Command = ViewModel.CopyAllCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.CopyThis, Command = ViewModel.CopyThisCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.CopyThisBeforeLine, Command = ViewModel.CopyThisBeforeLineCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.CopyThisAfterLine, Command = ViewModel.CopyThisAfterLineCommand, CommandParameter = leftSide }, + new() { Header = "-" }, + new() { Header = ViewModel.UsingNewMergeType ? ViewModel.UseNewMergeTypeCaption : ViewModel.UseOldMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } }; menuItems.AddRange(mainEditingItems); @@ -495,70 +447,23 @@ private List GetEditableMenuItems(bool leftSide) var menuItems = new List(); if (ViewModel!.EditorAvailable) { - menuItems.Add(new MenuItem - { - Header = ViewModel.Editor, - Command = ViewModel.EditorCommand, - CommandParameter = leftSide - }); - menuItems.Add(new MenuItem - { - Header = "-" - }); + menuItems.Add(new MenuItem { Header = ViewModel.Editor, Command = ViewModel.EditorCommand, CommandParameter = leftSide }); + menuItems.Add(new MenuItem { Header = "-" }); } var mainEditingItems = new List { - new() - { - Header = ViewModel.NextConflict, - Command = ViewModel.NextConflictCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.PrevConflict, - Command = ViewModel.PrevConflictCommand, - CommandParameter = leftSide - }, - new() - { - Header = "-" - }, - new() - { - Header = ViewModel.EditThis, - Command = ViewModel.EditThisCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.CopyText, - Command = ViewModel.CopyTextCommand, - CommandParameter = leftSide - }, - new() - { - Header = "-" - }, - new() - { - Header = ViewModel.DeleteText, - Command = ViewModel.DeleteTextCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.MoveUp, - Command = ViewModel.MoveUpCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.MoveDown, - Command = ViewModel.MoveDownCommand, - CommandParameter = leftSide - } + new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, + new() { Header = "-" }, + new() { Header = ViewModel.EditThis, Command = ViewModel.EditThisCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide }, + new() { Header = "-" }, + new() { Header = ViewModel.DeleteText, Command = ViewModel.DeleteTextCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.MoveUp, Command = ViewModel.MoveUpCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.MoveDown, Command = ViewModel.MoveDownCommand, CommandParameter = leftSide }, + new() { Header = "-" }, + new() { Header = ViewModel.UsingNewMergeType ? ViewModel.UseNewMergeTypeCaption : ViewModel.UseOldMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } }; menuItems.AddRange(mainEditingItems); @@ -566,28 +471,15 @@ private List GetEditableMenuItems(bool leftSide) var undoAvailable = ViewModel.IsUndoAvailable(); if (redoAvailable || undoAvailable) { - menuItems.Add(new MenuItem - { - Header = "-" - }); + menuItems.Add(new MenuItem { Header = "-" }); if (undoAvailable) { - menuItems.Add(new MenuItem - { - Header = ViewModel.Undo, - Command = ViewModel.UndoCommand, - CommandParameter = leftSide - }); + menuItems.Add(new MenuItem { Header = ViewModel.Undo, Command = ViewModel.UndoCommand, CommandParameter = leftSide }); } if (redoAvailable) { - menuItems.Add(new MenuItem - { - Header = ViewModel.Redo, - Command = ViewModel.RedoCommand, - CommandParameter = leftSide - }); + menuItems.Add(new MenuItem { Header = ViewModel.Redo, Command = ViewModel.RedoCommand, CommandParameter = leftSide }); } } @@ -604,42 +496,16 @@ private List GetNonEditableMenuItems(bool leftSide) var menuItems = new List(); if (ViewModel!.EditorAvailable) { - menuItems.Add(new MenuItem - { - Header = ViewModel.ReadOnlyEditor, - Command = ViewModel.ReadOnlyEditorCommand, - CommandParameter = leftSide - }); - menuItems.Add(new MenuItem - { - Header = "-" - }); + menuItems.Add(new MenuItem { Header = ViewModel.ReadOnlyEditor, Command = ViewModel.ReadOnlyEditorCommand, CommandParameter = leftSide }); + menuItems.Add(new MenuItem { Header = "-" }); } var mainEditingItems = new List { - new() - { - Header = ViewModel.NextConflict, - Command = ViewModel.NextConflictCommand, - CommandParameter = leftSide - }, - new() - { - Header = ViewModel.PrevConflict, - Command = ViewModel.PrevConflictCommand, - CommandParameter = leftSide - }, - new() - { - Header = "-" - }, - new() - { - Header = ViewModel.CopyText, - Command = ViewModel.CopyTextCommand, - CommandParameter = leftSide - } + new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, + new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, + new() { Header = "-" }, + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide } }; menuItems.AddRange(mainEditingItems); @@ -651,7 +517,7 @@ private List GetNonEditableMenuItems(bool leftSide) /// /// The text editor. /// The search panel. - /// if set to true [is replace mode]. + /// if set to true [if replace mode]. private void HandleEditorFindOrReplace(IronyModManager.Controls.TextEditor textEditor, AvaloniaEdit.Search.SearchPanel searchPanel, bool isReplaceMode) { if (searchPanel == null || textEditor == null) @@ -693,59 +559,17 @@ private void SetEditorContextMenu(IronyModManager.Controls.TextEditor textEditor { Items = new List { - new() - { - Header = ViewModel!.EditorCopy, - Command = ReactiveCommand.Create(() => textEditor.Copy()).DisposeWith(Disposables) - }, - new() - { - Header = ViewModel.EditorCut, - Command = ReactiveCommand.Create(() => textEditor.Cut()).DisposeWith(Disposables) - }, - new() - { - Header = ViewModel.EditorPaste, - Command = ReactiveCommand.Create(() => textEditor.Paste()).DisposeWith(Disposables) - }, - new() - { - Header = ViewModel.EditorDelete, - Command = ReactiveCommand.Create(() => textEditor.Delete()).DisposeWith(Disposables) - }, - new() - { - Header = ViewModel.EditorSelectAll, - Command = ReactiveCommand.Create(() => textEditor.SelectAll()).DisposeWith(Disposables) - }, - new() - { - Header = "-" - }, - new() - { - Header = ViewModel.EditorUndo, - Command = ReactiveCommand.Create(() => textEditor.Undo()).DisposeWith(Disposables) - }, - new() - { - Header = ViewModel.EditorRedo, - Command = ReactiveCommand.Create(() => textEditor.Redo()).DisposeWith(Disposables) - }, - new() - { - Header = "-" - }, - new() - { - Header = ViewModel.EditorFind, - Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(textEditor, searchPanel, false)).DisposeWith(Disposables) - }, - new() - { - Header = ViewModel.EditorReplace, - Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(textEditor, searchPanel, true)).DisposeWith(Disposables) - } + new() { Header = ViewModel!.EditorCopy, Command = ReactiveCommand.Create(textEditor.Copy).DisposeWith(Disposables) }, + new() { Header = ViewModel.EditorCut, Command = ReactiveCommand.Create(textEditor.Cut).DisposeWith(Disposables) }, + new() { Header = ViewModel.EditorPaste, Command = ReactiveCommand.Create(textEditor.Paste).DisposeWith(Disposables) }, + new() { Header = ViewModel.EditorDelete, Command = ReactiveCommand.Create(textEditor.Delete).DisposeWith(Disposables) }, + new() { Header = ViewModel.EditorSelectAll, Command = ReactiveCommand.Create(textEditor.SelectAll).DisposeWith(Disposables) }, + new() { Header = "-" }, + new() { Header = ViewModel.EditorUndo, Command = ReactiveCommand.Create(textEditor.Undo).DisposeWith(Disposables) }, + new() { Header = ViewModel.EditorRedo, Command = ReactiveCommand.Create(textEditor.Redo).DisposeWith(Disposables) }, + new() { Header = "-" }, + new() { Header = ViewModel.EditorFind, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(textEditor, searchPanel, false)).DisposeWith(Disposables) }, + new() { Header = ViewModel.EditorReplace, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(textEditor, searchPanel, true)).DisposeWith(Disposables) } } }; textEditor.ContextFlyout = ctx; From 7993fdbdf360a7153469a49a9e1480e17ab89e36 Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 20 Feb 2024 22:01:33 +0100 Subject: [PATCH 049/101] Make view recognize new diff control --- src/IronyModManager/Controls/TextEditor.cs | 138 ++++++- .../Controls/MergeViewerControlViewModel.cs | 9 +- .../Controls/MergeViewerControlView.xaml | 21 +- .../Controls/MergeViewerControlView.xaml.cs | 345 +++++++++++++++--- 4 files changed, 442 insertions(+), 71 deletions(-) diff --git a/src/IronyModManager/Controls/TextEditor.cs b/src/IronyModManager/Controls/TextEditor.cs index 60848130..5bb72ddb 100644 --- a/src/IronyModManager/Controls/TextEditor.cs +++ b/src/IronyModManager/Controls/TextEditor.cs @@ -4,18 +4,25 @@ // Created : 04-15-2020 // // Last Modified By : Mario -// Last Modified On : 04-27-2020 +// Last Modified On : 02-20-2024 // *********************************************************************** // // Mario // // // *********************************************************************** -using System.Collections.Generic; + using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Styling; +using AvaloniaEdit; using AvaloniaEdit.Editing; +using AvaloniaEdit.Rendering; using IronyModManager.DI; using IronyModManager.Implementation.Actions; using IronyModManager.Shared; @@ -34,6 +41,11 @@ public class TextEditor : AvaloniaEdit.TextEditor, IStyleable { #region Fields + /// + /// A private const double named MinimumDistanceToViewBorder. + /// + private const double MinimumDistanceToViewBorder = 30; + /// /// The application action /// @@ -48,9 +60,9 @@ public class TextEditor : AvaloniaEdit.TextEditor, IStyleable /// public TextEditor() : base(new TextArea()) { - TextArea.TextCopied += (sender, args) => + TextArea.TextCopied += (_, args) => { - string text = string.Join(Environment.NewLine, args.Text.SplitOnNewLine()); + var text = string.Join(Environment.NewLine, args.Text.SplitOnNewLine()); CopyTextAsync(text).ConfigureAwait(true); }; } @@ -59,6 +71,12 @@ public TextEditor() : base(new TextArea()) #region Properties + /// + /// Gets or sets a value representing the scroll viewer. + /// + /// The scroll viewer. + public ScrollViewer ScrollViewer { get; private set; } + /// /// Gets the style key. /// @@ -69,6 +87,101 @@ public TextEditor() : base(new TextArea()) #region Methods + /// + /// Scroll to. + /// + /// The line. + /// The column. + public new void ScrollTo(int line, int column) + { + const double minimumScrollFraction = 0.3; + ScrollTo(line, column, VisualYPosition.LineMiddle, + null != ScrollViewer ? ScrollViewer.Viewport.Height / 2 : 0.0, minimumScrollFraction); + } + + /// + /// Scroll to. + /// + /// The line. + /// The column. + /// The y position mode. + /// The referenced vertical view port offset. + /// The minimum scroll fraction. + public new void ScrollTo(int line, int column, VisualYPosition yPositionMode, double referencedVerticalViewPortOffset, double minimumScrollFraction) + { + // Backported from 0.11.x version + var textView = TextArea.TextView; + var document = textView.Document; + if (ScrollViewer != null && document != null) + { + if (line < 1) + line = 1; + if (line > document.LineCount) + line = document.LineCount; + + ILogicalScrollable scrollInfo = textView; + if (!scrollInfo.CanHorizontallyScroll) + { + var vl = textView.GetOrConstructVisualLine(document.GetLineByNumber(line)); + var remainingHeight = referencedVerticalViewPortOffset; + + while (remainingHeight > 0) + { + var prevLine = vl.FirstDocumentLine.PreviousLine; + if (prevLine == null) + break; + vl = textView.GetOrConstructVisualLine(prevLine); + remainingHeight -= vl.Height; + } + } + + var p = TextArea.TextView.GetVisualPosition( + new TextViewPosition(line, Math.Max(1, column)), + yPositionMode); + + var targetX = ScrollViewer.Offset.X; + var targetY = ScrollViewer.Offset.Y; + + var verticalPos = p.Y - referencedVerticalViewPortOffset; + if (Math.Abs(verticalPos - ScrollViewer.Offset.Y) > + minimumScrollFraction * ScrollViewer.Viewport.Height) + { + targetY = Math.Max(0, verticalPos); + } + + if (column > 0) + { + if (p.X > ScrollViewer.Viewport.Width - (MinimumDistanceToViewBorder * 2)) + { + var horizontalPos = Math.Max(0, p.X - (ScrollViewer.Viewport.Width / 2)); + if (Math.Abs(horizontalPos - ScrollViewer.Offset.X) > + minimumScrollFraction * ScrollViewer.Viewport.Width) + { + targetX = 0; + } + } + else + { + targetX = 0; + } + } + + if (!targetX.IsNearlyEqual(ScrollViewer.Offset.X) || !targetY.IsNearlyEqual(ScrollViewer.Offset.Y)) + { + ScrollViewer.Offset = new Vector(targetX, targetY); + } + } + } + + /// + /// Scrolls to a line. + /// + /// The line. + public new void ScrollToLine(int line) + { + ScrollTo(line, -1); + } + /// /// Copies the text asynchronous. /// @@ -76,13 +189,22 @@ public TextEditor() : base(new TextArea()) /// Task. protected virtual Task CopyTextAsync(string text) { - if (appAction == null) - { - appAction = DIResolver.Get(); - } + appAction ??= DIResolver.Get(); return appAction.CopyAsync(text); } + /// + /// Handles the event. + /// + /// The instance containing the event data. + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + // Yes, only to expose scroll viewer + ScrollViewer = (ScrollViewer)e.NameScope.Find("PART_ScrollViewer"); + } + #endregion Methods } } diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index 4e17651c..56cb66a3 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -1563,14 +1563,7 @@ protected virtual void SetEditThis(bool leftSide) EditingLeft = leftSide; EditingRight = !leftSide; EditingText = true; - if (leftSide) - { - CurrentEditText = LeftSide; - } - else - { - CurrentEditText = RightSide; - } + CurrentEditText = leftSide ? LeftSide : RightSide; LeftDocument = new TextDocument(LeftSide); RightDocument = new TextDocument(RightSide); diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml index 2fd5e2d5..b5dcc365 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml @@ -36,7 +36,7 @@ - + @@ -56,7 +56,7 @@ - @@ -71,7 +71,7 @@ @@ -81,7 +81,7 @@ - @@ -89,6 +89,17 @@ + + + + + \ No newline at end of file diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 84a93707..8e5eebe7 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -22,6 +22,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Threading; using AvaloniaEdit; +using AvaloniaEdit.Search; using DiffPlex.DiffBuilder.Model; using IronyModManager.Common; using IronyModManager.Common.Views; @@ -60,6 +61,26 @@ public class MergeViewerControlView : BaseControl /// private readonly IResourceLoader resourceLoader; + /// + /// A private IronyModManager.Controls.TextEditor named diffLeft. + /// + private IronyModManager.Controls.TextEditor diffLeft; + + /// + /// A private IronyModManager.Controls.TextEditor named diffRight. + /// + private IronyModManager.Controls.TextEditor diffRight; + + /// + /// A private AvaloniaEdit.Search.SearchPanel named diffSearchPanelLeft. + /// + private SearchPanel diffSearchPanelLeft; + + /// + /// A private AvaloniaEdit.Search.SearchPanel named diffSearchPanelRight. + /// + private SearchPanel diffSearchPanelRight; + /// /// The editor left /// @@ -73,12 +94,17 @@ public class MergeViewerControlView : BaseControl /// /// The search panel left /// - private AvaloniaEdit.Search.SearchPanel searchPanelLeft; + private SearchPanel editorSearchPanelLeft; /// /// The search panel right /// - private AvaloniaEdit.Search.SearchPanel searchPanelRight; + private SearchPanel editorSearchPanelRight; + + /// + /// A private bool named syncingDiffScroll. + /// + private bool syncingDiffScroll; /// /// The syncing scroll @@ -114,6 +140,29 @@ protected virtual void FocusConflict(int line, ListBox leftListBox, ListBox righ { leftListBox.SelectedIndex = line; rightListBox.SelectedIndex = line; + diffLeft.ScrollToLine(line); + diffRight.ScrollToLine(line); + } + + /// + /// Gets a text editor selected text range. + /// + /// The editor. + /// a Tuple with int,int . + protected virtual Tuple GetTextEditorSelectedTextRange(TextEditor editor) + { + var segments = editor.TextArea.Selection.Segments; + var doc = editor.TextArea.Document; + foreach (var segment in segments) + { + var start = doc.GetLineByOffset(segment.StartOffset).LineNumber; + var end = doc.GetLineByOffset(segment.EndOffset).LineNumber; + var min = Math.Min(start, end); + var max = Math.Max(start, end); + return Tuple.Create(min, max); + } + + return Tuple.Create(-1, -1); } /// @@ -147,6 +196,47 @@ protected virtual void HandleContextMenu(IronyModManager.Controls.ListBox listBo listBox.SetContextMenuItems(menuItems); } + /// + /// Handles an editor context menu. + /// + /// The editor. + /// The search panel. + /// If true, left side. + protected virtual void HandleEditorContextMenu(IronyModManager.Controls.TextEditor editor, SearchPanel searchPanel, bool leftSide) + { + if (editor == null || searchPanel == null) + { + return; + } + + List menuItems; + if ((!ViewModel!.RightSidePatchMod && !ViewModel.LeftSidePatchMod) || ViewModel.IsReadOnlyMode) + { + menuItems = GetNonEditableMenuItems(leftSide); + } + else + { + if (leftSide) + { + menuItems = ViewModel.RightSidePatchMod ? GetActionsMenuItems(true) : GetEditableMenuItems(true); + } + else + { + menuItems = ViewModel.LeftSidePatchMod ? GetActionsMenuItems(false) : GetEditableMenuItems(false); + } + } + + menuItems.AddRange( + [ + new MenuItem { Header = "-" }, + new MenuItem { Header = ViewModel.EditorFind, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, false)).DisposeWith(Disposables) }, + new MenuItem { Header = ViewModel.EditorReplace, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, true)).DisposeWith(Disposables) } + ]); + + var ctx = new MenuFlyout { Items = menuItems }; + editor.ContextFlyout = ctx; + } + /// /// Handles the listbox property changed. /// @@ -181,6 +271,29 @@ protected virtual void HandleListBoxPropertyChanged(AvaloniaPropertyChangedEvent } } + /// + /// Handles a text editor property changed. + /// + /// This text editor. + /// Other text editor. + protected virtual void HandleTextEditorPropertyChanged(IronyModManager.Controls.TextEditor thisTextEditor, IronyModManager.Controls.TextEditor otherTextEditor) + { + thisTextEditor.ScrollViewer.ScrollChanged += (_, _) => + { + if (syncingDiffScroll || (otherTextEditor.ScrollViewer.Offset.X.IsNearlyEqual(thisTextEditor.ScrollViewer.Offset.X) && otherTextEditor.ScrollViewer.Offset.Y.IsNearlyEqual(thisTextEditor.ScrollViewer.Offset.Y))) + { + return; + } + + syncingDiffScroll = true; + Dispatcher.UIThread.SafeInvoke(async () => + { + await SyncDiffScrollAsync(thisTextEditor, otherTextEditor); + syncingDiffScroll = false; + }); + }; + } + /// /// Called when [activated]. /// @@ -189,33 +302,52 @@ protected override void OnActivated(CompositeDisposable disposables) { editorLeft = this.FindControl("editorLeft"); editorRight = this.FindControl("editorRight"); - SetEditorOptions(editorLeft, true); - SetEditorOptions(editorRight, false); - searchPanelLeft = AvaloniaEdit.Search.SearchPanel.Install(editorLeft); - searchPanelRight = AvaloniaEdit.Search.SearchPanel.Install(editorRight); - SetEditorContextMenu(editorLeft, searchPanelLeft); - SetEditorContextMenu(editorRight, searchPanelRight); + SetEditorOptions(editorLeft); + SetEditorOptions(editorRight); + editorSearchPanelLeft = SearchPanel.Install(editorLeft); + editorSearchPanelRight = SearchPanel.Install(editorRight); + SetEditorContextMenu(editorLeft, editorSearchPanelLeft); + SetEditorContextMenu(editorRight, editorSearchPanelRight); + + diffLeft = this.FindControl("diffLeft"); + diffRight = this.FindControl("diffRight"); + SetDiffOptions(diffLeft, true); + SetDiffOptions(diffRight, false); + diffSearchPanelLeft = SearchPanel.Install(diffLeft); + diffSearchPanelRight = SearchPanel.Install(diffRight); + HandleEditorContextMenu(diffLeft, diffSearchPanelLeft, true); + HandleEditorContextMenu(diffRight, diffSearchPanelRight, false); + HandleTextEditorPropertyChanged(diffLeft, diffRight); + HandleTextEditorPropertyChanged(diffRight, diffLeft); var leftSide = this.FindControl("leftSide"); var rightSide = this.FindControl("rightSide"); - leftSide.PropertyChanged += (_, args) => { HandleListBoxPropertyChanged(args, leftSide, rightSide); }; rightSide.PropertyChanged += (_, args) => { HandleListBoxPropertyChanged(args, rightSide, leftSide); }; leftSide.ContextMenuOpening += item => { HandleContextMenu(leftSide, item, true); }; rightSide.ContextMenuOpening += item => { HandleContextMenu(rightSide, item, false); }; + ViewModel!.ConflictFound += line => { FocusConflict(line, leftSide, rightSide); }; int? focusSideScrollItem = null; var previousCount = 0; var autoScroll = leftSide.AutoScrollToSelectedItem; ViewModel.PreFocusSide += left => { - leftSide.AutoScrollToSelectedItem = rightSide.AutoScrollToSelectedItem = false; - var listBox = left ? leftSide : rightSide; - var visibleItems = listBox.ItemContainerGenerator.Containers.ToList(); - if (visibleItems.Count != 0) + if (ViewModel.UsingNewMergeType) { - focusSideScrollItem = visibleItems.LastOrDefault()!.Index; - previousCount = (left ? leftSide : rightSide).ItemCount; + focusSideScrollItem = GetTextEditorSelectedTextRange(left ? diffLeft : diffRight).Item2; + previousCount = left ? ViewModel.LeftDiff.Count : ViewModel.RightDiff.Count; + } + else + { + leftSide.AutoScrollToSelectedItem = rightSide.AutoScrollToSelectedItem = false; + var listBox = left ? leftSide : rightSide; + var visibleItems = listBox.ItemContainerGenerator.Containers.ToList(); + if (visibleItems.Count != 0) + { + focusSideScrollItem = visibleItems.LastOrDefault()!.Index; + previousCount = (left ? leftSide : rightSide).ItemCount; + } } }; ViewModel.PostFocusSide += left => @@ -223,27 +355,55 @@ protected override void OnActivated(CompositeDisposable disposables) async Task delay() { await Task.Delay(50); - leftSide.AutoScrollToSelectedItem = rightSide.AutoScrollToSelectedItem = autoScroll; - var listBox = left ? leftSide : rightSide; - listBox.Focus(); - if (focusSideScrollItem.HasValue) + if (ViewModel!.UsingNewMergeType) { - if (listBox.ItemCount != previousCount) + var textbox = left ? diffLeft : diffRight; + var diffList = left ? ViewModel.LeftDiff : ViewModel.RightDiff; + textbox.Focus(); + if (focusSideScrollItem.HasValue) { - focusSideScrollItem -= Math.Abs(previousCount - listBox.ItemCount); - } + if (diffList.Count != previousCount) + { + focusSideScrollItem -= Math.Abs(previousCount - diffList.Count); + } - FocusConflict(-1, leftSide, rightSide); - if (focusSideScrollItem.GetValueOrDefault() < 0) - { - focusSideScrollItem = 0; + FocusConflict(-1, leftSide, rightSide); + if (focusSideScrollItem.GetValueOrDefault() < 0) + { + focusSideScrollItem = 0; + } + else if (focusSideScrollItem.GetValueOrDefault() >= diffList.Count) + { + focusSideScrollItem = diffList.Count - 1; + } + + textbox.ScrollToLine(focusSideScrollItem.GetValueOrDefault()); } - else if (focusSideScrollItem.GetValueOrDefault() >= listBox.ItemCount) + } + else + { + leftSide.AutoScrollToSelectedItem = rightSide.AutoScrollToSelectedItem = autoScroll; + var listBox = left ? leftSide : rightSide; + listBox.Focus(); + if (focusSideScrollItem.HasValue) { - focusSideScrollItem = listBox.ItemCount - 1; - } + if (listBox.ItemCount != previousCount) + { + focusSideScrollItem -= Math.Abs(previousCount - listBox.ItemCount); + } - listBox.ScrollIntoView(focusSideScrollItem.GetValueOrDefault()); + FocusConflict(-1, leftSide, rightSide); + if (focusSideScrollItem.GetValueOrDefault() < 0) + { + focusSideScrollItem = 0; + } + else if (focusSideScrollItem.GetValueOrDefault() >= listBox.ItemCount) + { + focusSideScrollItem = listBox.ItemCount - 1; + } + + listBox.ScrollIntoView(focusSideScrollItem.GetValueOrDefault()); + } } focusSideScrollItem = null; @@ -299,21 +459,23 @@ void evalKey() // Yeah, it sucks that we can't access a property from a different thread if (ViewModel!.CanPerformHotKeyActions) { - DiffPiece item = null; - switch (hotkey.Hotkey) + if (ViewModel.UsingNewMergeType) { - case Enums.HotKeys.Ctrl_Shift_Up: - item = findItem(true); - break; - - case Enums.HotKeys.Ctrl_Shift_Down: - item = findItem(false); - break; + diffLeft.ScrollViewer.LineDown(); } - - if (item != null) + else { - leftSide.ScrollIntoView(item); + DiffPiece item = hotkey.Hotkey switch + { + Enums.HotKeys.Ctrl_Shift_Up => findItem(true), + Enums.HotKeys.Ctrl_Shift_Down => findItem(false), + _ => null + }; + + if (item != null) + { + leftSide.ScrollIntoView(item); + } } } } @@ -331,17 +493,60 @@ void evalKey() /// The old locale. protected override void OnLocaleChanged(string newLocale, string oldLocale) { - SetEditorContextMenu(editorLeft, searchPanelLeft); - SetEditorContextMenu(editorRight, searchPanelRight); + SetEditorContextMenu(editorLeft, editorSearchPanelLeft); + SetEditorContextMenu(editorRight, editorSearchPanelRight); + HandleEditorContextMenu(diffLeft, diffSearchPanelLeft, true); + HandleEditorContextMenu(diffRight, diffSearchPanelRight, false); base.OnLocaleChanged(newLocale, oldLocale); } /// - /// Sets the editor options. + /// Sets a diff options. + /// + /// The diff. + /// If true, left diff. + protected virtual void SetDiffOptions(TextEditor diff, bool leftDiff) + { + diff.Options = new TextEditorOptions { ConvertTabsToSpaces = true, IndentationSize = 4 }; + ViewModel.WhenAnyValue(p => p.EditingYaml).Subscribe(_ => { setEditMode(); }).DisposeWith(Disposables); + setEditMode(); + + void setEditMode() + { + diff.SyntaxHighlighting = ViewModel!.EditingYaml ? resourceLoader.GetYAMLDefinition() : resourceLoader.GetPDXScriptDefinition(); + } + + diff.TextChanged += (_, _) => + { + var lines = diff.Text.SplitOnNewLine().ToList(); + var text = string.Join(Environment.NewLine, lines); + if (leftDiff) + { + ViewModel!.SetText(text, ViewModel.RightSide); + } + else + { + ViewModel!.SetText(ViewModel.LeftSide, text); + } + }; + diff.TextArea.SelectionChanged += (_, _) => + { + var range = GetTextEditorSelectedTextRange(diff); + var col = leftDiff ? ViewModel!.LeftSideSelected : ViewModel!.RightSideSelected; + var sourceCol = leftDiff ? ViewModel.LeftDiff : ViewModel.RightDiff; + col.Clear(); + for (var i = range.Item1; i < range.Item2 + 1; i++) + { + col.Add(sourceCol[i]); + } + }; + } + + /// + /// Sets an editor options. /// /// The editor. - /// if set to true [left side]. - protected virtual void SetEditorOptions(TextEditor editor, bool leftSide) + protected virtual void SetEditorOptions(TextEditor editor) { editor.Options = new TextEditorOptions { ConvertTabsToSpaces = true, IndentationSize = 4 }; @@ -361,6 +566,46 @@ void setEditMode() }; } + /// + /// Syncs a diff scroll async. + /// + /// This text editor. + /// The other text editor. + /// A Task. + protected virtual Task SyncDiffScrollAsync(IronyModManager.Controls.TextEditor thisTextEditor, IronyModManager.Controls.TextEditor otherTextEditor) + { + var thisMaxX = Math.Abs(thisTextEditor.ScrollViewer.Extent.Width - thisTextEditor.ScrollViewer.Viewport.Width); + var thisMaxY = Math.Abs(thisTextEditor.ScrollViewer.Extent.Height - thisTextEditor.ScrollViewer.Viewport.Height); + var otherMaxX = Math.Abs(otherTextEditor.ScrollViewer.Extent.Width - otherTextEditor.ScrollViewer.Viewport.Width); + var otherMaxY = Math.Abs(otherTextEditor.ScrollViewer.Extent.Height - otherTextEditor.ScrollViewer.Viewport.Height); + var offset = thisTextEditor.ScrollViewer.Offset; + if (thisTextEditor.ScrollViewer.Offset.X > otherMaxX || thisTextEditor.ScrollViewer.Offset.X.IsNearlyEqual(thisMaxX)) + { + offset = offset.WithX(otherMaxX); + } + + if (thisTextEditor.ScrollViewer.Offset.Y > otherMaxY || thisTextEditor.ScrollViewer.Offset.Y.IsNearlyEqual(thisMaxY)) + { + offset = offset.WithY(otherMaxY); + } + + if (!otherTextEditor.ScrollViewer.Offset.X.IsNearlyEqual(offset.X) || otherTextEditor.ScrollViewer.Offset.Y.IsNearlyEqual(offset.Y)) + { + try + { + otherTextEditor.InvalidateArrange(); + thisTextEditor.InvalidateArrange(); + otherTextEditor.ScrollViewer.Offset = offset; + } + catch (Exception ex) + { + logger.Error(ex); + } + } + + return Task.CompletedTask; + } + /// /// Synchronizes the scroll asynchronous. /// @@ -398,7 +643,7 @@ protected virtual Task SyncScrollAsync(ListBox thisListBox, ListBox otherListBox } } - return Task.FromResult(true); + return Task.CompletedTask; } /// @@ -518,7 +763,7 @@ private List GetNonEditableMenuItems(bool leftSide) /// The text editor. /// The search panel. /// if set to true [if replace mode]. - private void HandleEditorFindOrReplace(IronyModManager.Controls.TextEditor textEditor, AvaloniaEdit.Search.SearchPanel searchPanel, bool isReplaceMode) + private void HandleEditorFindOrReplace(IronyModManager.Controls.TextEditor textEditor, SearchPanel searchPanel, bool isReplaceMode) { if (searchPanel == null || textEditor == null) { @@ -548,7 +793,7 @@ private void InitializeComponent() /// /// The text editor. /// The search panel. - private void SetEditorContextMenu(IronyModManager.Controls.TextEditor textEditor, AvaloniaEdit.Search.SearchPanel searchPanel) + private void SetEditorContextMenu(IronyModManager.Controls.TextEditor textEditor, SearchPanel searchPanel) { if (textEditor == null || searchPanel == null) { From 72ed1d259379bc0dd96496dd0889953d56f26ea4 Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 22 Feb 2024 06:34:39 +0100 Subject: [PATCH 050/101] React to text change, ensure scroll sync works and fix some bugs --- .../LocalizationResources.cs | 3 +- src/IronyModManager/Controls/TextEditor.cs | 15 +++- src/IronyModManager/Localization/de.json | 3 +- src/IronyModManager/Localization/en.json | 3 +- src/IronyModManager/Localization/es.json | 3 +- src/IronyModManager/Localization/fr.json | 3 +- src/IronyModManager/Localization/hr.json | 3 +- src/IronyModManager/Localization/ru.json | 3 +- src/IronyModManager/Localization/zh.json | 3 +- .../Controls/MergeViewerControlViewModel.cs | 39 ++++----- .../Controls/MergeViewerControlView.xaml | 6 +- .../Controls/MergeViewerControlView.xaml.cs | 85 +++++++++++++++++-- 12 files changed, 118 insertions(+), 51 deletions(-) diff --git a/src/IronyModManager.Shared/LocalizationResources.cs b/src/IronyModManager.Shared/LocalizationResources.cs index d3bc8ec7..c9c740f7 100644 --- a/src/IronyModManager.Shared/LocalizationResources.cs +++ b/src/IronyModManager.Shared/LocalizationResources.cs @@ -439,8 +439,7 @@ public static class ContextMenu public const string Redo = Prefix + "Redo"; public const string Editor = Prefix + "Editor"; public const string ReadonlyEditor = Prefix + "ReadonlyEditor"; - public const string UseNewCompare = Prefix + "UseNewCompare"; - public const string UseOldCompare = Prefix + "UseOldCompare"; + public const string ToggleCompare = Prefix + "ToggleCompare"; } public static class EditorContextMenu { diff --git a/src/IronyModManager/Controls/TextEditor.cs b/src/IronyModManager/Controls/TextEditor.cs index 5bb72ddb..3a95931d 100644 --- a/src/IronyModManager/Controls/TextEditor.cs +++ b/src/IronyModManager/Controls/TextEditor.cs @@ -4,7 +4,7 @@ // Created : 04-15-2020 // // Last Modified By : Mario -// Last Modified On : 02-20-2024 +// Last Modified On : 02-22-2024 // *********************************************************************** // // Mario @@ -69,6 +69,15 @@ public TextEditor() : base(new TextArea()) #endregion Constructors + #region Events + + /// + /// Occurs when [scroll initialized]. + /// + public event EventHandler ScrollInitialized; + + #endregion Events + #region Properties /// @@ -203,6 +212,10 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) // Yes, only to expose scroll viewer ScrollViewer = (ScrollViewer)e.NameScope.Find("PART_ScrollViewer"); + if (ScrollViewer != null) + { + ScrollInitialized?.Invoke(this, EventArgs.Empty); + } } #endregion Methods diff --git a/src/IronyModManager/Localization/de.json b/src/IronyModManager/Localization/de.json index 50b2dd0e..c9ae4b94 100644 --- a/src/IronyModManager/Localization/de.json +++ b/src/IronyModManager/Localization/de.json @@ -333,8 +333,7 @@ "Redo": "Wiederholen", "Editor": "Externe Zusammenführung", "ReadonlyEditor": "Externer Vergleich", - "UseNewCompare": "Neuen Diff-Vergleich verwenden", - "UseOldCompare": "Alten Diff-Vergleich verwenden" + "ToggleCompare": "Umschalten des Diff-Vergleichsmodus" }, "EditorContextMenu": { "Copy": "Kopieren", diff --git a/src/IronyModManager/Localization/en.json b/src/IronyModManager/Localization/en.json index fb87c60d..e851eb4d 100644 --- a/src/IronyModManager/Localization/en.json +++ b/src/IronyModManager/Localization/en.json @@ -333,8 +333,7 @@ "Redo": "Redo", "Editor": "External Merge", "ReadonlyEditor": "External Compare", - "UseNewCompare": "Use New Diff Compare", - "UseOldCompare": "Use Old Diff Compare" + "ToggleCompare": "Toggle Diff Compare Mode" }, "EditorContextMenu": { "Copy": "Copy", diff --git a/src/IronyModManager/Localization/es.json b/src/IronyModManager/Localization/es.json index e2e8d650..d782132c 100644 --- a/src/IronyModManager/Localization/es.json +++ b/src/IronyModManager/Localization/es.json @@ -333,8 +333,7 @@ "Redo": "Rehacer", "Editor": "Fusión Externa", "ReadonlyEditor": "Comparación externa", - "UseNewCompare": "Utilizar la nueva comparación de diferencias", - "UseOldCompare": "Utilizar la antigua comparación de diferencias" + "ToggleCompare": "Conmutar el modo de comparación de diferencias" }, "EditorContextMenu": { "Copy": "Copiar", diff --git a/src/IronyModManager/Localization/fr.json b/src/IronyModManager/Localization/fr.json index c14ef49b..cc6623f8 100644 --- a/src/IronyModManager/Localization/fr.json +++ b/src/IronyModManager/Localization/fr.json @@ -333,8 +333,7 @@ "Redo": "Refaire", "Editor": "Fusion externe", "ReadonlyEditor": "Comparaison externe", - "UseNewCompare": "Utiliser New Diff Compare", - "UseOldCompare": "Utiliser l'ancien Diff Comparez" + "ToggleCompare": "Basculer le mode de comparaison des différences" }, "EditorContextMenu": { "Copy": "Copier", diff --git a/src/IronyModManager/Localization/hr.json b/src/IronyModManager/Localization/hr.json index a6899dea..7cd6eaf2 100644 --- a/src/IronyModManager/Localization/hr.json +++ b/src/IronyModManager/Localization/hr.json @@ -333,8 +333,7 @@ "Redo": "Ponovi", "Editor": "Vanjsko spajanje", "ReadonlyEditor": "Vanjska usporedba", - "UseNewCompare": "Koristite novu usporedbu razlika", - "UseOldCompare": "Koristite staru usporedbu razlika" + "ToggleCompare": "Promjeni način usporedbe razlika" }, "EditorContextMenu": { "Copy": "Kopiraj", diff --git a/src/IronyModManager/Localization/ru.json b/src/IronyModManager/Localization/ru.json index ffedf39d..5b73abe2 100644 --- a/src/IronyModManager/Localization/ru.json +++ b/src/IronyModManager/Localization/ru.json @@ -333,8 +333,7 @@ "Redo": "Вернуть", "Editor": "Внешнее объединение", "ReadonlyEditor": "Внешнее сравнение", - "UseNewCompare": "Использовать новое сравнение различий", - "UseOldCompare": "Используйте старое сравнение различий" + "ToggleCompare": "Переключение режима сравнения диффов" }, "EditorContextMenu": { "Copy": "Копировать", diff --git a/src/IronyModManager/Localization/zh.json b/src/IronyModManager/Localization/zh.json index 0435aa23..4a861400 100644 --- a/src/IronyModManager/Localization/zh.json +++ b/src/IronyModManager/Localization/zh.json @@ -333,8 +333,7 @@ "Redo": "重做", "Editor": "外部合并", "ReadonlyEditor": "外部比较", - "UseNewCompare": "使用新差异比较", - "UseOldCompare": "使用旧 Diff 比较" + "ToggleCompare": "切换差值比较模式" }, "EditorContextMenu": { "Copy": "复制", diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index 56cb66a3..419c0b14 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -4,7 +4,7 @@ // Created : 03-20-2020 // // Last Modified By : Mario -// Last Modified On : 02-20-2024 +// Last Modified On : 02-22-2024 // *********************************************************************** // // Mario @@ -560,6 +560,15 @@ public class MergeViewerControlViewModel( /// The right side selected. public virtual IAvaloniaList RightSideSelected { get; set; } + /// + /// Gets or sets a value representing the toggle merge type caption. + /// + /// + /// The toggle merge type caption. + /// + [StaticLocalization(LocalizationResources.Conflict_Solver.ContextMenu.ToggleCompare)] + public virtual string ToggleMergeTypeCaption { get; protected set; } + /// /// Gets or sets a value representing the toggle merge type command. /// @@ -579,24 +588,6 @@ public class MergeViewerControlViewModel( /// The undo command. public virtual ReactiveCommand UndoCommand { get; protected set; } - /// - /// Gets or sets a value representing the use new merge type caption. - /// - /// - /// The use new merge type caption. - /// - [StaticLocalization(LocalizationResources.Conflict_Solver.ContextMenu.UseNewCompare)] - public virtual string UseNewMergeTypeCaption { get; protected set; } - - /// - /// Gets or sets a value representing the use old merge type caption. - /// - /// - /// The use old merge type caption. - /// - [StaticLocalization(LocalizationResources.Conflict_Solver.ContextMenu.UseOldCompare)] - public virtual string UseOldMergeTypeCaption { get; protected set; } - /// /// Gets or sets a value indicating whether the using new merge type. /// @@ -711,8 +702,8 @@ void evalStack(string text, string prevText) LeftDocument = new TextDocument(LeftSide); RightDocument = new TextDocument(RightSide); SetBracketText(); + Compare(LeftSide, RightSide); - Compare(); if (resetStack) { undoStack.Clear(); @@ -734,12 +725,14 @@ void evalStack(string text, string prevText) } /// - /// Compares this instance. + /// Compare. /// - protected virtual void Compare() + /// The left. + /// The right. + protected virtual void Compare(string left, string right) { var builder = new SideBySideDiffBuilder(new Differ()); - var diff = builder.BuildDiffModel(LeftSide, RightSide, true); + var diff = builder.BuildDiffModel(left, right, true); LeftDiff = GetDiffPieceWithIndex(diff.OldText.Lines); RightDiff = GetDiffPieceWithIndex(diff.NewText.Lines); } diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml index b5dcc365..e9daaa78 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml @@ -89,16 +89,16 @@ - + + TextBlock.FontSize="14" ShowLineNumbers="False" Grid.Row="1" Grid.Column="0" /> + TextBlock.FontSize="14" ShowLineNumbers="False" Grid.Row="1" Grid.Column="1" /> diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 8e5eebe7..50efd95e 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -4,7 +4,7 @@ // Created : 03-20-2020 // // Last Modified By : Mario -// Last Modified On : 02-20-2024 +// Last Modified On : 02-22-2024 // *********************************************************************** // // Mario @@ -280,7 +280,8 @@ protected virtual void HandleTextEditorPropertyChanged(IronyModManager.Controls. { thisTextEditor.ScrollViewer.ScrollChanged += (_, _) => { - if (syncingDiffScroll || (otherTextEditor.ScrollViewer.Offset.X.IsNearlyEqual(thisTextEditor.ScrollViewer.Offset.X) && otherTextEditor.ScrollViewer.Offset.Y.IsNearlyEqual(thisTextEditor.ScrollViewer.Offset.Y))) + if (syncingDiffScroll || thisTextEditor.ScrollViewer == null || otherTextEditor.ScrollViewer == null || (otherTextEditor.ScrollViewer.Offset.X.IsNearlyEqual(thisTextEditor.ScrollViewer.Offset.X) && + otherTextEditor.ScrollViewer.Offset.Y.IsNearlyEqual(thisTextEditor.ScrollViewer.Offset.Y))) { return; } @@ -317,8 +318,20 @@ protected override void OnActivated(CompositeDisposable disposables) diffSearchPanelRight = SearchPanel.Install(diffRight); HandleEditorContextMenu(diffLeft, diffSearchPanelLeft, true); HandleEditorContextMenu(diffRight, diffSearchPanelRight, false); - HandleTextEditorPropertyChanged(diffLeft, diffRight); - HandleTextEditorPropertyChanged(diffRight, diffLeft); + diffLeft.ScrollInitialized += (_, _) => HandleTextEditorPropertyChanged(diffLeft, diffRight); + diffRight.ScrollInitialized += (_, _) => HandleTextEditorPropertyChanged(diffRight, diffLeft); + var diffLeftMargin = new DiffMargin { Lines = ViewModel!.LeftDiff }; + var diffRightMargin = new DiffMargin { Lines = ViewModel.RightDiff }; + var diffLeftRenderer = new DiffBackgroundRenderer { Lines = ViewModel.LeftDiff }; + var diffRightRenderer = new DiffBackgroundRenderer { Lines = ViewModel.RightDiff }; + diffLeft.TextArea.LeftMargins.Add(diffLeftMargin); + diffLeft.TextArea.TextView.BackgroundRenderers.Add(diffLeftRenderer); + diffRight.TextArea.LeftMargins.Add(diffRightMargin); + diffRight.TextArea.TextView.BackgroundRenderers.Add(diffRightRenderer); + diffLeft.Text = string.Join(Environment.NewLine, ViewModel.LeftDiff); + diffRight.Text = string.Join(Environment.NewLine, ViewModel.RightDiff); + diffLeft.IsReadOnly = !ViewModel.LeftSidePatchMod; + diffRight.IsReadOnly = !ViewModel.RightSidePatchMod; var leftSide = this.FindControl("leftSide"); var rightSide = this.FindControl("rightSide"); @@ -483,6 +496,42 @@ void evalKey() Dispatcher.UIThread.SafeInvoke(evalKey); }).DisposeWith(disposables); + this.WhenAnyValue(v => v.ViewModel.LeftDiff).Subscribe(s => + { + diffLeftMargin.Lines = ViewModel!.LeftDiff; + diffLeftRenderer.Lines = ViewModel.LeftDiff; + try + { + diffLeft.Text = string.Join(Environment.NewLine, s.Select(p => p.Text)); + } + catch (InvalidOperationException) + { + } + }).DisposeWith(disposables); + + this.WhenAnyValue(v => v.ViewModel.RightDiff).Subscribe(s => + { + diffRightMargin.Lines = ViewModel!.RightDiff; + diffRightRenderer.Lines = ViewModel.RightDiff; + try + { + diffRight.Text = string.Join(Environment.NewLine, s.Select(p => p.Text)); + } + catch (InvalidOperationException) + { + } + }); + + this.WhenAnyValue(v => v.ViewModel.LeftSidePatchMod).Subscribe(s => + { + diffLeft.IsReadOnly = !s; + }).DisposeWith(disposables); + + this.WhenAnyValue(v => v.ViewModel.RightSidePatchMod).Subscribe(s => + { + diffRight.IsReadOnly = !s; + }).DisposeWith(disposables); + base.OnActivated(disposables); } @@ -520,6 +569,21 @@ void setEditMode() { var lines = diff.Text.SplitOnNewLine().ToList(); var text = string.Join(Environment.NewLine, lines); + text = text.Trim().Trim(Environment.NewLine); + if (leftDiff) + { + if (text.Equals(ViewModel!.LeftSide.Trim().Trim(Environment.NewLine))) + { + return; + } + } + else + { + if (text.Equals(ViewModel!.RightSide.Trim().Trim(Environment.NewLine))) + { + return; + } + } if (leftDiff) { ViewModel!.SetText(text, ViewModel.RightSide); @@ -532,6 +596,11 @@ void setEditMode() diff.TextArea.SelectionChanged += (_, _) => { var range = GetTextEditorSelectedTextRange(diff); + if (range.Item1 == -1 || range.Item2 == -1) + { + return; + } + var col = leftDiff ? ViewModel!.LeftSideSelected : ViewModel!.RightSideSelected; var sourceCol = leftDiff ? ViewModel.LeftDiff : ViewModel.RightDiff; col.Clear(); @@ -589,7 +658,7 @@ protected virtual Task SyncDiffScrollAsync(IronyModManager.Controls.TextEditor t offset = offset.WithY(otherMaxY); } - if (!otherTextEditor.ScrollViewer.Offset.X.IsNearlyEqual(offset.X) || otherTextEditor.ScrollViewer.Offset.Y.IsNearlyEqual(offset.Y)) + if (!otherTextEditor.ScrollViewer.Offset.X.IsNearlyEqual(offset.X) || !otherTextEditor.ScrollViewer.Offset.Y.IsNearlyEqual(offset.Y)) { try { @@ -629,7 +698,7 @@ protected virtual Task SyncScrollAsync(ListBox thisListBox, ListBox otherListBox offset = offset.WithY(otherMaxY); } - if (!otherListBox.Scroll.Offset.X.IsNearlyEqual(offset.X) || otherListBox.Scroll.Offset.Y.IsNearlyEqual(offset.Y)) + if (!otherListBox.Scroll.Offset.X.IsNearlyEqual(offset.X) || !otherListBox.Scroll.Offset.Y.IsNearlyEqual(offset.Y)) { try { @@ -675,7 +744,7 @@ private List GetActionsMenuItems(bool leftSide) new() { Header = ViewModel.CopyThisBeforeLine, Command = ViewModel.CopyThisBeforeLineCommand, CommandParameter = leftSide }, new() { Header = ViewModel.CopyThisAfterLine, Command = ViewModel.CopyThisAfterLineCommand, CommandParameter = leftSide }, new() { Header = "-" }, - new() { Header = ViewModel.UsingNewMergeType ? ViewModel.UseNewMergeTypeCaption : ViewModel.UseOldMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } + new() { Header = ViewModel.ToggleMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } }; menuItems.AddRange(mainEditingItems); @@ -708,7 +777,7 @@ private List GetEditableMenuItems(bool leftSide) new() { Header = ViewModel.MoveUp, Command = ViewModel.MoveUpCommand, CommandParameter = leftSide }, new() { Header = ViewModel.MoveDown, Command = ViewModel.MoveDownCommand, CommandParameter = leftSide }, new() { Header = "-" }, - new() { Header = ViewModel.UsingNewMergeType ? ViewModel.UseNewMergeTypeCaption : ViewModel.UseOldMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } + new() { Header = ViewModel.ToggleMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } }; menuItems.AddRange(mainEditingItems); From 6b77a221b9dd66faab3f3ca87034937c850b4176 Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 22 Feb 2024 21:16:08 +0100 Subject: [PATCH 051/101] Fix context menu --- .../Controls/MergeViewerControlView.xaml.cs | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 50efd95e..8dc1fea5 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -209,32 +209,39 @@ protected virtual void HandleEditorContextMenu(IronyModManager.Controls.TextEdit return; } - List menuItems; - if ((!ViewModel!.RightSidePatchMod && !ViewModel.LeftSidePatchMod) || ViewModel.IsReadOnlyMode) - { - menuItems = GetNonEditableMenuItems(leftSide); - } - else + var ctx = new MenuFlyout { Items = new List() }; + + void setMenuItems() { - if (leftSide) + List menuItems; + if ((!ViewModel!.RightSidePatchMod && !ViewModel.LeftSidePatchMod) || ViewModel.IsReadOnlyMode) { - menuItems = ViewModel.RightSidePatchMod ? GetActionsMenuItems(true) : GetEditableMenuItems(true); + menuItems = GetNonEditableMenuItems(leftSide); } else { - menuItems = ViewModel.LeftSidePatchMod ? GetActionsMenuItems(false) : GetEditableMenuItems(false); + if (leftSide) + { + menuItems = ViewModel.RightSidePatchMod ? GetActionsMenuItems(true) : GetEditableMenuItems(true); + } + else + { + menuItems = ViewModel.LeftSidePatchMod ? GetActionsMenuItems(false) : GetEditableMenuItems(false); + } } - } - menuItems.AddRange( - [ - new MenuItem { Header = "-" }, - new MenuItem { Header = ViewModel.EditorFind, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, false)).DisposeWith(Disposables) }, - new MenuItem { Header = ViewModel.EditorReplace, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, true)).DisposeWith(Disposables) } - ]); + menuItems.AddRange( + [ + new MenuItem { Header = "-" }, + new MenuItem { Header = ViewModel.EditorFind, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, false)).DisposeWith(Disposables) }, + new MenuItem { Header = ViewModel.EditorReplace, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, true)).DisposeWith(Disposables) } + ]); + + ctx.Items = menuItems; + } - var ctx = new MenuFlyout { Items = menuItems }; editor.ContextFlyout = ctx; + editor.ContextFlyout.Opening += (_, _) => setMenuItems(); } /// @@ -584,6 +591,7 @@ void setEditMode() return; } } + if (leftDiff) { ViewModel!.SetText(text, ViewModel.RightSide); From bf6e72cb07bd21018737029b0a1a78fd20169407 Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 22 Feb 2024 21:44:21 +0100 Subject: [PATCH 052/101] Actually fix setting text Fix copy ctd --- .../Controls/MergeViewerControlView.xaml.cs | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 8dc1fea5..7ca82871 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -505,28 +505,32 @@ void evalKey() this.WhenAnyValue(v => v.ViewModel.LeftDiff).Subscribe(s => { - diffLeftMargin.Lines = ViewModel!.LeftDiff; - diffLeftRenderer.Lines = ViewModel.LeftDiff; - try - { - diffLeft.Text = string.Join(Environment.NewLine, s.Select(p => p.Text)); - } - catch (InvalidOperationException) + diffLeftMargin.Lines = s; + diffLeftRenderer.Lines = s; + + var oldText = diffLeft.Text; + var newText = string.Join(Environment.NewLine, s.Select(p => p.Text)); + if (oldText.Equals(newText)) { + return; } + + diffLeft.Text = newText; }).DisposeWith(disposables); this.WhenAnyValue(v => v.ViewModel.RightDiff).Subscribe(s => { - diffRightMargin.Lines = ViewModel!.RightDiff; - diffRightRenderer.Lines = ViewModel.RightDiff; - try - { - diffRight.Text = string.Join(Environment.NewLine, s.Select(p => p.Text)); - } - catch (InvalidOperationException) + diffRightMargin.Lines = s; + diffRightRenderer.Lines = s; + + var oldText = diffRight.Text; + var newText = string.Join(Environment.NewLine, s.Select(p => p.Text)); + if (oldText.Equals(newText)) { + return; } + + diffRight.Text = string.Join(Environment.NewLine, s.Select(p => p.Text)); }); this.WhenAnyValue(v => v.ViewModel.LeftSidePatchMod).Subscribe(s => @@ -539,6 +543,7 @@ void evalKey() diffRight.IsReadOnly = !s; }).DisposeWith(disposables); + base.OnActivated(disposables); } @@ -611,6 +616,12 @@ void setEditMode() var col = leftDiff ? ViewModel!.LeftSideSelected : ViewModel!.RightSideSelected; var sourceCol = leftDiff ? ViewModel.LeftDiff : ViewModel.RightDiff; + + if (range.Item1 > sourceCol.Count - 1 || range.Item2 > sourceCol.Count) + { + return; + } + col.Clear(); for (var i = range.Item1; i < range.Item2 + 1; i++) { From 6ab34fbfde2313399d6d6266664487b92d94f9c3 Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 22 Feb 2024 22:12:35 +0100 Subject: [PATCH 053/101] Fix line number not showing --- .../Implementation/AvaloniaEdit/DiffMargin.cs | 246 +++++++++++++++++- 1 file changed, 242 insertions(+), 4 deletions(-) diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs index e29d4c3d..71fb0d01 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs @@ -4,22 +4,27 @@ // Created : 02-19-2024 // // Last Modified By : Mario -// Last Modified On : 02-19-2024 +// Last Modified On : 02-22-2024 // *********************************************************************** // // Mario // -// +// Portions based on LineNumberingMargin // *********************************************************************** using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Media; +using AvaloniaEdit.Document; using AvaloniaEdit.Editing; using AvaloniaEdit.Rendering; +using AvaloniaEdit.Utils; using IronyModManager.ViewModels.Controls; namespace IronyModManager.Implementation.AvaloniaEdit @@ -32,11 +37,36 @@ public class DiffMargin : AbstractMargin { #region Fields + /// + /// The maximum line number length + /// + private int maxLineNumberLength = 1; + /// /// A private const double named LineMargin. /// private const double LineMargin = 4d; + /// + /// The font family + /// + private FontFamily fontFamily; + + /// + /// The font size + /// + private double fontSize; + + /// + /// The selecting + /// + private bool selecting; + + /// + /// The selection start + /// + private AnchorSegment selectionStart; + #endregion Fields #region Properties @@ -104,13 +134,140 @@ protected override Size MeasureOverride(Size availableSize) { if (Lines == null || Lines.Count == 0) { - return new Size(0, 0); + fontFamily = GetValue(TextBlock.FontFamilyProperty); + fontSize = GetValue(TextBlock.FontSizeProperty); + + var txt = TextFormatterFactory.CreateFormattedText(this, new string('9', 2), fontFamily, fontSize, GetValue(TemplatedControl.ForegroundProperty)); + return new Size(txt.Bounds.Width + (LineMargin * 2), 0); } var text = Lines.LastOrDefault()!.Index.ToString(); var typeFace = CreateTypeface(); var lineText = new FormattedText(text, typeFace, TextView.GetValue(TextBlock.FontSizeProperty), TextAlignment.Left, TextWrapping.NoWrap, Size.Empty); - return new Size(lineText.Bounds.Width + LineMargin * 2, 0); + return new Size(lineText.Bounds.Width + (LineMargin * 2), 0); + } + + /// + /// Called when [document changed]. + /// + /// The old document. + /// The new document. + protected override void OnDocumentChanged(TextDocument oldDocument, TextDocument newDocument) + { + if (oldDocument != null) + { + TextDocumentWeakEventManager.LineCountChanged.RemoveHandler(oldDocument, OnDocumentLineCountChanged); + } + + base.OnDocumentChanged(oldDocument, newDocument); + if (newDocument != null) + { + TextDocumentWeakEventManager.LineCountChanged.AddHandler(newDocument, OnDocumentLineCountChanged); + } + + OnDocumentLineCountChanged(); + } + + /// + /// Called before the event occurs. + /// + /// The event args. + protected override void OnPointerMoved(PointerEventArgs e) + { + if (selecting && TextArea != null && TextView != null) + { + e.Handled = true; + var currentSeg = GetTextLineSegment(e); + if (currentSeg == SimpleSegment.Invalid) + { + return; + } + + ExtendSelection(currentSeg); + TextArea.Caret.BringCaretToView(5.0); + } + + base.OnPointerMoved(e); + } + + /// + /// Called before the event occurs. + /// + /// The event args. + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + base.OnPointerPressed(e); + + if (!e.Handled && TextView != null && TextArea != null) + { + e.Handled = true; + TextArea.Focus(); + + var currentSeg = GetTextLineSegment(e); + if (currentSeg == SimpleSegment.Invalid) + { + return; + } + + TextArea.Caret.Offset = currentSeg.Offset + currentSeg.Length; + e.Pointer.Capture(this); + if (Equals(e.Pointer.Captured, this)) + { + selecting = true; + selectionStart = new AnchorSegment(Document, currentSeg.Offset, currentSeg.Length); + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + { + if (TextArea.Selection is SimpleSelection simpleSelection) + selectionStart = new AnchorSegment(Document, simpleSelection.SurroundingSegment); + } + + TextArea.Selection = Selection.Create(TextArea, selectionStart); + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + { + ExtendSelection(currentSeg); + } + + TextArea.Caret.BringCaretToView(5.0); + } + } + } + + /// + /// Handles the event. + /// + /// The instance containing the event data. + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + if (selecting) + { + selecting = false; + selectionStart = null; + e.Pointer.Capture(null); + e.Handled = true; + } + + base.OnPointerReleased(e); + } + + /// + /// Called when [text view changed]. + /// + /// The old text view. + /// The new text view. + protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) + { + if (oldTextView != null) + { + oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged; + } + + base.OnTextViewChanged(oldTextView, newTextView); + if (newTextView != null) + { + newTextView.VisualLinesChanged += TextViewVisualLinesChanged; + } + + InvalidateVisual(); } /// @@ -124,6 +281,87 @@ private Typeface CreateTypeface() TextView.GetValue(TextBlock.FontWeightProperty)); } + /// + /// Extends the selection. + /// + /// The current seg. + private void ExtendSelection(SimpleSegment currentSeg) + { + if (currentSeg.Offset < selectionStart.Offset) + { + TextArea.Caret.Offset = currentSeg.Offset; + TextArea.Selection = Selection.Create(TextArea, currentSeg.Offset, selectionStart.Offset + selectionStart.Length); + } + else + { + TextArea.Caret.Offset = currentSeg.Offset + currentSeg.Length; + TextArea.Selection = Selection.Create(TextArea, selectionStart.Offset, currentSeg.Offset + currentSeg.Length); + } + } + + /// + /// Gets the text line segment. + /// + /// The instance containing the event data. + /// SimpleSegment. + private SimpleSegment GetTextLineSegment(PointerEventArgs e) + { + var pos = e.GetPosition(TextView); + pos = new Point(0, pos.Y.CoerceValue(0, TextView.Bounds.Height) + TextView.VerticalOffset); + var vl = TextView.GetVisualLineFromVisualTop(pos.Y); + if (vl == null) + return SimpleSegment.Invalid; + var tl = vl.GetTextLineByVisualYPosition(pos.Y); + var visualStartColumn = vl.GetTextLineVisualStartColumn(tl); + var visualEndColumn = visualStartColumn + tl.Length; + var relStart = vl.FirstDocumentLine.Offset; + var startOffset = vl.GetRelativeOffset(visualStartColumn) + relStart; + var endOffset = vl.GetRelativeOffset(visualEndColumn) + relStart; + if (endOffset == vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length) + endOffset += vl.LastDocumentLine.DelimiterLength; + return new SimpleSegment(startOffset, endOffset - startOffset); + } + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private void OnDocumentLineCountChanged(object sender, EventArgs e) + { + OnDocumentLineCountChanged(); + } + + /// + /// Called when [document line count changed]. + /// + private void OnDocumentLineCountChanged() + { + var documentLineCount = Document?.LineCount ?? 1; + var newLength = documentLineCount.ToString(CultureInfo.CurrentCulture).Length; + + if (newLength < 2) + { + newLength = 2; + } + + if (newLength != maxLineNumberLength) + { + maxLineNumberLength = newLength; + InvalidateMeasure(); + } + } + + /// + /// Texts the view visual lines changed. + /// + /// The sender. + /// The instance containing the event data. + private void TextViewVisualLinesChanged(object sender, EventArgs e) + { + InvalidateMeasure(); + } + #endregion Methods } } From 90d2cd47a6aa240f3d6a671f23a2ef5bb4698f57 Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 22 Feb 2024 22:20:44 +0100 Subject: [PATCH 054/101] Fix returning wrong selection range --- .../Views/Controls/MergeViewerControlView.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 7ca82871..21257f21 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -159,7 +159,7 @@ protected virtual Tuple GetTextEditorSelectedTextRange(TextEditor edit var end = doc.GetLineByOffset(segment.EndOffset).LineNumber; var min = Math.Min(start, end); var max = Math.Max(start, end); - return Tuple.Create(min, max); + return Tuple.Create(min - 1, max - 1); } return Tuple.Create(-1, -1); From d9b3bdac4d0723b93c0fabedcaf9502c47e4e9b3 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 07:59:45 +0100 Subject: [PATCH 055/101] Upgrade c# logic in mod patch collection service (modernize it) --- .../ModPatchCollectionService.cs | 811 +++++++++--------- .../Extensions.Double.cs | 33 +- 2 files changed, 435 insertions(+), 409 deletions(-) diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index 38ba7ad4..43e0797a 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -1,17 +1,17 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Services // Author : Mario // Created : 05-26-2020 // // Last Modified By : Mario -// Last Modified On : 01-23-2024 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -47,7 +47,6 @@ namespace IronyModManager.Services { - /// /// Class ModPatchCollectionService. /// Implements the @@ -237,6 +236,7 @@ public virtual Task AddCustomModPatchAsync(IConflictResult conflictResult, { definition.ModName = GenerateCollectionPatchName(collectionName); } + return ExportModPatchDefinitionAsync(conflictResult, definition, collectionName, ExportType.Custom); } @@ -261,6 +261,7 @@ public virtual void AddModsToIgnoreList(IConflictResult conflictResult, IEnumera sb.AppendLine(line); } } + if (mods != null) { foreach (var item in mods) @@ -268,6 +269,7 @@ public virtual void AddModsToIgnoreList(IConflictResult conflictResult, IEnumera sb.AppendLine($"{ModNameIgnoreId}{item.ModName}{IgnoreRulesSeparator}{ModNameIgnoreCounterId}{(item.Count > 1 ? item.Count : 2)}"); } } + conflictResult.IgnoredPaths = sb.ToString().Trim(Environment.NewLine.ToCharArray()); } } @@ -296,6 +298,7 @@ public virtual async Task CleanPatchCollectionAsync(string collectionName) { return false; } + var patchName = GenerateCollectionPatchName(collectionName); var allMods = GetInstalledModsInternal(game, false).ToList(); var mod = allMods.FirstOrDefault(p => p.Name.Equals(patchName)); @@ -303,10 +306,8 @@ public virtual async Task CleanPatchCollectionAsync(string collectionName) { await DeleteDescriptorsInternalAsync(new List { mod }); } - return await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = GetPatchModDirectory(game, patchName) - }, true); + + return await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = GetPatchModDirectory(game, patchName) }, true); } /// @@ -322,15 +323,16 @@ public Task CopyPatchCollectionAsync(string collectionName, string newColl { return Task.FromResult(false); } + var modDirRootPath = GetModDirectoryRootPath(game); var oldPatchName = GenerateCollectionPatchName(collectionName); var newPatchName = GenerateCollectionPatchName(newCollectionName); - return modPatchExporter.CopyPatchModAsync(new ModPatchExporterParameters() + return modPatchExporter.CopyPatchModAsync(new ModPatchExporterParameters { RootPath = modDirRootPath, ModPath = EvaluatePatchNamePath(game, oldPatchName, modDirRootPath), PatchPath = EvaluatePatchNamePath(game, newPatchName, modDirRootPath), - RenamePairs = new List>() { new(oldPatchName, newPatchName) } + RenamePairs = [new KeyValuePair(oldPatchName, newPatchName)] }); } @@ -348,11 +350,7 @@ public virtual async Task CreatePatchDefinitionAsync(IDefinition co var patch = Mapper.Map(copy); patch.UseSimpleValidation = false; patch.ModName = GenerateCollectionPatchName(collectionName); - var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters() - { - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patch.ModName) - }); + var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patch.ModName) }); if (state != null && state.IndexedConflictHistory.Any() && state.IndexedConflictHistory.TryGetValue(copy.TypeAndId, out var value)) { var history = value.FirstOrDefault(); @@ -361,13 +359,15 @@ public virtual async Task CreatePatchDefinitionAsync(IDefinition co patch.Code = history.Code; } } + return patch; } + return null; } /// - /// Evals the definition priority. + /// Evaluate the definition priority. /// /// The definitions. /// IPriorityDefinitionResult. @@ -394,18 +394,19 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition { actualMode = PatchStateMode.AdvancedWithoutLocalization; } + var conflicts = new HashSet(); var fileConflictCache = new Dictionary(); var fileKeys = await indexedDefinitions.GetAllFileKeysAsync(); var typeAndIdKeys = await indexedDefinitions.GetAllTypeAndIdKeysAsync(); - var overwritten = (await indexedDefinitions.GetByValueTypeAsync(ValueType.OverwrittenObject)).Concat((await indexedDefinitions.GetByValueTypeAsync(ValueType.OverwrittenObjectSingleFile))); - var empty = (await indexedDefinitions.GetByValueTypeAsync(ValueType.EmptyFile)); + var overwritten = (await indexedDefinitions.GetByValueTypeAsync(ValueType.OverwrittenObject)).Concat(await indexedDefinitions.GetByValueTypeAsync(ValueType.OverwrittenObjectSingleFile)); + var empty = await indexedDefinitions.GetByValueTypeAsync(ValueType.EmptyFile); var allCount = (await indexedDefinitions.GetAllAsync()).Count(); double total = (allCount * 2) + typeAndIdKeys.Count() + (overwritten.GroupBy(p => p.TypeAndId).Count() * 2) + empty.Count(); double processed = 0; double previousProgress = 0; - messageBus.Publish(new ModDefinitionAnalyzeEvent(0)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(0)); var stopWatch = new Stopwatch(); stopWatch.Start(); @@ -438,22 +439,26 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition copy.FileNameSuffix = item.FileNameSuffix; await indexedDefinitions.AddToMapAsync(copy); } + var fileNames = copy.AdditionalFileNames; foreach (var fileName in item.AdditionalFileNames) { fileNames.Add(fileName); } + copy.AdditionalFileNames = fileNames; } } + processed++; var perc = GetProgressPercentage(total, processed, 99.99); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { - messageBus.Publish(new ModDefinitionAnalyzeEvent(perc)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(perc)); previousProgress = perc; } } + Debug.WriteLine("FindConflictsAsync Empty Files Parse: " + stopWatch.Elapsed.FormatElapsed()); stopWatch.Restart(); @@ -472,22 +477,26 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var progressMutex = await opLock.LockAsync(); processed += definitions.Count(); var perc = GetProgressPercentage(total, processed, 99.99); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { - messageBus.Publish(new ModDefinitionAnalyzeEvent(perc)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(perc)); previousProgress = perc; } + progressMutex.Dispose(); } + var syncMutex = await opLock.LockAsync(); foreach (var item in localConflicts) { conflicts.Add(item); } + foreach (var item in localFileConflictCache) { fileConflictCache.TryAdd(item.Key, item.Value); } + syncMutex.Dispose(); }); }); @@ -510,22 +519,26 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var progressMutex = await opLock.LockAsync(); processed += definitions.Count(); var perc = GetProgressPercentage(total, processed, 99.99); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { - messageBus.Publish(new ModDefinitionAnalyzeEvent(perc)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(perc)); previousProgress = perc; } + progressMutex.Dispose(); } + var syncMutex = await opLock.LockAsync(); foreach (var item in localConflicts) { conflicts.Add(item); } + foreach (var item in localFileConflictCache) { fileConflictCache.TryAdd(item.Key, item.Value); } + syncMutex.Dispose(); }); }); @@ -549,11 +562,11 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition if (items.Any() && items.All(p => !p.ExistsInLastFile)) { var fileLock = await opLock.LockAsync(); - var fileDefs = await indexedDefinitions.GetByFileAsync(items.FirstOrDefault().FileCI); + var fileDefs = await indexedDefinitions.GetByFileAsync(items.FirstOrDefault()!.FileCI); fileLock.Dispose(); - var lastMod = fileDefs.GroupBy(p => p.ModName).Select(p => p.First()).OrderByDescending(p => modOrder.IndexOf(p.ModName)).FirstOrDefault(); + var lastMod = fileDefs.GroupBy(p => p.ModName).Select(p => p.First()).MaxBy(p => modOrder.IndexOf(p.ModName)); var copy = CopyDefinition(items.FirstOrDefault()); - copy.Dependencies = lastMod.Dependencies; + copy.Dependencies = lastMod!.Dependencies; copy.ModName = lastMod.ModName; copy.Code = copy.OriginalCode = Comments.GetEmptyCommentType(copy.File); copy.ContentSHA = lastMod.ContentSHA; @@ -568,6 +581,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition { fileNames.Add(fileName); } + copy.AdditionalFileNames = fileNames; copy.ExistsInLastFile = true; copy.IsFromGame = lastMod.IsFromGame; @@ -578,11 +592,12 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition conflicts.Add(copy); processed++; var perc = GetProgressPercentage(total, processed, 99.99); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { - messageBus.Publish(new ModDefinitionAnalyzeEvent(perc)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(perc)); previousProgress = perc; } + mutex.Dispose(); } else @@ -590,11 +605,12 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var mutex = await opLock.LockAsync(); processed++; var perc = GetProgressPercentage(total, processed, 99.99); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { - messageBus.Publish(new ModDefinitionAnalyzeEvent(perc)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(perc)); previousProgress = perc; } + mutex.Dispose(); } } @@ -611,28 +627,19 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition foreach (var item in overwritten.GroupBy(p => p.TypeAndId)) { - if (!overwrittenSort.TryGetValue(item.FirstOrDefault().ParentDirectoryCI, out var value)) + if (!overwrittenSort.TryGetValue(item.FirstOrDefault()!.ParentDirectoryCI, out var value)) { - var all = (await indexedDefinitions.GetByParentDirectoryAsync(item.FirstOrDefault().ParentDirectoryCI)).Where(IsValidDefinitionType); + var all = (await indexedDefinitions.GetByParentDirectoryAsync(item.FirstOrDefault()!.ParentDirectoryCI)).Where(IsValidDefinitionType); var ordered = all.GroupBy(p => p.TypeAndId).Select(p => { var partialCopy = new List(); p.ToList().ForEach(x => partialCopy.Add(PartialDefinitionCopy(x, false))); var priority = EvalDefinitionPriorityInternal(partialCopy.OrderBy(x => modOrder.IndexOf(x.ModName)), true); - return new DefinitionOrderSort() - { - TypeAndId = priority.Definition.TypeAndId, - Order = priority.Definition.Order, - File = Path.GetFileNameWithoutExtension(priority.FileName) - }; + return new DefinitionOrderSort { TypeAndId = priority.Definition.TypeAndId, Order = priority.Definition.Order, File = Path.GetFileNameWithoutExtension(priority.FileName) }; }).GroupBy(p => p.File).OrderBy(p => p.Key, StringComparer.Ordinal).SelectMany(p => p.OrderBy(x => x.Order)).ToList(); - var fullyOrdered = ordered.Select(p => new DefinitionOrderSort() - { - TypeAndId = p.TypeAndId, - Order = ordered.IndexOf(p) - }).ToList(); + var fullyOrdered = ordered.Select(p => new DefinitionOrderSort { TypeAndId = p.TypeAndId, Order = ordered.IndexOf(p) }).ToList(); value = fullyOrdered; - overwrittenSort.Add(item.FirstOrDefault().ParentDirectoryCI, value); + overwrittenSort.Add(item.FirstOrDefault()!.ParentDirectoryCI, value); } var conflicted = await indexedConflicts.GetByTypeAndIdAsync(item.First().TypeAndId); @@ -649,35 +656,39 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var all = item.Select(p => p); foreach (var def in all) { - var hasOverrides = all.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(p => p.Equals(def.ModName))); + var hasOverrides = all.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(s => s.Equals(def.ModName))); if (!hasOverrides || actualMode == PatchStateMode.Advanced) { valid.Add(def); } } + definitions = valid; definition = EvalDefinitionPriority(valid.OrderBy(p => modOrder.IndexOf(p.ModName))).Definition; } + if (!overwrittenDefs.ContainsKey(definition.TypeAndId)) { var newDefinition = CopyDefinition(definition); var ordered = value; - newDefinition.Order = ordered.FirstOrDefault(p => p.TypeAndId == newDefinition.TypeAndId).Order; + newDefinition.Order = ordered.FirstOrDefault(p => p.TypeAndId == newDefinition.TypeAndId)!.Order; if (!overwrittenSortExport.TryGetValue(newDefinition.ParentDirectoryCI, out var valueInner)) { - overwrittenSortExport.Add(newDefinition.ParentDirectoryCI, new List() { newDefinition }); + overwrittenSortExport.Add(newDefinition.ParentDirectoryCI, [newDefinition]); } else { valueInner.Add(newDefinition); } + overwrittenDefs.Add(definition.TypeAndId, Tuple.Create(newDefinition, definitions, definition)); } + processed++; var perc = GetProgressPercentage(total, processed, 99.99); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { - messageBus.Publish(new ModDefinitionAnalyzeEvent(perc)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(perc)); previousProgress = perc; } } @@ -703,8 +714,9 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition { overwrittenFileNames.Add(file); } + newDefinition.OverwrittenFileNames = overwrittenFileNames.Distinct().ToList(); - newDefinition.DiskFile = provider.GetDiskFileName(newDefinition); + newDefinition.DiskFile = provider!.GetDiskFileName(newDefinition); var preserveOverwrittenFileName = oldFileName == newDefinition.File; newDefinition.File = provider.GetFileName(newDefinition); if (preserveOverwrittenFileName) @@ -714,14 +726,16 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition preservedOverwrittenFileName.Add(oldFileName); newDefinition.OverwrittenFileNames = preservedOverwrittenFileName; } + var mutex = await opLock.LockAsync(); processed++; var perc = GetProgressPercentage(total, processed, 99.99); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { - messageBus.Publish(new ModDefinitionAnalyzeEvent(perc)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(perc)); previousProgress = perc; } + mutex.Dispose(); } }); @@ -731,9 +745,9 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition Debug.WriteLine("FindConflictsAsync Overwritten Objects Sort Parse: " + stopWatch.Elapsed.FormatElapsed()); stopWatch.Restart(); - messageBus.Publish(new ModDefinitionAnalyzeEvent(99.99)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(99.99)); var groupedConflicts = conflicts.GroupBy(p => p.TypeAndId); - var filteredConclicts = new List(); + var filteredConflicts = new List(); foreach (var conflict in groupedConflicts.Where(p => p.Count() > 1)) { if (conflict.Any(p => p.IsPlaceholder)) @@ -750,7 +764,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition nonPlaceholders.Add(priority.Definition); if (nonPlaceholders.Count > 1) { - filteredConclicts.AddRange(nonPlaceholders); + filteredConflicts.AddRange(nonPlaceholders); } } else @@ -758,14 +772,14 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var nonPlaceholders = conflict.Where(p => !p.IsPlaceholder); if (nonPlaceholders.Count() > 1) { - filteredConclicts.AddRange(nonPlaceholders); + filteredConflicts.AddRange(nonPlaceholders); } } } } else { - filteredConclicts.AddRange(conflict); + filteredConflicts.AddRange(conflict); } } @@ -775,7 +789,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var result = GetModelInstance(); result.Mode = patchStateMode; var conflictsIndexed = DIResolver.Get(); - await conflictsIndexed.InitMapAsync(filteredConclicts, true); + await conflictsIndexed.InitMapAsync(filteredConflicts, true); result.AllConflicts = indexedDefinitions; result.Conflicts = conflictsIndexed; var resolvedConflicts = DIResolver.Get(); @@ -793,7 +807,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var customConflicts = DIResolver.Get(); await customConflicts.InitMapAsync(null, true); result.CustomConflicts = customConflicts; - messageBus.Publish(new ModDefinitionAnalyzeEvent(100)); + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(100)); stopWatch.Stop(); Debug.WriteLine("FindConflictsAsync Init Result: " + stopWatch.Elapsed.FormatElapsed()); @@ -833,6 +847,7 @@ public virtual IReadOnlyList GetIgnoredMods(IConflictRe } } } + return mods; } @@ -844,7 +859,8 @@ public virtual IReadOnlyList GetIgnoredMods(IConflictRe /// Name of the collection. /// The mode. /// A Task<IIndexedDefinitions> representing the asynchronous operation. - /// Detected a mod which is potentially to large to parse. + /// Detected a mod which is potentially too large to parse. + /// Detected a mod which is potentially too large to parse. public virtual async Task GetModObjectsAsync(IGame game, IEnumerable mods, string collectionName, PatchStateMode mode) { if (game == null || mods == null || !mods.Any()) @@ -853,12 +869,13 @@ public virtual async Task GetModObjectsAsync(IGame game, IE } var tooLargeMod = false; - mods.AsParallel().WithDegreeOfParallelism(MaxModsToProcessInParallel).ForAll((m) => + mods.AsParallel().WithDegreeOfParallelism(MaxModsToProcessInParallel).ForAll(m => { if (tooLargeMod) { return; } + var size = Reader.GetTotalSize(m.FullPath, Shared.Constants.TextExtensions); if (size > MaxAllowedSource) { @@ -868,7 +885,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE if (tooLargeMod) { - throw new ModTooLargeException("Detected a mod which is potentially to large to parse."); + throw new ModTooLargeException("Detected a mod which is potentially too large to parse."); } var definitions = new ConcurrentBag(); @@ -879,7 +896,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE // Don't need full implementation just BOM check var provider = DefinitionInfoProviders.FirstOrDefault(p => p.CanProcess(game.Type)); - messageBus.Publish(new ModDefinitionLoadEvent(0)); + await messageBus.PublishAsync(new ModDefinitionLoadEvent(0)); var gameFolders = game.GameFolders.ToList(); if (mode == PatchStateMode.DefaultWithoutLocalization || mode == PatchStateMode.AdvancedWithoutLocalization || mode == PatchStateMode.ReadOnlyWithoutLocalization) @@ -887,10 +904,9 @@ public virtual async Task GetModObjectsAsync(IGame game, IE gameFolders = gameFolders.Where(p => !p.StartsWith(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase)).ToList(); } - mods.AsParallel().WithDegreeOfParallelism(MaxModsToProcessInParallel).WithExecutionMode(ParallelExecutionMode.ForceParallelism).ForAll((m) => + mods.AsParallel().WithDegreeOfParallelism(MaxModsToProcessInParallel).WithExecutionMode(ParallelExecutionMode.ForceParallelism).ForAll(m => { - IEnumerable result = null; - result = ParseModFiles(game, Reader.Read(m.FullPath, gameFolders), m, provider); + var result = ParseModFiles(game, Reader.Read(m.FullPath, gameFolders), m, provider); if (result?.Count() > 0) { foreach (var item in result) @@ -898,15 +914,17 @@ public virtual async Task GetModObjectsAsync(IGame game, IE definitions.Add(item); } } + lock (serviceLock) { processed++; var perc = GetProgressPercentage(total, processed, 100); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { messageBus.Publish(new ModDefinitionLoadEvent(perc)); previousProgress = perc; } + GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); @@ -917,15 +935,11 @@ public virtual async Task GetModObjectsAsync(IGame game, IE processed = 0; // Stellaris only (so far) - total = provider.SupportsInlineScripts ? definitions.Count * 2 : definitions.Count; + total = provider!.SupportsInlineScripts ? definitions.Count * 2 : definitions.Count; previousProgress = 0; List prunedDefinitions; var patchName = GenerateCollectionPatchName(collectionName); - var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters() - { - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - }); + var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }); // Process so far Giga related stuff for now. Scared what else might be valid for inline_scripts. List prunedInlineDefinitions; @@ -933,7 +947,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE { var tempIndex = DIResolver.Get(); await tempIndex.InitMapAsync(definitions); - prunedInlineDefinitions = new List(); + prunedInlineDefinitions = []; var reportedInlineErrors = new HashSet(); foreach (var item in definitions) { @@ -943,17 +957,17 @@ public virtual async Task GetModObjectsAsync(IGame game, IE addDefault = false; var path = Path.Combine(Parser.Common.Constants.Stellaris.InlineScripts, parametrizedParser.GetScriptPath(item.Code)); var pathCI = path.ToLowerInvariant(); - var files = (await tempIndex.GetByParentDirectoryAsync(Path.GetDirectoryName(path))).Where(p => Path.Combine(Path.GetDirectoryName(p.FileCI), Path.GetFileNameWithoutExtension(p.FileCI)).Equals(pathCI)).ToList(); - if (files.Any()) + var files = (await tempIndex.GetByParentDirectoryAsync(Path.GetDirectoryName(path))).Where(p => Path.Combine(Path.GetDirectoryName(p.FileCI)!, Path.GetFileNameWithoutExtension(p.FileCI)!).Equals(pathCI)).ToList(); + if (files.Count != 0) { var modOrder = mods.Select(p => p.Name).ToList(); var priorityDefinition = EvalDefinitionPriority(files.OrderBy(p => modOrder.IndexOf(p.ModName)).ToHashSet()); - if (priorityDefinition != null && priorityDefinition.Definition != null) + if (priorityDefinition is { Definition: not null }) { var parametrizedCode = parametrizedParser.Process(priorityDefinition.Definition.Code, item.Code); if (!string.IsNullOrWhiteSpace(parametrizedCode)) { - ValidationType validationType = ValidationType.Full; + var validationType = ValidationType.Full; if (item.UseSimpleValidation.GetValueOrDefault() || item.UseSimpleValidation == null) { validationType = MapValidationType(item); @@ -962,7 +976,8 @@ public virtual async Task GetModObjectsAsync(IGame game, IE { validationType = MapValidationType(priorityDefinition.Definition); } - var results = parserManager.Parse(new ParserManagerArgs() + + var results = parserManager.Parse(new ParserManagerArgs { ContentSHA = item.ContentSHA, // Want original file sha id File = item.File, // To trigger right parser @@ -978,13 +993,14 @@ public virtual async Task GetModObjectsAsync(IGame game, IE { MergeDefinitions(results.Concat(item.Variables)); } + if (results != null && results.Any()) { prunedInlineDefinitions.AddRange(results); } else if (!reportedInlineErrors.Contains($"{item.ModName} - {pathCI}")) { - // Could happen, will need mnually investigation though + // Could happen, will need manually investigation though var copy = CopyDefinition(item); copy.ValueType = ValueType.Invalid; copy.ErrorMessage = $"Inline script {path} failed to be processed. Please report to the author of Irony."; @@ -1004,18 +1020,21 @@ public virtual async Task GetModObjectsAsync(IGame game, IE reportedInlineErrors.Add($"{item.ModName} - {pathCI}"); } } + if (addDefault) { prunedInlineDefinitions.Add(item); } + processed++; var perc = GetProgressPercentage(total, processed, 100); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { await messageBus.PublishAsync(new ModDefinitionInvalidReplaceEvent(perc)); previousProgress = perc; } } + tempIndex.Dispose(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); GC.WaitForPendingFinalizers(); @@ -1025,6 +1044,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE { prunedInlineDefinitions = definitions.ToList(); } + definitions.Clear(); definitions = null; if (state != null && state.CustomConflicts.Any()) @@ -1034,7 +1054,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE await customIndexed.InitMapAsync(state.CustomConflicts); foreach (var item in prunedInlineDefinitions) { - bool addDefault = true; + var addDefault = true; if (item.ValueType == ValueType.Invalid) { var fileCodes = await customIndexed.GetByFileAsync(item.File); @@ -1048,7 +1068,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE var history = value.FirstOrDefault(); if (history != null && !string.IsNullOrWhiteSpace(history.Code)) { - fileDefs.AddRange(parserManager.Parse(new ParserManagerArgs() + fileDefs.AddRange(parserManager.Parse(new ParserManagerArgs { ContentSHA = item.ContentSHA, File = item.File, @@ -1060,25 +1080,29 @@ public virtual async Task GetModObjectsAsync(IGame game, IE } } } + if (fileDefs.Any()) { foreach (var def in fileDefs) { def.IsCustomPatch = true; } + addDefault = false; MergeDefinitions(fileDefs); prunedDefinitions.AddRange(fileDefs); } } } + if (addDefault) { prunedDefinitions.Add(item); } + processed++; var perc = GetProgressPercentage(total, processed, 100); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { await messageBus.PublishAsync(new ModDefinitionInvalidReplaceEvent(perc)); previousProgress = perc; @@ -1089,6 +1113,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE { prunedDefinitions = prunedInlineDefinitions.ToList(); } + prunedInlineDefinitions.Clear(); prunedInlineDefinitions = null; @@ -1110,11 +1135,7 @@ public virtual async Task GetPatchStateModeAsync(string collecti if (game != null && !string.IsNullOrWhiteSpace(collectionName)) { var patchName = GenerateCollectionPatchName(collectionName); - var @params = new ModPatchExporterParameters() - { - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - }; + var @params = new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }; var mode = await modPatchExporter.GetPatchStateModeAsync(@params); if (mode.HasValue) { @@ -1129,6 +1150,7 @@ public virtual async Task GetPatchStateModeAsync(string collecti } } } + return PatchStateMode.None; } @@ -1155,19 +1177,18 @@ public virtual async Task InitializePatchStateAsync(IConflictRe var game = GameService.GetSelected(); double previousProgress = 0; var allowCleanup = conflictResult != null && conflictResult.Mode != PatchStateMode.ReadOnly && conflictResult.Mode != PatchStateMode.ReadOnlyWithoutLocalization; + async Task cleanSingleMergeFiles(string directory, string patchName) { if (!allowCleanup) { return; } + var patchModDir = GetPatchModDirectory(game, patchName); - await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = patchModDir, - Path = directory - }); + await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = patchModDir, Path = directory }); } + async Task syncPatchFiles(IConflictResult conflicts, IEnumerable patchFiles, string patchName, int total, int processed, int maxProgress) { var patchModDir = GetPatchModDirectory(game, patchName); @@ -1183,43 +1204,40 @@ async Task syncPatchFiles(IConflictResult conflicts, IEnumerable pa { continue; } + if (!await conflicts.CustomConflicts.ExistsByFileAsync(file) && !await conflicts.OverwrittenConflicts.ExistsByFileAsync(file) && !await conflicts.ResolvedConflicts.ExistsByFileAsync(file)) { - cleaned = await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = patchModDir, - Path = file - }); + cleaned = await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = patchModDir, Path = file }); } + if (!cleaned) { var resolved = await conflicts.ResolvedConflicts.GetByDiskFileAsync(file); if (resolved.Any()) { - var overwritten = await conflicts.OverwrittenConflicts.GetByTypeAndIdAsync(resolved.FirstOrDefault().TypeAndId); - if (overwritten.Any() && overwritten.FirstOrDefault().DiskFileCI != resolved.FirstOrDefault().DiskFileCI) + var overwritten = await conflicts.OverwrittenConflicts.GetByTypeAndIdAsync(resolved.FirstOrDefault()!.TypeAndId); + if (overwritten.Any() && overwritten.FirstOrDefault()!.DiskFileCI != resolved.FirstOrDefault()!.DiskFileCI) { - await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = patchModDir, - Path = overwritten.FirstOrDefault().DiskFile - }); + await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = patchModDir, Path = overwritten.FirstOrDefault()!.DiskFile }); } } } } + processed++; var perc = GetProgressPercentage(total, processed, maxProgress); - if (previousProgress != perc) + if (previousProgress.IsNotNearlyEqual(perc)) { await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(perc)); previousProgress = perc; } } + return processed; } + async Task<(IIndexedDefinitions, int)> initAllIndexedDefinitions(IConflictResult conflictResult, int total, int processed, int maxProgress) { async void processedSearchItemHandler(object sender, ProcessedArgs args) @@ -1227,26 +1245,29 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) using var mutex = await searchInitLock.LockAsync(); processed++; var perc = GetProgressPercentage(total, processed, maxProgress); - if (previousProgress != perc) + if (previousProgress.IsNotNearlyEqual(perc)) { await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(perc)); previousProgress = perc; } + mutex.Dispose(); } var copy = DIResolver.Get(); var options = DIResolver.Get().GetOptions(); - string diskSearchPath = string.Empty; + var diskSearchPath = string.Empty; if (options.ConflictSolver.UseDiskSearch) { diskSearchPath = StorageProvider.GetRootStoragePath(); } + copy.UseSearch(diskSearchPath, nameof(conflictResult.AllConflicts)); if (options.ConflictSolver.UseHybridMemory) { copy.UseDiskStore(StorageProvider.GetRootStoragePath()); } + copy.SetAllowedType(AddToMapAllowedType.InvalidAndSpecial); var semaphore = new AsyncSemaphore(MaxDefinitionsToAdd); var searchDefinitions = new List(); @@ -1255,19 +1276,21 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) await semaphore.WaitAsync(); try { - IDefinition defCopy = item; + var defCopy = item; if (item.ValueType == ValueType.Invalid || item.IsSpecialFolder) { defCopy = PartialDefinitionCopy(item); } + await copy.AddToMapAsync(defCopy); if (defCopy.Tags != null && defCopy.Tags.Any() && !defCopy.IsFromGame) { searchDefinitions.Add(defCopy); } + processed++; var perc = GetProgressPercentage(total, processed, maxProgress); - if (previousProgress != perc) + if (previousProgress.IsNotNearlyEqual(perc)) { await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(perc)); previousProgress = perc; @@ -1287,23 +1310,16 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) if (game != null && conflictResult != null && !string.IsNullOrWhiteSpace(collectionName)) { - double perc = 0; + double perc; var patchName = GenerateCollectionPatchName(collectionName); await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(0)); - var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters() - { - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - }); - var patchFiles = modPatchExporter.GetPatchFiles(new ModPatchExporterParameters() - { - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - }); + var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }); + var patchFiles = modPatchExporter.GetPatchFiles(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }); foreach (var item in (await conflictResult.OverwrittenConflicts.GetAllAsync()).GroupBy(p => p.ParentDirectory)) { await cleanSingleMergeFiles(item.First().ParentDirectory, patchName); } + var all = await conflictResult.AllConflicts.GetAllAsync(); var total = patchFiles.Count() + all.Count() + all.Count(p => p.Tags != null && p.Tags.Any() && !p.IsFromGame); // Other all is the trie init counter if (state != null) @@ -1311,19 +1327,20 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) var resolvedConflicts = new List(state.ResolvedConflicts); var ignoredConflicts = new List(); total += state.Conflicts.Count() + (state.OverwrittenConflicts.Count() * 2) + 1; - int processed = 0; + var processed = 0; foreach (var item in state.Conflicts.GroupBy(p => p.TypeAndId)) { var files = ProcessPatchStateFiles(state, item, ref processed); var matchedConflicts = await FindPatchStateMatchedConflictsAsync(conflictResult.Conflicts, state, ignoredConflicts, item); await SyncPatchStateAsync(game, patchName, resolvedConflicts, item, files, matchedConflicts, !allowCleanup); perc = GetProgressPercentage(total, processed); - if (previousProgress != perc) + if (previousProgress.IsNotNearlyEqual(perc)) { await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(perc)); previousProgress = perc; } } + foreach (var item in state.OverwrittenConflicts.GroupBy(p => p.TypeAndId)) { processed += item.Count(); @@ -1340,10 +1357,11 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) files.RemoveAll(p => fileNames.Any(a => a.Equals(p, StringComparison.OrdinalIgnoreCase))); } } + var matchedConflicts = await conflictResult.OverwrittenConflicts.GetByTypeAndIdAsync(item.First().TypeAndId); await SyncPatchStatesAsync(matchedConflicts, item, patchName, game, !allowCleanup, files.ToArray()); perc = GetProgressPercentage(total, processed); - if (previousProgress != perc) + if (previousProgress.IsNotNearlyEqual(perc)) { await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(perc)); previousProgress = perc; @@ -1364,7 +1382,7 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) if (resolved.Any()) { definition = resolved.FirstOrDefault(); - definition.Order = item.Order; + definition!.Order = item.Order; definition.DiskFile = item.DiskFile; definition.File = item.File; definition.OverwrittenFileNames = item.OverwrittenFileNames; @@ -1377,6 +1395,7 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) } } } + var canExport = true; if (definition.ValueType == ValueType.OverwrittenObjectSingleFile) { @@ -1386,7 +1405,7 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) if (merged != null) { definition = PopulateModPath(merged, GetCollectionMods()).FirstOrDefault(); - alreadyMergedTypes.Add(definition.Type); + alreadyMergedTypes.Add(definition!.Type); } } else @@ -1394,19 +1413,18 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) canExport = false; } } + if (canExport && allowCleanup) { - await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters() + await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters { - Game = game.Type, - OverwrittenConflicts = new List() { definition }, - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) + Game = game.Type, OverwrittenConflicts = new List { definition }, RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }); } + processed++; perc = GetProgressPercentage(total, processed); - if (previousProgress != perc) + if (previousProgress.IsNotNearlyEqual(perc)) { await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(perc)); previousProgress = perc; @@ -1440,7 +1458,7 @@ await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters() if (allowCleanup) { - await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() + await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters { LoadOrder = GetCollectionMods(collectionName: collectionName).Select(p => p.DescriptorFile), Mode = MapPatchStateMode(conflicts.Mode), @@ -1455,6 +1473,7 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() HasGameDefinitions = await conflicts.AllConflicts.HasGameDefinitionsAsync() }); } + await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(100)); return conflicts; @@ -1476,11 +1495,12 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() if (resolved.Any()) { definition = resolved.FirstOrDefault(); - definition.Order = item.Order; + definition!.Order = item.Order; definition.DiskFile = item.DiskFile; definition.File = item.File; definition.OverwrittenFileNames = item.OverwrittenFileNames; } + var canExport = true; if (definition.ValueType == ValueType.OverwrittenObjectSingleFile) { @@ -1490,7 +1510,7 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() if (merged != null) { definition = PopulateModPath(merged, GetCollectionMods()).FirstOrDefault(); - alreadyMergedTypes.Add(definition.Type); + alreadyMergedTypes.Add(definition!.Type); } } else @@ -1498,22 +1518,21 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() canExport = false; } } + if (canExport && allowCleanup) { - if (await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters() - { - Game = game.Type, - OverwrittenConflicts = new List() { definition }, - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - })) + if (await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters + { + Game = game.Type, OverwrittenConflicts = new List { definition }, RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) + })) { exportedConflicts = true; } } + processed++; perc = GetProgressPercentage(total, processed); - if (previousProgress != perc) + if (previousProgress.IsNotNearlyEqual(perc)) { await messageBus.PublishAsync(new ModDefinitionPatchLoadEvent(perc)); previousProgress = perc; @@ -1525,7 +1544,7 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() if (exportedConflicts && allowCleanup) { - await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() + await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters { LoadOrder = GetCollectionMods(collectionName: collectionName).Select(p => p.DescriptorFile), Mode = MapPatchStateMode(conflictResult.Mode), @@ -1550,7 +1569,8 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() return conflictResult; } - }; + } + return null; } @@ -1558,16 +1578,17 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() /// Invalidates the state of the patch mod. /// /// Name of the collection. - /// true if XXXX, false otherwise. + /// true if invalid, false otherwise. public virtual bool InvalidatePatchModState(string collectionName) { var game = GameService.GetSelected(); if (game != null) { var patchName = GenerateCollectionPatchName(collectionName); - Cache.Invalidate(new CacheInvalidateParameters() { Region = CacheRegion, Prefix = game.Type, Keys = new List() { patchName } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = CacheRegion, Prefix = game.Type, Keys = new List { patchName } }); return true; } + return false; } @@ -1604,12 +1625,9 @@ public virtual Task LoadDefinitionContentsAsync(IDefinition definition, { return Task.FromResult(string.Empty); } + var patchName = GenerateCollectionPatchName(collectionName); - return modPatchExporter.LoadDefinitionContentsAsync(new ModPatchExporterParameters() - { - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - }, definition.File); + return modPatchExporter.LoadDefinitionContentsAsync(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }, definition.File); } /// @@ -1623,16 +1641,13 @@ public virtual async Task PatchHasGameDefinitionsAsync(string collectionNa if (game != null && !string.IsNullOrWhiteSpace(collectionName)) { var patchName = GenerateCollectionPatchName(collectionName); - var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters() - { - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - }, false); + var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }, false); if (state != null) { return state.HasGameDefinitions; } } + return false; } @@ -1645,43 +1660,37 @@ public virtual async Task PatchHasGameDefinitionsAsync(string collectionNa public virtual async Task PatchModNeedsUpdateAsync(string collectionName, IReadOnlyCollection loadOrder) { loadOrder ??= new List(); + List mapEvalState(IEnumerable definitions) { var result = new List(); if ((definitions?.Any()).GetValueOrDefault()) { - result.AddRange(definitions.Where(p => !p.IsFromGame).Select(m => new EvalState() - { - ContentSha = m.ContentSHA, - FileName = m.OriginalFileName, - FallBackFileName = m.File, - ModName = m.ModName - })); + result.AddRange(definitions!.Where(p => !p.IsFromGame).Select(m => new EvalState { ContentSha = m.ContentSHA, FileName = m.OriginalFileName, FallBackFileName = m.File, ModName = m.ModName })); } + return result; } + async Task evalState(IGame game, string patchName) { - Cache.Set(new CacheAddParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState() { CheckInProgress = true } }); + Cache.Set(new CacheAddParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState { CheckInProgress = true } }); var allMods = GetInstalledModsInternal(game, false); var mods = allMods.Where(p => loadOrder.Any(x => x.Equals(p.DescriptorFile))).ToList(); - var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters() - { - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - }, false); + var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }, false); if (state == null) { - Cache.Set(new CacheAddParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState() { NeedsUpdate = false, CheckInProgress = false } }); + Cache.Set(new CacheAddParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState { NeedsUpdate = false, CheckInProgress = false } }); return false; } // Check load order first if (!state.LoadOrder.SequenceEqual(loadOrder)) { - Cache.Set(new CacheAddParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState() { NeedsUpdate = true, CheckInProgress = false } }); + Cache.Set(new CacheAddParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState { NeedsUpdate = true, CheckInProgress = false } }); return true; } + var conflicts = new List(); conflicts.AddRange(mapEvalState(state.Conflicts)); conflicts.AddRange(mapEvalState(state.OverwrittenConflicts)); @@ -1697,25 +1706,26 @@ async Task evalState(IGame game, string patchName) foreach (var item in groupedMods.GroupBy(p => p.FileName)) { var definition = item.FirstOrDefault(); - var mod = mods.FirstOrDefault(p => p.Name.Equals(definition.ModName)); + var mod = mods.FirstOrDefault(p => p.Name.Equals(definition!.ModName)); if (mod == null) { // Mod no longer in collection, needs refresh break further checks... - Cache.Set(new CacheAddParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState() { NeedsUpdate = true, CheckInProgress = false } }); + Cache.Set(new CacheAddParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState { NeedsUpdate = true, CheckInProgress = false } }); return true; } else { - var info = Reader.GetFileInfo(mod.FullPath, definition.FileName); + var info = Reader.GetFileInfo(mod.FullPath, definition!.FileName); info ??= Reader.GetFileInfo(mod.FullPath, definition.FallBackFileName); if (info == null || !info.ContentSHA.Equals(definition.ContentSha)) { // File no longer in collection or content does not match, break further checks - Cache.Set(new CacheAddParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState() { NeedsUpdate = true, CheckInProgress = false } }); + Cache.Set(new CacheAddParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState { NeedsUpdate = true, CheckInProgress = false } }); return true; } } } + return false; }, cancellationToken.Token); } @@ -1731,13 +1741,15 @@ async Task evalState(IGame game, string patchName) var result = await task; if (result) { - cancellationToken.Cancel(); + await cancellationToken.CancelAsync(); return true; } } - Cache.Set(new CacheAddParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState() { NeedsUpdate = false, CheckInProgress = false } }); + + Cache.Set(new CacheAddParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName, Value = new PatchCollectionState { NeedsUpdate = false, CheckInProgress = false } }); return false; } + var game = GameService.GetSelected(); if (game != null && !string.IsNullOrWhiteSpace(collectionName)) { @@ -1746,21 +1758,23 @@ async Task evalState(IGame game, string patchName) { return false; } + var patchName = GenerateCollectionPatchName(collectionName); - var result = Cache.Get(new CacheGetParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName }); + var result = Cache.Get(new CacheGetParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName }); if (result != null) { while (result.CheckInProgress) { // Since another check is queued, wait and periodically check if the task is done... await Task.Delay(10); - result = Cache.Get(new CacheGetParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName }); + result = Cache.Get(new CacheGetParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName }); if (result == null) { await evalState(game, patchName); - result = Cache.Get(new CacheGetParameters() { Region = CacheRegion, Prefix = game.Type, Key = patchName }); + result = Cache.Get(new CacheGetParameters { Region = CacheRegion, Prefix = game.Type, Key = patchName }); } } + return result.NeedsUpdate; } else @@ -1768,6 +1782,7 @@ async Task evalState(IGame game, string patchName) return await evalState(game, patchName); } } + return false; } @@ -1784,14 +1799,15 @@ public Task RenamePatchCollectionAsync(string collectionName, string newCo { return Task.FromResult(false); } + var oldPatchName = GenerateCollectionPatchName(collectionName); var newPatchName = GenerateCollectionPatchName(newCollectionName); - return modPatchExporter.RenamePatchModAsync(new ModPatchExporterParameters() + return modPatchExporter.RenamePatchModAsync(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), ModPath = EvaluatePatchNamePath(game, oldPatchName), PatchPath = EvaluatePatchNamePath(game, newPatchName), - RenamePairs = new List>() { new(oldPatchName, newPatchName) } + RenamePairs = [new KeyValuePair(oldPatchName, newPatchName)] }); } @@ -1822,10 +1838,10 @@ public virtual Task ResetIgnoredConflictAsync(IConflictResult conflictResu /// /// Resets the patch state cache. /// - /// true if XXXX, false otherwise. + /// true if reset, false otherwise. public virtual bool ResetPatchStateCache() { - Cache.Invalidate(new CacheInvalidateParameters() { Region = ModsExportedRegion, Keys = new List() { ModExportedKey } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = ModsExportedRegion, Keys = new List { ModExportedKey } }); modPatchExporter.ResetCache(); return true; } @@ -1854,6 +1870,7 @@ public virtual string ResolveFullDefinitionPath(IDefinition definition) { return string.Empty; } + if (definition.IsFromGame) { return Path.Combine(pathResolver.GetPath(game), definition.File); @@ -1870,10 +1887,12 @@ public virtual string ResolveFullDefinitionPath(IDefinition definition) { return mod.FullPath; } + return Path.Combine(mod.FullPath, definition.File); } } } + return string.Empty; } @@ -1890,11 +1909,12 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe { return false; } + await EvalModIgnoreDefinitionsAsync(conflictResult); if (conflictResult.Mode != PatchStateMode.ReadOnly && conflictResult.Mode != PatchStateMode.ReadOnlyWithoutLocalization) { var patchName = GenerateCollectionPatchName(collectionName); - return await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() + return await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters { LoadOrder = GetCollectionMods(collectionName: collectionName).Select(p => p.DescriptorFile), Mode = MapPatchStateMode(conflictResult.Mode), @@ -1909,14 +1929,15 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync() }); } + return true; } /// - /// Shoulds the ignore game mods. + /// Checks whether it should ignore game mods. /// /// The conflict result. - /// true if XXXX, false otherwise. + /// true if it should ignore, false otherwise. public virtual bool? ShouldIgnoreGameMods(IConflictResult conflictResult) { if (conflictResult != null) @@ -1925,14 +1946,15 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe var lines = ignoredPaths.SplitOnNewLine(); return !lines.Any(p => p.Equals(ShowGameModsId)); } + return null; } /// - /// Shoulds the show reset conflicts. + /// Checks whether it should show the reset conflicts. /// /// The conflict result. - /// true if XXXX, false otherwise. + /// true if it should show, false otherwise. public virtual bool? ShouldShowResetConflicts(IConflictResult conflictResult) { if (conflictResult != null) @@ -1941,14 +1963,15 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe var lines = ignoredPaths.SplitOnNewLine(); return lines.Any(p => p.Equals(ShowResetConflicts)); } + return null; } /// - /// Shoulds the show self conflicts. + /// Checks whether if it should show self conflicts. /// /// The conflict result. - /// true if XXXX, false otherwise. + /// true if it should show, false otherwise. public virtual bool? ShouldShowSelfConflicts(IConflictResult conflictResult) { if (conflictResult != null) @@ -1957,6 +1980,7 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe var lines = ignoredPaths.SplitOnNewLine(); return lines.Any(p => p.Equals(ShowSelfConflictsId)); } + return null; } @@ -1964,7 +1988,7 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe /// Toggles the ignore game mods. /// /// The conflict result. - /// true if XXXX, false otherwise. + /// true if toggled, false otherwise. public virtual bool? ToggleIgnoreGameMods(IConflictResult conflictResult) { if (conflictResult != null) @@ -1980,9 +2004,11 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe { lines.Remove(ShowGameModsId); } + conflictResult.IgnoredPaths = string.Join(Environment.NewLine, lines).Trim(Environment.NewLine.ToCharArray()); return !shouldIgnore; } + return null; } @@ -1990,7 +2016,7 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe /// Toggles self mod conflicts. /// /// The conflict result. - /// true if XXXX, false otherwise. + /// true if toggled, false otherwise. public virtual bool? ToggleSelfModConflicts(IConflictResult conflictResult) { if (conflictResult != null) @@ -2006,9 +2032,11 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe { lines.Remove(ShowSelfConflictsId); } + conflictResult.IgnoredPaths = string.Join(Environment.NewLine, lines).Trim(Environment.NewLine.ToCharArray()); return !shouldShow; } + return null; } @@ -2016,7 +2044,7 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe /// Toggles the show reset conflicts. /// /// The conflict result. - /// true if XXXX, false otherwise. + /// true if toggled, false otherwise. public virtual bool? ToggleShowResetConflicts(IConflictResult conflictResult) { if (conflictResult != null) @@ -2032,9 +2060,11 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe { lines.Remove(ShowResetConflicts); } + conflictResult.IgnoredPaths = string.Join(Environment.NewLine, lines).Trim(Environment.NewLine.ToCharArray()); return !shouldReset; } + return null; } @@ -2050,26 +2080,22 @@ public virtual IValidateResult Validate(IDefinition definition) if (definition != null) { var lines = definition.Code.SplitOnNewLine(); - var args = new ParserArgs - { - Lines = lines, - File = definition.File, - ValidationType = MapValidationType(definition) - }; - IEnumerable validation = definition.ValueType != ValueType.Binary ? validateParser.Validate(args) : null; + var args = new ParserArgs { Lines = lines, File = definition.File, ValidationType = MapValidationType(definition) }; + var validation = definition.ValueType != ValueType.Binary ? validateParser.Validate(args) : null; if (validation != null && validation.Any()) { - result.ErrorMessage = validation.FirstOrDefault().ErrorMessage; - result.ErrorLine = validation.FirstOrDefault().ErrorLine; - result.ErrorColumn = validation.FirstOrDefault().ErrorColumn; + result.ErrorMessage = validation.FirstOrDefault()!.ErrorMessage; + result.ErrorLine = validation.FirstOrDefault()!.ErrorLine; + result.ErrorColumn = validation.FirstOrDefault()!.ErrorColumn; result.IsValid = false; } } + return result; } /// - /// Evals the definitions. + /// Evaluates the definitions. /// /// The indexed definitions. /// The conflicts. @@ -2079,25 +2105,29 @@ public virtual IValidateResult Validate(IDefinition definition) /// The file conflict cache. /// The mod sha conflict cache. /// A Task representing the asynchronous operation. - protected virtual async Task EvalDefinitionsAsync(IIndexedDefinitions indexedDefinitions, HashSet conflicts, IEnumerable definitions, IList modOrder, PatchStateMode patchStateMode, Dictionary fileConflictCache, Dictionary> modShaConflictCache) + protected virtual async Task EvalDefinitionsAsync(IIndexedDefinitions indexedDefinitions, HashSet conflicts, IEnumerable definitions, IList modOrder, PatchStateMode patchStateMode, + Dictionary fileConflictCache, Dictionary> modShaConflictCache) { async Task existsInLastFile(IDefinition definition) { var result = true; var fileDefs = await indexedDefinitions.GetByFileAsync(definition.FileCI); - var lastMod = fileDefs.GroupBy(p => p.ModName).Select(p => p.First()).OrderByDescending(p => modOrder.IndexOf(p.ModName)).FirstOrDefault(); + var lastMod = fileDefs.GroupBy(p => p.ModName).Select(p => p.First()).MaxBy(p => modOrder.IndexOf(p.ModName)); if (lastMod != null) { result = fileDefs.Any(p => p.ModName.Equals(lastMod.ModName) && p.TypeAndId.Equals(definition.TypeAndId)); } + return result; } - bool anyWholeTextFile = definitions.Any(p => p.ValueType == ValueType.WholeTextFile); + + var anyWholeTextFile = definitions.Any(p => p.ValueType == ValueType.WholeTextFile); var validDefinitions = new HashSet(); foreach (var item in definitions.Where(p => IsValidDefinitionType(p) || (anyWholeTextFile && p.ValueType == ValueType.EmptyFile))) { validDefinitions.Add(item); } + var processed = new HashSet(); foreach (var def in validDefinitions) { @@ -2105,11 +2135,13 @@ async Task existsInLastFile(IDefinition definition) { continue; } + var allConflicts = (await indexedDefinitions.GetByTypeAndIdAsync(def.Type, def.Id)).Where(p => IsValidDefinitionType(p) || (anyWholeTextFile && p.ValueType == ValueType.EmptyFile)); foreach (var conflict in allConflicts) { processed.Add(conflict); } + if (allConflicts.Count() > 1) { if (!allConflicts.All(p => p.DefinitionSHA.Equals(def.DefinitionSHA))) @@ -2121,6 +2153,7 @@ async Task existsInLastFile(IDefinition definition) { continue; } + var hasOverrides = allConflicts.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(p => p.Equals(conflict.ModName))); if (hasOverrides && (patchStateMode == PatchStateMode.Default || patchStateMode == PatchStateMode.DefaultWithoutLocalization)) { @@ -2135,10 +2168,13 @@ async Task existsInLastFile(IDefinition definition) fileNames.Add(fileName); } } + conflict.AdditionalFileNames = fileNames; } + continue; } + validConflicts.Add(conflict); } @@ -2157,6 +2193,7 @@ async Task existsInLastFile(IDefinition definition) continue; } } + var shaMatches = validConflictsGroup.FirstOrDefault(p => p.Key == item.DefinitionSHA); if (shaMatches.Count() > 1) { @@ -2168,6 +2205,7 @@ async Task existsInLastFile(IDefinition definition) fileNames.Add(fileName); } } + item.AdditionalFileNames = fileNames; if (item.IsPlaceholder && shaMatches.Any(p => !p.IsPlaceholder)) { @@ -2175,6 +2213,7 @@ async Task existsInLastFile(IDefinition definition) item.IsPlaceholder = false; } } + item.ExistsInLastFile = await existsInLastFile(item); conflicts.Add(item); if (!item.IsFromGame) @@ -2185,10 +2224,7 @@ async Task existsInLastFile(IDefinition definition) } else { - var sha = new List - { - item.DefinitionSHA - }; + var sha = new List { item.DefinitionSHA }; modShaConflictCache[item.TypeAndId] = sha; } } @@ -2223,10 +2259,7 @@ async Task existsInLastFile(IDefinition definition) } else { - var sha = new List - { - def.DefinitionSHA - }; + var sha = new List { def.DefinitionSHA }; modShaConflictCache[def.TypeAndId] = sha; } } @@ -2261,10 +2294,7 @@ async Task existsInLastFile(IDefinition definition) } else { - var sha = new List - { - def.DefinitionSHA - }; + var sha = new List { def.DefinitionSHA }; modShaConflictCache[def.TypeAndId] = sha; } } @@ -2283,7 +2313,7 @@ async Task existsInLastFile(IDefinition definition) } /// - /// Evals the mod ignore definitions. + /// Evaluates the mod ignore definitions. /// /// The conflict result. /// A Task representing the asynchronous operation. @@ -2299,6 +2329,7 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead return hierarchicalDefinition.Mods.Count > item.Count; } } + return true; } @@ -2352,6 +2383,7 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead } } } + if (!showResetConflicts) { foreach (var topConflict in conflictResult.Conflicts.GetHierarchicalDefinitions()) @@ -2362,24 +2394,24 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead { if (!item.Mods.Any(allowedMods.Contains) || !canAllowForbiddenMod(item, forbiddenMods)) { - if (!alreadyIgnored.Contains(item.Key)) + if (alreadyIgnored.Add(item.Key)) { - alreadyIgnored.Add(item.Key); await ruleIgnoredDefinitions.AddToMapAsync((await conflictResult.Conflicts.GetByTypeAndIdAsync(item.Key)).First()); } } } + var name = topConflict.Name; if (!name.EndsWith(Path.DirectorySeparatorChar)) { name = $"{name}{Path.DirectorySeparatorChar}"; } + var invalid = topConflict.Children.Where(p => ignoreRules.Any(r => EvalWildcard(r, p.FileNames.ToArray()))).Where(p => !includeRules.Any(r => EvalWildcard(r, p.FileNames.ToArray()))); foreach (var item in invalid) { - if (!alreadyIgnored.Contains(item.Key)) + if (alreadyIgnored.Add(item.Key)) { - alreadyIgnored.Add(item.Key); await ruleIgnoredDefinitions.AddToMapAsync((await conflictResult.Conflicts.GetByTypeAndIdAsync(item.Key)).First()); } } @@ -2388,9 +2420,8 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead { foreach (var item in topConflict.Children) { - if (!alreadyIgnored.Contains(item.Key)) + if (alreadyIgnored.Add(item.Key)) { - alreadyIgnored.Add(item.Key); await ruleIgnoredDefinitions.AddToMapAsync((await conflictResult.Conflicts.GetByTypeAndIdAsync(item.Key)).First()); } } @@ -2398,15 +2429,15 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead } } } + if (showResetConflicts) { foreach (var topConflict in conflictResult.Conflicts.GetHierarchicalDefinitions()) { foreach (var item in topConflict.Children) { - if (item.ResetType == ResetType.None && !alreadyIgnored.Contains(item.Key)) + if (item.ResetType == ResetType.None && alreadyIgnored.Add(item.Key)) { - alreadyIgnored.Add(item.Key); await ruleIgnoredDefinitions.AddToMapAsync((await conflictResult.Conflicts.GetByTypeAndIdAsync(item.Key)).First()); } } @@ -2420,29 +2451,29 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead { foreach (var item in topConflict.Children.Where(p => p.Mods.Count <= 1)) { - if (ignoreGameMods && item.NonGameDefinitions <= 1 && !alreadyIgnored.Contains(item.Key)) + if (ignoreGameMods && item.NonGameDefinitions <= 1 && alreadyIgnored.Add(item.Key)) { - alreadyIgnored.Add(item.Key); await ruleIgnoredDefinitions.AddToMapAsync((await conflictResult.Conflicts.GetByTypeAndIdAsync(item.Key)).First()); } - if (ignoreSelfConflicts && item.NonGameDefinitions > 1 && !alreadyIgnored.Contains(item.Key)) + + if (ignoreSelfConflicts && item.NonGameDefinitions > 1 && alreadyIgnored.Add(item.Key)) { - alreadyIgnored.Add(item.Key); await ruleIgnoredDefinitions.AddToMapAsync((await conflictResult.Conflicts.GetByTypeAndIdAsync(item.Key)).First()); } } } } } + conflictResult.RuleIgnoredConflicts = ruleIgnoredDefinitions; } /// - /// Evals the wildcard. + /// Evaluates the wildcard. /// /// The pattern. /// The content. - /// true if XXXX, false otherwise. + /// true if evaluated the wildcard, false otherwise. protected virtual bool EvalWildcard(string pattern, params string[] values) { foreach (var item in values) @@ -2463,6 +2494,7 @@ protected virtual bool EvalWildcard(string pattern, params string[] values) } } } + return false; } @@ -2473,7 +2505,7 @@ protected virtual bool EvalWildcard(string pattern, params string[] values) /// The definition. /// Name of the collection. /// Type of the export. - /// true if XXXX, false otherwise. + /// true if exported, false otherwise. protected virtual async Task ExportModPatchDefinitionAsync(IConflictResult conflictResult, IDefinition definition, string collectionName, ExportType exportType) { var game = GameService.GetSelected(); @@ -2485,24 +2517,14 @@ protected virtual async Task ExportModPatchDefinitionAsync(IConflictResult if (!allMods.Any(p => p.Name.Equals(patchName))) { mod = GeneratePatchModDescriptor(allMods, game, patchName); - await ModWriter.CreateModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = game.UserDirectory, - Path = Shared.Constants.ModDirectory - }); - await ModWriter.CreateModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = GetPatchModDirectory(game, patchName), - }); + await ModWriter.CreateModDirectoryAsync(new ModWriterParameters { RootDirectory = game.UserDirectory, Path = Shared.Constants.ModDirectory }); + await ModWriter.CreateModDirectoryAsync(new ModWriterParameters { RootDirectory = GetPatchModDirectory(game, patchName) }); if (game.ModDescriptorType == ModDescriptorType.JsonMetadata) { - await ModWriter.CreateModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = game.UserDirectory, - Path = Shared.Constants.JsonModDirectory - }); + await ModWriter.CreateModDirectoryAsync(new ModWriterParameters { RootDirectory = game.UserDirectory, Path = Shared.Constants.JsonModDirectory }); } - await ModWriter.WriteDescriptorAsync(new ModWriterParameters() + + await ModWriter.WriteDescriptorAsync(new ModWriterParameters { Mod = mod, RootDirectory = game.UserDirectory, @@ -2511,21 +2533,17 @@ await ModWriter.WriteDescriptorAsync(new ModWriterParameters() DescriptorType = MapDescriptorType(game.ModDescriptorType) }, IsPatchMod(mod)); allMods.Add(mod); - Cache.Invalidate(new CacheInvalidateParameters() { Region = ModsCacheRegion, Prefix = game.Type, Keys = new List { GetModsCacheKey(true), GetModsCacheKey(false) } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = ModsCacheRegion, Prefix = game.Type, Keys = new List { GetModsCacheKey(true), GetModsCacheKey(false) } }); } else { mod = allMods.FirstOrDefault(p => p.Name.Equals(patchName)); } + var definitionMod = allMods.FirstOrDefault(p => p.Name.Equals(definition.ModName)); if (definitionMod != null || definition.IsFromGame) { - var args = new ModPatchExporterParameters() - { - Game = game.Type, - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) - }; + var args = new ModPatchExporterParameters { Game = game.Type, RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }; var exportPatches = new HashSet(); switch (exportType) { @@ -2546,20 +2564,15 @@ await ModWriter.WriteDescriptorAsync(new ModWriterParameters() break; } - var state = Cache.Get(new CacheGetParameters() { Region = ModsExportedRegion, Key = ModExportedKey }); + var state = Cache.Get(new CacheGetParameters { Region = ModsExportedRegion, Key = ModExportedKey }); if (state == null || state.Exported.GetValueOrDefault() == false) { await Task.Run(() => { - ModWriter.ApplyModsAsync(new ModWriterParameters() - { - AppendOnly = true, - TopPriorityMods = new List() { mod }, - RootDirectory = game.UserDirectory, - DescriptorType = MapDescriptorType(game.ModDescriptorType) - }).ConfigureAwait(false); + ModWriter.ApplyModsAsync(new ModWriterParameters { AppendOnly = true, TopPriorityMods = new List { mod }, RootDirectory = game.UserDirectory, DescriptorType = MapDescriptorType(game.ModDescriptorType) }) + .ConfigureAwait(false); }).ConfigureAwait(false); - Cache.Set(new CacheAddParameters() { Region = ModsExportedRegion, Key = ModExportedKey, Value = new ModsExportedState() { Exported = true } }); + Cache.Set(new CacheAddParameters { Region = ModsExportedRegion, Key = ModExportedKey, Value = new ModsExportedState { Exported = true } }); } // Reset type flag since it was resolved now @@ -2583,25 +2596,22 @@ await Task.Run(() => var overwritten = await conflictResult.OverwrittenConflicts.GetByTypeAndIdAsync(definition.TypeAndId); if (overwritten.Any()) { - definition.Order = overwritten.FirstOrDefault().Order; + definition.Order = overwritten.FirstOrDefault()!.Order; } } + exportResult = await modPatchExporter.ExportDefinitionAsync(args); if (exportResult) { var overwritten = await conflictResult.OverwrittenConflicts.GetByTypeAndIdAsync(definition.TypeAndId); - if (overwritten.Any() && overwritten.FirstOrDefault().DiskFileCI != definition.DiskFileCI) + if (overwritten.Any() && overwritten.FirstOrDefault()!.DiskFileCI != definition.DiskFileCI) { - await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = GetPatchModDirectory(game, patchName), - Path = overwritten.FirstOrDefault().DiskFile - }); + await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = GetPatchModDirectory(game, patchName), Path = overwritten.FirstOrDefault()!.DiskFile }); } } } - var stateResult = await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() + var stateResult = await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters { LoadOrder = GetCollectionMods(collectionName: collectionName).Select(p => p.DescriptorFile), Mode = MapPatchStateMode(conflictResult.Mode), @@ -2619,6 +2629,7 @@ await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() return exportPatches.Any() ? exportResult && stateResult : stateResult; } } + return false; } @@ -2648,6 +2659,7 @@ protected virtual async Task> FindPatchStateMatchedConf } } } + return matchedConflicts; } @@ -2663,6 +2675,7 @@ protected virtual async Task> GetDefinitionOrDefaultAsync(IIn var defs = await definitions.GetAllAsync(); return defs.ToList(); } + return new List(); } @@ -2684,6 +2697,7 @@ protected virtual double GetProgressPercentage(double total, double processed, d { perc = maxPerc; } + return perc; } @@ -2699,6 +2713,7 @@ protected virtual bool IsCachedDefinitionDifferent(IEnumerable curr { return true; } + var cachedDiffs = cachedConflicts.Where(p => currentConflicts.Any(a => a.FileCI.Equals(p.FileCI) && a.DefinitionSHA.Equals(p.DefinitionSHA))); return cachedDiffs.Count() != cachedConflicts.Count(); } @@ -2726,7 +2741,7 @@ protected virtual PatchStateMode MapPatchStateMode(IO.Common.PatchStateMode mode IO.Common.PatchStateMode.Advanced => PatchStateMode.Advanced, IO.Common.PatchStateMode.AdvancedWithoutLocalization => PatchStateMode.AdvancedWithoutLocalization, IO.Common.PatchStateMode.DefaultWithoutLocalization => PatchStateMode.DefaultWithoutLocalization, - _ => PatchStateMode.None, + _ => PatchStateMode.None }; } @@ -2735,6 +2750,7 @@ protected virtual PatchStateMode MapPatchStateMode(IO.Common.PatchStateMode mode /// /// The mode. /// IO.Common.PatchStateMode. + /// Invalid readonly mode /// Invalid readonly mode protected virtual IO.Common.PatchStateMode MapPatchStateMode(PatchStateMode mode) { @@ -2742,13 +2758,14 @@ protected virtual IO.Common.PatchStateMode MapPatchStateMode(PatchStateMode mode { throw new ArgumentException("Invalid readonly mode"); } + return mode switch { PatchStateMode.Default => IO.Common.PatchStateMode.Default, PatchStateMode.Advanced => IO.Common.PatchStateMode.Advanced, PatchStateMode.AdvancedWithoutLocalization => IO.Common.PatchStateMode.AdvancedWithoutLocalization, PatchStateMode.DefaultWithoutLocalization => IO.Common.PatchStateMode.DefaultWithoutLocalization, - _ => IO.Common.PatchStateMode.None, + _ => IO.Common.PatchStateMode.None }; } @@ -2767,6 +2784,7 @@ protected virtual ValidationType MapValidationType(IDefinition definition) { return ValidationType.SkipAll; } + return ValidationType.Full; } @@ -2797,6 +2815,7 @@ static void appendLine(StringBuilder sb, IEnumerable lines, int indent = } } } + static void mergeCode(StringBuilder sb, string codeTag, string separator, IEnumerable variables, IEnumerable lines) { if (Shared.Constants.CodeSeparators.ClosingSeparators.Map.TryGetValue(separator, out var value)) @@ -2813,7 +2832,7 @@ static void mergeCode(StringBuilder sb, string codeTag, string separator, IEnume } else { - bool varsInserted = false; + var varsInserted = false; foreach (var item in lines) { var splitLines = item.SplitOnNewLine(); @@ -2832,6 +2851,7 @@ static void mergeCode(StringBuilder sb, string codeTag, string separator, IEnume } } } + sb.AppendLine(closingTag); } else @@ -2842,6 +2862,7 @@ static void mergeCode(StringBuilder sb, string codeTag, string separator, IEnume var splitLines = item.SplitOnNewLine(); appendLine(sb, splitLines, 4); } + foreach (var item in lines) { var splitLines = item.SplitOnNewLine(); @@ -2855,9 +2876,9 @@ static void mergeCode(StringBuilder sb, string codeTag, string separator, IEnume copy.ValueType = ValueType.OverwrittenObjectSingleFile; copy.IsFromGame = false; var groups = definitions.GroupBy(p => p.CodeTag, StringComparer.OrdinalIgnoreCase); - foreach (var group in groups.OrderBy(p => p.FirstOrDefault().CodeTag, StringComparer.OrdinalIgnoreCase)) + foreach (var group in groups.OrderBy(p => p.FirstOrDefault()!.CodeTag, StringComparer.OrdinalIgnoreCase)) { - bool hasCodeTag = !string.IsNullOrWhiteSpace(group.FirstOrDefault().CodeTag); + var hasCodeTag = !string.IsNullOrWhiteSpace(group.FirstOrDefault()!.CodeTag); if (!hasCodeTag) { var namespaces = group.Where(p => hasCode(p.File, p.Code) && p.ValueType == ValueType.Namespace); @@ -2873,7 +2894,7 @@ static void mergeCode(StringBuilder sb, string codeTag, string separator, IEnume var other = group.Where(p => p.ValueType != ValueType.Variable && p.ValueType != ValueType.Namespace && hasCode(p.File, p.Code)); var vars = namespaces.Select(p => p.OriginalCode).Concat(variables.Select(p => p.OriginalCode)); var code = other.Select(p => p.OriginalCode); - mergeCode(sb, group.FirstOrDefault().CodeTag, group.FirstOrDefault().CodeSeparator, vars, code); + mergeCode(sb, group.FirstOrDefault()!.CodeTag, group.FirstOrDefault()!.CodeSeparator, vars, code); } } @@ -2891,9 +2912,9 @@ protected virtual IModIgnoreConfiguration ParseIgnoreModLine(string line) var parsed = line.StandardizeDirectorySeparator().Trim().TrimStart(Path.DirectorySeparatorChar); if (parsed.StartsWith(ModNameIgnoreId)) { - string[] statements = line.Split(IgnoreRulesSeparator); + var statements = line.Split(IgnoreRulesSeparator); var ignoredModName = statements[0].Replace(ModNameIgnoreId, string.Empty).Trim(); - int counter = 2; + var counter = 2; if (statements.Length > 1) { if (int.TryParse(statements[1].Replace(ModNameIgnoreCounterId, string.Empty).Trim(), out var val)) @@ -2901,11 +2922,13 @@ protected virtual IModIgnoreConfiguration ParseIgnoreModLine(string line) counter = val; } } + var model = GetModelInstance(); model.ModName = ignoredModName; model.Count = counter; return model; } + return null; } @@ -2923,10 +2946,11 @@ protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable { return null; } + var definitions = new List(); foreach (var fileInfo in fileInfos) { - var fileDefs = parserManager.Parse(new ParserManagerArgs() + var fileDefs = parserManager.Parse(new ParserManagerArgs { ContentSHA = fileInfo.ContentSHA, File = fileInfo.FileName, @@ -2940,13 +2964,13 @@ protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable if (fileDefs.Any()) { // Validate and see whether we need to check encoding - if (!fileDefs.Any(p => p.ValueType == ValueType.Invalid)) + if (fileDefs.All(p => p.ValueType != ValueType.Invalid)) { - if (!fileDefs.Any(p => p.ValueType == ValueType.Binary) && definitionInfoProvider != null && !definitionInfoProvider.IsValidEncoding(Path.GetDirectoryName(fileInfo.FileName), fileInfo.Encoding)) + if (fileDefs.All(p => p.ValueType != ValueType.Binary) && definitionInfoProvider != null && !definitionInfoProvider.IsValidEncoding(Path.GetDirectoryName(fileInfo.FileName), fileInfo.Encoding)) { var definition = DIResolver.Get(); definition.ErrorMessage = "File has invalid encoding, please use UTF-8-BOM Encoding."; - definition.Id = Path.GetFileName(fileInfo.FileName).ToLowerInvariant(); + definition.Id = Path.GetFileName(fileInfo.FileName)!.ToLowerInvariant(); definition.ValueType = ValueType.Invalid; definition.OriginalCode = definition.Code = string.Join(Environment.NewLine, fileInfo.Content ?? new List()); definition.ContentSHA = fileInfo.ContentSHA; @@ -2960,10 +2984,12 @@ protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable continue; } } + MergeDefinitions(fileDefs); definitions.AddRange(fileDefs); } } + return definitions; } @@ -2971,19 +2997,21 @@ protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable /// Partials the definition copy. /// /// The definition. - /// if set to true [copy aditional filenames]. + /// The copy additional filenames. /// IDefinition. - protected virtual IDefinition PartialDefinitionCopy(IDefinition definition, bool copyAditionalFilenames = true) + protected virtual IDefinition PartialDefinitionCopy(IDefinition definition, bool copyAdditionalFilenames = true) { if (definition.ValueType == ValueType.Invalid || definition.AllowDuplicate) { return CopyDefinition(definition); } + var copy = DIResolver.Get(); - if (copyAditionalFilenames) + if (copyAdditionalFilenames) { copy.AdditionalFileNames = definition.AdditionalFileNames; } + copy.DiskFile = definition.DiskFile; copy.File = definition.File; copy.Id = definition.Id; @@ -3008,9 +3036,9 @@ protected virtual IDefinition PartialDefinitionCopy(IDefinition definition, bool /// The conflict result. /// Name of the patch. /// The type. - /// The state provinder. + /// The state provider. /// IDefinition. - protected virtual async Task ProcessOverwrittenSingleFileDefinitionsAsync(IConflictResult conflictResult, string patchName, string type, Tuple stateProvinder = null) + protected virtual async Task ProcessOverwrittenSingleFileDefinitionsAsync(IConflictResult conflictResult, string patchName, string type, Tuple stateProvider = null) { static string cleanString(string text) { @@ -3018,17 +3046,20 @@ static string cleanString(string text) text = text.Replace(" ", string.Empty).Replace("\t", string.Empty).Trim(); return text; } - static string getNextVariableName(List exportDefinitons, IDefinition definition) + + static string getNextVariableName(List exportDefinitions, IDefinition definition) { - var count = exportDefinitons.Where(p => p.Id.Equals(definition.Id, StringComparison.OrdinalIgnoreCase)).Count() + 1; + var count = exportDefinitions.Count(p => p.Id.Equals(definition.Id, StringComparison.OrdinalIgnoreCase)) + 1; var name = $"{definition.Id}_{count}"; - while (exportDefinitons.Any(p => p.Id.Equals(name, StringComparison.OrdinalIgnoreCase))) + while (exportDefinitions.Any(p => p.Id.Equals(name, StringComparison.OrdinalIgnoreCase))) { count++; name = $"{definition.Id}_{count}"; } + return name; } + void parseNameSpaces(List exportDefinitions, IDefinition def) { var namespaces = def.Variables?.Where(p => p.ValueType == ValueType.Namespace); @@ -3046,6 +3077,7 @@ void parseNameSpaces(List exportDefinitions, IDefinition def) } } } + void parseVariables(List exportDefinitions, IDefinition def) { var variables = def.Variables?.Where(p => p.ValueType == ValueType.Variable); @@ -3056,13 +3088,18 @@ void parseVariables(List exportDefinitions, IDefinition def) var copy = CopyDefinition(variable); var oldId = copy.Id; copy.Id = getNextVariableName(exportDefinitions, variable); - copy.Code = string.Join(" ", copy.Code.Split(" ", StringSplitOptions.None).Select(p => p.Contains(oldId) ? string.Join(Environment.NewLine, p.SplitOnNewLine(false).Select(s => s.Trim() == oldId ? s.Replace(oldId, copy.Id) : s)) : p)); - copy.OriginalCode = string.Join(" ", copy.OriginalCode.Split(" ", StringSplitOptions.None).Select(p => p.Contains(oldId) ? string.Join(Environment.NewLine, p.SplitOnNewLine(false).Select(s => s.Trim() == oldId ? s.Replace(oldId, copy.Id) : s)) : p)); + copy.Code = string.Join(" ", + copy.Code.Split(" ", StringSplitOptions.None).Select(p => p.Contains(oldId) ? string.Join(Environment.NewLine, p.SplitOnNewLine(false).Select(s => s.Trim() == oldId ? s.Replace(oldId, copy.Id) : s)) : p)); + copy.OriginalCode = string.Join(" ", + copy.OriginalCode.Split(" ", StringSplitOptions.None) + .Select(p => p.Contains(oldId) ? string.Join(Environment.NewLine, p.SplitOnNewLine(false).Select(s => s.Trim() == oldId ? s.Replace(oldId, copy.Id) : s)) : p)); copy.CodeTag = def.CodeTag; copy.CodeSeparator = def.CodeSeparator; exportDefinitions.Add(copy); - def.Code = string.Join(" ", def.Code.Split(" ", StringSplitOptions.None).Select(p => p.Contains(oldId) ? string.Join(Environment.NewLine, p.SplitOnNewLine(false).Select(s => s.Trim() == oldId ? s.Replace(oldId, copy.Id) : s)) : p)); - def.OriginalCode = string.Join(" ", def.OriginalCode.Split(" ", StringSplitOptions.None).Select(p => p.Contains(oldId) ? string.Join(Environment.NewLine, p.SplitOnNewLine(false).Select(s => s.Trim() == oldId ? s.Replace(oldId, copy.Id) : s)) : p)); + def.Code = string.Join(" ", + def.Code.Split(" ", StringSplitOptions.None).Select(p => p.Contains(oldId) ? string.Join(Environment.NewLine, p.SplitOnNewLine(false).Select(s => s.Trim() == oldId ? s.Replace(oldId, copy.Id) : s)) : p)); + def.OriginalCode = string.Join(" ", + def.OriginalCode.Split(" ", StringSplitOptions.None).Select(p => p.Contains(oldId) ? string.Join(Environment.NewLine, p.SplitOnNewLine(false).Select(s => s.Trim() == oldId ? s.Replace(oldId, copy.Id) : s)) : p)); } } } @@ -3073,20 +3110,15 @@ void parseVariables(List exportDefinitions, IDefinition def) var modOrder = GetCollectionMods().Select(p => p.Name).ToList(); var game = GameService.GetSelected(); var export = new List(); - var all = (await conflictResult.AllConflicts.GetByParentDirectoryAsync(definitions.FirstOrDefault().ParentDirectoryCI)).Where(IsValidDefinitionType); + var all = (await conflictResult.AllConflicts.GetByParentDirectoryAsync(definitions.FirstOrDefault()!.ParentDirectoryCI)).Where(IsValidDefinitionType); var ordered = all.GroupBy(p => p.TypeAndId).Select(p => { if (p.Any(v => v.AllowDuplicate)) { return p.GroupBy(p => p.File).Select(v => { - var defininition = v.FirstOrDefault(); - return new DefinitionOrderSort() - { - TypeAndId = defininition.TypeAndId, - Order = defininition.Order, - File = Path.GetFileNameWithoutExtension(defininition.File) - }; + var definition = v.FirstOrDefault(); + return new DefinitionOrderSort { TypeAndId = definition!.TypeAndId, Order = definition.Order, File = Path.GetFileNameWithoutExtension(definition.File) }; }); } else @@ -3094,34 +3126,21 @@ void parseVariables(List exportDefinitions, IDefinition def) var partialCopy = new List(); p.ToList().ForEach(x => partialCopy.Add(PartialDefinitionCopy(x, false))); var priority = EvalDefinitionPriorityInternal(partialCopy.OrderBy(x => modOrder.IndexOf(x.ModName)), true); - return new List() - { - new() - { - TypeAndId = priority.Definition.TypeAndId, - Order = priority.Definition.Order, - File = Path.GetFileNameWithoutExtension(priority.FileName) - } - }; + return new List { new() { TypeAndId = priority.Definition.TypeAndId, Order = priority.Definition.Order, File = Path.GetFileNameWithoutExtension(priority.FileName) } }; } }).SelectMany(p => p).GroupBy(p => p.File).OrderBy(p => p.Key, StringComparer.Ordinal).SelectMany(p => p.OrderBy(x => x.Order)).ToList(); - var fullyOrdered = ordered.Select(p => new DefinitionOrderSort() - { - TypeAndId = p.TypeAndId, - Order = ordered.IndexOf(p), - File = p.File - }).ToList(); + var fullyOrdered = ordered.Select(p => new DefinitionOrderSort { TypeAndId = p.TypeAndId, Order = ordered.IndexOf(p), File = p.File }).ToList(); var sortExport = new List(); var infoProvider = DefinitionInfoProviders.FirstOrDefault(p => p.CanProcess(game.Type) && p.IsFullyImplemented); var overwrittenFileNames = new HashSet(); async Task handleDefinition(IDefinition item) { - IDefinition definition = item; + var definition = item; IEnumerable resolved; - if (stateProvinder != null) + if (stateProvider != null) { - resolved = await stateProvinder.Item2.GetByTypeAndIdAsync(item.TypeAndId); + resolved = await stateProvider.Item2.GetByTypeAndIdAsync(item.TypeAndId); } else { @@ -3132,15 +3151,15 @@ async Task handleDefinition(IDefinition item) if (resolved.Any()) { definition = resolved.FirstOrDefault(); - if (stateProvinder != null) + if (stateProvider != null) { - definition.Order = item.Order; + definition!.Order = item.Order; definition.DiskFile = item.DiskFile; definition.File = item.File; definition.OverwrittenFileNames = item.OverwrittenFileNames; // If state is provided assume we need to load from conflict history - if (stateProvinder.Item1 != null && stateProvinder.Item1.IndexedConflictHistory != null && stateProvinder.Item1.IndexedConflictHistory.Any() && stateProvinder.Item1.IndexedConflictHistory.TryGetValue(definition.TypeAndId, out var value)) + if (stateProvider.Item1 is { IndexedConflictHistory: not null } && stateProvider.Item1.IndexedConflictHistory.Any() && stateProvider.Item1.IndexedConflictHistory.TryGetValue(definition.TypeAndId, out var value)) { var history = value.FirstOrDefault(); if (history != null) @@ -3150,8 +3169,9 @@ async Task handleDefinition(IDefinition item) } } } + var copy = CopyDefinition(definition); - var parsed = parserManager.Parse(new ParserManagerArgs() + var parsed = parserManager.Parse(new ParserManagerArgs { ContentSHA = copy.ContentSHA, File = copy.File, @@ -3166,18 +3186,19 @@ async Task handleDefinition(IDefinition item) var variables = parsed.Where(p => p.ValueType == ValueType.Variable || p.ValueType == ValueType.Namespace); other.Variables = variables; var exportCopy = CopyDefinition(other); - var allType = (await conflictResult.AllConflicts.GetByTypeAndIdAsync(definition.TypeAndId)).ToList(); + var allType = (await conflictResult.AllConflicts.GetByTypeAndIdAsync(definition!.TypeAndId)).ToList(); allType.ForEach(p => overwrittenFileNames.Add(p.OriginalFileName)); if (other.AllowDuplicate) { var match = fullyOrdered.FirstOrDefault(p => p.TypeAndId == definition.TypeAndId && p.File == Path.GetFileNameWithoutExtension(other.File)); match ??= fullyOrdered.FirstOrDefault(p => p.TypeAndId == definition.TypeAndId); - exportCopy.Order = match.Order; + exportCopy.Order = match!.Order; } else { - exportCopy.Order = fullyOrdered.FirstOrDefault(p => p.TypeAndId == definition.TypeAndId).Order; + exportCopy.Order = fullyOrdered.FirstOrDefault(p => p.TypeAndId == definition.TypeAndId)!.Order; } + export.Add(exportCopy); sortExport.Add(exportCopy); } @@ -3190,9 +3211,8 @@ async Task handleDefinition(IDefinition item) { await handleDefinition(item); } - else if (!handledDuplicates.Contains(item.TypeAndId)) + else if (handledDuplicates.Add(item.TypeAndId)) { - handledDuplicates.Add(item.TypeAndId); var duplicates = (await conflictResult.AllConflicts.GetByTypeAndIdAsync(item.TypeAndId)).GroupBy(p => p.File); foreach (var duplicate in duplicates) { @@ -3200,6 +3220,7 @@ async Task handleDefinition(IDefinition item) } } } + var fullySortedExport = sortExport.OrderBy(p => p.Order).ToList(); sortExport.ForEach(p => p.Order = fullySortedExport.IndexOf(p) + 1); foreach (var item in sortExport.OrderBy(p => p.Order).Where(p => p.ValueType != ValueType.Variable && p.ValueType != ValueType.Namespace)) @@ -3207,10 +3228,12 @@ async Task handleDefinition(IDefinition item) parseNameSpaces(export, item); parseVariables(export, item); } + if (export.All(p => p.ValueType == ValueType.Namespace || p.ValueType == ValueType.Variable)) { export.Clear(); } + if (export.Any()) { var namespaces = export.Where(p => p.ValueType == ValueType.Namespace).OrderBy(p => p.Id); @@ -3222,7 +3245,7 @@ async Task handleDefinition(IDefinition item) merged.Id = SingleFileMerged; merged.File = overwrittenFileNames.FirstOrDefault(); merged.GeneratedFileNames = overwrittenFileNames.Distinct().ToList(); - merged.File = infoProvider.GetFileName(merged); + merged.File = infoProvider!.GetFileName(merged); merged.DiskFile = infoProvider.GetDiskFileName(merged); merged.ValueType = ValueType.OverwrittenObjectSingleFile; merged.ModName = patchName; @@ -3231,6 +3254,7 @@ async Task handleDefinition(IDefinition item) } } } + return null; } @@ -3269,7 +3293,8 @@ protected virtual IList ProcessPatchStateFiles(IPatchState state, IGroup /// The matched conflicts. /// if set to true [read only mode]. /// A Task representing the asynchronous operation. - protected virtual async Task SyncPatchStateAsync(IGame game, string patchName, List resolvedConflicts, IGrouping item, IList files, IEnumerable matchedConflicts, bool readOnlyMode) + protected virtual async Task SyncPatchStateAsync(IGame game, string patchName, List resolvedConflicts, IGrouping item, IList files, IEnumerable matchedConflicts, + bool readOnlyMode) { var synced = await SyncPatchStatesAsync(matchedConflicts, item, patchName, game, readOnlyMode, files.ToArray()); if (synced) @@ -3281,7 +3306,7 @@ protected virtual async Task SyncPatchStateAsync(IGame game, string patchName, L p.ResetType = ResetType.Resolved; } }); - item.ToList().ForEach((diff) => + item.ToList().ForEach(diff => { var existingConflict = resolvedConflicts.FirstOrDefault(p => p.TypeAndId.Equals(diff.TypeAndId)); if (existingConflict != null) @@ -3301,7 +3326,7 @@ protected virtual async Task SyncPatchStateAsync(IGame game, string patchName, L /// The game. /// if set to true [read only mode]. /// The files. - /// true if XXXX, false otherwise. + /// true if synced, false otherwise. protected virtual async Task SyncPatchStatesAsync(IEnumerable currentConflicts, IEnumerable cachedConflicts, string patchName, IGame game, bool readOnlyMode, params string[] files) { if (IsCachedDefinitionDifferent(currentConflicts, cachedConflicts)) @@ -3310,15 +3335,13 @@ protected virtual async Task SyncPatchStatesAsync(IEnumerable { foreach (var file in files.Distinct()) { - await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = GetPatchModDirectory(game, patchName), - Path = file - }); + await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = GetPatchModDirectory(game, patchName), Path = file }); } } + return true; } + return false; } @@ -3329,14 +3352,14 @@ await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() /// The type and identifier. /// Name of the collection. /// Type of the export. - /// true if XXXX, false otherwise. + /// true if unresolved, false otherwise. protected virtual async Task UnResolveConflictAsync(IConflictResult conflictResult, string typeAndId, string collectionName, ExportType exportType) { var game = GameService.GetSelected(); var patchName = GenerateCollectionPatchName(collectionName); if (conflictResult != null && game != null) { - bool purgeFiles = true; + var purgeFiles = true; IIndexedDefinitions indexed; switch (exportType) { @@ -3364,19 +3387,12 @@ protected virtual async Task UnResolveConflictAsync(IConflictResult confli if (purgeFiles) { var patchModDirectory = GetPatchModDirectory(game, patchName); - await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = patchModDirectory, - Path = item.File - }); + await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = patchModDirectory, Path = item.File }); if (!string.IsNullOrWhiteSpace(item.DiskFile) && item.File != item.DiskFile) { - await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = patchModDirectory, - Path = item.DiskFile - }); + await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = patchModDirectory, Path = item.DiskFile }); } + if (IsOverwrittenType(item.ValueType)) { collectionMods ??= GetCollectionMods(); @@ -3388,21 +3404,20 @@ await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters() var merged = await ProcessOverwrittenSingleFileDefinitionsAsync(conflictResult, patchName, item.Type); if (merged != null) { - await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters() + await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters { - Game = game.Type, - OverwrittenConflicts = PopulateModPath(merged, collectionMods), - RootPath = GetModDirectoryRootPath(game), - PatchPath = EvaluatePatchNamePath(game, patchName) + Game = game.Type, OverwrittenConflicts = PopulateModPath(merged, collectionMods), RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }); } } else { - await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters() + await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters { Game = game.Type, - OverwrittenConflicts = PopulateModPath(await overwritten.ToAsyncEnumerable().WhereAwait(async p => !(await conflictResult.ResolvedConflicts.GetByTypeAndIdAsync(p.TypeAndId)).Any()).ToListAsync(), collectionMods), + OverwrittenConflicts = + PopulateModPath(await overwritten.ToAsyncEnumerable().WhereAwait(async p => !(await conflictResult.ResolvedConflicts.GetByTypeAndIdAsync(p.TypeAndId)).Any()).ToListAsync(), + collectionMods), RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }); @@ -3411,29 +3426,20 @@ await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters() } } } + var allMods = GetInstalledModsInternal(game, false).ToList(); IMod mod; if (!allMods.Any(p => p.Name.Equals(patchName))) { mod = GeneratePatchModDescriptor(allMods, game, patchName); - await ModWriter.CreateModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = game.UserDirectory, - Path = Shared.Constants.ModDirectory - }); - await ModWriter.CreateModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = GetPatchModDirectory(game, patchName) - }); + await ModWriter.CreateModDirectoryAsync(new ModWriterParameters { RootDirectory = game.UserDirectory, Path = Shared.Constants.ModDirectory }); + await ModWriter.CreateModDirectoryAsync(new ModWriterParameters { RootDirectory = GetPatchModDirectory(game, patchName) }); if (game.ModDescriptorType == ModDescriptorType.JsonMetadata) { - await ModWriter.CreateModDirectoryAsync(new ModWriterParameters() - { - RootDirectory = game.UserDirectory, - Path = Shared.Constants.JsonModDirectory - }); + await ModWriter.CreateModDirectoryAsync(new ModWriterParameters { RootDirectory = game.UserDirectory, Path = Shared.Constants.JsonModDirectory }); } - await ModWriter.WriteDescriptorAsync(new ModWriterParameters() + + await ModWriter.WriteDescriptorAsync(new ModWriterParameters { Mod = mod, RootDirectory = game.UserDirectory, @@ -3442,29 +3448,25 @@ await ModWriter.WriteDescriptorAsync(new ModWriterParameters() DescriptorType = MapDescriptorType(game.ModDescriptorType) }, IsPatchMod(mod)); allMods.Add(mod); - Cache.Invalidate(new CacheInvalidateParameters() { Region = ModsCacheRegion, Prefix = game.Type, Keys = new List { GetModsCacheKey(true), GetModsCacheKey(false) } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = ModsCacheRegion, Prefix = game.Type, Keys = new List { GetModsCacheKey(true), GetModsCacheKey(false) } }); } else { mod = allMods.FirstOrDefault(p => p.Name.Equals(patchName)); } - var state = Cache.Get(new CacheGetParameters() { Region = ModsExportedRegion, Key = ModExportedKey }); + var state = Cache.Get(new CacheGetParameters { Region = ModsExportedRegion, Key = ModExportedKey }); if (state == null || state.Exported.GetValueOrDefault() == false) { await Task.Run(() => { - ModWriter.ApplyModsAsync(new ModWriterParameters() - { - AppendOnly = true, - TopPriorityMods = new List() { mod }, - RootDirectory = game.UserDirectory, - DescriptorType = MapDescriptorType(game.ModDescriptorType) - }).ConfigureAwait(false); + ModWriter.ApplyModsAsync(new ModWriterParameters { AppendOnly = true, TopPriorityMods = new List { mod }, RootDirectory = game.UserDirectory, DescriptorType = MapDescriptorType(game.ModDescriptorType) }) + .ConfigureAwait(false); }).ConfigureAwait(false); - Cache.Set(new CacheAddParameters() { Region = ModsExportedRegion, Key = ModExportedKey, Value = new ModsExportedState() { Exported = true } }); + Cache.Set(new CacheAddParameters { Region = ModsExportedRegion, Key = ModExportedKey, Value = new ModsExportedState { Exported = true } }); } - await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() + + await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters { LoadOrder = GetCollectionMods(collectionName: collectionName).Select(p => p.DescriptorFile), Mode = MapPatchStateMode(conflictResult.Mode), @@ -3481,6 +3483,7 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters() return true; } } + return false; } @@ -3499,19 +3502,19 @@ private class DefinitionOrderSort /// Gets or sets the file. /// /// The file. - public string File { get; set; } + public string File { get; init; } /// /// Gets or sets the order. /// /// The order. - public int Order { get; set; } + public int Order { get; init; } /// /// Gets or sets the type and identifier. /// /// The type and identifier. - public string TypeAndId { get; set; } + public string TypeAndId { get; init; } #endregion Properties } @@ -3527,25 +3530,25 @@ private class EvalState /// Gets or sets the content sha. /// /// The content sha. - public string ContentSha { get; set; } + public string ContentSha { get; init; } /// - /// Gets or sets the name of the fall back file. + /// Gets or sets the name of the fallback file. /// - /// The name of the fall back file. - public string FallBackFileName { get; set; } + /// The name of the fallback file. + public string FallBackFileName { get; init; } /// /// Gets or sets the name of the file. /// /// The name of the file. - public string FileName { get; set; } + public string FileName { get; init; } /// /// Gets or sets the name of the mod. /// /// The name of the mod. - public string ModName { get; set; } + public string ModName { get; init; } #endregion Properties } @@ -3561,7 +3564,7 @@ private class ModsExportedState /// Gets or sets a value indicating whether this is exported. /// /// null if [exported] contains no value, true if [exported]; otherwise, false. - public bool? Exported { get; set; } + public bool? Exported { get; init; } #endregion Properties } @@ -3577,13 +3580,13 @@ private class PatchCollectionState /// Gets or sets a value indicating whether [check in progress]. /// /// true if [check in progress]; otherwise, false. - public bool CheckInProgress { get; set; } + public bool CheckInProgress { get; init; } /// /// Gets or sets a value indicating whether [needs update]. /// /// true if [needs update]; otherwise, false. - public bool NeedsUpdate { get; set; } + public bool NeedsUpdate { get; init; } #endregion Properties } diff --git a/src/IronyModManager.Shared/Extensions.Double.cs b/src/IronyModManager.Shared/Extensions.Double.cs index 4fae70c3..a65a50f7 100644 --- a/src/IronyModManager.Shared/Extensions.Double.cs +++ b/src/IronyModManager.Shared/Extensions.Double.cs @@ -4,7 +4,7 @@ // Created : 02-20-2024 // // Last Modified By : Mario -// Last Modified On : 02-20-2024 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario @@ -37,8 +37,8 @@ public static partial class Extensions /// /// Is nearly equal. /// - /// The a. - /// The b. + /// The A. + /// The B. /// The tolerance. /// A bool. public static bool IsNearlyEqual(this double a, double b, double tolerance) @@ -49,14 +49,37 @@ public static bool IsNearlyEqual(this double a, double b, double tolerance) /// /// Is nearly equal. /// - /// The a. - /// The b. + /// The A. + /// The B. /// A bool. public static bool IsNearlyEqual(this double a, double b) { return IsNearlyEqual(a, b, defaultTolerance); } + /// + /// Is not nearly equal. + /// + /// The A. + /// The B. + /// A bool. + public static bool IsNotNearlyEqual(this double a, double b) + { + return !IsNearlyEqual(a, b); + } + + /// + /// Is not nearly equal. + /// + /// The A. + /// The B. + /// The tolerance. + /// A bool. + public static bool IsNotNearlyEqual(this double a, double b, double tolerance) + { + return !IsNearlyEqual(a, b, tolerance); + } + #endregion Methods } } From f5185be99c44814b99c8b267936937823f5891f3 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 08:39:29 +0100 Subject: [PATCH 056/101] Adjust new diff viewer colors --- .../ModPatchCollectionService.cs | 124 +++++++++--------- .../Extensions.Double.cs | 4 +- .../Implementation/AvaloniaEdit/Constants.cs | 65 ++++++--- .../AvaloniaEdit/DiffBackgroundRenderer.cs | 83 ++++++++---- .../Implementation/AvaloniaEdit/DiffMargin.cs | 60 +++++++-- .../Resources/Light/PDXScript.xshd | 4 +- .../Controls/MergeViewerControlView.xaml.cs | 10 +- 7 files changed, 227 insertions(+), 123 deletions(-) diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index 43e0797a..48b045aa 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -54,7 +54,36 @@ namespace IronyModManager.Services /// /// /// - public class ModPatchCollectionService : ModBaseService, IModPatchCollectionService + /// + /// Initializes a new instance of the class. + /// + /// The cache. + /// The message bus. + /// The parser manager. + /// The definition information providers. + /// The mod patch exporter. + /// The reader. + /// The mod writer. + /// The mod parser. + /// The game service. + /// The storage provider. + /// The mapper. + /// The validate parser. + /// The parametrized parser. + public class ModPatchCollectionService( + ICache cache, + IMessageBus messageBus, + IParserManager parserManager, + IEnumerable definitionInfoProviders, + IModPatchExporter modPatchExporter, + IReader reader, + IModWriter modWriter, + IModParser modParser, + IGameService gameService, + IStorageProvider storageProvider, + IMapper mapper, + IValidateParser validateParser, + IParametrizedParser parametrizedParser) : ModBaseService(cache, definitionInfoProviders, reader, modWriter, modParser, gameService, storageProvider, mapper), IModPatchCollectionService { #region Fields @@ -136,22 +165,22 @@ public class ModPatchCollectionService : ModBaseService, IModPatchCollectionServ /// /// The message bus /// - private readonly IMessageBus messageBus; + private readonly IMessageBus messageBus = messageBus; /// /// The mod patch exporter /// - private readonly IModPatchExporter modPatchExporter; + private readonly IModPatchExporter modPatchExporter = modPatchExporter; /// /// The parametrized parser /// - private readonly IParametrizedParser parametrizedParser; + private readonly IParametrizedParser parametrizedParser = parametrizedParser; /// /// The parser manager /// - private readonly IParserManager parserManager; + private readonly IParserManager parserManager = parserManager; /// /// The search initialize lock @@ -161,39 +190,12 @@ public class ModPatchCollectionService : ModBaseService, IModPatchCollectionServ /// /// The validate parser /// - private readonly IValidateParser validateParser; + private readonly IValidateParser validateParser = validateParser; #endregion Fields #region Constructors - /// - /// Initializes a new instance of the class. - /// - /// The cache. - /// The message bus. - /// The parser manager. - /// The definition information providers. - /// The mod patch exporter. - /// The reader. - /// The mod writer. - /// The mod parser. - /// The game service. - /// The storage provider. - /// The mapper. - /// The validate parser. - /// The parametrized parser. - public ModPatchCollectionService(ICache cache, IMessageBus messageBus, IParserManager parserManager, IEnumerable definitionInfoProviders, - IModPatchExporter modPatchExporter, IReader reader, IModWriter modWriter, IModParser modParser, IGameService gameService, - IStorageProvider storageProvider, IMapper mapper, IValidateParser validateParser, IParametrizedParser parametrizedParser) : base(cache, definitionInfoProviders, reader, modWriter, modParser, gameService, storageProvider, mapper) - { - this.messageBus = messageBus; - this.parserManager = parserManager; - this.modPatchExporter = modPatchExporter; - this.validateParser = validateParser; - this.parametrizedParser = parametrizedParser; - } - #endregion Constructors #region Enums @@ -304,7 +306,7 @@ public virtual async Task CleanPatchCollectionAsync(string collectionName) var mod = allMods.FirstOrDefault(p => p.Name.Equals(patchName)); if (mod != null) { - await DeleteDescriptorsInternalAsync(new List { mod }); + await DeleteDescriptorsInternalAsync([mod]); } return await ModWriter.PurgeModDirectoryAsync(new ModWriterParameters { RootDirectory = GetPatchModDirectory(game, patchName) }, true); @@ -837,7 +839,7 @@ public virtual IReadOnlyList GetIgnoredMods(IConflictRe if (conflictResult != null) { var ignoredPaths = conflictResult.IgnoredPaths ?? string.Empty; - var lines = ignoredPaths.SplitOnNewLine().Where(p => !p.Trim().StartsWith("#")); + var lines = ignoredPaths.SplitOnNewLine().Where(p => !p.Trim().StartsWith('#')); foreach (var line in lines) { var ignoreMod = ParseIgnoreModLine(line); @@ -961,7 +963,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE if (files.Count != 0) { var modOrder = mods.Select(p => p.Name).ToList(); - var priorityDefinition = EvalDefinitionPriority(files.OrderBy(p => modOrder.IndexOf(p.ModName)).ToHashSet()); + var priorityDefinition = EvalDefinitionPriority([.. files.OrderBy(p => modOrder.IndexOf(p.ModName))]); if (priorityDefinition is { Definition: not null }) { var parametrizedCode = parametrizedParser.Process(priorityDefinition.Definition.Code, item.Code); @@ -1042,14 +1044,14 @@ public virtual async Task GetModObjectsAsync(IGame game, IE } else { - prunedInlineDefinitions = definitions.ToList(); + prunedInlineDefinitions = [.. definitions]; } definitions.Clear(); definitions = null; if (state != null && state.CustomConflicts.Any()) { - prunedDefinitions = new List(); + prunedDefinitions = []; var customIndexed = DIResolver.Get(); await customIndexed.InitMapAsync(state.CustomConflicts); foreach (var item in prunedInlineDefinitions) @@ -1081,7 +1083,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE } } - if (fileDefs.Any()) + if (fileDefs.Count != 0) { foreach (var def in fileDefs) { @@ -1111,7 +1113,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE } else { - prunedDefinitions = prunedInlineDefinitions.ToList(); + prunedDefinitions = [.. prunedInlineDefinitions]; } prunedInlineDefinitions.Clear(); @@ -1359,7 +1361,7 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) } var matchedConflicts = await conflictResult.OverwrittenConflicts.GetByTypeAndIdAsync(item.First().TypeAndId); - await SyncPatchStatesAsync(matchedConflicts, item, patchName, game, !allowCleanup, files.ToArray()); + await SyncPatchStatesAsync(matchedConflicts, item, patchName, game, !allowCleanup, [.. files]); perc = GetProgressPercentage(total, processed); if (previousProgress.IsNotNearlyEqual(perc)) { @@ -1418,7 +1420,7 @@ async void processedSearchItemHandler(object sender, ProcessedArgs args) { await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters { - Game = game.Type, OverwrittenConflicts = new List { definition }, RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) + Game = game.Type, OverwrittenConflicts = [definition], RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }); } @@ -1523,7 +1525,7 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters { if (await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters { - Game = game.Type, OverwrittenConflicts = new List { definition }, RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) + Game = game.Type, OverwrittenConflicts = [definition], RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) })) { exportedConflicts = true; @@ -1585,7 +1587,7 @@ public virtual bool InvalidatePatchModState(string collectionName) if (game != null) { var patchName = GenerateCollectionPatchName(collectionName); - Cache.Invalidate(new CacheInvalidateParameters { Region = CacheRegion, Prefix = game.Type, Keys = new List { patchName } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = CacheRegion, Prefix = game.Type, Keys = [patchName] }); return true; } @@ -1659,7 +1661,7 @@ public virtual async Task PatchHasGameDefinitionsAsync(string collectionNa /// Task<System.Boolean>. public virtual async Task PatchModNeedsUpdateAsync(string collectionName, IReadOnlyCollection loadOrder) { - loadOrder ??= new List(); + loadOrder ??= []; List mapEvalState(IEnumerable definitions) { @@ -1734,7 +1736,7 @@ async Task evalState(IGame game, string patchName) semaphore.Release(); } }).ToList(); - while (tasks.Any()) + while (tasks.Count != 0) { var task = await Task.WhenAny(tasks); tasks.Remove(task); @@ -1841,7 +1843,7 @@ public virtual Task ResetIgnoredConflictAsync(IConflictResult conflictResu /// true if reset, false otherwise. public virtual bool ResetPatchStateCache() { - Cache.Invalidate(new CacheInvalidateParameters { Region = ModsExportedRegion, Keys = new List { ModExportedKey } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = ModsExportedRegion, Keys = [ModExportedKey] }); modPatchExporter.ResetCache(); return true; } @@ -2345,7 +2347,7 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead var forbiddenMods = new List(); var ignoreRules = new List(); var includeRules = new List(); - var lines = conflictResult.IgnoredPaths.SplitOnNewLine().Where(p => !p.Trim().StartsWith("#")); + var lines = conflictResult.IgnoredPaths.SplitOnNewLine().Where(p => !p.Trim().StartsWith('#')); foreach (var line in lines) { var parsed = line.StandardizeDirectorySeparator().Trim().TrimStart(Path.DirectorySeparatorChar); @@ -2373,7 +2375,7 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead } else { - if (!parsed.StartsWith("!")) + if (!parsed.StartsWith('!')) { ignoreRules.Add(parsed); } @@ -2407,7 +2409,7 @@ bool canAllowForbiddenMod(IHierarchicalDefinitions hierarchicalDefinition, IRead name = $"{name}{Path.DirectorySeparatorChar}"; } - var invalid = topConflict.Children.Where(p => ignoreRules.Any(r => EvalWildcard(r, p.FileNames.ToArray()))).Where(p => !includeRules.Any(r => EvalWildcard(r, p.FileNames.ToArray()))); + var invalid = topConflict.Children.Where(p => ignoreRules.Any(r => EvalWildcard(r, [.. p.FileNames]))).Where(p => !includeRules.Any(r => EvalWildcard(r, [.. p.FileNames]))); foreach (var item in invalid) { if (alreadyIgnored.Add(item.Key)) @@ -2533,7 +2535,7 @@ await ModWriter.WriteDescriptorAsync(new ModWriterParameters DescriptorType = MapDescriptorType(game.ModDescriptorType) }, IsPatchMod(mod)); allMods.Add(mod); - Cache.Invalidate(new CacheInvalidateParameters { Region = ModsCacheRegion, Prefix = game.Type, Keys = new List { GetModsCacheKey(true), GetModsCacheKey(false) } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = ModsCacheRegion, Prefix = game.Type, Keys = [GetModsCacheKey(true), GetModsCacheKey(false)] }); } else { @@ -2569,7 +2571,7 @@ await ModWriter.WriteDescriptorAsync(new ModWriterParameters { await Task.Run(() => { - ModWriter.ApplyModsAsync(new ModWriterParameters { AppendOnly = true, TopPriorityMods = new List { mod }, RootDirectory = game.UserDirectory, DescriptorType = MapDescriptorType(game.ModDescriptorType) }) + ModWriter.ApplyModsAsync(new ModWriterParameters { AppendOnly = true, TopPriorityMods = [mod], RootDirectory = game.UserDirectory, DescriptorType = MapDescriptorType(game.ModDescriptorType) }) .ConfigureAwait(false); }).ConfigureAwait(false); Cache.Set(new CacheAddParameters { Region = ModsExportedRegion, Key = ModExportedKey, Value = new ModsExportedState { Exported = true } }); @@ -2580,7 +2582,7 @@ await Task.Run(() => await conflictResult.ResolvedConflicts.ChangeHierarchicalResetStateAsync(definition); var exportResult = false; - if (exportPatches.Any()) + if (exportPatches.Count != 0) { if (definition.ValueType == ValueType.OverwrittenObjectSingleFile) { @@ -2626,7 +2628,7 @@ await Task.Run(() => PatchPath = EvaluatePatchNamePath(game, patchName), HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync() }); - return exportPatches.Any() ? exportResult && stateResult : stateResult; + return exportPatches.Count != 0 ? exportResult && stateResult : stateResult; } } @@ -2676,7 +2678,7 @@ protected virtual async Task> GetDefinitionOrDefaultAsync(IIn return defs.ToList(); } - return new List(); + return []; } /// @@ -2972,7 +2974,7 @@ protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable definition.ErrorMessage = "File has invalid encoding, please use UTF-8-BOM Encoding."; definition.Id = Path.GetFileName(fileInfo.FileName)!.ToLowerInvariant(); definition.ValueType = ValueType.Invalid; - definition.OriginalCode = definition.Code = string.Join(Environment.NewLine, fileInfo.Content ?? new List()); + definition.OriginalCode = definition.Code = string.Join(Environment.NewLine, fileInfo.Content ?? []); definition.ContentSHA = fileInfo.ContentSHA; definition.Dependencies = modObject.Dependencies; definition.ModName = modObject.Name; @@ -3126,7 +3128,7 @@ void parseVariables(List exportDefinitions, IDefinition def) var partialCopy = new List(); p.ToList().ForEach(x => partialCopy.Add(PartialDefinitionCopy(x, false))); var priority = EvalDefinitionPriorityInternal(partialCopy.OrderBy(x => modOrder.IndexOf(x.ModName)), true); - return new List { new() { TypeAndId = priority.Definition.TypeAndId, Order = priority.Definition.Order, File = Path.GetFileNameWithoutExtension(priority.FileName) } }; + return [new DefinitionOrderSort { TypeAndId = priority.Definition.TypeAndId, Order = priority.Definition.Order, File = Path.GetFileNameWithoutExtension(priority.FileName) }]; } }).SelectMany(p => p).GroupBy(p => p.File).OrderBy(p => p.Key, StringComparer.Ordinal).SelectMany(p => p.OrderBy(x => x.Order)).ToList(); var fullyOrdered = ordered.Select(p => new DefinitionOrderSort { TypeAndId = p.TypeAndId, Order = ordered.IndexOf(p), File = p.File }).ToList(); @@ -3234,7 +3236,7 @@ async Task handleDefinition(IDefinition item) export.Clear(); } - if (export.Any()) + if (export.Count != 0) { var namespaces = export.Where(p => p.ValueType == ValueType.Namespace).OrderBy(p => p.Id); var variables = export.Where(p => p.ValueType == ValueType.Variable).OrderBy(p => p.Id); @@ -3296,7 +3298,7 @@ protected virtual IList ProcessPatchStateFiles(IPatchState state, IGroup protected virtual async Task SyncPatchStateAsync(IGame game, string patchName, List resolvedConflicts, IGrouping item, IList files, IEnumerable matchedConflicts, bool readOnlyMode) { - var synced = await SyncPatchStatesAsync(matchedConflicts, item, patchName, game, readOnlyMode, files.ToArray()); + var synced = await SyncPatchStatesAsync(matchedConflicts, item, patchName, game, readOnlyMode, [.. files]); if (synced) { matchedConflicts.ToList().ForEach(p => @@ -3448,7 +3450,7 @@ await ModWriter.WriteDescriptorAsync(new ModWriterParameters DescriptorType = MapDescriptorType(game.ModDescriptorType) }, IsPatchMod(mod)); allMods.Add(mod); - Cache.Invalidate(new CacheInvalidateParameters { Region = ModsCacheRegion, Prefix = game.Type, Keys = new List { GetModsCacheKey(true), GetModsCacheKey(false) } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = ModsCacheRegion, Prefix = game.Type, Keys = [GetModsCacheKey(true), GetModsCacheKey(false)] }); } else { @@ -3460,7 +3462,7 @@ await ModWriter.WriteDescriptorAsync(new ModWriterParameters { await Task.Run(() => { - ModWriter.ApplyModsAsync(new ModWriterParameters { AppendOnly = true, TopPriorityMods = new List { mod }, RootDirectory = game.UserDirectory, DescriptorType = MapDescriptorType(game.ModDescriptorType) }) + ModWriter.ApplyModsAsync(new ModWriterParameters { AppendOnly = true, TopPriorityMods = [mod], RootDirectory = game.UserDirectory, DescriptorType = MapDescriptorType(game.ModDescriptorType) }) .ConfigureAwait(false); }).ConfigureAwait(false); Cache.Set(new CacheAddParameters { Region = ModsExportedRegion, Key = ModExportedKey, Value = new ModsExportedState { Exported = true } }); diff --git a/src/IronyModManager.Shared/Extensions.Double.cs b/src/IronyModManager.Shared/Extensions.Double.cs index a65a50f7..f7d9e6ec 100644 --- a/src/IronyModManager.Shared/Extensions.Double.cs +++ b/src/IronyModManager.Shared/Extensions.Double.cs @@ -28,7 +28,7 @@ public static partial class Extensions /// /// A private const double named defaultTolerance. /// - private const double defaultTolerance = 0.01; + private const double DefaultTolerance = 0.01; #endregion Fields @@ -54,7 +54,7 @@ public static bool IsNearlyEqual(this double a, double b, double tolerance) /// A bool. public static bool IsNearlyEqual(this double a, double b) { - return IsNearlyEqual(a, b, defaultTolerance); + return IsNearlyEqual(a, b, DefaultTolerance); } /// diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/Constants.cs b/src/IronyModManager/Implementation/AvaloniaEdit/Constants.cs index 7b81def5..b7d53f69 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/Constants.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/Constants.cs @@ -4,7 +4,7 @@ // Created : 02-19-2024 // // Last Modified By : Mario -// Last Modified On : 02-19-2024 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario @@ -27,39 +27,74 @@ public class Constants #region Fields /// - /// A private static readonly SolidColorBrush named diffDeletedLine. + /// A public static readonly SolidColorBrush named DarkDiffDeletedLine. /// - public static readonly SolidColorBrush DiffDeletedLine = SolidColorBrush.Parse("#FF9999"); + public static readonly SolidColorBrush DarkDiffDeletedLine = SolidColorBrush.Parse("#923E3E"); /// - /// A private static readonly SolidColorBrush named diffDeletedPieces. + /// A public static readonly SolidColorBrush named DarkDiffDeletedPieces. /// - public static readonly SolidColorBrush DiffDeletedPieces = SolidColorBrush.Parse("#FF9999"); + public static readonly SolidColorBrush DarkDiffDeletedPieces = SolidColorBrush.Parse("#923E3E"); /// - /// A private static readonly SolidColorBrush named DiffImaginaryLine. + /// A public static readonly SolidColorBrush named DarkDiffImaginaryLine. /// - public static readonly SolidColorBrush DiffImaginaryLine = SolidColorBrush.Parse("#FF808080"); + public static readonly SolidColorBrush DarkDiffImaginaryLine = SolidColorBrush.Parse("#5A5A5A"); /// - /// A private static readonly SolidColorBrush named diffInsertedLine. + /// A public static readonly SolidColorBrush named DarkDiffInsertedLine. /// - public static readonly SolidColorBrush DiffInsertedLine = SolidColorBrush.Parse("#66cc99"); + public static readonly SolidColorBrush DarkDiffInsertedLine = SolidColorBrush.Parse("#1B4F35"); /// - /// A private static readonly SolidColorBrush named diffInsertedPieces. + /// A public static readonly SolidColorBrush named DarkDiffInsertedPieces. /// - public static readonly SolidColorBrush DiffInsertedPieces = SolidColorBrush.Parse("#66cc99"); + public static readonly SolidColorBrush DarkDiffInsertedPieces = SolidColorBrush.Parse("#1B4F35"); /// - /// A private static readonly SolidColorBrush named diffModifiedPieces. + /// A public static readonly SolidColorBrush named DarkDiffModifiedPieces. /// - public static readonly SolidColorBrush DiffModifiedPieces = SolidColorBrush.Parse("#ffe28b"); + public static readonly SolidColorBrush DarkDiffModifiedPieces = SolidColorBrush.Parse("#C49400"); /// - /// A private static readonly SolidColorBrush named diffUnchangedPieces. + /// A public static readonly SolidColorBrush named DarkDiffUnchangedPieces. /// - public static readonly SolidColorBrush DiffUnchangedPieces = SolidColorBrush.Parse("#ffe28b"); + public static readonly SolidColorBrush DarkDiffUnchangedPieces = SolidColorBrush.Parse("#C49400"); + + /// + /// A public static readonly SolidColorBrush named LightDiffDeletedLine. + /// + public static readonly SolidColorBrush LightDiffDeletedLine = SolidColorBrush.Parse("#FF9999"); + + /// + /// A public static readonly SolidColorBrush named LightDiffDeletedPieces. + /// + public static readonly SolidColorBrush LightDiffDeletedPieces = SolidColorBrush.Parse("#FF9999"); + + /// + /// A public static readonly SolidColorBrush named LightDiffImaginaryLine. + /// + public static readonly SolidColorBrush LightDiffImaginaryLine = SolidColorBrush.Parse("#FF808080"); + + /// + /// A public static readonly SolidColorBrush named LightDiffInsertedLine. + /// + public static readonly SolidColorBrush LightDiffInsertedLine = SolidColorBrush.Parse("#66cc99"); + + /// + /// A public static readonly SolidColorBrush named LightDiffInsertedPieces. + /// + public static readonly SolidColorBrush LightDiffInsertedPieces = SolidColorBrush.Parse("#66cc99"); + + /// + /// A public static readonly SolidColorBrush named LightDiffModifiedPieces. + /// + public static readonly SolidColorBrush LightDiffModifiedPieces = SolidColorBrush.Parse("#ffe28b"); + + /// + /// A public static readonly SolidColorBrush named LightDiffUnchangedPieces. + /// + public static readonly SolidColorBrush LightDiffUnchangedPieces = SolidColorBrush.Parse("#ffe28b"); /// /// A public static readonly Pen named TransparentPen. diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs index 98ad2908..0dba5675 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs @@ -4,7 +4,7 @@ // Created : 02-19-2024 // // Last Modified By : Mario -// Last Modified On : 02-19-2024 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario @@ -19,6 +19,9 @@ using Avalonia.Media; using AvaloniaEdit.Document; using AvaloniaEdit.Rendering; +using IronyModManager.DI; +using IronyModManager.Platform.Themes; +using IronyModManager.Services.Common; using IronyModManager.ViewModels.Controls; namespace IronyModManager.Implementation.AvaloniaEdit @@ -26,19 +29,38 @@ namespace IronyModManager.Implementation.AvaloniaEdit /// /// The diff background renderer. /// - /// + /// public class DiffBackgroundRenderer : IBackgroundRenderer { + #region Fields + + /// + /// A private bool? named isLightTheme. + /// + private bool? isLightTheme; + + /// + /// A private IThemeManager named themeManager. + /// + private IThemeManager themeManager; + + /// + /// A private IThemeService named themeService. + /// + private IThemeService themeService; + + #endregion Fields + #region Properties /// - /// Gets a value representing the layer. + /// Gets a value representing the layer. /// /// The layer. public KnownLayer Layer => KnownLayer.Background; /// - /// Gets or sets a value representing the lines. + /// Gets or sets a value representing the lines. /// /// The lines. public IList Lines { get; set; } @@ -72,9 +94,9 @@ public void Draw(TextView textView, DrawingContext drawingContext) Brush brush = diff.Type switch { - DiffPlex.DiffBuilder.Model.ChangeType.Deleted => Constants.DiffDeletedLine, - DiffPlex.DiffBuilder.Model.ChangeType.Inserted => Constants.DiffInsertedLine, - DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => Constants.DiffImaginaryLine, + DiffPlex.DiffBuilder.Model.ChangeType.Deleted => IsLightTheme() ? Constants.LightDiffDeletedLine : Constants.DarkDiffDeletedLine, + DiffPlex.DiffBuilder.Model.ChangeType.Inserted => IsLightTheme() ? Constants.LightDiffInsertedLine : Constants.DarkDiffInsertedLine, + DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => IsLightTheme() ? Constants.LightDiffImaginaryLine : Constants.DarkDiffImaginaryLine, _ => default }; @@ -93,18 +115,15 @@ public void Draw(TextView textView, DrawingContext drawingContext) { var subPieceBrush = piece.Type switch { - DiffPlex.DiffBuilder.Model.ChangeType.Deleted => Constants.DiffDeletedPieces, - DiffPlex.DiffBuilder.Model.ChangeType.Inserted => Constants.DiffInsertedPieces, - DiffPlex.DiffBuilder.Model.ChangeType.Modified => Constants.DiffModifiedPieces, - DiffPlex.DiffBuilder.Model.ChangeType.Unchanged => Constants.DiffUnchangedPieces, + DiffPlex.DiffBuilder.Model.ChangeType.Deleted => IsLightTheme() ? Constants.LightDiffDeletedPieces : Constants.DarkDiffDeletedPieces, + DiffPlex.DiffBuilder.Model.ChangeType.Inserted => IsLightTheme() ? Constants.LightDiffInsertedPieces : Constants.DarkDiffInsertedPieces, + DiffPlex.DiffBuilder.Model.ChangeType.Modified => IsLightTheme() ? Constants.LightDiffModifiedPieces : Constants.DarkDiffModifiedPieces, + DiffPlex.DiffBuilder.Model.ChangeType.Unchanged => IsLightTheme() ? Constants.LightDiffUnchangedPieces : Constants.DarkDiffUnchangedPieces, _ => default(Brush) }; if (subPieceBrush != default(Brush)) { - var builder = new BackgroundGeometryBuilder - { - AlignToWholePixels = true - }; + var builder = new BackgroundGeometryBuilder { AlignToWholePixels = true }; endOffset += piece.Text.Length; var diffSegment = new DiffSegment(line.StartOffset + offset, piece.Text.Length, line.StartOffset + endOffset); offset = piece.Text.Length; @@ -120,6 +139,22 @@ public void Draw(TextView textView, DrawingContext drawingContext) } } + /// + /// Is light theme. + /// + /// A bool. + private bool IsLightTheme() + { + if (!isLightTheme.HasValue) + { + themeManager ??= DIResolver.Get(); + themeService ??= DIResolver.Get(); + isLightTheme = themeManager.IsLightTheme(themeService.GetSelected().Type); + } + + return isLightTheme.GetValueOrDefault(); + } + #endregion Methods #region Classes @@ -127,13 +162,11 @@ public void Draw(TextView textView, DrawingContext drawingContext) /// /// The diff segment. /// - /// - /// - /// Initializes a new instance of the class. - /// + /// /// The offset. /// The length. /// The end offset. + /// Initializes a new instance of the class. private class DiffSegment(int offset, int length, int endOffset) : ISegment { #region Properties @@ -141,25 +174,19 @@ private class DiffSegment(int offset, int length, int endOffset) : ISegment /// /// Gets a value representing the end offset. /// - /// - /// The end offset. - /// + /// The end offset. public int EndOffset { get; } = endOffset; /// /// Gets a value representing the length. /// - /// - /// The length. - /// + /// The length. public int Length { get; } = length; /// /// Gets a value representing the offset. /// - /// - /// The offset. - /// + /// The offset. public int Offset { get; } = offset; #endregion Properties diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs index 71fb0d01..fc7496b2 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs @@ -4,7 +4,7 @@ // Created : 02-19-2024 // // Last Modified By : Mario -// Last Modified On : 02-22-2024 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario @@ -25,6 +25,9 @@ using AvaloniaEdit.Editing; using AvaloniaEdit.Rendering; using AvaloniaEdit.Utils; +using IronyModManager.DI; +using IronyModManager.Platform.Themes; +using IronyModManager.Services.Common; using IronyModManager.ViewModels.Controls; namespace IronyModManager.Implementation.AvaloniaEdit @@ -37,11 +40,6 @@ public class DiffMargin : AbstractMargin { #region Fields - /// - /// The maximum line number length - /// - private int maxLineNumberLength = 1; - /// /// A private const double named LineMargin. /// @@ -57,6 +55,16 @@ public class DiffMargin : AbstractMargin /// private double fontSize; + /// + /// A private bool? named isLightTheme. + /// + private bool? isLightTheme; + + /// + /// The maximum line number length + /// + private int maxLineNumberLength = 1; + /// /// The selecting /// @@ -67,6 +75,16 @@ public class DiffMargin : AbstractMargin /// private AnchorSegment selectionStart; + /// + /// A private IThemeManager named themeManager. + /// + private IThemeManager themeManager; + + /// + /// A private IThemeService named themeService. + /// + private IThemeService themeService; + #endregion Fields #region Properties @@ -109,9 +127,9 @@ public override void Render(DrawingContext context) Brush brush = diff.Type switch { - DiffPlex.DiffBuilder.Model.ChangeType.Deleted => Constants.DiffDeletedLine, - DiffPlex.DiffBuilder.Model.ChangeType.Inserted => Constants.DiffInsertedLine, - DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => Constants.DiffImaginaryLine, + DiffPlex.DiffBuilder.Model.ChangeType.Deleted => IsLightTheme() ? Constants.LightDiffDeletedLine : Constants.DarkDiffDeletedLine, + DiffPlex.DiffBuilder.Model.ChangeType.Inserted => IsLightTheme() ? Constants.LightDiffInsertedLine : Constants.DarkDiffInsertedLine, + DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => IsLightTheme() ? Constants.LightDiffImaginaryLine : Constants.DarkDiffImaginaryLine, _ => default }; @@ -235,7 +253,7 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) /// /// Handles the event. /// - /// The instance containing the event data. + /// The instance containing the event data. protected override void OnPointerReleased(PointerReleasedEventArgs e) { if (selecting) @@ -302,7 +320,7 @@ private void ExtendSelection(SimpleSegment currentSeg) /// /// Gets the text line segment. /// - /// The instance containing the event data. + /// The instance containing the event data. /// SimpleSegment. private SimpleSegment GetTextLineSegment(PointerEventArgs e) { @@ -322,11 +340,27 @@ private SimpleSegment GetTextLineSegment(PointerEventArgs e) return new SimpleSegment(startOffset, endOffset - startOffset); } + /// + /// Is light theme. + /// + /// A bool. + private bool IsLightTheme() + { + if (!isLightTheme.HasValue) + { + themeManager ??= DIResolver.Get(); + themeService ??= DIResolver.Get(); + isLightTheme = themeManager.IsLightTheme(themeService.GetSelected().Type); + } + + return isLightTheme.GetValueOrDefault(); + } + /// /// Handles the event. /// /// The sender. - /// The instance containing the event data. + /// The instance containing the event data. private void OnDocumentLineCountChanged(object sender, EventArgs e) { OnDocumentLineCountChanged(); @@ -356,7 +390,7 @@ private void OnDocumentLineCountChanged() /// Texts the view visual lines changed. /// /// The sender. - /// The instance containing the event data. + /// The instance containing the event data. private void TextViewVisualLinesChanged(object sender, EventArgs e) { InvalidateMeasure(); diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/Resources/Light/PDXScript.xshd b/src/IronyModManager/Implementation/AvaloniaEdit/Resources/Light/PDXScript.xshd index bb689a99..93d8ef4c 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/Resources/Light/PDXScript.xshd +++ b/src/IronyModManager/Implementation/AvaloniaEdit/Resources/Light/PDXScript.xshd @@ -2,8 +2,8 @@ - - + + diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 21257f21..2dedcfd3 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -213,6 +213,7 @@ protected virtual void HandleEditorContextMenu(IronyModManager.Controls.TextEdit void setMenuItems() { + var canReplace = false; List menuItems; if ((!ViewModel!.RightSidePatchMod && !ViewModel.LeftSidePatchMod) || ViewModel.IsReadOnlyMode) { @@ -223,19 +224,24 @@ void setMenuItems() if (leftSide) { menuItems = ViewModel.RightSidePatchMod ? GetActionsMenuItems(true) : GetEditableMenuItems(true); + canReplace = ViewModel.LeftSidePatchMod; } else { menuItems = ViewModel.LeftSidePatchMod ? GetActionsMenuItems(false) : GetEditableMenuItems(false); + canReplace = ViewModel.RightSidePatchMod; } } menuItems.AddRange( [ new MenuItem { Header = "-" }, - new MenuItem { Header = ViewModel.EditorFind, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, false)).DisposeWith(Disposables) }, - new MenuItem { Header = ViewModel.EditorReplace, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, true)).DisposeWith(Disposables) } + new MenuItem { Header = ViewModel.EditorFind, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, false)).DisposeWith(Disposables) } ]); + if (canReplace) + { + menuItems.Add(new MenuItem { Header = ViewModel.EditorReplace, Command = ReactiveCommand.Create(() => HandleEditorFindOrReplace(editor, searchPanel, true)).DisposeWith(Disposables) }); + } ctx.Items = menuItems; } From 4c2cc7934cfcd7d5a5c0f7ceca2c9b2b4cabf137 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 08:47:05 +0100 Subject: [PATCH 057/101] Small fix --- .../Views/Controls/MergeViewerControlView.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 2dedcfd3..9749bdf0 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -4,7 +4,7 @@ // Created : 03-20-2020 // // Last Modified By : Mario -// Last Modified On : 02-22-2024 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario @@ -536,7 +536,7 @@ void evalKey() return; } - diffRight.Text = string.Join(Environment.NewLine, s.Select(p => p.Text)); + diffRight.Text = newText; }); this.WhenAnyValue(v => v.ViewModel.LeftSidePatchMod).Subscribe(s => From 92f18496e4f325859f3815c540256578dc13e3c8 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 09:23:37 +0100 Subject: [PATCH 058/101] Diff line indicator covers the whole line Fix not showing changed sections properly --- .../Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs | 8 +++++--- .../Implementation/AvaloniaEdit/DiffMargin.cs | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs index 0dba5675..da2e3ffe 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs @@ -97,6 +97,7 @@ public void Draw(TextView textView, DrawingContext drawingContext) DiffPlex.DiffBuilder.Model.ChangeType.Deleted => IsLightTheme() ? Constants.LightDiffDeletedLine : Constants.DarkDiffDeletedLine, DiffPlex.DiffBuilder.Model.ChangeType.Inserted => IsLightTheme() ? Constants.LightDiffInsertedLine : Constants.DarkDiffInsertedLine, DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => IsLightTheme() ? Constants.LightDiffImaginaryLine : Constants.DarkDiffImaginaryLine, + DiffPlex.DiffBuilder.Model.ChangeType.Modified => IsLightTheme() ? Constants.LightDiffModifiedPieces : Constants.DarkDiffModifiedPieces, _ => default }; @@ -121,20 +122,21 @@ public void Draw(TextView textView, DrawingContext drawingContext) DiffPlex.DiffBuilder.Model.ChangeType.Unchanged => IsLightTheme() ? Constants.LightDiffUnchangedPieces : Constants.DarkDiffUnchangedPieces, _ => default(Brush) }; + endOffset += piece.Text.Length; if (subPieceBrush != default(Brush)) { var builder = new BackgroundGeometryBuilder { AlignToWholePixels = true }; - endOffset += piece.Text.Length; var diffSegment = new DiffSegment(line.StartOffset + offset, piece.Text.Length, line.StartOffset + endOffset); - offset = piece.Text.Length; builder.AddSegment(textView, diffSegment); var geo = builder.CreateGeometry(); if (geo != null) { - drawingContext.DrawGeometry(brush, null, geo); + drawingContext.DrawGeometry(subPieceBrush, null, geo); } } + + offset += piece.Text.Length; } } } diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs index fc7496b2..1eab904c 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs @@ -130,6 +130,7 @@ public override void Render(DrawingContext context) DiffPlex.DiffBuilder.Model.ChangeType.Deleted => IsLightTheme() ? Constants.LightDiffDeletedLine : Constants.DarkDiffDeletedLine, DiffPlex.DiffBuilder.Model.ChangeType.Inserted => IsLightTheme() ? Constants.LightDiffInsertedLine : Constants.DarkDiffInsertedLine, DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => IsLightTheme() ? Constants.LightDiffImaginaryLine : Constants.DarkDiffImaginaryLine, + DiffPlex.DiffBuilder.Model.ChangeType.Modified => IsLightTheme() ? Constants.LightDiffModifiedPieces : Constants.DarkDiffModifiedPieces, _ => default }; From b28aa3bf723dd31ec9a39b4a959a16396fd515e6 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 09:27:46 +0100 Subject: [PATCH 059/101] Fix NRE --- .../Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs index da2e3ffe..9c1eb0da 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs @@ -122,11 +122,12 @@ public void Draw(TextView textView, DrawingContext drawingContext) DiffPlex.DiffBuilder.Model.ChangeType.Unchanged => IsLightTheme() ? Constants.LightDiffUnchangedPieces : Constants.DarkDiffUnchangedPieces, _ => default(Brush) }; - endOffset += piece.Text.Length; + var text = piece.Text ?? string.Empty; + endOffset += text.Length; if (subPieceBrush != default(Brush)) { var builder = new BackgroundGeometryBuilder { AlignToWholePixels = true }; - var diffSegment = new DiffSegment(line.StartOffset + offset, piece.Text.Length, line.StartOffset + endOffset); + var diffSegment = new DiffSegment(line.StartOffset + offset, text.Length, line.StartOffset + endOffset); builder.AddSegment(textView, diffSegment); var geo = builder.CreateGeometry(); @@ -136,7 +137,7 @@ public void Draw(TextView textView, DrawingContext drawingContext) } } - offset += piece.Text.Length; + offset += text.Length; } } } From d00a6431bad0dd8c1be0cedb113d6d50767ea7f3 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 15:57:05 +0100 Subject: [PATCH 060/101] React to pointer pressed event and ensure we have a selection of 1 at least --- .../Implementation/AvaloniaEdit/DiffMargin.cs | 8 ++++++++ .../Controls/MergeViewerControlView.xaml.cs | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs index 1eab904c..fd9f6eb8 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffMargin.cs @@ -237,7 +237,9 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) { if (TextArea.Selection is SimpleSelection simpleSelection) + { selectionStart = new AnchorSegment(Document, simpleSelection.SurroundingSegment); + } } TextArea.Selection = Selection.Create(TextArea, selectionStart); @@ -329,7 +331,10 @@ private SimpleSegment GetTextLineSegment(PointerEventArgs e) pos = new Point(0, pos.Y.CoerceValue(0, TextView.Bounds.Height) + TextView.VerticalOffset); var vl = TextView.GetVisualLineFromVisualTop(pos.Y); if (vl == null) + { return SimpleSegment.Invalid; + } + var tl = vl.GetTextLineByVisualYPosition(pos.Y); var visualStartColumn = vl.GetTextLineVisualStartColumn(tl); var visualEndColumn = visualStartColumn + tl.Length; @@ -337,7 +342,10 @@ private SimpleSegment GetTextLineSegment(PointerEventArgs e) var startOffset = vl.GetRelativeOffset(visualStartColumn) + relStart; var endOffset = vl.GetRelativeOffset(visualEndColumn) + relStart; if (endOffset == vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length) + { endOffset += vl.LastDocumentLine.DelimiterLength; + } + return new SimpleSegment(startOffset, endOffset - startOffset); } diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 9749bdf0..7a66843b 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -23,6 +23,7 @@ using Avalonia.Threading; using AvaloniaEdit; using AvaloniaEdit.Search; +using AvaloniaEdit.Utils; using DiffPlex.DiffBuilder.Model; using IronyModManager.Common; using IronyModManager.Common.Views; @@ -634,6 +635,21 @@ void setEditMode() col.Add(sourceCol[i]); } }; + diff.PointerPressed += (_, args) => + { + var pos = args.GetPosition(diff); + pos = new Point(0, pos.Y.CoerceValue(0, diff.Bounds.Height) + diff.VerticalOffset); + var line = diff.TextArea.TextView.GetVisualLineFromVisualTop(pos.Y); + if (line != null) + { + var col = leftDiff ? ViewModel!.LeftSideSelected : ViewModel!.RightSideSelected; + var sourceCol = leftDiff ? ViewModel.LeftDiff : ViewModel.RightDiff; + if (col.Count == 0) + { + col.Add(sourceCol[line.FirstDocumentLine.LineNumber - 1]); + } + } + }; } /// From 4e0d7cc0e9b1aa95433e8dd10dde58a660d0fdd9 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 20:25:32 +0100 Subject: [PATCH 061/101] Cleanup and slight optimization --- .../ModPatchCollectionService.cs | 19 +- .../Controls/ModHolderControlViewModel.cs | 189 ++++++++---------- 2 files changed, 93 insertions(+), 115 deletions(-) diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index 48b045aa..ad74eb0f 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -556,6 +556,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition { return Task.Run(async () => { + var cache = new Dictionary(); foreach (var typeId in type) { var typeLock = await opLock.LockAsync(); @@ -563,10 +564,20 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition typeLock.Dispose(); if (items.Any() && items.All(p => !p.ExistsInLastFile)) { - var fileLock = await opLock.LockAsync(); - var fileDefs = await indexedDefinitions.GetByFileAsync(items.FirstOrDefault()!.FileCI); - fileLock.Dispose(); - var lastMod = fileDefs.GroupBy(p => p.ModName).Select(p => p.First()).MaxBy(p => modOrder.IndexOf(p.ModName)); + IDefinition lastMod; + if (cache.TryGetValue(items.FirstOrDefault()!.FileCI, out var value)) + { + lastMod = value; + } + else + { + var fileLock = await opLock.LockAsync(); + var fileDefs = await indexedDefinitions.GetByFileAsync(items.FirstOrDefault()!.FileCI); + fileLock.Dispose(); + lastMod = fileDefs.GroupBy(p => p.ModName).Select(p => p.First()).MaxBy(p => modOrder.IndexOf(p.ModName)); + cache[items.FirstOrDefault()!.FileCI] = lastMod; + } + var copy = CopyDefinition(items.FirstOrDefault()); copy.Dependencies = lastMod!.Dependencies; copy.ModName = lastMod.ModName; diff --git a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs index 94c5c925..103e48de 100644 --- a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs @@ -1,11 +1,10 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 02-29-2020 // // Last Modified By : Mario -// Last Modified On : 02-11-2024 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario @@ -43,7 +42,6 @@ namespace IronyModManager.ViewModels.Controls { - /// /// Class ModHolderViewModel. /// Implements the @@ -155,49 +153,49 @@ public class ModHolderControlViewModel : BaseViewModel private readonly IPromptNotificationsService promptNotificationsService; /// - /// The shut down state + /// The shut-down state /// private readonly IShutDownState shutDownState; /// /// The definition analyze load handler /// - private IDisposable definitionAnalyzeLoadHandler = null; + private IDisposable definitionAnalyzeLoadHandler; /// /// The definition load handler /// - private IDisposable definitionLoadHandler = null; + private IDisposable definitionLoadHandler; /// /// The definition synchronize handler /// - private IDisposable definitionSyncHandler = null; + private IDisposable definitionSyncHandler; /// /// The force enable resume button /// - private bool forceEnableResumeButton = false; + private bool forceEnableResumeButton; /// /// The game definition load handler /// - private IDisposable gameDefinitionLoadHandler = null; + private IDisposable gameDefinitionLoadHandler; /// /// The game index handler /// - private IDisposable gameIndexHandler = null; + private IDisposable gameIndexHandler; /// /// The mod invalid replace handler /// - private IDisposable modInvalidReplaceHandler = null; + private IDisposable modInvalidReplaceHandler; /// /// The showing invalid notification /// - private bool showingInvalidNotification = false; + private bool showingInvalidNotification; #endregion Fields @@ -214,7 +212,7 @@ public class ModHolderControlViewModel : BaseViewModel /// The mod list install refresh request handler. /// The mod definition invalid replace handler. /// The identifier generator. - /// State of the shut down. + /// State of the shut-down. /// The mod service. /// The mod patch collection service. /// The game service. @@ -265,6 +263,7 @@ public ModHolderControlViewModel(IExternalProcessHandlerService externalProcessH { forceEnableResumeButton = true; } + StaticResources.CommandLineArgsChanged += () => { Dispatcher.UIThread.SafeInvoke(() => @@ -330,47 +329,47 @@ public ModHolderControlViewModel(IExternalProcessHandlerService externalProcessH public virtual bool AllowModSelection { get; protected set; } /// - /// Gets or sets the analyze. + /// Gets or sets the analysis. /// - /// The analyze. + /// The analysis. [StaticLocalization(LocalizationResources.Mod_Actions.ConflictSolver.Conflict)] public virtual string Analyze { get; protected set; } /// - /// Gets or sets the analyze class. + /// Gets or sets the analysis class. /// - /// The analyze class. + /// The analysis class. public virtual string AnalyzeClass { get; protected set; } /// - /// Gets or sets the analyze command. + /// Gets or sets the analysis command. /// - /// The analyze command. + /// The analysis command. public virtual ReactiveCommand AnalyzeCommand { get; protected set; } /// - /// Gets or sets the analyze mode. + /// Gets or sets the analysis mode. /// - /// The analyze mode. + /// The analysis mode. [StaticLocalization(LocalizationResources.Conflict_Solver.Modes.Analyze)] public virtual string AnalyzeMode { get; protected set; } /// - /// Gets or sets the analyze mode command. + /// Gets or sets the analysis mode command. /// - /// The analyze mode command. + /// The analysis mode command. public virtual ReactiveCommand AnalyzeModeCommand { get; protected set; } /// - /// Gets or sets the analyze mode without localization command. + /// Gets or sets the analysis mode without localization command. /// - /// The analyze mode without localization command. + /// The analysis mode without localization command. public virtual ReactiveCommand AnalyzeModeWithoutLocalizationCommand { get; protected set; } /// - /// Gets or sets the analyze without localization mode. + /// Gets or sets the analysis without localization mode. /// - /// The analyze without localization mode. + /// The analysis without localization mode. [StaticLocalization(LocalizationResources.Conflict_Solver.Modes.AnalyzeWithoutLocalization)] public virtual string AnalyzeWithoutLocalizationMode { get; protected set; } @@ -570,12 +569,8 @@ protected virtual async Task AnalyzeModsAsync(long id, PatchStateMode mode, IEnu SubscribeToProgressReport(id, Disposables, totalSteps); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), new - { - PercentDone = 0.ToLocalizedPercentage(), - Count = 1, - TotalCount = totalSteps - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), + new { PercentDone = 0.ToLocalizedPercentage(), Count = 1, TotalCount = totalSteps }); var message = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Loading_Definitions); await TriggerOverlayAsync(id, true, message, overlayProgress); modPatchCollectionService.InvalidatePatchModState(CollectionMods.SelectedModCollection.Name); @@ -625,6 +620,7 @@ protected virtual async Task AnalyzeModsAsync(long id, PatchStateMode mode, IEnu notificationAction.ShowNotification(largeMessageTitle, largeMessageBody, NotificationType.Error, 60); return; } + if (versions != null && versions.Any()) { stopWatch.Restart(); @@ -652,6 +648,7 @@ await Task.Run(async () => }).ConfigureAwait(false); Debug.WriteLine("Conflict Solver Stage 3: " + stopWatch.Elapsed.FormatElapsed()); } + stopWatch.Restart(); var conflicts = await Task.Run(async () => { @@ -664,6 +661,7 @@ await Task.Run(async () => GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); return result; } + return null; }).ConfigureAwait(false); Debug.WriteLine("Conflict Solver Stage 4: " + stopWatch.Elapsed.FormatElapsed()); @@ -685,7 +683,7 @@ await Task.Run(async () => } Debug.WriteLine("Conflict Solver Stage 5: " + stopWatch.Elapsed.FormatElapsed()); - var args = new NavigationEventArgs() + var args = new NavigationEventArgs { SelectedCollection = CollectionMods.SelectedModCollection, Results = conflicts, @@ -720,6 +718,7 @@ protected virtual async Task ApplyCollectionAsync(long id, bool showOverlay = tr { return; } + if (validateParadoxLauncher) { if (await externalProcessHandlerService.IsParadoxLauncherRunningAsync()) @@ -730,6 +729,7 @@ protected virtual async Task ApplyCollectionAsync(long id, bool showOverlay = tr return; } } + ApplyingCollection = true; if (CollectionMods.SelectedModCollection != null) { @@ -737,6 +737,7 @@ protected virtual async Task ApplyCollectionAsync(long id, bool showOverlay = tr { await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Mod_Actions.Overlay_Apply_Message)); } + var notificationType = NotificationType.Success; try { @@ -754,6 +755,7 @@ protected virtual async Task ApplyCollectionAsync(long id, bool showOverlay = tr message = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Notifications.CollectionNotApplied.Message), new { CollectionName = CollectionMods.SelectedModCollection.Name }); notificationType = NotificationType.Error; } + notificationAction.ShowNotification(title, message, notificationType, 5); } catch (Exception ex) @@ -763,16 +765,18 @@ protected virtual async Task ApplyCollectionAsync(long id, bool showOverlay = tr logger.Error(ex); notificationAction.ShowNotification(title, message, NotificationType.Error, 30); } + if (showOverlay) { await TriggerOverlayAsync(id, false); } } + ApplyingCollection = false; } /// - /// Evals the resume availability. + /// Evaluate the resume availability. /// /// The game. protected virtual void EvalResumeAvailability(IGame game = null) @@ -811,13 +815,14 @@ protected virtual async Task InstallModsAsync(bool skipOverlay = false) var result = await modService.InstallModsAsync(InstalledMods.Mods); if (result != null) { - if (result.Any(p => p.Installed == true)) + if (result.Any(p => p.Installed)) { if (InstalledMods.IsActivated) { - await InstalledMods.RefreshModsAsync(skipOverlay: skipOverlay); + await InstalledMods.RefreshModsAsync(skipOverlay); } } + if (result.Any(p => p.Invalid)) { await ShowInvalidModsNotificationAsync(result.Where(p => p.Invalid).ToList()); @@ -846,6 +851,7 @@ async Task runAnalysis(PatchStateMode mode) var message = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.GameExecutableNotSetPrompt.Message); proceed = await notificationAction.ShowPromptAsync(title, title, message, NotificationType.Info, PromptType.YesNo); } + if (proceed) { await AnalyzeModsAsync(id, mode, versions); @@ -858,11 +864,11 @@ async Task runAnalysis(PatchStateMode mode) Task.Run(() => EvalResumeAvailabilityLoopAsync().ConfigureAwait(false)); - ShowAdvancedFeatures = (gameService.GetSelected()?.AdvancedFeatures) != GameAdvancedFeatures.None; + ShowAdvancedFeatures = gameService.GetSelected()?.AdvancedFeatures != GameAdvancedFeatures.None; AnalyzeClass = string.Empty; var allowModSelectionEnabled = this.WhenAnyValue(v => v.AllowModSelection); - var applyEnabled = Observable.Merge(this.WhenAnyValue(v => v.ApplyingCollection, v => !v), allowModSelectionEnabled); + var applyEnabled = this.WhenAnyValue(v => v.ApplyingCollection, v => !v).Merge(allowModSelectionEnabled); this.WhenAnyValue(v => v.CollectionMods.SelectedModCollection).Subscribe(s => { @@ -878,6 +884,7 @@ async Task runAnalysis(PatchStateMode mode) InstalledMods.AllowModSelection = false; CollectionMods.AllowModSelection = false; } + InstallModsAsync().ConfigureAwait(true); }).DisposeWith(disposables); @@ -891,12 +898,12 @@ async Task runAnalysis(PatchStateMode mode) CollectionMods.HandleModRefresh(s, InstalledMods.Mods, InstalledMods.ActiveGame); }).DisposeWith(disposables); - this.WhenAnyValue(v => v.CollectionMods.NeedsModListRefresh).Where(x => x).Subscribe(async s => + this.WhenAnyValue(v => v.CollectionMods.NeedsModListRefresh).Where(x => x).Subscribe(async _ => { await InstalledMods.RefreshModsAsync(); }).DisposeWith(disposables); - this.WhenAnyValue(v => v.InstalledMods.GameChangedRefresh).Where(x => x).Subscribe(s => + this.WhenAnyValue(v => v.InstalledMods.GameChangedRefresh).Where(x => x).Subscribe(_ => { CollectionMods.ReloadModCollection(); }).DisposeWith(disposables); @@ -925,6 +932,7 @@ async Task runAnalysis(PatchStateMode mode) messageState.ConflictSolverPromptShown = true; promptNotificationsService.Save(messageState); } + var id = idGenerator.GetNextId(); await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.App.WaitBackgroundOperationMessage)); await shutDownState.WaitUntilFreeAsync(); @@ -932,7 +940,6 @@ async Task runAnalysis(PatchStateMode mode) if (game.AdvancedFeatures == GameAdvancedFeatures.Full) { var mode = await modPatchCollectionService.GetPatchStateModeAsync(CollectionMods.SelectedModCollection.Name); - var versions = gameService.GetVersions(game); switch (mode) { case PatchStateMode.Default: @@ -978,6 +985,7 @@ async Task runAnalysis(PatchStateMode mode) AdvancedWithoutLocalizationModeVisible = false; DefaultWithoutLocalizationModeVisible = false; } + var height = (VerticalMenuSpacing + VerticalMenuItemHeight) * 2; AdvancedParentVisible = AdvancedModeVisible || AdvancedWithoutLocalizationModeVisible; DefaultParentVisible = DefaultModeVisible || DefaultWithoutLocalizationModeVisible; @@ -985,10 +993,12 @@ async Task runAnalysis(PatchStateMode mode) { height += VerticalMenuSpacing + VerticalMenuItemHeight; } + if (DefaultParentVisible) { height += VerticalMenuSpacing + VerticalMenuItemHeight; } + VerticalMenuHeight = height; await TriggerOverlayAsync(id, false); await Task.Delay(50); @@ -996,13 +1006,12 @@ async Task runAnalysis(PatchStateMode mode) } }, allowModSelectionEnabled).DisposeWith(disposables); - async Task ensureSteamIsRunning(IGameSettings args) + async Task ensureSteamIsRunning(IGameSettings args) { if (gameService.IsSteamGame(args)) { - return await externalProcessHandlerService.LaunchSteamAsync(gameService.GetSelected()); + await externalProcessHandlerService.LaunchSteamAsync(gameService.GetSelected()); } - return true; } async Task launchGame(bool continueGame) @@ -1017,6 +1026,7 @@ async Task launchGame(bool continueGame) notificationAction.ShowNotification(title, message, NotificationType.Error, 30); return; } + var args = gameService.GetLaunchSettings(game, continueGame); if (!string.IsNullOrWhiteSpace(args.ExecutableLocation)) { @@ -1027,6 +1037,7 @@ async Task launchGame(bool continueGame) await modService.DeleteDescriptorsAsync(InstalledMods.Mods); await modService.InstallModsAsync(InstalledMods.Mods); } + await ApplyCollectionAsync(id, false); await MessageBus.PublishAsync(new LaunchingGameEvent(game.Type)); if (gameService.IsSteamLaunchPath(args)) @@ -1085,40 +1096,19 @@ async Task launchGame(bool continueGame) await launchGame(true); }, allowModSelectionEnabled).DisposeWith(disposables); - AdvancedModeCommand = ReactiveCommand.CreateFromTask(() => - { - return runAnalysis(PatchStateMode.Advanced); - }).DisposeWith(disposables); + AdvancedModeCommand = ReactiveCommand.CreateFromTask(() => runAnalysis(PatchStateMode.Advanced)).DisposeWith(disposables); - DefaultModeCommand = ReactiveCommand.CreateFromTask(() => - { - return runAnalysis(PatchStateMode.Default); - }).DisposeWith(disposables); + DefaultModeCommand = ReactiveCommand.CreateFromTask(() => runAnalysis(PatchStateMode.Default)).DisposeWith(disposables); - AnalyzeModeCommand = ReactiveCommand.CreateFromTask(() => - { - return runAnalysis(PatchStateMode.ReadOnly); - }).DisposeWith(disposables); + AnalyzeModeCommand = ReactiveCommand.CreateFromTask(() => runAnalysis(PatchStateMode.ReadOnly)).DisposeWith(disposables); - AnalyzeModeWithoutLocalizationCommand = ReactiveCommand.CreateFromTask(() => - { - return runAnalysis(PatchStateMode.ReadOnlyWithoutLocalization); - }).DisposeWith(disposables); + AnalyzeModeWithoutLocalizationCommand = ReactiveCommand.CreateFromTask(() => runAnalysis(PatchStateMode.ReadOnlyWithoutLocalization)).DisposeWith(disposables); - DefaultWithoutLocalizationModeCommand = ReactiveCommand.CreateFromTask(() => - { - return runAnalysis(PatchStateMode.DefaultWithoutLocalization); - }).DisposeWith(disposables); + DefaultWithoutLocalizationModeCommand = ReactiveCommand.CreateFromTask(() => runAnalysis(PatchStateMode.DefaultWithoutLocalization)).DisposeWith(disposables); - AdvancedWithoutLocalizationModeCommand = ReactiveCommand.CreateFromTask(() => - { - return runAnalysis(PatchStateMode.AdvancedWithoutLocalization); - }).DisposeWith(disposables); + AdvancedWithoutLocalizationModeCommand = ReactiveCommand.CreateFromTask(() => runAnalysis(PatchStateMode.AdvancedWithoutLocalization)).DisposeWith(disposables); - CloseModeCommand = ReactiveCommand.Create(() => - { - ForceClosePopups(); - }).DisposeWith(disposables); + CloseModeCommand = ReactiveCommand.Create(ForceClosePopups).DisposeWith(disposables); var previousCollectionNotification = string.Empty; CollectionMods.ConflictSolverStateChanged += (collectionName, state) => @@ -1138,6 +1128,7 @@ async Task launchGame(bool continueGame) { CollectionMods.Reset(true); } + await InstalledMods.RefreshModsAsync(); EvalResumeAvailability(s.Game); }).DisposeWith(disposables); @@ -1158,7 +1149,7 @@ protected override void OnSelectedGameChanged(IGame game) { forceEnableResumeButton = false; EvalResumeAvailability(game); - ShowAdvancedFeatures = (game?.AdvancedFeatures) != GameAdvancedFeatures.None; + ShowAdvancedFeatures = game?.AdvancedFeatures != GameAdvancedFeatures.None; base.OnSelectedGameChanged(game); } @@ -1192,12 +1183,8 @@ private void SubscribeToProgressReport(long id, CompositeDisposable disposables, definitionLoadHandler = modDefinitionLoadHandler.Subscribe(s => { var message = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Loading_Definitions); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), new - { - PercentDone = s.Percentage.ToLocalizedPercentage(), - Count = 1, - TotalCount = totalSteps - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), + new { PercentDone = s.Percentage.ToLocalizedPercentage(), Count = 1, TotalCount = totalSteps }); TriggerOverlay(id, true, message, overlayProgress); }).DisposeWith(disposables); @@ -1205,12 +1192,8 @@ private void SubscribeToProgressReport(long id, CompositeDisposable disposables, modInvalidReplaceHandler = modDefinitionInvalidReplaceHandler.Subscribe(s => { var message = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Replacing_Definitions); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), new - { - PercentDone = s.Percentage.ToLocalizedPercentage(), - Count = 2, - TotalCount = totalSteps - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), + new { PercentDone = s.Percentage.ToLocalizedPercentage(), Count = 2, TotalCount = totalSteps }); TriggerOverlay(id, true, message, overlayProgress); }).DisposeWith(disposables); @@ -1218,12 +1201,8 @@ private void SubscribeToProgressReport(long id, CompositeDisposable disposables, gameIndexHandler = gameIndexProgressHandler.Subscribe(s => { var message = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Indexing_Game); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), new - { - PercentDone = s.Percentage.ToLocalizedPercentage(), - Count = 3, - TotalCount = totalSteps - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), + new { PercentDone = s.Percentage.ToLocalizedPercentage(), Count = 3, TotalCount = totalSteps }); TriggerOverlay(id, true, message, overlayProgress); }).DisposeWith(disposables); @@ -1231,12 +1210,8 @@ private void SubscribeToProgressReport(long id, CompositeDisposable disposables, gameDefinitionLoadHandler = gameDefinitionLoadProgressHandler.Subscribe(s => { var message = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Loading_Game_Definitions); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), new - { - PercentDone = s.Percentage.ToLocalizedPercentage(), - Count = 4, - TotalCount = totalSteps - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), + new { PercentDone = s.Percentage.ToLocalizedPercentage(), Count = 4, TotalCount = totalSteps }); TriggerOverlay(id, true, message, overlayProgress); }).DisposeWith(disposables); @@ -1244,12 +1219,8 @@ private void SubscribeToProgressReport(long id, CompositeDisposable disposables, definitionAnalyzeLoadHandler = modDefinitionAnalyzeHandler.Subscribe(s => { var message = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Analyzing_Conflicts); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), new - { - PercentDone = s.Percentage.ToLocalizedPercentage(), - Count = totalSteps == 6 ? 5 : 3, - TotalCount = totalSteps - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), + new { PercentDone = s.Percentage.ToLocalizedPercentage(), Count = totalSteps == 6 ? 5 : 3, TotalCount = totalSteps }); TriggerOverlay(id, true, message, overlayProgress); }).DisposeWith(disposables); @@ -1257,12 +1228,8 @@ private void SubscribeToProgressReport(long id, CompositeDisposable disposables, definitionSyncHandler = modDefinitionPatchLoadHandler.Subscribe(s => { var message = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Analyzing_Resolved_Conflicts); - var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), new - { - PercentDone = s.Percentage.ToLocalizedPercentage(), - Count = totalSteps == 6 ? 6 : 4, - TotalCount = totalSteps - }); + var overlayProgress = IronyFormatter.Format(localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.Overlay_Conflict_Solver_Progress), + new { PercentDone = s.Percentage.ToLocalizedPercentage(), Count = totalSteps == 6 ? 6 : 4, TotalCount = totalSteps }); TriggerOverlay(id, true, message, overlayProgress); }).DisposeWith(disposables); } From a2b2cb19b22357b5760eea1d7c698cd5b6c35029 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 21:03:05 +0100 Subject: [PATCH 062/101] Optimize gc calls --- .../Definitions/IndexedDefinitions.cs | 220 ++++++++++-------- .../GameIndexService.cs | 45 ++-- .../ModPatchCollectionService.cs | 8 +- src/IronyModManager.Shared/GCRunner.cs | 59 +++++ .../Controls/ModHolderControlViewModel.cs | 36 +-- 5 files changed, 220 insertions(+), 148 deletions(-) create mode 100644 src/IronyModManager.Shared/GCRunner.cs diff --git a/src/IronyModManager.Parser/Definitions/IndexedDefinitions.cs b/src/IronyModManager.Parser/Definitions/IndexedDefinitions.cs index 00f8516b..61343e4e 100644 --- a/src/IronyModManager.Parser/Definitions/IndexedDefinitions.cs +++ b/src/IronyModManager.Parser/Definitions/IndexedDefinitions.cs @@ -1,17 +1,17 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Parser.Definitions // Author : Mario // Created : 02-16-2020 // // Last Modified By : Mario -// Last Modified On : 12-13-2023 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -21,6 +21,7 @@ using System.Threading.Tasks; using CodexMicroORM.Core.Collections; using IronyModManager.DI; +using IronyModManager.Shared; using IronyModManager.Shared.KeyValueStore; using IronyModManager.Shared.Models; using IronyModManager.Shared.Trie; @@ -30,7 +31,6 @@ namespace IronyModManager.Parser.Definitions { - /// /// Class IndexedDefinitions. /// Implements the @@ -98,7 +98,7 @@ public class IndexedDefinitions : IIndexedDefinitions /// /// The disposed /// - private bool disposed = false; + private bool disposed; /// /// The file ci keys @@ -111,9 +111,9 @@ public class IndexedDefinitions : IIndexedDefinitions private long gameDefinitionsCount; /// - /// The main hierarchal definitions + /// A private ConcurrentIndexedList{IronyModManager.Shared.Models.IHierarchicalDefinitions} named mainHierarchicalDefinitions. /// - private ConcurrentIndexedList mainHierarchalDefinitions; + private ConcurrentIndexedList mainHierarchicalDefinitions; /// /// The reset definitions count @@ -123,12 +123,13 @@ public class IndexedDefinitions : IIndexedDefinitions /// /// The search database /// - private LiteDatabase searchDb = null; + private LiteDatabase searchDb; /// /// The search database path /// private string searchDbPath = string.Empty; + /// /// The store /// @@ -153,10 +154,11 @@ public class IndexedDefinitions : IIndexedDefinitions /// The type values /// private Dictionary> typeKeyValues; + /// - /// The use hierarchal map + /// A private bool named useHierarchicalMap. /// - private bool useHierarchalMap = false; + private bool useHierarchicalMap; #endregion Fields @@ -169,16 +171,16 @@ public IndexedDefinitions() { definitions = new ConcurrentIndexedList(nameof(IDefinition.FileCI), nameof(IDefinition.Type), nameof(IDefinition.TypeAndId), nameof(IDefinition.ParentDirectoryCI), nameof(IDefinition.ValueType), nameof(IDefinition.DiskFileCI)); - fileCIKeys = new Dictionary>(); - diskFileCIKeys = new Dictionary>(); - typeAndIdKeys = new HashSet(); - typeKeys = new Dictionary>(); - allFileKeys = new HashSet(); - directoryCIKeys = new Dictionary>(); - resetDefinitions = new HashSet(); - typeKeyValues = new Dictionary>(); + fileCIKeys = []; + diskFileCIKeys = []; + typeAndIdKeys = []; + typeKeys = []; + allFileKeys = []; + directoryCIKeys = []; + resetDefinitions = []; + typeKeyValues = []; childHierarchicalDefinitions = new ConcurrentDictionary>(); - mainHierarchalDefinitions = new ConcurrentIndexedList(nameof(IHierarchicalDefinitions.Name)); + mainHierarchicalDefinitions = new ConcurrentIndexedList(nameof(IHierarchicalDefinitions.Name)); } #endregion Constructors @@ -209,14 +211,14 @@ public Task AddToMapAsync(IDefinition definition, bool forceIgnoreHierarchical = /// Changes the state of the hierarchical reset. /// /// The definition. - /// true if XXXX, false otherwise. + /// true if changed, false otherwise. public async Task ChangeHierarchicalResetStateAsync(IDefinition definition) { using var mutex = await opLock.LockAsync(); if (definition != null) { - var parentDirectoryCI = ResolveHierarchalParentDirectory(definition); - var hierarchicalDefinition = mainHierarchalDefinitions.GetFirstByName(nameof(IHierarchicalDefinitions.Name), parentDirectoryCI); + var parentDirectoryCI = ResolveHierarchicalParentDirectory(definition); + var hierarchicalDefinition = mainHierarchicalDefinitions.GetFirstByName(nameof(IHierarchicalDefinitions.Name), parentDirectoryCI); if (hierarchicalDefinition != null) { if (childHierarchicalDefinitions.TryGetValue(hierarchicalDefinition.Name, out var children)) @@ -233,6 +235,7 @@ public async Task ChangeHierarchicalResetStateAsync(IDefinition definition } } } + mutex.Dispose(); return false; } @@ -246,6 +249,7 @@ public void Dispose() { return; } + GC.SuppressFinalize(this); disposed = true; definitions.Clear(); @@ -264,8 +268,8 @@ public void Dispose() allFileKeys = null; childHierarchicalDefinitions.Clear(); childHierarchicalDefinitions = null; - mainHierarchalDefinitions.Clear(); - mainHierarchalDefinitions = null; + mainHierarchicalDefinitions.Clear(); + mainHierarchicalDefinitions = null; trie = null; resetDefinitions.Clear(); resetDefinitions = null; @@ -297,6 +301,7 @@ public Task> GetAllAsync() { return ReadDefinitionsFromStoreAsync(typeAndIdKeys); } + return Task.FromResult>(new HashSet(definitions)); } @@ -306,7 +311,7 @@ public Task> GetAllAsync() /// IEnumerable<System.String>. public Task> GetAllDirectoryKeysAsync() { - return Task.FromResult>(directoryCIKeys.Keys.ToHashSet()); + return Task.FromResult>([.. directoryCIKeys.Keys]); } /// @@ -315,7 +320,7 @@ public Task> GetAllDirectoryKeysAsync() /// IEnumerable<System.String>. public Task> GetAllFileKeysAsync() { - return Task.FromResult>(fileCIKeys.Keys.ToHashSet()); + return Task.FromResult>([.. fileCIKeys.Keys]); } /// @@ -324,7 +329,7 @@ public Task> GetAllFileKeysAsync() /// IEnumerable<System.String>. public Task> GetAllTypeAndIdKeysAsync() { - return Task.FromResult>(typeAndIdKeys.ToHashSet()); + return Task.FromResult>([.. typeAndIdKeys]); } /// @@ -333,7 +338,7 @@ public Task> GetAllTypeAndIdKeysAsync() /// IEnumerable<System.String>. public Task> GetAllTypeKeysAsync() { - return Task.FromResult>(typeKeys.Keys.ToHashSet()); + return Task.FromResult>([.. typeKeys.Keys]); } /// @@ -350,8 +355,10 @@ public Task> GetByDiskFileAsync(string file) { return ReadDefinitionsFromStoreAsync(value); } - return Task.FromResult>(Array.Empty()); + + return Task.FromResult>([]); } + return Task.FromResult(definitions.GetAllByName(nameof(IDefinition.DiskFileCI), file.ToLowerInvariant())); } @@ -365,12 +372,9 @@ public Task> GetByFileAsync(string file) EnsureAllowedAllIsRespected(); if (store != null) { - if (fileCIKeys.TryGetValue(file, out var value)) - { - return ReadDefinitionsFromStoreAsync(value); - } - return Task.FromResult>(Array.Empty()); + return fileCIKeys.TryGetValue(file, out var value) ? ReadDefinitionsFromStoreAsync(value) : Task.FromResult>([]); } + return Task.FromResult(definitions.GetAllByName(nameof(IDefinition.FileCI), file.ToLowerInvariant())); } @@ -388,8 +392,10 @@ public Task> GetByParentDirectoryAsync(string directory { return ReadDefinitionsFromStoreAsync(value); } - return Task.FromResult>(Array.Empty()); + + return Task.FromResult>([]); } + return Task.FromResult(definitions.GetAllByName(nameof(IDefinition.ParentDirectoryCI), directory.ToLowerInvariant())); } @@ -416,6 +422,7 @@ public async Task> GetByTypeAndIdAsync(string typeAndId { return await store.ReadAsync(typeAndId); } + return definitions.GetAllByName(nameof(IDefinition.TypeAndId), typeAndId); } @@ -433,8 +440,10 @@ public Task> GetByTypeAsync(string type) { return ReadDefinitionsFromStoreAsync(value); } - return Task.FromResult>(Array.Empty()); + + return Task.FromResult>([]); } + return Task.FromResult(definitions.GetAllByName(nameof(IDefinition.Type), type)); } @@ -443,6 +452,7 @@ public Task> GetByTypeAsync(string type) /// /// The type. /// IEnumerable<IDefinition>. + /// Only invalid types can be queried. /// Only invalid types can be queried. public Task> GetByValueTypeAsync(ValueType type) { @@ -450,14 +460,17 @@ public Task> GetByValueTypeAsync(ValueType type) { throw new ArgumentException("Only invalid types can be queried."); } + if (store != null) { if (typeKeyValues.TryGetValue(type, out var value)) { return ReadDefinitionsFromStoreAsync(value); } - return Task.FromResult>(Array.Empty()); + + return Task.FromResult>([]); } + return Task.FromResult(definitions.GetAllByName(nameof(IDefinition.ValueType), type)); } @@ -467,15 +480,16 @@ public Task> GetByValueTypeAsync(ValueType type) /// IEnumerable<IHierarchicalDefinitions>. public IEnumerable GetHierarchicalDefinitions() { - var hierarchicalDefinitions = CopyHierarchicalDefinition(mainHierarchalDefinitions); + var hierarchicalDefinitions = CopyHierarchicalDefinition(mainHierarchicalDefinitions); foreach (var item in hierarchicalDefinitions) { if (childHierarchicalDefinitions.TryGetValue(item.Name, out var value)) { - item.Children = CopyHierarchicalDefinition(value.Select(p => p).OrderBy(p => p.Name).ToHashSet()).ToHashSet(); + item.Children = [.. CopyHierarchicalDefinition([.. value.Select(p => p).OrderBy(p => p.Name)])]; } } - return hierarchicalDefinitions.Select(p => p).OrderBy(p => p.Name).ToHashSet(); + + return [.. hierarchicalDefinitions.Select(p => p).OrderBy(p => p.Name)]; } /// @@ -493,7 +507,7 @@ public Task HasGameDefinitionsAsync() /// true if [has reset definitions]; otherwise, false. public Task HasResetDefinitionsAsync() { - return Task.FromResult(resetDefinitions.Any()); + return Task.FromResult(resetDefinitions.Count != 0); } /// @@ -503,7 +517,7 @@ public Task HasResetDefinitionsAsync() /// Task. public async Task InitializeSearchAsync(IReadOnlyCollection definitions) { - if (definitions != null && definitions.Any()) + if (definitions != null && definitions.Count != 0) { var total = definitions.Count; var counter = 0; @@ -530,17 +544,17 @@ await Task.Run(() => { counter++; var displayName = $"{definition.Id} - {definition.File} - {definition.ModName}"; - var item = new DefinitionSearch() { DisplayName = displayName, Tags = definition.Tags.ToArray() }; + var item = new DefinitionSearch { DisplayName = displayName, Tags = [.. definition.Tags] }; items.Add(item); OnProcessedSearchItem(counter, total); } + var col = searchDb.GetCollection(SearchTableName); col.EnsureIndex(x => x.Tags); col.InsertBulk(items); }); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); + + GCRunner.RunGC(GCCollectionMode.Optimized, false); } } } @@ -553,7 +567,7 @@ await Task.Run(() => /// A Task representing the asynchronous operation. public async Task InitMapAsync(IEnumerable definitions, bool mapHierarchicalDefinitions = false) { - useHierarchalMap = mapHierarchicalDefinitions; + useHierarchicalMap = mapHierarchicalDefinitions; if (definitions != null) { foreach (var item in definitions) @@ -575,10 +589,12 @@ public async Task RemoveAsync(IDefinition definition) { gameDefinitionsCount--; } + if (gameDefinitionsCount < 0) { gameDefinitionsCount = 0; } + AddOrRemoveFromResetDefinitions(definition, false); if (store != null) { @@ -594,7 +610,8 @@ public async Task RemoveAsync(IDefinition definition) { definitions.Remove(definition); } - var hierarchicalDefinition = mainHierarchalDefinitions.GetFirstByName(nameof(IHierarchicalDefinitions.Name), ResolveHierarchalParentDirectory(definition)); + + var hierarchicalDefinition = mainHierarchicalDefinitions.GetFirstByName(nameof(IHierarchicalDefinitions.Name), ResolveHierarchicalParentDirectory(definition)); if (hierarchicalDefinition != null) { if (childHierarchicalDefinitions.TryGetValue(hierarchicalDefinition.Name, out var children)) @@ -604,19 +621,22 @@ public async Task RemoveAsync(IDefinition definition) { children.Remove(child); } - bool removed = false; + + var removed = false; if (!children.Select(p => p).Any()) { removed = true; childHierarchicalDefinitions.TryRemove(hierarchicalDefinition.Name, out _); - mainHierarchalDefinitions.Remove(hierarchicalDefinition); + mainHierarchicalDefinitions.Remove(hierarchicalDefinition); } + if (!removed) { - hierarchicalDefinition.ResetType = children.Any() && children.Any(p => p.ResetType != ResetType.None) ? ResetType.Any : ResetType.None; + hierarchicalDefinition.ResetType = children.Count != 0 && children.Any(p => p.ResetType != ResetType.None) ? ResetType.Any : ResetType.None; } } } + mutex.Dispose(); } @@ -628,10 +648,11 @@ public async Task RemoveAsync(IDefinition definition) /// IEnumerable<IDefinition>. public async Task> SearchDefinitionsAsync(string searchTerm, CancellationToken? token = null) { - if (token != null && token.GetValueOrDefault().IsCancellationRequested) + if (token is { IsCancellationRequested: true }) { return null; } + if (trie != null) { var tags = trie.Get(searchTerm.ToLowerInvariant()); @@ -647,13 +668,12 @@ public async Task> SearchDefinitionsAsync(string searchTerm, { var col = searchDb.GetCollection(SearchTableName); var result = col.Query().Where(x => x.Tags.Any(f => f.Contains(searchTerm, StringComparison.OrdinalIgnoreCase))).Select(p => p.DisplayName).ToList(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); + GCRunner.RunGC(GCCollectionMode.Optimized); return Task.FromResult(result.Distinct()); }, token ?? CancellationToken.None); return result; } + return null; } @@ -661,13 +681,15 @@ public async Task> SearchDefinitionsAsync(string searchTerm, /// Sets the type of the allowed. /// /// Type of the allowed. + /// Cannot set allowed type index definition is already initialized. /// Cannot set allowed type index definition is already initialized. public void SetAllowedType(AddToMapAllowedType allowedType) { - if (typeKeys.Any()) + if (typeKeys.Count != 0) { throw new InvalidOperationException("Cannot set allowed type index definition is already initialized."); } + this.allowedType = allowedType; } @@ -679,21 +701,24 @@ public void SetAllowedType(AddToMapAllowedType allowedType) public async Task UpdateDefinitionsAsync(IReadOnlyCollection definitions) { // No implementation for in memory variants - if (definitions == null || !definitions.Any()) + if (definitions == null || definitions.Count == 0) { return false; } + if (store != null) { using var mutex = await opLock.LockAsync(); var group = definitions.GroupBy(p => p.TypeAndId); foreach (var item in group) { - await store.InsertAsync(item.Key, item.ToList()); + await store.InsertAsync(item.Key, [.. item]); } + mutex.Dispose(); return true; } + return true; } @@ -701,21 +726,16 @@ public async Task UpdateDefinitionsAsync(IReadOnlyCollection /// Uses the disk store. /// /// The store path. + /// Unable to switch to disk store as there are items in the memory. /// Unable to switch to disk store as there are items in the memory. public void UseDiskStore(string storePath) { - if (definitions.Any()) + if (definitions.Count != 0) { throw new InvalidOperationException("Unable to switch to disk store as there are items in the memory."); } - store = new Store>(ResolveStoragePath(storePath), (type) => - { - if (type.Equals(nameof(IDefinition))) - { - return DIResolver.GetImplementationType(typeof(IDefinition)); - } - return null; - }); + + store = new Store>(ResolveStoragePath(storePath), type => type.Equals(nameof(IDefinition)) ? DIResolver.GetImplementationType(typeof(IDefinition)) : null); } /// @@ -723,7 +743,7 @@ public void UseDiskStore(string storePath) /// /// The database path which is specified indicates that db provider is used. /// The database path suffix. Not used if dbPath is not provided - public void UseSearch(string dbPath = Shared.Constants.EmptyParam, string dbPathSuffix = Shared.Constants.EmptyParam) + public void UseSearch(string dbPath = Constants.EmptyParam, string dbPathSuffix = Constants.EmptyParam) { if (!string.IsNullOrWhiteSpace(dbPath)) { @@ -731,7 +751,7 @@ public void UseSearch(string dbPath = Shared.Constants.EmptyParam, string dbPath DisposeSearchDB(); if (!Directory.Exists(Path.GetDirectoryName(searchDbPath))) { - Directory.CreateDirectory(Path.GetDirectoryName(searchDbPath)); + Directory.CreateDirectory(Path.GetDirectoryName(searchDbPath)!); } } else @@ -747,11 +767,11 @@ public void UseSearch(string dbPath = Shared.Constants.EmptyParam, string dbPath /// System.String. protected virtual string ResolveStoragePath(string path) { - return Path.Combine(path, Parser.Common.Constants.StoreCacheRootRolder); + return Path.Combine(path, Common.Constants.StoreCacheRootRolder); } /// - /// Adds the or remove from reset definitions. + /// Adds or removes from reset definitions. /// /// The definition. /// if set to true [add]. @@ -796,6 +816,7 @@ async Task addDefinition() { mutex = await opLock.LockAsync(); } + MapKeys(fileCIKeys, definition.FileCI, definition.TypeAndId); MapKeys(typeKeys, definition.Type, definition.TypeAndId); MapKeys(typeAndIdKeys, ConstructKey(definition.Type, definition.Id)); @@ -807,6 +828,7 @@ async Task addDefinition() MapKeys(diskFileCIKeys, definition.DiskFileCI, definition.TypeAndId); MapKeys(allFileKeys, definition.DiskFileCI); } + if (definition.OverwrittenFileNames?.Count > 0) { foreach (var item in definition.OverwrittenFileNames) @@ -814,10 +836,12 @@ async Task addDefinition() MapKeys(allFileKeys, item.ToLowerInvariant()); } } - if (useHierarchalMap && !forceIgnoreHierarchical) + + if (useHierarchicalMap && !forceIgnoreHierarchical) { MapHierarchicalDefinition(definition); } + if (definition.IsFromGame) { gameDefinitionsCount++; @@ -830,6 +854,7 @@ async Task addDefinition() { await addDefinition(); } + break; default: await addDefinition(); @@ -854,7 +879,7 @@ private string ConstructKey(params string[] keys) /// /// The source. /// IEnumerable<IHierarchicalDefinitions>. - private IEnumerable CopyHierarchicalDefinition(IEnumerable source) + private HashSet CopyHierarchicalDefinition(IEnumerable source) { var result = new HashSet(); foreach (var item in source) @@ -869,6 +894,7 @@ private IEnumerable CopyHierarchicalDefinition(IEnumer copy.NonGameDefinitions = item.NonGameDefinitions; result.Add(copy); } + return result; } @@ -890,6 +916,7 @@ private void DisposeSearchDB() { item.Attributes = FileAttributes.Normal; } + dirInfo.Delete(true); } } @@ -903,6 +930,7 @@ private void DisposeSearchDB() /// Ensures the allowed all is respected. /// /// if set to true [allow invalid]. + /// Collection is empty. /// Collection is empty. private void EnsureAllowedAllIsRespected(bool allowInvalid = false) { @@ -931,9 +959,9 @@ private LiteDatabase GetDatabase(string path) /// The definition. private void MapHierarchicalDefinition(IDefinition definition) { - bool shouldAdd = false; - var parentDirectoryCI = ResolveHierarchalParentDirectory(definition); - var hierarchicalDefinition = mainHierarchalDefinitions.GetFirstByName(nameof(IHierarchicalDefinitions.Name), parentDirectoryCI); + var shouldAdd = false; + var parentDirectoryCI = ResolveHierarchicalParentDirectory(definition); + var hierarchicalDefinition = mainHierarchicalDefinitions.GetFirstByName(nameof(IHierarchicalDefinitions.Name), parentDirectoryCI); if (hierarchicalDefinition == null) { hierarchicalDefinition = DIResolver.Get(); @@ -941,23 +969,25 @@ private void MapHierarchicalDefinition(IDefinition definition) childHierarchicalDefinitions.TryAdd(parentDirectoryCI, new ConcurrentIndexedList(nameof(IHierarchicalDefinitions.Name))); shouldAdd = true; } - bool exists = false; + + var exists = false; IHierarchicalDefinitions child = null; if (childHierarchicalDefinitions.TryGetValue(hierarchicalDefinition.Name, out var children)) { child = children.GetFirstByName(nameof(IHierarchicalDefinitions.Name), definition.Id); exists = child != null; } + if (!exists) { child = DIResolver.Get(); child.Name = definition.Id; child.Key = definition.TypeAndId; child.FileNames.Add(definition.FileCI); - children.Add(child); + children!.Add(child); if (shouldAdd) { - mainHierarchalDefinitions.Add(hierarchicalDefinition); + mainHierarchicalDefinitions.Add(hierarchicalDefinition); } } else @@ -967,23 +997,27 @@ private void MapHierarchicalDefinition(IDefinition definition) child.FileNames.Add(definition.FileCI); } } + if (definition.ResetType != ResetType.None) { child.ResetType = definition.ResetType; hierarchicalDefinition.ResetType = ResetType.Any; AddOrRemoveFromResetDefinitions(definition, true); } - child.Mods ??= new List(); + + child.Mods ??= []; if (!child.Mods.Contains(definition.ModName) && !definition.IsFromGame) { child.Mods.Add(definition.ModName); } + if (!definition.IsFromGame) { child.NonGameDefinitions++; hierarchicalDefinition.NonGameDefinitions++; } - hierarchicalDefinition.Mods ??= new List(); + + hierarchicalDefinition.Mods ??= []; if (!hierarchicalDefinition.Mods.Contains(definition.ModName) && !definition.IsFromGame) { hierarchicalDefinition.Mods.Add(definition.ModName); @@ -997,10 +1031,7 @@ private void MapHierarchicalDefinition(IDefinition definition) /// The key. private void MapKeys(HashSet map, string key) { - if (!map.Contains(key)) - { - map.Add(key); - } + map.Add(key); } /// @@ -1012,7 +1043,7 @@ private void MapKeys(HashSet map, string key) /// The cache value. private void MapKeys(Dictionary> map, T key, string cacheValue) { - if (object.Equals(key, default(T))) + if (Equals(key, default(T))) { return; } @@ -1020,13 +1051,14 @@ private void MapKeys(Dictionary> map, T key, string cacheV { return; } + if (map.TryGetValue(key, out var values)) { values.Add(cacheValue); } else { - map[key] = new HashSet() { cacheValue }; + map[key] = [cacheValue]; } } @@ -1037,7 +1069,7 @@ private void MapKeys(Dictionary> map, T key, string cacheV /// The total. private void OnProcessedSearchItem(int current, int total) { - ProcessedSearchItem?.Invoke(this, new ProcessedArgs() { Current = current, Total = total }); + ProcessedSearchItem?.Invoke(this, new ProcessedArgs { Current = current, Total = total }); } /// @@ -1053,17 +1085,13 @@ private async Task> ReadDefinitionsFromStoreAsync(IRead } /// - /// Resolves the hierarchal parent directory. + /// Resolves the hierarchical parent directory. /// /// The definition. /// System.String. - private string ResolveHierarchalParentDirectory(IDefinition definition) + private string ResolveHierarchicalParentDirectory(IDefinition definition) { - if (string.IsNullOrWhiteSpace(definition.VirtualParentDirectoryCI)) - { - return definition.ParentDirectoryCI; - } - return definition.VirtualParentDirectoryCI; + return string.IsNullOrWhiteSpace(definition.VirtualParentDirectoryCI) ? definition.ParentDirectoryCI : definition.VirtualParentDirectoryCI; } /// @@ -1081,7 +1109,7 @@ private async Task UpdateStoreDefinitionAsync(IDefinition definition) } else { - await store.InsertAsync(definition.TypeAndId, new List() { definition }); + await store.InsertAsync(definition.TypeAndId, [definition]); } } diff --git a/src/IronyModManager.Services/GameIndexService.cs b/src/IronyModManager.Services/GameIndexService.cs index a0ea53e0..a6027482 100644 --- a/src/IronyModManager.Services/GameIndexService.cs +++ b/src/IronyModManager.Services/GameIndexService.cs @@ -1,17 +1,17 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Services // Author : Mario // Created : 05-27-2021 // // Last Modified By : Mario -// Last Modified On : 11-27-2023 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -29,6 +29,7 @@ using IronyModManager.Parser.Common.Mod; using IronyModManager.Services.Common; using IronyModManager.Services.Common.MessageBus; +using IronyModManager.Shared; using IronyModManager.Shared.Cache; using IronyModManager.Shared.MessageBus; using IronyModManager.Shared.Models; @@ -37,7 +38,6 @@ namespace IronyModManager.Services { - /// /// Class GameIndexService. /// Implements the @@ -111,7 +111,7 @@ public GameIndexService(IMessageBus messageBus, IParserManager parserManager, IG /// The game. /// The versions. /// The indexed definitions. - /// true if XXXX, false otherwise. + /// true if indexed, false otherwise. public virtual async Task IndexDefinitionsAsync(IGame game, IEnumerable versions, IIndexedDefinitions indexedDefinitions) { if (game != null && versions != null && versions.Any()) @@ -122,15 +122,16 @@ public virtual async Task IndexDefinitionsAsync(IGame game, IEnumerable game.GameFolders.Any(x => p.StartsWith(x))); + files = files.Where(p => game.GameFolders.Any(p.StartsWith)); var indexedFolders = (await indexedDefinitions.GetAllDirectoryKeysAsync()).Select(p => p.ToLowerInvariant()); - var validFolders = files.Select(p => Path.GetDirectoryName(p)).GroupBy(p => p).Select(p => p.FirstOrDefault()).Where(p => indexedFolders.Any(a => a.ToLowerInvariant().Equals(p.ToLowerInvariant()))); + var validFolders = files.Select(Path.GetDirectoryName).GroupBy(p => p).Select(p => p.FirstOrDefault()).Where(p => indexedFolders.Any(a => a.ToLowerInvariant().Equals(p!.ToLowerInvariant()))); var folders = new List(); foreach (var item in validFolders) { @@ -139,6 +140,7 @@ public virtual async Task IndexDefinitionsAsync(IGame game, IEnumerable using var mutex = await asyncServiceLock.LockAsync(); processed++; var perc = GetProgressPercentage(total, processed, 100); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { await messageBus.PublishAsync(new GameIndexProgressEvent(perc)); previousProgress = perc; } - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); + + GCRunner.RunGC(GCCollectionMode.Optimized); mutex.Dispose(); } @@ -179,10 +180,13 @@ await Task.Run(async () => }); await Task.WhenAll(tasks); } + return true; } + return false; } + return false; } @@ -211,11 +215,12 @@ public virtual async Task LoadDefinitionsAsync(IIndexedDefi var definitions = await Task.Run(async () => await gameIndexer.GetDefinitionsAsync(GetStoragePath(), game, directory)); if ((definitions?.Any()).GetValueOrDefault()) { - foreach (var def in definitions) + foreach (var def in definitions!) { def.ModName = game.Name; def.IsFromGame = true; } + return definitions; } } @@ -223,6 +228,7 @@ public virtual async Task LoadDefinitionsAsync(IIndexedDefi { semaphore.Release(); } + return null; }).ToList(); while (tasks.Any()) @@ -236,22 +242,24 @@ public virtual async Task LoadDefinitionsAsync(IIndexedDefi gameDefinitions.Add(item); } } + processed++; var perc = GetProgressPercentage(total, processed, 100); - if (perc != previousProgress) + if (perc.IsNotNearlyEqual(previousProgress)) { await messageBus.PublishAsync(new GameDefinitionLoadProgressEvent(perc)); previousProgress = perc; } - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); + + GCRunner.RunGC(GCCollectionMode.Optimized); } + foreach (var item in gameDefinitions) { await modDefinitions.AddToMapAsync(item); } } + return modDefinitions; } @@ -273,6 +281,7 @@ protected virtual double GetProgressPercentage(double total, double processed, d { perc = maxPerc; } + return perc; } @@ -298,10 +307,11 @@ protected virtual IEnumerable ParseGameFiles(IGame game, IEnumerabl { return null; } + var definitions = new List(); foreach (var fileInfo in fileInfos) { - var fileDefs = parserManager.Parse(new ParserManagerArgs() + var fileDefs = parserManager.Parse(new ParserManagerArgs { ContentSHA = fileInfo.ContentSHA, File = Path.Combine(folder, fileInfo.FileName), @@ -313,6 +323,7 @@ protected virtual IEnumerable ParseGameFiles(IGame game, IEnumerabl MergeDefinitions(fileDefs); definitions.AddRange(fileDefs); } + return definitions; } diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index ad74eb0f..a79dd024 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -938,9 +938,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE previousProgress = perc; } - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); + GCRunner.RunGC(GCCollectionMode.Optimized); } }); @@ -1049,9 +1047,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE } tempIndex.Dispose(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized); + GCRunner.RunGC(GCCollectionMode.Optimized); } else { diff --git a/src/IronyModManager.Shared/GCRunner.cs b/src/IronyModManager.Shared/GCRunner.cs new file mode 100644 index 00000000..380b9762 --- /dev/null +++ b/src/IronyModManager.Shared/GCRunner.cs @@ -0,0 +1,59 @@ +// *********************************************************************** +// Assembly : IronyModManager.Shared +// Author : Mario +// Created : 02-23-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-23-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace IronyModManager.Shared +{ + /// + /// The gc runner. + /// + public class GCRunner + { + #region Methods + + /// + /// Run gc. + /// + /// The mode. + /// If true, blocking. + /// + public static void RunGC(GCCollectionMode mode, bool blocking = false) + { + if (blocking) + { + Task.Run(() => + { + GC.Collect(GC.MaxGeneration, mode, blocking, blocking); + GC.WaitForPendingFinalizers(); + GC.Collect(GC.MaxGeneration, mode, blocking, blocking); + }); + } + else + { + Task.Run(() => + { + GC.Collect(GC.MaxGeneration, mode); + GC.WaitForPendingFinalizers(); + GC.Collect(GC.MaxGeneration, mode); + }); + } + } + + #endregion Methods + } +} diff --git a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs index 103e48de..5c175df5 100644 --- a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs @@ -592,10 +592,7 @@ protected virtual async Task AnalyzeModsAsync(long id, PatchStateMode mode, IEnu tooLargeMod = true; } - // To stop people from whining - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); + GCRunner.RunGC(GCCollectionMode.Aggressive, true); return result; }).ConfigureAwait(false); @@ -610,11 +607,7 @@ protected virtual async Task AnalyzeModsAsync(long id, PatchStateMode mode, IEnu gameIndexHandler?.Dispose(); gameDefinitionLoadHandler?.Dispose(); - // I know, I know... but I wanna force a cleanup - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - + GCRunner.RunGC(GCCollectionMode.Aggressive, true); var largeMessageTitle = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.TooLargePrompt.Title); var largeMessageBody = localizationManager.GetResource(LocalizationResources.Mod_Actions.ConflictSolver.TooLargePrompt.Message); notificationAction.ShowNotification(largeMessageTitle, largeMessageBody, NotificationType.Error, 60); @@ -628,10 +621,7 @@ await Task.Run(async () => { await gameIndexService.IndexDefinitionsAsync(game, versions, definitions); - // To stop people from whining - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); + GCRunner.RunGC(GCCollectionMode.Aggressive, true); }); Debug.WriteLine("Conflict Solver Stage 2: " + stopWatch.Elapsed.FormatElapsed()); @@ -640,10 +630,7 @@ await Task.Run(async () => { var result = await gameIndexService.LoadDefinitionsAsync(definitions, game, versions); - // To stop people from whining - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); + GCRunner.RunGC(GCCollectionMode.Aggressive, true); return result; }).ConfigureAwait(false); Debug.WriteLine("Conflict Solver Stage 3: " + stopWatch.Elapsed.FormatElapsed()); @@ -654,11 +641,8 @@ await Task.Run(async () => { if (definitions != null) { - // To stop people from whining var result = await modPatchCollectionService.FindConflictsAsync(definitions, CollectionMods.SelectedMods.Select(p => p.Name).ToList(), mode); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); + GCRunner.RunGC(GCCollectionMode.Aggressive, true); return result; } @@ -671,10 +655,7 @@ await Task.Run(async () => { var result = await modPatchCollectionService.InitializePatchStateAsync(conflicts, CollectionMods.SelectedModCollection.Name).ConfigureAwait(false); - // To stop people from whining - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); + GCRunner.RunGC(GCCollectionMode.Aggressive, true); return result; }).ConfigureAwait(false); if (syncedConflicts != null) @@ -699,10 +680,7 @@ await Task.Run(async () => gameIndexHandler?.Dispose(); gameDefinitionLoadHandler?.Dispose(); - // I know, I know... but I wanna force a cleanup - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - GC.WaitForPendingFinalizers(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); + GCRunner.RunGC(GCCollectionMode.Aggressive, true); } /// From 5e7dc2708d34d5ab628ad002ca626a75f2e8d00e Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 21:18:41 +0100 Subject: [PATCH 063/101] Cleanup --- src/IronyModManager.Services/ModPatchCollectionService.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index a79dd024..2c26aec5 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -194,10 +194,6 @@ public class ModPatchCollectionService( #endregion Fields - #region Constructors - - #endregion Constructors - #region Enums /// From fec9f3ad5535c86ca14578ad1df22a8f041735f6 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 22:10:29 +0100 Subject: [PATCH 064/101] Cleanup --- .../GameIndexService.cs | 66 +++-- .../ModBaseService.cs | 258 +++++++++--------- .../ModPatchCollectionService.cs | 234 ++++++++-------- 3 files changed, 274 insertions(+), 284 deletions(-) diff --git a/src/IronyModManager.Services/GameIndexService.cs b/src/IronyModManager.Services/GameIndexService.cs index a6027482..2547013a 100644 --- a/src/IronyModManager.Services/GameIndexService.cs +++ b/src/IronyModManager.Services/GameIndexService.cs @@ -45,7 +45,32 @@ namespace IronyModManager.Services /// /// /// - public class GameIndexService : ModBaseService, IGameIndexService + /// + /// Initializes a new instance of the class. + /// + /// The message bus. + /// The parser manager. + /// The game indexer. + /// The cache. + /// The definition information providers. + /// The reader. + /// The mod writer. + /// The mod parser. + /// The game service. + /// The storage provider. + /// The mapper. + public class GameIndexService( + IMessageBus messageBus, + IParserManager parserManager, + IGameIndexer gameIndexer, + ICache cache, + IEnumerable definitionInfoProviders, + IReader reader, + IModWriter modWriter, + IModParser modParser, + IGameService gameService, + IStorageProvider storageProvider, + IMapper mapper) : ModBaseService(cache, definitionInfoProviders, reader, modWriter, modParser, gameService, storageProvider, mapper), IGameIndexService { #region Fields @@ -62,47 +87,20 @@ public class GameIndexService : ModBaseService, IGameIndexService /// /// The game indexer /// - private readonly IGameIndexer gameIndexer; + private readonly IGameIndexer gameIndexer = gameIndexer; /// /// The message bus /// - private readonly IMessageBus messageBus; + private readonly IMessageBus messageBus = messageBus; /// /// The parser manager /// - private readonly IParserManager parserManager; + private readonly IParserManager parserManager = parserManager; #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The message bus. - /// The parser manager. - /// The game indexer. - /// The cache. - /// The definition information providers. - /// The reader. - /// The mod writer. - /// The mod parser. - /// The game service. - /// The storage provider. - /// The mapper. - public GameIndexService(IMessageBus messageBus, IParserManager parserManager, IGameIndexer gameIndexer, ICache cache, IEnumerable definitionInfoProviders, IReader reader, - IModWriter modWriter, IModParser modParser, IGameService gameService, IStorageProvider storageProvider, IMapper mapper) : - base(cache, definitionInfoProviders, reader, modWriter, modParser, gameService, storageProvider, mapper) - { - this.gameIndexer = gameIndexer; - this.messageBus = messageBus; - this.parserManager = parserManager; - } - - #endregion Constructors - #region Methods /// @@ -123,7 +121,7 @@ public virtual async Task IndexDefinitionsAsync(IGame game, IEnumerable IndexDefinitionsAsync(IGame game, IEnumerable LoadDefinitionsAsync(IIndexedDefi return null; }).ToList(); - while (tasks.Any()) + while (tasks.Count != 0) { var result = await Task.WhenAny(tasks); tasks.Remove(result); diff --git a/src/IronyModManager.Services/ModBaseService.cs b/src/IronyModManager.Services/ModBaseService.cs index a1a6fa73..37c6c4cd 100644 --- a/src/IronyModManager.Services/ModBaseService.cs +++ b/src/IronyModManager.Services/ModBaseService.cs @@ -1,11 +1,10 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Services // Author : Mario // Created : 04-07-2020 // // Last Modified By : Mario -// Last Modified On : 11-30-2023 +// Last Modified On : 02-23-2024 // *********************************************************************** // // Mario @@ -38,12 +37,28 @@ namespace IronyModManager.Services { - /// /// Class ModBaseService. Implements the /// /// - public abstract class ModBaseService : BaseService + /// The cache. + /// The definition information providers. + /// The reader. + /// The mod writer. + /// The mod parser. + /// The game service. + /// The storage provider. + /// The mapper. + /// Initializes a new instance of the class. + public abstract class ModBaseService( + ICache cache, + IEnumerable definitionInfoProviders, + IReader reader, + IModWriter modWriter, + IModParser modParser, + IGameService gameService, + IStorageProvider storageProvider, + IMapper mapper) : BaseService(storageProvider, mapper) { #region Fields @@ -75,7 +90,7 @@ public abstract class ModBaseService : BaseService /// /// The path resolver /// - protected readonly IGameRootPathResolver pathResolver; + protected readonly IGameRootPathResolver PathResolver = new GameRootPathResolver(); /// /// The clean variables pattern @@ -84,71 +99,43 @@ public abstract class ModBaseService : BaseService #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The cache. - /// The definition information providers. - /// The reader. - /// The mod writer. - /// The mod parser. - /// The game service. - /// The storage provider. - /// The mapper. - public ModBaseService(ICache cache, IEnumerable definitionInfoProviders, IReader reader, IModWriter modWriter, - IModParser modParser, IGameService gameService, - IStorageProvider storageProvider, IMapper mapper) : base(storageProvider, mapper) - { - Cache = cache; - DefinitionInfoProviders = definitionInfoProviders; - GameService = gameService; - Reader = reader; - ModParser = modParser; - ModWriter = modWriter; - pathResolver = new GameRootPathResolver(); - } - - #endregion Constructors - #region Properties /// /// Gets the cache. /// /// The cache. - protected ICache Cache { get; private set; } + protected ICache Cache { get; } = cache; /// /// Gets the definition information providers. /// /// The definition information providers. - protected IEnumerable DefinitionInfoProviders { get; private set; } + protected IEnumerable DefinitionInfoProviders { get; } = definitionInfoProviders; /// /// Gets the game service. /// /// The game service. - protected IGameService GameService { get; private set; } + protected IGameService GameService { get; } = gameService; /// /// Gets the mod parser. /// /// The mod parser. - protected IModParser ModParser { get; private set; } + protected IModParser ModParser { get; } = modParser; /// /// Gets the mod writer. /// /// The mod writer. - protected IModWriter ModWriter { get; private set; } + protected IModWriter ModWriter { get; } = modWriter; /// /// Gets the reader. /// /// The reader. - protected IReader Reader { get; private set; } + protected IReader Reader { get; } = reader; #endregion Properties @@ -166,9 +153,10 @@ protected virtual bool CheckIfModShouldBeLocked(IGame game, IMod mod) { var fullPath = mod.FullPath ?? string.Empty; return IsPatchModInternal(mod.Name) || (mod.Source == ModSource.Local && - (fullPath.EndsWith(Shared.Constants.ZipExtension, StringComparison.OrdinalIgnoreCase) || fullPath.EndsWith(Shared.Constants.BinExtension, StringComparison.OrdinalIgnoreCase)) && - (fullPath.StartsWith(game.UserDirectory) || fullPath.StartsWith(game.CustomModDirectory))); + (fullPath.EndsWith(Shared.Constants.ZipExtension, StringComparison.OrdinalIgnoreCase) || fullPath.EndsWith(Shared.Constants.BinExtension, StringComparison.OrdinalIgnoreCase)) && + (fullPath.StartsWith(game.UserDirectory) || fullPath.StartsWith(game.CustomModDirectory))); } + return false; } @@ -187,7 +175,7 @@ protected virtual IDefinition CopyDefinition(IDefinition definition) /// delete descriptors internal as an asynchronous operation. /// /// The mods. - /// true if XXXX, false otherwise. + /// true if deleted, false otherwise. protected virtual async Task DeleteDescriptorsInternalAsync(IEnumerable mods) { var game = GameService.GetSelected(); @@ -196,22 +184,20 @@ protected virtual async Task DeleteDescriptorsInternalAsync(IEnumerable(); foreach (var item in mods) { - var task = ModWriter.DeleteDescriptorAsync(new ModWriterParameters() - { - Mod = item, - RootDirectory = game.UserDirectory - }); + var task = ModWriter.DeleteDescriptorAsync(new ModWriterParameters { Mod = item, RootDirectory = game.UserDirectory }); tasks.Add(task); } + await Task.WhenAll(tasks); - Cache.Invalidate(new CacheInvalidateParameters() { Region = ModsCacheRegion, Prefix = game.Type, Keys = new List { GetModsCacheKey(true), GetModsCacheKey(false) } }); + Cache.Invalidate(new CacheInvalidateParameters { Region = ModsCacheRegion, Prefix = game.Type, Keys = [GetModsCacheKey(true), GetModsCacheKey(false)] }); return true; } + return false; } /// - /// Evals the definition priority internal. + /// Evaluates the definition priority internal. /// /// The definitions. /// if set to true [force fios]. @@ -232,7 +218,7 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum if (!noProvider) { // Handle localizations differently - var file = definitions.FirstOrDefault().File ?? string.Empty; + var file = definitions.FirstOrDefault()!.File ?? string.Empty; if (file.StartsWith(Shared.Constants.LocalizationDirectory)) { IEnumerable filtered = null; @@ -245,7 +231,7 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum } else { - var topPriority = replaceDefinitions.OrderByDescending(p => p.CustomPriorityOrder).FirstOrDefault().CustomPriorityOrder; + var topPriority = replaceDefinitions.MaxBy(p => p.CustomPriorityOrder)!.CustomPriorityOrder; filtered = replaceDefinitions.Where(p => p.CustomPriorityOrder == topPriority); } } @@ -257,17 +243,18 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum } else { - var topPriority = definitions.OrderByDescending(p => p.CustomPriorityOrder).FirstOrDefault().CustomPriorityOrder; + var topPriority = definitions.MaxBy(p => p.CustomPriorityOrder)!.CustomPriorityOrder; filtered = definitions.Where(p => p.CustomPriorityOrder == topPriority); } } + var uniqueDefinitions = filtered.GroupBy(p => p.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.File), StringComparer.Ordinal).Last()); if (uniqueDefinitions.Count() == 1) { var definition = uniqueDefinitions.FirstOrDefault(p => !p.IsFromGame); definition ??= uniqueDefinitions.FirstOrDefault(); result.Definition = definition; - result.FileName = definition.File; + result.FileName = definition!.File; } else if (uniqueDefinitions.Count() > 1) { @@ -276,6 +263,7 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum { definitions = uniqueDefinitions; } + var definition = modDefinitions.OrderBy(p => Path.GetFileNameWithoutExtension(p.File), StringComparer.Ordinal).Last(); result.Definition = definition; result.FileName = definition.File; @@ -288,47 +276,44 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum { validDefinitions = definitions.Where(d => !string.IsNullOrWhiteSpace(d.VirtualPath)).ToList(); } + if (validDefinitions.Count == 1) { result.Definition = validDefinitions.FirstOrDefault(); // If it's the only valid one assume load order is responsible result.PriorityType = DefinitionPriorityType.ModOrder; - result.FileName = validDefinitions.FirstOrDefault().File; + result.FileName = validDefinitions.FirstOrDefault()!.File; } else if (validDefinitions.Count > 1) { var definitionEvals = new List(); - bool isFios = false; + var isFios = false; - bool overrideSkipped = false; + var overrideSkipped = false; isFios = forceFios || provider.DefinitionUsesFIOSRules(validDefinitions.First()); foreach (var item in validDefinitions) { - var fileName = isFios ? item.AdditionalFileNames.OrderBy(p => Path.GetFileNameWithoutExtension(p), StringComparer.Ordinal).First() : item.AdditionalFileNames.OrderBy(p => Path.GetFileNameWithoutExtension(p), StringComparer.Ordinal).Last(); + var fileName = isFios + ? item.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).First() + : item.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).Last(); var hasOverrides = validDefinitions.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(d => d.Equals(item.ModName)) && - (isFios ? p.AdditionalFileNames.OrderBy(p => Path.GetFileNameWithoutExtension(p), StringComparer.Ordinal).First().Equals(fileName) : p.AdditionalFileNames.OrderBy(p => Path.GetFileNameWithoutExtension(p), StringComparer.Ordinal).Last().Equals(fileName))); + (isFios + ? p.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).First().Equals(fileName) + : p.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).Last().Equals(fileName))); if (hasOverrides) { overrideSkipped = true; continue; } - definitionEvals.Add(new DefinitionEval() - { - Definition = item, - FileName = fileName - }); - } - List uniqueDefinitions; - if (isFios) - { - uniqueDefinitions = definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).First()).ToList(); - } - else - { - uniqueDefinitions = definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).Last()).ToList(); + + definitionEvals.Add(new DefinitionEval { Definition = item, FileName = fileName }); } + var uniqueDefinitions = isFios + ? definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).First()).ToList() + : definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).Last()).ToList(); + // Filter out game definitions which might have the same filename var filteredGameDefinitions = false; var gameDefinitions = uniqueDefinitions.GroupBy(p => p.FileNameCI).Where(p => p.Any(a => a.Definition.IsFromGame) && p.Count() > 1).SelectMany(p => p.Where(w => w.Definition.IsFromGame)); @@ -340,11 +325,12 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum uniqueDefinitions.Remove(gameDef); } } + if (uniqueDefinitions.Count == 1 && (overrideSkipped || filteredGameDefinitions)) { var definition = definitionEvals.FirstOrDefault(p => !p.Definition.IsFromGame); definition ??= definitionEvals.FirstOrDefault(); - result.Definition = definition.Definition; + result.Definition = definition!.Definition; result.FileName = definition.FileName; if (overrideSkipped) { @@ -363,7 +349,7 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum if (uniqueDefinitions.Any(p => p.Definition.IsCustomPatch)) { var definition = uniqueDefinitions.FirstOrDefault(p => p.Definition.IsCustomPatch); - result.Definition = definition.Definition; + result.Definition = definition!.Definition; result.FileName = definition.FileName; result.PriorityType = DefinitionPriorityType.ModOrder; } @@ -398,13 +384,15 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum } } } + if (result.Definition == null) { var definition = definitions?.FirstOrDefault(p => !p.IsFromGame); if (definition == null && (definitions?.Any()).GetValueOrDefault()) { - definition = definitions.FirstOrDefault(); + definition = definitions!.FirstOrDefault(); } + result.Definition = definition; result.FileName = definition?.File; if (noProvider) @@ -412,6 +400,7 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum result.PriorityType = DefinitionPriorityType.NoProvider; } } + return result; } @@ -428,12 +417,14 @@ protected virtual string EvaluatePatchNamePath(IGame game, string patchName, str { modDirectoryRootPath = GetModDirectoryRootPath(game); } + modDirectoryRootPath = modDirectoryRootPath.StandardizeDirectorySeparator(); var patchNamePath = GetPatchModDirectory(game, patchName).StandardizeDirectorySeparator(); - if (Path.GetDirectoryName(patchNamePath).Equals(modDirectoryRootPath)) + if (Path.GetDirectoryName(patchNamePath)!.Equals(modDirectoryRootPath)) { patchNamePath = patchName; } + return patchNamePath; } @@ -458,14 +449,9 @@ protected virtual string GenerateCollectionPatchName(string collectionName) protected virtual IMod GeneratePatchModDescriptor(IEnumerable allMods, IGame game, string patchName) { var mod = DIResolver.Get(); - if (game.ModDescriptorType == ModDescriptorType.DescriptorMod) - { - mod.DescriptorFile = $"{Shared.Constants.ModDirectory}/{patchName}{Shared.Constants.ModExtension}"; - } - else - { - mod.DescriptorFile = $"{Shared.Constants.JsonModDirectory}/{patchName}{Shared.Constants.JsonExtension}"; - } + mod.DescriptorFile = game.ModDescriptorType == ModDescriptorType.DescriptorMod + ? $"{Shared.Constants.ModDirectory}/{patchName}{Shared.Constants.ModExtension}" + : $"{Shared.Constants.JsonModDirectory}/{patchName}{Shared.Constants.JsonExtension}"; mod.FileName = GetPatchModDirectory(game, patchName).Replace("\\", "/"); mod.Name = patchName; mod.Source = ModSource.Local; @@ -476,9 +462,10 @@ protected virtual IMod GeneratePatchModDescriptor(IEnumerable allMods, IGa } else { - mod.Version = allMods.OrderByDescending(p => p.VersionData).FirstOrDefault() != null ? allMods.OrderByDescending(p => p.VersionData).FirstOrDefault().Version : string.Empty; + mod.Version = allMods.MaxBy(p => p.VersionData) != null ? allMods.MaxBy(p => p.VersionData)!.Version : string.Empty; } - mod.Tags = new List() { "Fixes" }; + + mod.Tags = ["Fixes"]; mod.IsValid = true; mod.FullPath = mod.FileName.StandardizeDirectorySeparator(); return mod; @@ -493,14 +480,16 @@ protected virtual IEnumerable GetAllModCollectionsInternal() var game = GameService.GetSelected(); if (game == null) { - return new List(); + return []; } + var collections = StorageProvider.GetModCollections().Where(s => s.Game.Equals(game.Type)); if (collections.Any()) { return collections.OrderBy(p => p.Name); } - return new List(); + + return []; } /// @@ -516,21 +505,13 @@ protected virtual IEnumerable GetCollectionMods(IEnumerable mods = n var collections = GetAllModCollectionsInternal(); if (collections?.Count() > 0) { - IModCollection collection; - if (!string.IsNullOrWhiteSpace(collectionName)) - { - collection = collections.FirstOrDefault(p => p.Name.Equals(collectionName, StringComparison.OrdinalIgnoreCase)); - } - else - { - collection = collections.FirstOrDefault(p => p.IsSelected); - } + var collection = !string.IsNullOrWhiteSpace(collectionName) ? collections.FirstOrDefault(p => p.Name.Equals(collectionName, StringComparison.OrdinalIgnoreCase)) : collections.FirstOrDefault(p => p.IsSelected); if (collection != null) { var colMods = collection.Mods.ToList(); var colModPaths = collection.ModPaths.ToList(); - for (int i = 0; i < colMods.Count; i++) + for (var i = 0; i < colMods.Count; i++) { var item = colMods[i]; var mod = mods.FirstOrDefault(p => p.DescriptorFile.Equals(item, StringComparison.OrdinalIgnoreCase)); @@ -539,6 +520,7 @@ protected virtual IEnumerable GetCollectionMods(IEnumerable mods = n item = colModPaths[i]; mod = mods.FirstOrDefault(p => p.FullPath.Equals(item, StringComparison.OrdinalIgnoreCase)); } + if (mod != null) { collectionMods.Add(mod); @@ -546,6 +528,7 @@ protected virtual IEnumerable GetCollectionMods(IEnumerable mods = n } } } + return collectionMods; } @@ -566,6 +549,7 @@ protected virtual IEnumerable GetInstalledModsInternal(string game, bool i /// The game. /// if set to true [ignore patch mods]. /// IEnumerable<IMod>. + /// nameof(game) /// game protected virtual IEnumerable GetInstalledModsInternal(IGame game, bool ignorePatchMods) { @@ -573,7 +557,8 @@ protected virtual IEnumerable GetInstalledModsInternal(IGame game, bool ig { throw new ArgumentNullException(nameof(game)); } - var mods = Cache.Get>(new CacheGetParameters() { Region = ModsCacheRegion, Prefix = game.Type, Key = GetModsCacheKey(ignorePatchMods) }); + + var mods = Cache.Get>(new CacheGetParameters { Region = ModsCacheRegion, Prefix = game.Type, Key = GetModsCacheKey(ignorePatchMods) }); if (mods != null) { return mods; @@ -591,22 +576,17 @@ protected virtual IEnumerable GetInstalledModsInternal(IGame game, bool ig { return; } + mod.Name = string.IsNullOrWhiteSpace(mod.Name) ? string.Empty : mod.Name; mod.Version = string.IsNullOrWhiteSpace(mod.Version) ? string.Empty : mod.Version; mod.IsLocked = installedMod.IsReadOnly; - if (game.ModDescriptorType == ModDescriptorType.DescriptorMod) - { - mod.DescriptorFile = $"{Shared.Constants.ModDirectory}/{installedMod.FileName}"; - } - else - { - mod.DescriptorFile = $"{Shared.Constants.JsonModDirectory}/{installedMod.FileName}"; - } + mod.DescriptorFile = game.ModDescriptorType == ModDescriptorType.DescriptorMod ? $"{Shared.Constants.ModDirectory}/{installedMod.FileName}" : $"{Shared.Constants.JsonModDirectory}/{installedMod.FileName}"; mod.Source = GetModSource(installedMod); if (mod.Source == ModSource.Paradox) { mod.RemoteId = GetPdxModId(installedMod.FileName); } + if (string.IsNullOrWhiteSpace(mod.FileName)) { mod.FileName = string.Empty; @@ -621,7 +601,7 @@ protected virtual IEnumerable GetInstalledModsInternal(IGame game, bool ig else { // Check user directory and workshop directory. - var userDirectoryMod = new List() { Path.Combine(game.CustomModDirectory, mod.FileName), Path.Combine(game.UserDirectory, mod.FileName) }.GroupBy(p => p).Select(p => p.First()); + var userDirectoryMod = new List { Path.Combine(game.CustomModDirectory, mod.FileName), Path.Combine(game.UserDirectory, mod.FileName) }.GroupBy(p => p).Select(p => p.First()); var workshopDirectoryMod = game.WorkshopDirectory.Select(p => Path.Combine(p, mod.FileName)).GroupBy(p => p).Select(p => p.First()); if (userDirectoryMod.Any(p => File.Exists(p) || Directory.Exists(p))) { @@ -640,7 +620,8 @@ protected virtual IEnumerable GetInstalledModsInternal(IGame game, bool ig result.Add(mod); }); } - Cache.Set(new CacheAddParameters>() { Region = ModsCacheRegion, Prefix = game.Type, Key = GetModsCacheKey(ignorePatchMods), Value = result.ToList() }); + + Cache.Set(new CacheAddParameters> { Region = ModsCacheRegion, Prefix = game.Type, Key = GetModsCacheKey(ignorePatchMods), Value = [.. result] }); return result; } } @@ -680,6 +661,7 @@ protected virtual ModSource GetModSource(IFileInfo fileInfo) { return ModSource.Steam; } + return ModSource.Local; } @@ -693,14 +675,12 @@ protected virtual string GetPatchModDirectory(IGame game, string patchOrMergeNam { var path = Path.Combine(game.UserDirectory, Shared.Constants.ModDirectory, patchOrMergeName); path = path.StandardizeDirectorySeparator(); - var parameters = new ModWriterParameters() - { - Path = path - }; + var parameters = new ModWriterParameters { Path = path }; if (!ModWriter.ModDirectoryExists(parameters) && !string.IsNullOrWhiteSpace(game.CustomModDirectory)) { path = Path.Combine(game.CustomModDirectory, patchOrMergeName).StandardizeDirectorySeparator(); } + return path; } @@ -740,6 +720,7 @@ protected virtual bool IsPatchModInternal(IMod mod) { return IsPatchModInternal(mod.Name); } + return false; } @@ -754,6 +735,7 @@ protected virtual bool IsPatchModInternal(string modName) { return modName.StartsWith(PatchCollectionName); } + return false; } @@ -765,9 +747,9 @@ protected virtual bool IsPatchModInternal(string modName) protected virtual bool IsValidDefinitionType(IDefinition definition) { return definition != null && definition.ValueType != ValueType.Variable && - definition.ValueType != ValueType.Namespace && - definition.ValueType != ValueType.Invalid && - definition.ValueType != ValueType.EmptyFile; + definition.ValueType != ValueType.Namespace && + definition.ValueType != ValueType.Invalid && + definition.ValueType != ValueType.EmptyFile; } /// @@ -781,6 +763,7 @@ static string cleanCodeForVarCheck(string code) code = code.ReplaceTabs().ReplaceNewLine(); return Regex.Replace(code, CleanVariablesPattern, " "); } + static bool evalNamespace(string code, string id) { var split = code.Split(Parser.Common.Constants.Scripts.EqualsOperator, StringSplitOptions.RemoveEmptyEntries); @@ -788,8 +771,10 @@ static bool evalNamespace(string code, string id) { return id.Trim().StartsWith(split[1].Trim(), StringComparison.OrdinalIgnoreCase); } + return true; } + static void appendLine(StringBuilder sb, IEnumerable lines) { if (lines != null && lines.Any()) @@ -797,11 +782,11 @@ static void appendLine(StringBuilder sb, IEnumerable lines) sb.AppendLine(string.Join(Environment.NewLine, lines)); } } + static string mergeCode(string codeTag, string separator, IEnumerable lines) { if (Shared.Constants.CodeSeparators.ClosingSeparators.Map.TryGetValue(separator, out var value)) { - var closingTag = value; var sb = new StringBuilder(); sb.AppendLine($"{codeTag} = {separator}"); foreach (var item in lines) @@ -812,7 +797,8 @@ static string mergeCode(string codeTag, string separator, IEnumerable li sb.AppendLine($"{new string(' ', 4)}{split}"); } } - sb.Append(closingTag); + + sb.Append(value); return sb.ToString(); } else @@ -827,13 +813,14 @@ static string mergeCode(string codeTag, string separator, IEnumerable li sb.AppendLine($"{new string(' ', 4)}{split}"); } } + return sb.ToString(); } } if (definitions != null && definitions.Any()) { - var otherDefinitions = definitions.Where(p => IsValidDefinitionType(p)); + var otherDefinitions = definitions.Where(IsValidDefinitionType); var variableDefinitions = definitions.Where(p => !IsValidDefinitionType(p)); if (variableDefinitions.Any()) { @@ -847,17 +834,18 @@ static string mergeCode(string codeTag, string separator, IEnumerable li { definition.Variables = allVars.ToList(); } + if (string.IsNullOrWhiteSpace(definition.CodeTag)) { - StringBuilder sb = new StringBuilder(); - appendLine(sb, namespaces.GroupBy(p => p.Code).Select(p => p.FirstOrDefault().Code)); - appendLine(sb, variables.GroupBy(p => p.Code).Select(p => p.FirstOrDefault().Code)); - appendLine(sb, new List { definition.Code }); + var sb = new StringBuilder(); + appendLine(sb, namespaces.GroupBy(p => p.Code).Select(p => p.FirstOrDefault()!.Code)); + appendLine(sb, variables.GroupBy(p => p.Code).Select(p => p.FirstOrDefault()!.Code)); + appendLine(sb, [definition.Code]); definition.Code = sb.ToString(); } else { - definition.Code = mergeCode(definition.CodeTag, definition.CodeSeparator, namespaces.Select(p => p.OriginalCode).Concat(variables.Select(p => p.OriginalCode)).Concat(new List() { definition.OriginalCode })); + definition.Code = mergeCode(definition.CodeTag, definition.CodeSeparator, namespaces.Select(p => p.OriginalCode).Concat(variables.Select(p => p.OriginalCode)).Concat([definition.OriginalCode])); } } } @@ -868,7 +856,7 @@ static string mergeCode(string codeTag, string separator, IEnumerable li /// populate mod files internal as an asynchronous operation. /// /// The mods. - /// true if XXXX, false otherwise. + /// true if populated, false otherwise. protected virtual async Task PopulateModFilesInternalAsync(IEnumerable mods) { var logger = DIResolver.Get(); @@ -879,7 +867,7 @@ protected virtual async Task PopulateModFilesInternalAsync(IEnumerable(); + mod.Files = []; } else { @@ -889,7 +877,8 @@ protected virtual async Task PopulateModFilesInternalAsync(IEnumerable @@ -898,14 +887,14 @@ protected virtual async Task PopulateModFilesInternalAsync(IEnumerable(); + localMod.Files = []; } else if (localMod.Files == null || !localMod.Files.Any()) { try { var files = Reader.GetFiles(localMod.FullPath); - localMod.Files = files ?? new List(); + localMod.Files = files ?? []; } catch (Exception ex) { @@ -924,8 +913,10 @@ protected virtual async Task PopulateModFilesInternalAsync(IEnumerable PopulateModFilesInternalAsync(IEnumerableIEnumerable<IDefinition>. protected virtual IEnumerable PopulateModPath(IDefinition definition, IEnumerable collectionMods) { - return PopulateModPath(new List() { definition }, collectionMods); + return PopulateModPath([definition], collectionMods); } /// @@ -958,14 +949,15 @@ protected virtual IEnumerable PopulateModPath(IEnumerable p.Name.Equals(item.ModName)).FullPath; + item.ModPath = collectionMods.FirstOrDefault(p => p.Name.Equals(item.ModName))!.FullPath; } } } + return definitions; } diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index 2c26aec5..5300b7c7 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -908,7 +908,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE await messageBus.PublishAsync(new ModDefinitionLoadEvent(0)); var gameFolders = game.GameFolders.ToList(); - if (mode == PatchStateMode.DefaultWithoutLocalization || mode == PatchStateMode.AdvancedWithoutLocalization || mode == PatchStateMode.ReadOnlyWithoutLocalization) + if (mode is PatchStateMode.DefaultWithoutLocalization or PatchStateMode.AdvancedWithoutLocalization or PatchStateMode.ReadOnlyWithoutLocalization) { gameFolders = gameFolders.Where(p => !p.StartsWith(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase)).ToList(); } @@ -1878,7 +1878,7 @@ public virtual string ResolveFullDefinitionPath(IDefinition definition) if (definition.IsFromGame) { - return Path.Combine(pathResolver.GetPath(game), definition.File); + return Path.Combine(PathResolver.GetPath(game), definition.File); } else { @@ -2147,109 +2147,145 @@ async Task existsInLastFile(IDefinition definition) processed.Add(conflict); } - if (allConflicts.Count() > 1) + switch (allConflicts.Count()) { - if (!allConflicts.All(p => p.DefinitionSHA.Equals(def.DefinitionSHA))) + case > 1: { - var validConflicts = new HashSet(); - foreach (var conflict in allConflicts) + if (!allConflicts.All(p => p.DefinitionSHA.Equals(def.DefinitionSHA))) { - if (conflicts.Contains(conflict) || validConflicts.Contains(conflict)) + var validConflicts = new HashSet(); + foreach (var conflict in allConflicts) { - continue; - } + if (conflicts.Contains(conflict) || validConflicts.Contains(conflict)) + { + continue; + } - var hasOverrides = allConflicts.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(p => p.Equals(conflict.ModName))); - if (hasOverrides && (patchStateMode == PatchStateMode.Default || patchStateMode == PatchStateMode.DefaultWithoutLocalization)) - { - var existing = allConflicts.Where(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(p => p.Equals(conflict.ModName))); - if (existing.Any()) + var hasOverrides = allConflicts.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(p => p.Equals(conflict.ModName))); + if (hasOverrides && patchStateMode is PatchStateMode.Default or PatchStateMode.DefaultWithoutLocalization) { - var fileNames = conflict.AdditionalFileNames; - foreach (var item in existing.Where(p => p != null)) + var existing = allConflicts.Where(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(p => p.Equals(conflict.ModName))); + if (existing.Any()) { - foreach (var fileName in item.AdditionalFileNames) + var fileNames = conflict.AdditionalFileNames; + foreach (var item in existing.Where(p => p != null)) { - fileNames.Add(fileName); + foreach (var fileName in item.AdditionalFileNames) + { + fileNames.Add(fileName); + } } + + conflict.AdditionalFileNames = fileNames; } - conflict.AdditionalFileNames = fileNames; + continue; } - continue; + validConflicts.Add(conflict); } - validConflicts.Add(conflict); - } - - var validConflictsGroup = validConflicts.GroupBy(p => p.DefinitionSHA); - if (validConflictsGroup.Count() > 1) - { - var filteredConflicts = validConflictsGroup.Select(p => EvalDefinitionPriority(p.OrderBy(m => modOrder.IndexOf(m.ModName))).Definition); - foreach (var item in filteredConflicts) + var validConflictsGroup = validConflicts.GroupBy(p => p.DefinitionSHA); + if (validConflictsGroup.Count() > 1) { - if (!conflicts.Contains(item) && (IsValidDefinitionType(item) || (anyWholeTextFile && item.ValueType == ValueType.EmptyFile))) + var filteredConflicts = validConflictsGroup.Select(p => EvalDefinitionPriority(p.OrderBy(m => modOrder.IndexOf(m.ModName))).Definition); + foreach (var item in filteredConflicts) { - if (!item.IsFromGame) + if (!conflicts.Contains(item) && (IsValidDefinitionType(item) || (anyWholeTextFile && item.ValueType == ValueType.EmptyFile))) { - if (modShaConflictCache.TryGetValue(item.TypeAndId, out var value) && value.Contains(item.DefinitionSHA)) + if (!item.IsFromGame) { - continue; + if (modShaConflictCache.TryGetValue(item.TypeAndId, out var value) && value.Contains(item.DefinitionSHA)) + { + continue; + } } - } - var shaMatches = validConflictsGroup.FirstOrDefault(p => p.Key == item.DefinitionSHA); - if (shaMatches.Count() > 1) - { - var fileNames = item.AdditionalFileNames; - foreach (var shaMatch in shaMatches.Where(p => p != item)) + var shaMatches = validConflictsGroup.FirstOrDefault(p => p.Key == item.DefinitionSHA); + if (shaMatches.Count() > 1) { - foreach (var fileName in shaMatch.AdditionalFileNames) + var fileNames = item.AdditionalFileNames; + foreach (var shaMatch in shaMatches.Where(p => p != item)) { - fileNames.Add(fileName); + foreach (var fileName in shaMatch.AdditionalFileNames) + { + fileNames.Add(fileName); + } + } + + item.AdditionalFileNames = fileNames; + if (item.IsPlaceholder && shaMatches.Any(p => !p.IsPlaceholder)) + { + // Uncheck placeholder mark as there's a duplicate which is not marked as placeholder + item.IsPlaceholder = false; } } - item.AdditionalFileNames = fileNames; - if (item.IsPlaceholder && shaMatches.Any(p => !p.IsPlaceholder)) + item.ExistsInLastFile = await existsInLastFile(item); + conflicts.Add(item); + if (!item.IsFromGame) { - // Uncheck placeholder mark as there's a duplicate which is not marked as placeholder - item.IsPlaceholder = false; + if (modShaConflictCache.TryGetValue(item.TypeAndId, out var value)) + { + value.Add(item.DefinitionSHA); + } + else + { + var sha = new List { item.DefinitionSHA }; + modShaConflictCache[item.TypeAndId] = sha; + } } } + } + } + } - item.ExistsInLastFile = await existsInLastFile(item); - conflicts.Add(item); - if (!item.IsFromGame) + break; + } + case 1 when allConflicts.FirstOrDefault().ValueType == ValueType.Binary: + fileConflictCache.TryAdd(def.FileCI, false); + break; + case 1 when fileConflictCache.TryGetValue(def.FileCI, out var result): + { + if (result) + { + if (!conflicts.Contains(def) && (IsValidDefinitionType(def) || (anyWholeTextFile && def.ValueType == ValueType.EmptyFile))) + { + def.ExistsInLastFile = await existsInLastFile(def); + if (!def.ExistsInLastFile) + { + conflicts.Add(def); + if (!def.IsFromGame) { - if (modShaConflictCache.TryGetValue(item.TypeAndId, out var value)) + if (modShaConflictCache.TryGetValue(def.TypeAndId, out var value)) { - value.Add(item.DefinitionSHA); + value.Add(def.DefinitionSHA); } else { - var sha = new List { item.DefinitionSHA }; - modShaConflictCache[item.TypeAndId] = sha; + var sha = new List { def.DefinitionSHA }; + modShaConflictCache[def.TypeAndId] = sha; } } } } } + + break; } - } - else if (allConflicts.Count() == 1) - { - if (allConflicts.FirstOrDefault().ValueType == ValueType.Binary) - { - fileConflictCache.TryAdd(def.FileCI, false); - } - else + case 1: { - if (fileConflictCache.TryGetValue(def.FileCI, out var result)) + var fileDefs = await indexedDefinitions.GetByFileAsync(def.FileCI); + if (fileDefs.GroupBy(p => p.ModName).Count() > 1) { - if (result) + var hasOverrides = !def.IsCustomPatch && def.Dependencies != null && def.Dependencies.Any(p => fileDefs.Any(s => s.ModName.Equals(p))); + if (hasOverrides && patchStateMode is PatchStateMode.Default or PatchStateMode.DefaultWithoutLocalization) { + fileConflictCache.TryAdd(def.FileCI, false); + } + else + { + fileConflictCache.TryAdd(def.FileCI, true); if (!conflicts.Contains(def) && (IsValidDefinitionType(def) || (anyWholeTextFile && def.ValueType == ValueType.EmptyFile))) { def.ExistsInLastFile = await existsInLastFile(def); @@ -2274,44 +2310,10 @@ async Task existsInLastFile(IDefinition definition) } else { - var fileDefs = await indexedDefinitions.GetByFileAsync(def.FileCI); - if (fileDefs.GroupBy(p => p.ModName).Count() > 1) - { - var hasOverrides = !def.IsCustomPatch && def.Dependencies != null && def.Dependencies.Any(p => fileDefs.Any(s => s.ModName.Equals(p))); - if (hasOverrides && (patchStateMode == PatchStateMode.Default || patchStateMode == PatchStateMode.DefaultWithoutLocalization)) - { - fileConflictCache.TryAdd(def.FileCI, false); - } - else - { - fileConflictCache.TryAdd(def.FileCI, true); - if (!conflicts.Contains(def) && (IsValidDefinitionType(def) || (anyWholeTextFile && def.ValueType == ValueType.EmptyFile))) - { - def.ExistsInLastFile = await existsInLastFile(def); - if (!def.ExistsInLastFile) - { - conflicts.Add(def); - if (!def.IsFromGame) - { - if (modShaConflictCache.TryGetValue(def.TypeAndId, out var value)) - { - value.Add(def.DefinitionSHA); - } - else - { - var sha = new List { def.DefinitionSHA }; - modShaConflictCache[def.TypeAndId] = sha; - } - } - } - } - } - } - else - { - fileConflictCache.TryAdd(def.FileCI, false); - } + fileConflictCache.TryAdd(def.FileCI, false); } + + break; } } } @@ -2969,25 +2971,23 @@ protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable if (fileDefs.Any()) { // Validate and see whether we need to check encoding - if (fileDefs.All(p => p.ValueType != ValueType.Invalid)) - { - if (fileDefs.All(p => p.ValueType != ValueType.Binary) && definitionInfoProvider != null && !definitionInfoProvider.IsValidEncoding(Path.GetDirectoryName(fileInfo.FileName), fileInfo.Encoding)) - { - var definition = DIResolver.Get(); - definition.ErrorMessage = "File has invalid encoding, please use UTF-8-BOM Encoding."; - definition.Id = Path.GetFileName(fileInfo.FileName)!.ToLowerInvariant(); - definition.ValueType = ValueType.Invalid; - definition.OriginalCode = definition.Code = string.Join(Environment.NewLine, fileInfo.Content ?? []); - definition.ContentSHA = fileInfo.ContentSHA; - definition.Dependencies = modObject.Dependencies; - definition.ModName = modObject.Name; - definition.OriginalModName = modObject.Name; - definition.OriginalFileName = fileInfo.FileName; - definition.File = fileInfo.FileName; - definition.Type = fileInfo.FileName.FormatDefinitionType(); - definitions.Add(definition); - continue; - } + if (fileDefs.All(p => p.ValueType != ValueType.Invalid && p.ValueType != ValueType.Binary) && definitionInfoProvider != null && + !definitionInfoProvider.IsValidEncoding(Path.GetDirectoryName(fileInfo.FileName), fileInfo.Encoding)) + { + var definition = DIResolver.Get(); + definition.ErrorMessage = "File has invalid encoding, please use UTF-8-BOM Encoding."; + definition.Id = Path.GetFileName(fileInfo.FileName)!.ToLowerInvariant(); + definition.ValueType = ValueType.Invalid; + definition.OriginalCode = definition.Code = string.Join(Environment.NewLine, fileInfo.Content ?? []); + definition.ContentSHA = fileInfo.ContentSHA; + definition.Dependencies = modObject.Dependencies; + definition.ModName = modObject.Name; + definition.OriginalModName = modObject.Name; + definition.OriginalFileName = fileInfo.FileName; + definition.File = fileInfo.FileName; + definition.Type = fileInfo.FileName.FormatDefinitionType(); + definitions.Add(definition); + continue; } MergeDefinitions(fileDefs); From 6175746cf5800c432fd9214ae4e6eec09deab44d Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 22:23:02 +0100 Subject: [PATCH 065/101] Convert to switches --- .../ModBaseService.cs | 184 ++++++++++-------- .../Controls/MergeViewerControlViewModel.cs | 2 +- 2 files changed, 99 insertions(+), 87 deletions(-) diff --git a/src/IronyModManager.Services/ModBaseService.cs b/src/IronyModManager.Services/ModBaseService.cs index 37c6c4cd..e1af1f68 100644 --- a/src/IronyModManager.Services/ModBaseService.cs +++ b/src/IronyModManager.Services/ModBaseService.cs @@ -249,24 +249,29 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum } var uniqueDefinitions = filtered.GroupBy(p => p.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.File), StringComparer.Ordinal).Last()); - if (uniqueDefinitions.Count() == 1) + switch (uniqueDefinitions.Count()) { - var definition = uniqueDefinitions.FirstOrDefault(p => !p.IsFromGame); - definition ??= uniqueDefinitions.FirstOrDefault(); - result.Definition = definition; - result.FileName = definition!.File; - } - else if (uniqueDefinitions.Count() > 1) - { - var modDefinitions = uniqueDefinitions.Where(p => !p.IsFromGame); - if (!modDefinitions.Any()) + case 1: { - definitions = uniqueDefinitions; + var definition = uniqueDefinitions.FirstOrDefault(p => !p.IsFromGame); + definition ??= uniqueDefinitions.FirstOrDefault(); + result.Definition = definition; + result.FileName = definition!.File; + break; } + case > 1: + { + var modDefinitions = uniqueDefinitions.Where(p => !p.IsFromGame); + if (!modDefinitions.Any()) + { + definitions = uniqueDefinitions; + } - var definition = modDefinitions.OrderBy(p => Path.GetFileNameWithoutExtension(p.File), StringComparer.Ordinal).Last(); - result.Definition = definition; - result.FileName = definition.File; + var definition = modDefinitions.OrderBy(p => Path.GetFileNameWithoutExtension(p.File), StringComparer.Ordinal).Last(); + result.Definition = definition; + result.FileName = definition.File; + break; + } } } else @@ -277,108 +282,115 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum validDefinitions = definitions.Where(d => !string.IsNullOrWhiteSpace(d.VirtualPath)).ToList(); } - if (validDefinitions.Count == 1) - { - result.Definition = validDefinitions.FirstOrDefault(); - - // If it's the only valid one assume load order is responsible - result.PriorityType = DefinitionPriorityType.ModOrder; - result.FileName = validDefinitions.FirstOrDefault()!.File; - } - else if (validDefinitions.Count > 1) + switch (validDefinitions.Count) { - var definitionEvals = new List(); - var isFios = false; - - var overrideSkipped = false; - isFios = forceFios || provider.DefinitionUsesFIOSRules(validDefinitions.First()); - foreach (var item in validDefinitions) + case 1: + result.Definition = validDefinitions.FirstOrDefault(); + + // If it's the only valid one assume load order is responsible + result.PriorityType = DefinitionPriorityType.ModOrder; + result.FileName = validDefinitions.FirstOrDefault()!.File; + break; + case > 1: { - var fileName = isFios - ? item.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).First() - : item.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).Last(); - var hasOverrides = validDefinitions.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(d => d.Equals(item.ModName)) && - (isFios - ? p.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).First().Equals(fileName) - : p.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).Last().Equals(fileName))); - if (hasOverrides) + var definitionEvals = new List(); + var isFios = false; + + var overrideSkipped = false; + isFios = forceFios || provider.DefinitionUsesFIOSRules(validDefinitions.First()); + foreach (var item in validDefinitions) { - overrideSkipped = true; - continue; - } + var fileName = isFios + ? item.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).First() + : item.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).Last(); + var hasOverrides = validDefinitions.Any(p => !p.IsCustomPatch && p.Dependencies != null && p.Dependencies.Any(d => d.Equals(item.ModName)) && + (isFios + ? p.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).First().Equals(fileName) + : p.AdditionalFileNames.OrderBy(Path.GetFileNameWithoutExtension, StringComparer.Ordinal).Last().Equals(fileName))); + if (hasOverrides) + { + overrideSkipped = true; + continue; + } - definitionEvals.Add(new DefinitionEval { Definition = item, FileName = fileName }); - } + definitionEvals.Add(new DefinitionEval { Definition = item, FileName = fileName }); + } - var uniqueDefinitions = isFios - ? definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).First()).ToList() - : definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).Last()).ToList(); + var uniqueDefinitions = isFios + ? definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).First()).ToList() + : definitionEvals.GroupBy(p => p.Definition.ModName).Select(p => p.OrderBy(f => Path.GetFileNameWithoutExtension(f.FileName), StringComparer.Ordinal).Last()).ToList(); - // Filter out game definitions which might have the same filename - var filteredGameDefinitions = false; - var gameDefinitions = uniqueDefinitions.GroupBy(p => p.FileNameCI).Where(p => p.Any(a => a.Definition.IsFromGame) && p.Count() > 1).SelectMany(p => p.Where(w => w.Definition.IsFromGame)); - if (gameDefinitions.Any()) - { - filteredGameDefinitions = true; - foreach (var gameDef in gameDefinitions) + // Filter out game definitions which might have the same filename + var filteredGameDefinitions = false; + var gameDefinitions = uniqueDefinitions.GroupBy(p => p.FileNameCI).Where(p => p.Any(a => a.Definition.IsFromGame) && p.Count() > 1).SelectMany(p => p.Where(w => w.Definition.IsFromGame)); + if (gameDefinitions.Any()) { - uniqueDefinitions.Remove(gameDef); + filteredGameDefinitions = true; + foreach (var gameDef in gameDefinitions) + { + uniqueDefinitions.Remove(gameDef); + } } - } - if (uniqueDefinitions.Count == 1 && (overrideSkipped || filteredGameDefinitions)) - { - var definition = definitionEvals.FirstOrDefault(p => !p.Definition.IsFromGame); - definition ??= definitionEvals.FirstOrDefault(); - result.Definition = definition!.Definition; - result.FileName = definition.FileName; - if (overrideSkipped) - { - result.PriorityType = DefinitionPriorityType.ModOverride; - } - else if (filteredGameDefinitions) - { - result.PriorityType = DefinitionPriorityType.ModOrder; - } - } - else if (uniqueDefinitions.Count > 1) - { - // Has same filenames? - if (uniqueDefinitions.GroupBy(p => p.FileNameCI).Count() == 1) + switch (uniqueDefinitions.Count) { - if (uniqueDefinitions.Any(p => p.Definition.IsCustomPatch)) + case 1 when (overrideSkipped || filteredGameDefinitions): { - var definition = uniqueDefinitions.FirstOrDefault(p => p.Definition.IsCustomPatch); + var definition = definitionEvals.FirstOrDefault(p => !p.Definition.IsFromGame); + definition ??= definitionEvals.FirstOrDefault(); result.Definition = definition!.Definition; result.FileName = definition.FileName; - result.PriorityType = DefinitionPriorityType.ModOrder; + if (overrideSkipped) + { + result.PriorityType = DefinitionPriorityType.ModOverride; + } + else if (filteredGameDefinitions) + { + result.PriorityType = DefinitionPriorityType.ModOrder; + } + + break; } - else + // Has same filenames? + case > 1 when uniqueDefinitions.GroupBy(p => p.FileNameCI).Count() == 1: { - var definition = uniqueDefinitions.Last(); - result.Definition = definition.Definition; - result.FileName = definition.FileName; - result.PriorityType = DefinitionPriorityType.ModOrder; + if (uniqueDefinitions.Any(p => p.Definition.IsCustomPatch)) + { + var definition = uniqueDefinitions.FirstOrDefault(p => p.Definition.IsCustomPatch); + result.Definition = definition!.Definition; + result.FileName = definition.FileName; + result.PriorityType = DefinitionPriorityType.ModOrder; + } + else + { + var definition = uniqueDefinitions.Last(); + result.Definition = definition.Definition; + result.FileName = definition.FileName; + result.PriorityType = DefinitionPriorityType.ModOrder; + } + + break; } - } - else - { // Using FIOS or LIOS? - if (isFios) + case > 1 when isFios: { var definition = uniqueDefinitions.OrderBy(p => Path.GetFileNameWithoutExtension(p.FileName), StringComparer.Ordinal).First(); result.Definition = definition.Definition; result.FileName = definition.FileName; result.PriorityType = DefinitionPriorityType.FIOS; + break; } - else + case > 1: { var definition = uniqueDefinitions.OrderBy(p => Path.GetFileNameWithoutExtension(p.FileName), StringComparer.Ordinal).Last(); result.Definition = definition.Definition; result.FileName = definition.FileName; result.PriorityType = DefinitionPriorityType.LIOS; + break; } } + + break; } } } diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index 419c0b14..68010762 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -1302,7 +1302,7 @@ protected override void OnActivated(CompositeDisposable disposables) ExitEditMode(); }, okEnabled).DisposeWith(disposables); - CancelCommand = ReactiveCommand.Create(() => { ExitEditMode(); }).DisposeWith(disposables); + CancelCommand = ReactiveCommand.Create(ExitEditMode).DisposeWith(disposables); EditThisCommand = ReactiveCommand.Create((bool leftSide) => { SetEditThis(leftSide); }).DisposeWith(disposables); From 5318242a7120addc8aae0cea6578ce0401ec3a90 Mon Sep 17 00:00:00 2001 From: bcssov Date: Fri, 23 Feb 2024 22:43:35 +0100 Subject: [PATCH 066/101] Fix find conflict navigation --- src/IronyModManager/Controls/TextEditor.cs | 8 ++++++++ .../Controls/MergeViewerControlView.xaml.cs | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/IronyModManager/Controls/TextEditor.cs b/src/IronyModManager/Controls/TextEditor.cs index 3a95931d..d4db9a81 100644 --- a/src/IronyModManager/Controls/TextEditor.cs +++ b/src/IronyModManager/Controls/TextEditor.cs @@ -124,9 +124,14 @@ public TextEditor() : base(new TextArea()) if (ScrollViewer != null && document != null) { if (line < 1) + { line = 1; + } + if (line > document.LineCount) + { line = document.LineCount; + } ILogicalScrollable scrollInfo = textView; if (!scrollInfo.CanHorizontallyScroll) @@ -138,7 +143,10 @@ public TextEditor() : base(new TextArea()) { var prevLine = vl.FirstDocumentLine.PreviousLine; if (prevLine == null) + { break; + } + vl = textView.GetOrConstructVisualLine(prevLine); remainingHeight -= vl.Height; } diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 7a66843b..da7a0f6f 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -644,9 +644,24 @@ void setEditMode() { var col = leftDiff ? ViewModel!.LeftSideSelected : ViewModel!.RightSideSelected; var sourceCol = leftDiff ? ViewModel.LeftDiff : ViewModel.RightDiff; + var idx = line.FirstDocumentLine.LineNumber - 1; + if (idx >= sourceCol.Count) + { + return; + } + if (col.Count == 0) { - col.Add(sourceCol[line.FirstDocumentLine.LineNumber - 1]); + col.Add(sourceCol[idx]); + } + else + { + var item = sourceCol[idx]; + if (!col.Contains(item)) + { + col.Clear(); + col.Add(sourceCol[idx]); + } } } }; From 4fd3d48fb4e161fb7f70c674e72fb525e5a0faca Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 24 Feb 2024 05:49:00 +0100 Subject: [PATCH 067/101] Make the new viewer default as per vote --- src/IronyModManager.Models/AppState.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IronyModManager.Models/AppState.cs b/src/IronyModManager.Models/AppState.cs index 32002ae5..11f61cb8 100644 --- a/src/IronyModManager.Models/AppState.cs +++ b/src/IronyModManager.Models/AppState.cs @@ -4,7 +4,7 @@ // Created : 03-03-2020 // // Last Modified By : Mario -// Last Modified On : 02-20-2024 +// Last Modified On : 02-24-2024 // *********************************************************************** // // Mario @@ -88,7 +88,7 @@ public class AppState : BaseModel, IAppState /// Gets or sets a value indicating whether the use new diff viewer. /// /// true if use new diff viewer; otherwise, false. - public bool UseNewDiffViewer { get; set; } + public bool UseNewDiffViewer { get; set; } = true; #endregion Properties } From d72af38f2046b23612014ce67421cdc845081349 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 24 Feb 2024 06:29:51 +0100 Subject: [PATCH 068/101] Ensure redraw is requested while performing some context actions --- .../Controls/MergeViewerControlViewModel.cs | 6 +- .../Controls/MergeViewerControlView.xaml.cs | 77 +++++++++++++++++-- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index 68010762..724ec61e 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -4,7 +4,7 @@ // Created : 03-20-2020 // // Last Modified By : Mario -// Last Modified On : 02-22-2024 +// Last Modified On : 02-24-2024 // *********************************************************************** // // Mario @@ -563,9 +563,7 @@ public class MergeViewerControlViewModel( /// /// Gets or sets a value representing the toggle merge type caption. /// - /// - /// The toggle merge type caption. - /// + /// The toggle merge type caption. [StaticLocalization(LocalizationResources.Conflict_Solver.ContextMenu.ToggleCompare)] public virtual string ToggleMergeTypeCaption { get; protected set; } diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index da7a0f6f..28c64727 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -790,15 +790,60 @@ private List GetActionsMenuItems(bool leftSide) new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, new() { Header = "-" }, - new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide }, + new() + { + Header = ViewModel.CopyText, + Command = ReactiveCommand.Create(() => + { + ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); + diffLeft.TextArea.TextView.Redraw(); + diffRight.TextArea.TextView.Redraw(); + }) + }, new() { Header = "-" // Separator magic string, and it's documented... NOT really!!! }, - new() { Header = ViewModel.CopyAll, Command = ViewModel.CopyAllCommand, CommandParameter = leftSide }, - new() { Header = ViewModel.CopyThis, Command = ViewModel.CopyThisCommand, CommandParameter = leftSide }, - new() { Header = ViewModel.CopyThisBeforeLine, Command = ViewModel.CopyThisBeforeLineCommand, CommandParameter = leftSide }, - new() { Header = ViewModel.CopyThisAfterLine, Command = ViewModel.CopyThisAfterLineCommand, CommandParameter = leftSide }, + new() + { + Header = ViewModel.CopyAll, + Command = ReactiveCommand.Create(() => + { + ViewModel.CopyAllCommand.Execute(leftSide).Subscribe(); + diffLeft.TextArea.TextView.Redraw(); + diffRight.TextArea.TextView.Redraw(); + }) + }, + new() + { + Header = ViewModel.CopyThis, + Command = ReactiveCommand.Create(() => + { + ViewModel.CopyThisCommand.Execute(leftSide).Subscribe(); + diffLeft.TextArea.TextView.Redraw(); + diffRight.TextArea.TextView.Redraw(); + }) + }, + new() + { + Header = ViewModel.CopyThisBeforeLine, + Command = ReactiveCommand.Create(() => + { + ViewModel.CopyThisBeforeLineCommand.Execute(leftSide).Subscribe(); + diffLeft.TextArea.TextView.Redraw(); + diffRight.TextArea.TextView.Redraw(); + }) + }, + new() + { + Header = ViewModel.CopyThisAfterLine, + Command = ReactiveCommand.Create(() => + { + ViewModel.CopyThisAfterLineCommand.Execute(leftSide).Subscribe(); + diffLeft.TextArea.TextView.Redraw(); + diffRight.TextArea.TextView.Redraw(); + }) + }, new() { Header = "-" }, new() { Header = ViewModel.ToggleMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } }; @@ -827,7 +872,16 @@ private List GetEditableMenuItems(bool leftSide) new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, new() { Header = "-" }, new() { Header = ViewModel.EditThis, Command = ViewModel.EditThisCommand, CommandParameter = leftSide }, - new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide }, + new() + { + Header = ViewModel.CopyText, + Command = ReactiveCommand.Create(() => + { + ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); + diffLeft.TextArea.TextView.Redraw(); + diffRight.TextArea.TextView.Redraw(); + }) + }, new() { Header = "-" }, new() { Header = ViewModel.DeleteText, Command = ViewModel.DeleteTextCommand, CommandParameter = leftSide }, new() { Header = ViewModel.MoveUp, Command = ViewModel.MoveUpCommand, CommandParameter = leftSide }, @@ -875,7 +929,16 @@ private List GetNonEditableMenuItems(bool leftSide) new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, new() { Header = "-" }, - new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide } + new() + { + Header = ViewModel.CopyText, + Command = ReactiveCommand.Create(() => + { + ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); + diffLeft.TextArea.TextView.Redraw(); + diffRight.TextArea.TextView.Redraw(); + }) + } }; menuItems.AddRange(mainEditingItems); From ef0d82c1c25af5287003804da046dcdd505650c4 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 24 Feb 2024 07:08:25 +0100 Subject: [PATCH 069/101] Fix undo\redo jumping --- src/IronyModManager/Controls/TextEditor.cs | 22 +++++-- .../Controls/MergeViewerControlView.xaml.cs | 64 +++++++++++++------ 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/src/IronyModManager/Controls/TextEditor.cs b/src/IronyModManager/Controls/TextEditor.cs index d4db9a81..4daf40ef 100644 --- a/src/IronyModManager/Controls/TextEditor.cs +++ b/src/IronyModManager/Controls/TextEditor.cs @@ -4,7 +4,7 @@ // Created : 04-15-2020 // // Last Modified By : Mario -// Last Modified On : 02-22-2024 +// Last Modified On : 02-24-2024 // *********************************************************************** // // Mario @@ -96,6 +96,17 @@ public TextEditor() : base(new TextArea()) #region Methods + /// + /// Gets a middle visible line. + /// + /// An int. + public int GetMiddleVisibleLine() + { + var firstLine = TextArea.TextView.GetDocumentLineByVisualTop(ScrollViewer.Offset.Y).LineNumber; + var lastLine = TextArea.TextView.GetDocumentLineByVisualTop(ScrollViewer.Offset.Y + ScrollViewer.Viewport.Height).LineNumber; + return firstLine + ((lastLine - firstLine) / 2); + } + /// /// Scroll to. /// @@ -104,8 +115,7 @@ public TextEditor() : base(new TextArea()) public new void ScrollTo(int line, int column) { const double minimumScrollFraction = 0.3; - ScrollTo(line, column, VisualYPosition.LineMiddle, - null != ScrollViewer ? ScrollViewer.Viewport.Height / 2 : 0.0, minimumScrollFraction); + ScrollTo(line, column, VisualYPosition.LineMiddle, null != ScrollViewer ? ScrollViewer.Viewport.Height / 2 : 0.0, minimumScrollFraction); } /// @@ -160,8 +170,7 @@ public TextEditor() : base(new TextArea()) var targetY = ScrollViewer.Offset.Y; var verticalPos = p.Y - referencedVerticalViewPortOffset; - if (Math.Abs(verticalPos - ScrollViewer.Offset.Y) > - minimumScrollFraction * ScrollViewer.Viewport.Height) + if (Math.Abs(verticalPos - ScrollViewer.Offset.Y) > minimumScrollFraction * ScrollViewer.Viewport.Height) { targetY = Math.Max(0, verticalPos); } @@ -171,8 +180,7 @@ public TextEditor() : base(new TextArea()) if (p.X > ScrollViewer.Viewport.Width - (MinimumDistanceToViewBorder * 2)) { var horizontalPos = Math.Max(0, p.X - (ScrollViewer.Viewport.Width / 2)); - if (Math.Abs(horizontalPos - ScrollViewer.Offset.X) > - minimumScrollFraction * ScrollViewer.Viewport.Width) + if (Math.Abs(horizontalPos - ScrollViewer.Offset.X) > minimumScrollFraction * ScrollViewer.Viewport.Width) { targetX = 0; } diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 28c64727..336ceb5a 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -1,4 +1,5 @@ -// *********************************************************************** + +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 03-20-2020 @@ -32,11 +33,12 @@ using IronyModManager.Implementation.Hotkey; using IronyModManager.Shared; using IronyModManager.ViewModels.Controls; -using ReactiveUI; using static IronyModManager.ViewModels.Controls.MergeViewerControlViewModel; +using ReactiveUI; namespace IronyModManager.Views.Controls { + /// /// Class MergeViewerControlView. /// Implements the @@ -550,7 +552,6 @@ void evalKey() diffRight.IsReadOnly = !s; }).DisposeWith(disposables); - base.OnActivated(disposables); } @@ -796,8 +797,7 @@ private List GetActionsMenuItems(bool leftSide) Command = ReactiveCommand.Create(() => { ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); - diffLeft.TextArea.TextView.Redraw(); - diffRight.TextArea.TextView.Redraw(); + RedrawEditorDiffs(); }) }, new() @@ -810,8 +810,7 @@ private List GetActionsMenuItems(bool leftSide) Command = ReactiveCommand.Create(() => { ViewModel.CopyAllCommand.Execute(leftSide).Subscribe(); - diffLeft.TextArea.TextView.Redraw(); - diffRight.TextArea.TextView.Redraw(); + RedrawEditorDiffs(); }) }, new() @@ -820,8 +819,7 @@ private List GetActionsMenuItems(bool leftSide) Command = ReactiveCommand.Create(() => { ViewModel.CopyThisCommand.Execute(leftSide).Subscribe(); - diffLeft.TextArea.TextView.Redraw(); - diffRight.TextArea.TextView.Redraw(); + RedrawEditorDiffs(); }) }, new() @@ -830,8 +828,7 @@ private List GetActionsMenuItems(bool leftSide) Command = ReactiveCommand.Create(() => { ViewModel.CopyThisBeforeLineCommand.Execute(leftSide).Subscribe(); - diffLeft.TextArea.TextView.Redraw(); - diffRight.TextArea.TextView.Redraw(); + RedrawEditorDiffs(); }) }, new() @@ -840,8 +837,7 @@ private List GetActionsMenuItems(bool leftSide) Command = ReactiveCommand.Create(() => { ViewModel.CopyThisAfterLineCommand.Execute(leftSide).Subscribe(); - diffLeft.TextArea.TextView.Redraw(); - diffRight.TextArea.TextView.Redraw(); + RedrawEditorDiffs(); }) }, new() { Header = "-" }, @@ -878,8 +874,7 @@ private List GetEditableMenuItems(bool leftSide) Command = ReactiveCommand.Create(() => { ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); - diffLeft.TextArea.TextView.Redraw(); - diffRight.TextArea.TextView.Redraw(); + RedrawEditorDiffs(); }) }, new() { Header = "-" }, @@ -898,12 +893,34 @@ private List GetEditableMenuItems(bool leftSide) menuItems.Add(new MenuItem { Header = "-" }); if (undoAvailable) { - menuItems.Add(new MenuItem { Header = ViewModel.Undo, Command = ViewModel.UndoCommand, CommandParameter = leftSide }); + menuItems.Add(new MenuItem + { + Header = ViewModel.Undo, + Command = ReactiveCommand.Create(() => + { + var visibleLine = diffLeft.GetMiddleVisibleLine(); + ViewModel.UndoCommand.Execute(leftSide).Subscribe(); + diffLeft.ScrollToLine(visibleLine); + diffRight.ScrollToLine(visibleLine); + RedrawEditorDiffs(); + }) + }); } if (redoAvailable) { - menuItems.Add(new MenuItem { Header = ViewModel.Redo, Command = ViewModel.RedoCommand, CommandParameter = leftSide }); + menuItems.Add(new MenuItem + { + Header = ViewModel.Redo, + Command = ReactiveCommand.Create(() => + { + var visibleLine = diffLeft.GetMiddleVisibleLine(); + ViewModel.RedoCommand.Execute(leftSide).Subscribe(); + diffLeft.ScrollToLine(visibleLine); + diffRight.ScrollToLine(visibleLine); + RedrawEditorDiffs(); + }) + }); } } @@ -935,8 +952,7 @@ private List GetNonEditableMenuItems(bool leftSide) Command = ReactiveCommand.Create(() => { ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); - diffLeft.TextArea.TextView.Redraw(); - diffRight.TextArea.TextView.Redraw(); + RedrawEditorDiffs(); }) } }; @@ -976,6 +992,16 @@ private void InitializeComponent() AvaloniaXamlLoader.Load(this); } + /// + /// Redraws an editor diffs. + /// + /// + private void RedrawEditorDiffs() + { + diffLeft.TextArea.TextView.Redraw(); + diffRight.TextArea.TextView.Redraw(); + } + /// /// Sets the editor context menu. /// From 45810423729f1463225ba8ba80e026f2b502f811 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 24 Feb 2024 16:39:06 +0100 Subject: [PATCH 070/101] Fix copy all jumping --- .../Views/Controls/MergeViewerControlView.xaml.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 336ceb5a..03dfff85 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -809,7 +809,10 @@ private List GetActionsMenuItems(bool leftSide) Header = ViewModel.CopyAll, Command = ReactiveCommand.Create(() => { + var visibleLine = diffLeft.GetMiddleVisibleLine(); ViewModel.CopyAllCommand.Execute(leftSide).Subscribe(); + diffLeft.ScrollToLine(visibleLine); + diffRight.ScrollToLine(visibleLine); RedrawEditorDiffs(); }) }, From b7ad897b188e71b1224a8328adb39d6efb00e442 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 24 Feb 2024 16:43:44 +0100 Subject: [PATCH 071/101] Rearrange context menus --- .../Controls/MergeViewerControlView.xaml.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 03dfff85..1128e1ae 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -1,5 +1,4 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 03-20-2020 @@ -33,12 +32,11 @@ using IronyModManager.Implementation.Hotkey; using IronyModManager.Shared; using IronyModManager.ViewModels.Controls; -using static IronyModManager.ViewModels.Controls.MergeViewerControlViewModel; using ReactiveUI; +using static IronyModManager.ViewModels.Controls.MergeViewerControlViewModel; namespace IronyModManager.Views.Controls { - /// /// Class MergeViewerControlView. /// Implements the @@ -783,6 +781,7 @@ private List GetActionsMenuItems(bool leftSide) if (ViewModel!.EditorAvailable) { menuItems.Add(new MenuItem { Header = ViewModel.Editor, Command = ViewModel.EditorCommand, CommandParameter = !leftSide }); + menuItems.Add(new MenuItem { Header = ViewModel.ToggleMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand }); menuItems.Add(new MenuItem { Header = "-" }); } @@ -842,9 +841,7 @@ private List GetActionsMenuItems(bool leftSide) ViewModel.CopyThisAfterLineCommand.Execute(leftSide).Subscribe(); RedrawEditorDiffs(); }) - }, - new() { Header = "-" }, - new() { Header = ViewModel.ToggleMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } + } }; menuItems.AddRange(mainEditingItems); @@ -862,6 +859,7 @@ private List GetEditableMenuItems(bool leftSide) if (ViewModel!.EditorAvailable) { menuItems.Add(new MenuItem { Header = ViewModel.Editor, Command = ViewModel.EditorCommand, CommandParameter = leftSide }); + menuItems.Add(new MenuItem { Header = ViewModel.ToggleMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand }); menuItems.Add(new MenuItem { Header = "-" }); } @@ -883,9 +881,7 @@ private List GetEditableMenuItems(bool leftSide) new() { Header = "-" }, new() { Header = ViewModel.DeleteText, Command = ViewModel.DeleteTextCommand, CommandParameter = leftSide }, new() { Header = ViewModel.MoveUp, Command = ViewModel.MoveUpCommand, CommandParameter = leftSide }, - new() { Header = ViewModel.MoveDown, Command = ViewModel.MoveDownCommand, CommandParameter = leftSide }, - new() { Header = "-" }, - new() { Header = ViewModel.ToggleMergeTypeCaption, Command = ViewModel.ToggleMergeTypeCommand } + new() { Header = ViewModel.MoveDown, Command = ViewModel.MoveDownCommand, CommandParameter = leftSide } }; menuItems.AddRange(mainEditingItems); From 0df92dbc05b12fca2935da09c9a9aa5c167a9947 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 24 Feb 2024 22:36:46 +0100 Subject: [PATCH 072/101] Handle all other hotkeys --- src/IronyModManager/Controls/TextArea.cs | 61 +++++++ src/IronyModManager/Controls/TextEditor.cs | 25 ++- .../Controls/MergeViewerControlViewModel.cs | 70 ++++++++ .../Controls/MergeViewerControlView.xaml.cs | 149 +++++++++++------- 4 files changed, 249 insertions(+), 56 deletions(-) create mode 100644 src/IronyModManager/Controls/TextArea.cs diff --git a/src/IronyModManager/Controls/TextArea.cs b/src/IronyModManager/Controls/TextArea.cs new file mode 100644 index 00000000..1749a49e --- /dev/null +++ b/src/IronyModManager/Controls/TextArea.cs @@ -0,0 +1,61 @@ +// *********************************************************************** +// Assembly : IronyModManager +// Author : Mario +// Created : 02-24-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-24-2024 +// *********************************************************************** +// +// Mario +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Input; +using Avalonia.Styling; +using IronyModManager.Shared; + +namespace IronyModManager.Controls +{ + /// + /// The text area. + /// + /// + [ExcludeFromCoverage("Should be tested in functional testing.")] + public class TextArea : AvaloniaEdit.Editing.TextArea, IStyleable + { + /// + /// Occurs when [left pointer pressed]. + /// + public event EventHandler LeftPointerPressed; + + #region Properties + + /// + /// Gets a value representing the style key. + /// + /// The style key. + Type IStyleable.StyleKey => typeof(AvaloniaEdit.Editing.TextArea); + + #endregion Properties + + #region Methods + + /// + /// Handles the event. + /// + /// The instance containing the event data. + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + // You're asking why? Ask whoever designed avaloia which prevents it from routing events even if they are freaking handled + LeftPointerPressed?.Invoke(this, e); + base.OnPointerPressed(e); + } + + #endregion Methods + } +} diff --git a/src/IronyModManager/Controls/TextEditor.cs b/src/IronyModManager/Controls/TextEditor.cs index 4daf40ef..baaa0072 100644 --- a/src/IronyModManager/Controls/TextEditor.cs +++ b/src/IronyModManager/Controls/TextEditor.cs @@ -21,7 +21,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Styling; using AvaloniaEdit; -using AvaloniaEdit.Editing; using AvaloniaEdit.Rendering; using IronyModManager.DI; using IronyModManager.Implementation.Actions; @@ -96,17 +95,37 @@ public TextEditor() : base(new TextArea()) #region Methods + /// + /// Gets a bottom visible line. + /// + /// An int. + public int GetBottomVisibleLine() + { + var lastLine = TextArea.TextView.GetDocumentLineByVisualTop(ScrollViewer.Offset.Y + ScrollViewer.Viewport.Height).LineNumber; + return lastLine; + } + /// /// Gets a middle visible line. /// /// An int. public int GetMiddleVisibleLine() { - var firstLine = TextArea.TextView.GetDocumentLineByVisualTop(ScrollViewer.Offset.Y).LineNumber; - var lastLine = TextArea.TextView.GetDocumentLineByVisualTop(ScrollViewer.Offset.Y + ScrollViewer.Viewport.Height).LineNumber; + var firstLine = GetTopVisibleLine(); + var lastLine = GetBottomVisibleLine(); return firstLine + ((lastLine - firstLine) / 2); } + /// + /// Gets a top visible line. + /// + /// An int. + public int GetTopVisibleLine() + { + var firstLine = TextArea.TextView.GetDocumentLineByVisualTop(ScrollViewer.Offset.Y).LineNumber; + return firstLine; + } + /// /// Scroll to. /// diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index 724ec61e..e71927ca 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -155,6 +155,16 @@ public class MergeViewerControlViewModel( /// public event ConflictFoundDelegate ConflictFound; + /// + /// Occurs when [hotkey after perform]. + /// + public event EventHandler HotkeyAfterPerform; + + /// + /// Occurs when [hotkey copy before perform]. + /// + public event EventHandler HotkeyBeforePerform; + /// /// Occurs when [focus side]. /// @@ -278,6 +288,18 @@ public class MergeViewerControlViewModel( /// The delete text command. public virtual ReactiveCommand DeleteTextCommand { get; protected set; } + /// + /// Gets or sets a value representing the diff editor bottom line. + /// + /// The diff editor bottom line. + public virtual int? DiffEditorBottomLine { get; set; } + + /// + /// Gets or sets a value representing the diff editor top line. + /// + /// The diff editor top line. + public virtual int? DiffEditorTopLine { get; set; } + /// /// Gets or sets a value indicating whether [editing left]. /// @@ -1350,6 +1372,15 @@ void performAction() LeftSideSelected.Add(LeftDiff.FirstOrDefault()); } + if (UsingNewMergeType) + { + if (DiffEditorTopLine.HasValue && DiffEditorTopLine.GetValueOrDefault() - 1 < LeftDiff.Count) + { + LeftSideSelected.Clear(); + LeftSideSelected.Add(LeftDiff[DiffEditorTopLine.GetValueOrDefault() - 1]); + } + } + FindConflict(true, false, false); break; @@ -1359,6 +1390,15 @@ void performAction() LeftSideSelected.Add(LeftDiff.FirstOrDefault()); } + if (UsingNewMergeType) + { + if (DiffEditorBottomLine.HasValue && DiffEditorBottomLine.GetValueOrDefault() - 1 < LeftDiff.Count) + { + LeftSideSelected.Clear(); + LeftSideSelected.Add(LeftDiff[DiffEditorBottomLine.GetValueOrDefault() - 1]); + } + } + FindConflict(true, true, false); break; @@ -1368,6 +1408,15 @@ void performAction() LeftSideSelected.Add(LeftDiff.FirstOrDefault()); } + if (UsingNewMergeType) + { + if (DiffEditorTopLine.HasValue && DiffEditorTopLine.GetValueOrDefault() - 1 < LeftDiff.Count) + { + LeftSideSelected.Clear(); + LeftSideSelected.Add(LeftDiff[DiffEditorTopLine.GetValueOrDefault() - 1]); + } + } + FindConflict(true, false, true); break; @@ -1377,6 +1426,15 @@ void performAction() LeftSideSelected.Add(LeftDiff.FirstOrDefault()); } + if (UsingNewMergeType) + { + if (DiffEditorBottomLine.HasValue && DiffEditorBottomLine.GetValueOrDefault() - 1 < LeftDiff.Count) + { + LeftSideSelected.Clear(); + LeftSideSelected.Add(LeftDiff[DiffEditorBottomLine.GetValueOrDefault() - 1]); + } + } + FindConflict(true, true, true); break; @@ -1399,11 +1457,15 @@ void performAction() case Enums.HotKeys.Ctrl_C: if (LeftSidePatchMod) { + HotkeyBeforePerform?.Invoke(this, EventArgs.Empty); Copy(RightSideSelected, RightDiff, LeftDiff, false); + HotkeyAfterPerform?.Invoke(this, EventArgs.Empty); } else if (RightSidePatchMod) { + HotkeyBeforePerform?.Invoke(this, EventArgs.Empty); Copy(LeftSideSelected, LeftDiff, RightDiff, true); + HotkeyAfterPerform?.Invoke(this, EventArgs.Empty); } break; @@ -1411,11 +1473,15 @@ void performAction() case Enums.HotKeys.Ctrl_V: if (LeftSidePatchMod) { + HotkeyBeforePerform?.Invoke(this, EventArgs.Empty); CopyBeforeLines(RightSideSelected, RightDiff, LeftDiff, false); + HotkeyAfterPerform?.Invoke(this, EventArgs.Empty); } else if (RightSidePatchMod) { + HotkeyBeforePerform?.Invoke(this, EventArgs.Empty); CopyBeforeLines(LeftSideSelected, LeftDiff, RightDiff, true); + HotkeyAfterPerform?.Invoke(this, EventArgs.Empty); } break; @@ -1423,11 +1489,15 @@ void performAction() case Enums.HotKeys.Ctrl_B: if (LeftSidePatchMod) { + HotkeyBeforePerform?.Invoke(this, EventArgs.Empty); CopyAfterLines(RightSideSelected, LeftDiff, RightDiff, false); + HotkeyAfterPerform?.Invoke(this, EventArgs.Empty); } else if (RightSidePatchMod) { + HotkeyBeforePerform?.Invoke(this, EventArgs.Empty); CopyAfterLines(LeftSideSelected, LeftDiff, RightDiff, true); + HotkeyAfterPerform?.Invoke(this, EventArgs.Empty); } break; diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 1128e1ae..a42fb76f 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -4,7 +4,7 @@ // Created : 03-20-2020 // // Last Modified By : Mario -// Last Modified On : 02-23-2024 +// Last Modified On : 02-24-2024 // *********************************************************************** // // Mario @@ -19,9 +19,11 @@ using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Threading; using AvaloniaEdit; +using AvaloniaEdit.Document; using AvaloniaEdit.Search; using AvaloniaEdit.Utils; using DiffPlex.DiffBuilder.Model; @@ -143,6 +145,13 @@ protected virtual void FocusConflict(int line, ListBox leftListBox, ListBox righ rightListBox.SelectedIndex = line; diffLeft.ScrollToLine(line); diffRight.ScrollToLine(line); + ViewModel!.DiffEditorTopLine = diffLeft.GetTopVisibleLine(); + ViewModel.DiffEditorBottomLine = diffLeft.GetBottomVisibleLine(); + if (!(ViewModel.DiffEditorTopLine <= line && ViewModel.DiffEditorBottomLine >= line)) + { + diffLeft.TextArea.Caret.Location = new TextLocation(line, 1); + diffRight.TextArea.Caret.Location = new TextLocation(line, 1); + } } /// @@ -304,6 +313,8 @@ protected virtual void HandleTextEditorPropertyChanged(IronyModManager.Controls. Dispatcher.UIThread.SafeInvoke(async () => { await SyncDiffScrollAsync(thisTextEditor, otherTextEditor); + ViewModel!.DiffEditorTopLine = thisTextEditor.GetTopVisibleLine(); + ViewModel.DiffEditorBottomLine = thisTextEditor.GetBottomVisibleLine(); syncingDiffScroll = false; }); }; @@ -355,6 +366,9 @@ protected override void OnActivated(CompositeDisposable disposables) rightSide.ContextMenuOpening += item => { HandleContextMenu(rightSide, item, false); }; ViewModel!.ConflictFound += line => { FocusConflict(line, leftSide, rightSide); }; + + var diffHotkeyActionLeft = -1; + var diffHotkeyActionRight = -1; int? focusSideScrollItem = null; var previousCount = 0; var autoScroll = leftSide.AutoScrollToSelectedItem; @@ -362,8 +376,8 @@ protected override void OnActivated(CompositeDisposable disposables) { if (ViewModel.UsingNewMergeType) { - focusSideScrollItem = GetTextEditorSelectedTextRange(left ? diffLeft : diffRight).Item2; - previousCount = left ? ViewModel.LeftDiff.Count : ViewModel.RightDiff.Count; + diffHotkeyActionLeft = diffLeft.GetMiddleVisibleLine(); + diffHotkeyActionRight = diffRight.GetMiddleVisibleLine(); } else { @@ -384,28 +398,17 @@ async Task delay() await Task.Delay(50); if (ViewModel!.UsingNewMergeType) { - var textbox = left ? diffLeft : diffRight; - var diffList = left ? ViewModel.LeftDiff : ViewModel.RightDiff; - textbox.Focus(); - if (focusSideScrollItem.HasValue) + diffLeft.ScrollToLine(diffHotkeyActionLeft); + diffRight.ScrollToLine(diffHotkeyActionRight); + ViewModel!.DiffEditorTopLine = diffLeft.GetTopVisibleLine(); + ViewModel.DiffEditorBottomLine = diffLeft.GetBottomVisibleLine(); + if (!(ViewModel.DiffEditorTopLine <= diffHotkeyActionLeft && ViewModel.DiffEditorBottomLine >= diffHotkeyActionLeft)) { - if (diffList.Count != previousCount) - { - focusSideScrollItem -= Math.Abs(previousCount - diffList.Count); - } - - FocusConflict(-1, leftSide, rightSide); - if (focusSideScrollItem.GetValueOrDefault() < 0) - { - focusSideScrollItem = 0; - } - else if (focusSideScrollItem.GetValueOrDefault() >= diffList.Count) - { - focusSideScrollItem = diffList.Count - 1; - } - - textbox.ScrollToLine(focusSideScrollItem.GetValueOrDefault()); + diffLeft.TextArea.Caret.Location = new TextLocation(diffHotkeyActionLeft, 1); + diffRight.TextArea.Caret.Location = new TextLocation(diffHotkeyActionLeft, 1); } + + RedrawEditorDiffs(); } else { @@ -488,7 +491,15 @@ void evalKey() { if (ViewModel.UsingNewMergeType) { - diffLeft.ScrollViewer.LineDown(); + switch (hotkey.Hotkey) + { + case Enums.HotKeys.Ctrl_Shift_Up: + diffLeft.ScrollViewer.LineUp(); + break; + case Enums.HotKeys.Ctrl_Shift_Down: + diffLeft.ScrollViewer.LineDown(); + break; + } } else { @@ -550,6 +561,26 @@ void evalKey() diffRight.IsReadOnly = !s; }).DisposeWith(disposables); + ViewModel!.HotkeyBeforePerform += (_, _) => + { + diffHotkeyActionLeft = diffLeft.GetMiddleVisibleLine(); + diffHotkeyActionRight = diffRight.GetMiddleVisibleLine(); + }; + ViewModel.HotkeyAfterPerform += (_, _) => + { + diffLeft.ScrollToLine(diffHotkeyActionLeft); + diffRight.ScrollToLine(diffHotkeyActionRight); + ViewModel!.DiffEditorTopLine = diffLeft.GetTopVisibleLine(); + ViewModel.DiffEditorBottomLine = diffLeft.GetBottomVisibleLine(); + if (!(ViewModel.DiffEditorTopLine <= diffHotkeyActionLeft && ViewModel.DiffEditorBottomLine >= diffHotkeyActionLeft)) + { + diffLeft.TextArea.Caret.Location = new TextLocation(diffHotkeyActionLeft, 1); + diffRight.TextArea.Caret.Location = new TextLocation(diffHotkeyActionLeft, 1); + } + + RedrawEditorDiffs(); + }; + base.OnActivated(disposables); } @@ -606,10 +637,12 @@ void setEditMode() if (leftDiff) { ViewModel!.SetText(text, ViewModel.RightSide); + diffRight.TextArea.TextView.Redraw(); } else { ViewModel!.SetText(ViewModel.LeftSide, text); + diffLeft.TextArea.TextView.Redraw(); } }; diff.TextArea.SelectionChanged += (_, _) => @@ -634,7 +667,8 @@ void setEditMode() col.Add(sourceCol[i]); } }; - diff.PointerPressed += (_, args) => + + void handlePointerPressed(PointerPressedEventArgs args) { var pos = args.GetPosition(diff); pos = new Point(0, pos.Y.CoerceValue(0, diff.Bounds.Height) + diff.VerticalOffset); @@ -663,6 +697,16 @@ void setEditMode() } } } + } + + diff.PointerPressed += (_, args) => + { + handlePointerPressed(args); + }; + + ((IronyModManager.Controls.TextArea)diff.TextArea).LeftPointerPressed += (_, eventArgs) => + { + handlePointerPressed(eventArgs); }; } @@ -790,15 +834,7 @@ private List GetActionsMenuItems(bool leftSide) new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, new() { Header = "-" }, - new() - { - Header = ViewModel.CopyText, - Command = ReactiveCommand.Create(() => - { - ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); - RedrawEditorDiffs(); - }) - }, + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide }, new() { Header = "-" // Separator magic string, and it's documented... NOT really!!! @@ -812,6 +848,14 @@ private List GetActionsMenuItems(bool leftSide) ViewModel.CopyAllCommand.Execute(leftSide).Subscribe(); diffLeft.ScrollToLine(visibleLine); diffRight.ScrollToLine(visibleLine); + ViewModel!.DiffEditorTopLine = diffLeft.GetTopVisibleLine(); + ViewModel.DiffEditorBottomLine = diffLeft.GetBottomVisibleLine(); + if (!(ViewModel.DiffEditorTopLine <= visibleLine && ViewModel.DiffEditorBottomLine >= visibleLine)) + { + diffLeft.TextArea.Caret.Location = new TextLocation(visibleLine, 1); + diffRight.TextArea.Caret.Location = new TextLocation(visibleLine, 1); + } + RedrawEditorDiffs(); }) }, @@ -869,15 +913,7 @@ private List GetEditableMenuItems(bool leftSide) new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, new() { Header = "-" }, new() { Header = ViewModel.EditThis, Command = ViewModel.EditThisCommand, CommandParameter = leftSide }, - new() - { - Header = ViewModel.CopyText, - Command = ReactiveCommand.Create(() => - { - ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); - RedrawEditorDiffs(); - }) - }, + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide }, new() { Header = "-" }, new() { Header = ViewModel.DeleteText, Command = ViewModel.DeleteTextCommand, CommandParameter = leftSide }, new() { Header = ViewModel.MoveUp, Command = ViewModel.MoveUpCommand, CommandParameter = leftSide }, @@ -901,6 +937,14 @@ private List GetEditableMenuItems(bool leftSide) ViewModel.UndoCommand.Execute(leftSide).Subscribe(); diffLeft.ScrollToLine(visibleLine); diffRight.ScrollToLine(visibleLine); + ViewModel!.DiffEditorTopLine = diffLeft.GetTopVisibleLine(); + ViewModel.DiffEditorBottomLine = diffLeft.GetBottomVisibleLine(); + if (!(ViewModel.DiffEditorTopLine <= visibleLine && ViewModel.DiffEditorBottomLine >= visibleLine)) + { + diffLeft.TextArea.Caret.Location = new TextLocation(visibleLine, 1); + diffRight.TextArea.Caret.Location = new TextLocation(visibleLine, 1); + } + RedrawEditorDiffs(); }) }); @@ -917,6 +961,14 @@ private List GetEditableMenuItems(bool leftSide) ViewModel.RedoCommand.Execute(leftSide).Subscribe(); diffLeft.ScrollToLine(visibleLine); diffRight.ScrollToLine(visibleLine); + ViewModel!.DiffEditorTopLine = diffLeft.GetTopVisibleLine(); + ViewModel.DiffEditorBottomLine = diffLeft.GetBottomVisibleLine(); + if (!(ViewModel.DiffEditorTopLine <= visibleLine && ViewModel.DiffEditorBottomLine >= visibleLine)) + { + diffLeft.TextArea.Caret.Location = new TextLocation(visibleLine, 1); + diffRight.TextArea.Caret.Location = new TextLocation(visibleLine, 1); + } + RedrawEditorDiffs(); }) }); @@ -945,15 +997,7 @@ private List GetNonEditableMenuItems(bool leftSide) new() { Header = ViewModel.NextConflict, Command = ViewModel.NextConflictCommand, CommandParameter = leftSide }, new() { Header = ViewModel.PrevConflict, Command = ViewModel.PrevConflictCommand, CommandParameter = leftSide }, new() { Header = "-" }, - new() - { - Header = ViewModel.CopyText, - Command = ReactiveCommand.Create(() => - { - ViewModel.CopyTextCommand.Execute(leftSide).Subscribe(); - RedrawEditorDiffs(); - }) - } + new() { Header = ViewModel.CopyText, Command = ViewModel.CopyTextCommand, CommandParameter = leftSide } }; menuItems.AddRange(mainEditingItems); @@ -994,7 +1038,6 @@ private void InitializeComponent() /// /// Redraws an editor diffs. /// - /// private void RedrawEditorDiffs() { diffLeft.TextArea.TextView.Redraw(); From 6a68e4897b8b86ab73d4da2a534a75ba77aa618c Mon Sep 17 00:00:00 2001 From: bcssov Date: Sat, 24 Feb 2024 23:02:39 +0100 Subject: [PATCH 073/101] Update dependencies --- src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj | 2 +- src/IronyModManager.IO/IronyModManager.IO.csproj | 2 +- .../IronyModManager.Localization.Tests.csproj | 2 +- .../IronyModManager.Model.Tests.csproj | 2 +- .../IronyModManager.Parser.Tests.csproj | 2 +- .../IronyModManager.Services.Tests.csproj | 2 +- .../IronyModManager.Storage.Tests.csproj | 2 +- .../IronyModManager.Tests.Common.csproj | 2 +- src/IronyModManager.Tests/IronyModManager.Tests.csproj | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj b/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj index 495298c6..f63e38d1 100644 --- a/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj +++ b/src/IronyModManager.IO.Tests/IronyModManager.IO.Tests.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.IO/IronyModManager.IO.csproj b/src/IronyModManager.IO/IronyModManager.IO.csproj index 244790fb..ab408e54 100644 --- a/src/IronyModManager.IO/IronyModManager.IO.csproj +++ b/src/IronyModManager.IO/IronyModManager.IO.csproj @@ -43,7 +43,7 @@ - + diff --git a/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj b/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj index 2b665fe7..743329c3 100644 --- a/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj +++ b/src/IronyModManager.Localization.Tests/IronyModManager.Localization.Tests.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj b/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj index 807e9e79..fc366376 100644 --- a/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj +++ b/src/IronyModManager.Model.Tests/IronyModManager.Model.Tests.csproj @@ -34,7 +34,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj b/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj index 784c82ce..4199ebc9 100644 --- a/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj +++ b/src/IronyModManager.Parser.Tests/IronyModManager.Parser.Tests.csproj @@ -55,7 +55,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj b/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj index 7f163f65..85e34f20 100644 --- a/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj +++ b/src/IronyModManager.Services.Tests/IronyModManager.Services.Tests.csproj @@ -51,7 +51,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj b/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj index 1b97aa4f..c13cb455 100644 --- a/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj +++ b/src/IronyModManager.Storage.Tests/IronyModManager.Storage.Tests.csproj @@ -51,7 +51,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj b/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj index 0770d44e..e8d29412 100644 --- a/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj +++ b/src/IronyModManager.Tests.Common/IronyModManager.Tests.Common.csproj @@ -52,7 +52,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IronyModManager.Tests/IronyModManager.Tests.csproj b/src/IronyModManager.Tests/IronyModManager.Tests.csproj index a8d3ff53..f70383ed 100644 --- a/src/IronyModManager.Tests/IronyModManager.Tests.csproj +++ b/src/IronyModManager.Tests/IronyModManager.Tests.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 8ccb9d300fc4d43245dd674ce44663b975e4020e Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 25 Feb 2024 07:51:02 +0100 Subject: [PATCH 074/101] Add game language service and tests --- .../IGameLanguage.cs | 42 +++++++ .../IPreferences.cs | 13 ++- src/IronyModManager.Models/DIPackage.cs | 10 +- src/IronyModManager.Models/GameLanguage.cs | 45 ++++++++ src/IronyModManager.Models/MappingProfile.cs | 8 +- ...iguration.cs => ModIgnoreConfiguration.cs} | 10 +- src/IronyModManager.Models/Preferences.cs | 22 +++- .../IGameLanguageService.cs | 43 +++++++ .../GameLanguageServiceTests.cs | 104 +++++++++++++++++ src/IronyModManager.Services/DIPackage.cs | 5 +- .../GameLanguageService.cs | 109 ++++++++++++++++++ 11 files changed, 394 insertions(+), 17 deletions(-) create mode 100644 src/IronyModManager.Models.Common/IGameLanguage.cs create mode 100644 src/IronyModManager.Models/GameLanguage.cs rename src/IronyModManager.Models/{IModIgnoreConfiguration.cs => ModIgnoreConfiguration.cs} (87%) create mode 100644 src/IronyModManager.Services.Common/IGameLanguageService.cs create mode 100644 src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs create mode 100644 src/IronyModManager.Services/GameLanguageService.cs diff --git a/src/IronyModManager.Models.Common/IGameLanguage.cs b/src/IronyModManager.Models.Common/IGameLanguage.cs new file mode 100644 index 00000000..65c933b5 --- /dev/null +++ b/src/IronyModManager.Models.Common/IGameLanguage.cs @@ -0,0 +1,42 @@ +// *********************************************************************** +// Assembly : +// Author : Mario +// Created : 02-25-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-25-2024 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace IronyModManager.Models.Common +{ + /// + /// An i game language interface. + /// + public interface IGameLanguage : IModel + { + #region Properties + + /// + /// Gets or sets a value indicating whether the is selected. + /// + /// true if is selected; otherwise, false. + public bool IsSelected { get; set; } + + /// + /// Gets or sets a value representing the type. + /// + /// The type. + public string Type { get; set; } + + #endregion Properties + } +} diff --git a/src/IronyModManager.Models.Common/IPreferences.cs b/src/IronyModManager.Models.Common/IPreferences.cs index 5b134e4e..04589f52 100644 --- a/src/IronyModManager.Models.Common/IPreferences.cs +++ b/src/IronyModManager.Models.Common/IPreferences.cs @@ -4,7 +4,7 @@ // Created : 01-11-2020 // // Last Modified By : Mario -// Last Modified On : 11-02-2022 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario @@ -15,6 +15,11 @@ /// /// The Models namespace. /// + +using System; +using System.Collections.Generic; +using System.Linq; + namespace IronyModManager.Models.Common { /// @@ -38,6 +43,12 @@ public interface IPreferences : IModel /// true if [check for prerelease]; otherwise, false. bool CheckForPrerelease { get; set; } + /// + /// Gets or sets a value representing the conflict solver languages. + /// + /// The conflict solver languages. + List ConflictSolverLanguages { get; set; } + /// /// Gets or sets a value indicating whether [conflict solver prompt shown]. /// diff --git a/src/IronyModManager.Models/DIPackage.cs b/src/IronyModManager.Models/DIPackage.cs index f04c85f5..6108b939 100644 --- a/src/IronyModManager.Models/DIPackage.cs +++ b/src/IronyModManager.Models/DIPackage.cs @@ -1,19 +1,20 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Models // Author : Mario // Created : 01-15-2020 // // Last Modified By : Mario -// Last Modified On : 06-28-2023 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.DI.Extensions; using IronyModManager.Localization; using IronyModManager.Models.Common; @@ -23,7 +24,6 @@ namespace IronyModManager.Models { - /// /// Class DIPackage. /// Implements the @@ -32,7 +32,6 @@ namespace IronyModManager.Models [ExcludeFromCoverage("Should not test external DI.")] public class DIPackage : IPackage { - #region Methods /// @@ -66,6 +65,7 @@ public void RegisterServices(Container container) container.RegisterModel(); container.Register(); container.RegisterModel(); + container.RegisterModel(); } #endregion Methods diff --git a/src/IronyModManager.Models/GameLanguage.cs b/src/IronyModManager.Models/GameLanguage.cs new file mode 100644 index 00000000..23d0e61a --- /dev/null +++ b/src/IronyModManager.Models/GameLanguage.cs @@ -0,0 +1,45 @@ +// *********************************************************************** +// Assembly : +// Author : Mario +// Created : 02-25-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-25-2024 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using IronyModManager.Models.Common; + +namespace IronyModManager.Models +{ + /// + /// The game language. + /// + /// + /// + public class GameLanguage : BaseModel, IGameLanguage + { + #region Properties + + /// + /// Gets or sets a value indicating whether the is selected. + /// + /// true if is selected; otherwise, false. + public bool IsSelected { get; set; } + + /// + /// Gets or sets a value representing the type. + /// + /// The type. + public string Type { get; set; } + + #endregion Properties + } +} diff --git a/src/IronyModManager.Models/MappingProfile.cs b/src/IronyModManager.Models/MappingProfile.cs index 78d97b07..1a0566a8 100644 --- a/src/IronyModManager.Models/MappingProfile.cs +++ b/src/IronyModManager.Models/MappingProfile.cs @@ -1,11 +1,10 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Models // Author : Mario // Created : 01-11-2020 // // Last Modified By : Mario -// Last Modified On : 06-28-2023 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Copyright (c) Mario. All rights reserved. @@ -15,13 +14,13 @@ using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.Models.Common; using IronyModManager.Shared; using IronyModManager.Shared.Models; namespace IronyModManager.Models { - /// /// Class MappingProfile. /// Implements the @@ -63,6 +62,7 @@ public MappingProfile() CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } #endregion Constructors diff --git a/src/IronyModManager.Models/IModIgnoreConfiguration.cs b/src/IronyModManager.Models/ModIgnoreConfiguration.cs similarity index 87% rename from src/IronyModManager.Models/IModIgnoreConfiguration.cs rename to src/IronyModManager.Models/ModIgnoreConfiguration.cs index d8775cdd..9a64058d 100644 --- a/src/IronyModManager.Models/IModIgnoreConfiguration.cs +++ b/src/IronyModManager.Models/ModIgnoreConfiguration.cs @@ -1,17 +1,17 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Models // Author : Mario // Created : 06-28-2023 // // Last Modified By : Mario -// Last Modified On : 06-28-2023 +// Last Modified On : 07-16-2023 // *********************************************************************** -// +// // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Linq; @@ -19,7 +19,6 @@ namespace IronyModManager.Models { - /// /// Class ModIgnoreConfiguration. /// Implements the @@ -36,6 +35,7 @@ public class ModIgnoreConfiguration : BaseModel, IModIgnoreConfiguration /// /// The count. public int Count { get; set; } + /// /// Gets or sets the name of the mod. /// diff --git a/src/IronyModManager.Models/Preferences.cs b/src/IronyModManager.Models/Preferences.cs index dd0a5fab..087b34ba 100644 --- a/src/IronyModManager.Models/Preferences.cs +++ b/src/IronyModManager.Models/Preferences.cs @@ -4,7 +4,7 @@ // Created : 01-11-2020 // // Last Modified By : Mario -// Last Modified On : 11-02-2022 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.Models.Common; /// @@ -30,6 +31,19 @@ namespace IronyModManager.Models /// public class Preferences : BaseModel, IPreferences { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public Preferences() + { + // Magic string, ensures backwardds compatibility -- I hate it you hate it now move on + ConflictSolverLanguages = ["all"]; + } + + #endregion Constructors + #region Properties /// @@ -44,6 +58,12 @@ public class Preferences : BaseModel, IPreferences /// true if [check for prerelease]; otherwise, false. public virtual bool CheckForPrerelease { get; set; } + /// + /// Gets or sets a value representing the conflict solver languages. + /// + /// The conflict solver languages. + public List ConflictSolverLanguages { get; set; } + /// /// Gets or sets a value indicating whether [conflict solver prompt shown]. /// diff --git a/src/IronyModManager.Services.Common/IGameLanguageService.cs b/src/IronyModManager.Services.Common/IGameLanguageService.cs new file mode 100644 index 00000000..7b11cfaf --- /dev/null +++ b/src/IronyModManager.Services.Common/IGameLanguageService.cs @@ -0,0 +1,43 @@ +// *********************************************************************** +// Assembly : +// Author : Mario +// Created : 02-25-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-25-2024 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using IronyModManager.Models.Common; + +namespace IronyModManager.Services.Common +{ + /// + /// An game language service interface. + /// + public interface IGameLanguageService : IBaseService + { + #region Methods + + /// + /// Get. + /// + /// A list of IGameLanguages. + IEnumerable Get(); + + /// + /// Save. + /// + /// The languages. + void Save(IEnumerable languages); + + #endregion Methods + } +} diff --git a/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs b/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs new file mode 100644 index 00000000..407bac9c --- /dev/null +++ b/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs @@ -0,0 +1,104 @@ +// *********************************************************************** +// Assembly : +// Author : Mario +// Created : 02-25-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-25-2024 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using IronyModManager.Localization; +using IronyModManager.Models; +using IronyModManager.Models.Common; +using IronyModManager.Services.Common; +using IronyModManager.Storage.Common; +using IronyModManager.Tests.Common; +using Moq; +using Xunit; + +namespace IronyModManager.Services.Tests +{ + /// + /// The game language service tests. + /// + public class GameLanguageServiceTests + { + /// + /// Setup mocks. + /// + /// The preferences service. + /// A list of strings + private static void SetupMocks(Mock preferencesService, params string[] languages) + { + DISetup.SetupContainer(); + CurrentLocale.SetCurrent("en"); + preferencesService.Setup(p => p.Get()).Returns(() => new Preferences { ConflictSolverLanguages = languages.ToList() }); + preferencesService.Setup(p => p.Save(It.IsAny())).Returns(true); + } + + /// + /// Shoulds a return default to all language. + /// + [Fact] + public void Should_return_default_to_all_language() + { + // So we use a magic string -- because lazy ensure that tests cover it properly + var preferencesService = new Mock(); + SetupMocks(preferencesService, "all"); + var service = new GameLanguageService(new Mock().Object, null, preferencesService.Object); + var result = service.Get(); + result.All(p => p.IsSelected).Should().BeTrue(); + } + + /// + /// Shoulds a return valid selection. + /// + [Fact] + public void Should_return_valid_selection() + { + var preferencesService = new Mock(); + SetupMocks(preferencesService, "l_english"); + var service = new GameLanguageService(new Mock().Object, null, preferencesService.Object); + var result = service.Get(); + result.Count(p => p.IsSelected).Should().Be(1); + result.FirstOrDefault(p => p.IsSelected)!.Type.Should().Be("l_english"); + } + + /// + /// Shoulds a save selection. + /// + [Fact] + public void Should_save_selection() + { + var preferencesService = new Mock(); + SetupMocks(preferencesService, "all"); + IPreferences prefs = null; + preferencesService.Setup(p => p.Save(It.IsAny())).Returns((IPreferences saved) => + { + prefs = saved; + return true; + }); + var service = new GameLanguageService(new Mock().Object, null, preferencesService.Object); + var langs = service.Get(); + foreach (var lang in langs) + { + lang.IsSelected = lang.Type == "l_english"; + } + + service.Save(langs); + + prefs.Should().NotBeNull(); + prefs.ConflictSolverLanguages.Count.Should().Be(1); + prefs.ConflictSolverLanguages.FirstOrDefault().Should().Be("l_english"); + } + } +} diff --git a/src/IronyModManager.Services/DIPackage.cs b/src/IronyModManager.Services/DIPackage.cs index daef7090..8f30bfca 100644 --- a/src/IronyModManager.Services/DIPackage.cs +++ b/src/IronyModManager.Services/DIPackage.cs @@ -4,15 +4,17 @@ // Created : 01-11-2020 // // Last Modified By : Mario -// Last Modified On : 05-14-2023 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.DI.Extensions; using IronyModManager.Services.Common; using IronyModManager.Shared; @@ -58,6 +60,7 @@ public void RegisterServices(Container container) container.Register(); container.Register(); container.Register(); + container.Register(); } #endregion Methods diff --git a/src/IronyModManager.Services/GameLanguageService.cs b/src/IronyModManager.Services/GameLanguageService.cs new file mode 100644 index 00000000..a60ec108 --- /dev/null +++ b/src/IronyModManager.Services/GameLanguageService.cs @@ -0,0 +1,109 @@ +// *********************************************************************** +// Assembly : +// Author : Mario +// Created : 02-25-2024 +// +// Last Modified By : Mario +// Last Modified On : 02-25-2024 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using IronyModManager.Models.Common; +using IronyModManager.Services.Common; +using IronyModManager.Storage.Common; + +namespace IronyModManager.Services +{ + /// + /// The game language service. + /// + /// + /// + /// The storage provider. + /// The mapper. + /// The preferences service. + /// Initializes a new instance of the class. + public class GameLanguageService(IStorageProvider storageProvider, IMapper mapper, IPreferencesService preferencesService) : BaseService(storageProvider, mapper), IGameLanguageService + { + #region Fields + + /// + /// All selected + /// + private const string AllSelected = "all"; + + /// + /// A private string named defaultLanguage. + /// + private const string DefaultLanguage = "l_default"; + + /// + /// A private static object named objLock. + /// + private static readonly object objLock = new(); + + /// + /// A private readonly IPreferencesService named preferencesService. + /// + private readonly IPreferencesService preferencesService = preferencesService; + + #endregion Fields + + #region Methods + + /// + /// Get. + /// + /// A list of IGameLanguages. + public IEnumerable Get() + { + var result = new List(); + var selectedLanguages = preferencesService.Get().ConflictSolverLanguages; + var locales = Parser.Common.Constants.Localization.Locales.Where(p => p != DefaultLanguage); + foreach (var locale in locales) + { + var model = GetModelInstance(); + InitModel(model, locale, selectedLanguages); + result.Add(model); + } + + return result; + } + + /// + /// Save. + /// + /// The languages. + public void Save(IEnumerable languages) + { + lock (objLock) + { + var preferences = preferencesService.Get(); + preferences.ConflictSolverLanguages = languages.Where(p => p.IsSelected).Select(p => p.Type).ToList(); + preferencesService.Save(preferences); + } + } + + /// + /// Init model. + /// + /// The language. + /// The type. + /// A list of strings + private void InitModel(IGameLanguage language, string type, List selected) + { + language.Type = type; + language.IsSelected = selected.Contains(AllSelected) || selected.Contains(type); + } + + #endregion Methods + } +} From 5cf828cc3145491ca308240ca2661e965d427863 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 25 Feb 2024 07:55:35 +0100 Subject: [PATCH 075/101] Resolve some code warnings --- .../Controls/OptionsControlViewModel.cs | 56 +++++++---- .../Views/Controls/OptionsControlView.xaml | 92 +++++++++---------- .../Views/Controls/OptionsControlView.xaml.cs | 31 ++++--- 3 files changed, 101 insertions(+), 78 deletions(-) diff --git a/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs index f4d3ff10..d70f3131 100644 --- a/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs @@ -1,5 +1,4 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 05-30-2020 @@ -12,6 +11,7 @@ // // // *********************************************************************** + using System; using System.Collections.Generic; using System.IO; @@ -38,7 +38,6 @@ namespace IronyModManager.ViewModels.Controls { - /// /// Class OptionsControlViewModel. /// Implements the @@ -137,22 +136,22 @@ public class OptionsControlViewModel : BaseViewModel /// /// The is editor reloading /// - private bool isEditorReloading = false; + private bool isEditorReloading; /// /// The is game reloading /// - private bool isGameReloading = false; + private bool isGameReloading; /// /// The is notification position reloading /// - private bool isNotificationPositionReloading = false; + private bool isNotificationPositionReloading; /// /// The is update reloading /// - private bool isUpdateReloading = false; + private bool isUpdateReloading; /// /// The last skipped version changed @@ -675,7 +674,7 @@ protected virtual async Task CheckForUpdatesAsync(bool autoUpdateCheck = false) { var title = localizationManager.GetResource(LocalizationResources.Options.Updates.UpdateNotification.Title); var message = localizationManager.GetResource(LocalizationResources.Options.Updates.UpdateNotification.Message); - notificationAction.ShowNotification(title, message, NotificationType.Info, 30, onClick: () => { IsOpen = true; }); + notificationAction.ShowNotification(title, message, NotificationType.Info, 30, () => { IsOpen = true; }); } } else @@ -687,6 +686,7 @@ protected virtual async Task CheckForUpdatesAsync(bool autoUpdateCheck = false) notificationAction.ShowNotification(title, message, NotificationType.Info); } } + CheckingForUpdates = false; } @@ -720,6 +720,7 @@ async Task showPrompt() SaveUpdateSettings(); } } + showPrompt().ConfigureAwait(false); } else if (updateSettings.AutoUpdates.GetValueOrDefault()) @@ -727,6 +728,7 @@ async Task showPrompt() CheckForUpdatesAsync(true).ConfigureAwait(false); } } + SetUpdateSettings(updateSettings); var updateCheckAllowed = this.WhenAnyValue(p => p.CheckingForUpdates, v => !v); @@ -756,11 +758,13 @@ async Task showPrompt() { Game.LaunchArguments = defaultSettings.LaunchArguments; } + if (string.IsNullOrWhiteSpace(Game.UserDirectory)) { Game.UserDirectory = defaultSettings.UserDirectory; } } + SaveGame(); } }).DisposeWith(disposables); @@ -793,6 +797,7 @@ async Task showPrompt() { result = result.TrimEnd(Path.DirectorySeparatorChar + Shared.Constants.JsonModDirectory); } + Game.UserDirectory = result; SaveGame(); } @@ -806,10 +811,12 @@ async Task showPrompt() { Game.LaunchArguments = defaultSettings.LaunchArguments; } + if (string.IsNullOrWhiteSpace(Game.UserDirectory)) { Game.UserDirectory = defaultSettings.UserDirectory; } + SaveGame(); }).DisposeWith(disposables); @@ -890,6 +897,7 @@ async Task showPrompt() { updater.SetSkippedVersion(version); } + UpdateSettings.LastSkippedVersion = version; }); @@ -904,6 +912,7 @@ async Task showPrompt() var message = localizationManager.GetResource(LocalizationResources.Options.Prompts.CustomModDirectory.Message); save = await notificationAction.ShowPromptAsync(title, title, message, NotificationType.Warning); } + IsOpen = true; if (save) { @@ -921,6 +930,7 @@ async Task showPrompt() var message = localizationManager.GetResource(LocalizationResources.Options.Prompts.CustomModDirectory.Message); save = await notificationAction.ShowPromptAsync(title, title, message, NotificationType.Warning); } + if (save) { var defaultSettings = gameService.GetDefaultGameSettings(Game); @@ -961,6 +971,7 @@ async Task showPrompt() { return; } + ITempFile createTempFile(string text) { var file = DIResolver.Get(); @@ -968,6 +979,7 @@ ITempFile createTempFile(string text) file.Text = text; return file; } + var left = createTempFile(localizationManager.GetResource(LocalizationResources.Options.Editor.TestLeft)); var right = createTempFile(localizationManager.GetResource(LocalizationResources.Options.Editor.TestRight)); var arguments = externalEditorService.GetLaunchArguments(left.File, right.File); @@ -975,6 +987,7 @@ ITempFile createTempFile(string text) { await notificationAction.ShowPromptAsync(TestExternalEditorConfiguration, TestExternalEditorConfiguration, TestExternalEditorConfiguration, NotificationType.Info, PromptType.OK); } + left.Dispose(); right.Dispose(); }).DisposeWith(disposables); @@ -1010,14 +1023,14 @@ protected virtual void SaveEditor() protected virtual void SaveGame() { var game = gameService.GetSelected(); - bool exeChanged = game.ExecutableLocation != Game.ExecutableLocation; + var exeChanged = game.ExecutableLocation != Game.ExecutableLocation; game.ExecutableLocation = Game.ExecutableLocation; game.LaunchArguments = Game.LaunchArguments; game.RefreshDescriptors = Game.RefreshDescriptors; game.CloseAppAfterGameLaunch = Game.CloseAppAfterGameLaunch; - bool dirChanged = game.UserDirectory != Game.UserDirectory; + var dirChanged = game.UserDirectory != Game.UserDirectory; game.UserDirectory = Game.UserDirectory; - bool customDirectoryChanged = game.CustomModDirectory != Game.CustomModDirectory; + var customDirectoryChanged = game.CustomModDirectory != Game.CustomModDirectory; game.CustomModDirectory = Game.CustomModDirectory; if (gameService.Save(game)) { @@ -1025,11 +1038,13 @@ protected virtual void SaveGame() { MessageBus.PublishAsync(new GameUserDirectoryChangedEvent(game, customDirectoryChanged)); } + if (exeChanged) { MessageBus.PublishAsync(new GameExeChangedEvent(game.ExecutableLocation)); } } + SetGame(game); } @@ -1064,7 +1079,7 @@ protected virtual void SetEditor(IExternalEditor externalEditor) isEditorReloading = true; Editor = externalEditor; editorArgsChanged?.Dispose(); - editorArgsChanged = this.WhenAnyValue(p => p.Editor.ExternalEditorParameters).Where(p => !isEditorReloading).Subscribe(s => + editorArgsChanged = this.WhenAnyValue(p => p.Editor.ExternalEditorParameters).Where(_ => !isEditorReloading).Subscribe(_ => { SaveEditor(); }).DisposeWith(Disposables); @@ -1082,15 +1097,15 @@ protected virtual void SetGame(IGame game) refreshDescriptorsChanged?.Dispose(); closeGameChanged?.Dispose(); Game = game; - gameArgsChanged = this.WhenAnyValue(p => p.Game.LaunchArguments).Where(p => !isGameReloading).Subscribe(s => + gameArgsChanged = this.WhenAnyValue(p => p.Game.LaunchArguments).Where(_ => !isGameReloading).Subscribe(_ => { SaveGame(); }).DisposeWith(Disposables); - refreshDescriptorsChanged = this.WhenAnyValue(p => p.Game.RefreshDescriptors).Where(p => !isGameReloading).Subscribe(s => + refreshDescriptorsChanged = this.WhenAnyValue(p => p.Game.RefreshDescriptors).Where(_ => !isGameReloading).Subscribe(_ => { SaveGame(); }).DisposeWith(Disposables); - closeGameChanged = this.WhenAnyValue(p => p.Game.CloseAppAfterGameLaunch).Where(p => !isGameReloading).Subscribe(s => + closeGameChanged = this.WhenAnyValue(p => p.Game.CloseAppAfterGameLaunch).Where(_ => !isGameReloading).Subscribe(_ => { SaveGame(); }).DisposeWith(Disposables); @@ -1112,19 +1127,22 @@ protected virtual void SetNotificationPosition(IEnumerable p.NotificationPosition).Where(p => !isNotificationPositionReloading).Subscribe(s => + notificationPositionChanged = this.WhenAnyValue(p => p.NotificationPosition).Where(_ => !isNotificationPositionReloading).Subscribe(s => { foreach (var item in NotificationPositions) { item.IsSelected = item == s; } + SaveNotificationOption(); }).DisposeWith(Disposables); if (!resubscribeOnly && notificationPositions != null) { NotificationPosition = NotificationPositions.FirstOrDefault(p => p.IsSelected); } + isNotificationPositionReloading = false; } @@ -1139,16 +1157,16 @@ protected virtual void SetUpdateSettings(IUpdateSettings updateSettings) checkForPrereleaseChanged?.Dispose(); lastSkippedVersionChanged?.Dispose(); UpdateSettings = updateSettings; - autoUpdateChanged = this.WhenAnyValue(p => p.UpdateSettings.AutoUpdates).Where(v => !isUpdateReloading).Subscribe(s => + autoUpdateChanged = this.WhenAnyValue(p => p.UpdateSettings.AutoUpdates).Where(_ => !isUpdateReloading).Subscribe(_ => { SaveUpdateSettings(); }).DisposeWith(Disposables); - checkForPrereleaseChanged = this.WhenAnyValue(p => p.UpdateSettings.CheckForPrerelease).Where(v => !isUpdateReloading).Subscribe(s => + checkForPrereleaseChanged = this.WhenAnyValue(p => p.UpdateSettings.CheckForPrerelease).Where(_ => !isUpdateReloading).Subscribe(_ => { UpdateInfoVisible = false; SaveUpdateSettings(); }).DisposeWith(Disposables); - lastSkippedVersionChanged = this.WhenAnyValue(p => p.UpdateSettings.LastSkippedVersion).Where(v => !isUpdateReloading).Subscribe(s => + lastSkippedVersionChanged = this.WhenAnyValue(p => p.UpdateSettings.LastSkippedVersion).Where(_ => !isUpdateReloading).Subscribe(_ => { UpdateInfoVisible = false; SaveUpdateSettings(); diff --git a/src/IronyModManager/Views/Controls/OptionsControlView.xaml b/src/IronyModManager/Views/Controls/OptionsControlView.xaml index b39b03d0..7c66fe1d 100644 --- a/src/IronyModManager/Views/Controls/OptionsControlView.xaml +++ b/src/IronyModManager/Views/Controls/OptionsControlView.xaml @@ -9,53 +9,53 @@ + Foreground="{DynamicResource IronyAccentBrush}" x:Name="openPopupButton" /> - + - + - - - - - - + HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" MaxHeight="30" /> + + + - - - - - - - - + HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" /> + + + - - - + + + + HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" /> + HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" MaxHeight="30" /> - + - - + + + HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" MaxHeight="30" /> - + @@ -101,24 +101,24 @@ - + - + + HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" MaxHeight="30" /> + HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" MaxHeight="30" /> - + /// The display name. - public string DisplayName { get; set; } + [DynamicLocalization(LocalizationResources.GameLanguages.Prefix, nameof(Type))] + public virtual string DisplayName { get; set; } /// /// Gets or sets a value indicating whether the is selected. /// /// true if is selected; otherwise, false. - public bool IsSelected { get; set; } + public virtual bool IsSelected { get; set; } /// /// Gets or sets a value representing the type. /// /// The type. - public string Type { get; set; } + public virtual string Type { get; set; } #endregion Properties } diff --git a/src/IronyModManager.Services/GameLanguageService.cs b/src/IronyModManager.Services/GameLanguageService.cs index 8c98a111..ca83dcb4 100644 --- a/src/IronyModManager.Services/GameLanguageService.cs +++ b/src/IronyModManager.Services/GameLanguageService.cs @@ -1,12 +1,12 @@ // *********************************************************************** -// Assembly : +// Assembly : IronyModManager.Services // Author : Mario // Created : 02-25-2024 // // Last Modified By : Mario // Last Modified On : 02-25-2024 // *********************************************************************** -// +// // Copyright (c) . All rights reserved. // // @@ -18,7 +18,6 @@ using AutoMapper; using IronyModManager.Models.Common; using IronyModManager.Services.Common; -using IronyModManager.Shared; using IronyModManager.Storage.Common; namespace IronyModManager.Services @@ -103,7 +102,7 @@ private void InitModel(IGameLanguage language, string type, List selecte { language.Type = type; language.IsSelected = selected.Contains(AllSelected) || selected.Contains(type); - language.DisplayName = type[2..].Replace("_", " ").CapitalizeEveryFirstLetter(); // first letters are "l_" + language.DisplayName = type; } #endregion Methods diff --git a/src/IronyModManager.Shared/LocalizationResources.cs b/src/IronyModManager.Shared/LocalizationResources.cs index 03240f63..6f0ffff3 100644 --- a/src/IronyModManager.Shared/LocalizationResources.cs +++ b/src/IronyModManager.Shared/LocalizationResources.cs @@ -138,6 +138,22 @@ public static class Games public const string Victoria3 = Prefix + "Victoria3"; public const string STInfinite = Prefix + "STInfinite"; } + public static class GameLanguages + { + public const string Prefix = "GameLanguages."; + public const string l_english = Prefix + "l_english"; + public const string l_braz_por = Prefix + "l_braz_por"; + public const string l_french = Prefix + "l_french"; + public const string l_german = Prefix + "l_german"; + public const string l_polish = Prefix + "l_polish"; + public const string l_russian = Prefix + "l_russian"; + public const string l_simp_chinese = Prefix + "l_simp_chinese"; + public const string l_spanish = Prefix + "l_spanish"; + public const string l_chinese = Prefix + "l_chinese"; + public const string l_traditional_chinese = Prefix + "l_traditional_chinese"; + public const string l_japanese = Prefix + "l_japanese"; + public const string l_korean = Prefix + "l_korean"; + } public static class Installed_Mods { public const string Prefix = "Installed_Mods."; diff --git a/src/IronyModManager/Localization/de.json b/src/IronyModManager/Localization/de.json index e128ad37..5a1bb566 100644 --- a/src/IronyModManager/Localization/de.json +++ b/src/IronyModManager/Localization/de.json @@ -99,6 +99,20 @@ "Victoria3": "Victoria 3", "STInfinite": "Star Trek: Infinite" }, + "GameLanguages": { + "l_english": "Englisch", + "l_braz_por": "Portugiesisch", + "l_french": "Französisch", + "l_german": "Deutsch", + "l_polish": "Polnisch", + "l_russian": "Russisch", + "l_simp_chinese": "Einfaches Chinesisch", + "l_spanish": "Spanisch", + "l_chinese": "Chinesisch", + "l_traditional_chinese": "Traditionelles Chinesisch", + "l_japanese": "Japanisch", + "l_korean": "Koreanisch" + }, "Installed_Mods": { "Name": "Installierte Mods", "Filter": "Mods Filtern", diff --git a/src/IronyModManager/Localization/en.json b/src/IronyModManager/Localization/en.json index 9c1ec43f..328a0eed 100644 --- a/src/IronyModManager/Localization/en.json +++ b/src/IronyModManager/Localization/en.json @@ -99,6 +99,20 @@ "Victoria3": "Victoria 3", "STInfinite": "Star Trek: Infinite" }, + "GameLanguages": { + "l_english": "English", + "l_braz_por": "Portugese", + "l_french": "French", + "l_german": "German", + "l_polish": "Polish", + "l_russian": "Russian", + "l_simp_chinese": "Simp Chinese", + "l_spanish": "Spanish", + "l_chinese": "Chinese", + "l_traditional_chinese": "Traditional Chinese", + "l_japanese": "Japanese", + "l_korean": "Korean" + }, "Installed_Mods": { "Name": "Installed Mods", "Filter": "Filter Mods", diff --git a/src/IronyModManager/Localization/es.json b/src/IronyModManager/Localization/es.json index 94ccb719..39d88324 100644 --- a/src/IronyModManager/Localization/es.json +++ b/src/IronyModManager/Localization/es.json @@ -99,6 +99,20 @@ "Victoria3": "Victoria 3", "STInfinite": "Star Trek: Infinite" }, + "GameLanguages": { + "l_english": "English", + "l_braz_por": "Portugués", + "l_french": "Francés", + "l_german": "Alemán", + "l_polish": "Polaco", + "l_russian": "Ruso", + "l_simp_chinese": "Simp Chinese", + "l_spanish": "Español", + "l_chinese": "Chino", + "l_traditional_chinese": "Chino tradicional", + "l_japanese": "Japonés", + "l_korean": "Coreano" + }, "Installed_Mods": { "Name": "Mods Instalados", "Filter": "Filtro de Mods", diff --git a/src/IronyModManager/Localization/fr.json b/src/IronyModManager/Localization/fr.json index 236c2919..effcd6b8 100644 --- a/src/IronyModManager/Localization/fr.json +++ b/src/IronyModManager/Localization/fr.json @@ -99,6 +99,20 @@ "Victoria3": "Victoria 3", "STInfinite": "Star Trek: Infinite" }, + "GameLanguages": { + "l_english": "English", + "l_braz_por": "Portugais", + "l_french": "Français", + "l_german": "Allemand", + "l_polish": "Polonais", + "l_russian": "Russe", + "l_simp_chinese": "Chinois simplifié", + "l_spanish": "Espagnol", + "l_chinese": "Chinois", + "l_traditional_chinese": "Chinois traditionnel", + "l_japanese": "japonais", + "l_korean": "Koréen" + }, "Installed_Mods": { "Name": "Mods installés", "Filter": "Filtrer les mods", diff --git a/src/IronyModManager/Localization/hr.json b/src/IronyModManager/Localization/hr.json index 4b8c431f..1bee4e93 100644 --- a/src/IronyModManager/Localization/hr.json +++ b/src/IronyModManager/Localization/hr.json @@ -99,6 +99,20 @@ "Victoria3": "Victoria 3", "STInfinite": "Star Trek: Infinite" }, + "GameLanguages": { + "l_english": "Engleski", + "l_braz_por": "Portugalski", + "l_french": "Francuski", + "l_german": "Njemački", + "l_polish": "Poljski", + "l_russian": "Ruski", + "l_simp_chinese": "Jednostavan kineski", + "l_spanish": "Španjolski", + "l_chinese": "Kineski", + "l_traditional_chinese": "Tradicionalni kineski", + "l_japanese": "Japanski", + "l_korean": "Korejski" + }, "Installed_Mods": { "Name": "Instalirani modovi", "Filter": "Filtriraj modove", diff --git a/src/IronyModManager/Localization/ru.json b/src/IronyModManager/Localization/ru.json index c35423ef..afe8de09 100644 --- a/src/IronyModManager/Localization/ru.json +++ b/src/IronyModManager/Localization/ru.json @@ -99,6 +99,20 @@ "Victoria3": "Victoria 3", "STInfinite": "Star Trek: Infinite" }, + "GameLanguages": { + "l_english": "Английский", + "l_braz_por": "Португальский", + "l_french": "Французский", + "l_german": "Немецкий", + "l_polish": "Польский", + "l_russian": "Русский", + "l_simp_chinese": "Упрощенный китайский", + "l_spanish": "Испанский", + "l_chinese": "Китайский", + "l_traditional_chinese": "Традиционный китайский", + "l_japanese": "Японский", + "l_korean": "Корейский" + }, "Installed_Mods": { "Name": "Установленные моды", "Filter": "Фильтр", diff --git a/src/IronyModManager/Localization/zh.json b/src/IronyModManager/Localization/zh.json index 10134ad7..cd92222c 100644 --- a/src/IronyModManager/Localization/zh.json +++ b/src/IronyModManager/Localization/zh.json @@ -99,6 +99,20 @@ "Victoria3": "维多利亚 Ⅲ", "STInfinite": "Star Trek: Infinite" }, + "GameLanguages": { + "l_english": "英语", + "l_braz_por": "葡萄牙语", + "l_french": "法语", + "l_german": "德语", + "l_polish": "波兰语", + "l_russian": "俄语", + "l_simp_chinese": "简体中文", + "l_spanish": "西班牙语", + "l_chinese": "中文", + "l_traditional_chinese": "繁体中文", + "l_japanese": "日语", + "l_korean": "韩语" + }, "Installed_Mods": { "Name": "已安装模组", "Filter": "过滤模组", diff --git a/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs index 4778458d..5d68aaec 100644 --- a/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/OptionsControlViewModel.cs @@ -360,6 +360,7 @@ public class OptionsControlViewModel( /// Gets or sets a value representing the game languages. /// /// The game languages. + [AutoRefreshLocalization] public virtual AvaloniaList GameLanguages { get; protected set; } /// @@ -671,7 +672,7 @@ protected virtual void BindGameLanguages(IGame game = null) gameLanguagesChanged = null; var sourceList = languages.ToSourceList(); - gameLanguagesChanged = sourceList.Connect().WhenPropertyChanged(p => p.IsSelected, notifyOnInitialValue:false).Subscribe(s => + gameLanguagesChanged = sourceList.Connect().WhenPropertyChanged(p => p.IsSelected, false).Subscribe(_ => { gameLanguageService.Save(languages); }).DisposeWith(Disposables); diff --git a/src/IronyModManager/Views/Controls/OptionsControlView.xaml b/src/IronyModManager/Views/Controls/OptionsControlView.xaml index 0350374e..aee70f39 100644 --- a/src/IronyModManager/Views/Controls/OptionsControlView.xaml +++ b/src/IronyModManager/Views/Controls/OptionsControlView.xaml @@ -85,14 +85,16 @@ - + - + + + From 5cc6bdcedd6965332b0c9edcf292667305df1192 Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 25 Feb 2024 16:45:12 +0100 Subject: [PATCH 084/101] Add initial filtering --- .../IGameLanguageService.cs | 10 +- .../IModPatchCollectionService.cs | 11 +- .../GameLanguageServiceTests.cs | 14 + .../ModPatchCollectionServiceTests.cs | 1818 ++++++----------- .../GameLanguageService.cs | 9 + .../ModPatchCollectionService.cs | 45 +- .../Controls/ModHolderControlViewModel.cs | 17 +- 7 files changed, 643 insertions(+), 1281 deletions(-) diff --git a/src/IronyModManager.Services.Common/IGameLanguageService.cs b/src/IronyModManager.Services.Common/IGameLanguageService.cs index 7b11cfaf..f42297a1 100644 --- a/src/IronyModManager.Services.Common/IGameLanguageService.cs +++ b/src/IronyModManager.Services.Common/IGameLanguageService.cs @@ -1,12 +1,12 @@ // *********************************************************************** -// Assembly : +// Assembly : IronyModManager.Services.Common // Author : Mario // Created : 02-25-2024 // // Last Modified By : Mario // Last Modified On : 02-25-2024 // *********************************************************************** -// +// // Copyright (c) . All rights reserved. // // @@ -32,6 +32,12 @@ public interface IGameLanguageService : IBaseService /// A list of IGameLanguages. IEnumerable Get(); + /// + /// Get selected. + /// + /// A read only collection of IGameLanguages. + IReadOnlyCollection GetSelected(); + /// /// Save. /// diff --git a/src/IronyModManager.Services.Common/IModPatchCollectionService.cs b/src/IronyModManager.Services.Common/IModPatchCollectionService.cs index 51fe43e7..d3fb0d16 100644 --- a/src/IronyModManager.Services.Common/IModPatchCollectionService.cs +++ b/src/IronyModManager.Services.Common/IModPatchCollectionService.cs @@ -1,19 +1,20 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Services.Common // Author : Mario // Created : 05-26-2020 // // Last Modified By : Mario -// Last Modified On : 06-28-2023 +// Last Modified On : 07-16-2023 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using IronyModManager.Models.Common; using IronyModManager.Parser.Common.Parsers.Models; @@ -21,7 +22,6 @@ namespace IronyModManager.Services.Common { - /// /// Interface IModPatchCollectionService /// Implements the @@ -117,8 +117,9 @@ public interface IModPatchCollectionService : IBaseService /// The mods. /// Name of the collection. /// The mode. + /// The allowed game languages. /// Task<IIndexedDefinitions>. - Task GetModObjectsAsync(IGame game, IEnumerable mods, string collectionName, PatchStateMode mode); + Task GetModObjectsAsync(IGame game, IEnumerable mods, string collectionName, PatchStateMode mode, IReadOnlyCollection allowedGameLanguages); /// /// Gets the patch state mode asynchronous. diff --git a/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs b/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs index 407bac9c..618b3b7c 100644 --- a/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs +++ b/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs @@ -73,6 +73,20 @@ public void Should_return_valid_selection() result.FirstOrDefault(p => p.IsSelected)!.Type.Should().Be("l_english"); } + /// + /// Defines the test method Should_return_only_selected. + /// + [Fact] + public void Should_return_only_selected() + { + var preferencesService = new Mock(); + SetupMocks(preferencesService, "l_english"); + var service = new GameLanguageService(new Mock().Object, null, preferencesService.Object); + var result = service.GetSelected(); + result.Count().Should().Be(1); + result.FirstOrDefault(p => p.IsSelected)!.Type.Should().Be("l_english"); + } + /// /// Shoulds a save selection. /// diff --git a/src/IronyModManager.Services.Tests/ModPatchCollectionServiceTests.cs b/src/IronyModManager.Services.Tests/ModPatchCollectionServiceTests.cs index c2038b72..709bafeb 100644 --- a/src/IronyModManager.Services.Tests/ModPatchCollectionServiceTests.cs +++ b/src/IronyModManager.Services.Tests/ModPatchCollectionServiceTests.cs @@ -4,7 +4,7 @@ // Created : 05-26-2020 // // Last Modified By : Mario -// Last Modified On : 06-28-2023 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario @@ -20,13 +20,11 @@ using System.Threading.Tasks; using AutoMapper; using FluentAssertions; -using IronyModManager.IO.Common; using IronyModManager.IO.Common.Mods; using IronyModManager.IO.Common.Readers; using IronyModManager.IO.Mods.Models; using IronyModManager.Models; using IronyModManager.Models.Common; -using IronyModManager.Parser; using IronyModManager.Parser.Common; using IronyModManager.Parser.Common.Args; using IronyModManager.Parser.Common.Mod; @@ -67,15 +65,18 @@ public class ModPatchCollectionServiceTests /// The mod patch exporter. /// The definition information providers. /// The validate parser. + /// The parametrized parser. /// ModService. private static ModPatchCollectionService GetService(Mock storageProvider, Mock modParser, Mock parserManager, Mock reader, Mock mapper, Mock modWriter, - Mock gameService, Mock modPatchExporter, IEnumerable definitionInfoProviders = null, Mock validateParser = null, Mock parametrizedParser = null) + Mock gameService, Mock modPatchExporter, IEnumerable definitionInfoProviders = null, Mock validateParser = null, + Mock parametrizedParser = null) { var messageBus = new Mock(); messageBus.Setup(p => p.PublishAsync(It.IsAny())); messageBus.Setup(p => p.Publish(It.IsAny())); - return new ModPatchCollectionService(new Cache(), messageBus.Object, parserManager.Object, definitionInfoProviders, modPatchExporter.Object, reader.Object, modWriter.Object, modParser.Object, gameService.Object, storageProvider.Object, mapper.Object, validateParser?.Object, parametrizedParser?.Object); + return new ModPatchCollectionService(new Cache(), messageBus.Object, parserManager.Object, definitionInfoProviders, modPatchExporter.Object, reader.Object, modWriter.Object, modParser.Object, gameService.Object, + storageProvider.Object, mapper.Object, validateParser?.Object, parametrizedParser?.Object); } /// @@ -86,42 +87,30 @@ private static ModPatchCollectionService GetService(Mock stora /// The mod parser. private static void SetupMockCase(Mock reader, Mock parserManager, Mock modParser) { - var fileInfos = new List() + var fileInfos = new List { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "fake1.txt", - IsBinary = false - }, - new FileInfo() - { - Content = new List() { "2" } , - FileName = "fake2.txt", - IsBinary = false - } + new FileInfo { Content = new List { "1" }, FileName = "fake1.txt", IsBinary = false }, new FileInfo { Content = new List { "2" }, FileName = "fake2.txt", IsBinary = false } }; reader.Setup(s => s.Read(It.IsAny(), It.IsAny>(), It.IsAny())).Returns(fileInfos); modParser.Setup(s => s.Parse(It.IsAny>(), It.IsAny())).Returns((IEnumerable values, DescriptorModType t) => { - return new ModObject() - { - FileName = values.First(), - Name = values.First() - }; + return new ModObject { FileName = values.First(), Name = values.First() }; }); parserManager.Setup(s => s.Parse(It.IsAny())).Returns((ParserManagerArgs args) => { - return new List() { new Definition() + return new List { - Code = args.File, - File = args.File, - ContentSHA = args.File, - Id = args.File, - Type = args.ModName - } }; + new Definition + { + Code = args.File, + File = args.File, + ContentSHA = args.File, + Id = args.File, + Type = args.ModName + } + }; }); } @@ -141,13 +130,13 @@ public async Task Should_not_return_any_mod_objects_when_no_game_or_mods() var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.GetModObjectsAsync(null, new List(), string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced); + var result = await service.GetModObjectsAsync(null, new List(), string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); result.Should().BeNull(); - result = await service.GetModObjectsAsync(new Game(), new List(), string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced); + result = await service.GetModObjectsAsync(new Game(), new List(), string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); result.Should().BeNull(); - result = await service.GetModObjectsAsync(new Game(), null, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced); + result = await service.GetModObjectsAsync(new Game(), null, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); result.Should().BeNull(); } @@ -174,15 +163,9 @@ public async Task Should_return_mod_objects_when_using_fully_qualified_path() SetupMockCase(reader, parserManager, modParser); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); - var result = await service.GetModObjectsAsync(new Game() { UserDirectory = "c:\\fake", GameFolders = new List() }, new List() - { - new Mod() - { - FileName = Assembly.GetExecutingAssembly().Location, - Name = "fake" - } - }, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); + var result = await service.GetModObjectsAsync(new Game { UserDirectory = "c:\\fake", GameFolders = new List() }, new List { new Mod { FileName = Assembly.GetExecutingAssembly().Location, Name = "fake" } }, + string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); (await result.GetAllAsync()).Count().Should().Be(2); var ordered = (await result.GetAllAsync()).OrderBy(p => p.Id); ordered.First().Id.Should().Be("fake1.txt"); @@ -212,15 +195,9 @@ public async Task Should_return_mod_objects_when_using_user_directory() SetupMockCase(reader, parserManager, modParser); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); - var result = await service.GetModObjectsAsync(new Game() { UserDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), WorkshopDirectory = new List(), GameFolders = new List() { "fake1" } }, new List() - { - new Mod() - { - FileName = Path.GetFileName(Assembly.GetExecutingAssembly().Location), - Name = "fake" - } - }, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); + var result = await service.GetModObjectsAsync(new Game { UserDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), WorkshopDirectory = new List(), GameFolders = new List { "fake1" } }, + new List { new Mod { FileName = Path.GetFileName(Assembly.GetExecutingAssembly().Location), Name = "fake" } }, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); (await result.GetAllAsync()).Count().Should().Be(2); var ordered = (await result.GetAllAsync()).OrderBy(p => p.Id); ordered.First().Id.Should().Be("fake1.txt"); @@ -250,15 +227,9 @@ public async Task Should_return_mod_objects_when_using_workshop_directory() SetupMockCase(reader, parserManager, modParser); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); - var result = await service.GetModObjectsAsync(new Game() { WorkshopDirectory = new List() { Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) }, UserDirectory = "fake1", GameFolders = new List() }, new List() - { - new Mod() - { - FileName = Path.GetFileName(Assembly.GetExecutingAssembly().Location), - Name = "fake" - } - }, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); + var result = await service.GetModObjectsAsync(new Game { WorkshopDirectory = new List { Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) }, UserDirectory = "fake1", GameFolders = new List() }, + new List { new Mod { FileName = Path.GetFileName(Assembly.GetExecutingAssembly().Location), Name = "fake" } }, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); (await result.GetAllAsync()).Count().Should().Be(2); var ordered = (await result.GetAllAsync()).OrderBy(p => p.Id); ordered.First().Id.Should().Be("fake1.txt"); @@ -286,18 +257,18 @@ public async Task Should_find_filename_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -335,18 +306,18 @@ public async Task Should_find_orphan_filename_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -355,7 +326,7 @@ public async Task Should_find_orphan_filename_conflicts() ModName = "test2", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -367,7 +338,7 @@ public async Task Should_find_orphan_filename_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List() { "test2", "test1" }, IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List { "test2", "test1" }, IronyModManager.Models.Common.PatchStateMode.Default); (await result.Conflicts.GetAllAsync()).Count().Should().Be(4); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(1); (await result.Conflicts.GetAllAsync()).All(p => p.ModName == "test1" || p.ModName == "test2").Should().BeTrue(); @@ -375,7 +346,6 @@ public async Task Should_find_orphan_filename_conflicts() } - /// /// Defines the test method Should_ignore_orphan_filename_conflicts. /// @@ -396,18 +366,18 @@ public async Task Should_not_ignore_orphan_localisation_filename_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "localisation\\1.yml", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "localisation\\1.yml", Code = "b", @@ -416,7 +386,7 @@ public async Task Should_not_ignore_orphan_localisation_filename_conflicts() ModName = "test2", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "localisation\\1.yml", Code = "b", @@ -428,7 +398,7 @@ public async Task Should_not_ignore_orphan_localisation_filename_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List() { "test2", "test1" }, IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List { "test2", "test1" }, IronyModManager.Models.Common.PatchStateMode.Default); (await result.Conflicts.GetAllAsync()).Count().Should().Be(4); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(1); (await result.Conflicts.GetAllAsync()).All(p => p.ModName == "test1" || p.ModName == "test2").Should().BeTrue(); @@ -455,18 +425,18 @@ public async Task Should_find_definition_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", @@ -504,18 +474,18 @@ public async Task Should_not_find_override_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -524,7 +494,7 @@ public async Task Should_not_find_override_conflicts() ModName = "test2", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -532,7 +502,7 @@ public async Task Should_not_find_override_conflicts() Id = "a", ModName = "test3", ValueType = ValueType.Object, - Dependencies = new List() { "test1", "test2" } + Dependencies = new List { "test1", "test2" } } }; var indexed = new IndexedDefinitions(); @@ -562,18 +532,18 @@ public async Task Should_not_find_dependency_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -582,7 +552,7 @@ public async Task Should_not_find_dependency_conflicts() ModName = "test2", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -590,7 +560,7 @@ public async Task Should_not_find_dependency_conflicts() Id = "a", ModName = "test3", ValueType = ValueType.Object, - Dependencies = new List() { "test1" } + Dependencies = new List { "test1" } } }; var indexed = new IndexedDefinitions(); @@ -620,18 +590,18 @@ public async Task Should_find_dependency_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -640,7 +610,7 @@ public async Task Should_find_dependency_conflicts() ModName = "test2", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -648,7 +618,7 @@ public async Task Should_find_dependency_conflicts() Id = "a", ModName = "test3", ValueType = ValueType.Object, - Dependencies = new List() { "test2" } + Dependencies = new List { "test2" } } }; var indexed = new IndexedDefinitions(); @@ -679,18 +649,18 @@ public async Task Should_find_multiple_dependency_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -699,7 +669,7 @@ public async Task Should_find_multiple_dependency_conflicts() ModName = "test2", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -707,9 +677,9 @@ public async Task Should_find_multiple_dependency_conflicts() Id = "a", ModName = "test3", ValueType = ValueType.Object, - Dependencies = new List() { "test1", "test2" } + Dependencies = new List { "test1", "test2" } }, - new Definition() + new Definition { File = "events\\1.txt", Code = "f", @@ -747,27 +717,27 @@ public async Task Should_not_include_variable_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a1", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a2", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Variable }, - new Definition() + new Definition { File = "events\\2.txt", Code = "a", @@ -776,15 +746,15 @@ public async Task Should_not_include_variable_conflicts() ModName = "test2", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", Id = "a2", - Type= "events", + Type = "events", ModName = "test2", ValueType = ValueType.Variable - }, + } }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); @@ -813,18 +783,18 @@ public async Task Should_return_all_conflicts() SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\1.txt", Code = "b", @@ -890,20 +860,13 @@ public async Task Should_not_apply_mod_patch_when_nothing_to_merge() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_not_apply_mod_patch_when_nothing_to_merge", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\fake" }, - CustomModDirectory = string.Empty + Type = "Should_not_apply_mod_patch_when_nothing_to_merge", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); SetupMockCase(reader, parserManager, modParser); @@ -911,13 +874,8 @@ public async Task Should_not_apply_mod_patch_when_nothing_to_merge() var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = indexed, - Conflicts = indexed, - ResolvedConflicts = indexed - }; - var result = await service.ApplyModPatchAsync(c, new Definition() { ModName = "test", ValueType = ValueType.Object }, "colname"); + var c = new ConflictResult { AllConflicts = indexed, Conflicts = indexed, ResolvedConflicts = indexed }; + var result = await service.ApplyModPatchAsync(c, new Definition { ModName = "test", ValueType = ValueType.Object }, "colname"); result.Should().BeFalse(); } @@ -938,31 +896,15 @@ public async Task Should_return_true_when_applying_patches() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_return_true_when_applying_patches", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\fake" }, - CustomModDirectory = string.Empty + Type = "Should_return_true_when_applying_patches", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Should_return_true_when_applying_patches" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Should_return_true_when_applying_patches" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; @@ -977,6 +919,7 @@ public async Task Should_return_true_when_applying_patches() { return true; } + return false; }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => @@ -985,18 +928,18 @@ public async Task Should_return_true_when_applying_patches() }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", @@ -1004,7 +947,7 @@ public async Task Should_return_true_when_applying_patches() Id = "a", ModName = "test2", ValueType = ValueType.Object - }, + } }; var all = new IndexedDefinitions(); await all.InitMapAsync(definitions); @@ -1015,14 +958,8 @@ public async Task Should_return_true_when_applying_patches() var resolved = new IndexedDefinitions(); await resolved.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = all, - Conflicts = all, - ResolvedConflicts = resolved, - OverwrittenConflicts = overwritten - }; - var result = await service.ApplyModPatchAsync(c, new Definition() { ModName = "1" }, "colname"); + var c = new ConflictResult { AllConflicts = all, Conflicts = all, ResolvedConflicts = resolved, OverwrittenConflicts = overwritten }; + var result = await service.ApplyModPatchAsync(c, new Definition { ModName = "1" }, "colname"); result.Should().BeTrue(); } @@ -1042,23 +979,16 @@ public async Task Should_not_create_patch_definition() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_not_create_patch_definition", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_not_create_patch_definition", UserDirectory = "C:\\Users\\Fake" }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IDefinition o) => { - return new Definition() - { - File = o.File - }; + return new Definition { File = o.File }; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.CreatePatchDefinitionAsync(null, "fake"); result.Should().BeNull(); - result = await service.CreatePatchDefinitionAsync(new Definition() { File = "1" }, null); + result = await service.CreatePatchDefinitionAsync(new Definition { File = "1" }, null); result.Should().BeNull(); } @@ -1081,14 +1011,11 @@ public async Task Should_not_create_patch_definition_when_no_selected_game() gameService.Setup(p => p.GetSelected()).Returns((IGame)null); mapper.Setup(s => s.Map(It.IsAny())).Returns((IDefinition o) => { - return new Definition() - { - File = o.File - }; + return new Definition { File = o.File }; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.CreatePatchDefinitionAsync(new Definition() { File = "1" }, "fake"); + var result = await service.CreatePatchDefinitionAsync(new Definition { File = "1" }, "fake"); result.Should().BeNull(); } @@ -1108,20 +1035,13 @@ public async Task Should_create_patch_definition() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_create_patch_definition", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_create_patch_definition", UserDirectory = "C:\\Users\\Fake" }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IDefinition o) => { - return new Definition() - { - File = o.File - }; + return new Definition { File = o.File }; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.CreatePatchDefinitionAsync(new Definition() { File = "1" }, "fake"); + var result = await service.CreatePatchDefinitionAsync(new Definition { File = "1" }, "fake"); result.Should().NotBeNull(); result.ModName.Should().Be("IronyModManager_fake"); } @@ -1142,32 +1062,21 @@ public async Task Should_create_patch_definition_and_overwrite_code_from_history var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_create_patch_definition_and_overwrite_code_from_history", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_create_patch_definition_and_overwrite_code_from_history", UserDirectory = "C:\\Users\\Fake" }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IDefinition o) => { - return new Definition() - { - File = o.File, - Id = o.Id, - Type = o.Type - }; + return new Definition { File = o.File, Id = o.Id, Type = o.Type }; }); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() + var res = new PatchState { - Conflicts = new List(), - ResolvedConflicts = new List(), - ConflictHistory = new List() { new Definition() { File = "1", Id = "test", Type = "events", Code = "ab" } } + Conflicts = new List(), ResolvedConflicts = new List(), ConflictHistory = new List { new Definition { File = "1", Id = "test", Type = "events", Code = "ab" } } }; return res; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.CreatePatchDefinitionAsync(new Definition() { File = "1", Id = "test", Type = "events", Code = "a" }, "fake"); + var result = await service.CreatePatchDefinitionAsync(new Definition { File = "1", Id = "test", Type = "events", Code = "a" }, "fake"); result.Should().NotBeNull(); result.ModName.Should().Be("IronyModManager_fake"); result.Code.Should().Be("ab"); @@ -1193,12 +1102,7 @@ public async Task Should_not_initialize_patch_state_when_no_selected_game() var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = indexed, - Conflicts = indexed, - ResolvedConflicts = indexed - }; + var c = new ConflictResult { AllConflicts = indexed, Conflicts = indexed, ResolvedConflicts = indexed }; var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.InitializePatchStateAsync(c, "fake"); result.Should().BeNull(); @@ -1224,12 +1128,7 @@ public async Task Should_not_initialize_patch_state() var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = indexed, - Conflicts = indexed, - ResolvedConflicts = indexed - }; + var c = new ConflictResult { AllConflicts = indexed, Conflicts = indexed, ResolvedConflicts = indexed }; var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.InitializePatchStateAsync(c, null); result.Should().BeNull(); @@ -1258,33 +1157,23 @@ public async Task Should_initialize_patch_state() { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dummy"); }); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_sync_patch_state", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_sync_patch_state", UserDirectory = "C:\\Users\\Fake" }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IConflictResult o) => { - return new ConflictResult() - { - AllConflicts = o.Conflicts, - Conflicts = o.Conflicts, - ResolvedConflicts = o.ResolvedConflicts, - OverwrittenConflicts = o.OverwrittenConflicts - }; + return new ConflictResult { AllConflicts = o.Conflicts, Conflicts = o.Conflicts, ResolvedConflicts = o.ResolvedConflicts, OverwrittenConflicts = o.OverwrittenConflicts }; }); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", @@ -1292,16 +1181,11 @@ public async Task Should_initialize_patch_state() Id = "a", ModName = "test2", ValueType = ValueType.Object - }, + } }; modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() - { - Conflicts = definitions, - ResolvedConflicts = definitions, - OverwrittenConflicts = new List() - }; + var res = new PatchState { Conflicts = definitions, ResolvedConflicts = definitions, OverwrittenConflicts = new List() }; return res; }); modWriter.Setup(p => p.PurgeModDirectoryAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); @@ -1318,12 +1202,7 @@ public async Task Should_initialize_patch_state() var resolved = new IndexedDefinitions(); await resolved.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = all, - Conflicts = conflicts, - OverwrittenConflicts = overwritten - }; + var c = new ConflictResult { AllConflicts = all, Conflicts = conflicts, OverwrittenConflicts = overwritten }; var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.InitializePatchStateAsync(c, "fake"); (await result.Conflicts.GetAllAsync()).Count().Should().Be(2); @@ -1351,33 +1230,23 @@ public async Task Should_initialize_patch_state_and_remove_different() { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dummy"); }); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_sync_patch_state_and_remove_different", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_sync_patch_state_and_remove_different", UserDirectory = "C:\\Users\\Fake" }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IConflictResult o) => { - return new ConflictResult() - { - AllConflicts = o.Conflicts, - Conflicts = o.Conflicts, - ResolvedConflicts = o.ResolvedConflicts, - OverwrittenConflicts = o.OverwrittenConflicts - }; + return new ConflictResult { AllConflicts = o.Conflicts, Conflicts = o.Conflicts, ResolvedConflicts = o.ResolvedConflicts, OverwrittenConflicts = o.OverwrittenConflicts }; }); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", @@ -1385,20 +1254,20 @@ public async Task Should_initialize_patch_state_and_remove_different() Id = "a", ModName = "test2", ValueType = ValueType.Object - }, + } }; - var definitions2 = new List() + var definitions2 = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "ab", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", @@ -1410,12 +1279,7 @@ public async Task Should_initialize_patch_state_and_remove_different() }; modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() - { - Conflicts = definitions2, - ResolvedConflicts = definitions2, - OverwrittenConflicts = new List() - }; + var res = new PatchState { Conflicts = definitions2, ResolvedConflicts = definitions2, OverwrittenConflicts = new List() }; return res; }); modWriter.Setup(p => p.PurgeModDirectoryAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); @@ -1432,12 +1296,7 @@ public async Task Should_initialize_patch_state_and_remove_different() var resolved = new IndexedDefinitions(); await resolved.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = all, - Conflicts = conflicts, - OverwrittenConflicts = overwritten - }; + var c = new ConflictResult { AllConflicts = all, Conflicts = conflicts, OverwrittenConflicts = overwritten }; var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.InitializePatchStateAsync(c, "fake"); (await result.Conflicts.GetAllAsync()).Count().Should().Be(2); @@ -1460,10 +1319,7 @@ public void Should_not_be_a_patch_mod() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = service.IsPatchMod(new Mod() - { - Name = "test" - }); + var result = service.IsPatchMod(new Mod { Name = "test" }); result.Should().BeFalse(); result = service.IsPatchMod(default(Mod)); result.Should().BeFalse(); @@ -1484,10 +1340,7 @@ public void Should_be_a_patch_mod() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = service.IsPatchMod(new Mod() - { - Name = "IronyModManager_fake_collection" - }); + var result = service.IsPatchMod(new Mod { Name = "IronyModManager_fake_collection" }); result.Should().BeTrue(); } @@ -1533,12 +1386,7 @@ public async Task Should_clean_collection_patch() var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_clean_collection_patch", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\workshop" } - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_clean_collection_patch", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\workshop" } }); modWriter.Setup(p => p.DeleteDescriptorAsync(It.IsAny())).Returns(Task.FromResult(true)); modWriter.Setup(p => p.PurgeModDirectoryAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); @@ -1596,20 +1444,13 @@ public async Task Should_not_ignore_mod_patch_when_nothing_to_merge() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_not_ignore_mod_patch_when_nothing_to_merge", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\fake" }, - CustomModDirectory = string.Empty + Type = "Should_not_ignore_mod_patch_when_nothing_to_merge", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); SetupMockCase(reader, parserManager, modParser); @@ -1617,14 +1458,8 @@ public async Task Should_not_ignore_mod_patch_when_nothing_to_merge() var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = indexed, - Conflicts = indexed, - ResolvedConflicts = indexed, - IgnoredConflicts = indexed - }; - var result = await service.IgnoreModPatchAsync(c, new Definition() { ModName = "test", ValueType = ValueType.Object }, "colname"); + var c = new ConflictResult { AllConflicts = indexed, Conflicts = indexed, ResolvedConflicts = indexed, IgnoredConflicts = indexed }; + var result = await service.IgnoreModPatchAsync(c, new Definition { ModName = "test", ValueType = ValueType.Object }, "colname"); result.Should().BeFalse(); } @@ -1645,20 +1480,13 @@ public async Task Should_return_true_when_ignoring_patches() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_return_true_when_ignoring_patches", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\fake" }, - CustomModDirectory = string.Empty + Type = "Should_return_true_when_ignoring_patches", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); modWriter.Setup(p => p.CreateModDirectoryAsync(It.IsAny())).Returns(Task.FromResult(true)); modWriter.Setup(p => p.WriteDescriptorAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); @@ -1670,6 +1498,7 @@ public async Task Should_return_true_when_ignoring_patches() { return true; } + return false; }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => @@ -1678,18 +1507,18 @@ public async Task Should_return_true_when_ignoring_patches() }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", @@ -1697,7 +1526,7 @@ public async Task Should_return_true_when_ignoring_patches() Id = "a", ModName = "test2", ValueType = ValueType.Object - }, + } }; var all = new IndexedDefinitions(); await all.InitMapAsync(definitions); @@ -1709,14 +1538,8 @@ public async Task Should_return_true_when_ignoring_patches() await ignored.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = all, - Conflicts = all, - ResolvedConflicts = resolved, - IgnoredConflicts = ignored - }; - var result = await service.IgnoreModPatchAsync(c, new Definition() { ModName = "1" }, "colname"); + var c = new ConflictResult { AllConflicts = all, Conflicts = all, ResolvedConflicts = resolved, IgnoredConflicts = ignored }; + var result = await service.IgnoreModPatchAsync(c, new Definition { ModName = "1" }, "colname"); result.Should().BeTrue(); } @@ -1742,7 +1565,7 @@ public void EvalDefinitionPriority_should_return_null() infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); var result = service.EvalDefinitionPriority(null); result.Definition.Should().BeNull(); @@ -1765,19 +1588,15 @@ public void EvalDefinitionPriority_should_return_first_object() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_first_object", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_first_object", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); var def = new Definition(); - var result = service.EvalDefinitionPriority(new List() { def }); + var result = service.EvalDefinitionPriority(new List { def }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -1799,20 +1618,16 @@ public void EvalDefinitionPriority_should_return_first_object_when_no_info_provi var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_first_object_when_no_info_provider", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_first_object_when_no_info_provider", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(false); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); var def = new Definition(); var def2 = new Definition(); - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.NoProvider); } @@ -1834,19 +1649,15 @@ public void EvalDefinitionPriority_should_return_first_game_object() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_first_game_object", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_first_game_object", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def }); + var def = new Definition { IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -1868,20 +1679,16 @@ public void EvalDefinitionPriority_should_return_first_only_valid_object() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_first_only_valid_object", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_first_only_valid_object", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { }; - var def2 = new Definition() { ExistsInLastFile = false }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition(); + var def2 = new Definition { ExistsInLastFile = false }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.ModOrder); } @@ -1903,20 +1710,16 @@ public void EvalDefinitionPriority_should_return_last_object() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_last_object", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_last_object", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test.txt", ModName = "1" }; - var def2 = new Definition() { File = "test.txt", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = "test.txt", ModName = "1" }; + var def2 = new Definition { File = "test.txt", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.ModOrder); } @@ -1938,20 +1741,16 @@ public void EvalDefinitionPriority_should_return_last_object_as_cutom_patch() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_last_object_as_cutom_patch", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_last_object_as_cutom_patch", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test.txt", ModName = "1", IsCustomPatch = true }; - var def2 = new Definition() { File = "test.txt", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = "test.txt", ModName = "1", IsCustomPatch = true }; + var def2 = new Definition { File = "test.txt", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.ModOrder); } @@ -1973,21 +1772,17 @@ public void EvalDefinitionPriority_should_return_last_non_game_object() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_last_non_game_object", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_last_non_game_object", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test.txt", ModName = "1" }; - var def2 = new Definition() { File = "test.txt", ModName = "2" }; - var def3 = new Definition() { File = "test.txt", ModName = "Game", IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def3, def, def2 }); + var def = new Definition { File = "test.txt", ModName = "1" }; + var def2 = new Definition { File = "test.txt", ModName = "2" }; + var def3 = new Definition { File = "test.txt", ModName = "Game", IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def3, def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.ModOrder); } @@ -2009,20 +1804,16 @@ public void EvalDefinitionPriority_should_return_first_object_due_to_FIOS() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_first_object_due_to_FIOS", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_first_object_due_to_FIOS", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1" }; - var def2 = new Definition() { File = "test2.txt", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1" }; + var def2 = new Definition { File = "test2.txt", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.FIOS); } @@ -2044,21 +1835,17 @@ public void EvalDefinitionPriority_should_return_first_object_due_to_FIOS_and_ig var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_first_object_due_to_FIOS_and_ignore_game_object", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_first_object_due_to_FIOS_and_ignore_game_object", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1" }; - var def2 = new Definition() { File = "test2.txt", ModName = "2" }; - var def3 = new Definition() { File = "test1.txt", ModName = "Game", IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def3, def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1" }; + var def2 = new Definition { File = "test2.txt", ModName = "2" }; + var def3 = new Definition { File = "test1.txt", ModName = "Game", IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def3, def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.FIOS); } @@ -2080,20 +1867,16 @@ public void EvalDefinitionPriority_should_return_object_due_to_Override() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_object_due_to_Override", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_object_due_to_Override", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1", Dependencies = new List() { "2" } }; - var def2 = new Definition() { File = "test1.txt", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1", Dependencies = new List { "2" } }; + var def2 = new Definition { File = "test1.txt", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.ModOverride); } @@ -2115,20 +1898,16 @@ public void EvalDefinitionPriority_should_return_object_due_to_game_object_filte var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_object_due_to_game_object_filtering", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_object_due_to_game_object_filtering", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1", IsFromGame = true }; - var def2 = new Definition() { File = "test1.txt", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1", IsFromGame = true }; + var def2 = new Definition { File = "test1.txt", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.ModOrder); } @@ -2150,21 +1929,17 @@ public void EvalDefinitionPriority_should_return_object_due_to_Override_and_igno var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_object_due_to_Override_and_ignore_game_object", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_object_due_to_Override_and_ignore_game_object", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1", Dependencies = new List() { "2" } }; - var def2 = new Definition() { File = "test1.txt", ModName = "2" }; - var def3 = new Definition() { File = "test1.txt", ModName = "Game", IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def3, def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1", Dependencies = new List { "2" } }; + var def2 = new Definition { File = "test1.txt", ModName = "2" }; + var def3 = new Definition { File = "test1.txt", ModName = "Game", IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def3, def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.ModOverride); } @@ -2186,20 +1961,16 @@ public void EvalDefinitionPriority_should_return_object_due_to_override_but_prio var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_object_due_to_override_but_priority_should_be_fios", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_object_due_to_override_but_priority_should_be_fios", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1", Dependencies = new List() { "2" } }; - var def2 = new Definition() { File = "test2.txt", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1", Dependencies = new List { "2" } }; + var def2 = new Definition { File = "test2.txt", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.FIOS); } @@ -2221,21 +1992,17 @@ public void EvalDefinitionPriority_should_return_object_due_to_override_and_igno var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_object_due_to_override_and_ignore_non_game_object_and_priority_should_be_fios", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_object_due_to_override_and_ignore_non_game_object_and_priority_should_be_fios", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(true); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1", Dependencies = new List() { "2" } }; - var def2 = new Definition() { File = "test2.txt", ModName = "2" }; - var def3 = new Definition() { File = "test2.txt", ModName = "Game", IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def3, def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1", Dependencies = new List { "2" } }; + var def2 = new Definition { File = "test2.txt", ModName = "2" }; + var def3 = new Definition { File = "test2.txt", ModName = "Game", IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def3, def, def2 }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.FIOS); } @@ -2257,20 +2024,16 @@ public void EvalDefinitionPriority_should_return_first_object_due_to_LIOS() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_first_object_due_to_LIOS", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_first_object_due_to_LIOS", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1" }; - var def2 = new Definition() { File = "test2.txt", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1" }; + var def2 = new Definition { File = "test2.txt", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.LIOS); } @@ -2292,21 +2055,17 @@ public void EvalDefinitionPriority_should_return_first_object_due_to_LIOS_and_ig var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_first_object_due_to_LIOS_and_ignore_non_game_object", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_first_object_due_to_LIOS_and_ignore_non_game_object", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = "test1.txt", ModName = "1" }; - var def2 = new Definition() { File = "test2.txt", ModName = "2" }; - var def3 = new Definition() { File = "test2.txt", ModName = "Game", IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def3, def, def2 }); + var def = new Definition { File = "test1.txt", ModName = "1" }; + var def2 = new Definition { File = "test2.txt", ModName = "2" }; + var def3 = new Definition { File = "test2.txt", ModName = "Game", IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def3, def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.LIOS); } @@ -2328,20 +2087,16 @@ public void EvalDefinitionPriority_should_return_localization_override() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_override", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_override", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\replace\test.yml", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\replace\test.yml", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2363,20 +2118,16 @@ public void EvalDefinitionPriority_should_return_localization() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\test.yml", ModName = "2" }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\test.yml", ModName = "2" }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2398,20 +2149,16 @@ public void EvalDefinitionPriority_should_return_localization_override_with_cust var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_override_with_custom_priority", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_override_with_custom_priority", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\replace\test.yml", ModName = "2", CustomPriorityOrder = 5 }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\replace\test.yml", ModName = "2", CustomPriorityOrder = 5 }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2433,20 +2180,16 @@ public void EvalDefinitionPriority_should_return_localization_with_custom_priori var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_with_custom_priority", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_with_custom_priority", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\test.yml", ModName = "2", CustomPriorityOrder = 5 }; - var result = service.EvalDefinitionPriority(new List() { def, def2 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\test.yml", ModName = "2", CustomPriorityOrder = 5 }; + var result = service.EvalDefinitionPriority(new List { def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2468,21 +2211,17 @@ public void EvalDefinitionPriority_should_return_localization_override_and_filen var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_override_and_filename_priority", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_override_and_filename_priority", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\replace\test.yml", ModName = "2" }; - var def3 = new Definition() { File = @"localisation\replace\test2.yml", ModName = "3" }; - var result = service.EvalDefinitionPriority(new List() { def, def2, def3 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\replace\test.yml", ModName = "2" }; + var def3 = new Definition { File = @"localisation\replace\test2.yml", ModName = "3" }; + var result = service.EvalDefinitionPriority(new List { def, def2, def3 }); result.Definition.Should().Be(def3); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2504,21 +2243,17 @@ public void EvalDefinitionPriority_should_return_localization_and_filename_prior var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_and_filename_priority", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_and_filename_priority", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\test.yml", ModName = "2" }; - var def3 = new Definition() { File = @"localisation\test2.yml", ModName = "3" }; - var result = service.EvalDefinitionPriority(new List() { def, def2, def3 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\test.yml", ModName = "2" }; + var def3 = new Definition { File = @"localisation\test2.yml", ModName = "3" }; + var result = service.EvalDefinitionPriority(new List { def, def2, def3 }); result.Definition.Should().Be(def3); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2540,21 +2275,17 @@ public void EvalDefinitionPriority_should_return_localization_override_with_mult var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_override_with_multiple_custom_priority", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_override_with_multiple_custom_priority", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\replace\test.yml", ModName = "2", CustomPriorityOrder = 1000 }; - var def3 = new Definition() { File = @"localisation\replace\test2.yml", ModName = "3", CustomPriorityOrder = 100 }; - var result = service.EvalDefinitionPriority(new List() { def, def2, def3 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\replace\test.yml", ModName = "2", CustomPriorityOrder = 1000 }; + var def3 = new Definition { File = @"localisation\replace\test2.yml", ModName = "3", CustomPriorityOrder = 100 }; + var result = service.EvalDefinitionPriority(new List { def, def2, def3 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2576,21 +2307,17 @@ public void EvalDefinitionPriority_should_return_localization_with_multiple_cust var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_with_multiple_custom_priority", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_with_multiple_custom_priority", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\test.yml", ModName = "2", CustomPriorityOrder = 1000 }; - var def3 = new Definition() { File = @"localisation\test2.yml", ModName = "3", CustomPriorityOrder = 100 }; - var result = service.EvalDefinitionPriority(new List() { def, def2, def3 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\test.yml", ModName = "2", CustomPriorityOrder = 1000 }; + var def3 = new Definition { File = @"localisation\test2.yml", ModName = "3", CustomPriorityOrder = 100 }; + var result = service.EvalDefinitionPriority(new List { def, def2, def3 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2612,21 +2339,17 @@ public void EvalDefinitionPriority_should_return_localization_override_with_cust var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_override_with_custom_priority_and_filename_priority", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_override_with_custom_priority_and_filename_priority", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\replace\test.yml", ModName = "2", CustomPriorityOrder = 1000 }; - var def3 = new Definition() { File = @"localisation\replace\test2.yml", ModName = "3", CustomPriorityOrder = 1000 }; - var result = service.EvalDefinitionPriority(new List() { def, def2, def3 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\replace\test.yml", ModName = "2", CustomPriorityOrder = 1000 }; + var def3 = new Definition { File = @"localisation\replace\test2.yml", ModName = "3", CustomPriorityOrder = 1000 }; + var result = service.EvalDefinitionPriority(new List { def, def2, def3 }); result.Definition.Should().Be(def3); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2648,21 +2371,17 @@ public void EvalDefinitionPriority_should_return_localization_with_custom_priori var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_with_custom_priority_and_filename_priority", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_with_custom_priority_and_filename_priority", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\replace\test.yml", ModName = "2", CustomPriorityOrder = 1000 }; - var def3 = new Definition() { File = @"localisation\replace\test2.yml", ModName = "3", CustomPriorityOrder = 1000 }; - var result = service.EvalDefinitionPriority(new List() { def, def2, def3 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\replace\test.yml", ModName = "2", CustomPriorityOrder = 1000 }; + var def3 = new Definition { File = @"localisation\replace\test2.yml", ModName = "3", CustomPriorityOrder = 1000 }; + var result = service.EvalDefinitionPriority(new List { def, def2, def3 }); result.Definition.Should().Be(def3); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2684,21 +2403,17 @@ public void EvalDefinitionPriority_should_return_localization_override_and_ignor var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_override_and_ignore_non_game_definitions", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_override_and_ignore_non_game_definitions", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def2 = new Definition() { File = @"localisation\replace\test2.yml", ModName = "2" }; - var def3 = new Definition() { File = @"localisation\replace\test.yml", ModName = "Game", IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def3, def, def2 }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def2 = new Definition { File = @"localisation\replace\test2.yml", ModName = "2" }; + var def3 = new Definition { File = @"localisation\replace\test.yml", ModName = "Game", IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def3, def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2720,20 +2435,16 @@ public void EvalDefinitionPriority_should_return_localization_and_ignore_non_gam var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_should_return_localization_and_ignore_non_game_definitions", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_should_return_localization_and_ignore_non_game_definitions", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"localisation\test.yml", ModName = "1" }; - var def3 = new Definition() { File = @"localisation\test.yml", ModName = "Game", IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def3, def }); + var def = new Definition { File = @"localisation\test.yml", ModName = "1" }; + var def3 = new Definition { File = @"localisation\test.yml", ModName = "Game", IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def3, def }); result.Definition.Should().Be(def); result.PriorityType.Should().Be(DefinitionPriorityType.None); } @@ -2755,21 +2466,17 @@ public void EvalDefinitionPriority_gfx_replace_directory_should_win() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "EvalDefinitionPriority_gfx_replace_directory_should_win", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "EvalDefinitionPriority_gfx_replace_directory_should_win", UserDirectory = "C:\\Users\\Fake" }); var infoProvider = new Mock(); infoProvider.Setup(p => p.DefinitionUsesFIOSRules(It.IsAny())).Returns(false); infoProvider.Setup(p => p.CanProcess(It.IsAny())).Returns(true); infoProvider.Setup(p => p.IsFullyImplemented).Returns(true); - var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List() { infoProvider.Object }); + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); - var def = new Definition() { File = @"gfx\test.gfx", ModName = "1" }; - var def2 = new Definition() { File = @"gfx\replace\test.gfx", ModName = "2", VirtualPath = "gfx\\test.gfx" }; - var def3 = new Definition() { File = @"gfx\test.gfx", ModName = "Game", IsFromGame = true }; - var result = service.EvalDefinitionPriority(new List() { def3, def, def2 }); + var def = new Definition { File = @"gfx\test.gfx", ModName = "1" }; + var def2 = new Definition { File = @"gfx\replace\test.gfx", ModName = "2", VirtualPath = "gfx\\test.gfx" }; + var def3 = new Definition { File = @"gfx\test.gfx", ModName = "Game", IsFromGame = true }; + var result = service.EvalDefinitionPriority(new List { def3, def, def2 }); result.Definition.Should().Be(def2); result.PriorityType.Should().Be(DefinitionPriorityType.ModOrder); } @@ -2816,32 +2523,16 @@ public async Task SaveIgnoredPathsAsync_should_be_false() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "SaveIgnoredPathsAsync_should_be_false", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\Fake" }, - CustomModDirectory = string.Empty + Type = "SaveIgnoredPathsAsync_should_be_false", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\Fake" }, CustomModDirectory = string.Empty }); modPatchExporter.Setup(p => p.SaveStateAsync(It.IsAny())).Returns(Task.FromResult(false)); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "SaveIgnoredPathsAsync_should_be_false" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "SaveIgnoredPathsAsync_should_be_false" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; @@ -2853,7 +2544,7 @@ public async Task SaveIgnoredPathsAsync_should_be_false() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); var indexed = new IndexedDefinitions(); - var result = await service.SaveIgnoredPathsAsync(new ConflictResult() { AllConflicts = indexed, Conflicts = indexed }, "test"); + var result = await service.SaveIgnoredPathsAsync(new ConflictResult { AllConflicts = indexed, Conflicts = indexed }, "test"); result.Should().BeFalse(); } @@ -2874,32 +2565,16 @@ public async Task SaveIgnoredPathsAsync_should_be_true() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "SaveIgnoredPathsAsync_should_be_true", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\Fake" }, - CustomModDirectory = string.Empty + Type = "SaveIgnoredPathsAsync_should_be_true", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\Fake" }, CustomModDirectory = string.Empty }); modPatchExporter.Setup(p => p.SaveStateAsync(It.IsAny())).Returns(Task.FromResult(true)); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "SaveIgnoredPathsAsync_should_be_false" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "SaveIgnoredPathsAsync_should_be_false" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; @@ -2911,7 +2586,7 @@ public async Task SaveIgnoredPathsAsync_should_be_true() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); var indexed = new IndexedDefinitions(); - var result = await service.SaveIgnoredPathsAsync(new ConflictResult() { AllConflicts = indexed, Conflicts = indexed }, "test"); + var result = await service.SaveIgnoredPathsAsync(new ConflictResult { AllConflicts = indexed, Conflicts = indexed }, "test"); result.Should().BeTrue(); } @@ -2932,32 +2607,16 @@ public async Task SaveIgnoredPathsAsync_should_be_true_when_readonly_mode() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "SaveIgnoredPathsAsync_should_be_false_when_readonly_mode", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\Fake" }, - CustomModDirectory = string.Empty + Type = "SaveIgnoredPathsAsync_should_be_false_when_readonly_mode", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\Fake" }, CustomModDirectory = string.Empty }); modPatchExporter.Setup(p => p.SaveStateAsync(It.IsAny())).Returns(Task.FromResult(true)); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "SaveIgnoredPathsAsync_should_be_false" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "SaveIgnoredPathsAsync_should_be_false" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; @@ -2969,7 +2628,7 @@ public async Task SaveIgnoredPathsAsync_should_be_true_when_readonly_mode() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); var indexed = new IndexedDefinitions(); - var result = await service.SaveIgnoredPathsAsync(new ConflictResult() { AllConflicts = indexed, Conflicts = indexed, Mode = IronyModManager.Models.Common.PatchStateMode.ReadOnly }, "test"); + var result = await service.SaveIgnoredPathsAsync(new ConflictResult { AllConflicts = indexed, Conflicts = indexed, Mode = IronyModManager.Models.Common.PatchStateMode.ReadOnly }, "test"); result.Should().BeTrue(); } @@ -3016,11 +2675,7 @@ public async Task CopyPatchMod_should_be_false() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "CopyPatchMod_should_be_false", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "CopyPatchMod_should_be_false", UserDirectory = "C:\\Users\\Fake" }); modPatchExporter.Setup(p => p.CopyPatchModAsync(It.IsAny())).Returns(Task.FromResult(false)); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); @@ -3045,11 +2700,7 @@ public async Task CopyPatchMod_should_be_true() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "CopyPatchMod_should_be_true", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "CopyPatchMod_should_be_true", UserDirectory = "C:\\Users\\Fake" }); modPatchExporter.Setup(p => p.CopyPatchModAsync(It.IsAny())).Returns(Task.FromResult(true)); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); @@ -3100,11 +2751,7 @@ public async Task RenamePatchMod_should_be_false() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "RenamePatchMod_should_be_false", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "RenamePatchMod_should_be_false", UserDirectory = "C:\\Users\\Fake" }); modPatchExporter.Setup(p => p.RenamePatchModAsync(It.IsAny())).Returns(Task.FromResult(false)); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); @@ -3129,11 +2776,7 @@ public async Task RenamePatchMod_should_be_true() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "RenamePatchMod_should_be_true", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "RenamePatchMod_should_be_true", UserDirectory = "C:\\Users\\Fake" }); modPatchExporter.Setup(p => p.RenamePatchModAsync(It.IsAny())).Returns(Task.FromResult(true)); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); @@ -3158,11 +2801,7 @@ public void ResetCache_should_be_true() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "ResetCache_should_be_true", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "ResetCache_should_be_true", UserDirectory = "C:\\Users\\Fake" }); modPatchExporter.Setup(p => p.ResetCache()); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); @@ -3234,20 +2873,10 @@ public async Task Should_get_patch_state() var modPatchExporter = new Mock(); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() - { - Conflicts = new List(), - ResolvedConflicts = new List(), - OverwrittenConflicts = new List(), - Mode = IO.Common.PatchStateMode.Default - }; + var res = new PatchState { Conflicts = new List(), ResolvedConflicts = new List(), OverwrittenConflicts = new List(), Mode = IO.Common.PatchStateMode.Default }; return res; }); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_get_patch_state", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_get_patch_state", UserDirectory = "C:\\Users\\Fake" }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.GetPatchStateModeAsync("fake"); @@ -3274,11 +2903,7 @@ public async Task Should_get_patch_state_from_mode_text() { return IO.Common.PatchStateMode.Default; }); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_get_patch_state", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_get_patch_state", UserDirectory = "C:\\Users\\Fake" }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.GetPatchStateModeAsync("fake"); @@ -3303,20 +2928,12 @@ public void Should_not_resolve_full_definition_path_when_no_game() var modPatchExporter = new Mock(); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); gameService.Setup(p => p.GetSelected()).Returns((IGame)null); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = service.ResolveFullDefinitionPath(new Definition() - { - File = "events\\test.txt", - ModName = "test" - }); + var result = service.ResolveFullDefinitionPath(new Definition { File = "events\\test.txt", ModName = "test" }); result.Should().Be(string.Empty); } @@ -3338,18 +2955,9 @@ public void Should_not_resolve_full_definition_path_when_definition_null() var modPatchExporter = new Mock(); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; - }); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_not_resolve_full_definition_path_when_definition_null", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\fake" } + return new Mod { FileName = o.FileName, Name = o.Name }; }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_not_resolve_full_definition_path_when_definition_null", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\fake" } }); SetupMockCase(reader, parserManager, modParser); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); @@ -3373,53 +2981,28 @@ public void Should_resolve_full_definition_path() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_resolve_full_definition_path", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" }, + WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); SetupMockCase(reader, parserManager, modParser); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fakemod.mod"}, - Name = "test", - Game = "Should_resolve_full_definition_path" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fakemod.mod" }, Name = "test", Game = "Should_resolve_full_definition_path" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; }); - var fileInfos = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "fakemod.mod", - IsBinary = false - } - }; + var fileInfos = new List { new FileInfo { Content = new List { "1" }, FileName = "fakemod.mod", IsBinary = false } }; reader.Setup(s => s.Read(It.IsAny(), It.IsAny>(), It.IsAny())).Returns(fileInfos); modParser.Setup(s => s.Parse(It.IsAny>(), It.IsAny())).Returns((IEnumerable values, DescriptorModType t) => { - return new ModObject() - { - FileName = "fakemod", - Name = "1" - }; + return new ModObject { FileName = "fakemod", Name = "1" }; }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => { @@ -3427,11 +3010,7 @@ public void Should_resolve_full_definition_path() }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = service.ResolveFullDefinitionPath(new Definition() - { - File = "events\\test.txt", - ModName = "1" - }); + var result = service.ResolveFullDefinitionPath(new Definition { File = "events\\test.txt", ModName = "1" }); result.Should().Be(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod\\fakemod\\events\\test.txt")); } @@ -3451,53 +3030,28 @@ public void Should_resolve_full_definition_archive_path() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_resolve_full_definition_archive_path", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" }, + WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); SetupMockCase(reader, parserManager, modParser); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fakemod.mod"}, - Name = "test", - Game = "Should_resolve_full_definition_archive_path" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fakemod.mod" }, Name = "test", Game = "Should_resolve_full_definition_archive_path" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; }); - var fileInfos = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "fakemod.mod", - IsBinary = false - } - }; + var fileInfos = new List { new FileInfo { Content = new List { "1" }, FileName = "fakemod.mod", IsBinary = false } }; reader.Setup(s => s.Read(It.IsAny(), It.IsAny>(), It.IsAny())).Returns(fileInfos); modParser.Setup(s => s.Parse(It.IsAny>(), It.IsAny())).Returns((IEnumerable values, DescriptorModType t) => { - return new ModObject() - { - FileName = "fakemod.zip", - Name = "1" - }; + return new ModObject { FileName = "fakemod.zip", Name = "1" }; }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => { @@ -3505,11 +3059,7 @@ public void Should_resolve_full_definition_archive_path() }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = service.ResolveFullDefinitionPath(new Definition() - { - File = "events\\test.txt", - ModName = "1" - }); + var result = service.ResolveFullDefinitionPath(new Definition { File = "events\\test.txt", ModName = "1" }); result.Should().Be(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod\\fakemod.zip")); } @@ -3528,11 +3078,8 @@ public void Should_add_mods_to_ignore_list() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "modName:a" - }; - service.AddModsToIgnoreList(c, new List() { new ModIgnoreConfiguration() { ModName = "a" }, new ModIgnoreConfiguration() { ModName = "b" } }); + var c = new ConflictResult { IgnoredPaths = "modName:a" }; + service.AddModsToIgnoreList(c, new List { new ModIgnoreConfiguration { ModName = "a" }, new ModIgnoreConfiguration { ModName = "b" } }); c.IgnoredPaths.Should().Be("modName:a--count:2" + Environment.NewLine + "modName:b--count:2"); } @@ -3551,11 +3098,8 @@ public void Should_add_mods_to_ignore_list_with_specified_count() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "modName:a" - }; - service.AddModsToIgnoreList(c, new List() { new ModIgnoreConfiguration() { ModName = "a", Count = 3 }, new ModIgnoreConfiguration() { ModName = "b" } }); + var c = new ConflictResult { IgnoredPaths = "modName:a" }; + service.AddModsToIgnoreList(c, new List { new ModIgnoreConfiguration { ModName = "a", Count = 3 }, new ModIgnoreConfiguration { ModName = "b" } }); c.IgnoredPaths.Should().Be("modName:a--count:3" + Environment.NewLine + "modName:b--count:2"); } @@ -3574,10 +3118,7 @@ public void Should_get_ignored_mods() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = service.GetIgnoredMods(new ConflictResult() - { - IgnoredPaths = "modName:a" + Environment.NewLine + "modName:b" - }); + var result = service.GetIgnoredMods(new ConflictResult { IgnoredPaths = "modName:a" + Environment.NewLine + "modName:b" }); result.Count.Should().Be(2); result[0].ModName.Should().Be("a"); result[0].Count.Should().Be(2); @@ -3600,10 +3141,7 @@ public void Should_get_ignored_mods_with_specified_count() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = service.GetIgnoredMods(new ConflictResult() - { - IgnoredPaths = "modName:a--count:3" + Environment.NewLine + "modName:b" - }); + var result = service.GetIgnoredMods(new ConflictResult { IgnoredPaths = "modName:a--count:3" + Environment.NewLine + "modName:b" }); result.Count.Should().Be(2); result[0].ModName.Should().Be("a"); result[0].Count.Should().Be(3); @@ -3625,30 +3163,16 @@ public async Task Should_reset_resolved_conflict() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_reset_resolved_conflict", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Should_reset_resolved_conflict", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var resolved = new IndexedDefinitions(); - await resolved.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await resolved.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); - var c = new ConflictResult() - { - AllConflicts = new IndexedDefinitions(), - ResolvedConflicts = resolved - }; + var c = new ConflictResult { AllConflicts = new IndexedDefinitions(), ResolvedConflicts = resolved }; var result = await service.ResetResolvedConflictAsync(c, "test-1", "fake"); result.Should().BeTrue(); } @@ -3667,28 +3191,15 @@ public async Task Should_not_reset_resolved_conflict() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_not_reset_resolved_conflict", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Should_not_reset_resolved_conflict", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var resolved = new IndexedDefinitions(); - await resolved.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await resolved.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); - var c = new ConflictResult() - { - ResolvedConflicts = resolved - }; + var c = new ConflictResult { ResolvedConflicts = resolved }; var result = await service.ResetResolvedConflictAsync(c, "test-2", "fake"); result.Should().BeFalse(); } @@ -3708,30 +3219,16 @@ public async Task Should_reset_ignored_conflict() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_reset_ignored_conflict", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Should_reset_ignored_conflict", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var ignored = new IndexedDefinitions(); - await ignored.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await ignored.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); - var c = new ConflictResult() - { - AllConflicts = new IndexedDefinitions(), - IgnoredConflicts = ignored - }; + var c = new ConflictResult { AllConflicts = new IndexedDefinitions(), IgnoredConflicts = ignored }; var result = await service.ResetIgnoredConflictAsync(c, "test-1", "fake"); result.Should().BeTrue(); } @@ -3750,29 +3247,16 @@ public async Task Should_not_reset_ignored_conflict() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_not_reset_ignored_conflict", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Should_not_reset_ignored_conflict", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var ignored = new IndexedDefinitions(); - await ignored.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await ignored.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); - var c = new ConflictResult() - { - IgnoredConflicts = ignored - }; + var c = new ConflictResult { IgnoredConflicts = ignored }; var result = await service.ResetIgnoredConflictAsync(c, "test-2", "fake"); result.Should().BeFalse(); } @@ -3828,20 +3312,13 @@ public async Task Should_not_apply_custom_mod_patch_when_nothing_to_merge() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_not_apply_custom_mod_patch_when_nothing_to_merge", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\fake" }, - CustomModDirectory = string.Empty + Type = "Should_not_apply_custom_mod_patch_when_nothing_to_merge", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); SetupMockCase(reader, parserManager, modParser); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => @@ -3853,14 +3330,8 @@ public async Task Should_not_apply_custom_mod_patch_when_nothing_to_merge() var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(new List()); - var c = new ConflictResult() - { - AllConflicts = indexed, - Conflicts = indexed, - ResolvedConflicts = indexed, - CustomConflicts = indexed, - }; - var result = await service.AddCustomModPatchAsync(c, new Definition() { ModName = "test", ValueType = ValueType.Object }, "colname"); + var c = new ConflictResult { AllConflicts = indexed, Conflicts = indexed, ResolvedConflicts = indexed, CustomConflicts = indexed }; + var result = await service.AddCustomModPatchAsync(c, new Definition { ModName = "test", ValueType = ValueType.Object }, "colname"); result.Should().BeFalse(); } @@ -3881,31 +3352,15 @@ public async Task Should_return_true_when_applying_custom_patches() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_return_true_when_applying_custom_patches", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\fake" }, - CustomModDirectory = string.Empty + Type = "Should_return_true_when_applying_custom_patches", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Should_return_true_when_applying_custom_patches" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Should_return_true_when_applying_custom_patches" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; @@ -3920,6 +3375,7 @@ public async Task Should_return_true_when_applying_custom_patches() { return true; } + return false; }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => @@ -3928,18 +3384,18 @@ public async Task Should_return_true_when_applying_custom_patches() }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", @@ -3947,7 +3403,7 @@ public async Task Should_return_true_when_applying_custom_patches() Id = "a", ModName = "test2", ValueType = ValueType.Object - }, + } }; var all = new IndexedDefinitions(); await all.InitMapAsync(definitions); @@ -3958,7 +3414,7 @@ public async Task Should_return_true_when_applying_custom_patches() var custom = new IndexedDefinitions(); await custom.InitMapAsync(new List()); - var c = new ConflictResult() + var c = new ConflictResult { AllConflicts = all, Conflicts = all, @@ -3966,7 +3422,7 @@ public async Task Should_return_true_when_applying_custom_patches() CustomConflicts = custom, OverwrittenConflicts = custom }; - var result = await service.AddCustomModPatchAsync(c, new Definition() { ModName = "1" }, "colname"); + var result = await service.AddCustomModPatchAsync(c, new Definition { ModName = "1" }, "colname"); result.Should().BeTrue(); } @@ -3987,31 +3443,15 @@ public async Task Should_throw_exception_when_applying_custom_patches_due_to_rea var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_throw_exception_when_applying_custom_patches_due_to_readonly_mode", - UserDirectory = "C:\\Users\\Fake", - WorkshopDirectory = new List() { "C:\\fake" }, - CustomModDirectory = string.Empty + Type = "Should_throw_exception_when_applying_custom_patches_due_to_readonly_mode", UserDirectory = "C:\\Users\\Fake", WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Should_return_true_when_applying_custom_patches" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Should_return_true_when_applying_custom_patches" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; @@ -4026,6 +3466,7 @@ public async Task Should_throw_exception_when_applying_custom_patches_due_to_rea { return true; } + return false; }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => @@ -4034,18 +3475,18 @@ public async Task Should_throw_exception_when_applying_custom_patches_due_to_rea }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var definitions = new List() + var definitions = new List { - new Definition() + new Definition { File = "events\\1.txt", Code = "a", Id = "a", - Type= "events", + Type = "events", ModName = "test1", ValueType = ValueType.Object }, - new Definition() + new Definition { File = "events\\2.txt", Code = "b", @@ -4053,7 +3494,7 @@ public async Task Should_throw_exception_when_applying_custom_patches_due_to_rea Id = "a", ModName = "test2", ValueType = ValueType.Object - }, + } }; var all = new IndexedDefinitions(); await all.InitMapAsync(definitions); @@ -4064,7 +3505,7 @@ public async Task Should_throw_exception_when_applying_custom_patches_due_to_rea var custom = new IndexedDefinitions(); await custom.InitMapAsync(new List()); - var c = new ConflictResult() + var c = new ConflictResult { AllConflicts = all, Conflicts = all, @@ -4076,13 +3517,14 @@ public async Task Should_throw_exception_when_applying_custom_patches_due_to_rea var exceptionThrown = false; try { - var result = await service.AddCustomModPatchAsync(c, new Definition() { ModName = "1" }, "colname"); + var result = await service.AddCustomModPatchAsync(c, new Definition { ModName = "1" }, "colname"); } catch (Exception ex) { ex.GetType().Should().Be(typeof(ArgumentException)); exceptionThrown = true; } + exceptionThrown.Should().BeTrue(); } @@ -4100,30 +3542,16 @@ public async Task Should_reset_custom_conflict() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_reset_custom_conflict", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Should_reset_custom_conflict", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var custom = new IndexedDefinitions(); - await custom.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await custom.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); - var c = new ConflictResult() - { - AllConflicts = new IndexedDefinitions(), - CustomConflicts = custom - }; + var c = new ConflictResult { AllConflicts = new IndexedDefinitions(), CustomConflicts = custom }; var result = await service.ResetCustomConflictAsync(c, "test-1", "fake"); result.Should().BeTrue(); } @@ -4142,31 +3570,16 @@ public async Task Should_not_reset_custom_conflict_due_to_readonly_mode() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_reset_custom_conflict", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Should_reset_custom_conflict", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var custom = new IndexedDefinitions(); - await custom.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await custom.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); - var c = new ConflictResult() - { - AllConflicts = new IndexedDefinitions(), - CustomConflicts = custom, - Mode = IronyModManager.Models.Common.PatchStateMode.ReadOnly - }; + var c = new ConflictResult { AllConflicts = new IndexedDefinitions(), CustomConflicts = custom, Mode = IronyModManager.Models.Common.PatchStateMode.ReadOnly }; var exceptionThrown = false; try { @@ -4177,6 +3590,7 @@ await custom.InitMapAsync(new List() ex.GetType().Should().Be(typeof(ArgumentException)); exceptionThrown = true; } + exceptionThrown.Should().BeTrue(); } @@ -4194,28 +3608,15 @@ public async Task Should_not_reset_custom_conflict() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_not_reset_custom_conflict", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Should_not_reset_custom_conflict", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var custom = new IndexedDefinitions(); - await custom.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await custom.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); - var c = new ConflictResult() - { - CustomConflicts = custom - }; + var c = new ConflictResult { CustomConflicts = custom }; var result = await service.ResetCustomConflictAsync(c, "test-2", "fake"); result.Should().BeFalse(); } @@ -4254,11 +3655,9 @@ public void Should_invalidate_mod_patch_state() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Should_invalidate_mod_patch_state", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Should_invalidate_mod_patch_state", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = service.ResetPatchStateCache(); @@ -4284,6 +3683,7 @@ public async Task Patch_mod_should_not_need_update_when_no_game() var result = await service.PatchModNeedsUpdateAsync("test", null); result.Should().BeFalse(); } + /// /// Defines the test method Patch_mod_should_not_need_update_when_no_collection. /// @@ -4298,11 +3698,9 @@ public async Task Patch_mod_should_not_need_update_when_no_collection() var gameService = new Mock(); var mapper = new Mock(); var modPatchExporter = new Mock(); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { - Type = "Patch_mod_should_not_need_update_when_no_collection", - UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" } + Type = "Patch_mod_should_not_need_update_when_no_collection", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), WorkshopDirectory = new List { "C:\\fake" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.PatchModNeedsUpdateAsync(null, null); @@ -4324,42 +3722,39 @@ public async Task Patch_mod_should_need_update_when_mod_not_present() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Patch_mod_should_not_need_update_when_mod_not_present", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" }, + WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Patch_mod_should_not_need_update_when_mod_not_present" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Patch_mod_should_not_need_update_when_mod_not_present" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; }); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() + var res = new PatchState { - Conflicts = new List() { new Definition() { File = "1", Id = "test", Type = "events", Code = "ab", ModName = "1" } }, + Conflicts = new List + { + new Definition + { + File = "1", + Id = "test", + Type = "events", + Code = "ab", + ModName = "1" + } + }, OverwrittenConflicts = new List(), - LoadOrder = new List() { "mod/fake2.txt", "mod/fake1.txt" } + LoadOrder = new List { "mod/fake2.txt", "mod/fake1.txt" } }; return res; }); @@ -4368,7 +3763,7 @@ public async Task Patch_mod_should_need_update_when_mod_not_present() return false; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.PatchModNeedsUpdateAsync("colname", new List() { "mod/fake2.txt", "mod/fake1.txt" }); + var result = await service.PatchModNeedsUpdateAsync("colname", new List { "mod/fake2.txt", "mod/fake1.txt" }); result.Should().BeTrue(); } @@ -4387,42 +3782,39 @@ public async Task Patch_mod_should_need_update_when_file_not_present() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Patch_mod_should_need_update_when_file_not_present", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" }, + WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Patch_mod_should_need_update_when_file_not_present" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Patch_mod_should_need_update_when_file_not_present" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; }); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() + var res = new PatchState { - Conflicts = new List() { new Definition() { File = "1", Id = "test", Type = "events", Code = "ab", ModName = "1" } }, + Conflicts = new List + { + new Definition + { + File = "1", + Id = "test", + Type = "events", + Code = "ab", + ModName = "1" + } + }, OverwrittenConflicts = new List(), - LoadOrder = new List() { "mod/fake2.txt", "mod/fake1.txt" } + LoadOrder = new List { "mod/fake2.txt", "mod/fake1.txt" } }; return res; }); @@ -4431,7 +3823,7 @@ public async Task Patch_mod_should_need_update_when_file_not_present() return false; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.PatchModNeedsUpdateAsync("colname", new List() { "mod/fake2.txt", "mod/fake1.txt" }); + var result = await service.PatchModNeedsUpdateAsync("colname", new List { "mod/fake2.txt", "mod/fake1.txt" }); result.Should().BeTrue(); } @@ -4450,55 +3842,49 @@ public async Task Patch_mod_should_need_update_when_sha_not_same() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Patch_mod_should_need_update_when_sha_not_same", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" }, + WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Patch_mod_should_need_update_when_sha_not_same" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Patch_mod_should_need_update_when_sha_not_same" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; }); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() + var res = new PatchState { - Conflicts = new List() { new Definition() { File = "1", Id = "test", Type = "events", Code = "ab", ModName = "1" } }, + Conflicts = new List + { + new Definition + { + File = "1", + Id = "test", + Type = "events", + Code = "ab", + ModName = "1" + } + }, OverwrittenConflicts = new List(), - LoadOrder = new List() { "mod/fake2.txt", "mod/fake1.txt" } + LoadOrder = new List { "mod/fake2.txt", "mod/fake1.txt" } }; return res; }); - reader.Setup(p => p.GetFileInfo(It.IsAny(), It.IsAny())).Returns(new FileInfo() - { - ContentSHA = "2" - }); + reader.Setup(p => p.GetFileInfo(It.IsAny(), It.IsAny())).Returns(new FileInfo { ContentSHA = "2" }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => { return false; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.PatchModNeedsUpdateAsync("colname", new List() { "mod/fake2.txt", "mod/fake1.txt" }); + var result = await service.PatchModNeedsUpdateAsync("colname", new List { "mod/fake2.txt", "mod/fake1.txt" }); result.Should().BeTrue(); } @@ -4517,30 +3903,20 @@ public async Task Patch_mod_should_need_update_when_overwritten_sha_not_same() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Patch_mod_should_need_update_when_overwritten_sha_not_same", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" }, + WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() + var collections = new List { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Patch_mod_should_need_update_when_overwritten_sha_not_same" - } + new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Patch_mod_should_need_update_when_overwritten_sha_not_same" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { @@ -4548,24 +3924,32 @@ public async Task Patch_mod_should_need_update_when_overwritten_sha_not_same() }); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() + var res = new PatchState { Conflicts = new List(), - OverwrittenConflicts = new List() { new Definition() { File = "1", Id = "test", Type = "events", Code = "ab", ModName = "1", OriginalFileName = "1" } }, - LoadOrder = new List() { "mod/fake2.txt", "mod/fake1.txt" } + OverwrittenConflicts = new List + { + new Definition + { + File = "1", + Id = "test", + Type = "events", + Code = "ab", + ModName = "1", + OriginalFileName = "1" + } + }, + LoadOrder = new List { "mod/fake2.txt", "mod/fake1.txt" } }; return res; }); - reader.Setup(p => p.GetFileInfo(It.IsAny(), It.IsAny())).Returns(new FileInfo() - { - ContentSHA = "2" - }); + reader.Setup(p => p.GetFileInfo(It.IsAny(), It.IsAny())).Returns(new FileInfo { ContentSHA = "2" }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => { return false; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.PatchModNeedsUpdateAsync("colname", new List() { "mod/fake2.txt", "mod/fake1.txt" }); + var result = await service.PatchModNeedsUpdateAsync("colname", new List { "mod/fake2.txt", "mod/fake1.txt" }); result.Should().BeTrue(); } @@ -4584,55 +3968,49 @@ public async Task Patch_mod_should_need_update_when_load_order_not_same() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Patch_mod_should_need_update_when_load_order_not_same", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" }, + WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Patch_mod_should_need_update_when_load_order_not_same" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Patch_mod_should_need_update_when_load_order_not_same" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; }); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() + var res = new PatchState { - Conflicts = new List() { new Definition() { File = "1", Id = "test", Type = "events", Code = "ab", ModName = "1" } }, + Conflicts = new List + { + new Definition + { + File = "1", + Id = "test", + Type = "events", + Code = "ab", + ModName = "1" + } + }, OverwrittenConflicts = new List(), - LoadOrder = new List() { "mod/fake1.txt", "mod/fake2.txt" } + LoadOrder = new List { "mod/fake1.txt", "mod/fake2.txt" } }; return res; }); - reader.Setup(p => p.GetFileInfo(It.IsAny(), It.IsAny())).Returns(new FileInfo() - { - ContentSHA = "2" - }); + reader.Setup(p => p.GetFileInfo(It.IsAny(), It.IsAny())).Returns(new FileInfo { ContentSHA = "2" }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => { return false; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.PatchModNeedsUpdateAsync("colname", new List() { "mod/fake2.txt", "mod/fake1.txt" }); + var result = await service.PatchModNeedsUpdateAsync("colname", new List { "mod/fake2.txt", "mod/fake1.txt" }); result.Should().BeTrue(); } @@ -4651,55 +4029,50 @@ public async Task Patch_mod_should_not_need_update() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Patch_mod_should_not_need_update", UserDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "mod"), - WorkshopDirectory = new List() { "C:\\fake" }, + WorkshopDirectory = new List { "C:\\fake" }, CustomModDirectory = string.Empty }); mapper.Setup(s => s.Map(It.IsAny())).Returns((IModObject o) => { - return new Mod() - { - FileName = o.FileName, - Name = o.Name - }; + return new Mod { FileName = o.FileName, Name = o.Name }; }); - var collections = new List() - { - new ModCollection() - { - IsSelected = true, - Mods = new List() { "mod/fake1.txt", "mod/fake2.txt"}, - Name = "test", - Game = "Patch_mod_should_not_need_update" - } - }; + var collections = new List { new ModCollection { IsSelected = true, Mods = new List { "mod/fake1.txt", "mod/fake2.txt" }, Name = "test", Game = "Patch_mod_should_not_need_update" } }; storageProvider.Setup(s => s.GetModCollections()).Returns(() => { return collections; }); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() + var res = new PatchState { - Conflicts = new List() { new Definition() { File = "1", Id = "test", Type = "events", Code = "ab", ModName = "1", ContentSHA = "1" } }, + Conflicts = new List + { + new Definition + { + File = "1", + Id = "test", + Type = "events", + Code = "ab", + ModName = "1", + ContentSHA = "1" + } + }, OverwrittenConflicts = new List(), - LoadOrder = new List() { "mod/fake2.txt", "mod/fake1.txt" } + LoadOrder = new List { "mod/fake2.txt", "mod/fake1.txt" } }; return res; }); - reader.Setup(p => p.GetFileInfo(It.IsAny(), It.IsAny())).Returns(new FileInfo() - { - ContentSHA = "1" - }); + reader.Setup(p => p.GetFileInfo(It.IsAny(), It.IsAny())).Returns(new FileInfo { ContentSHA = "1" }); modWriter.Setup(p => p.ModDirectoryExists(It.IsAny())).Returns((ModWriterParameters p) => { return false; }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.PatchModNeedsUpdateAsync("colname", new List() { "mod/fake2.txt", "mod/fake1.txt" }); + var result = await service.PatchModNeedsUpdateAsync("colname", new List { "mod/fake2.txt", "mod/fake1.txt" }); result.Should().BeFalse(); } @@ -4724,7 +4097,7 @@ public async Task LoadDefinitionContent_should_be_null_when_no_game() modPatchExporter.Setup(p => p.LoadDefinitionContentsAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult("test-response")); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); - var result = await service.LoadDefinitionContentsAsync(new Definition() { File = "test.txt" }, "test"); + var result = await service.LoadDefinitionContentsAsync(new Definition { File = "test.txt" }, "test"); result.Should().BeNullOrWhiteSpace(); } @@ -4745,18 +4118,14 @@ public async Task LoadDefinitionContent_should_be_null() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "LoadDefinitionContent_should_be_false_when_no_game", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "LoadDefinitionContent_should_be_false_when_no_game", UserDirectory = "C:\\Users\\Fake" }); modPatchExporter.Setup(p => p.LoadDefinitionContentsAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult("test-response")); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); var result = await service.LoadDefinitionContentsAsync(null, "test"); result.Should().BeNullOrWhiteSpace(); - result = await service.LoadDefinitionContentsAsync(new Definition() { File = "test.txt" }, string.Empty); + result = await service.LoadDefinitionContentsAsync(new Definition { File = "test.txt" }, string.Empty); result.Should().BeNullOrWhiteSpace(); } @@ -4777,15 +4146,11 @@ public async Task LoadDefinitionContent_should_not_be_null() var mapper = new Mock(); var modPatchExporter = new Mock(); SetupMockCase(reader, parserManager, modParser); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "LoadDefinitionContent_should_be_false_when_no_game", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "LoadDefinitionContent_should_be_false_when_no_game", UserDirectory = "C:\\Users\\Fake" }); modPatchExporter.Setup(p => p.LoadDefinitionContentsAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult("test-response")); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); - var result = await service.LoadDefinitionContentsAsync(new Definition() { File = "test.txt" }, "test"); + var result = await service.LoadDefinitionContentsAsync(new Definition { File = "test.txt" }, "test"); result.Should().Be("test-response"); } @@ -4853,7 +4218,7 @@ public async Task Should_have_game_files_included() var modPatchExporter = new Mock(); modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => { - var res = new PatchState() + var res = new PatchState { Conflicts = new List(), ResolvedConflicts = new List(), @@ -4863,11 +4228,7 @@ public async Task Should_have_game_files_included() }; return res; }); - gameService.Setup(p => p.GetSelected()).Returns(new Game() - { - Type = "Should_have_game_files_included", - UserDirectory = "C:\\Users\\Fake" - }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_have_game_files_included", UserDirectory = "C:\\Users\\Fake" }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.PatchHasGameDefinitionsAsync("fake"); @@ -4908,10 +4269,7 @@ public void Should_ignore_game_mods() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "" - }; + var c = new ConflictResult { IgnoredPaths = "" }; var result = service.ShouldIgnoreGameMods(c); result.Should().BeTrue(); } @@ -4931,10 +4289,7 @@ public void Should_not_ignore_game_mods() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "--showGameMods" - }; + var c = new ConflictResult { IgnoredPaths = "--showGameMods" }; var result = service.ShouldIgnoreGameMods(c); result.Should().BeFalse(); } @@ -4973,10 +4328,7 @@ public void Should_not_toggle_ignore_game_mods() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "" - }; + var c = new ConflictResult { IgnoredPaths = "" }; var result = service.ToggleIgnoreGameMods(c); result.Should().BeFalse(); c.IgnoredPaths.Should().Contain("--showGameMods"); @@ -4997,10 +4349,7 @@ public void Should_toggle_ignore_game_mods() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "--showGameMods" - }; + var c = new ConflictResult { IgnoredPaths = "--showGameMods" }; var result = service.ToggleIgnoreGameMods(c); result.Should().BeTrue(); c.IgnoredPaths.Should().BeNullOrWhiteSpace(); @@ -5040,10 +4389,7 @@ public void Should_show_self_conflicts() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "--showSelfConflicts" - }; + var c = new ConflictResult { IgnoredPaths = "--showSelfConflicts" }; var result = service.ShouldShowSelfConflicts(c); result.Should().BeTrue(); } @@ -5063,10 +4409,7 @@ public void Should_not_show_self_conflicts() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "" - }; + var c = new ConflictResult { IgnoredPaths = "" }; var result = service.ShouldShowSelfConflicts(c); result.Should().BeFalse(); } @@ -5105,10 +4448,7 @@ public void Should_not_toggle_self_conflicts() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "--showSelfConflicts" - }; + var c = new ConflictResult { IgnoredPaths = "--showSelfConflicts" }; var result = service.ToggleSelfModConflicts(c); result.Should().BeFalse(); c.IgnoredPaths.Should().BeNullOrWhiteSpace(); @@ -5129,10 +4469,7 @@ public void Should_toggle_self_conflicts() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "" - }; + var c = new ConflictResult { IgnoredPaths = "" }; var result = service.ToggleSelfModConflicts(c); result.Should().BeTrue(); c.IgnoredPaths.Should().Contain("--showSelfConflicts"); @@ -5172,10 +4509,7 @@ public void Should_show_reset_conflicts() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "--showResetConflicts" - }; + var c = new ConflictResult { IgnoredPaths = "--showResetConflicts" }; var result = service.ShouldShowResetConflicts(c); result.Should().BeTrue(); } @@ -5195,10 +4529,7 @@ public void Should_not_show_reset_conflicts() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "" - }; + var c = new ConflictResult { IgnoredPaths = "" }; var result = service.ShouldShowResetConflicts(c); result.Should().BeFalse(); } @@ -5237,10 +4568,7 @@ public void Should_not_toggle_reset_conflicts() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "--showResetConflicts" - }; + var c = new ConflictResult { IgnoredPaths = "--showResetConflicts" }; var result = service.ToggleShowResetConflicts(c); result.Should().BeFalse(); c.IgnoredPaths.Should().BeNullOrWhiteSpace(); @@ -5261,10 +4589,7 @@ public void Should_toggle_reset_conflicts() var mapper = new Mock(); var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var c = new ConflictResult() - { - IgnoredPaths = "" - }; + var c = new ConflictResult { IgnoredPaths = "" }; var result = service.ToggleShowResetConflicts(c); result.Should().BeTrue(); c.IgnoredPaths.Should().Contain("--showResetConflicts"); @@ -5285,7 +4610,7 @@ public void Should_return_bracket_count_result() var mapper = new Mock(); var modPatchExporter = new Mock(); var validateParser = new Mock(); - validateParser.Setup(p => p.GetBracketCount(It.IsAny(), It.IsAny())).Returns(new BracketValidateResult() { CloseBracketCount = 1, OpenBracketCount = 1 }); + validateParser.Setup(p => p.GetBracketCount(It.IsAny(), It.IsAny())).Returns(new BracketValidateResult { CloseBracketCount = 1, OpenBracketCount = 1 }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null, validateParser); var result = service.GetBracketCount("test.txt", "test"); result.Should().NotBeNull(); @@ -5308,9 +4633,9 @@ public void Should_validate_definition_and_treat_as_invalid() var mapper = new Mock(); var modPatchExporter = new Mock(); var validateParser = new Mock(); - validateParser.Setup(p => p.Validate(It.IsAny())).Returns(new List() { new Definition() { ErrorMessage = "test" } }); + validateParser.Setup(p => p.Validate(It.IsAny())).Returns(new List { new Definition { ErrorMessage = "test" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null, validateParser); - var result = service.Validate(new Definition() { ValueType = ValueType.Object }); + var result = service.Validate(new Definition { ValueType = ValueType.Object }); result.Should().NotBeNull(); result.IsValid.Should().BeFalse(); result.ErrorMessage.Should().Be("test"); @@ -5334,7 +4659,7 @@ public void Should_validate_definition_and_treat_as_valid() var validateParser = new Mock(); validateParser.Setup(p => p.Validate(It.IsAny())).Returns((IEnumerable)null); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null, validateParser); - var result = service.Validate(new Definition() { ValueType = ValueType.Object }); + var result = service.Validate(new Definition { ValueType = ValueType.Object }); result.Should().NotBeNull(); result.IsValid.Should().BeTrue(); } @@ -5354,9 +4679,9 @@ public void Should_not_validate_binary_definition_and_treat_as_valid() var mapper = new Mock(); var modPatchExporter = new Mock(); var validateParser = new Mock(); - validateParser.Setup(p => p.Validate(It.IsAny())).Returns(new List() { new Definition() { ErrorMessage = "test" } }); + validateParser.Setup(p => p.Validate(It.IsAny())).Returns(new List { new Definition { ErrorMessage = "test" } }); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null, validateParser); - var result = service.Validate(new Definition() { ValueType = ValueType.Binary }); + var result = service.Validate(new Definition { ValueType = ValueType.Binary }); result.Should().NotBeNull(); result.IsValid.Should().BeTrue(); } @@ -5374,11 +4699,11 @@ public async Task Stellaris_Performance_profiling() { DISetup.SetupContainer(); - var registration = new Services.Registrations.GameRegistration(); + var registration = new Registrations.GameRegistration(); registration.OnPostStartup(); var game = DISetup.Container.GetInstance().Get().First(s => s.Type == "Stellaris"); var mods = await DISetup.Container.GetInstance().GetInstalledModsAsync(game); - var defs = await DISetup.Container.GetInstance().GetModObjectsAsync(game, mods, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced); + var defs = await DISetup.Container.GetInstance().GetModObjectsAsync(game, mods, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); } /// @@ -5386,12 +4711,13 @@ public async Task Stellaris_Performance_profiling() /// Implements the /// /// - private class DomainConfigDummy : Shared.Configuration.IDomainConfiguration + private class DomainConfigDummy : IDomainConfiguration { /// /// The domain /// - DomainConfigurationOptions domain = new(); + private readonly DomainConfigurationOptions domain = new(); + /// /// Initializes a new instance of the class. /// diff --git a/src/IronyModManager.Services/GameLanguageService.cs b/src/IronyModManager.Services/GameLanguageService.cs index ca83dcb4..f8cb0cac 100644 --- a/src/IronyModManager.Services/GameLanguageService.cs +++ b/src/IronyModManager.Services/GameLanguageService.cs @@ -78,6 +78,15 @@ public IEnumerable Get() return result; } + /// + /// Get selected. + /// + /// A read only collection of IGameLanguages. + public IReadOnlyCollection GetSelected() + { + return Get().Where(p => p.IsSelected).ToList(); + } + /// /// Save. /// diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index 5300b7c7..b5d8fc50 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -4,7 +4,7 @@ // Created : 05-26-2020 // // Last Modified By : Mario -// Last Modified On : 02-23-2024 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario @@ -48,28 +48,10 @@ namespace IronyModManager.Services { /// - /// Class ModPatchCollectionService. - /// Implements the - /// Implements the + /// The mod patch collection service. /// - /// - /// - /// - /// Initializes a new instance of the class. - /// - /// The cache. - /// The message bus. - /// The parser manager. - /// The definition information providers. - /// The mod patch exporter. - /// The reader. - /// The mod writer. - /// The mod parser. - /// The game service. - /// The storage provider. - /// The mapper. - /// The validate parser. - /// The parametrized parser. + /// + /// public class ModPatchCollectionService( ICache cache, IMessageBus messageBus, @@ -867,10 +849,11 @@ public virtual IReadOnlyList GetIgnoredMods(IConflictRe /// The mods. /// Name of the collection. /// The mode. + /// The allowed game languages. /// A Task<IIndexedDefinitions> representing the asynchronous operation. /// Detected a mod which is potentially too large to parse. /// Detected a mod which is potentially too large to parse. - public virtual async Task GetModObjectsAsync(IGame game, IEnumerable mods, string collectionName, PatchStateMode mode) + public virtual async Task GetModObjectsAsync(IGame game, IEnumerable mods, string collectionName, PatchStateMode mode, IReadOnlyCollection allowedGameLanguages) { if (game == null || mods == null || !mods.Any()) { @@ -907,15 +890,19 @@ public virtual async Task GetModObjectsAsync(IGame game, IE var provider = DefinitionInfoProviders.FirstOrDefault(p => p.CanProcess(game.Type)); await messageBus.PublishAsync(new ModDefinitionLoadEvent(0)); + var allowedLanguages = allowedGameLanguages; var gameFolders = game.GameFolders.ToList(); if (mode is PatchStateMode.DefaultWithoutLocalization or PatchStateMode.AdvancedWithoutLocalization or PatchStateMode.ReadOnlyWithoutLocalization) { gameFolders = gameFolders.Where(p => !p.StartsWith(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase)).ToList(); + + // Mode override + allowedGameLanguages = null; } mods.AsParallel().WithDegreeOfParallelism(MaxModsToProcessInParallel).WithExecutionMode(ParallelExecutionMode.ForceParallelism).ForAll(m => { - var result = ParseModFiles(game, Reader.Read(m.FullPath, gameFolders), m, provider); + var result = ParseModFiles(game, Reader.Read(m.FullPath, gameFolders), m, provider, allowedLanguages); if (result?.Count() > 0) { foreach (var item in result) @@ -2946,8 +2933,9 @@ protected virtual IModIgnoreConfiguration ParseIgnoreModLine(string line) /// The file infos. /// The mod object. /// The definition information provider. + /// The allowed game languages. /// IEnumerable<IDefinition>. - protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable fileInfos, IModObject modObject, IDefinitionInfoProvider definitionInfoProvider) + protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable fileInfos, IModObject modObject, IDefinitionInfoProvider definitionInfoProvider, IReadOnlyCollection allowedGameLanguages) { if (fileInfos == null) { @@ -2957,6 +2945,13 @@ protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable var definitions = new List(); foreach (var fileInfo in fileInfos) { + // See if we should skip maybe + var onlyFilename = Path.GetFileNameWithoutExtension(fileInfo.FileName); + if (allowedGameLanguages != null && !allowedGameLanguages.Any(p => onlyFilename!.EndsWith(p.Type, StringComparison.OrdinalIgnoreCase))) + { + continue; + } + var fileDefs = parserManager.Parse(new ParserManagerArgs { ContentSHA = fileInfo.ContentSHA, diff --git a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs index 5c175df5..ca11c0f5 100644 --- a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs @@ -4,7 +4,7 @@ // Created : 02-29-2020 // // Last Modified By : Mario -// Last Modified On : 02-23-2024 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario @@ -187,6 +187,11 @@ public class ModHolderControlViewModel : BaseViewModel /// private IDisposable gameIndexHandler; + /// + /// The game language service + /// + private readonly IGameLanguageService gameLanguageService; + /// /// The mod invalid replace handler /// @@ -204,6 +209,7 @@ public class ModHolderControlViewModel : BaseViewModel /// /// Initializes a new instance of the class. /// + /// The game language service. /// The external process handler service. /// The game definition load progress handler. /// The game index progress handler. @@ -226,7 +232,8 @@ public class ModHolderControlViewModel : BaseViewModel /// The mod definition patch load handler. /// The game directory changed handler. /// The logger. - public ModHolderControlViewModel(IExternalProcessHandlerService externalProcessHandlerService, GameDefinitionLoadProgressHandler gameDefinitionLoadProgressHandler, GameIndexProgressHandler gameIndexProgressHandler, + public ModHolderControlViewModel(IGameLanguageService gameLanguageService, IExternalProcessHandlerService externalProcessHandlerService, GameDefinitionLoadProgressHandler gameDefinitionLoadProgressHandler, + GameIndexProgressHandler gameIndexProgressHandler, IGameIndexService gameIndexService, IPromptNotificationsService promptNotificationsService, ModListInstallRefreshRequestHandler modListInstallRefreshRequestHandler, ModDefinitionInvalidReplaceHandler modDefinitionInvalidReplaceHandler, IIDGenerator idGenerator, IShutDownState shutDownState, IModService modService, IModPatchCollectionService modPatchCollectionService, IGameService gameService, @@ -256,6 +263,7 @@ public ModHolderControlViewModel(IExternalProcessHandlerService externalProcessH this.gameIndexProgressHandler = gameIndexProgressHandler; this.gameDefinitionLoadProgressHandler = gameDefinitionLoadProgressHandler; this.externalProcessHandlerService = externalProcessHandlerService; + this.gameLanguageService = gameLanguageService; InstalledMods = installedModsControlViewModel; CollectionMods = collectionModsControlViewModel; UseSimpleLayout = !DIResolver.Get().GetOptions().ConflictSolver.UseSubMenus; @@ -576,6 +584,8 @@ protected virtual async Task AnalyzeModsAsync(long id, PatchStateMode mode, IEnu modPatchCollectionService.InvalidatePatchModState(CollectionMods.SelectedModCollection.Name); modPatchCollectionService.ResetPatchStateCache(); + var allowedLanguages = gameLanguageService.GetSelected(); + var tooLargeMod = false; var game = gameService.GetSelected(); var stopWatch = new Stopwatch(); @@ -585,7 +595,8 @@ protected virtual async Task AnalyzeModsAsync(long id, PatchStateMode mode, IEnu IIndexedDefinitions result = null; try { - result = await modPatchCollectionService.GetModObjectsAsync(gameService.GetSelected(), CollectionMods.SelectedMods, CollectionMods.SelectedModCollection.Name, mode).ConfigureAwait(false); + result = await modPatchCollectionService + .GetModObjectsAsync(gameService.GetSelected(), CollectionMods.SelectedMods, CollectionMods.SelectedModCollection.Name, mode, allowedLanguages).ConfigureAwait(false); } catch (ModTooLargeException) { From 3a15f5c6767c55b2a9145877aeef4f933e31cd8e Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 25 Feb 2024 21:39:14 +0100 Subject: [PATCH 085/101] Filter out languages in service layer and store settings in patch state --- .../Mods/IModPatchExporter.cs | 11 +- .../Mods/ModPatchExporterParameters.cs | 12 +- .../Mods/Models/IPatchState.cs | 14 +- .../Mods/ModPatchExporter.cs | 258 +++++++++++------- .../Mods/Models/PatchState.cs | 27 +- .../IConflictResult.cs | 12 +- src/IronyModManager.Models/ConflictResult.cs | 23 +- .../IGameLanguageService.cs | 9 +- .../IModPatchCollectionService.cs | 12 +- .../GameLanguageServiceTests.cs | 19 +- .../ModPatchCollectionServiceTests.cs | 159 +++++++++-- .../GameLanguageService.cs | 25 ++ .../ModPatchCollectionService.cs | 57 +++- .../Controls/ModHolderControlViewModel.cs | 25 +- 14 files changed, 510 insertions(+), 153 deletions(-) diff --git a/src/IronyModManager.IO.Common/Mods/IModPatchExporter.cs b/src/IronyModManager.IO.Common/Mods/IModPatchExporter.cs index 1298a8f9..fe63da25 100644 --- a/src/IronyModManager.IO.Common/Mods/IModPatchExporter.cs +++ b/src/IronyModManager.IO.Common/Mods/IModPatchExporter.cs @@ -4,15 +4,17 @@ // Created : 03-31-2020 // // Last Modified By : Mario -// Last Modified On : 03-06-2022 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using IronyModManager.IO.Common.Mods.Models; @@ -39,6 +41,13 @@ public interface IModPatchExporter /// Task<System.Boolean>. Task ExportDefinitionAsync(ModPatchExporterParameters parameters); + /// + /// Gets an allowed languages async. + /// + /// The parameters. + /// A Task containing IReadOnlyCollection of strings. + Task> GetAllowedLanguagesAsync(ModPatchExporterParameters parameters); + /// /// Gets the patch files. /// diff --git a/src/IronyModManager.IO.Common/Mods/ModPatchExporterParameters.cs b/src/IronyModManager.IO.Common/Mods/ModPatchExporterParameters.cs index 4aece4bd..28baa7a1 100644 --- a/src/IronyModManager.IO.Common/Mods/ModPatchExporterParameters.cs +++ b/src/IronyModManager.IO.Common/Mods/ModPatchExporterParameters.cs @@ -4,15 +4,17 @@ // Created : 04-02-2020 // // Last Modified By : Mario -// Last Modified On : 11-01-2021 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.Shared; using IronyModManager.Shared.Models; @@ -26,6 +28,14 @@ public class ModPatchExporterParameters { #region Properties + /// + /// Gets or sets a value representing the allowed languages. + /// + /// + /// The allowed languages. + /// + public IEnumerable AllowedLanguages { get; set; } + /// /// Gets or sets the conflicts. /// diff --git a/src/IronyModManager.IO.Common/Mods/Models/IPatchState.cs b/src/IronyModManager.IO.Common/Mods/Models/IPatchState.cs index a4b41cc0..eb89d4e1 100644 --- a/src/IronyModManager.IO.Common/Mods/Models/IPatchState.cs +++ b/src/IronyModManager.IO.Common/Mods/Models/IPatchState.cs @@ -1,4 +1,5 @@ -// *********************************************************************** + +// *********************************************************************** // Assembly : IronyModManager.IO.Common // Author : Mario // Created : 04-06-2020 @@ -11,12 +12,15 @@ // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.Shared.Models; namespace IronyModManager.IO.Common.Mods.Models { + /// /// Interface IPatchState /// @@ -24,6 +28,14 @@ public interface IPatchState { #region Properties + /// + /// Gets or sets a value representing the allowed languages. + /// + /// + /// The allowed languages. + /// + IEnumerable AllowedLanguages { get; set; } + /// /// Gets or sets the conflict history. /// diff --git a/src/IronyModManager.IO/Mods/ModPatchExporter.cs b/src/IronyModManager.IO/Mods/ModPatchExporter.cs index b076b082..35966244 100644 --- a/src/IronyModManager.IO/Mods/ModPatchExporter.cs +++ b/src/IronyModManager.IO/Mods/ModPatchExporter.cs @@ -4,7 +4,7 @@ // Created : 03-31-2020 // // Last Modified By : Mario -// Last Modified On : 05-14-2023 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario @@ -37,15 +37,19 @@ namespace IronyModManager.IO.Mods { /// - /// Class ModPatchExporter. - /// Implements the + /// The mod patch exporter. /// /// [ExcludeFromCoverage("Skipping testing IO logic.")] - public class ModPatchExporter : IModPatchExporter + public class ModPatchExporter(IObjectClone objectClone, ICache cache, IReader reader, IEnumerable definitionInfoProviders, IMessageBus messageBus) : IModPatchExporter { #region Fields + /// + /// A private const string named AllowedLanguagesFileName. + /// + private const string AllowedLanguagesFileName = "allowed_languages.txt"; + /// /// The cache external code key /// @@ -99,7 +103,7 @@ public class ModPatchExporter : IModPatchExporter /// /// The old format paths /// - private static readonly List OldFormatPaths = new() { JsonStateName, JsonStateName + ".bak", JsonStateName + ".tmp" }; + private static readonly List oldFormatPaths = [JsonStateName, JsonStateName + ".bak", JsonStateName + ".tmp"]; /// /// The write lock @@ -109,27 +113,27 @@ public class ModPatchExporter : IModPatchExporter /// /// The cache /// - private readonly ICache cache; + private readonly ICache cache = cache; /// /// The definition information providers /// - private readonly IEnumerable definitionInfoProviders; + private readonly IEnumerable definitionInfoProviders = definitionInfoProviders; /// /// The message bus /// - private readonly IMessageBus messageBus; + private readonly IMessageBus messageBus = messageBus; /// /// The object clone /// - private readonly IObjectClone objectClone; + private readonly IObjectClone objectClone = objectClone; /// /// The reader /// - private readonly IReader reader; + private readonly IReader reader = reader; /// /// The saving token @@ -139,31 +143,10 @@ public class ModPatchExporter : IModPatchExporter /// /// The write counter /// - private int writeCounter = 0; + private int writeCounter; #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The object clone. - /// The cache. - /// The reader. - /// The definition information providers. - /// The message bus. - public ModPatchExporter(IObjectClone objectClone, ICache cache, IReader reader, IEnumerable definitionInfoProviders, IMessageBus messageBus) - { - this.cache = cache; - this.definitionInfoProviders = definitionInfoProviders; - this.reader = reader; - this.messageBus = messageBus; - this.objectClone = objectClone; - } - - #endregion Constructors - #region Enums /// @@ -217,13 +200,15 @@ async Task export() { throw new ArgumentNullException(nameof(parameters), "Game."); } + var definitionsInvalid = (parameters.Definitions == null || !parameters.Definitions.Any()) && - (parameters.OverwrittenConflicts == null || !parameters.OverwrittenConflicts.Any()) && - (parameters.CustomConflicts == null || !parameters.CustomConflicts.Any()); + (parameters.OverwrittenConflicts == null || !parameters.OverwrittenConflicts.Any()) && + (parameters.CustomConflicts == null || !parameters.CustomConflicts.Any()); if (definitionsInvalid) { throw new ArgumentNullException(nameof(parameters), "Definitions."); } + var definitionInfoProvider = definitionInfoProviders.FirstOrDefault(p => p.CanProcess(parameters.Game) && p.IsFullyImplemented); if (definitionInfoProvider != null) { @@ -250,12 +235,39 @@ async Task export() results.Add(await WriteMergedContentAsync(parameters.CustomConflicts.Where(p => p.ValueType != ValueType.Binary), GetPatchRootPath(parameters.RootPath, parameters.PatchPath), parameters.Game, true, FileNameGeneration.UseExistingFileName)); } + return results.All(p => p); } + return false; } + var retry = new RetryStrategy(); - return await retry.RetryActionAsync(() => export()); + return await retry.RetryActionAsync(export); + } + + /// + /// Gets an allowed languages async. + /// + /// The parameters. + /// A Task containing IReadOnlyCollection of strings. + public async Task> GetAllowedLanguagesAsync(ModPatchExporterParameters parameters) + { + var rootPath = GetPatchRootPath(parameters.RootPath, parameters.PatchPath); + var filename = Path.Combine(rootPath, AllowedLanguagesFileName); + if (File.Exists(filename)) + { + var content = await File.ReadAllTextAsync(filename); + if (!string.IsNullOrWhiteSpace(content)) + { + var split = content.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + return split.Select(s => s.Trim()).ToList(); + } + + return []; + } + + return null; } /// @@ -278,6 +290,7 @@ public IEnumerable GetPatchFiles(ModPatchExporterParameters parameters) } } } + return files; } @@ -309,6 +322,7 @@ public async Task GetPatchStateAsync(ModPatchExporterParameters par return (PatchStateMode)value; } } + return null; } @@ -320,9 +334,8 @@ public async Task GetPatchStateAsync(ModPatchExporterParameters par /// Task<System.String>. public async Task LoadDefinitionContentsAsync(ModPatchExporterParameters parameters, string path) { - var patchPath = Path.Combine(parameters.RootPath, parameters.PatchPath); var state = await GetPatchStateAsync(parameters); - if (state != null && state.ConflictHistory != null) + if (state is { ConflictHistory: not null }) { var history = state.ConflictHistory.FirstOrDefault(p => p.FileCI.Equals(path, StringComparison.OrdinalIgnoreCase)); if (history != null) @@ -330,6 +343,7 @@ public async Task LoadDefinitionContentsAsync(ModPatchExporterParameters return history.Code; } } + return string.Empty; } @@ -351,10 +365,12 @@ async Task rename() DiskOperations.DeleteDirectory(oldPath, true); } } + return result; - }; + } + var retry = new RetryStrategy(); - return await retry.RetryActionAsync(() => rename()); + return await retry.RetryActionAsync(rename); } /// @@ -362,7 +378,7 @@ async Task rename() /// public void ResetCache() { - cache.Invalidate(new CacheInvalidateParameters() { Region = CacheStateRegion, Keys = new List() { CacheStateKey } }); + cache.Invalidate(new CacheInvalidateParameters { Region = CacheStateRegion, Keys = [CacheStateKey] }); } /// @@ -383,6 +399,7 @@ public async Task SaveStateAsync(ModPatchExporterParameters parameters) state.OverwrittenConflicts = MapDefinitions(parameters.OverwrittenConflicts, false); state.CustomConflicts = MapDefinitions(parameters.CustomConflicts, false); state.Mode = parameters.Mode; + state.AllowedLanguages = parameters.AllowedLanguages; state.LoadOrder = parameters.LoadOrder; state.HasGameDefinitions = parameters.HasGameDefinitions; var history = new ConcurrentDictionary>(); @@ -390,6 +407,7 @@ public async Task SaveStateAsync(ModPatchExporterParameters parameters) { history.TryAdd(item.Key, item.Value); } + if (parameters.ResolvedConflicts != null) { var tasks = parameters.ResolvedConflicts.Where(s => !string.IsNullOrWhiteSpace(s.Code)).Select(item => @@ -398,41 +416,45 @@ public async Task SaveStateAsync(ModPatchExporterParameters parameters) { if (!history.TryGetValue(item.TypeAndId, out var existingHits)) { - existingHits = new List(); + existingHits = []; } + var existing = existingHits.FirstOrDefault(p => item.Code.Equals(p.Code)); if (existing == null) { - var definitions = new List() { item }; - history.AddOrUpdate(item.TypeAndId, definitions, (k, v) => definitions); - modifiedHistory.AddOrUpdate(item.TypeAndId, item, (k, v) => item); + var definitions = new List { item }; + history.AddOrUpdate(item.TypeAndId, definitions, (_, _) => definitions); + modifiedHistory.AddOrUpdate(item.TypeAndId, item, (_, _) => item); } else if (existingHits.Count() > 1) { - var definitions = new List() { existing }; - history.AddOrUpdate(existing.TypeAndId, definitions, (k, v) => definitions); - modifiedHistory.AddOrUpdate(existing.TypeAndId, existing, (k, v) => existing); + var definitions = new List { existing }; + history.AddOrUpdate(existing.TypeAndId, definitions, (_, _) => definitions); + modifiedHistory.AddOrUpdate(existing.TypeAndId, existing, (_, _) => existing); } }); }); await Task.WhenAll(tasks); } + if (parameters.Definitions != null) { foreach (var item in parameters.Definitions.Where(s => !string.IsNullOrWhiteSpace(s.Code) && !modifiedHistory.Any(p => p.Key.Equals(s.TypeAndId)))) { - var definitions = new List() { item }; - history.AddOrUpdate(item.TypeAndId, definitions, (k, v) => definitions); - modifiedHistory.AddOrUpdate(item.TypeAndId, item, (k, v) => item); + var definitions = new List { item }; + history.AddOrUpdate(item.TypeAndId, definitions, (_, _) => definitions); + modifiedHistory.AddOrUpdate(item.TypeAndId, item, (_, _) => item); } } + state.ConflictHistory = MapDefinitions(history.SelectMany(p => p.Value), true); - var externallyLoadedCode = cache.Get>(new CacheGetParameters() { Key = CacheExternalCodeKey, Region = CacheStateRegion }); + var externallyLoadedCode = cache.Get>(new CacheGetParameters { Key = CacheExternalCodeKey, Region = CacheStateRegion }); if (externallyLoadedCode == null) { - externallyLoadedCode = new HashSet(); - cache.Set(new CacheAddParameters>() { Key = CacheExternalCodeKey, Value = externallyLoadedCode, Region = CacheStateRegion }); + externallyLoadedCode = []; + cache.Set(new CacheAddParameters> { Key = CacheExternalCodeKey, Value = externallyLoadedCode, Region = CacheStateRegion }); } + return StoreState(state, modifiedHistory.Select(p => p.Value), externallyLoadedCode, path); } @@ -451,8 +473,10 @@ static IList standardizeArray(IList paths) { newPaths.Add(item.StandardizeDirectorySeparator()); } + return newPaths; } + return paths; } @@ -489,17 +513,20 @@ private static async Task CopyPatchModInternalAsync(ModPatchExporterParame var destinationPath = Path.Combine(newPath, info.FullName.Replace(oldPath, string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart(Path.DirectorySeparatorChar)); if (!Directory.Exists(Path.GetDirectoryName(destinationPath))) { - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!); } + info.CopyTo(destinationPath, true); } + var text = await ReadPatchContentAsync(newPath); foreach (var renamePair in parameters.RenamePairs) { text = text.Replace($"\"{renamePair.Key}\"", $"\"{renamePair.Value}\""); } + await SavePatchContentAsync(Path.Combine(newPath, StateName), text); - OldFormatPaths.ForEach(path => + oldFormatPaths.ForEach(path => { var fullPath = Path.Combine(newPath, path); if (File.Exists(fullPath)) @@ -509,6 +536,7 @@ private static async Task CopyPatchModInternalAsync(ModPatchExporterParame }); return true; } + return false; } @@ -539,16 +567,17 @@ private static async Task ReadPatchContentAsync(string homePath) else if (File.Exists(path)) { var bytes = await File.ReadAllBytesAsync(path); - if (bytes.Any()) + if (bytes.Length != 0) { using var source = new MemoryStream(bytes); using var destination = new MemoryStream(); - using var compress = new GZipStream(source, CompressionMode.Decompress); + await using var compress = new GZipStream(source, CompressionMode.Decompress); await compress.CopyToAsync(destination); var text = Encoding.UTF8.GetString(destination.ToArray()); return text; } } + return string.Empty; } @@ -563,7 +592,7 @@ private static async Task SavePatchContentAsync(string fullPath, string co var bytes = Encoding.UTF8.GetBytes(content); using var source = new MemoryStream(bytes); using var destination = new MemoryStream(); - using var compress = new GZipStream(destination, CompressionLevel.Fastest, true); + await using var compress = new GZipStream(destination, CompressionLevel.Fastest, true); await source.CopyToAsync(compress); await compress.FlushAsync(); await File.WriteAllBytesAsync(fullPath, destination.ToArray()); @@ -597,7 +626,9 @@ static async Task copyStream(Stream s, FileStream fs) { continue; } + var stream = reader.GetStream(def.ModPath, def.File); + // If image and no stream try switching extension if (FileSignatureUtility.IsImageFile(def.File) && stream == null) { @@ -612,22 +643,23 @@ static async Task copyStream(Stream s, FileStream fs) } } } + if (!Directory.Exists(Path.GetDirectoryName(outPath))) { - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outPath)!); } + var fs = new FileStream(outPath, FileMode.Create, FileAccess.Write, FileShare.Read); - if (stream.CanSeek) + if (stream!.CanSeek) { stream.Seek(0, SeekOrigin.Begin); } - tasks.Add(retry.RetryActionAsync(() => - { - return copyStream(stream, fs); - })); + + tasks.Add(retry.RetryActionAsync(() => copyStream(stream, fs))); streams.Add(stream); streams.Add(fs); } + if (tasks.Count > 0) { await Task.WhenAll(tasks); @@ -637,6 +669,7 @@ static async Task copyStream(Stream s, FileStream fs) await fs.DisposeAsync(); } } + return true; } @@ -647,7 +680,7 @@ static async Task copyStream(Stream s, FileStream fs) /// IPatchState. private IPatchState GetPatchState(string path) { - var cachedItem = cache.Get(new CacheGetParameters() { Key = CacheStateKey, Region = CacheStateRegion }); + var cachedItem = cache.Get(new CacheGetParameters { Key = CacheStateKey, Region = CacheStateRegion }); if (cachedItem != null) { var lastPath = cachedItem.LastCachedPath ?? string.Empty; @@ -656,8 +689,10 @@ private IPatchState GetPatchState(string path) ResetCache(); return null; } + return cachedItem.PatchState; } + return null; } @@ -684,59 +719,69 @@ private async Task GetPatchStateInternalAsync(ModPatchExporterParam { cached.IgnoreConflictPaths = string.Empty; } + if (cached.ConflictHistory == null) { - cached.ConflictHistory = new List(); + cached.ConflictHistory = []; } else { StandardizeDefinitionPaths(cached.ConflictHistory); } + if (cached.Conflicts == null) { - cached.Conflicts = new List(); + cached.Conflicts = []; } else { StandardizeDefinitionPaths(cached.Conflicts); } + if (cached.IgnoredConflicts == null) { - cached.IgnoredConflicts = new List(); + cached.IgnoredConflicts = []; } else { StandardizeDefinitionPaths(cached.IgnoredConflicts); } + if (cached.ResolvedConflicts == null) { - cached.ResolvedConflicts = new List(); + cached.ResolvedConflicts = []; } else { StandardizeDefinitionPaths(cached.ResolvedConflicts); } + if (cached.OverwrittenConflicts == null) { - cached.OverwrittenConflicts = new List(); + cached.OverwrittenConflicts = []; } else { StandardizeDefinitionPaths(cached.OverwrittenConflicts); } + if (cached.CustomConflicts == null) { - cached.CustomConflicts = new List(); + cached.CustomConflicts = []; } else { StandardizeDefinitionPaths(cached.CustomConflicts); } - cached.LoadOrder ??= new List(); + + cached.LoadOrder ??= []; + cached.AllowedLanguages ??= []; + // If not allowing full load don't cache anything if (loadExternalCode) { var externallyLoadedCode = new ConcurrentBag(); + async Task loadCode(IDefinition definition) { var historyPath = Path.Combine(GetPatchRootPath(parameters.RootPath, parameters.PatchPath), StateHistory, definition.Type, definition.Id.GenerateValidFileName() + StateConflictHistoryExtension); @@ -747,29 +792,30 @@ async Task loadCode(IDefinition definition) externallyLoadedCode.Add(definition.TypeAndId); } } + var tasks = new List(); foreach (var item in cached.ConflictHistory) { tasks.Add(loadCode(item)); } - var cachedItem = new CachedState() - { - LastCachedPath = statePath, - PatchState = cached - }; + + var cachedItem = new CachedState { LastCachedPath = statePath, PatchState = cached }; await Task.WhenAll(tasks); - cache.Set(new CacheAddParameters() { Region = CacheStateRegion, Key = CacheStateKey, Value = cachedItem }); - cache.Set(new CacheAddParameters>() { Region = CacheStateRegion, Key = CacheExternalCodeKey, Value = externallyLoadedCode.Distinct().ToHashSet() }); + cache.Set(new CacheAddParameters { Region = CacheStateRegion, Key = CacheStateKey, Value = cachedItem }); + cache.Set(new CacheAddParameters> { Region = CacheStateRegion, Key = CacheExternalCodeKey, Value = externallyLoadedCode.Distinct().ToHashSet() }); } } + mutex.Dispose(); } + if (cached != null) { var result = DIResolver.Get(); MapPatchState(cached, result, true); return result; } + return null; } @@ -790,7 +836,7 @@ private IDefinition MapDefinition(IDefinition original, bool includeCode) /// The originals. /// if set to true [include code]. /// IEnumerable<IDefinition>. - private IEnumerable MapDefinitions(IEnumerable originals, bool includeCode) + private List MapDefinitions(IEnumerable originals, bool includeCode) { var col = new List(); if (originals != null) @@ -800,6 +846,7 @@ private IEnumerable MapDefinitions(IEnumerable origina col.Add(MapDefinition(original, includeCode)); } } + return col; } @@ -821,6 +868,7 @@ private void MapPatchState(IPatchState source, IPatchState destination, bool inc destination.Mode = source.Mode; destination.LoadOrder = source.LoadOrder; destination.HasGameDefinitions = source.HasGameDefinitions; + destination.AllowedLanguages = source.AllowedLanguages; } /// @@ -835,11 +883,11 @@ private bool StoreState(IPatchState model, IEnumerable modifiedHist { var statePath = Path.Combine(path, StateName); - var cachedItem = cache.Get(new CacheGetParameters() { Key = CacheStateKey, Region = CacheStateRegion }); + var cachedItem = cache.Get(new CacheGetParameters { Key = CacheStateKey, Region = CacheStateRegion }); cachedItem ??= new CachedState(); cachedItem.LastCachedPath = statePath; cachedItem.PatchState = model; - cache.Set(new CacheAddParameters() { Key = CacheStateKey, Value = cachedItem, Region = CacheStateRegion }); + cache.Set(new CacheAddParameters { Key = CacheStateKey, Value = cachedItem, Region = CacheStateRegion }); savingToken?.Cancel(); savingToken = new CancellationTokenSource(); @@ -859,9 +907,10 @@ private bool StoreState(IPatchState model, IEnumerable modifiedHist private async Task WriteMergedContentAsync(IEnumerable definitions, string patchRootPath, string game, bool checkIfFileExists, FileNameGeneration mode) { var tasks = new List(); - List results = new List(); + var results = new List(); var validDefinitions = definitions.Where(p => p.ValueType != ValueType.Namespace && p.ValueType != ValueType.Variable); var retry = new RetryStrategy(); + async Task evalZeroByteFiles(IDefinition definition, IDefinitionInfoProvider infoProvider, string fileName, string diskFile) { if (mode == FileNameGeneration.UseExistingFileNameAndWriteEmptyFiles) @@ -884,8 +933,8 @@ await retry.RetryActionAsync(async () => var infoProvider = definitionInfoProviders.FirstOrDefault(p => p.CanProcess(game) && p.IsFullyImplemented); if (infoProvider != null) { - string diskFile = string.Empty; - string fileName = string.Empty; + var diskFile = string.Empty; + var fileName = string.Empty; fileName = mode switch { FileNameGeneration.GenerateFileName => infoProvider.GetFileName(item), @@ -896,12 +945,14 @@ await retry.RetryActionAsync(async () => FileNameGeneration.GenerateFileName => infoProvider.GetDiskFileName(item), _ => !string.IsNullOrWhiteSpace(item.DiskFile) ? item.DiskFile : item.File }; + // For backwards compatibility when filename was used var altFileName = Path.Combine(patchRootPath, fileName); if (diskFile != fileName && File.Exists(altFileName)) { DiskOperations.DeleteFile(altFileName); } + var outPath = Path.Combine(patchRootPath, diskFile); if (checkIfFileExists && File.Exists(outPath)) { @@ -909,10 +960,12 @@ await retry.RetryActionAsync(async () => await evalZeroByteFiles(item, infoProvider, fileName, diskFile); continue; } + if (!Directory.Exists(Path.GetDirectoryName(outPath))) { - Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + Directory.CreateDirectory(Path.GetDirectoryName(outPath)!); } + // Update filename item.DiskFile = diskFile; item.File = fileName; @@ -923,6 +976,7 @@ await retry.RetryActionAsync(async () => { code += Environment.NewLine; } + await File.WriteAllTextAsync(outPath, code, infoProvider.GetEncoding(item)); return true; })); @@ -934,6 +988,7 @@ await retry.RetryActionAsync(async () => results.Add(false); } } + if (tasks.Count > 0) { await Task.WhenAll(tasks); @@ -954,7 +1009,7 @@ await retry.RetryActionAsync(async () => private async Task WriteStateInBackground(IPatchState model, IEnumerable modifiedHistory, HashSet externalCode, string path, CancellationToken cancellationToken) { writeCounter++; - using var ctr = cancellationToken.Register(() => + await using var ctr = cancellationToken.Register(() => { writeCounter--; messageBus.PublishAsync(new WritingStateOperationEvent(writeCounter <= 0)).ConfigureAwait(false); @@ -971,6 +1026,7 @@ await Task.Run(async () => { return; } + var retry = new RetryStrategy(); var patchState = DIResolver.Get(); MapPatchState(model, patchState, true); @@ -989,8 +1045,9 @@ await Task.Run(async () => var historyDirectory = Path.GetDirectoryName(historyPath); if (!Directory.Exists(historyDirectory)) { - Directory.CreateDirectory(historyDirectory); + Directory.CreateDirectory(historyDirectory!); } + if (externalCode != null && !externalCode.Contains(item.TypeAndId)) { loadedCode.Add(item.TypeAndId); @@ -1003,54 +1060,59 @@ await Task.Run(async () => } } } + await retry.RetryActionAsync(async () => { - await File.WriteAllTextAsync(historyPath, item.Code); + await File.WriteAllTextAsync(historyPath, item.Code, cancellationToken); return true; }); } - var existingLoadedCode = cache.Get>(new CacheGetParameters() { Key = CacheExternalCodeKey, Region = CacheStateRegion }); + var existingLoadedCode = cache.Get>(new CacheGetParameters { Key = CacheExternalCodeKey, Region = CacheStateRegion }); if (existingLoadedCode != null) { foreach (var item in loadedCode) { existingLoadedCode.Add(item); } - cache.Set(new CacheAddParameters>() { Key = CacheExternalCodeKey, Value = existingLoadedCode, Region = CacheStateRegion }); + + cache.Set(new CacheAddParameters> { Key = CacheExternalCodeKey, Value = existingLoadedCode, Region = CacheStateRegion }); } var dirPath = Path.GetDirectoryName(statePath); if (!Directory.Exists(dirPath)) { - Directory.CreateDirectory(dirPath); + Directory.CreateDirectory(dirPath!); } if (File.Exists(stateTemp)) { DiskOperations.DeleteFile(stateTemp); } + var serialized = JsonDISerializer.Serialize(patchState); - await retry.RetryActionAsync(async () => - { - return await SavePatchContentAsync(stateTemp, serialized); - }); + await retry.RetryActionAsync(async () => await SavePatchContentAsync(stateTemp, serialized)); if (File.Exists(backupPath)) { DiskOperations.DeleteFile(backupPath); } + if (File.Exists(statePath)) { File.Copy(statePath, backupPath); DiskOperations.DeleteFile(statePath); } + if (File.Exists(stateTemp)) { File.Copy(stateTemp, statePath); } + var modeFileName = Path.Combine(path, ModeFileName); - await File.WriteAllTextAsync(modeFileName, ((int)model.Mode).ToString()); - OldFormatPaths.ForEach(oldPath => + await File.WriteAllTextAsync(modeFileName, ((int)model.Mode).ToString(), cancellationToken); + var allowedLanguagesFileName = Path.Combine(path, AllowedLanguagesFileName); + await File.WriteAllLinesAsync(allowedLanguagesFileName, model.AllowedLanguages, cancellationToken); + oldFormatPaths.ForEach(oldPath => { var fullPath = Path.Combine(path, oldPath); if (File.Exists(fullPath)) diff --git a/src/IronyModManager.IO/Mods/Models/PatchState.cs b/src/IronyModManager.IO/Mods/Models/PatchState.cs index 6ecc79cc..49f2d09e 100644 --- a/src/IronyModManager.IO/Mods/Models/PatchState.cs +++ b/src/IronyModManager.IO/Mods/Models/PatchState.cs @@ -4,13 +4,14 @@ // Created : 03-31-2020 // // Last Modified By : Mario -// Last Modified On : 11-01-2021 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Linq; @@ -31,6 +32,11 @@ public class PatchState : IPatchState { #region Fields + /// + /// A private IEnumerable{string} named allowedLanguages. + /// + private IEnumerable allowedLanguages; + /// /// The conflict history /// @@ -39,12 +45,24 @@ public class PatchState : IPatchState /// /// The indexed conflict history /// +#pragma warning disable CA1859 // Use concrete types when possible for improved performance private IDictionary> indexedConflictHistory; +#pragma warning restore CA1859 // Use concrete types when possible for improved performance #endregion Fields #region Properties + /// + /// Gets or sets a value representing the allowed languages. + /// + /// The allowed languages. + public IEnumerable AllowedLanguages + { + get => allowedLanguages; + set => allowedLanguages = value == null ? [] : [.. value]; + } + /// /// Gets or sets the conflict history. /// @@ -105,6 +123,7 @@ public IDictionary> IndexedConflictHistory { InitConflictHistoryIndex(); } + return indexedConflictHistory; } } @@ -147,15 +166,15 @@ private void InitConflictHistoryIndex() { conflictHistory.ToList().ForEach(p => { - if (indexedConflictHistory.ContainsKey(p.TypeAndId)) + if (indexedConflictHistory.TryGetValue(p.TypeAndId, out var value)) { - var col = indexedConflictHistory[p.TypeAndId].ToList(); + var col = value.ToList(); col.Add(p); indexedConflictHistory[p.Type] = col; } else { - indexedConflictHistory.Add(p.TypeAndId, new List() { p }); + indexedConflictHistory.Add(p.TypeAndId, [p]); } }); } diff --git a/src/IronyModManager.Models.Common/IConflictResult.cs b/src/IronyModManager.Models.Common/IConflictResult.cs index 3530f657..3a9535c0 100644 --- a/src/IronyModManager.Models.Common/IConflictResult.cs +++ b/src/IronyModManager.Models.Common/IConflictResult.cs @@ -4,15 +4,17 @@ // Created : 03-18-2020 // // Last Modified By : Mario -// Last Modified On : 11-01-2021 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.Shared.Models; namespace IronyModManager.Models.Common @@ -34,6 +36,14 @@ public interface IConflictResult : IModel, IDisposable /// All conflicts. IIndexedDefinitions AllConflicts { get; set; } + /// + /// Gets or sets a value representing the allowed languages. + /// + /// + /// The allowed languages. + /// + IEnumerable AllowedLanguages { get; set; } + /// /// Gets or sets the conflicts. /// diff --git a/src/IronyModManager.Models/ConflictResult.cs b/src/IronyModManager.Models/ConflictResult.cs index c71c1dec..708874d4 100644 --- a/src/IronyModManager.Models/ConflictResult.cs +++ b/src/IronyModManager.Models/ConflictResult.cs @@ -4,15 +4,17 @@ // Created : 03-18-2020 // // Last Modified By : Mario -// Last Modified On : 11-01-2021 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using IronyModManager.Models.Common; using IronyModManager.Shared.Models; @@ -29,10 +31,15 @@ public class ConflictResult : BaseModel, IConflictResult { #region Fields + /// + /// The allowed languages + /// + private List allowedLanguages; + /// /// The disposed /// - private bool disposed = false; + private bool disposed; #endregion Fields @@ -44,6 +51,16 @@ public class ConflictResult : BaseModel, IConflictResult /// All conflicts. public IIndexedDefinitions AllConflicts { get; set; } + /// + /// Gets or sets the allowed languages. + /// + /// The allowed languages. + public IEnumerable AllowedLanguages + { + get => allowedLanguages; + set => allowedLanguages = value == null ? [] : [.. value]; + } + /// /// Gets or sets the conflicts. /// @@ -105,6 +122,7 @@ public void Dispose() { return; } + GC.SuppressFinalize(this); disposed = true; AllConflicts?.Dispose(); @@ -121,6 +139,7 @@ public void Dispose() RuleIgnoredConflicts = null; OverwrittenConflicts = null; CustomConflicts = null; + AllowedLanguages = null; } #endregion Methods diff --git a/src/IronyModManager.Services.Common/IGameLanguageService.cs b/src/IronyModManager.Services.Common/IGameLanguageService.cs index f42297a1..4c18aee3 100644 --- a/src/IronyModManager.Services.Common/IGameLanguageService.cs +++ b/src/IronyModManager.Services.Common/IGameLanguageService.cs @@ -32,10 +32,17 @@ public interface IGameLanguageService : IBaseService /// A list of IGameLanguages. IEnumerable Get(); + /// + /// Gets the by abrv. + /// + /// The languages. + /// IReadOnlyCollection<IGameLanguage>. + IReadOnlyCollection GetByAbrv(IReadOnlyCollection languages); + /// /// Get selected. /// - /// A read only collection of IGameLanguages. + /// A read only collection of IGameLanguages. IReadOnlyCollection GetSelected(); /// diff --git a/src/IronyModManager.Services.Common/IModPatchCollectionService.cs b/src/IronyModManager.Services.Common/IModPatchCollectionService.cs index d3fb0d16..4892dbd1 100644 --- a/src/IronyModManager.Services.Common/IModPatchCollectionService.cs +++ b/src/IronyModManager.Services.Common/IModPatchCollectionService.cs @@ -4,7 +4,7 @@ // Created : 05-26-2020 // // Last Modified By : Mario -// Last Modified On : 07-16-2023 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario @@ -92,8 +92,16 @@ public interface IModPatchCollectionService : IBaseService /// The indexed definitions. /// The mod order. /// The patch state mode. + /// The allowed languages. /// Task<IConflictResult>. - Task FindConflictsAsync(IIndexedDefinitions indexedDefinitions, IList modOrder, PatchStateMode patchStateMode); + Task FindConflictsAsync(IIndexedDefinitions indexedDefinitions, IList modOrder, PatchStateMode patchStateMode, IReadOnlyCollection allowedLanguages); + + /// + /// Gets an allowed languages async. + /// + /// The collection name. + /// A Task containing IReadOnlyCollection of strings. + Task> GetAllowedLanguagesAsync(string collectionName); /// /// Gets the bracket count. diff --git a/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs b/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs index 618b3b7c..b63b15ba 100644 --- a/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs +++ b/src/IronyModManager.Services.Tests/GameLanguageServiceTests.cs @@ -41,7 +41,7 @@ private static void SetupMocks(Mock preferencesService, par { DISetup.SetupContainer(); CurrentLocale.SetCurrent("en"); - preferencesService.Setup(p => p.Get()).Returns(() => new Preferences { ConflictSolverLanguages = languages.ToList() }); + preferencesService.Setup(p => p.Get()).Returns(() => new Preferences { ConflictSolverLanguages = [.. languages] }); preferencesService.Setup(p => p.Save(It.IsAny())).Returns(true); } @@ -83,10 +83,25 @@ public void Should_return_only_selected() SetupMocks(preferencesService, "l_english"); var service = new GameLanguageService(new Mock().Object, null, preferencesService.Object); var result = service.GetSelected(); - result.Count().Should().Be(1); + result.Count.Should().Be(1); result.FirstOrDefault(p => p.IsSelected)!.Type.Should().Be("l_english"); } + /// + /// Shoulds a return only requested. + /// + /// + [Fact] + public void Should_return_only_requested() + { + var preferencesService = new Mock(); + SetupMocks(preferencesService, "l_english"); + var service = new GameLanguageService(new Mock().Object, null, preferencesService.Object); + var result = service.GetByAbrv(["l_german"]); + result.Count.Should().Be(1); + result.FirstOrDefault(p => p.IsSelected)!.Type.Should().Be("l_german"); + } + /// /// Shoulds a save selection. /// diff --git a/src/IronyModManager.Services.Tests/ModPatchCollectionServiceTests.cs b/src/IronyModManager.Services.Tests/ModPatchCollectionServiceTests.cs index 709bafeb..2044f961 100644 --- a/src/IronyModManager.Services.Tests/ModPatchCollectionServiceTests.cs +++ b/src/IronyModManager.Services.Tests/ModPatchCollectionServiceTests.cs @@ -50,6 +50,7 @@ namespace IronyModManager.Services.Tests /// /// Class ModPatchCollectionServiceTests. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0028:Simplify collection initialization", Justification = "It's a unit test")] public class ModPatchCollectionServiceTests { /// @@ -130,13 +131,13 @@ public async Task Should_not_return_any_mod_objects_when_no_game_or_mods() var modPatchExporter = new Mock(); var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); - var result = await service.GetModObjectsAsync(null, new List(), string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); + var result = await service.GetModObjectsAsync(null, new List(), string.Empty, PatchStateMode.Advanced, null); result.Should().BeNull(); - result = await service.GetModObjectsAsync(new Game(), new List(), string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); + result = await service.GetModObjectsAsync(new Game(), new List(), string.Empty, PatchStateMode.Advanced, null); result.Should().BeNull(); - result = await service.GetModObjectsAsync(new Game(), null, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); + result = await service.GetModObjectsAsync(new Game(), null, string.Empty, PatchStateMode.Advanced, null); result.Should().BeNull(); } @@ -165,7 +166,7 @@ public async Task Should_return_mod_objects_when_using_fully_qualified_path() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); var result = await service.GetModObjectsAsync(new Game { UserDirectory = "c:\\fake", GameFolders = new List() }, new List { new Mod { FileName = Assembly.GetExecutingAssembly().Location, Name = "fake" } }, - string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); + string.Empty, PatchStateMode.Advanced, null); (await result.GetAllAsync()).Count().Should().Be(2); var ordered = (await result.GetAllAsync()).OrderBy(p => p.Id); ordered.First().Id.Should().Be("fake1.txt"); @@ -197,7 +198,7 @@ public async Task Should_return_mod_objects_when_using_user_directory() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); var result = await service.GetModObjectsAsync(new Game { UserDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), WorkshopDirectory = new List(), GameFolders = new List { "fake1" } }, - new List { new Mod { FileName = Path.GetFileName(Assembly.GetExecutingAssembly().Location), Name = "fake" } }, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); + new List { new Mod { FileName = Path.GetFileName(Assembly.GetExecutingAssembly().Location), Name = "fake" } }, string.Empty, PatchStateMode.Advanced, null); (await result.GetAllAsync()).Count().Should().Be(2); var ordered = (await result.GetAllAsync()).OrderBy(p => p.Id); ordered.First().Id.Should().Be("fake1.txt"); @@ -229,7 +230,7 @@ public async Task Should_return_mod_objects_when_using_workshop_directory() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, new List { infoProvider.Object }); var result = await service.GetModObjectsAsync(new Game { WorkshopDirectory = new List { Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) }, UserDirectory = "fake1", GameFolders = new List() }, - new List { new Mod { FileName = Path.GetFileName(Assembly.GetExecutingAssembly().Location), Name = "fake" } }, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); + new List { new Mod { FileName = Path.GetFileName(Assembly.GetExecutingAssembly().Location), Name = "fake" } }, string.Empty, PatchStateMode.Advanced, null); (await result.GetAllAsync()).Count().Should().Be(2); var ordered = (await result.GetAllAsync()).OrderBy(p => p.Id); ordered.First().Id.Should().Be("fake1.txt"); @@ -280,7 +281,7 @@ public async Task Should_find_filename_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List(), IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List(), PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(2); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(1); (await result.Conflicts.GetAllAsync()).All(p => p.ModName == "test1" || p.ModName == "test2").Should().BeTrue(); @@ -338,7 +339,7 @@ public async Task Should_find_orphan_filename_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List { "test2", "test1" }, IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List { "test2", "test1" }, PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(4); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(1); (await result.Conflicts.GetAllAsync()).All(p => p.ModName == "test1" || p.ModName == "test2").Should().BeTrue(); @@ -398,7 +399,7 @@ public async Task Should_not_ignore_orphan_localisation_filename_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List { "test2", "test1" }, IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List { "test2", "test1" }, PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(4); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(1); (await result.Conflicts.GetAllAsync()).All(p => p.ModName == "test1" || p.ModName == "test2").Should().BeTrue(); @@ -448,7 +449,7 @@ public async Task Should_find_definition_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List(), IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List(), PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(2); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(2); (await result.Conflicts.GetAllAsync()).All(p => p.ModName == "test1" || p.ModName == "test2").Should().BeTrue(); @@ -507,7 +508,7 @@ public async Task Should_not_find_override_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List(), IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List(), PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(0); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(0); } @@ -565,7 +566,7 @@ public async Task Should_not_find_dependency_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List(), IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List(), PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(0); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(0); } @@ -623,7 +624,7 @@ public async Task Should_find_dependency_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List(), IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List(), PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(2); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(1); (await result.Conflicts.GetAllAsync()).All(p => p.ModName == "test1" || p.ModName == "test3").Should().BeTrue(); @@ -691,7 +692,7 @@ public async Task Should_find_multiple_dependency_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List(), IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List(), PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(2); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(1); (await result.Conflicts.GetAllAsync()).All(p => p.ModName == "test3" || p.ModName == "test4").Should().BeTrue(); @@ -758,7 +759,7 @@ public async Task Should_not_include_variable_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List(), IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List(), PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(0); (await result.Conflicts.GetAllFileKeysAsync()).Count().Should().Be(0); } @@ -806,7 +807,7 @@ public async Task Should_return_all_conflicts() }; var indexed = new IndexedDefinitions(); await indexed.InitMapAsync(definitions); - var result = await service.FindConflictsAsync(indexed, new List(), IronyModManager.Models.Common.PatchStateMode.Default); + var result = await service.FindConflictsAsync(indexed, new List(), PatchStateMode.Default, null); (await result.Conflicts.GetAllAsync()).Count().Should().Be(2); } @@ -2628,7 +2629,7 @@ public async Task SaveIgnoredPathsAsync_should_be_true_when_readonly_mode() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter, null); var indexed = new IndexedDefinitions(); - var result = await service.SaveIgnoredPathsAsync(new ConflictResult { AllConflicts = indexed, Conflicts = indexed, Mode = IronyModManager.Models.Common.PatchStateMode.ReadOnly }, "test"); + var result = await service.SaveIgnoredPathsAsync(new ConflictResult { AllConflicts = indexed, Conflicts = indexed, Mode = PatchStateMode.ReadOnly }, "test"); result.Should().BeTrue(); } @@ -2829,7 +2830,7 @@ public async Task Should_not_get_patch_state_when_no_selected_game() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.GetPatchStateModeAsync("fake"); - result.Should().Be(IronyModManager.Models.Common.PatchStateMode.None); + result.Should().Be(PatchStateMode.None); } /// @@ -2852,7 +2853,7 @@ public async Task Should_not_get_patch_state_when_no_collection() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.GetPatchStateModeAsync(null); - result.Should().Be(IronyModManager.Models.Common.PatchStateMode.None); + result.Should().Be(PatchStateMode.None); } /// @@ -2880,7 +2881,7 @@ public async Task Should_get_patch_state() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.GetPatchStateModeAsync("fake"); - result.Should().Be(IronyModManager.Models.Common.PatchStateMode.Default); + result.Should().Be(PatchStateMode.Default); } /// @@ -2907,7 +2908,7 @@ public async Task Should_get_patch_state_from_mode_text() var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); var result = await service.GetPatchStateModeAsync("fake"); - result.Should().Be(IronyModManager.Models.Common.PatchStateMode.Default); + result.Should().Be(PatchStateMode.Default); } /// @@ -3512,7 +3513,7 @@ public async Task Should_throw_exception_when_applying_custom_patches_due_to_rea ResolvedConflicts = resolved, CustomConflicts = custom, OverwrittenConflicts = custom, - Mode = IronyModManager.Models.Common.PatchStateMode.ReadOnly + Mode = PatchStateMode.ReadOnly }; var exceptionThrown = false; try @@ -3579,7 +3580,7 @@ public async Task Should_not_reset_custom_conflict_due_to_readonly_mode() var custom = new IndexedDefinitions(); await custom.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); - var c = new ConflictResult { AllConflicts = new IndexedDefinitions(), CustomConflicts = custom, Mode = IronyModManager.Models.Common.PatchStateMode.ReadOnly }; + var c = new ConflictResult { AllConflicts = new IndexedDefinitions(), CustomConflicts = custom, Mode = PatchStateMode.ReadOnly }; var exceptionThrown = false; try { @@ -4686,6 +4687,116 @@ public void Should_not_validate_binary_definition_and_treat_as_valid() result.IsValid.Should().BeTrue(); } + + /// + /// Defines the test method Should_not_get_allowed_game_language_when_no_selected_game. + /// + [Fact] + public async Task Should_not_get_allowed_game_language_when_no_selected_game() + { + DISetup.SetupContainer(); + + var storageProvider = new Mock(); + var modParser = new Mock(); + var parserManager = new Mock(); + var reader = new Mock(); + var modWriter = new Mock(); + var gameService = new Mock(); + var mapper = new Mock(); + var modPatchExporter = new Mock(); + gameService.Setup(p => p.GetSelected()).Returns((IGame)null); + + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); + var result = await service.GetAllowedLanguagesAsync("fake"); + result.Should().BeNull(); + } + + + /// + /// Defines the test method Should_not_get_allowed_game_language_when_no_collection. + /// + [Fact] + public async Task Should_not_get_allowed_game_language_when_no_collection() + { + DISetup.SetupContainer(); + + var storageProvider = new Mock(); + var modParser = new Mock(); + var parserManager = new Mock(); + var reader = new Mock(); + var modWriter = new Mock(); + var gameService = new Mock(); + var mapper = new Mock(); + var modPatchExporter = new Mock(); + gameService.Setup(p => p.GetSelected()).Returns((IGame)null); + + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); + var result = await service.GetAllowedLanguagesAsync(null); + result.Should().BeNull(); + } + + + /// + /// Defines the test method Should_get_allowed_game_language. + /// + [Fact] + public async Task Should_get_allowed_game_language() + { + DISetup.SetupContainer(); + + var storageProvider = new Mock(); + var modParser = new Mock(); + var parserManager = new Mock(); + var reader = new Mock(); + var modWriter = new Mock(); + var gameService = new Mock(); + var mapper = new Mock(); + var modPatchExporter = new Mock(); + modPatchExporter.Setup(p => p.GetPatchStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync((ModPatchExporterParameters p, bool load) => + { + var res = new PatchState + { + Conflicts = new List(), + ResolvedConflicts = new List(), + OverwrittenConflicts = new List(), + Mode = IO.Common.PatchStateMode.Default, + AllowedLanguages = new List { "test" } + }; + return res; + }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_get_allowed_game_language", UserDirectory = "C:\\Users\\Fake" }); + + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); + var result = await service.GetAllowedLanguagesAsync("fake"); + result.Count.Should().Be(1); + result.FirstOrDefault().Should().Be("test"); + } + + /// + /// Defines the test method Should_get_allowed_game_language_from_mode_text. + /// + [Fact] + public async Task Should_get_allowed_game_language_from_mode_text() + { + DISetup.SetupContainer(); + + var storageProvider = new Mock(); + var modParser = new Mock(); + var parserManager = new Mock(); + var reader = new Mock(); + var modWriter = new Mock(); + var gameService = new Mock(); + var mapper = new Mock(); + var modPatchExporter = new Mock(); + modPatchExporter.Setup(p => p.GetAllowedLanguagesAsync(It.IsAny())).ReturnsAsync((ModPatchExporterParameters p) => new List { "test_file" }); + gameService.Setup(p => p.GetSelected()).Returns(new Game { Type = "Should_get_patch_state", UserDirectory = "C:\\Users\\Fake" }); + + var service = GetService(storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService, modPatchExporter); + var result = await service.GetAllowedLanguagesAsync("fake"); + result.Count.Should().Be(1); + result.FirstOrDefault().Should().Be("test_file"); + } + /// /// Defines the test method Stellaris_Performance_profiling. /// @@ -4703,7 +4814,7 @@ public async Task Stellaris_Performance_profiling() registration.OnPostStartup(); var game = DISetup.Container.GetInstance().Get().First(s => s.Type == "Stellaris"); var mods = await DISetup.Container.GetInstance().GetInstalledModsAsync(game); - var defs = await DISetup.Container.GetInstance().GetModObjectsAsync(game, mods, string.Empty, IronyModManager.Models.Common.PatchStateMode.Advanced, null); + var defs = await DISetup.Container.GetInstance().GetModObjectsAsync(game, mods, string.Empty, PatchStateMode.Advanced, null); } /// diff --git a/src/IronyModManager.Services/GameLanguageService.cs b/src/IronyModManager.Services/GameLanguageService.cs index f8cb0cac..9cc78eab 100644 --- a/src/IronyModManager.Services/GameLanguageService.cs +++ b/src/IronyModManager.Services/GameLanguageService.cs @@ -78,6 +78,31 @@ public IEnumerable Get() return result; } + /// + /// Gets the by abrv. + /// + /// The languages. + /// IReadOnlyCollection<IGameLanguage>. + /// + public IReadOnlyCollection GetByAbrv(IReadOnlyCollection languages) + { + var models = Get(); + var filtered = new List(); + if (languages != null) + { + foreach (var lang in models) + { + if (languages.Any(p => p.Equals(lang.Type))) + { + lang.IsSelected = true; + filtered.Add(lang); + } + } + } + + return filtered; + } + /// /// Get selected. /// diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index b5d8fc50..7cfcf218 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -362,8 +362,9 @@ public virtual IPriorityDefinitionResult EvalDefinitionPriority(IEnumerableThe indexed definitions. /// The mod order. /// The patch state mode. + /// The allowed languages. /// IConflictResult. - public virtual async Task FindConflictsAsync(IIndexedDefinitions indexedDefinitions, IList modOrder, PatchStateMode patchStateMode) + public virtual async Task FindConflictsAsync(IIndexedDefinitions indexedDefinitions, IList modOrder, PatchStateMode patchStateMode, IReadOnlyCollection allowedLanguages) { var actualMode = patchStateMode; if (patchStateMode == PatchStateMode.ReadOnly) @@ -778,6 +779,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition stopWatch.Restart(); var result = GetModelInstance(); + result.AllowedLanguages = allowedLanguages != null ? allowedLanguages.Select(l => l.Type).ToList() : []; result.Mode = patchStateMode; var conflictsIndexed = DIResolver.Get(); await conflictsIndexed.InitMapAsync(filteredConflicts, true); @@ -806,6 +808,36 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition return result; } + /// + /// Gets an allowed languages async. + /// + /// The collection name. + /// A Task containing IReadOnlyCollection of strings. + public async Task> GetAllowedLanguagesAsync(string collectionName) + { + var game = GameService.GetSelected(); + if (game != null && !string.IsNullOrWhiteSpace(collectionName)) + { + var patchName = GenerateCollectionPatchName(collectionName); + var @params = new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }; + var languages = await modPatchExporter.GetAllowedLanguagesAsync(@params); + if (languages != null) + { + return languages; + } + else + { + var state = await modPatchExporter.GetPatchStateAsync(@params, false); + if (state != null) + { + return state.AllowedLanguages != null ? [.. state.AllowedLanguages] : []; + } + } + } + + return null; + } + /// /// Gets the bracket count. /// @@ -1439,6 +1471,7 @@ await modPatchExporter.ExportDefinitionAsync(new ModPatchExporterParameters await customConflicts.InitMapAsync(state.CustomConflicts, true); conflicts.CustomConflicts = customConflicts; conflicts.Mode = conflictResult.Mode; + conflicts.AllowedLanguages = conflictResult.AllowedLanguages; var indexResult = await initAllIndexedDefinitions(conflictResult, total, processed, 100); conflictResult.AllConflicts.Dispose(); @@ -1462,7 +1495,8 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters CustomConflicts = await GetDefinitionOrDefaultAsync(conflicts.CustomConflicts), RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName), - HasGameDefinitions = await conflicts.AllConflicts.HasGameDefinitionsAsync() + HasGameDefinitions = await conflicts.AllConflicts.HasGameDefinitionsAsync(), + AllowedLanguages = conflicts.AllowedLanguages }); } @@ -1548,7 +1582,8 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters CustomConflicts = await GetDefinitionOrDefaultAsync(conflictResult.CustomConflicts), RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName), - HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync() + HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync(), + AllowedLanguages = conflictResult.AllowedLanguages }); } @@ -1918,7 +1953,8 @@ public virtual async Task SaveIgnoredPathsAsync(IConflictResult conflictRe CustomConflicts = await GetDefinitionOrDefaultAsync(conflictResult.CustomConflicts), RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName), - HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync() + HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync(), + AllowedLanguages = conflictResult.AllowedLanguages }); } @@ -2618,7 +2654,8 @@ await Task.Run(() => CustomConflicts = await GetDefinitionOrDefaultAsync(conflictResult.CustomConflicts), RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName), - HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync() + HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync(), + AllowedLanguages = conflictResult.AllowedLanguages }); return exportPatches.Count != 0 ? exportResult && stateResult : stateResult; } @@ -2947,9 +2984,12 @@ protected virtual IEnumerable ParseModFiles(IGame game, IEnumerable { // See if we should skip maybe var onlyFilename = Path.GetFileNameWithoutExtension(fileInfo.FileName); - if (allowedGameLanguages != null && !allowedGameLanguages.Any(p => onlyFilename!.EndsWith(p.Type, StringComparison.OrdinalIgnoreCase))) + if (allowedGameLanguages != null && fileInfo.FileName!.StartsWith(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase)) { - continue; + if (!allowedGameLanguages.Any(p => onlyFilename!.EndsWith(p.Type, StringComparison.OrdinalIgnoreCase))) + { + continue; + } } var fileDefs = parserManager.Parse(new ParserManagerArgs @@ -3478,7 +3518,8 @@ await modPatchExporter.SaveStateAsync(new ModPatchExporterParameters CustomConflicts = await GetDefinitionOrDefaultAsync(conflictResult.CustomConflicts), RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName), - HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync() + HasGameDefinitions = await conflictResult.AllConflicts.HasGameDefinitionsAsync(), + AllowedLanguages = conflictResult.AllowedLanguages }); return true; } diff --git a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs index ca11c0f5..b7c4082c 100644 --- a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs @@ -87,6 +87,11 @@ public class ModHolderControlViewModel : BaseViewModel /// private readonly IGameIndexService gameIndexService; + /// + /// The game language service + /// + private readonly IGameLanguageService gameLanguageService; + /// /// The game service /// @@ -187,11 +192,6 @@ public class ModHolderControlViewModel : BaseViewModel /// private IDisposable gameIndexHandler; - /// - /// The game language service - /// - private readonly IGameLanguageService gameLanguageService; - /// /// The mod invalid replace handler /// @@ -584,7 +584,16 @@ protected virtual async Task AnalyzeModsAsync(long id, PatchStateMode mode, IEnu modPatchCollectionService.InvalidatePatchModState(CollectionMods.SelectedModCollection.Name); modPatchCollectionService.ResetPatchStateCache(); - var allowedLanguages = gameLanguageService.GetSelected(); + IReadOnlyCollection allowedLanguages; + var usedAllowedLanguages = await modPatchCollectionService.GetAllowedLanguagesAsync(CollectionMods.SelectedModCollection.Name); + if (usedAllowedLanguages != null) + { + allowedLanguages = gameLanguageService.GetByAbrv(usedAllowedLanguages); + } + else + { + allowedLanguages = gameLanguageService.GetSelected(); + } var tooLargeMod = false; var game = gameService.GetSelected(); @@ -652,7 +661,7 @@ await Task.Run(async () => { if (definitions != null) { - var result = await modPatchCollectionService.FindConflictsAsync(definitions, CollectionMods.SelectedMods.Select(p => p.Name).ToList(), mode); + var result = await modPatchCollectionService.FindConflictsAsync(definitions, CollectionMods.SelectedMods.Select(p => p.Name).ToList(), mode, allowedLanguages); GCRunner.RunGC(GCCollectionMode.Aggressive, true); return result; } @@ -679,7 +688,7 @@ await Task.Run(async () => { SelectedCollection = CollectionMods.SelectedModCollection, Results = conflicts, - State = mode == PatchStateMode.ReadOnly || mode == PatchStateMode.ReadOnlyWithoutLocalization ? NavigationState.ReadOnlyConflictSolver : NavigationState.ConflictSolver, + State = mode is PatchStateMode.ReadOnly or PatchStateMode.ReadOnlyWithoutLocalization ? NavigationState.ReadOnlyConflictSolver : NavigationState.ConflictSolver, SelectedMods = CollectionMods.SelectedMods.Select(p => p.Name).ToList() }; ReactiveUI.MessageBus.Current.SendMessage(args); From 8a51da1c5a5d7a5aa39cdc490ab142120d9969cb Mon Sep 17 00:00:00 2001 From: bcssov Date: Sun, 25 Feb 2024 22:56:53 +0100 Subject: [PATCH 086/101] Game indexing should also ignore certain localization folders --- .../IGameIndexService.cs | 6 +- .../GameIndexServiceTests.cs | 242 ++++++------------ .../GameIndexService.cs | 61 +++-- .../ModBaseService.cs | 9 +- .../Controls/ModHolderControlViewModel.cs | 2 +- 5 files changed, 134 insertions(+), 186 deletions(-) diff --git a/src/IronyModManager.Services.Common/IGameIndexService.cs b/src/IronyModManager.Services.Common/IGameIndexService.cs index 8ecc593d..48ae2cdc 100644 --- a/src/IronyModManager.Services.Common/IGameIndexService.cs +++ b/src/IronyModManager.Services.Common/IGameIndexService.cs @@ -4,13 +4,14 @@ // Created : 05-27-2021 // // Last Modified By : Mario -// Last Modified On : 09-05-2021 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Linq; @@ -44,8 +45,9 @@ public interface IGameIndexService : IBaseService /// The mod definitions. /// The game. /// The versions. + /// The game languages. /// Task<IIndexedDefinitions>. - Task LoadDefinitionsAsync(IIndexedDefinitions modDefinitions, IGame game, IEnumerable versions); + Task LoadDefinitionsAsync(IIndexedDefinitions modDefinitions, IGame game, IEnumerable versions, IReadOnlyCollection gameLanguages); #endregion Methods } diff --git a/src/IronyModManager.Services.Tests/GameIndexServiceTests.cs b/src/IronyModManager.Services.Tests/GameIndexServiceTests.cs index 0dc13c0b..0ce5f891 100644 --- a/src/IronyModManager.Services.Tests/GameIndexServiceTests.cs +++ b/src/IronyModManager.Services.Tests/GameIndexServiceTests.cs @@ -11,11 +11,11 @@ // // // *********************************************************************** + using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using AutoMapper; using FluentAssertions; @@ -43,6 +43,7 @@ namespace IronyModManager.Services.Tests /// /// Class GameIndexServiceTests. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0028:Simplify collection initialization", Justification = "It's a unit test, back off")] public class GameIndexServiceTests { /// @@ -59,13 +60,14 @@ public class GameIndexServiceTests /// The definition information providers. /// GameIndexService. private static GameIndexService GetService(Mock gameIndexer, Mock storageProvider, Mock modParser, - Mock parserManager, Mock reader, Mock mapper, Mock modWriter, - Mock gameService, IEnumerable definitionInfoProviders = null) + Mock parserManager, Mock reader, Mock mapper, Mock modWriter, + Mock gameService, IEnumerable definitionInfoProviders = null) { var messageBus = new Mock(); messageBus.Setup(p => p.PublishAsync(It.IsAny())); messageBus.Setup(p => p.Publish(It.IsAny())); - return new GameIndexService(messageBus.Object, parserManager.Object, gameIndexer.Object, new Cache(), definitionInfoProviders, reader.Object, modWriter.Object, modParser.Object, gameService.Object, storageProvider.Object, mapper.Object); + return new GameIndexService(messageBus.Object, parserManager.Object, gameIndexer.Object, new Cache(), definitionInfoProviders, reader.Object, modWriter.Object, modParser.Object, gameService.Object, storageProvider.Object, + mapper.Object); } /// @@ -84,7 +86,7 @@ public async Task Should_not_index_definitions_when_no_game() var gameIndexer = new Mock(); var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); - var result = await service.IndexDefinitionsAsync(null, new List() { "3.0.3" }, new IndexedDefinitions()); + var result = await service.IndexDefinitionsAsync(null, new List { "3.0.3" }, new IndexedDefinitions()); result.Should().BeFalse(); } @@ -128,37 +130,24 @@ public async Task Should_index_definitions_when_definition_signature_not_same() gameIndexer.Setup(p => p.ClearDefinitionAsync(It.IsAny(), It.IsAny())).Returns((string p1, IGame p2) => Task.FromResult(false)); gameIndexer.Setup(p => p.WriteVersionAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns((string p1, IGame p2, IEnumerable p3, int p4) => Task.FromResult(false)); gameIndexer.Setup(p => p.FolderCachedAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns((string p1, IGame p2, string p3) => Task.FromResult(false)); - reader.Setup(p => p.GetFiles(It.IsAny())).Returns(new List() { "test1\\1.txt", "test2\\2.txt", "test3\\3.txt" }); - var fileInfos1 = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "test1\\1.txt", - IsBinary = false - } - }; - var fileInfos2 = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "test2\\2.txt", - IsBinary = false - } - }; + reader.Setup(p => p.GetFiles(It.IsAny())).Returns(new List { "test1\\1.txt", "test2\\2.txt", "test3\\3.txt" }); + var fileInfos1 = new List { new FileInfo { Content = new List { "1" }, FileName = "test1\\1.txt", IsBinary = false } }; + var fileInfos2 = new List { new FileInfo { Content = new List { "1" }, FileName = "test2\\2.txt", IsBinary = false } }; reader.Setup(s => s.Read(It.Is(p => p.Contains("test1")), It.IsAny>(), It.IsAny())).Returns(fileInfos1); reader.Setup(s => s.Read(It.Is(p => p.Contains("test2")), It.IsAny>(), It.IsAny())).Returns(fileInfos2); parserManager.Setup(s => s.Parse(It.IsAny())).Returns((ParserManagerArgs args) => { - return new List() { new Definition() + return new List { - Code = args.File, - File = args.File, - ContentSHA = args.File, - Id = args.File, - Type = args.ModName - } }; + new Definition + { + Code = args.File, + File = args.File, + ContentSHA = args.File, + Id = args.File, + Type = args.ModName + } + }; }); var saved = new ConcurrentBag(); gameIndexer.Setup(p => p.SaveDefinitionsAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Returns((string p1, IGame p2, IEnumerable p3) => @@ -169,8 +158,10 @@ public async Task Should_index_definitions_when_definition_signature_not_same() var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); var indexed = new IndexedDefinitions(); - await indexed.InitMapAsync(new List() { new Definition() { File = "test1\\1.txt" }, new Definition() { File = "test2\\3.txt" } }); - var result = await service.IndexDefinitionsAsync(new Game() { ExecutableLocation = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)), GameFolders = new List() { "test1", "test2" } }, new List() { "3.0.3" }, indexed); + await indexed.InitMapAsync(new List { new Definition { File = "test1\\1.txt" }, new Definition { File = "test2\\3.txt" } }); + var result = await service.IndexDefinitionsAsync( + new Game { ExecutableLocation = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)), GameFolders = new List { "test1", "test2" } }, + new List { "3.0.3" }, indexed); result.Should().BeTrue(); saved.Count.Should().Be(2); saved.FirstOrDefault(p => p.Contains("test1")).Should().NotBeNull(); @@ -197,37 +188,24 @@ public async Task Should_index_definitions_when_game_version_signature_not_same( gameIndexer.Setup(p => p.ClearDefinitionAsync(It.IsAny(), It.IsAny())).Returns((string p1, IGame p2) => Task.FromResult(false)); gameIndexer.Setup(p => p.WriteVersionAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns((string p1, IGame p2, IEnumerable p3, int p4) => Task.FromResult(false)); gameIndexer.Setup(p => p.FolderCachedAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns((string p1, IGame p2, string p3) => Task.FromResult(false)); - reader.Setup(p => p.GetFiles(It.IsAny())).Returns(new List() { "test1\\1.txt", "test2\\2.txt", "test3\\3.txt" }); - var fileInfos1 = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "test1\\1.txt", - IsBinary = false - } - }; - var fileInfos2 = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "test2\\2.txt", - IsBinary = false - } - }; + reader.Setup(p => p.GetFiles(It.IsAny())).Returns(new List { "test1\\1.txt", "test2\\2.txt", "test3\\3.txt" }); + var fileInfos1 = new List { new FileInfo { Content = new List { "1" }, FileName = "test1\\1.txt", IsBinary = false } }; + var fileInfos2 = new List { new FileInfo { Content = new List { "1" }, FileName = "test2\\2.txt", IsBinary = false } }; reader.Setup(s => s.Read(It.Is(p => p.Contains("test1")), It.IsAny>(), It.IsAny())).Returns(fileInfos1); reader.Setup(s => s.Read(It.Is(p => p.Contains("test2")), It.IsAny>(), It.IsAny())).Returns(fileInfos2); parserManager.Setup(s => s.Parse(It.IsAny())).Returns((ParserManagerArgs args) => { - return new List() { new Definition() + return new List { - Code = args.File, - File = args.File, - ContentSHA = args.File, - Id = args.File, - Type = args.ModName - } }; + new Definition + { + Code = args.File, + File = args.File, + ContentSHA = args.File, + Id = args.File, + Type = args.ModName + } + }; }); var saved = new ConcurrentBag(); gameIndexer.Setup(p => p.SaveDefinitionsAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Returns((string p1, IGame p2, IEnumerable p3) => @@ -238,8 +216,10 @@ public async Task Should_index_definitions_when_game_version_signature_not_same( var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); var indexed = new IndexedDefinitions(); - await indexed.InitMapAsync(new List() { new Definition() { File = "test1\\1.txt" }, new Definition() { File = "test2\\3.txt" } }); - var result = await service.IndexDefinitionsAsync(new Game() { ExecutableLocation = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)), GameFolders = new List() { "test1", "test2" } }, new List() { "3.0.3" }, indexed); + await indexed.InitMapAsync(new List { new Definition { File = "test1\\1.txt" }, new Definition { File = "test2\\3.txt" } }); + var result = await service.IndexDefinitionsAsync( + new Game { ExecutableLocation = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)), GameFolders = new List { "test1", "test2" } }, + new List { "3.0.3" }, indexed); result.Should().BeTrue(); saved.Count.Should().Be(2); saved.FirstOrDefault(p => p.Contains("test1")).Should().NotBeNull(); @@ -271,39 +251,27 @@ public async Task Should_index_definitions_which_are_not_indexed() { return Task.FromResult(true); } + return Task.FromResult(false); }); - reader.Setup(p => p.GetFiles(It.IsAny())).Returns(new List() { "test1\\1.txt", "test2\\2.txt", "test3\\3.txt" }); - var fileInfos1 = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "test1\\1.txt", - IsBinary = false - } - }; - var fileInfos2 = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "test2\\2.txt", - IsBinary = false - } - }; + reader.Setup(p => p.GetFiles(It.IsAny())).Returns(new List { "test1\\1.txt", "test2\\2.txt", "test3\\3.txt" }); + var fileInfos1 = new List { new FileInfo { Content = new List { "1" }, FileName = "test1\\1.txt", IsBinary = false } }; + var fileInfos2 = new List { new FileInfo { Content = new List { "1" }, FileName = "test2\\2.txt", IsBinary = false } }; reader.Setup(s => s.Read(It.Is(p => p.Contains("test1")), It.IsAny>(), It.IsAny())).Returns(fileInfos1); reader.Setup(s => s.Read(It.Is(p => p.Contains("test2")), It.IsAny>(), It.IsAny())).Returns(fileInfos2); parserManager.Setup(s => s.Parse(It.IsAny())).Returns((ParserManagerArgs args) => { - return new List() { new Definition() + return new List { - Code = args.File, - File = args.File, - ContentSHA = args.File, - Id = args.File, - Type = args.ModName - } }; + new Definition + { + Code = args.File, + File = args.File, + ContentSHA = args.File, + Id = args.File, + Type = args.ModName + } + }; }); var saved = new ConcurrentBag(); gameIndexer.Setup(p => p.SaveDefinitionsAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Returns((string p1, IGame p2, IEnumerable p3) => @@ -314,8 +282,10 @@ public async Task Should_index_definitions_which_are_not_indexed() var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); var indexed = new IndexedDefinitions(); - await indexed.InitMapAsync(new List() { new Definition() { File = "test1\\1.txt" }, new Definition() { File = "test2\\3.txt" } }); - var result = await service.IndexDefinitionsAsync(new Game() { ExecutableLocation = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)), GameFolders = new List() { "test1", "test2" } }, new List() { "3.0.3" }, indexed); + await indexed.InitMapAsync(new List { new Definition { File = "test1\\1.txt" }, new Definition { File = "test2\\3.txt" } }); + var result = await service.IndexDefinitionsAsync( + new Game { ExecutableLocation = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)), GameFolders = new List { "test1", "test2" } }, + new List { "3.0.3" }, indexed); result.Should().BeTrue(); saved.Count.Should().Be(1); saved.FirstOrDefault(p => p.Contains("test1")).Should().NotBeNull(); @@ -342,37 +312,24 @@ public async Task Should_not_index_definitions() gameIndexer.Setup(p => p.ClearDefinitionAsync(It.IsAny(), It.IsAny())).Returns((string p1, IGame p2) => Task.FromResult(false)); gameIndexer.Setup(p => p.WriteVersionAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns((string p1, IGame p2, IEnumerable p3, int p4) => Task.FromResult(false)); gameIndexer.Setup(p => p.FolderCachedAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns((string p1, IGame p2, string p3) => Task.FromResult(true)); - reader.Setup(p => p.GetFiles(It.IsAny())).Returns(new List() { "test1\\1.txt", "test2\\2.txt", "test3\\3.txt" }); - var fileInfos1 = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "test1\\1.txt", - IsBinary = false - } - }; - var fileInfos2 = new List() - { - new FileInfo() - { - Content = new List() { "1" }, - FileName = "test2\\2.txt", - IsBinary = false - } - }; + reader.Setup(p => p.GetFiles(It.IsAny())).Returns(new List { "test1\\1.txt", "test2\\2.txt", "test3\\3.txt" }); + var fileInfos1 = new List { new FileInfo { Content = new List { "1" }, FileName = "test1\\1.txt", IsBinary = false } }; + var fileInfos2 = new List { new FileInfo { Content = new List { "1" }, FileName = "test2\\2.txt", IsBinary = false } }; reader.Setup(s => s.Read(It.Is(p => p.Contains("test1")), It.IsAny>(), It.IsAny())).Returns(fileInfos1); reader.Setup(s => s.Read(It.Is(p => p.Contains("test2")), It.IsAny>(), It.IsAny())).Returns(fileInfos2); parserManager.Setup(s => s.Parse(It.IsAny())).Returns((ParserManagerArgs args) => { - return new List() { new Definition() + return new List { - Code = args.File, - File = args.File, - ContentSHA = args.File, - Id = args.File, - Type = args.ModName - } }; + new Definition + { + Code = args.File, + File = args.File, + ContentSHA = args.File, + Id = args.File, + Type = args.ModName + } + }; }); var saved = new ConcurrentBag(); gameIndexer.Setup(p => p.SaveDefinitionsAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Returns((string p1, IGame p2, IEnumerable p3) => @@ -383,8 +340,8 @@ public async Task Should_not_index_definitions() var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); var indexed = new IndexedDefinitions(); - await indexed.InitMapAsync(new List() { new Definition() { File = "test1\\1.txt" }, new Definition() { File = "test2\\3.txt" } }); - var result = await service.IndexDefinitionsAsync(new Game() { ExecutableLocation = "c:\\test\\test.exe", GameFolders = new List() { "test1", "test2" } }, new List() { "3.0.3" }, indexed); + await indexed.InitMapAsync(new List { new Definition { File = "test1\\1.txt" }, new Definition { File = "test2\\3.txt" } }); + var result = await service.IndexDefinitionsAsync(new Game { ExecutableLocation = "c:\\test\\test.exe", GameFolders = new List { "test1", "test2" } }, new List { "3.0.3" }, indexed); result.Should().BeTrue(); saved.Count.Should().Be(0); } @@ -405,17 +362,9 @@ public async Task Should_not_load_definitions_when_no_game() var gameIndexer = new Mock(); var defs = new IndexedDefinitions(); - await defs.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await defs.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); - var result = await service.LoadDefinitionsAsync(defs, null, new List() { "3.0.3" }); + var result = await service.LoadDefinitionsAsync(defs, null, new List { "3.0.3" }, null); result.Should().Be(defs); } @@ -435,17 +384,9 @@ public async Task Should_not_load_definitions_when_no_version() var gameIndexer = new Mock(); var defs = new IndexedDefinitions(); - await defs.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await defs.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); - var result = await service.LoadDefinitionsAsync(defs, new Game(), new List() { string.Empty }); + var result = await service.LoadDefinitionsAsync(defs, new Game(), new List { string.Empty }, null); result.Should().Be(defs); } @@ -467,17 +408,9 @@ public async Task Should_not_load_definitions_when_definitions_dont_exist() gameIndexer.Setup(p => p.GameVersionsSameAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Returns((string p1, IGame p2, IEnumerable p3) => Task.FromResult(false)); var defs = new IndexedDefinitions(); - await defs.InitMapAsync(new List() - { - new Definition() - { - Type = "test", - Id = "1", - ModName = "test" - } - }); + await defs.InitMapAsync(new List { new Definition { Type = "test", Id = "1", ModName = "test" } }); var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); - var result = await service.LoadDefinitionsAsync(defs, new Game(), new List() { "3.0.3" }); + var result = await service.LoadDefinitionsAsync(defs, new Game(), new List { "3.0.3" }, null); result.Should().Be(defs); } @@ -499,22 +432,13 @@ public async Task Should_load_definitions() var gameIndexer = new Mock(); storageProvider.Setup(p => p.GetRootStoragePath()).Returns("c:\\test"); gameIndexer.Setup(p => p.GameVersionsSameAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Returns((string p1, IGame p2, IEnumerable p3) => Task.FromResult(true)); - var gameDefs = new List() { new Definition() { File = "test\\testgame.txt", Type = "test", Id = "2", ModName = "test game" } }; + var gameDefs = new List { new Definition { File = "test\\testgame.txt", Type = "test", Id = "2", ModName = "test game" } }; gameIndexer.Setup(p => p.GetDefinitionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns((string p1, IGame p2, string p3) => Task.FromResult(gameDefs as IEnumerable)); var defs = new IndexedDefinitions(); - await defs.InitMapAsync(new List() - { - new Definition() - { - File = "test\\test.txt", - Type = "test", - Id = "1", - ModName = "test" - } - }); + await defs.InitMapAsync(new List { new Definition { File = "test\\test.txt", Type = "test", Id = "1", ModName = "test" } }); var service = GetService(gameIndexer, storageProvider, modParser, parserManager, reader, mapper, modWriter, gameService); - var result = await service.LoadDefinitionsAsync(defs, new Game() { Name = "fake game" }, new List() { "3.0.3" }); + var result = await service.LoadDefinitionsAsync(defs, new Game { Name = "fake game" }, new List { "3.0.3" }, null); (await result.GetAllAsync()).Count().Should().Be(2); (await result.GetAllAsync()).FirstOrDefault(p => p.ModName == "fake game").Should().NotBeNull(); } diff --git a/src/IronyModManager.Services/GameIndexService.cs b/src/IronyModManager.Services/GameIndexService.cs index 2547013a..e8797218 100644 --- a/src/IronyModManager.Services/GameIndexService.cs +++ b/src/IronyModManager.Services/GameIndexService.cs @@ -4,7 +4,7 @@ // Created : 05-27-2021 // // Last Modified By : Mario -// Last Modified On : 02-23-2024 +// Last Modified On : 02-25-2024 // *********************************************************************** // // Mario @@ -41,24 +41,10 @@ namespace IronyModManager.Services /// /// Class GameIndexService. /// Implements the - /// Implements the + /// Implements the /// /// - /// - /// - /// Initializes a new instance of the class. - /// - /// The message bus. - /// The parser manager. - /// The game indexer. - /// The cache. - /// The definition information providers. - /// The reader. - /// The mod writer. - /// The mod parser. - /// The game service. - /// The storage provider. - /// The mapper. + /// public class GameIndexService( IMessageBus messageBus, IParserManager parserManager, @@ -194,13 +180,52 @@ await Task.Run(async () => /// The mod definitions. /// The game. /// The versions. + /// The game languages. /// IIndexedDefinitions. - public virtual async Task LoadDefinitionsAsync(IIndexedDefinitions modDefinitions, IGame game, IEnumerable versions) + public virtual async Task LoadDefinitionsAsync(IIndexedDefinitions modDefinitions, IGame game, IEnumerable versions, IReadOnlyCollection gameLanguages) { if (game != null && versions != null && versions.Any() && await gameIndexer.GameVersionsSameAsync(GetStoragePath(), game, versions)) { var gameDefinitions = new ConcurrentBag(); var directories = await modDefinitions.GetAllDirectoryKeysAsync(); + // Kinda need to force insert localisation directory itself + if (directories.Any(p => p.StartsWith(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase)) && + !directories.Any(p => p.Equals(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase))) + { + var newDirs = new List(directories) { Shared.Constants.LocalizationDirectory }; + directories = newDirs; + } + + if (gameLanguages != null && gameLanguages.Count != 0) + { + var folders = gameLanguages.Select(p => p.Type[2..]); + var filtered = new List(); + foreach (var dir in directories) + { + // So far games that support CS have structure localisation -> l_english -> files though language id part is stripped (l_ part) + if (dir.StartsWith(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase)) + { + if (dir.StandardizeDirectorySeparator().Any(p => p == Path.DirectorySeparatorChar)) + { + if (folders.Any(p => dir!.EndsWith(p, StringComparison.OrdinalIgnoreCase))) + { + filtered.Add(dir); + } + } + else + { + filtered.Add(dir); + } + } + else + { + filtered.Add(dir); + } + } + + directories = filtered; + } + double processed = 0; double total = directories.Count(); double previousProgress = 0; diff --git a/src/IronyModManager.Services/ModBaseService.cs b/src/IronyModManager.Services/ModBaseService.cs index e1af1f68..f510a320 100644 --- a/src/IronyModManager.Services/ModBaseService.cs +++ b/src/IronyModManager.Services/ModBaseService.cs @@ -49,7 +49,7 @@ namespace IronyModManager.Services /// The game service. /// The storage provider. /// The mapper. - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. public abstract class ModBaseService( ICache cache, IEnumerable definitionInfoProviders, @@ -334,7 +334,7 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum switch (uniqueDefinitions.Count) { - case 1 when (overrideSkipped || filteredGameDefinitions): + case 1 when overrideSkipped || filteredGameDefinitions: { var definition = definitionEvals.FirstOrDefault(p => !p.Definition.IsFromGame); definition ??= definitionEvals.FirstOrDefault(); @@ -565,10 +565,7 @@ protected virtual IEnumerable GetInstalledModsInternal(string game, bool i /// game protected virtual IEnumerable GetInstalledModsInternal(IGame game, bool ignorePatchMods) { - if (game == null) - { - throw new ArgumentNullException(nameof(game)); - } + ArgumentNullException.ThrowIfNull(game); var mods = Cache.Get>(new CacheGetParameters { Region = ModsCacheRegion, Prefix = game.Type, Key = GetModsCacheKey(ignorePatchMods) }); if (mods != null) diff --git a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs index b7c4082c..8eb6af8d 100644 --- a/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/ModHolderControlViewModel.cs @@ -648,7 +648,7 @@ await Task.Run(async () => stopWatch.Restart(); definitions = await Task.Run(async () => { - var result = await gameIndexService.LoadDefinitionsAsync(definitions, game, versions); + var result = await gameIndexService.LoadDefinitionsAsync(definitions, game, versions, allowedLanguages); GCRunner.RunGC(GCCollectionMode.Aggressive, true); return result; From 53f39cd18ad249bbc24ebf1a7c97e2c36cf5a6f2 Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 26 Feb 2024 00:14:18 +0100 Subject: [PATCH 087/101] Make close options button bigger --- src/IronyModManager/Views/Controls/OptionsControlView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IronyModManager/Views/Controls/OptionsControlView.xaml b/src/IronyModManager/Views/Controls/OptionsControlView.xaml index aee70f39..6cf3c57a 100644 --- a/src/IronyModManager/Views/Controls/OptionsControlView.xaml +++ b/src/IronyModManager/Views/Controls/OptionsControlView.xaml @@ -138,7 +138,7 @@ - public static readonly SolidColorBrush DarkDiffInsertedPieces = SolidColorBrush.Parse("#1B4F35"); + /// + /// A public static readonly SolidColorBrush named DarkDiffModifiedLine. + /// + public static readonly SolidColorBrush DarkDiffModifiedLine = SolidColorBrush.Parse("#C49400"); + /// /// A public static readonly SolidColorBrush named DarkDiffModifiedPieces. /// @@ -86,6 +91,11 @@ public class Constants /// public static readonly SolidColorBrush LightDiffInsertedPieces = SolidColorBrush.Parse("#66cc99"); + /// + /// A public static readonly SolidColorBrush named LightDiffModifiedLine. + /// + public static readonly SolidColorBrush LightDiffModifiedLine = SolidColorBrush.Parse("#ffe28b"); + /// /// A public static readonly SolidColorBrush named LightDiffModifiedPieces. /// diff --git a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs index 9c1eb0da..2106820a 100644 --- a/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs +++ b/src/IronyModManager/Implementation/AvaloniaEdit/DiffBackgroundRenderer.cs @@ -4,7 +4,7 @@ // Created : 02-19-2024 // // Last Modified By : Mario -// Last Modified On : 02-23-2024 +// Last Modified On : 02-28-2024 // *********************************************************************** // // Mario @@ -97,7 +97,7 @@ public void Draw(TextView textView, DrawingContext drawingContext) DiffPlex.DiffBuilder.Model.ChangeType.Deleted => IsLightTheme() ? Constants.LightDiffDeletedLine : Constants.DarkDiffDeletedLine, DiffPlex.DiffBuilder.Model.ChangeType.Inserted => IsLightTheme() ? Constants.LightDiffInsertedLine : Constants.DarkDiffInsertedLine, DiffPlex.DiffBuilder.Model.ChangeType.Imaginary => IsLightTheme() ? Constants.LightDiffImaginaryLine : Constants.DarkDiffImaginaryLine, - DiffPlex.DiffBuilder.Model.ChangeType.Modified => IsLightTheme() ? Constants.LightDiffModifiedPieces : Constants.DarkDiffModifiedPieces, + DiffPlex.DiffBuilder.Model.ChangeType.Modified => IsLightTheme() ? Constants.LightDiffModifiedLine : Constants.DarkDiffModifiedLine, _ => default }; From 74384d1f7e8adf5b20ed6061b21ec335735f80bf Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 4 Mar 2024 20:59:45 +0100 Subject: [PATCH 091/101] Ensure in Paradox launcher import backwards compatibility to guess if v2 is only if id length > 4 --- .../Mods/Importers/ParadoxLauncherImporter.cs | 64 +++++++++++-------- .../Importers/ParadoxLauncherImporterBeta.cs | 21 ++---- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/src/IronyModManager.IO/Mods/Importers/ParadoxLauncherImporter.cs b/src/IronyModManager.IO/Mods/Importers/ParadoxLauncherImporter.cs index ca3f3826..19c0141a 100644 --- a/src/IronyModManager.IO/Mods/Importers/ParadoxLauncherImporter.cs +++ b/src/IronyModManager.IO/Mods/Importers/ParadoxLauncherImporter.cs @@ -4,13 +4,14 @@ // Created : 08-12-2020 // // Last Modified By : Mario -// Last Modified On : 02-23-2023 +// Last Modified On : 03-04-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Data; @@ -28,39 +29,25 @@ namespace IronyModManager.IO.Mods.Importers { /// - /// Class ParadoxLauncherImporter. + /// The paradox launcher importer. /// [ExcludeFromCoverage("Skipping testing IO logic.")] - internal class ParadoxLauncherImporter + internal class ParadoxLauncherImporter(ILogger logger) { #region Fields /// /// The logger /// - private readonly ILogger logger; + private readonly ILogger logger = logger; /// /// The trace /// - private readonly SQLTraceLog trace; + private readonly SQLTraceLog trace = new(logger); #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - public ParadoxLauncherImporter(ILogger logger) - { - this.logger = logger; - trace = new SQLTraceLog(logger); - } - - #endregion Constructors - #region Enums /// @@ -99,6 +86,7 @@ public async Task DatabaseImportAsync(ModCollectionExpo { return null; } + // Caching sucks in this ORM DbFieldCache.Flush(); FieldCache.Flush(); @@ -140,13 +128,15 @@ public virtual async Task JsonImportAsync(ModCollection { return (ex, null); } + if (!string.IsNullOrWhiteSpace(model.Game) && !string.IsNullOrWhiteSpace(model.Name)) { // Validate whether this really is v2 (execting length larger than 4 as a dumb best guess) - if (model.Mods.Any(p => p.Position.Length >= 4)) + if (model.Mods.Any(p => p.Position.Length > 4)) { var result = DIResolver.Get(); result.Name = model.Name; + // Will need to lookup the game and mod ids in the mod service result.Game = model.Game; var mods = model.Mods.Where(p => p.Enabled).OrderBy(p => p.Position); @@ -157,18 +147,22 @@ public virtual async Task JsonImportAsync(ModCollection { result.ParadoxId = pdxid; } + if (long.TryParse(p.SteamId, out var steamId)) { result.SteamId = steamId; } + return result; }).ToList(); return (null, result); } } } + return (null, null); } + async Task<(Exception, ICollectionImportResult)> parseV3() { var content = await File.ReadAllTextAsync(parameters.File); @@ -183,10 +177,12 @@ public virtual async Task JsonImportAsync(ModCollection { return (ex, null); } + if (!string.IsNullOrWhiteSpace(model.Game) && !string.IsNullOrWhiteSpace(model.Name)) { var result = DIResolver.Get(); result.Name = model.Name; + // Will need to lookup the game and mod ids in the mod service result.Game = model.Game; var mods = model.Mods.Where(p => p.Enabled).OrderBy(p => p.Position); @@ -197,15 +193,18 @@ public virtual async Task JsonImportAsync(ModCollection { result.ParadoxId = pdxid; } + if (long.TryParse(p.SteamId, out var steamId)) { result.SteamId = steamId; } + return result; }).ToList(); return (null, result); } } + return (null, null); } @@ -217,24 +216,29 @@ public virtual async Task JsonImportAsync(ModCollection { return result.Item2; } + if (result.Item1 != null) { exceptions.Add(result.Item1); } + result = await parseV3(); if (result.Item2 != null) { return result.Item2; } + if (result.Item1 != null) { exceptions.Add(result.Item1); } + if (exceptions.Any()) { throw new AggregateException(exceptions); } } + return null; } @@ -251,7 +255,7 @@ protected virtual async Task DatabaseImportv2Async(ModC var activeCollection = (await con.QueryAsync(p => p.IsActive == true, trace: trace)).FirstOrDefault(); if (activeCollection != null) { - var collectionMods = await con.QueryAsync(p => p.PlaysetId == activeCollection.Id.ToString(), trace: trace); + var collectionMods = await con.QueryAsync(p => p.PlaysetId == activeCollection.Id, trace: trace); if (collectionMods?.Count() > 0) { var mods = await con.QueryAllAsync(trace: trace); @@ -269,6 +273,7 @@ protected virtual async Task DatabaseImportv2Async(ModC { result.FullPaths = validMods.Select(p => p.DirPath.StandardizeDirectorySeparator()).ToList(); } + result.ModNames = validMods.Select(p => p.DisplayName).ToList(); return result; } @@ -279,8 +284,8 @@ protected virtual async Task DatabaseImportv2Async(ModC { logger.Error(ex); } + con.Close(); - con.Dispose(); return null; } @@ -297,7 +302,7 @@ protected virtual async Task DatabaseImportv3Async(ModC var activeCollection = (await con.QueryAsync(p => p.IsActive == true, trace: trace)).FirstOrDefault(); if (activeCollection != null) { - var collectionMods = await con.QueryAsync(p => p.PlaysetId == activeCollection.Id.ToString(), trace: trace); + var collectionMods = await con.QueryAsync(p => p.PlaysetId == activeCollection.Id, trace: trace); if (collectionMods?.Count() > 0) { var mods = await con.QueryAllAsync(trace: trace); @@ -315,6 +320,7 @@ protected virtual async Task DatabaseImportv3Async(ModC { result.FullPaths = validMods.Select(p => p.DirPath.StandardizeDirectorySeparator()).ToList(); } + result.ModNames = validMods.Select(p => p.DisplayName).ToList(); return result; } @@ -325,8 +331,8 @@ protected virtual async Task DatabaseImportv3Async(ModC { logger.Error(ex); } + con.Close(); - con.Dispose(); return null; } @@ -343,7 +349,7 @@ protected virtual async Task DatabaseImportv4Async(ModC var activeCollection = (await con.QueryAsync(p => p.IsActive == true, trace: trace)).FirstOrDefault(); if (activeCollection != null) { - var collectionMods = await con.QueryAsync(p => p.PlaysetId == activeCollection.Id.ToString(), trace: trace); + var collectionMods = await con.QueryAsync(p => p.PlaysetId == activeCollection.Id, trace: trace); if (collectionMods?.Count() > 0) { var mods = await con.QueryAllAsync(trace: trace); @@ -361,6 +367,7 @@ protected virtual async Task DatabaseImportv4Async(ModC { result.FullPaths = validMods.Select(p => p.DirPath.StandardizeDirectorySeparator()).ToList(); } + result.ModNames = validMods.Select(p => p.DisplayName).ToList(); return result; } @@ -371,8 +378,8 @@ protected virtual async Task DatabaseImportv4Async(ModC { logger.Error(ex); } + con.Close(); - con.Dispose(); return null; } @@ -383,7 +390,7 @@ protected virtual async Task DatabaseImportv4Async(ModC /// System.String. protected virtual string GetDbPath(ModCollectionExporterParams parameters) { - return Path.Combine(Path.GetDirectoryName(parameters.ModDirectory), Constants.Sql_db_path); + return Path.Combine(Path.GetDirectoryName(parameters.ModDirectory)!, Constants.Sql_db_path); } /// @@ -413,6 +420,7 @@ private async Task GetVersionAsync(ModCollectionExporterParams paramete { return Version.v5; } + if (changes.Any(c => c.Name.Equals(Constants.SqlV4Id.Name, StringComparison.OrdinalIgnoreCase))) { return Version.v4; @@ -422,8 +430,8 @@ private async Task GetVersionAsync(ModCollectionExporterParams paramete catch { } + con.Close(); - con.Dispose(); return Version.Default; } diff --git a/src/IronyModManager.IO/Mods/Importers/ParadoxLauncherImporterBeta.cs b/src/IronyModManager.IO/Mods/Importers/ParadoxLauncherImporterBeta.cs index 80dd3b17..f326d62c 100644 --- a/src/IronyModManager.IO/Mods/Importers/ParadoxLauncherImporterBeta.cs +++ b/src/IronyModManager.IO/Mods/Importers/ParadoxLauncherImporterBeta.cs @@ -4,13 +4,14 @@ // Created : 11-16-2021 // // Last Modified By : Mario -// Last Modified On : 11-16-2021 +// Last Modified On : 03-04-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.IO; @@ -27,21 +28,11 @@ namespace IronyModManager.IO.Mods.Importers /// Implements the /// /// + /// The logger. + /// Initializes a new instance of the class. [ExcludeFromCoverage("Skipping testing IO logic.")] - internal class ParadoxLauncherImporterBeta : ParadoxLauncherImporter + internal class ParadoxLauncherImporterBeta(ILogger logger) : ParadoxLauncherImporter(logger) { - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - public ParadoxLauncherImporterBeta(ILogger logger) : base(logger) - { - } - - #endregion Constructors - #region Methods /// @@ -62,7 +53,7 @@ public override Task JsonImportAsync(ModCollectionExpor /// System.String. protected override string GetDbPath(ModCollectionExporterParams parameters) { - return Path.Combine(Path.GetDirectoryName(parameters.ModDirectory), Constants.Sql_db_beta_path); + return Path.Combine(Path.GetDirectoryName(parameters.ModDirectory)!, Constants.Sql_db_beta_path); } #endregion Methods From 71b5efc89ac24bf84b1f4fb96f4d7604c3d1e562 Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 4 Mar 2024 21:19:43 +0100 Subject: [PATCH 092/101] Add natural sort for mod collections as requested in #490 --- .../ModBaseService.cs | 21 +-- .../ModCollectionService.cs | 173 +++++++++--------- .../IronyModManager.Shared.csproj | 3 +- 3 files changed, 97 insertions(+), 100 deletions(-) diff --git a/src/IronyModManager.Services/ModBaseService.cs b/src/IronyModManager.Services/ModBaseService.cs index f510a320..57bcfce7 100644 --- a/src/IronyModManager.Services/ModBaseService.cs +++ b/src/IronyModManager.Services/ModBaseService.cs @@ -4,7 +4,7 @@ // Created : 04-07-2020 // // Last Modified By : Mario -// Last Modified On : 02-23-2024 +// Last Modified On : 03-04-2024 // *********************************************************************** // // Mario @@ -33,23 +33,16 @@ using IronyModManager.Shared.Cache; using IronyModManager.Shared.Models; using IronyModManager.Storage.Common; +using NaturalSort.Extension; using ValueType = IronyModManager.Shared.Models.ValueType; namespace IronyModManager.Services { /// - /// Class ModBaseService. Implements the + /// Class ModBaseService. + /// Implements the /// /// - /// The cache. - /// The definition information providers. - /// The reader. - /// The mod writer. - /// The mod parser. - /// The game service. - /// The storage provider. - /// The mapper. - /// Initializes a new instance of the class. public abstract class ModBaseService( ICache cache, IEnumerable definitionInfoProviders, @@ -351,6 +344,7 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum break; } + // Has same filenames? case > 1 when uniqueDefinitions.GroupBy(p => p.FileNameCI).Count() == 1: { @@ -371,6 +365,7 @@ protected virtual IPriorityDefinitionResult EvalDefinitionPriorityInternal(IEnum break; } + // Using FIOS or LIOS? case > 1 when isFios: { @@ -498,7 +493,7 @@ protected virtual IEnumerable GetAllModCollectionsInternal() var collections = StorageProvider.GetModCollections().Where(s => s.Game.Equals(game.Type)); if (collections.Any()) { - return collections.OrderBy(p => p.Name); + return collections.OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase.WithNaturalSort()); } return []; @@ -907,7 +902,7 @@ protected virtual async Task PopulateModFilesInternalAsync(IEnumerable(); + localMod.Files = []; logger.Error(ex); } finally diff --git a/src/IronyModManager.Services/ModCollectionService.cs b/src/IronyModManager.Services/ModCollectionService.cs index daf4cf20..3593695a 100644 --- a/src/IronyModManager.Services/ModCollectionService.cs +++ b/src/IronyModManager.Services/ModCollectionService.cs @@ -1,17 +1,17 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Services // Author : Mario // Created : 03-04-2020 // // Last Modified By : Mario -// Last Modified On : 07-12-2023 +// Last Modified On : 03-04-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.IO; @@ -33,15 +33,23 @@ namespace IronyModManager.Services { - /// - /// Class ModCollectionService. - /// Implements the - /// Implements the + /// The mod collection service. /// /// /// - public class ModCollectionService : ModBaseService, IModCollectionService + public class ModCollectionService( + IMessageBus messageBus, + IReportExportService exportService, + ICache cache, + IEnumerable definitionInfoProviders, + IReader reader, + IModWriter modWriter, + IModParser modParser, + IGameService gameService, + IModCollectionExporter modCollectionExporter, + IStorageProvider storageProvider, + IMapper mapper) : ModBaseService(cache, definitionInfoProviders, reader, modWriter, modParser, gameService, storageProvider, mapper), IModCollectionService { #region Fields @@ -53,48 +61,20 @@ public class ModCollectionService : ModBaseService, IModCollectionService /// /// The mod report exporter /// - private readonly IReportExportService exportService; + private readonly IReportExportService exportService = exportService; /// /// The message bus /// - private readonly IMessageBus messageBus; + private readonly IMessageBus messageBus = messageBus; /// /// The mod collection exporter /// - private readonly IModCollectionExporter modCollectionExporter; + private readonly IModCollectionExporter modCollectionExporter = modCollectionExporter; #endregion Fields - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The message bus. - /// The export service. - /// The cache. - /// The definition information providers. - /// The reader. - /// The mod writer. - /// The mod parser. - /// The game service. - /// The mod collection exporter. - /// The storage provider. - /// The mapper. - public ModCollectionService(IMessageBus messageBus, IReportExportService exportService, ICache cache, - IEnumerable definitionInfoProviders, IReader reader, IModWriter modWriter, - IModParser modParser, IGameService gameService, IModCollectionExporter modCollectionExporter, - IStorageProvider storageProvider, IMapper mapper) : base(cache, definitionInfoProviders, reader, modWriter, modParser, gameService, storageProvider, mapper) - { - this.messageBus = messageBus; - this.exportService = exportService; - this.modCollectionExporter = modCollectionExporter; - } - - #endregion Constructors - #region Enums /// @@ -143,6 +123,7 @@ public virtual IModCollection Create() { return null; } + var instance = GetModelInstance(); instance.Game = game.Type; return instance; @@ -160,6 +141,7 @@ public virtual bool Delete(string name) { return false; } + lock (serviceLock) { var collections = StorageProvider.GetModCollections().ToList(); @@ -173,6 +155,7 @@ public virtual bool Delete(string name) } } } + return false; } @@ -202,14 +185,16 @@ public virtual Task ExportAsync(string file, IModCollection modCollection, { return Task.FromResult(false); } + var collection = Mapper.Map(modCollection); if (string.IsNullOrWhiteSpace(collection.MergedFolderName) && exportMods) { collection.MergedFolderName = collection.Name.GenerateValidFileName(); } + var path = GetPatchModDirectory(game, modCollection); var modNameOverride = $"({collection.Name}) "; - var parameters = new ModCollectionExporterParams() + var parameters = new ModCollectionExporterParams { File = file, Mod = collection, @@ -243,28 +228,28 @@ public virtual async Task ExportHashReportAsync(IEnumerable mods, st { var modExport = mods.ToList(); var collection = GetAllModCollectionsInternal().FirstOrDefault(p => p.IsSelected); - var patchModName = GenerateCollectionPatchName(collection.Name); + var patchModName = GenerateCollectionPatchName(collection!.Name); var allMods = GetInstalledModsInternal(GameService.GetSelected(), false); var patchMod = allMods.FirstOrDefault(p => p.Name.Equals(patchModName)); if (patchMod == null) { var game = GameService.GetSelected(); - if (await ModWriter.ModDirectoryExistsAsync(new ModWriterParameters() - { - RootDirectory = GetPatchModDirectory(game, patchModName) - })) + if (await ModWriter.ModDirectoryExistsAsync(new ModWriterParameters { RootDirectory = GetPatchModDirectory(game, patchModName) })) { patchMod = GeneratePatchModDescriptor(allMods, game, patchModName); } } + if (patchMod != null && collection.PatchModEnabled) { modExport.Add(patchMod); } + await PopulateModFilesInternalAsync(modExport); var reports = await ParseReportAsync(modExport); return await exportService.ExportAsync(reports, path); } + return false; } @@ -281,8 +266,9 @@ public virtual Task ExportParadoxLauncher202110JsonAsync(string file, IMod { return Task.FromResult(false); } + var collection = Mapper.Map(modCollection); - var parameters = new ModCollectionExporterParams() + var parameters = new ModCollectionExporterParams { File = file, Mod = collection, @@ -306,8 +292,9 @@ public virtual Task ExportParadoxLauncherJsonAsync(string file, IModCollec { return Task.FromResult(false); } + var collection = Mapper.Map(modCollection); - var parameters = new ModCollectionExporterParams() + var parameters = new ModCollectionExporterParams { File = file, Mod = collection, @@ -330,12 +317,14 @@ public virtual IModCollection Get(string name) { return null; } + var collections = StorageProvider.GetModCollections(); if (collections?.Count() > 0) { var collection = collections.FirstOrDefault(c => c.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && c.Game.Equals(game.Type)); return collection; } + return null; } @@ -360,18 +349,15 @@ public virtual async Task GetImportedCollectionDetailsAsync(stri { return null; } + var instance = GetModelInstance(); - var result = await modCollectionExporter.ImportAsync(new ModCollectionExporterParams() - { - File = file, - Mod = instance, - DescriptorType = MapDescriptorType(game.ModDescriptorType) - }); + var result = await modCollectionExporter.ImportAsync(new ModCollectionExporterParams { File = file, Mod = instance, DescriptorType = MapDescriptorType(game.ModDescriptorType) }); if (result != null) { MapImportResult(instance, result, false); return instance; } + return null; } @@ -387,6 +373,7 @@ public async Task ImportAsync(string file) { return null; } + var instance = await GetImportedCollectionDetailsAsync(file); if (instance != null) { @@ -395,21 +382,23 @@ public async Task ImportAsync(string file) { game = GameService.Get().FirstOrDefault(p => p.Type.Equals(instance.Game)); } + var path = GetPatchModDirectory(game, instance); var exportPath = GetPatchModDirectory(game, !string.IsNullOrWhiteSpace(instance.MergedFolderName) ? instance.MergedFolderName : instance.Name); - var result = await modCollectionExporter.ImportModDirectoryAsync(new ModCollectionExporterParams() + var result = await modCollectionExporter.ImportModDirectoryAsync(new ModCollectionExporterParams { File = file, ModDirectory = path, Mod = instance, ExportModDirectory = exportPath, - DescriptorType = MapDescriptorType(game.ModDescriptorType) + DescriptorType = MapDescriptorType(game!.ModDescriptorType) }); if (result) { return instance; } } + return null; } @@ -426,26 +415,26 @@ public virtual async Task> ImportHashReportAsync(IEnume { return null; } + var modExport = mods.ToList(); var collection = GetAllModCollectionsInternal().FirstOrDefault(p => p.IsSelected); - var patchModName = GenerateCollectionPatchName(collection.Name); + var patchModName = GenerateCollectionPatchName(collection!.Name); var allMods = GetInstalledModsInternal(GameService.GetSelected(), false); var patchMod = allMods.FirstOrDefault(p => p.Name.Equals(patchModName)); if (patchMod == null) { var game = GameService.GetSelected(); - if (await ModWriter.ModDirectoryExistsAsync(new ModWriterParameters() - { - RootDirectory = GetPatchModDirectory(game, patchModName) - })) + if (await ModWriter.ModDirectoryExistsAsync(new ModWriterParameters { RootDirectory = GetPatchModDirectory(game, patchModName) })) { patchMod = GeneratePatchModDescriptor(allMods, game, patchModName); } } + if (patchMod != null) { modExport.Add(patchMod); } + await PopulateModFilesInternalAsync(modExport); var currentReports = await ParseReportAsync(modExport); return exportService.CompareReports(currentReports.ToList(), importedReports.ToList()); @@ -503,6 +492,7 @@ public virtual Task ImportParadoxosAsync(string file) /// /// The collection. /// true if XXXX, false otherwise. + /// nameof(collection) /// collection public virtual bool Save(IModCollection collection) { @@ -510,11 +500,13 @@ public virtual bool Save(IModCollection collection) { throw new ArgumentNullException(nameof(collection)); } + var game = GameService.GetSelected(); if (game == null) { return false; } + lock (serviceLock) { var collections = StorageProvider.GetModCollections().ToList(); @@ -525,6 +517,7 @@ public virtual bool Save(IModCollection collection) { collections.Remove(existing); } + if (collection.IsSelected) { foreach (var item in collections.Where(p => p.Game.Equals(collection.Game) && p.IsSelected)) @@ -533,6 +526,7 @@ public virtual bool Save(IModCollection collection) } } } + collections.Add(collection); return StorageProvider.SetModCollections(collections); } @@ -556,6 +550,7 @@ protected virtual double GetProgressPercentage(double total, double processed, d { perc = maxPerc; } + return perc; } @@ -570,13 +565,7 @@ protected virtual async Task ImportModsAsync(ImportType importTy async Task performImport(IGame game) { var instance = Create(); - var parameters = new ModCollectionExporterParams() - { - ModDirectory = Path.Combine(game.UserDirectory, Shared.Constants.ModDirectory), - File = file, - Mod = instance, - DescriptorType = MapDescriptorType(game.ModDescriptorType) - }; + var parameters = new ModCollectionExporterParams { ModDirectory = Path.Combine(game.UserDirectory, Shared.Constants.ModDirectory), File = file, Mod = instance, DescriptorType = MapDescriptorType(game.ModDescriptorType) }; ICollectionImportResult result = null; switch (importType) { @@ -605,17 +594,17 @@ async Task performImport(IGame game) { DIResolver.Get().Error(ex); } - break; - default: break; } + if (result != null) { // Order of operations is very important here MapImportResult(instance, result, true); return instance; } + return null; } @@ -624,6 +613,7 @@ async Task performImport(IGame game) { return null; } + return await performImport(game); } @@ -644,6 +634,7 @@ protected virtual void MapImportResult(IModCollection modCollection, ICollection modCollection.Game = collectionGame.Type; } } + modCollection.IsSelected = importResult.IsSelected; modCollection.MergedFolderName = importResult.MergedFolderName; modCollection.ModNames = importResult.ModNames; @@ -662,8 +653,11 @@ protected virtual void MapImportResult(IModCollection modCollection, ICollection // Filtering as local mods don't have priority in case of paradox launcher json import var filteredMods = mods.GroupBy(p => p.RemoteId).Select(p => p.Any(o => o.Source != ModSource.Local) ? p.FirstOrDefault(o => o.Source != ModSource.Local) : p.FirstOrDefault()); - var collectionMods = filteredMods.Where(p => importResult.ModIds.Any(x => (x.ParadoxId.HasValue && x.ParadoxId.GetValueOrDefault() == p.RemoteId.GetValueOrDefault()) || (x.SteamId.HasValue && x.SteamId.GetValueOrDefault() == p.RemoteId.GetValueOrDefault()))). - OrderBy(p => sort.IndexOf(sort.FirstOrDefault(x => (x.ParadoxId.HasValue && x.ParadoxId.GetValueOrDefault() == p.RemoteId.GetValueOrDefault()) || (x.SteamId.HasValue && x.SteamId.GetValueOrDefault() == p.RemoteId.GetValueOrDefault())))).ToList(); + var collectionMods = filteredMods + .Where(p => importResult.ModIds.Any(x => + (x.ParadoxId.HasValue && x.ParadoxId.GetValueOrDefault() == p!.RemoteId.GetValueOrDefault()) || (x.SteamId.HasValue && x.SteamId.GetValueOrDefault() == p!.RemoteId.GetValueOrDefault()))).OrderBy(p => + sort.IndexOf(sort.FirstOrDefault(x => + (x.ParadoxId.HasValue && x.ParadoxId.GetValueOrDefault() == p.RemoteId.GetValueOrDefault()) || (x.SteamId.HasValue && x.SteamId.GetValueOrDefault() == p.RemoteId.GetValueOrDefault())))).ToList(); collectionMods = collectionMods.GroupBy(p => p.FullPath).Select(p => p.FirstOrDefault()).ToList(); modCollection.Mods = collectionMods.Select(p => p.DescriptorFile).ToList(); modCollection.ModNames = collectionMods.Select(p => p.Name).ToList(); @@ -676,8 +670,8 @@ protected virtual void MapImportResult(IModCollection modCollection, ICollection if (mods.Any()) { var sort = importResult.FullPaths.ToList(); - var collectionMods = mods.Where(p => importResult.FullPaths.Any(x => x.StandardizeDirectorySeparator().Equals(p.FullPath.StandardizeDirectorySeparator(), StringComparison.OrdinalIgnoreCase))). - OrderBy(p => sort.IndexOf(sort.FirstOrDefault(x => x.StandardizeDirectorySeparator().Equals(p.FullPath.StandardizeDirectorySeparator(), StringComparison.OrdinalIgnoreCase)))).ToList(); + var collectionMods = mods.Where(p => importResult.FullPaths.Any(x => x.StandardizeDirectorySeparator().Equals(p.FullPath.StandardizeDirectorySeparator(), StringComparison.OrdinalIgnoreCase))).OrderBy(p => + sort.IndexOf(sort.FirstOrDefault(x => x.StandardizeDirectorySeparator().Equals(p.FullPath.StandardizeDirectorySeparator(), StringComparison.OrdinalIgnoreCase)))).ToList(); collectionMods = collectionMods.GroupBy(p => p.FullPath).Select(p => p.FirstOrDefault()).ToList(); modCollection.Mods = collectionMods.Select(p => p.DescriptorFile).ToList(); modCollection.ModNames = collectionMods.Select(p => p.Name).ToList(); @@ -691,12 +685,13 @@ protected virtual void MapImportResult(IModCollection modCollection, ICollection if (mods.Any() && importResult.Descriptors != null && importResult.Descriptors.Any()) { var sort = importResult.Descriptors.ToList(); - var collectionMods = mods.Where(p => importResult.Descriptors.Any(x => x.Equals(p.DescriptorFile, StringComparison.OrdinalIgnoreCase))).OrderBy(p => sort.IndexOf(sort.FirstOrDefault(x => x.Equals(p.DescriptorFile, StringComparison.OrdinalIgnoreCase)))).ToList(); + var collectionMods = mods.Where(p => importResult.Descriptors.Any(x => x.Equals(p.DescriptorFile, StringComparison.OrdinalIgnoreCase))) + .OrderBy(p => sort.IndexOf(sort.FirstOrDefault(x => x.Equals(p.DescriptorFile, StringComparison.OrdinalIgnoreCase)))).ToList(); modCollection.ModPaths = collectionMods.Select(p => p.FullPath).ToList(); if (modCollection.ModPaths.Count() != modCollection.Mods.Count() && importResult.ModNames != null && importResult.ModNames.Any()) { sort = importResult.ModNames.ToList(); - collectionMods = mods.Where(p => importResult.ModNames.Any(x => x.Equals(p.Name))).OrderBy(p => sort.IndexOf(sort.FirstOrDefault(x => x.Equals(p.Name)))).ToList(); + collectionMods = [.. mods.Where(p => importResult.ModNames.Any(x => x.Equals(p.Name))).OrderBy(p => sort.IndexOf(sort.FirstOrDefault(x => x.Equals(p.Name))))]; collectionMods = collectionMods.GroupBy(p => p.FullPath).Select(p => p.FirstOrDefault()).ToList(); modCollection.ModPaths = collectionMods.Select(p => p.FullPath).ToList(); } @@ -719,22 +714,22 @@ protected virtual async Task> ParseReportAsync(IEnumera var total = mods.SelectMany(p => p.Files).Count(p => game.GameFolders.Any(a => p.StartsWith(a))); var progress = 0; double lastPercentage = 0; - int i = 0; - var tasks = new List>>(); + var i = 0; + var tasks = new List>>(); foreach (var mod in mods) { var report = GetModelInstance(); report.Name = mod.Name; report.ReportType = HashReportType.Collection; - int j = 0; + var j = 0; - foreach (var item in mod.Files.Where(p => game.GameFolders.Any(a => p.StartsWith(a)))) + foreach (var item in mod.Files.Where(p => game.GameFolders.Any(p.StartsWith))) { - string modPath = mod.FullPath; - string filePath = item; - int modIndex = i; - int fileIndex = j; + var modPath = mod.FullPath; + var filePath = item; + var modIndex = i; + var fileIndex = j; tasks.Add(Task.Run(() => { var info = Reader.GetFileInfo(modPath, filePath); @@ -745,34 +740,40 @@ protected virtual async Task> ParseReportAsync(IEnumera fileReport.Hash = info.ContentSHA; return new Tuple(modIndex, fileIndex, fileReport); } + return null; })); j++; } + reports.Add(report); reports2.Add(new IHashFileReport[j]); i++; } + while (tasks.Count > 0) { i = Task.WaitAny(tasks.ToArray()); - Tuple result = tasks[i].Result; + var result = tasks[i].Result; tasks.RemoveAt(i); if (result != null) { reports2[result.Item1][result.Item2] = result.Item3; } + progress++; var percentage = GetProgressPercentage(total, progress); - if (percentage != lastPercentage) + if (percentage.IsNotNearlyEqual(lastPercentage)) { await messageBus.PublishAsync(new ModReportExportEvent(1, percentage)); } + lastPercentage = percentage; } + for (i = 0; i < reports.Count; i++) { - reports[i].Reports = reports2[i].ToList(); + reports[i].Reports = [.. reports2[i]]; } return reports; diff --git a/src/IronyModManager.Shared/IronyModManager.Shared.csproj b/src/IronyModManager.Shared/IronyModManager.Shared.csproj index 03022d95..33fa4130 100644 --- a/src/IronyModManager.Shared/IronyModManager.Shared.csproj +++ b/src/IronyModManager.Shared/IronyModManager.Shared.csproj @@ -49,6 +49,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -57,7 +58,7 @@ - + From a4f270a7726a897cdfc59e87ff7771afa8a3e31f Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 4 Mar 2024 21:25:40 +0100 Subject: [PATCH 093/101] Update credits --- Credits/Credits.txt | 77 ++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/Credits/Credits.txt b/Credits/Credits.txt index 824661bc..ff9e376e 100644 --- a/Credits/Credits.txt +++ b/Credits/Credits.txt @@ -28,7 +28,7 @@ license Type:MIT ######################### Package:AutoMapper -Version:12.0.1 +Version:13.0.1 project URL:https://automapper.org/ Description:A convention-based object-object mapper. licenseUrl:https://licenses.nuget.org/MIT @@ -126,10 +126,10 @@ license Type:MIT ######################### Package:CompareNETObjects -Version:4.79.0 +Version:4.83.0 project URL:https://github.com/GregFinzer/Compare-Net-Objects Description:What you have been waiting for. Perform a deep compare of any two .NET objects using reflection. Shows the differences between the two objects. -licenseUrl:https://www.nuget.org/packages/CompareNETObjects/4.77.0/License +licenseUrl:https://www.nuget.org/packages/CompareNETObjects/4.83.0/License license Type:Microsoft Public License (Ms-PL) ######################### @@ -142,7 +142,7 @@ license Type:MIT ######################### Package:DiffPlex -Version:1.7.1 +Version:1.7.2 project URL:https://github.com/mmanela/diffplex/ Description:DiffPlex is a diffing library that allows you to programatically create text diffs. DiffPlex is a fast and tested library. licenseUrl:https://licenses.nuget.org/Apache-2.0 @@ -158,7 +158,7 @@ license Type:BSD, Apache, zlib and MIT ######################### Package:FluentAssertions -Version:6.11.0 +Version:6.12.0 project URL:https://www.fluentassertions.com/ Description:A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET Framework 4.7, .NET Core 2.1 and 3.0, .NET 6, as well as .NET Standard 2.0 and 2.1. @@ -170,11 +170,9 @@ license Type:Apache-2.0 ######################### Package:FSharp.Core -Version:7.0.300 +Version:8.0.200 project URL:https://github.com/dotnet/fsharp -Description:FSharp.Core redistributables from F# Tools version 12.4.0 For F# 7.0. Contains code from the F# Software Foundation. -licenseUrl:https://licenses.nuget.org/MIT -license Type:MIT +Description:FSharp.Core redistributables from F# Tools version 12.8.200 For F# 8.0. Contains code from the F# Software Foundation. licenseUrl:https://licenses.nuget.org/MIT license Type:MIT @@ -212,7 +210,7 @@ license Type:Apache-2.0 ######################### Package:Magick.NET-Q8-x64 -Version:13.1.3 +Version:13.6.0 project URL:https://github.com/dlemstra/Magick.NET Description:ImageMagick is a powerful image manipulation library that supports over 100 major file formats (not including sub-formats). With Magick.NET you can use ImageMagick without having to install ImageMagick on your server or desktop. Visit https://github.com/dlemstra/Magick.NET/tree/main/docs before installing to help you decide the best version. licenseUrl:https://licenses.nuget.org/Apache-2.0 @@ -252,7 +250,7 @@ license Type:MIT ######################### Package:Microsoft.Bcl.AsyncInterfaces -Version:7.0.0 +Version:8.0.0 project URL:https://dot.net/ Description:Provides the IAsyncEnumerable and IAsyncDisposable interfaces and helper types for .NET Standard 2.0. This package is not required starting with .NET Standard 2.1 and .NET Core 3.0. @@ -265,8 +263,8 @@ license Type:MIT ######################### Package:Microsoft.NET.Test.Sdk -Version:17.6.2 -project URL:https://github.com/microsoft/vstest/ +Version:17.9.0 +project URL:https://github.com/microsoft/vstest Description:The MSbuild targets and properties for building .NET test projects. licenseUrl:https://www.nuget.org/packages/Microsoft.NET.Test.Sdk/17.3.0/License license Type:MICROSOFT SOFTWARE LICENSE TERMS @@ -306,13 +304,19 @@ license Type:Apache-2.0 ######################### Package:Moq -Version:4.18.4 -project URL:https://github.com/moq/moq4 +Version:4.20.70 +project URL:https://github.com/moq/moq Description:Moq is the most popular and friendly mocking framework for .NET. +licenseUrl:https://licenses.nuget.org/BSD-3-Clause +license Type:BSD-3-Clause -Built from https://github.com/moq/moq4/tree/042a2ebbe -licenseUrl:https://raw.githubusercontent.com/moq/moq4/main/License.txt -license Type:BSD 3-Clause License +######################### +Package:NaturalSort.Extension +Version:4.2.0+build.231 +project URL:https://github.com/tompazourek/NaturalSort.Extension +Description:Extension method for StringComparison that adds support for natural sorting (e.g. "abc1", "abc2", "abc10" instead of "abc1", "abc10", "abc2"). +licenseUrl:https://licenses.nuget.org/MIT +license Type:MIT ######################### Package:Nerdbank.GitVersioning @@ -324,10 +328,10 @@ license Type:MIT ######################### Package:NetSparkleUpdater.SparkleUpdater -Version:2.2.3 +Version:2.3.0 project URL:https://github.com/NetSparkleUpdater/NetSparkle Description:NetSparkleUpdater/NetSparkle is a C# .NET software update framework that allows you to easily download installer files and update your C# .NET Framework or .NET Core software. Built-in UIs are available for WinForms, WPF, and Avalonia; if you want a built-in UI, please reference a NetSparkleUpdater.UI package. You provide, somewhere on the internet, an XML appcast with software version information along with release notes in Markdown or HTML format. The NetSparkle framework then checks for an update in the background, displays the release notes to the user, and lets users download or skip the software update. The framework can also perform silent downloads so that you can present all of the UI yourself or set up your own silent software update system, as allowed by your software architecture. It was inspired by the Sparkle (https://sparkle-project.org/) project for Cocoa developers and the WinSparkle (https://winsparkle.org/) project (a Win32 port). -licenseUrl:https://www.nuget.org/packages/NetSparkleUpdater.SparkleUpdater/2.2.3/License +licenseUrl:https://www.nuget.org/packages/NetSparkleUpdater.SparkleUpdater/2.3.0/License license Type:MIT ######################### @@ -348,14 +352,14 @@ license Type:MIT ######################### Package:NLog -Version:5.2.0 +Version:5.2.8 project URL:https://nlog-project.org/ Description:NLog is a logging platform for .NET with rich log routing and management capabilities. NLog supports traditional logging, structured logging and the combination of both. Supported platforms: -- .NET 5, 6 and 7 +- .NET 5, 6, 7 and 8 - .NET Core 1, 2 and 3 - .NET Standard 1.3+ and 2.0+ - .NET Framework 3.5 - 4.8 @@ -376,7 +380,7 @@ license Type:MIT ######################### Package:PommaLabs.MimeTypes -Version:2.8.3+5c6b155 +Version:2.9.1+63b5125a project URL:https://gitlab.com/pommalabs/mime-types Description:MIME content type definitions mapped with file extensions and file signatures. licenseUrl:https://licenses.nuget.org/MIT @@ -392,7 +396,7 @@ license Type:Adapted MIT License ######################### Package:ReactiveUI -Version:19.2.1 +Version:19.5.41 project URL:https://reactiveui.net/ Description:A MVVM framework that integrates with the Reactive Extensions for .NET to create elegant, testable User Interfaces that run on any mobile or desktop platform. This is the base package with the base platform implementations licenseUrl:https://licenses.nuget.org/MIT @@ -403,7 +407,7 @@ Package:RepoDb Version:1.13.1 project URL:https://repodb.net/ Description:A hybrid ORM library for .NET. -licenseUrl:https://www.nuget.org/packages/RepoDb/1.12.10/License +licenseUrl:https://www.nuget.org/packages/RepoDb/1.13.1/License license Type:Apache License 2.0 ######################### @@ -424,7 +428,7 @@ license Type:MIT ######################### Package:SharpCompress -Version:0.33.0 +Version:0.36.0 project URL:https://github.com/adamhathcock/sharpcompress Description:SharpCompress is a compression library for NET Standard 2.0/2.1/NET 6.0/NET 7.0 that can unrar, decompress 7zip, decompress xz, zip/unzip, tar/untar lzip/unlzip, bzip2/unbzip2 and gzip/ungzip with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip is implemented. licenseUrl:https://licenses.nuget.org/MIT @@ -432,7 +436,7 @@ license Type:MIT ######################### Package:SimpleInjector -Version:5.4.1 +Version:5.4.4 project URL:https://simpleinjector.org/ Description:Simple Injector is an easy, flexible and fast dependency injection library that uses best practice to guide your solutions toward the pit of success. licenseUrl:https://licenses.nuget.org/MIT @@ -462,9 +466,18 @@ Description:An extension to ImageSharp that allows the drawing of images, paths, licenseUrl:https://licenses.nuget.org/Apache-2.0 license Type:Apache-2.0 +######################### +Package:SkiaSharp +Version:2.88.7 +project URL:https://go.microsoft.com/fwlink/?linkid=868515 +Description:SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. +It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images. +licenseUrl:https://go.microsoft.com/fwlink/?linkid=868514 +license Type:MIT License + ######################### Package:SlimMessageBus.Host.Memory -Version:2.1.8 +Version:2.2.3 project URL:https://github.com/zarusz/SlimMessageBus Description:Simple provider for SlimMessageBus for in process message passing. Messages are stored in memory (state is transient). licenseUrl:https://licenses.nuget.org/Apache-2.0 @@ -472,7 +485,7 @@ license Type:Apache-2.0 ######################### Package:SmartFormat -Version:3.2.1 +Version:3.3.2 project URL:https://github.com/axuno/SmartFormat Description:This package contains the core SmartFormat assemblies with core extensions built-in. @@ -484,7 +497,7 @@ license Type:MIT ######################### Package:Splat.NLog -Version:14.6.37 +Version:14.8.12 project URL:https://github.com/reactiveui/splat/ Description:A library to make things cross-platform that should be. licenseUrl:https://licenses.nuget.org/MIT @@ -492,7 +505,7 @@ license Type:MIT ######################### Package:Splat.SimpleInjector -Version:14.6.37 +Version:14.8.12 project URL:https://github.com/reactiveui/splat/ Description:A library to make things cross-platform that should be. licenseUrl:https://licenses.nuget.org/MIT @@ -583,7 +596,7 @@ license Type:MIT ######################### Package:xunit -Version:2.4.2 +Version:2.7.0 project URL:https://github.com/xunit/xunit Description:xUnit.net is a developer testing framework, built to support Test Driven Development, with a design goal of extreme simplicity and alignment with framework features. From d4b13f1f71c94f9de5ca618e2d47d4e213c75111 Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 4 Mar 2024 21:26:09 +0100 Subject: [PATCH 094/101] Set version to '1.26-rc' --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 0237c8ad..cdca8ff4 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.26-alpha", + "version": "1.26-rc", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/v\\d+(?:\\.\\d+)?$", From 9c6b5a74ff64cc11c142618335c29b5b5d5d83ac Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 7 Mar 2024 16:06:00 +0100 Subject: [PATCH 095/101] Fix CTD when editing text by bypassing undostack of editor (we have our own) --- src/IronyModManager/Controls/TextEditor.cs | 25 ++++++++++++++++++- .../Controls/MergeViewerControlView.xaml.cs | 14 +++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/IronyModManager/Controls/TextEditor.cs b/src/IronyModManager/Controls/TextEditor.cs index baaa0072..c26042d0 100644 --- a/src/IronyModManager/Controls/TextEditor.cs +++ b/src/IronyModManager/Controls/TextEditor.cs @@ -4,7 +4,7 @@ // Created : 04-15-2020 // // Last Modified By : Mario -// Last Modified On : 02-24-2024 +// Last Modified On : 03-07-2024 // *********************************************************************** // // Mario @@ -21,6 +21,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Styling; using AvaloniaEdit; +using AvaloniaEdit.Document; using AvaloniaEdit.Rendering; using IronyModManager.DI; using IronyModManager.Implementation.Actions; @@ -126,6 +127,17 @@ public int GetTopVisibleLine() return firstLine; } + /// + /// Saves a set text. + /// + /// The value. + public void SafeSetText(string value) + { + var document = GetDocument(); + document.Text = value ?? string.Empty; + CaretOffset = 0; + } + /// /// Scroll to. /// @@ -253,6 +265,17 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) } } + /// + /// Get document. + /// + /// A TextDocument. + /// No document + private TextDocument GetDocument() + { + var document = Document; + return document ?? throw new NullReferenceException("No document"); + } + #endregion Methods } } diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index a42fb76f..95839746 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -4,7 +4,7 @@ // Created : 03-20-2020 // // Last Modified By : Mario -// Last Modified On : 02-24-2024 +// Last Modified On : 03-07-2024 // *********************************************************************** // // Mario @@ -353,8 +353,8 @@ protected override void OnActivated(CompositeDisposable disposables) diffLeft.TextArea.TextView.BackgroundRenderers.Add(diffLeftRenderer); diffRight.TextArea.LeftMargins.Add(diffRightMargin); diffRight.TextArea.TextView.BackgroundRenderers.Add(diffRightRenderer); - diffLeft.Text = string.Join(Environment.NewLine, ViewModel.LeftDiff); - diffRight.Text = string.Join(Environment.NewLine, ViewModel.RightDiff); + diffLeft.SafeSetText(string.Join(Environment.NewLine, ViewModel.LeftDiff)); + diffRight.SafeSetText(string.Join(Environment.NewLine, ViewModel.RightDiff)); diffLeft.IsReadOnly = !ViewModel.LeftSidePatchMod; diffRight.IsReadOnly = !ViewModel.RightSidePatchMod; @@ -533,7 +533,9 @@ void evalKey() return; } - diffLeft.Text = newText; + var locLine = diffLeft.TextArea.Caret.Location.Line; + diffLeft.SafeSetText(newText); + diffLeft.TextArea.Caret.Location = new TextLocation(locLine, diffLeft.CaretOffset); }).DisposeWith(disposables); this.WhenAnyValue(v => v.ViewModel.RightDiff).Subscribe(s => @@ -548,7 +550,9 @@ void evalKey() return; } - diffRight.Text = newText; + var locLine = diffRight.TextArea.Caret.Location.Line; + diffRight.SafeSetText(newText); + diffRight.TextArea.Caret.Location = new TextLocation(locLine, diffRight.CaretOffset); }); this.WhenAnyValue(v => v.ViewModel.LeftSidePatchMod).Subscribe(s => From ec7ffe65a579958d1d4caf00c0293e6b502b2f03 Mon Sep 17 00:00:00 2001 From: bcssov Date: Thu, 7 Mar 2024 21:05:27 +0100 Subject: [PATCH 096/101] Better handle full line deletions caret positioning --- src/IronyModManager/Controls/TextEditor.cs | 16 ++++++++ .../Controls/MergeViewerControlViewModel.cs | 28 ++++++++------ .../Controls/MergeViewerControlView.xaml.cs | 38 ++++++++++++++++++- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/IronyModManager/Controls/TextEditor.cs b/src/IronyModManager/Controls/TextEditor.cs index c26042d0..2934488a 100644 --- a/src/IronyModManager/Controls/TextEditor.cs +++ b/src/IronyModManager/Controls/TextEditor.cs @@ -106,6 +106,22 @@ public int GetBottomVisibleLine() return lastLine; } + /// + /// Gets a last column by line. + /// + /// The line. + /// An int. + public int GetLastColumnByLine(int line) + { + var docLine = Document.GetLineByNumber(line); + if (docLine != null && docLine.Length != 0) + { + return docLine.Length + 1; + } + + return 0; + } + /// /// Gets a middle visible line. /// diff --git a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs index e71927ca..c9329feb 100644 --- a/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs +++ b/src/IronyModManager/ViewModels/Controls/MergeViewerControlViewModel.cs @@ -4,7 +4,7 @@ // Created : 03-20-2020 // // Last Modified By : Mario -// Last Modified On : 02-24-2024 +// Last Modified On : 03-07-2024 // *********************************************************************** // // Mario @@ -41,17 +41,9 @@ namespace IronyModManager.ViewModels.Controls { /// /// Class MergeViewerControlViewModel. - /// Implements the + /// Implements the /// - /// - /// State of the scroll. - /// The mod patch collection service. - /// The hotkey pressed handler. - /// The application action. - /// The external editor service. - /// The notification action. - /// The localization manager. - /// Initializes a new instance of the class. + /// [ExcludeFromCoverage("This should be tested via functional testing.")] public class MergeViewerControlViewModel( IAppStateService appStateService, @@ -526,6 +518,18 @@ public class MergeViewerControlViewModel( /// The previous conflict command. public virtual ReactiveCommand PrevConflictCommand { get; protected set; } + /// + /// Gets or sets a value representing the previous left diff. + /// + /// The previous left diff. + public virtual IList PreviousLeftDiff { get; set; } + + /// + /// Gets or sets a value representing the previous right diff. + /// + /// The previous right diff. + public IList PreviousRightDiff { get; set; } + /// /// Gets or sets the read only editor. /// @@ -753,6 +757,8 @@ protected virtual void Compare(string left, string right) { var builder = new SideBySideDiffBuilder(new Differ()); var diff = builder.BuildDiffModel(left, right, true); + PreviousLeftDiff = LeftDiff; + PreviousRightDiff = RightDiff; LeftDiff = GetDiffPieceWithIndex(diff.OldText.Lines); RightDiff = GetDiffPieceWithIndex(diff.NewText.Lines); } diff --git a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs index 95839746..d43b5477 100644 --- a/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs +++ b/src/IronyModManager/Views/Controls/MergeViewerControlView.xaml.cs @@ -533,9 +533,26 @@ void evalKey() return; } + var lineDeleted = ViewModel!.PreviousLeftDiff != null && s != null && s.Count < ViewModel!.PreviousLeftDiff.Count && s.Count > 0 && ViewModel!.PreviousLeftDiff.Count > 0; var locLine = diffLeft.TextArea.Caret.Location.Line; + if (lineDeleted) + { + locLine--; + if (locLine < 1) + { + locLine = 1; + } + } + + var locColumn = diffLeft.TextArea.Caret.Location.Column; diffLeft.SafeSetText(newText); - diffLeft.TextArea.Caret.Location = new TextLocation(locLine, diffLeft.CaretOffset); + + if (lineDeleted) + { + locColumn = diffLeft.GetLastColumnByLine(locLine); + } + + diffLeft.TextArea.Caret.Location = new TextLocation(locLine, locColumn); }).DisposeWith(disposables); this.WhenAnyValue(v => v.ViewModel.RightDiff).Subscribe(s => @@ -550,9 +567,26 @@ void evalKey() return; } + var lineDeleted = ViewModel!.PreviousRightDiff != null && s != null && s.Count < ViewModel!.PreviousRightDiff.Count && s.Count > 0 && ViewModel!.PreviousRightDiff.Count > 0; var locLine = diffRight.TextArea.Caret.Location.Line; + if (lineDeleted) + { + locLine--; + if (locLine < 1) + { + locLine = 1; + } + } + + var locColumn = diffRight.TextArea.Caret.Location.Column; diffRight.SafeSetText(newText); - diffRight.TextArea.Caret.Location = new TextLocation(locLine, diffRight.CaretOffset); + + if (lineDeleted) + { + locColumn = diffRight.GetLastColumnByLine(locLine); + } + + diffRight.TextArea.Caret.Location = new TextLocation(locLine, locColumn); }); this.WhenAnyValue(v => v.ViewModel.LeftSidePatchMod).Subscribe(s => From 759788eb09790ec5b7d96b2c17342c9c87adc833 Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 11 Mar 2024 20:43:19 +0100 Subject: [PATCH 097/101] Ensure that irony restored proper actual normal\maximized state upon receiving an IPC signal --- src/IronyModManager/Program.cs | 5 +- src/IronyModManager/Views/MainWindow.xaml.cs | 48 ++++++++++++++++---- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/IronyModManager/Program.cs b/src/IronyModManager/Program.cs index db560e4d..6edc5c6b 100644 --- a/src/IronyModManager/Program.cs +++ b/src/IronyModManager/Program.cs @@ -31,6 +31,7 @@ using IronyModManager.Platform; using IronyModManager.Platform.Configuration; using IronyModManager.Shared; +using IronyModManager.Views; using NLog; using ILogger = IronyModManager.Shared.ILogger; @@ -225,10 +226,10 @@ private static void InitSingleInstance() ParseArguments(args.CommandLineArgs); Dispatcher.UIThread.SafeInvoke(() => { - var mainWindow = Helpers.GetMainWindow(); + var mainWindow = (MainWindow)Helpers.GetMainWindow(); mainWindow.Show(); mainWindow.Activate(); - var previousState = mainWindow.WindowState; + var previousState = mainWindow.ActualState; mainWindow.WindowState = WindowState.Minimized; mainWindow.WindowState = previousState; }); diff --git a/src/IronyModManager/Views/MainWindow.xaml.cs b/src/IronyModManager/Views/MainWindow.xaml.cs index 76bd68d4..ccde061e 100644 --- a/src/IronyModManager/Views/MainWindow.xaml.cs +++ b/src/IronyModManager/Views/MainWindow.xaml.cs @@ -4,7 +4,7 @@ // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 02-15-2024 +// Last Modified On : 03-11-2024 // *********************************************************************** // // Mario @@ -90,6 +90,16 @@ public MainWindow() #endregion Constructors + #region Properties + + /// + /// The actual state + /// + /// The actual state. + public WindowState ActualState { get; set; } + + #endregion Properties + #region Methods /// @@ -272,17 +282,11 @@ protected override void OnActivated(CompositeDisposable disposables) preventShutdown = x.PreventShutdown; if (shutdownRequested && !preventShutdown) { - Dispatcher.UIThread.InvokeAsync(() => - { - ((IClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!).Shutdown(); - }); + Dispatcher.UIThread.InvokeAsync(() => { ((IClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!).Shutdown(); }); } }).DisposeWith(disposables); - this.WhenAnyValue(v => v.ViewModel.RegisterHotkeyCommand).Subscribe(_ => - { - InitializeHotKeys(); - }).DisposeWith(disposables); + this.WhenAnyValue(v => v.ViewModel.RegisterHotkeyCommand).Subscribe(_ => { InitializeHotKeys(); }).DisposeWith(disposables); var hotkeySuspendHandler = DIResolver.Get(); hotkeySuspendHandler.Subscribe(s => @@ -319,6 +323,23 @@ protected override void OnActivated(CompositeDisposable disposables) base.OnActivated(disposables); } + /// + /// Called when [property changed]. + /// + /// + /// The change. + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (change != null && change.Property == WindowStateProperty) + { + if (WindowState != WindowState.Minimized) + { + ActualState = WindowState; + } + } + } + /// /// Initializes the component. /// @@ -329,6 +350,15 @@ private void InitializeComponent() { InitWindowSize(); } + + if (WindowState != WindowState.Minimized) + { + ActualState = WindowState; + } + else + { + ActualState = WindowState.Normal; + } } /// From eccbf406b286e8a6abeeb889b69a665a58c3c3e4 Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 11 Mar 2024 21:16:03 +0100 Subject: [PATCH 098/101] Handle initialization better --- .../SingleInstance/SingleInstance.cs | 25 +++++++++++++------ src/IronyModManager/Program.cs | 18 ++++++++++--- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs index 5f19d657..a2d82060 100644 --- a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs +++ b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs @@ -5,15 +5,18 @@ // Created : 02-10-2024 // // Last Modified By : Mario -// Last Modified On : 02-10-2024 +// Last Modified On : 03-11-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; +using System.Collections.Generic; using System.IO.Pipes; +using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading; @@ -76,7 +79,8 @@ internal static class SingleInstance /// /// Initializes this instance. /// - public static void Initialize() + /// true if XXXX, false otherwise. + public static bool Initialize() { lock (objLock) { @@ -90,10 +94,7 @@ public static void Initialize() } else { - var data = new Args() - { - CommandLineArgs = Environment.GetCommandLineArgs() - }; + var data = new Args { CommandLineArgs = Environment.GetCommandLineArgs() }; var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)); using var pipe = new NamedPipeClientStream(".", GetMutexName(), PipeDirection.Out, PipeOptions.CurrentUserOnly | PipeOptions.WriteThrough); pipe.Connect(); @@ -103,10 +104,14 @@ public static void Initialize() catch { } + if (!initial) { Environment.Exit(0); + return false; } + + return true; } } @@ -115,7 +120,7 @@ public static void Initialize() /// public static async Task Monitor() { - using var pipe = new NamedPipeServerStream(GetMutexName(), PipeDirection.In, maxNumberOfServerInstances: 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly | PipeOptions.WriteThrough); + using var pipe = new NamedPipeServerStream(GetMutexName(), PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly | PipeOptions.WriteThrough); while (mutex != null) { try @@ -124,6 +129,7 @@ public static async Task Monitor() { await pipe.WaitForConnectionAsync(); } + var buffer = new byte[1024]; var sb = new StringBuilder(); while (true) @@ -133,8 +139,10 @@ public static async Task Monitor() { break; } + sb.Append(Encoding.UTF8.GetString(buffer)); } + var args = JsonConvert.DeserializeObject(sb.ToString()); pipe.Disconnect(); if (args != null) @@ -173,10 +181,13 @@ private static string GetMutexName() { break; } + sb.Append($"{item:X2}"); } + mutexName = sb.ToString(); } + return mutexName; } } diff --git a/src/IronyModManager/Program.cs b/src/IronyModManager/Program.cs index 6edc5c6b..176cf7bb 100644 --- a/src/IronyModManager/Program.cs +++ b/src/IronyModManager/Program.cs @@ -4,7 +4,7 @@ // Created : 01-10-2020 // // Last Modified By : Mario -// Last Modified On : 02-21-2024 +// Last Modified On : 03-11-2024 // *********************************************************************** // // Copyright (c) Mario. All rights reserved. @@ -98,9 +98,15 @@ public static void Main(string[] args) try { ParseArguments(args); + var canInitialize = true; if (!StaticResources.CommandLineOptions.ShowFatalErrorNotification) { - InitSingleInstance(); + canInitialize = InitSingleInstance(); + } + + if (!canInitialize) + { + return; } var app = BuildAvaloniaApp(); @@ -210,12 +216,13 @@ private static void InitLogging() /// /// Initializes the single instance. /// - private static void InitSingleInstance() + /// true if XXXX, false otherwise. + private static bool InitSingleInstance() { var configuration = DIResolver.Get().GetOptions().App; if (configuration.SingleInstance) { - SingleInstance.Initialize(); + var result = SingleInstance.Initialize(); SingleInstance.InstanceLaunched += args => { if (!StaticResources.AllowCommandLineChange) @@ -234,7 +241,10 @@ private static void InitSingleInstance() mainWindow.WindowState = previousState; }); }; + return result; } + + return true; } /// From 3c1bd3af6a39e4587821cbdb8d220327483f17a7 Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 11 Mar 2024 21:46:47 +0100 Subject: [PATCH 099/101] Switch single instance to dedicated background thread --- .../SingleInstance/SingleInstance.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs index a2d82060..ad711a21 100644 --- a/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs +++ b/src/IronyModManager/Implementation/SingleInstance/SingleInstance.cs @@ -1,5 +1,4 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager // Author : Mario // Created : 02-10-2024 @@ -20,12 +19,10 @@ using System.Security.Cryptography; using System.Text; using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; namespace IronyModManager.Implementation.SingleInstance { - /// /// Class SingleInstance. /// @@ -53,6 +50,11 @@ internal static class SingleInstance /// private static string mutexName; + /// + /// The thread + /// + private static Thread thread; + #endregion Fields #region Delegates @@ -90,7 +92,8 @@ public static bool Initialize() mutex = new Mutex(true, $"Global\\{GetMutexName()}", out initial); if (initial) { - Task.Run(Monitor); + thread = new Thread(Monitor) { Name = typeof(SingleInstance).FullName, IsBackground = true }; + thread.Start(); } else { @@ -118,7 +121,7 @@ public static bool Initialize() /// /// Monitors this instance. /// - public static async Task Monitor() + public static void Monitor() { using var pipe = new NamedPipeServerStream(GetMutexName(), PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly | PipeOptions.WriteThrough); while (mutex != null) @@ -127,7 +130,7 @@ public static async Task Monitor() { if (!pipe.IsConnected) { - await pipe.WaitForConnectionAsync(); + pipe.WaitForConnection(); } var buffer = new byte[1024]; @@ -152,7 +155,7 @@ public static async Task Monitor() } catch { - await Task.Delay(Delay); + Thread.Sleep(Delay); } } } From 47fc6cb0cbca8adbc3355e0a811ab8471e8ba768 Mon Sep 17 00:00:00 2001 From: bcssov Date: Mon, 11 Mar 2024 22:25:28 +0100 Subject: [PATCH 100/101] Slight tweaks to single instance --- src/IronyModManager/Program.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/IronyModManager/Program.cs b/src/IronyModManager/Program.cs index 176cf7bb..24d20e49 100644 --- a/src/IronyModManager/Program.cs +++ b/src/IronyModManager/Program.cs @@ -234,11 +234,17 @@ private static bool InitSingleInstance() Dispatcher.UIThread.SafeInvoke(() => { var mainWindow = (MainWindow)Helpers.GetMainWindow(); - mainWindow.Show(); - mainWindow.Activate(); var previousState = mainWindow.ActualState; - mainWindow.WindowState = WindowState.Minimized; + if (mainWindow.WindowState != WindowState.Minimized) + { + mainWindow.WindowState = WindowState.Minimized; + } + mainWindow.WindowState = previousState; + mainWindow.Show(); + mainWindow.BringIntoView(); + mainWindow.Activate(); + mainWindow.Focus(); }); }; return result; From d51efef703e4d28744a7ec7e559726aa112a216a Mon Sep 17 00:00:00 2001 From: bcssov Date: Tue, 12 Mar 2024 21:29:06 +0100 Subject: [PATCH 101/101] Set version to '1.26' --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index cdca8ff4..faeb57ee 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.26-rc", + "version": "1.26", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/v\\d+(?:\\.\\d+)?$",