@@ -13,22 +13,29 @@ namespace ProjectCeleste.GameFiles.GameScanner.FileDownloader
1313 public class ChunkFileDownloader : IFileDownloader
1414 {
1515 internal const int MaxChunkSize = 10 * 1024 * 1024 ; //10Mb
16- private const int MaxConcurrentDownloads = 6 ;
16+ public const int MaxConcurrentDownloads = 10 ;
1717
18+ private readonly int _concurrentDownloads ;
1819 private readonly Stopwatch _downloadSpeedStopwatch ;
1920 private readonly string _downloadTempFolder ;
20-
2121 private readonly ConcurrentDictionary < long , string > _completedChunks = new ConcurrentDictionary < long , string > ( ) ;
22- private ConcurrentQueue < FileRange > _chunkDownloadQueue ;
2322
23+ private ConcurrentQueue < FileRange > _chunkDownloadQueue ;
2424 private long _downloadSizeCompleted ;
25- private int _activeDownloads = 1 ;
26- private bool _downloadFailed = false ;
2725
28- public ChunkFileDownloader ( string httpLink , string outputFileName , string tmpFolder )
26+ public ChunkFileDownloader ( string httpLink , string outputFileName , string tmpFolder , int concurrentDownload = 0 )
2927 {
3028 DownloadUrl = httpLink ;
3129 FilePath = outputFileName ;
30+
31+ if ( concurrentDownload <= 0 )
32+ concurrentDownload = 5 ;
33+
34+ if ( concurrentDownload > MaxConcurrentDownloads )
35+ concurrentDownload = MaxConcurrentDownloads ;
36+
37+ _concurrentDownloads = concurrentDownload ;
38+
3239 _downloadTempFolder = tmpFolder ;
3340 _downloadSpeedStopwatch = new Stopwatch ( ) ;
3441
@@ -37,7 +44,7 @@ public ChunkFileDownloader(string httpLink, string outputFileName, string tmpFol
3744 ServicePointManager . MaxServicePointIdleTime = 1000 ;
3845 }
3946
40- public FileDownloaderState State { get ; private set ; } = FileDownloaderState . Invalid ;
47+ public FileDownloaderState State { get ; private set ; }
4148
4249 public double DownloadProgress => DownloadSize > 0 ? ( double ) BytesDownloaded / DownloadSize * 100 : 0 ;
4350
@@ -80,31 +87,36 @@ public async Task DownloadAsync(CancellationToken ct = default)
8087 try
8188 {
8289 _downloadSpeedStopwatch . Start ( ) ;
83-
84- OnProgressChanged ( ) ;
85-
8690 DownloadSize = await GetDownloadSizeAsync ( ) ;
8791
88- var readRanges = CalculateFileChunkRanges ( ) ;
92+ ReportProgress ( null ) ; //Forced
8993
90- //Parallel download
91- using ( new Timer ( ReportProgress , null , 100 , 100 ) )
94+ using ( var reportProgressTimer = new Timer ( ReportProgress , null , 500 , 500 ) )
9295 {
93- await OrchestrateDownloadWorkersAsync ( readRanges , ct ) ;
96+ await StartDownloadAsync ( ct ) ;
9497 _downloadSpeedStopwatch . Stop ( ) ;
98+ reportProgressTimer . Change ( Timeout . Infinite , Timeout . Infinite ) ;
99+ }
95100
96- if ( BytesDownloaded != DownloadSize )
97- throw new Exception (
98- $ "Download was completed ({ BytesDownloaded } bytes), but did not receive expected size of { DownloadSize } bytes") ;
101+ ReportProgress ( null ) ; //Forced
99102
100- State = FileDownloaderState . Finalize ;
101- ReportProgress ( null ) ; //Forced
103+ if ( _chunkDownloadQueue . Count > 0 )
104+ throw new Exception (
105+ $ "Download was incomplete ({ _chunkDownloadQueue . Count } missing chunks)") ;
102106
103- WriteChunksToFile ( _completedChunks ) ;
107+ if ( BytesDownloaded != DownloadSize )
108+ throw new Exception (
109+ $ "Download was incomplete ({ BytesDownloaded } /{ DownloadSize } bytes)") ;
104110
105- State = FileDownloaderState . Complete ;
106- ReportProgress ( null ) ; //Forced
107- }
111+ State = FileDownloaderState . Finalize ;
112+
113+ ReportProgress ( null ) ; //Forced
114+
115+ ct . ThrowIfCancellationRequested ( ) ;
116+
117+ WriteChunksToFile ( _completedChunks ) ;
118+
119+ State = FileDownloaderState . Complete ;
108120 }
109121 catch ( Exception e )
110122 {
@@ -115,46 +127,61 @@ public async Task DownloadAsync(CancellationToken ct = default)
115127 }
116128 finally
117129 {
118- OnProgressChanged ( ) ;
130+ ReportProgress ( null ) ; //Forced
119131 }
120132 }
121133
122- private async Task OrchestrateDownloadWorkersAsync ( IEnumerable < FileRange > chunks , CancellationToken ct )
134+ private async Task StartDownloadAsync ( CancellationToken ct = default )
123135 {
124- var downloaderWorkers = new ConcurrentQueue < Task > ( ) ;
125- _chunkDownloadQueue = new ConcurrentQueue < FileRange > ( chunks ) ;
136+ var readRanges = CalculateFileChunkRanges ( ) ;
137+ var fileRanges = readRanges as FileRange [ ] ?? readRanges . ToArray ( ) ;
138+ _chunkDownloadQueue = new ConcurrentQueue < FileRange > ( fileRanges ) ;
126139
127- while ( _activeDownloads < MaxConcurrentDownloads && _chunkDownloadQueue . Count > 0 && ! _downloadFailed )
128- {
129- _activeDownloads ++ ;
140+ var tasks = Enumerable . Range ( 1 , Math . Min ( _concurrentDownloads , _chunkDownloadQueue . Count ) ) . Select (
141+ async workerIndex =>
142+ {
143+ if ( workerIndex > 1 )
144+ await Task . Delay ( 1000 * ( workerIndex - 1 ) , ct ) ;
130145
131- downloaderWorkers . Enqueue ( Task . Run ( DequeueAndDownloadChunksAsync , ct ) ) ;
132- await Task . Delay ( 1000 , ct ) ;
133- }
146+ await DequeueAndDownloadChunksAsync ( ct ) ;
147+ } ) ;
134148
135- await Task . WhenAll ( downloaderWorkers ) ;
149+ await Task . WhenAll ( tasks ) ; //Start parallel download
150+
151+ if ( _completedChunks . Count > 0 && _chunkDownloadQueue . Count > 0 )
152+ {
153+ //Try to get missing chunk if any
154+ await DequeueAndDownloadChunksAsync ( ct ) ;
155+ }
136156 }
137157
138- private async Task DequeueAndDownloadChunksAsync ( )
158+ private async Task DequeueAndDownloadChunksAsync ( CancellationToken ct = default )
139159 {
140- var workerFailedDownloading = false ;
141- var taskId = _activeDownloads ;
142-
143- while ( _chunkDownloadQueue . TryDequeue ( out var fileChunk ) && ! workerFailedDownloading )
160+ while ( _chunkDownloadQueue . TryDequeue ( out var fileChunk ) )
144161 {
162+ var workerFailedDownloadingOnce = false ;
163+
164+ retry :
145165 var chunkDownload = new ChunkDownload ( DownloadUrl , fileChunk , _downloadTempFolder ) ;
146- var downloadSuccesfullyCompleted = await chunkDownload . TryDownloadAsync ( IncrementTotalDownloadProgress ) ;
166+ var downloadSuccessfullyCompleted =
167+ await chunkDownload . TryDownloadAsync ( IncrementTotalDownloadProgress , ct ) ;
147168
148- if ( ! downloadSuccesfullyCompleted )
149- {
150- _chunkDownloadQueue . Enqueue ( fileChunk ) ;
151- workerFailedDownloading = true ;
152- _downloadFailed = true ;
153- }
154- else
169+ if ( ! downloadSuccessfullyCompleted )
155170 {
156- _completedChunks . TryAdd ( fileChunk . Start , chunkDownload . DownloadTmpFileName ) ;
171+ if ( workerFailedDownloadingOnce )
172+ {
173+ _chunkDownloadQueue . Enqueue ( fileChunk ) ;
174+ break ;
175+ }
176+
177+ workerFailedDownloadingOnce = true ;
178+ await Task . Delay ( 1000 , ct ) ;
179+ goto retry ;
157180 }
181+
182+ _completedChunks . TryAdd ( fileChunk . Start , chunkDownload . DownloadTmpFileName ) ;
183+
184+ await Task . Delay ( 1000 , ct ) ;
158185 }
159186 }
160187
@@ -176,23 +203,28 @@ private async Task<long> GetDownloadSizeAsync()
176203 var sizeDownloadRequest = WebRequest . Create ( DownloadUrl ) ;
177204 sizeDownloadRequest . Method = "HEAD" ;
178205
179- using ( var response = await sizeDownloadRequest . GetResponseAsync ( ) )
180- {
181- return long . Parse ( response . Headers . Get ( "Content-Length" ) ) ;
182- }
206+ using var response = await sizeDownloadRequest . GetResponseAsync ( ) ;
207+ return long . Parse ( response . Headers . Get ( "Content-Length" ) ) ;
183208 }
184209
185210 private void WriteChunksToFile ( IDictionary < long , string > fileChunks )
186211 {
187- using ( var targetFile = new BufferedStream ( new FileStream ( FilePath , FileMode . Create , FileAccess . Write ) ) )
212+ using var targetFile = new BufferedStream ( new FileStream ( FilePath , FileMode . Create , FileAccess . Write ) ,
213+ ChunkDownload . ChunkBufferSize ) ;
214+ foreach ( var tempFile in fileChunks . ToArray ( ) . OrderBy ( b => b . Key ) )
188215 {
189- foreach ( var tempFile in fileChunks . ToArray ( ) . OrderBy ( b => b . Key ) )
190- {
191- using ( var sourceChunks = new BufferedStream ( File . OpenRead ( tempFile . Value ) ) )
192- sourceChunks . CopyTo ( targetFile ) ;
216+ using ( var sourceChunks =
217+ new BufferedStream ( File . OpenRead ( tempFile . Value ) , ChunkDownload . ChunkBufferSize ) )
218+ sourceChunks . CopyTo ( targetFile ) ;
193219
220+ try
221+ {
194222 File . Delete ( tempFile . Value ) ;
195223 }
224+ catch
225+ {
226+ //
227+ }
196228 }
197229 }
198230
@@ -203,14 +235,9 @@ private void IncrementTotalDownloadProgress(int bytesDownloaded)
203235
204236 public event EventHandler ProgressChanged ;
205237
206- protected virtual void OnProgressChanged ( )
207- {
208- ProgressChanged ? . Invoke ( this , null ) ;
209- }
210-
211238 private void ReportProgress ( object state )
212239 {
213- OnProgressChanged ( ) ;
240+ ProgressChanged ? . Invoke ( this , null ) ;
214241 }
215242 }
216243}
0 commit comments