1616namespace Microsoft . Web . LibraryManager . Providers
1717{
1818 /// <summary>
19- /// Default implenentation for a provider, since most provider implementations are very similar.
19+ /// Default implementation for a provider, since most provider implementations are very similar.
2020 /// </summary>
2121 internal abstract class BaseProvider : IProvider
2222 {
@@ -60,30 +60,61 @@ public virtual async Task<ILibraryOperationResult> InstallAsync(ILibraryInstalla
6060 return LibraryOperationResult . FromCancelled ( desiredState ) ;
6161 }
6262
63- //Expand the files property if needed
64- ILibraryOperationResult updateResult = await UpdateStateAsync ( desiredState , cancellationToken ) ;
65- if ( ! updateResult . Success )
66- {
67- return updateResult ;
68- }
63+ ILibraryCatalog catalog = GetCatalog ( ) ;
64+ ILibrary library = await catalog . GetLibraryAsync ( desiredState . Name , desiredState . Version , cancellationToken ) . ConfigureAwait ( false ) ;
6965
70- desiredState = updateResult . InstallationState ;
66+ LibraryInstallationGoalState goalState = GenerateGoalState ( desiredState , library ) ;
7167
72- // Refresh cache if needed
73- ILibraryOperationResult cacheUpdateResult = await RefreshCacheAsync ( desiredState , cancellationToken ) ;
74- if ( ! cacheUpdateResult . Success )
68+ if ( ! IsSourceCacheReady ( goalState ) )
7569 {
76- return cacheUpdateResult ;
70+ ILibraryOperationResult updateCacheResult = await RefreshCacheAsync ( desiredState , library , cancellationToken ) ;
71+ if ( ! updateCacheResult . Success )
72+ {
73+ return updateCacheResult ;
74+ }
7775 }
7876
79- // Check if Library is already up to date
80- if ( IsLibraryUpToDate ( desiredState ) )
77+ if ( goalState . IsAchieved ( ) )
8178 {
8279 return LibraryOperationResult . FromUpToDate ( desiredState ) ;
8380 }
8481
85- // Write files to destination
86- return await WriteToFilesAsync ( desiredState , cancellationToken ) ;
82+ return await InstallFiles ( goalState , cancellationToken ) ;
83+
84+ }
85+
86+ private async Task < LibraryOperationResult > InstallFiles ( LibraryInstallationGoalState goalState , CancellationToken cancellationToken )
87+ {
88+ try
89+ {
90+ foreach ( KeyValuePair < string , string > kvp in goalState . InstalledFiles )
91+ {
92+ if ( cancellationToken . IsCancellationRequested )
93+ {
94+ return LibraryOperationResult . FromCancelled ( goalState . InstallationState ) ;
95+ }
96+
97+ string sourcePath = kvp . Value ;
98+ string destinationPath = kvp . Key ;
99+ bool writeOk = await HostInteraction . CopyFileAsync ( sourcePath , destinationPath , cancellationToken ) ;
100+
101+ if ( ! writeOk )
102+ {
103+ return new LibraryOperationResult ( goalState . InstallationState , PredefinedErrors . CouldNotWriteFile ( destinationPath ) ) ;
104+ }
105+ }
106+ }
107+ catch ( UnauthorizedAccessException )
108+ {
109+ return new LibraryOperationResult ( goalState . InstallationState , PredefinedErrors . PathOutsideWorkingDirectory ( ) ) ;
110+ }
111+ catch ( Exception ex )
112+ {
113+ HostInteraction . Logger . Log ( ex . ToString ( ) , LogLevel . Error ) ;
114+ return new LibraryOperationResult ( goalState . InstallationState , PredefinedErrors . UnknownException ( ) ) ;
115+ }
116+
117+ return LibraryOperationResult . FromSuccess ( goalState . InstallationState ) ;
87118 }
88119
89120 /// <inheritdoc />
@@ -165,6 +196,50 @@ public virtual async Task<ILibraryOperationResult> UpdateStateAsync(ILibraryInst
165196
166197 #endregion
167198
199+ public LibraryInstallationGoalState GenerateGoalState ( ILibraryInstallationState desiredState , ILibrary library )
200+ {
201+ var goalState = new LibraryInstallationGoalState ( desiredState ) ;
202+ IEnumerable < string > outFiles ;
203+ if ( desiredState . Files == null || desiredState . Files . Count == 0 )
204+ {
205+ outFiles = library . Files . Keys ;
206+ }
207+ else
208+ {
209+ outFiles = FileGlobbingUtility . ExpandFileGlobs ( desiredState . Files , library . Files . Keys ) ;
210+ }
211+
212+ foreach ( string outFile in outFiles )
213+ {
214+ // strip the source prefix
215+ string destinationFile = Path . Combine ( HostInteraction . WorkingDirectory , desiredState . DestinationPath , outFile ) ;
216+
217+ // don't forget to include the cache folder in the path
218+ string sourceFile = GetCachedFileLocalPath ( desiredState , outFile ) ;
219+
220+ // TODO: make goalState immutable
221+ // map destination back to the library-relative file it originated from
222+ goalState . InstalledFiles . Add ( destinationFile , sourceFile ) ;
223+ }
224+
225+ return goalState ;
226+ }
227+
228+ public bool IsSourceCacheReady ( LibraryInstallationGoalState goalState )
229+ {
230+ foreach ( KeyValuePair < string , string > item in goalState . InstalledFiles )
231+ {
232+ string cachePath = GetCachedFileLocalPath ( goalState . InstallationState , item . Value ) ;
233+ // TODO: use abstraction for filesystem ops
234+ if ( ! File . Exists ( cachePath ) )
235+ {
236+ return false ;
237+ }
238+ }
239+
240+ return true ;
241+ }
242+
168243 protected virtual ILibraryOperationResult CheckForInvalidFiles ( ILibraryInstallationState desiredState , string libraryId , ILibrary library )
169244 {
170245 IReadOnlyList < string > invalidFiles = library . GetInvalidFiles ( desiredState . Files ) ;
@@ -239,36 +314,7 @@ protected async Task<ILibraryOperationResult> WriteToFilesAsync(ILibraryInstalla
239314 /// <returns></returns>
240315 private string GetCachedFileLocalPath ( ILibraryInstallationState state , string sourceFile )
241316 {
242- return Path . Combine ( CacheFolder , state . Name , state . Version , sourceFile ) ;
243- }
244-
245- private bool IsLibraryUpToDate ( ILibraryInstallationState state )
246- {
247- try
248- {
249- if ( ! string . IsNullOrEmpty ( state . Name ) && ! string . IsNullOrEmpty ( state . Version ) )
250- {
251- string cacheDir = Path . Combine ( CacheFolder , state . Name , state . Version ) ;
252- string destinationDir = Path . Combine ( HostInteraction . WorkingDirectory , state . DestinationPath ) ;
253-
254- foreach ( string sourceFile in state . Files )
255- {
256- var destinationFile = new FileInfo ( Path . Combine ( destinationDir , sourceFile ) . Replace ( '\\ ' , '/' ) ) ;
257- var cacheFile = new FileInfo ( Path . Combine ( cacheDir , sourceFile ) . Replace ( '\\ ' , '/' ) ) ;
258-
259- if ( ! destinationFile . Exists || ! cacheFile . Exists || ! FileHelpers . AreFilesUpToDate ( destinationFile , cacheFile ) )
260- {
261- return false ;
262- }
263- }
264- }
265- }
266- catch
267- {
268- return false ;
269- }
270-
271- return true ;
317+ return Path . Combine ( CacheFolder , state . Name , state . Version , sourceFile . Trim ( '/' ) ) ;
272318 }
273319
274320 /// <summary>
@@ -277,7 +323,7 @@ private bool IsLibraryUpToDate(ILibraryInstallationState state)
277323 /// <param name="state"></param>
278324 /// <param name="cancellationToken"></param>
279325 /// <returns></returns>
280- private async Task < ILibraryOperationResult > RefreshCacheAsync ( ILibraryInstallationState state , CancellationToken cancellationToken )
326+ private async Task < ILibraryOperationResult > RefreshCacheAsync ( ILibraryInstallationState state , ILibrary library , CancellationToken cancellationToken )
281327 {
282328 if ( cancellationToken . IsCancellationRequested )
283329 {
@@ -288,8 +334,19 @@ private async Task<ILibraryOperationResult> RefreshCacheAsync(ILibraryInstallati
288334
289335 try
290336 {
337+ IEnumerable < string > filesToCache ;
338+ // expand "files" to concrete files in the library
339+ if ( state . Files == null || state . Files . Count == 0 )
340+ {
341+ filesToCache = library . Files . Keys ;
342+ }
343+ else
344+ {
345+ filesToCache = FileGlobbingUtility . ExpandFileGlobs ( state . Files , library . Files . Keys ) ;
346+ }
347+
291348 var librariesMetadata = new HashSet < CacheFileMetadata > ( ) ;
292- foreach ( string sourceFile in state . Files )
349+ foreach ( string sourceFile in filesToCache )
293350 {
294351 string cacheFile = Path . Combine ( libraryDir , sourceFile ) ;
295352 string url = GetDownloadUrl ( state , sourceFile ) ;
0 commit comments