55using System . IO . Compression ;
66using System . Reflection ;
77using System . Text ;
8+ using System . Threading ;
89using System . Threading . Tasks ;
910using Windows . Storage ;
1011using Amethyst . Plugins . Contract ;
@@ -27,6 +28,24 @@ internal class SetupData : ICoreSetupData
2728}
2829
2930internal class RuntimeInstaller : IDependencyInstaller
31+ {
32+ public IDependencyInstaller . ILocalizationHost Host { get ; set ; }
33+
34+ public List < IDependency > ListDependencies ( )
35+ {
36+ return new List < IDependency >
37+ {
38+ new KinectRuntime
39+ {
40+ Host = Host ,
41+ Name = Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Dependencies/Runtime/Name" ) ??
42+ "Kinect for Xbox One Runtime"
43+ }
44+ } ;
45+ }
46+ }
47+
48+ internal class KinectRuntime : IDependency
3049{
3150 private const string WixDownloadUrl =
3251 "https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip" ;
@@ -36,10 +55,10 @@ internal class RuntimeInstaller : IDependencyInstaller
3655
3756 private string TemporaryFolderName { get ; } = Guid . NewGuid ( ) . ToString ( ) . ToUpper ( ) ;
3857
39- public Task < bool > InstallTools ( IProgress < InstallationProgress > progress )
40- {
41- return Task . FromResult ( false ) ; // Not supported (yet?)
42- }
58+ public IDependencyInstaller . ILocalizationHost Host { get ; set ; }
59+
60+ public string Name { get ; set ; }
61+ public bool IsMandatory => true ;
4362
4463 public bool IsInstalled
4564 {
@@ -76,18 +95,17 @@ public string InstallerEula
7695 }
7796 }
7897
79- public bool ProvidesTools => false ;
80- public bool ToolsInstalled => false ;
81- public IDependencyInstaller . ILocalizationHost Host { get ; set ; }
82-
83- public async Task < bool > Install ( IProgress < InstallationProgress > progress )
98+ public async Task < bool > Install ( IProgress < InstallationProgress > progress , CancellationToken cancellationToken )
8499 {
100+ // Amethyst will handle this exception for us anyway
101+ cancellationToken . ThrowIfCancellationRequested ( ) ;
102+
85103 return
86104 // Download and unpack WiX
87- await SetupWix ( "WiXToolset" , progress ) &&
105+ await SetupWix ( "WiXToolset" , progress , cancellationToken ) &&
88106
89107 // Download, unpack, and install the runtime
90- await SetupRuntime ( "WiXToolset" , progress ) ;
108+ await SetupRuntime ( "WiXToolset" , progress , cancellationToken ) ;
91109 }
92110
93111 private async Task < StorageFolder > GetTempDirectory ( )
@@ -96,8 +114,12 @@ private async Task<StorageFolder> GetTempDirectory()
96114 TemporaryFolderName , CreationCollisionOption . OpenIfExists ) ;
97115 }
98116
99- private async Task < bool > SetupWix ( string outputFolder , IProgress < InstallationProgress > progress )
117+ private async Task < bool > SetupWix ( string outputFolder ,
118+ IProgress < InstallationProgress > progress , CancellationToken cancellationToken )
100119 {
120+ // Amethyst will handle this exception for us anyway
121+ cancellationToken . ThrowIfCancellationRequested ( ) ;
122+
101123 try
102124 {
103125 using var client = new RestClient ( ) ;
@@ -118,15 +140,17 @@ private async Task<bool> SetupWix(string outputFolder, IProgress<InstallationPro
118140
119141 // Create an output stream and push all the available data to it
120142 await using var fsInstallerFile = await installerFile . OpenStreamForWriteAsync ( ) ;
121- await stream . CopyToWithProgressAsync ( fsInstallerFile , innerProgress =>
122- {
123- progress . Report ( new InstallationProgress
143+ await stream . CopyToWithProgressAsync ( fsInstallerFile , cancellationToken ,
144+ innerProgress =>
124145 {
125- IsIndeterminate = false , OverallProgress = innerProgress / 34670186.0 ,
126- StageTitle = Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Downloading/WiX" ) ??
127- "Downloading WiX Toolset"
128- } ) ;
129- } ) ; // The runtime will do the rest for us
146+ progress . Report ( new InstallationProgress
147+ {
148+ IsIndeterminate = false ,
149+ OverallProgress = innerProgress / 34670186.0 ,
150+ StageTitle = Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Downloading/WiX" ) ??
151+ "Downloading WiX Toolset"
152+ } ) ;
153+ } ) ; // The runtime will do the rest for us
130154
131155 // Close the file to unlock it
132156 fsInstallerFile . Close ( ) ;
@@ -174,8 +198,12 @@ await stream.CopyToWithProgressAsync(fsInstallerFile, innerProgress =>
174198 return false ;
175199 }
176200
177- private async Task < bool > SetupRuntime ( string wixFolder , IProgress < InstallationProgress > progress )
201+ private async Task < bool > SetupRuntime ( string wixFolder ,
202+ IProgress < InstallationProgress > progress , CancellationToken cancellationToken )
178203 {
204+ // Amethyst will handle this exception for us anyway
205+ cancellationToken . ThrowIfCancellationRequested ( ) ;
206+
179207 try
180208 {
181209 using var client = new RestClient ( ) ;
@@ -196,28 +224,31 @@ private async Task<bool> SetupRuntime(string wixFolder, IProgress<InstallationPr
196224
197225 // Create an output stream and push all the available data to it
198226 await using var fsInstallerFile = await installerFile . OpenStreamForWriteAsync ( ) ;
199- await stream . CopyToWithProgressAsync ( fsInstallerFile , innerProgress =>
200- {
201- progress . Report ( new InstallationProgress
227+ await stream . CopyToWithProgressAsync ( fsInstallerFile , cancellationToken ,
228+ innerProgress =>
202229 {
203- IsIndeterminate = false ,
204- OverallProgress = innerProgress / 93314296.0 ,
205- StageTitle = Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Downloading/Runtime" ) ??
206- "Downloading Kinect for Xbox One Runtime..."
207- } ) ;
208- } ) ; // The runtime will do the rest for us
230+ progress . Report ( new InstallationProgress
231+ {
232+ IsIndeterminate = false ,
233+ OverallProgress = innerProgress / 93314296.0 ,
234+ StageTitle = Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Downloading/Runtime" ) ??
235+ "Downloading Kinect for Xbox One Runtime..."
236+ } ) ;
237+ } ) ; // The runtime will do the rest for us
209238
210239 // Close the file to unlock it
211240 fsInstallerFile . Close ( ) ;
212241
213242 return
214243 // Extract all runtime files for the installation
215244 await ExtractFiles ( Path . GetFullPath ( Path . Combine ( ( await GetTempDirectory ( ) ) . Path , wixFolder ) ) ,
216- installerFile . Path , Path . Join ( ( await GetTempDirectory ( ) ) . Path , "KinectRuntime" ) , progress ) &&
245+ installerFile . Path , Path . Join ( ( await GetTempDirectory ( ) ) . Path , "KinectRuntime" ) ,
246+ progress , cancellationToken ) &&
217247
218248 // Install the files using msi installers
219249 InstallFiles ( Directory . GetFiles ( Path . Join ( ( await GetTempDirectory ( ) ) . Path ,
220- "KinectRuntime" , "AttachedContainer" ) , "*.msi" ) , progress ) ;
250+ "KinectRuntime" , "AttachedContainer" ) , "*.msi" ) ,
251+ progress , cancellationToken ) ;
221252 }
222253 catch ( Exception e )
223254 {
@@ -233,8 +264,11 @@ await ExtractFiles(Path.GetFullPath(Path.Combine((await GetTempDirectory()).Path
233264 }
234265
235266 private async Task < bool > ExtractFiles ( string wixPath , string sourceFile , string outputFolder ,
236- IProgress < InstallationProgress > progress )
267+ IProgress < InstallationProgress > progress , CancellationToken cancellationToken )
237268 {
269+ // Amethyst will handle this exception for us anyway
270+ cancellationToken . ThrowIfCancellationRequested ( ) ;
271+
238272 try
239273 {
240274 progress . Report ( new InstallationProgress
@@ -293,7 +327,8 @@ private async Task<bool> ExtractFiles(string wixPath, string sourceFile, string
293327 // WTF
294328 progress . Report ( new InstallationProgress
295329 {
296- IsIndeterminate = true , StageTitle =
330+ IsIndeterminate = true ,
331+ StageTitle =
297332 Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Dark/Error/Timeout" ) ??
298333 "Failed to execute dark.exe in the allocated time!"
299334 } ) ;
@@ -306,7 +341,8 @@ private async Task<bool> ExtractFiles(string wixPath, string sourceFile, string
306341 // Assume WiX failed
307342 progress . Report ( new InstallationProgress
308343 {
309- IsIndeterminate = true , StageTitle =
344+ IsIndeterminate = true ,
345+ StageTitle =
310346 ( Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Dark/Error/Result" ) ??
311347 "Dark.exe exited with error code: {0}" ) . Replace ( "{0}" , proc . ExitCode . ToString ( ) )
312348 } ) ;
@@ -318,7 +354,8 @@ private async Task<bool> ExtractFiles(string wixPath, string sourceFile, string
318354 {
319355 progress . Report ( new InstallationProgress
320356 {
321- IsIndeterminate = true , StageTitle =
357+ IsIndeterminate = true ,
358+ StageTitle =
322359 ( Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Exceptions/Other" ) ??
323360 "Exception: {0}" ) . Replace ( "{0}" , e . Message )
324361 } ) ;
@@ -329,16 +366,21 @@ private async Task<bool> ExtractFiles(string wixPath, string sourceFile, string
329366 return true ;
330367 }
331368
332- private bool InstallFiles ( IEnumerable < string > files , IProgress < InstallationProgress > progress )
369+ private bool InstallFiles ( IEnumerable < string > files ,
370+ IProgress < InstallationProgress > progress , CancellationToken cancellationToken )
333371 {
372+ // Amethyst will handle this exception for us anyway
373+ cancellationToken . ThrowIfCancellationRequested ( ) ;
374+
334375 // Execute each install
335376 foreach ( var installFile in files )
336377 try
337378 {
338379 // msi /qn /norestart
339380 progress . Report ( new InstallationProgress
340381 {
341- IsIndeterminate = true , StageTitle =
382+ IsIndeterminate = true ,
383+ StageTitle =
342384 ( Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Installing" ) ??
343385 "Installing {0}..." ) . Replace ( "{0}" , Path . GetFileName ( installFile ) )
344386 } ) ;
@@ -362,7 +404,8 @@ private bool InstallFiles(IEnumerable<string> files, IProgress<InstallationProgr
362404 {
363405 progress . Report ( new InstallationProgress
364406 {
365- IsIndeterminate = true , StageTitle =
407+ IsIndeterminate = true ,
408+ StageTitle =
366409 ( Host ? . RequestLocalizedString ( "/Plugins/KinectOne/Stages/Exceptions/Other" ) ??
367410 "Exception: {0}" ) . Replace ( "{0}" , e . Message )
368411 } ) ;
@@ -392,7 +435,8 @@ public static Task<Stream> ExecuteDownloadStreamAsync(this RestClient client, st
392435public static class StreamExtensions
393436{
394437 public static async Task CopyToWithProgressAsync ( this Stream source ,
395- Stream destination , Action < long > progress = null , int bufferSize = 10240 )
438+ Stream destination , CancellationToken cancellationToken ,
439+ Action < long > progress = null , int bufferSize = 10240 )
396440 {
397441 var buffer = new byte [ bufferSize ] ;
398442 var total = 0L ;
@@ -404,13 +448,13 @@ public static async Task CopyToWithProgressAsync(this Stream source,
404448 while ( amtRead < bufferSize )
405449 {
406450 var numBytes = await source . ReadAsync (
407- buffer , amtRead , bufferSize - amtRead ) ;
451+ buffer , amtRead , bufferSize - amtRead , cancellationToken ) ;
408452 if ( numBytes == 0 ) break ;
409453 amtRead += numBytes ;
410454 }
411455
412456 total += amtRead ;
413- await destination . WriteAsync ( buffer , 0 , amtRead ) ;
457+ await destination . WriteAsync ( buffer , 0 , amtRead , cancellationToken ) ;
414458 progress ? . Invoke ( total ) ;
415459 } while ( amtRead == bufferSize ) ;
416460 }
0 commit comments