diff --git a/Mono.Addins.Setup/Mono.Addins.Setup.csproj b/Mono.Addins.Setup/Mono.Addins.Setup.csproj
index 26c276d2..ac0e039c 100644
--- a/Mono.Addins.Setup/Mono.Addins.Setup.csproj
+++ b/Mono.Addins.Setup/Mono.Addins.Setup.csproj
@@ -46,6 +46,7 @@
+
{91DD5A2D-9FE3-4C3C-9253-876141874DAD}
@@ -89,6 +90,8 @@
+
+
diff --git a/Mono.Addins.Setup/Mono.Addins.Setup/AddinStore.cs b/Mono.Addins.Setup/Mono.Addins.Setup/AddinStore.cs
index eef58f29..cc8a1dae 100644
--- a/Mono.Addins.Setup/Mono.Addins.Setup/AddinStore.cs
+++ b/Mono.Addins.Setup/Mono.Addins.Setup/AddinStore.cs
@@ -46,6 +46,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
namespace Mono.Addins.Setup
{
@@ -641,28 +642,30 @@ internal string DownloadFile (IProgressMonitor monitor, string url)
try {
monitor.BeginTask ("Requesting " + url, 2);
- var resp = WebRequestHelper.GetResponse (
- () => (HttpWebRequest)WebRequest.Create (url),
- r => r.Headers ["Pragma"] = "no-cache"
- );
- monitor.Step (1);
- monitor.BeginTask ("Downloading " + url, (int) resp.ContentLength);
+ var task = DownloadFileRequest.DownloadFile (url, noCache: true);
+ if (!WaitForTask (task, monitor))
+ throw new InstallException ("Installation cancelled.");
- file = Path.GetTempFileName ();
- fs = new FileStream (file, FileMode.Create, FileAccess.Write);
- s = resp.GetResponseStream ();
- byte[] buffer = new byte [4096];
-
- int n;
- while ((n = s.Read (buffer, 0, buffer.Length)) != 0) {
- monitor.Step (n);
- fs.Write (buffer, 0, n);
- if (monitor.IsCancelRequested)
- throw new InstallException ("Installation cancelled.");
+ using (var request = task.Result) {
+ monitor.Step (1);
+ monitor.BeginTask ("Downloading " + url, (int) request.ContentLength);
+
+ file = Path.GetTempFileName ();
+ fs = new FileStream (file, FileMode.Create, FileAccess.Write);
+ s = request.Stream;
+ byte[] buffer = new byte [4096];
+
+ int n;
+ while ((n = s.Read (buffer, 0, buffer.Length)) != 0) {
+ monitor.Step (n);
+ fs.Write (buffer, 0, n);
+ if (monitor.IsCancelRequested)
+ throw new InstallException ("Installation cancelled.");
+ }
+ fs.Close ();
+ s.Close ();
+ return file;
}
- fs.Close ();
- s.Close ();
- return file;
} catch {
if (fs != null)
fs.Close ();
@@ -673,10 +676,22 @@ internal string DownloadFile (IProgressMonitor monitor, string url)
throw;
} finally {
monitor.EndTask ();
- monitor.EndTask ();
}
}
-
+
+ static bool WaitForTask (Task task, IProgressMonitor monitor)
+ {
+ bool result = SpinWait.SpinUntil (() => {
+ return monitor.IsCancelRequested || task.IsCompleted || task.IsFaulted;
+ }, 100000); // Use same default timeout as HttpClient.
+
+ if (monitor.IsCancelRequested)
+ return false;
+ if (!result)
+ throw new InstallException ("Timed out.");
+ return result;
+ }
+
internal bool HasWriteAccess (string file)
{
FileInfo f = new FileInfo (file);
diff --git a/Mono.Addins.Setup/Mono.Addins.Setup/DownloadFileRequest.cs b/Mono.Addins.Setup/Mono.Addins.Setup/DownloadFileRequest.cs
new file mode 100644
index 00000000..565c0635
--- /dev/null
+++ b/Mono.Addins.Setup/Mono.Addins.Setup/DownloadFileRequest.cs
@@ -0,0 +1,129 @@
+//
+// WebRequestWrapper.cs
+//
+// Author:
+// Matt Ward
+//
+// Copyright (c) 2018 Microsoft
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace Mono.Addins.Setup
+{
+ abstract class DownloadFileRequest : IDisposable
+ {
+ public abstract void Dispose ();
+ public abstract int ContentLength { get; }
+ public abstract Stream Stream { get; }
+
+ public static Task DownloadFile (string url, bool noCache)
+ {
+ if (HttpClientProvider.HasCustomCreation || !WebRequestHelper.HasCustomRequestHandler)
+ return HttpClientDownloadFileRequest.Create (url, noCache);
+
+ return WebRequestDownloadFileRequest.Create (url, noCache);
+ }
+ }
+
+ class HttpClientDownloadFileRequest : DownloadFileRequest
+ {
+ HttpClient client;
+ HttpResponseMessage response;
+ Stream stream;
+
+ public static Task Create (string url, bool noCache)
+ {
+ // Use Task.Run to avoid hanging the UI thread when waiting for the GetAsync method to return
+ // with the response for an .mpack file download.
+ return Task.Run (async () => {
+ var client = HttpClientProvider.CreateHttpClient (url);
+ if (noCache)
+ client.DefaultRequestHeaders.Add ("Pragma", "no-cache");
+
+ var response = await client.GetAsync (url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait (false);
+ var stream = await response.Content.ReadAsStreamAsync ().ConfigureAwait (false);
+
+ return new HttpClientDownloadFileRequest {
+ client = client,
+ response = response,
+ stream = stream
+ };
+ });
+ }
+
+ public override int ContentLength {
+ get { return (int)response.Content.Headers.ContentLength; }
+ }
+
+ public override Stream Stream {
+ get { return stream; }
+ }
+
+ public override void Dispose ()
+ {
+ stream?.Dispose ();
+ response?.Dispose ();
+ client.Dispose ();
+ }
+ }
+
+ class WebRequestDownloadFileRequest : DownloadFileRequest
+ {
+ WebResponse response;
+ Stream stream;
+
+ public static Task Create (string url, bool noCache)
+ {
+ var response = WebRequestHelper.GetResponse (
+ () => (HttpWebRequest)WebRequest.Create (url),
+ r => {
+ if (noCache)
+ r.Headers ["Pragma"] = "no-cache";
+ }
+ );
+
+ var request = new WebRequestDownloadFileRequest {
+ response = response,
+ stream = response.GetResponseStream ()
+ };
+
+ return Task.FromResult (request);
+ }
+
+ public override int ContentLength {
+ get { return (int)response.ContentLength; }
+ }
+
+ public override Stream Stream {
+ get { return stream; }
+ }
+
+ public override void Dispose ()
+ {
+ stream?.Dispose ();
+ response.Dispose ();
+ }
+ }
+}
diff --git a/Mono.Addins.Setup/Mono.Addins.Setup/HttpClientProvider.cs b/Mono.Addins.Setup/Mono.Addins.Setup/HttpClientProvider.cs
new file mode 100644
index 00000000..387cb59c
--- /dev/null
+++ b/Mono.Addins.Setup/Mono.Addins.Setup/HttpClientProvider.cs
@@ -0,0 +1,65 @@
+//
+// HttpClientProvider.cs
+//
+// Author:
+// Matt Ward
+//
+// Copyright (c) 2018 Microsoft
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Net;
+using System.Net.Http;
+
+namespace Mono.Addins.Setup
+{
+ ///
+ /// Creates a HttpClient with support for authenticated proxies.
+ ///
+ public static class HttpClientProvider
+ {
+ static Func httpClientFactory;
+
+ public static void SetHttpClientFactory (Func factory)
+ {
+ httpClientFactory = factory;
+ }
+
+ static internal bool HasCustomCreation {
+ get { return httpClientFactory != null; }
+ }
+
+ ///
+ /// Creates a new HttpClient.
+ ///
+ /// The HttpClient.
+ /// The request url.
+ public static HttpClient CreateHttpClient (string uri)
+ {
+ if (httpClientFactory != null)
+ return httpClientFactory.Invoke (uri);
+
+ var handler = new HttpClientHandler {
+ AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
+ };
+ return new HttpClient (handler);
+ }
+ }
+}
diff --git a/Mono.Addins.Setup/Mono.Addins.Setup/Repository.cs b/Mono.Addins.Setup/Mono.Addins.Setup/Repository.cs
index 86abd8f8..5c28af8e 100644
--- a/Mono.Addins.Setup/Mono.Addins.Setup/Repository.cs
+++ b/Mono.Addins.Setup/Mono.Addins.Setup/Repository.cs
@@ -128,21 +128,27 @@ public IAsyncResult BeginDownloadSupportFile (string name, AsyncCallback cb, obj
}
res.FilePath = cachedFile;
- WebRequestHelper.GetResponseAsync (() => (HttpWebRequest)WebRequest.Create (u)).ContinueWith (t => {
+ var request = DownloadFileRequest.DownloadFile (u.ToString (), false).ContinueWith (t => {
try {
- var resp = t.Result;
- string dir = Path.GetDirectoryName (res.FilePath);
- lock (this) {
- if (!Directory.Exists (dir))
- Directory.CreateDirectory (dir);
- }
- byte[] buffer = new byte [8092];
- using (var s = resp.GetResponseStream ()) {
- using (var f = File.OpenWrite (res.FilePath)) {
- int nr = 0;
- while ((nr = s.Read (buffer, 0, buffer.Length)) > 0)
- f.Write (buffer, 0, nr);
+ using (var resp = t.Result) {
+ string dir = Path.GetDirectoryName (res.FilePath);
+ lock (this) {
+ if (!Directory.Exists (dir))
+ Directory.CreateDirectory (dir);
+ }
+ if (File.Exists (res.FilePath)) {
+ res.SetDone ();
+ return;
+ }
+ byte [] buffer = new byte [8092];
+ using (var s = resp.Stream) {
+ using (var f = File.OpenWrite (res.FilePath)) {
+ int nr = 0;
+ while ((nr = s.Read (buffer, 0, buffer.Length)) > 0)
+ f.Write (buffer, 0, nr);
+ }
}
+ res.SetDone ();
}
} catch (Exception ex) {
res.Error = ex;
diff --git a/Mono.Addins.Setup/Mono.Addins.Setup/WebRequestHelper.cs b/Mono.Addins.Setup/Mono.Addins.Setup/WebRequestHelper.cs
index 5685b5ac..f962c44e 100644
--- a/Mono.Addins.Setup/Mono.Addins.Setup/WebRequestHelper.cs
+++ b/Mono.Addins.Setup/Mono.Addins.Setup/WebRequestHelper.cs
@@ -46,6 +46,10 @@ public static void SetRequestHandler (Func, Action
/// Gets the web response, using the request handler to handle proxy authentication
/// if necessary.