Gallery/Gallery.Share/Util/NetHelper.cs
2021-08-11 14:09:03 +08:00

284 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Xamarin.Essentials;
namespace Gallery.Util
{
public static class NetHelper
{
public static bool NetworkAvailable
{
get
{
try
{
return Connectivity.NetworkAccess == NetworkAccess.Internet
|| Connectivity.NetworkAccess == NetworkAccess.ConstrainedInternet;
}
catch
{
return false;
}
}
}
public static async Task<(T result, string error)> RequestObject<T>(string url,
string referer = null,
HttpContent post = null,
Action<HttpRequestHeaders> headerHandler = null,
Func<string, string> contentHandler = null,
Func<string, T> @return = null)
{
var response = await Request(url, headers =>
{
if (referer != null)
{
headers.Referrer = new Uri(referer);
}
headers.Add("User-Agent", Config.UserAgent);
headerHandler?.Invoke(headers);
}, post);
if (response == null)
{
return (default, "response is null");
}
if (!response.IsSuccessStatusCode)
{
Log.Print($"http failed with code: {(int)response.StatusCode} - {response.StatusCode}");
return (default, response.StatusCode.ToString());
}
string content;
using (response)
{
try
{
content = await response.Content.ReadAsStringAsync();
if (contentHandler != null)
{
content = contentHandler(content);
}
if (@return != null)
{
return (@return(content), null);
}
}
catch (Exception ex)
{
Log.Error("stream.load", $"failed to read stream, error: {ex.Message}");
return (default, ex.Message);
}
}
if (content == null)
{
content = string.Empty;
}
try
{
var result = JsonConvert.DeserializeObject<T>(content);
return (result, null);
}
catch (Exception ex)
{
var memo = content.Length < 20 ? content : content[0..20] + "...";
Log.Error("content.deserialize", $"failed to parse JSON object, content: {memo}, error: {ex.Message}");
return (default, content);
}
}
public static async Task<string> DownloadImage(string url, string working, string folder)
{
try
{
var directory = Path.Combine(working, folder);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var file = Path.Combine(directory, Path.GetFileName(url));
var response = await Request(url, headers =>
{
headers.Add("User-Agent", Config.UserAgent);
headers.Add("Accept", Config.AcceptImage);
});
if (response == null)
{
return null;
}
using (response)
using (var fs = File.OpenWrite(file))
{
await response.Content.CopyToAsync(fs);
}
return file;
}
catch (Exception ex)
{
Log.Error("image.download", ex.Message);
return null;
}
}
public static async Task<string> DownloadImageAsync(string url, string id, string working, string folder)
{
try
{
var directory = Path.Combine(working, folder);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var file = Path.Combine(directory, Path.GetFileName(url));
var proxy = Config.Proxy;
var handler = new HttpClientHandler
{
UseCookies = false
};
if (proxy != null)
{
handler.Proxy = proxy;
handler.UseProxy = true;
}
var client = new HttpClient(handler, true)
{
Timeout = Config.Timeout
};
long size;
DateTimeOffset lastModified;
using (var request = new HttpRequestMessage(HttpMethod.Head, url))
{
var headers = request.Headers;
headers.Add("Accept", Config.AcceptImage);
headers.Add("Accept-Language", Config.AcceptLanguage);
headers.Add("User-Agent", Config.UserAgent);
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
size = response.Content.Headers.ContentLength.Value;
lastModified = response.Content.Headers.LastModified.Value;
#if DEBUG
Log.Print($"content length: {size:n0} bytes, last modified: {lastModified}");
#endif
}
// segments
const int SIZE = 150000;
var list = new List<(long from, long to)>();
for (long i = 0; i < size; i += SIZE)
{
long to;
if (i + SIZE >= size)
{
to = size - 1;
}
else
{
to = i + SIZE - 1;
}
list.Add((i, to));
}
var data = new byte[size];
var task = new TaskCompletionSource<string>();
ParallelTask.Start($"download.async.{id}", 0, list.Count, 2, i =>
{
var (from, to) = list[i];
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
var headers = request.Headers;
headers.Add("Accept", Config.AcceptImage);
headers.Add("Accept-Language", Config.AcceptLanguage);
headers.Add("Accept-Encoding", "identity");
headers.IfRange = new RangeConditionHeaderValue(lastModified);
headers.Range = new RangeHeaderValue(from, to);
headers.Add("User-Agent", Config.UserAgent);
using var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
using var ms = new MemoryStream(data, (int)from, (int)(to - from + 1));
response.Content.CopyToAsync(ms).Wait();
#if DEBUG
Log.Print($"downloaded range: from({from:n0}) to ({to:n0})");
#endif
}
return true;
},
complete: o =>
{
using (var fs = File.OpenWrite(file))
{
fs.Write(data, 0, data.Length);
}
task.SetResult(file);
});
return await task.Task;
}
catch (Exception ex)
{
Log.Error("image.download.async", $"failed to download image, error: {ex.Message}");
return null;
}
}
private static async Task<HttpResponseMessage> Request(string url, Action<HttpRequestHeaders> headerHandler, HttpContent post = null)
{
#if DEBUG
var method = post == null ? "GET" : "POST";
Log.Print($"{method}: {url}");
#endif
var uri = new Uri(url);
var proxy = Config.Proxy;
var handler = new HttpClientHandler
{
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate,
UseCookies = false
};
if (proxy != null)
{
handler.Proxy = proxy;
handler.UseProxy = true;
}
var client = new HttpClient(handler, true)
{
BaseAddress = new Uri($"{uri.Scheme}://{uri.Host}:{uri.Port}"),
Timeout = Config.Timeout
};
return await TryCount(() =>
{
using var request = new HttpRequestMessage(post == null ? HttpMethod.Get : HttpMethod.Post, uri.PathAndQuery);
var headers = request.Headers;
headerHandler?.Invoke(headers);
headers.Add("Accept-Language", Config.AcceptLanguage);
if (post != null)
{
request.Content = post;
}
return client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
});
}
private static T TryCount<T>(Func<T> func, int tryCount = 2)
{
int tries = 0;
while (tries < tryCount)
{
try
{
return func();
}
catch (Exception ex)
{
tries++;
Thread.Sleep(1000);
Log.Error("try.do", $"tries: {tries}, error: {ex.Message}");
}
}
return default;
}
}
}