22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Collections . Concurrent ;
5-
5+ using System . Diagnostics ;
66using Docfx . Common ;
77using Docfx . Plugins ;
88
@@ -107,26 +107,45 @@ private void CopyTemplateResources(string outputDirectory, IEnumerable<TemplateB
107107 {
108108 foreach ( var resourceInfo in templateBundles . SelectMany ( s => s . Resources ) . Distinct ( ) )
109109 {
110+ var resourceKey = resourceInfo . ResourceKey ;
111+
110112 try
111113 {
112- using var stream = _resourceProvider . GetResourceStream ( resourceInfo . ResourceKey ) ;
113- CopyTemplateResource ( stream , outputDirectory , resourceInfo . ResourceKey ) ;
114+ using var stream = _resourceProvider . GetResourceStream ( resourceKey ) ;
115+
116+ if ( stream is FileStream fileStream )
117+ {
118+ CopyTemplateResourceFile ( new FileInfo ( fileStream . Name ) , outputDirectory , resourceKey ) ;
119+ }
120+ else
121+ {
122+ CopyTemplateResourceStream ( stream , outputDirectory , resourceKey ) ;
123+ }
114124 }
115125 catch ( Exception e )
116126 {
117- Logger . Log ( LogLevel . Info , $ "Unable to get relative resource for { resourceInfo . ResourceKey } : { e . Message } ") ;
127+ Logger . Log ( LogLevel . Info , $ "Unable to get relative resource for { resourceKey } : { e . Message } ") ;
118128 }
119129 }
120130 }
121131
122- private static void CopyTemplateResource ( Stream stream , string outputDirectory , string filePath )
132+ /// <summary>
133+ /// Copy template resource from stream.
134+ /// </summary>
135+ private static void CopyTemplateResourceStream ( Stream stream , string outputDirectory , string filePath )
123136 {
124137 if ( stream != null )
125138 {
126139 var path = Path . Combine ( outputDirectory , filePath ) ;
127140 Directory . CreateDirectory ( Path . GetFullPath ( Path . GetDirectoryName ( path ) ) ) ;
128141
129- using ( var writer = new FileStream ( path , FileMode . Create , FileAccess . ReadWrite ) )
142+ using ( var writer = new FileStream ( path , new FileStreamOptions
143+ {
144+ Mode = FileMode . Create ,
145+ Access = FileAccess . Write ,
146+ Share = FileShare . None ,
147+ PreallocationSize = stream . Length ,
148+ } ) )
130149 {
131150 stream . CopyTo ( writer ) ;
132151 }
@@ -139,6 +158,35 @@ private static void CopyTemplateResource(Stream stream, string outputDirectory,
139158 }
140159 }
141160
161+ /// <summary>
162+ /// Copy template resource as file.
163+ /// </summary>
164+ private static void CopyTemplateResourceFile ( FileInfo inputFileInfo , string outputDirectory , string filePath )
165+ {
166+ Debug . Assert ( inputFileInfo . Exists ) ;
167+
168+ var path = Path . Combine ( outputDirectory , filePath ) ;
169+ var outputFileInfo = new FileInfo ( path ) ;
170+ outputFileInfo . Directory . Create ( ) ;
171+
172+ bool isOverwrite = outputFileInfo . Exists ;
173+
174+ // Skip file copy if following condition met.
175+ bool skipFileCopy = isOverwrite // Output file is already exists.
176+ && inputFileInfo . Length == outputFileInfo . Length // File's lengths are identical.
177+ && inputFileInfo . LastWriteTimeUtc == outputFileInfo . LastWriteTimeUtc ; // File's LastWriteTimeUtc are identical.
178+ if ( skipFileCopy )
179+ {
180+ Logger . Log ( LogLevel . Verbose , $ "Skipped resource { filePath } that template dependants on to { outputFileInfo . FullName } ") ;
181+ return ;
182+ }
183+ else
184+ {
185+ File . Copy ( inputFileInfo . FullName , outputFileInfo . FullName , isOverwrite ) ; // Use `File.Copy` API to preserve LastWriteTime.
186+ Logger . Log ( LogLevel . Verbose , $ "Saved resource { filePath } that template dependants on to { outputFileInfo . FullName } ") ;
187+ }
188+ }
189+
142190 private List < ManifestItem > ProcessCore ( List < InternalManifestItem > items , ApplyTemplateSettings settings , IDictionary < string , object > globals )
143191 {
144192 var manifest = new ConcurrentBag < ManifestItem > ( ) ;
0 commit comments