Fix cancellation of downloads.

The Download class cancels asynchronously, which means callers must handle cancellation inside the completion event, and not after requesting cancellation.
This commit is contained in:
RoosterDragon
2016-07-30 16:27:45 +01:00
parent 77d0a8c54e
commit 3b5798b5e8
6 changed files with 61 additions and 55 deletions

View File

@@ -17,8 +17,8 @@ namespace OpenRA
{ {
public class Download public class Download
{ {
readonly object syncObject = new object();
WebClient wc; WebClient wc;
bool cancelled;
public static string FormatErrorMessage(Exception e) public static string FormatErrorMessage(Exception e)
{ {
@@ -28,6 +28,8 @@ namespace OpenRA
switch (ex.Status) switch (ex.Status)
{ {
case WebExceptionStatus.RequestCanceled:
return "Cancelled";
case WebExceptionStatus.NameResolutionFailure: case WebExceptionStatus.NameResolutionFailure:
return "DNS lookup failed"; return "DNS lookup failed";
case WebExceptionStatus.Timeout: case WebExceptionStatus.Timeout:
@@ -41,40 +43,42 @@ namespace OpenRA
} }
} }
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs, bool> onComplete) public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
{ {
wc = new WebClient(); lock (syncObject)
wc.Proxy = null; {
wc = new WebClient { Proxy = null };
wc.DownloadProgressChanged += (_, a) => onProgress(a); wc.DownloadProgressChanged += (_, a) => onProgress(a);
wc.DownloadFileCompleted += (_, a) => onComplete(a, cancelled); wc.DownloadFileCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
wc.DownloadFileAsync(new Uri(url), path);
Game.OnQuit += Cancel; }
wc.DownloadFileCompleted += (_, a) => { Game.OnQuit -= Cancel; };
wc.DownloadFileAsync(new Uri(url), path);
} }
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs, bool> onComplete) public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
{ {
wc = new WebClient(); lock (syncObject)
wc.Proxy = null; {
wc = new WebClient { Proxy = null };
wc.DownloadProgressChanged += (_, a) => onProgress(a); wc.DownloadProgressChanged += (_, a) => onProgress(a);
wc.DownloadDataCompleted += (_, a) => onComplete(a, cancelled); wc.DownloadDataCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
wc.DownloadDataAsync(new Uri(url));
Game.OnQuit += Cancel; }
wc.DownloadDataCompleted += (_, a) => { Game.OnQuit -= Cancel; };
wc.DownloadDataAsync(new Uri(url));
} }
public void Cancel() void DisposeWebClient()
{ {
Game.OnQuit -= Cancel; lock (syncObject)
wc.CancelAsync(); {
wc.Dispose(); wc.Dispose();
cancelled = true; wc = null;
}
}
public void CancelAsync()
{
lock (syncObject)
if (wc != null)
wc.CancelAsync();
} }
} }
} }

View File

