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:
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user