From 80489a4369dadfe4c57b5d004fcf1d6827c43bf9 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Mon, 13 Apr 2026 21:03:15 +1000 Subject: [PATCH 1/5] . --- buildTransitive/EmptyFileTargets.md | 21 ----- buildTransitive/EmptyFiles.targets | 38 -------- index/empty.7z | Bin 32 -> 32 bytes index/empty.avif | Bin 298 -> 70 bytes index/empty.bmp | Bin 58 -> 58 bytes index/empty.dds | Bin 136 -> 132 bytes index/empty.dib | Bin 58 -> 58 bytes index/empty.docx | Bin 1921 -> 810 bytes index/empty.emf | Bin 620 -> 132 bytes index/empty.exif | Bin 734 -> 139 bytes index/empty.gif | Bin 799 -> 28 bytes index/empty.gz | Bin 29 -> 20 bytes index/empty.gzip | Bin 29 -> 20 bytes index/empty.heic | Bin 3242 -> 32 bytes index/empty.heif | Bin 209 -> 32 bytes index/empty.ico | Bin 70 -> 68 bytes index/empty.j2c | Bin 270 -> 44 bytes index/empty.jfif | Bin 734 -> 139 bytes index/empty.jp2 | Bin 354 -> 77 bytes index/empty.jpc | Bin 270 -> 44 bytes index/empty.jpe | Bin 734 -> 139 bytes index/empty.jpeg | Bin 734 -> 139 bytes index/empty.jpg | Bin 734 -> 139 bytes index/empty.jxr | Bin 300 -> 48 bytes index/empty.kmz | Bin 292 -> 204 bytes index/empty.nupkg | Bin 1891 -> 258 bytes index/empty.odp | Bin 7959 -> 667 bytes index/empty.ods | Bin 2784 -> 683 bytes index/empty.odt | Bin 2287 -> 647 bytes index/empty.pbm | Bin 8 -> 8 bytes index/empty.pcx | Bin 131 -> 129 bytes index/empty.pdf | Bin 280 -> 212 bytes index/empty.pgm | Bin 12 -> 10 bytes index/empty.png | Bin 119 -> 69 bytes index/empty.ppm | Bin 14 -> 12 bytes index/empty.pptx | Bin 13583 -> 842 bytes index/empty.rle | Bin 58 -> 58 bytes index/empty.tar | Bin 1536 -> 1024 bytes index/empty.tga | Bin 543 -> 21 bytes index/empty.tif | Bin 250 -> 198 bytes index/empty.tiff | Bin 250 -> 198 bytes index/empty.wdp | Bin 300 -> 48 bytes index/empty.webp | Bin 228 -> 26 bytes index/empty.wmp | Bin 300 -> 48 bytes index/empty.xlsx | Bin 4589 -> 1388 bytes readme.md | 80 ++++++++--------- src/EmptyFiles/AllFiles.cs | 76 ++++++++++------ src/EmptyFiles/AssemblyLocation.cs | 26 ------ src/EmptyFiles/EmptyFile.cs | 25 +++++- src/EmptyFiles/EmptyFiles.csproj | 27 ++++-- src/Tests/BuildTargetsTests.cs | 130 ---------------------------- src/Tests/Tests.cs | 34 ++++++++ src/Tests/Tests.csproj | 1 - src/extensions.include.md | 76 ++++++++-------- 54 files changed, 203 insertions(+), 331 deletions(-) delete mode 100644 buildTransitive/EmptyFileTargets.md delete mode 100644 buildTransitive/EmptyFiles.targets delete mode 100644 src/EmptyFiles/AssemblyLocation.cs delete mode 100644 src/Tests/BuildTargetsTests.cs diff --git a/buildTransitive/EmptyFileTargets.md b/buildTransitive/EmptyFileTargets.md deleted file mode 100644 index 85e4fdf..0000000 --- a/buildTransitive/EmptyFileTargets.md +++ /dev/null @@ -1,21 +0,0 @@ -How it works: - - 1. Inputs="$(MSBuildThisFileFile)" - Tracks the package file timestamp - 2. Outputs="$(EmptyFilesMarker)" - Tracks a marker file in the intermediate output directory - 3. MSBuild automatically skips the target if all outputs are newer than all inputs - 4. SkipUnchangedFiles="true" - Additional optimization to skip individual unchanged files - 5. Touch - Updates the marker file timestamp after successful copy - 6. DeleteEmptyFilesMarkerIfTargetMissing - If the EmptyFiles directory is missing from the output, deletes the marker file so the copy re-runs - -This will only copy files when: - - 1. The targets is newer than the marker file, OR - 2. The marker file doesn't exist (first build/clean build), OR - 3. The EmptyFiles directory doesn't exist in the output (marker is deleted to force re-copy) - -Benefits: - - * Dramatically reduces IO on incremental builds - * Works with dotnet clean (removes marker file) - * Recovers when the output directory is cleaned without cleaning the intermediate directory - * Still respects individual file changes via SkipUnchangedFiles diff --git a/buildTransitive/EmptyFiles.targets b/buildTransitive/EmptyFiles.targets deleted file mode 100644 index fcefd44..0000000 --- a/buildTransitive/EmptyFiles.targets +++ /dev/null @@ -1,38 +0,0 @@ - - - - $(MSBuildThisFileDirectory)..\files - - - - - - - - - - $(TargetDir)EmptyFiles - $(IntermediateOutputPath)EmptyFiles.copied - - - - - - - - - - - - - - - diff --git a/index/empty.7z b/index/empty.7z index 46e2c912486832867be41bd1dbc1bc01f25959a4..6e0ae425bbc5f8304b4a463837f38d8c2821777a 100644 GIT binary patch literal 32 ScmXr7+Ou9=hJl3v2LJ#^JpyC^ literal 32 WcmXr7+Ou9=hJmGb_EmlcEC2v?>jZEB diff --git a/index/empty.avif b/index/empty.avif index d32be6d7b479d166eee8fb90491781ee44e54aba..820cc60833dab2283501533f43a5c949f20f270c 100644 GIT binary patch literal 70 zcmZQzV30{GsVqn=%S>Yc0uY^>nP!-qnV9D5Xy^nK63I<1Nd(CO0Z&F^UP=y#Rgjrn I0;0hH0ACyqwEzGB literal 298 zcmXwz&uYRz5XL7OLhxWEh!qiuw;r3GJPJakm%hW0KRS}FgD#;x7Nzgi7xEeUO>AbF z{l4Gq%>DlUt<-Vv-cT1#_`_6YdRPnBb0Q*AfV{R0# zDZoMmNvl)XCy@_(07c{sQ1q9=vEi{?k@SkBrWz}uwdNcn6%9Vgsm zo9~op5UwctZ9C*spwy4!81g9U*cgGog+cFfg-u<-{yy|FnyjNK|4?ezZXaF`e=epi Axc~qF diff --git a/index/empty.bmp b/index/empty.bmp index de874f7db456d4e9f9720b614c545be4d67b4f9d..e9a72d5f32210287bf581742445e09344615ae6b 100644 GIT binary patch literal 58 bcmZ?rwPJt(Ga#h_#Eft(0hV9^lb8emL7xFf delta 29 VcmcDrnxG`X00c+)AQ;4k0RTU50^930A0KU|?VuU|?Vb(jd$N#2^4-lL8cgiXEXEKx_tv|1bpRa0obnXbb=VZ@&g> literal 136 ucmZ>930A0KU|?Vu;9_6|(jd$Mr9o^`fC5l43lO_RgcxGgCxBHHEC>J{k^=ew diff --git a/index/empty.dib b/index/empty.dib index de874f7db456d4e9f9720b614c545be4d67b4f9d..e9a72d5f32210287bf581742445e09344615ae6b 100644 GIT binary patch literal 58 bcmZ?rwPJt(Ga#h_#Eft(0hV9^lb8emL7xFf delta 29 VcmcDrnxG`X00c+)AQ;4k0RTU50^ep_Az zHNTlQ#WlBO&4HnlC z`s4GZUv_z>VM))kSDI7g)%Qt-Ub6`fEa=ai?u{pY8l1%fB_YYi^xM@4YW?zs=~Jsxi^p_WG>r3_0ms zC)85A7WJ%TSz|Pf)99A|9FBRjo=N6(O-+iuy43mmTkYN*pF{V`rqnJJ`LyQh>7zR~ zSoK;~r*G`y`)%=O(VBHE$|pCsr%(Us$RqVmNbG6;0n_h_=k@XwzIHC%mn$rCNI}5i;%XC_1=O@rJ8zlHIP%5y?OIW0g+%`ssm~^5-00T+Wwt5r!p>ts zmMRl{q~!{(|6+V$yfXXelON3K5ADl=E@Na8VZfcZfX)Mh24HldBsFx6==fP+B0F~TUoo0Scugc%5Tf^1*{@c=3&H6j22 literal 1921 zcmWIWW@Zs#-~hsqq(*-RB)|=1#}}pM6zl7O$liWyzC#8AF6)zW{mlXtS{^NN^vGOX zz;ODWMc||UoX@*$^)xsZEs%M8=Jw20l`~rJY8*Kk$#;7Zi(8nLY2?|5m*$(ZXS`aq z*mcR9j>k`wPYX(0_Gtc8PMV@DDaif&@p6xbbW80B?2< z|L(gs%YcrTVPs$k!0A01po;SRq7;3IA5-#^OLJ56O7tpnbHM&xJK;1J(7&$rs%P#k zD^*nT-7UEAc96Y-+GTb#{z)Nk?n{dt>1yJhe)!DZ=LVIPp~^cKd~UwTvq^=uDN4ff zMc=`VmTR^Jh*kC~^M5?_z4@z(+m`eT?Fr>msxB^mWM?Q=dS}ZqO}R%etg?1meCB&; z3`BCEz?F&bnyQM+?zw?>lz&4=ors4I!Ak6>BK93DNn0Hu#=kL^x z-(J(4&0Uv%hfU~f6HWvbNfF6PGj zlOJcLmCe|fX)b?i7pJvFxvj(_g~G>24?eb=pO>(uqOVR|jom(I#<7J_b<5SCE}Qx7 zK-TK!ty2ywWkqOKzI~#Q_eRz7=cTl`4PJfj(;QZ<^|6lo^{?w>J`dY*dyw084EGn- z0A1D##JF<^Kg{jLC6zg;#c+o!2r%q-`j+R&e{8~~do_)A4F_&bI*@l*Xyg9rk5i0{ z*YBTnReZII8vFH=4NhD#!nu6+)<1vB=rwEk3u9p`i@=1~XL(EKO1k#WIa0=6o!x#l z>QLX&(?{4Z709nUy1lXV^4x42Ep`#f?~}fsov~ZbPEcd-A4ZU?*9V_)tOUBL3$Lq% zfwIxg`FSP4%p4z5S&&*B3v&6|NqhZ{7znh)A1&%mV&`fJn56wfv++>{Lvp^*>p$x2 zi|_2Uoz1e;!Gq(aS9RJq>9yN#&fSyjej+0|2fJSzZS4fFfXmuF6Ci$T6xN*x%{m1m#m^z z+qo~QkERuzxOq5hPNZ(Qrse8T#aOxKpN=c{aW+lrW_O?cp^InE$#}u;FY-=rs#hO$ zv90XtI6uSpR3Nj`+GExmHREn>m>8cq^`=xpzSeZ{r=7~rSMW^!-{2Ei_)+cli~Z3F zmevPOhetfmtJ!(S`#UQr%>E~3@A?c3kyvmlWn|K2Kos`KGC(O1?wakDQ`FsT~0r oS)lq6X&+rPa?pYj9Rh3zYQ~ZT1H4(;K#JIaumR}3*Q_8O05-_XC;$Ke diff --git a/index/empty.emf b/index/empty.emf index 363b241f6b4b9fcb6e788f16db76860e90a4c4e4..d79ba31182115e370c11c7e9c3f6f7c239b1cbae 100644 GIT binary patch literal 132 scmZQ%U|`4rVi;fqGC&vxV0;BvUpJ5pNG%ggk_?&;s7C~OV literal 620 zcmZ{hO-chn5QSe4gCzbXO8f%}lVDKLg%D71)f-)if?hy8Kn~!-!*~+$0O|$YyYdeD zJu_tnAq5}(s_RvC&vcSBfrz(4+AJfJPA{b~hU)zKLQ0yrPC3mizv`X+E&86xHvScl zKMS?aN-;o3Pyyo(Q%>4NccJLMe7Nnd#@;K)VDAvIjCS;M`%mv>rPv?jnWt>%HG1r6 z!4mkq`-&D|f)9Z`^AN{0#2lH^l=5CmDPeb*Cz(enqBpQ>J)#URO;Y1B)Q5kfNa@n{Z$vyHcTuQRBpg9Li1`4~hm|{Gei- zRMf=DB_=K*DW$5WuA!-AVrph?VQJ;;;_Bw^;pr6|5*ijB5gC=7lA4yDk(pIoQd(AC zQCZd8(%RPE(b+X=@|3C5rq7r;YtiB*OP4KQv2xX>&0Dr^+rDGxu0w~996fgY#K}{a zE?>EN?fQ+Iw;n!v{N(Ag=PzEq`uOSdm#^Qx|M>X}>z(JGL-`{vmgtrq9L1*V<3BCp|FxsBZr97#DyCVaw;1KeGpA5y2vG_V)9V+BgkuD kpAqM=CbE16_ZY%ow-|Vs8G(__*m3xfV&WDl0TrZxD3v-eUC)=~l$?FC58`HH z<|i@qudp+~*r5aOdvTI7dSF3*E&JY&=brcO@eM}^xji(edMqR-U`h^yh3~vj!Z6H% z-5bZ#Y~d23B{xfdIc{rdlnosCK9$qd()C}G4R{s?73UMUG1o+l*Zrdf;LGW77=A} zdLlybG3`^=Ld+zhZxkDeFvOi4rRmfS+Ft891rm@pJnX+NDIkjI*ccu9{%kh$W>35{ z8~H(})A3tvzuj&EqM4s2h84{uzYh!z*K+hl&NHd;ce%*PPSVdNqO{?b)J6{1+)3`) zrFuM7K34p|Yx%(Dx?K#z_n8#X1nKsIu7&)SgRXcg!b@#oGd2agL3gDNlKy+En^e-c rmaCSlk^+^$_35hRs-!?AaDBRJxhg483H%?OuJyfEBygFX!L9WV2=eF= diff --git a/index/empty.heif b/index/empty.heif index ca70c194370367967982f570a68aace8f5c214c3..0cc434cb32a41a73d72bedf5b85ee1044e7cba7a 100644 GIT binary patch delta 11 Scmcb}s4zjAl_NJLu>=4U$pcXU literal 209 zcmZQzV30^FsVvAy%}izhg51nBLnwP=ZfZ#)NDK%RGg5MjAWQ~^g3RO+7#l|O6=as= zf;o&pQXn%YA8ee916WKsGczv@E-MP;q%wiHK(!fV$%Y`Nb7nzN0gzVDEJ)4=((*tF mXOILOd|>$V=f@8Q24;|mNM=DU%yEnijEs#yF^=4n#1a6)3L}F6 diff --git a/index/empty.ico b/index/empty.ico index d4b3252593c00557ccdf103b2969b34f713cc56a..4be38450ff34c6529120dbbf66d522b526a88418 100644 GIT binary patch literal 68 lcmZQzU<5%%1|TWHz`!5|#2P>h6kuQi(;yah@E-_3G5{@G1YH0C literal 70 wcmZQzU<5%%1|X@xV8Fn@AO^%5KnxUOU;@($K$3xh1&9yxK`>YVLi`6&08Ls0T>t<8 diff --git a/index/empty.j2c b/index/empty.j2c index 905019b867beef2ab52a048e6fa1293771fc2d70..759da063b70d6a6af703f61b8a177ec16807e695 100644 GIT binary patch literal 44 gcmezG|38pHp8*6IQ7|JrBjf)d1|EnMBam|w0FOZi6#xJL literal 270 zcmezG|38pHp8*6Ip%~1;;4`x`GD6V*6b5AmM(3i`#FG3XD+RB_;(*j5g|O73;>`R! z1w%bc0|Pz7|3M5qATti_@% diff --git a/index/empty.jfif b/index/empty.jfif index 43a6ab7eb14d6a4874a74a9394663803db16b55e..d0b963e8298fe1fd9fe1c50e0b3e8bbaac5bc792 100644 GIT binary patch literal 139 zcmY+7I}XAy5Cz``n^<1Q*kK(~auW)qNN{S3^hi{E8g8=KCG)19r%(MNU6;!OB7o|k zADpr2x@N^xt+Un`>-&D|f)9Z`^AN{0#2lH^l=5CmDPeb*Cz(enqBpQ>J)#URO;Y1B)Q5kfNa@n{Z$vyHcTuQRBpg9Li1`4~hm|{Gei- zRMf=DB_=K*DW$5WuA!-AVrph?VQJ;;;_Bw^;pr6|5*ijB5gC=7lA4yDk(pIoQd(AC zQCZd8(%RPE(b+X=@|3C5rq7r;YtiB*OP4KQv2xX>&0Dr^+rDGxu0w~996fgY#K}{a zE?>EN?fQ+Iw;n!v{N(Ag=PzEq`uOSdm#^Qx|M>X}>z(JGL-`{vmgtrq9L1*V<3BCp|FxsBZr97#DyCVaw;1KeGpA5y2vG_V)9V+BgkuD kpAqM=CbE16_ZY%ow-|Vs8G(_kPrid0Ehr8PX6!zKafEm#DgjZGqLcQ*%=uj=zj`>G6SP?QEFmIevy@eS7LEM zYLP-%YEf}!ex8D%o~40-p5gx>1|E=^jI1my42=I{7=#@hJUjxB(f?Qm5k{<{Oqik* r7(jkzU`PcT2Gqyk-~i?#2&icY0Wb?B3N#H}bn^WM4)z9g@c$+NpUFgP diff --git a/index/empty.jpc b/index/empty.jpc index 905019b867beef2ab52a048e6fa1293771fc2d70..759da063b70d6a6af703f61b8a177ec16807e695 100644 GIT binary patch literal 44 gcmezG|38pHp8*6IQ7|JrBjf)d1|EnMBam|w0FOZi6#xJL literal 270 zcmezG|38pHp8*6Ip%~1;;4`x`GD6V*6b5AmM(3i`#FG3XD+RB_;(*j5g|O73;>`R! z1w%bc0|Pz7|3M5qATti_@% diff --git a/index/empty.jpe b/index/empty.jpe index 43a6ab7eb14d6a4874a74a9394663803db16b55e..d0b963e8298fe1fd9fe1c50e0b3e8bbaac5bc792 100644 GIT binary patch literal 139 zcmY+7I}XAy5Cz``n^<1Q*kK(~auW)qNN{S3^hi{E8g8=KCG)19r%(MNU6;!OB7o|k zADpr2x@N^xt+Un`>-&D|f)9Z`^AN{0#2lH^l=5CmDPeb*Cz(enqBpQ>J)#URO;Y1B)Q5kfNa@n{Z$vyHcTuQRBpg9Li1`4~hm|{Gei- zRMf=DB_=K*DW$5WuA!-AVrph?VQJ;;;_Bw^;pr6|5*ijB5gC=7lA4yDk(pIoQd(AC zQCZd8(%RPE(b+X=@|3C5rq7r;YtiB*OP4KQv2xX>&0Dr^+rDGxu0w~996fgY#K}{a zE?>EN?fQ+Iw;n!v{N(Ag=PzEq`uOSdm#^Qx|M>X}>z(JGL-`{vmgtrq9L1*V<3BCp|FxsBZr97#DyCVaw;1KeGpA5y2vG_V)9V+BgkuD kpAqM=CbE16_ZY%ow-|Vs8G(_-&D|f)9Z`^AN{0#2lH^l=5CmDPeb*Cz(enqBpQ>J)#URO;Y1B)Q5kfNa@n{Z$vyHcTuQRBpg9Li1`4~hm|{Gei- zRMf=DB_=K*DW$5WuA!-AVrph?VQJ;;;_Bw^;pr6|5*ijB5gC=7lA4yDk(pIoQd(AC zQCZd8(%RPE(b+X=@|3C5rq7r;YtiB*OP4KQv2xX>&0Dr^+rDGxu0w~996fgY#K}{a zE?>EN?fQ+Iw;n!v{N(Ag=PzEq`uOSdm#^Qx|M>X}>z(JGL-`{vmgtrq9L1*V<3BCp|FxsBZr97#DyCVaw;1KeGpA5y2vG_V)9V+BgkuD kpAqM=CbE16_ZY%ow-|Vs8G(_-&D|f)9Z`^AN{0#2lH^l=5CmDPeb*Cz(enqBpQ>J)#URO;Y1B)Q5kfNa@n{Z$vyHcTuQRBpg9Li1`4~hm|{Gei- zRMf=DB_=K*DW$5WuA!-AVrph?VQJ;;;_Bw^;pr6|5*ijB5gC=7lA4yDk(pIoQd(AC zQCZd8(%RPE(b+X=@|3C5rq7r;YtiB*OP4KQv2xX>&0Dr^+rDGxu0w~996fgY#K}{a zE?>EN?fQ+Iw;n!v{N(Ag=PzEq`uOSdm#^Qx|M>X}>z(JGL-`{vmgtrq9L1*V<3BCp|FxsBZr97#DyCVaw;1KeGpA5y2vG_V)9V+BgkuD kpAqM=CbE16_ZY%ow-|Vs8G(_dlQhY!N?32X9BVf8JQRafb=dPX57OFW}gDG znf9=N)c~0cEJ*AIC>vyMBNDr54>wf(0jFjp_5rAxTR`;(q3j@#p+JxT#2|BEbU|Wf zUWr~_YKel0o{^p@gUaE%`OJR*yf?PmmY4OOQoo(n6uk z*Gh2*xNn3wZ(p=@-uiWuC;oli_h_MZ#Cfi)Ku5HH1JQp>9qdoyx=|=ksQN$ zvPoW^KPKO}p)f<~Sz~X8!|8+i+m-uPJYD~bIl!BdNrVBn6M(J&g9b(r1$9t>H!B;6 O%Ls&~K)MmcVE_RCg+0gs literal 292 zcmWIWW@Zs#U|`^2P@Se7eRzMM!~`JkGZ3=_aY}x&UUqIy?>S$tLk>Kw4{H4bHA+|p%kZrzpB-hN!+e_^Y7W_;g`>?4u?%C4Sd5AbGWvS+~MY!#rxK|lda!aczS r6<}nLU}*fu`r(Jt9Y%=+4lr%A_tRw%Kfs%n4Wx?^2qS^?RwO* diff --git a/index/empty.nupkg b/index/empty.nupkg index de763dc4acc8d4755d72856e347de14e6c6b9a25..29838eb55b99c98956ea6b898ed43f6212d7b7f5 100644 GIT binary patch literal 258 zcmWIWW@Zs#U|`^2c;f36lb;f3GXcoE0v2IlNYu+KEiOn+_U-fLYc}9<`96o~DMR6l zWkIQjBG;(2t~l#n=peJ-v2~U7Y2&(ChJMu*-)C#T36?pkFm*;r`^+=lY1g+WKMB;? z`iz}n_EFX($vv8#mp6M&W~zylPv5c9v2mAkjHR`5`L;!=tl=kvRD17bIb{f+ce?v) ziPH|1OHtE9ejHdQx;8nuW*z&5KlPm-xZi)MIr5S}z?+dtgaNm^fzAhm21XDC^F@F+ RD;tQ<2!v)pdK-wt006fPSL^@) literal 1891 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 MK)4A=KL)!D0Gxg9tN;K2 literal 2784 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 literal 280 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 PoE)89`w!p+miRgVDx!+Z=A diff --git a/index/empty.tif b/index/empty.tif index 7bad36c983fce9ba662d1066fb75a6d50f3bfb2b..118d283d69e4e6856ee73c547ab21e818bb50be0 100644 GIT binary patch literal 198 zcmebD)MDUZU|`^7U|?isU<9(j7>UgUWHSTBtALmpNt_MJW&(-}GO~d6H2~S7P;rnR yF(mckP&FWNDMnVX`Zge28p`eivNe#*G-PA~s{smw0SC~XAeVxG2M~kY38Mk%g#^t2 literal 250 zcmebD)MDUcU|?uqPzVrU5McPnz{0=?-|lMpm#nw}5PEDEkqREd$jHvO@#Od_zViplcZz oUIDQ!k~omdzyXE~<%|q3KvqKOg2c?c61}|C5(N`IBRx|F0BH9QrvLx| diff --git a/index/empty.tiff b/index/empty.tiff index 7bad36c983fce9ba662d1066fb75a6d50f3bfb2b..118d283d69e4e6856ee73c547ab21e818bb50be0 100644 GIT binary patch literal 198 zcmebD)MDUZU|`^7U|?isU<9(j7>UgUWHSTBtALmpNt_MJW&(-}GO~d6H2~S7P;rnR yF(mckP&FWNDMnVX`Zge28p`eivNe#*G-PA~s{smw0SC~XAeVxG2M~kY38Mk%g#^t2 literal 250 zcmebD)MDUcU|?uqPzVrU5McPnz{0=?-|lMpm#nw}5PEDEkqREd$jHvO@#Od_zViplcZz oUIDQ!k~omdzyXE~<%|q3KvqKOg2c?c61}|C5(N`IBRx|F0BH9QrvLx| diff --git a/index/empty.wdp b/index/empty.wdp index 0138cf3c029aa29cc77f26d9a608d8f036d22716..67031e6d074e1ba5e404507a10d8e0096e015aac 100644 GIT binary patch literal 48 rcmebD+{4Jn00WE+dl*?57=c^`sF2FxyZOw1|GYQ0+Lo8~p5z4poOTJ( literal 300 zcmebD+{4Jhz`(%EAjQbazzAfm0%B<>dlQhY!N?32X9BVf8JQRafb=dPX57OFW}gDG znf9=N)c~0cEJ*AIC>vyMBNDr54>wf(0jFjp_5rAxTR`;(q3j@#p+JxT#2|BEbU|Wf zUWr~_YKel0o{^p@gUaE%`OJR*yf?PmmY4OOJ$(bU=hIuWOD#91OP>RIDt%k5Vxb_A3K9qys{jB1 diff --git a/index/empty.wmp b/index/empty.wmp index 0138cf3c029aa29cc77f26d9a608d8f036d22716..67031e6d074e1ba5e404507a10d8e0096e015aac 100644 GIT binary patch literal 48 rcmebD+{4Jn00WE+dl*?57=c^`sF2FxyZOw1|GYQ0+Lo8~p5z4poOTJ( literal 300 zcmebD+{4Jhz`(%EAjQbazzAfm0%B<>dlQhY!N?32X9BVf8JQRafb=dPX57OFW}gDG znf9=N)c~0cEJ*AIC>vyMBNDr54>wf(0jFjp_5rAxTR`;(q3j@#p+JxT#2|BEbU|Wf zUWr~_YKel0o{^p@gUaE%`OJR*yf?PmmY4OO_Giy3VIUptoS#>cnpYAZQdy8%9IIE6o3nP}-k`$<0xj{bi_L`RPHt-2I9KTc8&}+e z3FkK+`z1YB=*(?p>&7O|3judMc30oe3VE-VesreL><7|21E=2E`t@|RikR!iJ0}#M zSMVRGQfOb}q!K^HPNjXP%w<8PSAzX4w^!|AmaS2nf4%aa;_Y9JRW>(uzj1 zbM#-*oXFlI5^FEc=;lkmU%PnCdX~^h-R%MRG z-x{9ozRiysD&o;H#})uX<0laF193%;etCXTc2a(RHYh}T`>lnV4R~DEKf1_hu*Bn_ z#N{Om&vD8KY-IlJztC6)UaC@Cx?B_-`}M!v|GX|W0h3Z+PI0+qIRGVv0Ci>Jfpn4|P z>}AO;peNXXDO3vL8Au{U_7XI~T7wcS8*+k;5ESnS+P?CZjmYflZ#yLfrb^7v`M=HX zbngETt9J!Hvt;A-4RyZ1H9e|#O2geb5mS2)NX_xEQrJ}bgg4wUP-C)TTK&Dg{KQ~k}qWb7gzeG|x&?i+uECKNm$S=hisi`H!`e4cs6i>E2j(os)V%gvH zi9x37L4(tdFUnzsoJ{Hyb1&NM7i^bHcehUb`RQh?%C6})yBB`X>wFmeYy z>1STCELiW-b8EWS&Y9(cZrK{r8KQ^2+I{5YfA5}fXLI+%d-F0l3=iy=sSogGWD;S( zT_gZq2nG$n2tX+y&^4lGMu;{Bh6cv{Kqi)~iEaRTx*Aa}V&>WbCC2*>SpynOJD5;I6v@p;G@-y*c2~Lw_xpkQpMu`1i8=&e zGP(@N9&8M{de#0=vVsJ%u2z%b_*2wuny~^m+z@G8;$h>r!+VrXle=7h*3gKQ4>uKE zS`RQUswTac&}pJ}Q~K(xAo23M8j=O(Lip7Zf}!i)H;10DUfYtx&kA_`Du-ikwH+%9 zYkvDgV?3f(gm7OzoBmLoC4$oG5NF?RwyXrNLPuKr2(vplOehWzEt<~r3dud8s&Fht zAbvu-`?*)QrT>FP?`QHJOa`sa%k_%>z0p-1<}E3<%0Ayh>tI+6p##0I#DC0xuHxwv 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 source | anchor +snippet source | anchor @@ -240,7 +240,7 @@ Use or replace a file AllFiles.UseFile(Category.Document, pathToFile); IsTrue(AllFiles.DocumentPaths.Contains(pathToFile)); ``` -snippet source | anchor +snippet source | anchor diff --git a/src/EmptyFiles/AllFiles.cs b/src/EmptyFiles/AllFiles.cs index 119ccf2..98c4467 100644 --- a/src/EmptyFiles/AllFiles.cs +++ b/src/EmptyFiles/AllFiles.cs @@ -1,4 +1,6 @@ -namespace EmptyFiles; +using System.Reflection; + +namespace EmptyFiles; public static class AllFiles { @@ -32,7 +34,8 @@ public static class AllFiles static AllFiles() { - var directory = FindEmptyFilesDirectory(); + var directory = ExtractDirectory(); + ExtractAll(directory); archives = AddCategory(archiveExtensions, Category.Archive, directory); documents = AddCategory(documentExtensions, Category.Document, directory); @@ -62,52 +65,71 @@ void Append(FrozenDictionary files) static FrozenDictionary AddCategory(FrozenSet extensions, Category category, string emptyDirectory) { Dictionary items = []; - var categoryDirectory = Path.Combine( - emptyDirectory, - category - .ToString() - .ToLowerInvariant()); + var categoryName = category + .ToString() + .ToLowerInvariant(); + var categoryDirectory = Path.Combine(emptyDirectory, categoryName); foreach (var extension in extensions) { var file = Path.Combine(categoryDirectory, $"empty{extension}"); - items[extension] = EmptyFile.Build(file, category); + var resourceName = $"EmptyFiles.{categoryName}.empty{extension}"; + items[extension] = EmptyFile.Build(file, category, resourceName); } return items.ToFrozenDictionary(); } - static string FindEmptyFilesDirectory() + static string ExtractDirectory() + { + var assembly = typeof(AllFiles).Assembly; + var version = assembly.GetName().Version?.ToString() ?? "unknown"; + return Path.Combine(Path.GetTempPath(), "EmptyFiles", version); + } + + static void ExtractAll(string directory) + { + var assembly = typeof(AllFiles).Assembly; + ExtractCategory(assembly, directory, "archive", archiveExtensions); + ExtractCategory(assembly, directory, "binary", binaryExtensions); + ExtractCategory(assembly, directory, "document", documentExtensions); + ExtractCategory(assembly, directory, "image", imageExtensions); + ExtractCategory(assembly, directory, "sheet", sheetExtensions); + ExtractCategory(assembly, directory, "slide", slideExtensions); + } + + static void ExtractCategory(Assembly assembly, string root, string categoryName, FrozenSet extensions) { - var directories = FindDirectories() - .Select(_ => Path.Combine(_, "EmptyFiles")) - .ToList(); - foreach (var directory in directories) + var categoryDirectory = Path.Combine(root, categoryName); + Directory.CreateDirectory(categoryDirectory); + foreach (var extension in extensions) { - if (Directory.Exists(directory)) + var resourceName = $"EmptyFiles.{categoryName}.empty{extension}"; + using var resource = assembly.GetManifestResourceStream(resourceName) ?? + throw new($"Embedded resource not found: {resourceName}"); + var target = Path.Combine(categoryDirectory, $"empty{extension}"); + if (File.Exists(target) && new FileInfo(target).Length == resource.Length) { - return directory; + continue; } - } - throw new( - $""" - Could not find empty files directory. Searched: - {string.Join(Environment.NewLine, directories)} - """); + using var file = File.Create(target); + resource.CopyTo(file); + } } - static IEnumerable FindDirectories() + public static void WriteAllTo(string directory) { - yield return AppDomain.CurrentDomain.BaseDirectory; - yield return AssemblyLocation.Directory; - yield return Environment.CurrentDirectory; + Guard.AgainstNullOrEmpty(directory); + Directory.CreateDirectory(directory); + ExtractAll(directory); } public static void UseFile(Category category, string file) { Guard.FileExists(file); var extension = Path.GetExtension(file); - var emptyFile = EmptyFile.Build(file, category); + var resourceName = $"EmptyFiles.{category.ToString().ToLowerInvariant()}.empty{extension}"; + var emptyFile = EmptyFile.Build(file, category, resourceName); switch (category) { case Category.Archive: @@ -343,4 +365,4 @@ public static bool TryGetPathFor(string extension, [NotNullWhen(true)] out strin path = null; return false; } -} \ No newline at end of file +} diff --git a/src/EmptyFiles/AssemblyLocation.cs b/src/EmptyFiles/AssemblyLocation.cs deleted file mode 100644 index c5d5d4c..0000000 --- a/src/EmptyFiles/AssemblyLocation.cs +++ /dev/null @@ -1,26 +0,0 @@ -#pragma warning disable IL3000 - -static class AssemblyLocation -{ - static AssemblyLocation() - { - var assembly = typeof(AssemblyLocation).Assembly; -#if NET5_0_OR_GREATER - Path = assembly.Location; -#else - var uri = new UriBuilder(assembly.CodeBase); - Path = Uri.UnescapeDataString(uri.Path); -#endif - - if (string.IsNullOrWhiteSpace(Path)) - { - Path = AppContext.BaseDirectory; - } - - Directory = System.IO.Path.GetDirectoryName(Path)!; - } - - public static string Path; - - public static string Directory; -} \ No newline at end of file diff --git a/src/EmptyFiles/EmptyFile.cs b/src/EmptyFiles/EmptyFile.cs index 3ffb6ab..00118ab 100644 --- a/src/EmptyFiles/EmptyFile.cs +++ b/src/EmptyFiles/EmptyFile.cs @@ -1,22 +1,39 @@ -namespace EmptyFiles; +namespace EmptyFiles; public class EmptyFile { public string Path { get; } public DateTime LastWriteTime { get; } public Category Category { get; } + public string Extension { get; } + internal string ResourceName { get; } - internal static EmptyFile Build(string file, Category category) + internal static EmptyFile Build(string file, Category category, string resourceName) { var writeTime = File.GetLastWriteTime(file); - return new(file, writeTime, category); + var extension = System.IO.Path.GetExtension(file); + return new(file, writeTime, category, extension, resourceName); } public EmptyFile(string path, in DateTime lastWriteTime, in Category category) + : this(path, lastWriteTime, category, System.IO.Path.GetExtension(path), $"EmptyFiles.{category.ToString().ToLowerInvariant()}.empty{System.IO.Path.GetExtension(path)}") + { + } + + EmptyFile(string path, in DateTime lastWriteTime, in Category category, string extension, string resourceName) { Guard.AgainstNullOrEmpty(path); Path = path; LastWriteTime = lastWriteTime; Category = category; + Extension = extension; + ResourceName = resourceName; + } + + public Stream OpenRead() + { + var assembly = typeof(EmptyFile).Assembly; + return assembly.GetManifestResourceStream(ResourceName) ?? + throw new($"Embedded resource not found: {ResourceName}"); } -} \ No newline at end of file +} diff --git a/src/EmptyFiles/EmptyFiles.csproj b/src/EmptyFiles/EmptyFiles.csproj index 8981a7f..8df7861 100644 --- a/src/EmptyFiles/EmptyFiles.csproj +++ b/src/EmptyFiles/EmptyFiles.csproj @@ -4,6 +4,7 @@ $(TargetFrameworks);net6.0;net7.0;net8.0;net9.0;net10.0;net11.0 true true + false @@ -12,12 +13,26 @@ - - - - true - files - + + + + EmptyFiles.archive.%(Filename)%(Extension) + + + EmptyFiles.binary.%(Filename)%(Extension) + + + EmptyFiles.document.%(Filename)%(Extension) + + + EmptyFiles.image.%(Filename)%(Extension) + + + EmptyFiles.sheet.%(Filename)%(Extension) + + + EmptyFiles.slide.%(Filename)%(Extension) + diff --git a/src/Tests/BuildTargetsTests.cs b/src/Tests/BuildTargetsTests.cs deleted file mode 100644 index ffc15a6..0000000 --- a/src/Tests/BuildTargetsTests.cs +++ /dev/null @@ -1,130 +0,0 @@ -public class BuildTargetsTests -{ - [Test] - public async Task BuildCopiesEmptyFiles() - { - using var temp = new TempDirectory(); - - var (nugetSource, packageVersion) = FindPackageInfo(); - WriteCsproj(temp, packageVersion); - WriteNugetConfig(temp, nugetSource); - - var exitCode = await DotnetBuild(temp); - That(exitCode, Is.EqualTo(0)); - - var emptyFilesDir = Path.Combine(temp.Path, "bin", "Release", "net11.0", "EmptyFiles"); - That(Directory.Exists(emptyFilesDir), Is.True, "EmptyFiles directory should exist after build"); - That(Directory.GetFiles(emptyFilesDir, "*", SearchOption.AllDirectories), Is.Not.Empty, "EmptyFiles directory should contain files"); - } - - [Test] - public async Task RecoverFromDeletedEmptyFilesDirectory() - { - using var temp = new TempDirectory(); - - var (nugetSource, packageVersion) = FindPackageInfo(); - WriteCsproj(temp, packageVersion); - WriteNugetConfig(temp, nugetSource); - - // First build - var exitCode = await DotnetBuild(temp); - That(exitCode, Is.EqualTo(0)); - - var emptyFilesDir = Path.Combine(temp.Path, "bin", "Release", "net11.0", "EmptyFiles"); - That(Directory.Exists(emptyFilesDir), Is.True, "EmptyFiles directory should exist after first build"); - - // Delete EmptyFiles directory from output (leave obj/ intact so marker file survives) - Directory.Delete(emptyFilesDir, true); - That(Directory.Exists(emptyFilesDir), Is.False); - - // Second build — should recover - exitCode = await DotnetBuild(temp); - That(exitCode, Is.EqualTo(0)); - - That(Directory.Exists(emptyFilesDir), Is.True, "EmptyFiles directory should exist after second build"); - That(Directory.GetFiles(emptyFilesDir, "*", SearchOption.AllDirectories), Is.Not.Empty, "EmptyFiles directory should contain files after recovery"); - } - - static void WriteCsproj(TempDirectory temp, string version) - { - var csproj = $""" - - - net11.0 - packages - - - - - - """; - File.WriteAllText(Path.Combine(temp.Path, "Project.csproj"), csproj); - } - - static void WriteNugetConfig(TempDirectory temp, string nugetSource) - { - var config = $""" - - - - - - - - """; - File.WriteAllText(Path.Combine(temp.Path, "nuget.config"), config); - } - - static async Task DotnetBuild(TempDirectory temp) - { - var startInfo = new ProcessStartInfo("dotnet", "build --configuration Release --disable-build-servers -nodeReuse:false /p:UseSharedCompilation=false") - { - WorkingDirectory = temp.Path, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - - using var process = Process.Start(startInfo)!; - var stdout = await process.StandardOutput.ReadToEndAsync(); - var stderr = await process.StandardError.ReadToEndAsync(); - await process.WaitForExitAsync(); - - await TestContext.Out.WriteLineAsync("STDOUT:"); - await TestContext.Out.WriteLineAsync(stdout); - await TestContext.Out.WriteLineAsync("STDERR:"); - await TestContext.Out.WriteLineAsync(stderr); - - return process.ExitCode; - } - - static (string nugetSource, string packageVersion) FindPackageInfo() - { - var nugetSource = FindNugetSource(); - var packageVersion = FindPackageVersion(nugetSource); - return (nugetSource, packageVersion); - } - - static string FindNugetSource() - { - var nugetsDir = Path.GetFullPath(Path.Combine(ProjectFiles.SolutionDirectory, "..", "nugets")); - if (Directory.Exists(nugetsDir)) - { - return nugetsDir; - } - - throw new InvalidOperationException($"Cannot find nugets directory at {nugetsDir}"); - } - - static string FindPackageVersion(string nugetSource) - { - var nupkg = Directory - .GetFiles(nugetSource, "EmptyFiles.*.nupkg") - .FirstOrDefault(_ => !Path.GetFileName(_).StartsWith("EmptyFiles.Tool")) ?? - throw new InvalidOperationException($"Cannot find EmptyFiles nupkg in {nugetSource}"); - - var fileName = Path.GetFileNameWithoutExtension(nupkg); - return fileName["EmptyFiles.".Length..]; - } -} \ No newline at end of file diff --git a/src/Tests/Tests.cs b/src/Tests/Tests.cs index f409451..07ce201 100644 --- a/src/Tests/Tests.cs +++ b/src/Tests/Tests.cs @@ -175,6 +175,40 @@ public void IsEmptyFile() File.Delete(temp); } + [Test] + public void WriteAllTo() + { + var directory = Path.Combine(Path.GetTempPath(), "EmptyFilesWriteAllTo"); + if (Directory.Exists(directory)) + { + Directory.Delete(directory, true); + } + + #region WriteAllTo + + AllFiles.WriteAllTo(directory); + + #endregion + + foreach (var category in Enum.GetValues()) + { + var categoryDir = Path.Combine(directory, category.ToString().ToLowerInvariant()); + True(Directory.Exists(categoryDir)); + } + + foreach (var file in AllFiles.Files.Values) + { + var expected = Path.Combine( + directory, + file.Category.ToString().ToLowerInvariant(), + $"empty{file.Extension}"); + True(File.Exists(expected), expected); + That(new FileInfo(expected).Length, Is.EqualTo(new FileInfo(file.Path).Length)); + } + + Directory.Delete(directory, true); + } + [Test] public void AllPaths() { diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index 8161d2d..deb9ba6 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -18,5 +18,4 @@ - diff --git a/src/extensions.include.md b/src/extensions.include.md index c849350..bf002d0 100644 --- a/src/extensions.include.md +++ b/src/extensions.include.md @@ -5,63 +5,63 @@ * .7zip (32 bytes) * .bz2 (14 bytes) * .bzip2 (14 bytes) - * .gz (29 bytes) - * .gzip (29 bytes) - * .kmz (292 bytes) - * .nupkg (1.8 KB) - * .tar (1.5 KB) + * .gz (20 bytes) + * .gzip (20 bytes) + * .kmz (204 bytes) + * .nupkg (258 bytes) + * .tar (1 KB) * .xz (32 bytes) * .zip (22 bytes) ### Document - * .docx (1.9 KB) - * .odt (2.2 KB) - * .pdf (280 bytes) + * .docx (810 bytes) + * .odt (647 bytes) + * .pdf (212 bytes) * .rtf (6 bytes) ### Image - * .avif (298 bytes) + * .avif (70 bytes) * .bmp (58 bytes) - * .dds (136 bytes) + * .dds (132 bytes) * .dib (58 bytes) - * .emf (620 bytes) - * .exif (734 bytes) - * .gif (799 bytes) - * .heic (3.2 KB) - * .heif (209 bytes) - * .ico (70 bytes) - * .j2c (270 bytes) - * .jfif (734 bytes) - * .jp2 (354 bytes) - * .jpc (270 bytes) - * .jpe (734 bytes) - * .jpeg (734 bytes) - * .jpg (734 bytes) - * .jxr (300 bytes) + * .emf (132 bytes) + * .exif (139 bytes) + * .gif (28 bytes) + * .heic (32 bytes) + * .heif (32 bytes) + * .ico (68 bytes) + * .j2c (44 bytes) + * .jfif (139 bytes) + * .jp2 (77 bytes) + * .jpc (44 bytes) + * .jpe (139 bytes) + * .jpeg (139 bytes) + * .jpg (139 bytes) + * .jxr (48 bytes) * .pbm (8 bytes) - * .pcx (131 bytes) - * .pgm (12 bytes) - * .png (119 bytes) - * .ppm (14 bytes) + * .pcx (129 bytes) + * .pgm (10 bytes) + * .png (69 bytes) + * .ppm (12 bytes) * .rle (58 bytes) - * .tga (543 bytes) - * .tif (250 bytes) - * .tiff (250 bytes) - * .wdp (300 bytes) - * .webp (228 bytes) - * .wmp (300 bytes) + * .tga (21 bytes) + * .tif (198 bytes) + * .tiff (198 bytes) + * .wdp (48 bytes) + * .webp (26 bytes) + * .wmp (48 bytes) ### Sheet - * .ods (2.7 KB) - * .xlsx (4.5 KB) + * .ods (683 bytes) + * .xlsx (1.4 KB) ### Slide - * .odp (7.8 KB) - * .pptx (13.3 KB) + * .odp (667 bytes) + * .pptx (842 bytes) ### Binary From e9a7bc6be1116bba6de0e85a69344ec83da76b10 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Mon, 13 Apr 2026 21:43:32 +1000 Subject: [PATCH 2/5] . --- readme.md | 4 +- src/EmptyFiles/AllFiles.cs | 8 +--- src/EmptyFiles/EmptyFile.cs | 82 ++++++++++++++++++++++++++++++------- src/Tests/Tests.cs | 6 +-- 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/readme.md b/readme.md index c11053a..e8cd2e8 100644 --- a/readme.md +++ b/readme.md @@ -226,7 +226,7 @@ foreach (var path in AllFiles.AllPaths) Trace.WriteLine(path); } ``` -snippet source | anchor +snippet source | anchor @@ -240,7 +240,7 @@ Use or replace a file AllFiles.UseFile(Category.Document, pathToFile); IsTrue(AllFiles.DocumentPaths.Contains(pathToFile)); ``` -snippet source | anchor +snippet source | anchor diff --git a/src/EmptyFiles/AllFiles.cs b/src/EmptyFiles/AllFiles.cs index 98c4467..d46c28e 100644 --- a/src/EmptyFiles/AllFiles.cs +++ b/src/EmptyFiles/AllFiles.cs @@ -1,5 +1,3 @@ -using System.Reflection; - namespace EmptyFiles; public static class AllFiles @@ -35,7 +33,6 @@ public static class AllFiles static AllFiles() { var directory = ExtractDirectory(); - ExtractAll(directory); archives = AddCategory(archiveExtensions, Category.Archive, directory); documents = AddCategory(documentExtensions, Category.Document, directory); @@ -73,7 +70,7 @@ static FrozenDictionary AddCategory(FrozenSet extensi { var file = Path.Combine(categoryDirectory, $"empty{extension}"); var resourceName = $"EmptyFiles.{categoryName}.empty{extension}"; - items[extension] = EmptyFile.Build(file, category, resourceName); + items[extension] = EmptyFile.Embedded(file, category, extension, resourceName); } return items.ToFrozenDictionary(); @@ -128,8 +125,7 @@ public static void UseFile(Category category, string file) { Guard.FileExists(file); var extension = Path.GetExtension(file); - var resourceName = $"EmptyFiles.{category.ToString().ToLowerInvariant()}.empty{extension}"; - var emptyFile = EmptyFile.Build(file, category, resourceName); + var emptyFile = new EmptyFile(file, File.GetLastWriteTime(file), category); switch (category) { case Category.Archive: diff --git a/src/EmptyFiles/EmptyFile.cs b/src/EmptyFiles/EmptyFile.cs index 00118ab..9bb1072 100644 --- a/src/EmptyFiles/EmptyFile.cs +++ b/src/EmptyFiles/EmptyFile.cs @@ -2,32 +2,86 @@ namespace EmptyFiles; public class EmptyFile { - public string Path { get; } - public DateTime LastWriteTime { get; } + public string Path + { + get + { + EnsureExtracted(); + return path; + } + } + + public DateTime LastWriteTime + { + get + { + EnsureExtracted(); + return lastWriteTime; + } + } + public Category Category { get; } public string Extension { get; } internal string ResourceName { get; } - internal static EmptyFile Build(string file, Category category, string resourceName) + string path; + DateTime lastWriteTime; + bool extracted; + readonly object sync = new(); + + internal static EmptyFile Embedded(string targetPath, Category category, string extension, string resourceName) => + new(targetPath, category, extension, resourceName, extracted: false, lastWriteTime: default); + + EmptyFile(string path, Category category, string extension, string resourceName, bool extracted, DateTime lastWriteTime) { - var writeTime = File.GetLastWriteTime(file); - var extension = System.IO.Path.GetExtension(file); - return new(file, writeTime, category, extension, resourceName); + Guard.AgainstNullOrEmpty(path); + this.path = path; + Category = category; + Extension = extension; + ResourceName = resourceName; + this.extracted = extracted; + this.lastWriteTime = lastWriteTime; } public EmptyFile(string path, in DateTime lastWriteTime, in Category category) - : this(path, lastWriteTime, category, System.IO.Path.GetExtension(path), $"EmptyFiles.{category.ToString().ToLowerInvariant()}.empty{System.IO.Path.GetExtension(path)}") + : this( + path, + category, + System.IO.Path.GetExtension(path), + $"EmptyFiles.{category.ToString().ToLowerInvariant()}.empty{System.IO.Path.GetExtension(path)}", + extracted: true, + lastWriteTime: lastWriteTime) { } - EmptyFile(string path, in DateTime lastWriteTime, in Category category, string extension, string resourceName) + void EnsureExtracted() { - Guard.AgainstNullOrEmpty(path); - Path = path; - LastWriteTime = lastWriteTime; - Category = category; - Extension = extension; - ResourceName = resourceName; + if (extracted) + { + return; + } + + lock (sync) + { + if (extracted) + { + return; + } + + var directory = System.IO.Path.GetDirectoryName(path)!; + Directory.CreateDirectory(directory); + var assembly = typeof(EmptyFile).Assembly; + using var resource = assembly.GetManifestResourceStream(ResourceName) ?? + throw new($"Embedded resource not found: {ResourceName}"); + if (!File.Exists(path) || new FileInfo(path).Length != resource.Length) + { + using var fileStream = File.Create(path); + resource.CopyTo(fileStream); + } + + lastWriteTime = File.GetLastWriteTime(path); + extracted = true; + } } public Stream OpenRead() diff --git a/src/Tests/Tests.cs b/src/Tests/Tests.cs index 07ce201..18a6aa7 100644 --- a/src/Tests/Tests.cs +++ b/src/Tests/Tests.cs @@ -178,11 +178,7 @@ public void IsEmptyFile() [Test] public void WriteAllTo() { - var directory = Path.Combine(Path.GetTempPath(), "EmptyFilesWriteAllTo"); - if (Directory.Exists(directory)) - { - Directory.Delete(directory, true); - } + using var directory = new TempDirectory(); #region WriteAllTo From 04082cb2b4b5c4cca425fe5626502080da55bc16 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Mon, 13 Apr 2026 21:59:58 +1000 Subject: [PATCH 3/5] . --- src/EmptyFiles/AllFiles.cs | 35 ++++++++++++++++----------------- src/EmptyFiles/EmptyFile.cs | 39 ++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/EmptyFiles/AllFiles.cs b/src/EmptyFiles/AllFiles.cs index d46c28e..977e28a 100644 --- a/src/EmptyFiles/AllFiles.cs +++ b/src/EmptyFiles/AllFiles.cs @@ -32,14 +32,12 @@ public static class AllFiles static AllFiles() { - var directory = ExtractDirectory(); - - archives = AddCategory(archiveExtensions, Category.Archive, directory); - documents = AddCategory(documentExtensions, Category.Document, directory); - images = AddCategory(imageExtensions, Category.Image, directory); - sheets = AddCategory(sheetExtensions, Category.Sheet, directory); - slides = AddCategory(slideExtensions, Category.Slide, directory); - binary = AddCategory(binaryExtensions, Category.Binary, directory); + archives = AddCategory(archiveExtensions, Category.Archive); + documents = AddCategory(documentExtensions, Category.Document); + images = AddCategory(imageExtensions, Category.Image); + sheets = AddCategory(sheetExtensions, Category.Sheet); + slides = AddCategory(slideExtensions, Category.Slide); + binary = AddCategory(binaryExtensions, Category.Binary); var all = new Dictionary(); Append(archives); Append(documents); @@ -59,29 +57,30 @@ void Append(FrozenDictionary files) } } - static FrozenDictionary AddCategory(FrozenSet extensions, Category category, string emptyDirectory) + static FrozenDictionary AddCategory(FrozenSet extensions, Category category) { Dictionary items = []; var categoryName = category .ToString() .ToLowerInvariant(); - var categoryDirectory = Path.Combine(emptyDirectory, categoryName); foreach (var extension in extensions) { - var file = Path.Combine(categoryDirectory, $"empty{extension}"); var resourceName = $"EmptyFiles.{categoryName}.empty{extension}"; - items[extension] = EmptyFile.Embedded(file, category, extension, resourceName); + items[extension] = EmptyFile.Embedded(category, extension, resourceName); } return items.ToFrozenDictionary(); } - static string ExtractDirectory() - { - var assembly = typeof(AllFiles).Assembly; - var version = assembly.GetName().Version?.ToString() ?? "unknown"; - return Path.Combine(Path.GetTempPath(), "EmptyFiles", version); - } + static readonly Lazy extractDirectory = new( + () => + { + var assembly = typeof(AllFiles).Assembly; + var version = assembly.GetName().Version?.ToString() ?? "unknown"; + return Path.Combine(Path.GetTempPath(), "EmptyFiles", version); + }); + + internal static string ExtractDirectory => extractDirectory.Value; static void ExtractAll(string directory) { diff --git a/src/EmptyFiles/EmptyFile.cs b/src/EmptyFiles/EmptyFile.cs index 9bb1072..3aa47ae 100644 --- a/src/EmptyFiles/EmptyFile.cs +++ b/src/EmptyFiles/EmptyFile.cs @@ -7,7 +7,7 @@ public string Path get { EnsureExtracted(); - return path; + return path!; } } @@ -24,34 +24,30 @@ public DateTime LastWriteTime public string Extension { get; } internal string ResourceName { get; } - string path; + string? path; DateTime lastWriteTime; bool extracted; readonly object sync = new(); - internal static EmptyFile Embedded(string targetPath, Category category, string extension, string resourceName) => - new(targetPath, category, extension, resourceName, extracted: false, lastWriteTime: default); + internal static EmptyFile Embedded(Category category, string extension, string resourceName) => + new(category, extension, resourceName); - EmptyFile(string path, Category category, string extension, string resourceName, bool extracted, DateTime lastWriteTime) + EmptyFile(Category category, string extension, string resourceName) { - Guard.AgainstNullOrEmpty(path); - this.path = path; Category = category; Extension = extension; ResourceName = resourceName; - this.extracted = extracted; - this.lastWriteTime = lastWriteTime; } public EmptyFile(string path, in DateTime lastWriteTime, in Category category) - : this( - path, - category, - System.IO.Path.GetExtension(path), - $"EmptyFiles.{category.ToString().ToLowerInvariant()}.empty{System.IO.Path.GetExtension(path)}", - extracted: true, - lastWriteTime: lastWriteTime) { + Guard.AgainstNullOrEmpty(path); + this.path = path; + this.lastWriteTime = lastWriteTime; + Category = category; + Extension = System.IO.Path.GetExtension(path); + ResourceName = $"EmptyFiles.{category.ToString().ToLowerInvariant()}.empty{Extension}"; + extracted = true; } void EnsureExtracted() @@ -68,18 +64,21 @@ void EnsureExtracted() return; } - var directory = System.IO.Path.GetDirectoryName(path)!; + var categoryName = Category.ToString().ToLowerInvariant(); + var directory = System.IO.Path.Combine(AllFiles.ExtractDirectory, categoryName); Directory.CreateDirectory(directory); + var target = System.IO.Path.Combine(directory, $"empty{Extension}"); var assembly = typeof(EmptyFile).Assembly; using var resource = assembly.GetManifestResourceStream(ResourceName) ?? throw new($"Embedded resource not found: {ResourceName}"); - if (!File.Exists(path) || new FileInfo(path).Length != resource.Length) + if (!File.Exists(target) || new FileInfo(target).Length != resource.Length) { - using var fileStream = File.Create(path); + using var fileStream = File.Create(target); resource.CopyTo(fileStream); } - lastWriteTime = File.GetLastWriteTime(path); + path = target; + lastWriteTime = File.GetLastWriteTime(target); extracted = true; } } From fd86b1ce27d263a44d1abd82f1c7fbf3a3fc7c48 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Mon, 13 Apr 2026 22:06:49 +1000 Subject: [PATCH 4/5] . --- src/Directory.Build.props | 2 +- src/EmptyFiles.slnx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 0e9d113..25bb5e7 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ CS1591;CS0649;NU5119;NU1608;NU1109 - 8.17.2 + 8.18.0 preview 1.0.0 A collection of minimal binary files. diff --git a/src/EmptyFiles.slnx b/src/EmptyFiles.slnx index 29160d4..c95ac2d 100644 --- a/src/EmptyFiles.slnx +++ b/src/EmptyFiles.slnx @@ -1,7 +1,6 @@ - From d4f63072dfa8650848f97bbe36e36e7a6cfc3d98 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Tue, 14 Apr 2026 21:57:59 +1000 Subject: [PATCH 5/5] refs and cleanup --- src/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index bae00ad..d59a128 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,12 +4,12 @@ true - + - +