@@ -128,11 +128,11 @@ namespace OpenRA
var url = Game.Settings.Game.MapRepository + "hash/" + string.Join(",", maps.Keys) + "/yaml"; var url = Game.Settings.Game.MapRepository + "hash/" + string.Join(",", maps.Keys) + "/yaml";
Action<DownloadDataCompletedEventArgs, bool> onInfoComplete = (i, cancelled) => Action<DownloadDataCompletedEventArgs> onInfoComplete = i =>
{ {
if (cancelled || i.Error != null) if (i.Error != null)
{ {
Log.Write("debug", "Remote map query failed with error: {0}", i.Error != null ? i.Error.Message : "cancelled"); Log.Write("debug", "Remote map query failed with error: {0}", Download.FormatErrorMessage(i.Error));
Log.Write("debug", "URL was: {0}", url); Log.Write("debug", "URL was: {0}", url);
foreach (var p in maps.Values) foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Unavailable, null); p.UpdateRemoteSearch(MapStatus.Unavailable, null);

View File

@@ -444,13 +444,13 @@ namespace OpenRA
} }
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i => { DownloadBytes = i.BytesReceived; DownloadPercentage = i.ProgressPercentage; }; Action<DownloadProgressChangedEventArgs> onDownloadProgress = i => { DownloadBytes = i.BytesReceived; DownloadPercentage = i.ProgressPercentage; };
Action<DownloadDataCompletedEventArgs, bool> onDownloadComplete = (i, cancelled) => Action<DownloadDataCompletedEventArgs> onDownloadComplete = i =>
{ {
download = null; download = null;
if (cancelled || i.Error != null) if (i.Error != null)
{ {
Log.Write("debug", "Remote map download failed with error: {0}", i.Error != null ? i.Error.Message : "cancelled"); Log.Write("debug", "Remote map download failed with error: {0}", Download.FormatErrorMessage(i.Error));
Log.Write("debug", "URL was: {0}", mapUrl); Log.Write("debug", "URL was: {0}", mapUrl);
innerData.Status = MapStatus.DownloadError; innerData.Status = MapStatus.DownloadError;
@@ -487,7 +487,7 @@ namespace OpenRA
if (download == null) if (download == null)
return; return;
download.Cancel(); download.CancelAsync();
download = null; download = null;
} }

View File

@@ -115,17 +115,19 @@ namespace OpenRA.Mods.Common.Widgets.Logic
retryButton.IsVisible = () => true; retryButton.IsVisible = () => true;
}); });
Action<AsyncCompletedEventArgs, bool> onDownloadComplete = (i, cancelled) => Action<AsyncCompletedEventArgs> onDownloadComplete = i =>
{ {
if (i.Error != null) if (i.Cancelled)
{ {
onError(Download.FormatErrorMessage(i.Error)); deleteTempFile();
Game.RunAfterTick(Ui.CloseWindow);
return; return;
} }
if (cancelled) if (i.Error != null)
{ {
onError("Download cancelled"); deleteTempFile();
onError(Download.FormatErrorMessage(i.Error));
return; return;
} }
@@ -185,30 +187,30 @@ namespace OpenRA.Mods.Common.Widgets.Logic
downloadHost = new Uri(url).Host; downloadHost = new Uri(url).Host;
var dl = new Download(url, file, onDownloadProgress, onDownloadComplete); var dl = new Download(url, file, onDownloadProgress, onDownloadComplete);
cancelButton.OnClick = () => { dl.Cancel(); deleteTempFile(); Ui.CloseWindow(); }; cancelButton.OnClick = dl.CancelAsync;
retryButton.OnClick = () => { dl.Cancel(); ShowDownloadDialog(); }; retryButton.OnClick = ShowDownloadDialog;
}; };
if (download.MirrorList != null) if (download.MirrorList != null)
{ {
Log.Write("install", "Fetching mirrors from " + download.MirrorList); Log.Write("install", "Fetching mirrors from " + download.MirrorList);
Action<DownloadDataCompletedEventArgs, bool> onFetchMirrorsComplete = (i, cancelled) => Action<DownloadDataCompletedEventArgs> onFetchMirrorsComplete = i =>
{ {
progressBar.Indeterminate = true; progressBar.Indeterminate = true;
if (i.Cancelled)
{
Game.RunAfterTick(Ui.CloseWindow);
return;
}
if (i.Error != null) if (i.Error != null)
{ {
onError(Download.FormatErrorMessage(i.Error)); onError(Download.FormatErrorMessage(i.Error));
return; return;
} }
if (cancelled)
{
onError("Download cancelled");
return;
}
try try
{ {
var data = Encoding.UTF8.GetString(i.Result); var data = Encoding.UTF8.GetString(i.Result);
@@ -224,8 +226,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}; };
var updateMirrors = new Download(download.MirrorList, onDownloadProgress, onFetchMirrorsComplete); var updateMirrors = new Download(download.MirrorList, onDownloadProgress, onFetchMirrorsComplete);
cancelButton.OnClick = () => { updateMirrors.Cancel(); Ui.CloseWindow(); }; cancelButton.OnClick = updateMirrors.CancelAsync;
retryButton.OnClick = () => { updateMirrors.Cancel(); ShowDownloadDialog(); }; retryButton.OnClick = ShowDownloadDialog;
} }
else else
downloadUrl(download.URL); downloadUrl(download.URL);

View File

@@ -294,8 +294,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
.JoinWith("&"); .JoinWith("&");
new Download(newsURL, cacheFile, e => { }, new Download(newsURL, cacheFile, e => { },
(e, c) => NewsDownloadComplete(e, cacheFile, currentNews, e => NewsDownloadComplete(e, cacheFile, currentNews,
() => newsButton.AttachPanel(newsPanel))); () => newsButton.AttachPanel(newsPanel)));
} }
newsButton.OnClick = () => newsButton.AttachPanel(newsPanel); newsButton.OnClick = () => newsButton.AttachPanel(newsPanel);

View File

@@ -296,11 +296,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
searchStatus = SearchStatus.Fetching; searchStatus = SearchStatus.Fetching;
Action<DownloadDataCompletedEventArgs, bool> onComplete = (i, cancelled) => Action<DownloadDataCompletedEventArgs> onComplete = i =>
{ {
currentQuery = null; currentQuery = null;
if (i.Error != null || cancelled) if (i.Error != null)
{ {
RefreshServerListInner(null); RefreshServerListInner(null);
return; return;