Represents an API asset.
Inheritance: IApiObject
 /// <summary>
 ///     Initializes a new instance of the <see cref="DownloadedAsset" /> class.
 /// </summary>
 /// <param name="fileName">Name of the file.</param>
 /// <param name="apiAsset"></param>
 /// <param name="info"></param>
 /// <param name="stream">The stream.</param>
 /// <exception cref="ArgumentNullException">fileName or stream is null.</exception>
 public DownloadedAsset(string fileName, ApiAsset apiAsset, DownloadInfo info, Stream stream)
 {
     if (fileName == null) throw new ArgumentNullException(nameof(fileName));
     if (apiAsset == null) throw new ArgumentNullException(nameof(apiAsset));
     if (stream == null) throw new ArgumentNullException(nameof(stream));
     FileName = fileName;
     ApiAsset = apiAsset;
     Info = info;
     Stream = stream;
 }
        /// <summary>
        ///     Downloads the asset.
        /// </summary>
        /// <param name="asset">The asset.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="Exception">the specified asset id is invalid</exception>
        public async Task<IDownloadedAsset> DownloadAsset(ApiAsset asset)
        {
            if (asset == null) throw new ArgumentNullException(nameof(asset));

            var downloadInfo = ResolveDownloadInfo(asset);

            using (var webClient = _webClientFactory.CreateWebClient(asset.Type != AssetType.Mod))
            {
                using (var stream = await webClient.OpenReadTaskAsync(downloadInfo.Url))
                {
                    // Read content information from the headers.
                    var contentDispositionHeader = webClient.ResponseHeaders.Get("Content-Disposition");
                    var contentLengthHeader = webClient.ResponseHeaders.Get("Content-Length");
                    var contentTypeHeader = webClient.ResponseHeaders.Get("Content-Type");

                    // Ensure the required content headers exist.
                    if (string.IsNullOrWhiteSpace(contentDispositionHeader) ||
                        string.IsNullOrWhiteSpace(contentTypeHeader))
                        throw new Exception("invalid headers");

                    // Parse the content length header to an integer.
                    var contentLength = 0;
                    if (contentLengthHeader != null && !int.TryParse(contentLengthHeader, out contentLength))
                        throw new Exception("invalid headers");

                    // Get asset information for the asset type specified in the url.
                    var assetInfo = asset.Type.GetCustomAttribute<AssetInfoAttribute>();

                    // Ensure the type of the received content matches the expected content type.
                    if (assetInfo == null || assetInfo.ContentType != contentTypeHeader.Split(';').FirstOrDefault())
                        throw new Exception("invalid response type");

                    // Extract the filename of the asset from the content disposition header.
                    var fileNameMatch = Regex.Match(contentDispositionHeader, @"attachment; filename=(""?)(.*)\1");

                    if (fileNameMatch == null || !fileNameMatch.Success)
                        throw new Exception("invalid headers");

                    var fileName = fileNameMatch.Groups[2].Value;

                    // Copy the contents of the downloaded stream to a memory stream.
                    var memoryStream = new MemoryStream();
                    await stream.CopyToAsync(memoryStream);

                    // Verify we received all content.
                    if (contentLengthHeader != null && memoryStream.Length != contentLength)
                        throw new Exception("unexpected end of stream");

                    // Create an instance of ParkitectAsset with the received content and data.
                    return new DownloadedAsset(fileName, asset, downloadInfo, memoryStream);
                }
            }
        }
        private DownloadInfo ResolveDownloadInfo(ApiAsset asset)
        {
            if (asset == null) throw new ArgumentNullException(nameof(asset));

            switch (asset.Type)
            {
                case AssetType.Blueprint:
                case AssetType.Savegame:
                    return new DownloadInfo(asset.Resource.Data.Url, null, null);
                case AssetType.Mod:
                    var repoUrl = asset.Resource.Data.Source;
                    var repoUrlParts = repoUrl.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    var repo = $"{repoUrlParts[repoUrlParts.Length - 2]}/{repoUrlParts[repoUrlParts.Length - 1]}";

                    if (asset.Resource.Data.ZipBall == null)
                        throw new Exception("mod has not yet been released(tagged)");
                    return new DownloadInfo(asset.Resource.Data.ZipBall, repo, asset.Resource.Data.Version);
                default:
                    throw new Exception("unsupported mod type");
            }
        }