/*
* Copyright (c) 2006-2014, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using OpenMetaverse.Http;
namespace OpenMetaverse
{
///
/// Represends individual HTTP Download request
///
public class DownloadRequest
{
/// URI of the item to fetch
public Uri Address;
/// Timout specified in milliseconds
public int MillisecondsTimeout;
/// Download progress callback
public CapsBase.DownloadProgressEventHandler DownloadProgressCallback;
/// Download completed callback
public CapsBase.RequestCompletedEventHandler CompletedCallback;
/// Accept the following content type
public string ContentType;
/// How many times will this request be retried
public int Retries = 5;
/// Current fetch attempt
public int Attempt = 0;
/// Default constructor
public DownloadRequest()
{
}
/// Constructor
public DownloadRequest(Uri address, int millisecondsTimeout,
string contentType,
CapsBase.DownloadProgressEventHandler downloadProgressCallback,
CapsBase.RequestCompletedEventHandler completedCallback)
{
this.Address = address;
this.MillisecondsTimeout = millisecondsTimeout;
this.DownloadProgressCallback = downloadProgressCallback;
this.CompletedCallback = completedCallback;
this.ContentType = contentType;
}
}
internal class ActiveDownload
{
public List ProgresHadlers = new List();
public List CompletedHandlers = new List();
public HttpWebRequest Request;
}
///
/// Manages async HTTP downloads with a limit on maximum
/// concurrent downloads
///
public class DownloadManager
{
Queue queue = new Queue();
Dictionary activeDownloads = new Dictionary();
X509Certificate2 m_ClientCert;
/// Maximum number of parallel downloads from a single endpoint
public int ParallelDownloads { get; set; }
/// Client certificate
public X509Certificate2 ClientCert
{
get { return m_ClientCert; }
set { m_ClientCert = value; }
}
/// Default constructor
public DownloadManager()
{
ParallelDownloads = 8;
}
/// Cleanup method
public virtual void Dispose()
{
lock (activeDownloads)
{
foreach (ActiveDownload download in activeDownloads.Values)
{
try
{
if (download.Request != null)
{
download.Request.Abort();
}
}
catch { }
}
activeDownloads.Clear();
}
}
/// Setup http download request
protected virtual HttpWebRequest SetupRequest(Uri address, string acceptHeader)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(address);
request.Method = "GET";
if (!string.IsNullOrEmpty(acceptHeader))
request.Accept = acceptHeader;
// Add the client certificate to the request if one was given
if (m_ClientCert != null)
request.ClientCertificates.Add(m_ClientCert);
// Leave idle connections to this endpoint open for up to 60 seconds
request.ServicePoint.MaxIdleTime = 0;
// Disable stupid Expect-100: Continue header
request.ServicePoint.Expect100Continue = false;
// Crank up the max number of connections per endpoint
if (request.ServicePoint.ConnectionLimit < Settings.MAX_HTTP_CONNECTIONS)
{
Logger.Log(string.Format("In DownloadManager.SetupRequest() setting conn limit for {0}:{1} to {2}", address.Host, address.Port, Settings.MAX_HTTP_CONNECTIONS), Helpers.LogLevel.Debug);
request.ServicePoint.ConnectionLimit = Settings.MAX_HTTP_CONNECTIONS;
}
return request;
}
/// Check the queue for pending work
private void EnqueuePending()
{
lock (queue)
{
if (queue.Count > 0)
{
int nr = 0;
lock (activeDownloads)
{
nr = activeDownloads.Count;
}
// Logger.DebugLog(nr.ToString() + " active downloads. Queued textures: " + queue.Count.ToString());
for (int i = nr; i < ParallelDownloads && queue.Count > 0; i++)
{
DownloadRequest item = queue.Dequeue();
lock (activeDownloads)
{
string addr = item.Address.ToString();
if (activeDownloads.ContainsKey(addr))
{
activeDownloads[addr].CompletedHandlers.Add(item.CompletedCallback);
if (item.DownloadProgressCallback != null)
{
activeDownloads[addr].ProgresHadlers.Add(item.DownloadProgressCallback);
}
}
else
{
ActiveDownload activeDownload = new ActiveDownload();
activeDownload.CompletedHandlers.Add(item.CompletedCallback);
if (item.DownloadProgressCallback != null)
{
activeDownload.ProgresHadlers.Add(item.DownloadProgressCallback);
}
Logger.DebugLog("Requesting " + item.Address.ToString());
activeDownload.Request = SetupRequest(item.Address, item.ContentType);
CapsBase.DownloadDataAsync(
activeDownload.Request,
item.MillisecondsTimeout,
(HttpWebRequest request, HttpWebResponse response, int bytesReceived, int totalBytesToReceive) =>
{
foreach (CapsBase.DownloadProgressEventHandler handler in activeDownload.ProgresHadlers)
{
handler(request, response, bytesReceived, totalBytesToReceive);
}
},
(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error) =>
{
lock (activeDownloads) activeDownloads.Remove(addr);
if (error == null || item.Attempt >= item.Retries || (error != null && error.Message.Contains("404")))
{
foreach (CapsBase.RequestCompletedEventHandler handler in activeDownload.CompletedHandlers)
{
handler(request, response, responseData, error);
}
}
else
{
item.Attempt++;
Logger.Log(string.Format("Texture {0} HTTP download failed, trying again retry {1}/{2}",
item.Address, item.Attempt, item.Retries), Helpers.LogLevel.Warning);
lock (queue) queue.Enqueue(item);
}
EnqueuePending();
}
);
activeDownloads[addr] = activeDownload;
}
}
}
}
}
}
/// Enqueue a new HTTP download
public void QueueDownload(DownloadRequest req)
{
lock (activeDownloads)
{
string addr = req.Address.ToString();
if (activeDownloads.ContainsKey(addr))
{
activeDownloads[addr].CompletedHandlers.Add(req.CompletedCallback);
if (req.DownloadProgressCallback != null)
{
activeDownloads[addr].ProgresHadlers.Add(req.DownloadProgressCallback);
}
return;
}
}
lock (queue)
{
queue.Enqueue(req);
}
EnqueuePending();
}
}
}