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(string url, string referer = null, HttpContent post = null, Action headerHandler = null, Func contentHandler = null, Func @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(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 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 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(); 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 Request(string url, Action 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(Func 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; } } }