combine projects into one
This commit is contained in:
102
Gallery.Share/Util/Converter.cs
Normal file
102
Gallery.Share/Util/Converter.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Gallery.Util.Model;
|
||||
|
||||
namespace Gallery.Util
|
||||
{
|
||||
public class GalleryItemConverter : JsonConverter<GalleryItem>
|
||||
{
|
||||
public override GalleryItem Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartObject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var item = new GalleryItem();
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.EndObject)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
var name = reader.GetString();
|
||||
reader.Read();
|
||||
switch (name)
|
||||
{
|
||||
case nameof(GalleryItem.Id): item.Id = reader.GetInt64(); break;
|
||||
case nameof(GalleryItem.Tags):
|
||||
if (reader.TokenType == JsonTokenType.StartArray)
|
||||
{
|
||||
var tags = new List<string>();
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.EndArray)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
tags.Add(reader.GetString());
|
||||
}
|
||||
}
|
||||
item.Tags = tags.ToArray();
|
||||
}
|
||||
break;
|
||||
case nameof(GalleryItem.CreatedTime): item.CreatedTime = reader.GetDateTime(); break;
|
||||
case nameof(GalleryItem.UpdatedTime): item.UpdatedTime = reader.GetDateTime(); break;
|
||||
case nameof(GalleryItem.UserId): item.UserId = reader.GetString(); break;
|
||||
case nameof(GalleryItem.UserName): item.UserName = reader.GetString(); break;
|
||||
case nameof(GalleryItem.Source): item.Source = reader.GetString(); break;
|
||||
case nameof(GalleryItem.PreviewUrl): item.PreviewUrl = reader.GetString(); break;
|
||||
case nameof(GalleryItem.RawUrl): item.RawUrl = reader.GetString(); break;
|
||||
case nameof(GalleryItem.Width): item.Width = reader.GetInt32(); break;
|
||||
case nameof(GalleryItem.Height): item.Height = reader.GetInt32(); break;
|
||||
case nameof(GalleryItem.BookmarkId): item.BookmarkId = reader.GetString(); break;
|
||||
case nameof(GalleryItem.IsRawPage): item.IsRawPage = reader.GetBoolean(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, GalleryItem value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteNullValue();
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WriteNumber(nameof(GalleryItem.Id), value.Id);
|
||||
if (value.Tags != null)
|
||||
{
|
||||
writer.WritePropertyName(nameof(GalleryItem.Tags));
|
||||
writer.WriteStartArray();
|
||||
for (var i = 0; i < value.Tags.Length; i++)
|
||||
{
|
||||
writer.WriteStringValue(value.Tags[i]);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
writer.WriteString(nameof(GalleryItem.CreatedTime), value.CreatedTime);
|
||||
writer.WriteString(nameof(GalleryItem.UpdatedTime), value.UpdatedTime);
|
||||
writer.WriteString(nameof(GalleryItem.UserId), value.UserId);
|
||||
writer.WriteString(nameof(GalleryItem.UserName), value.UserName);
|
||||
writer.WriteString(nameof(GalleryItem.Source), value.Source);
|
||||
writer.WriteString(nameof(GalleryItem.PreviewUrl), value.PreviewUrl);
|
||||
writer.WriteString(nameof(GalleryItem.RawUrl), value.RawUrl);
|
||||
writer.WriteNumber(nameof(GalleryItem.Width), value.Width);
|
||||
writer.WriteNumber(nameof(GalleryItem.Height), value.Height);
|
||||
writer.WriteString(nameof(GalleryItem.BookmarkId), value.BookmarkId);
|
||||
writer.WriteBoolean(nameof(GalleryItem.IsRawPage), value.IsRawPage);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
67
Gallery.Share/Util/Extensions.cs
Normal file
67
Gallery.Share/Util/Extensions.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
|
||||
namespace Gallery.Util
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static int IndexOf<T>(this T[] array, Predicate<T> predicate)
|
||||
{
|
||||
for (var i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (predicate(array[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int LastIndexOf<T>(this T[] array, Predicate<T> predicate)
|
||||
{
|
||||
for (var i = array.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (predicate(array[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static bool All<T>(this T[] array, Predicate<T> predicate)
|
||||
{
|
||||
for (var i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (!predicate(array[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool AnyFor<T>(this T[] array, int from, int to, Predicate<T> predicate)
|
||||
{
|
||||
for (var i = from; i <= to; i++)
|
||||
{
|
||||
if (predicate(array[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static DateTime ToLocalTime(this long time)
|
||||
{
|
||||
//return new DateTime(1970, 1, 1, 0, 0, 0).AddMilliseconds(time).ToLocalTime();
|
||||
return new DateTime(621355968000000000L + time * 10000000).ToLocalTime();
|
||||
}
|
||||
|
||||
public static long ToTimestamp(this DateTime datetime)
|
||||
{
|
||||
var ticks = datetime.Ticks;
|
||||
return (ticks - 621355968000000000L) / 10000000;
|
||||
}
|
||||
}
|
||||
}
|
23
Gallery.Share/Util/Interface/IGallerySource.cs
Normal file
23
Gallery.Share/Util/Interface/IGallerySource.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.Util.Model;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Util.Interface
|
||||
{
|
||||
public interface IGallerySource
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
string Route { get; }
|
||||
|
||||
string FlyoutIconKey { get; }
|
||||
|
||||
string HomePage { get; }
|
||||
|
||||
void SetCookie();
|
||||
|
||||
void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark);
|
||||
|
||||
Task<GalleryItem[]> GetRecentItemsAsync(int page);
|
||||
}
|
||||
}
|
41
Gallery.Share/Util/Log.cs
Normal file
41
Gallery.Share/Util/Log.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Gallery.Util
|
||||
{
|
||||
public static class Log
|
||||
{
|
||||
public static ILog Logger { get; set; } = new DefaultLogger();
|
||||
|
||||
public static void Print(string message)
|
||||
{
|
||||
Logger?.Print(message);
|
||||
}
|
||||
|
||||
public static void Error(string category, string message)
|
||||
{
|
||||
Logger?.Error(category, message);
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultLogger : ILog
|
||||
{
|
||||
public void Print(string message)
|
||||
{
|
||||
#if DEBUG
|
||||
Debug.WriteLine("[{0:HH:mm:ss.fff}] - {1}", DateTime.Now, message);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Error(string category, string message)
|
||||
{
|
||||
Debug.Fail(string.Format("[{0:HH:mm:ss.fff}] - {1} - {2}", DateTime.Now, category, message));
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILog
|
||||
{
|
||||
void Print(string message);
|
||||
void Error(string category, string message);
|
||||
}
|
||||
}
|
131
Gallery.Share/Util/Model/GalleryItem.cs
Normal file
131
Gallery.Share/Util/Model/GalleryItem.cs
Normal file
@ -0,0 +1,131 @@
|
||||
using System;
|
||||
//using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Util.Model
|
||||
{
|
||||
//[JsonConverter(typeof(GalleryItemConverter))]
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class GalleryItem : BindableObject
|
||||
{
|
||||
const double PREVIEW_WIDTH = 200.0;
|
||||
|
||||
public static readonly BindableProperty TagDescriptionProperty = BindableProperty.Create(nameof(TagDescription), typeof(string), typeof(GalleryItem));
|
||||
public static readonly BindableProperty PreviewImageProperty = BindableProperty.Create(nameof(PreviewImage), typeof(ImageSource), typeof(GalleryItem));
|
||||
public static readonly BindableProperty UserNameProperty = BindableProperty.Create(nameof(UserName), typeof(string), typeof(GalleryItem));
|
||||
public static readonly BindableProperty CreatedTimeProperty = BindableProperty.Create(nameof(CreatedTime), typeof(DateTime), typeof(GalleryItem));
|
||||
public static readonly BindableProperty UpdatedTimeProperty = BindableProperty.Create(nameof(UpdatedTime), typeof(DateTime), typeof(GalleryItem));
|
||||
public static readonly BindableProperty ImageHeightProperty = BindableProperty.Create(nameof(ImageHeight), typeof(GridLength), typeof(GalleryItem),
|
||||
defaultValue: GridLength.Auto);
|
||||
public static readonly BindableProperty IsFavoriteProperty = BindableProperty.Create(nameof(IsFavorite), typeof(bool), typeof(GalleryItem));
|
||||
public static readonly BindableProperty BookmarkIdProperty = BindableProperty.Create(nameof(BookmarkId), typeof(string), typeof(GalleryItem));
|
||||
|
||||
public string TagDescription
|
||||
{
|
||||
get => (string)GetValue(TagDescriptionProperty);
|
||||
set => SetValue(TagDescriptionProperty, value);
|
||||
}
|
||||
public ImageSource PreviewImage
|
||||
{
|
||||
get => (ImageSource)GetValue(PreviewImageProperty);
|
||||
set => SetValue(PreviewImageProperty, value);
|
||||
}
|
||||
public GridLength ImageHeight
|
||||
{
|
||||
get => (GridLength)GetValue(ImageHeightProperty);
|
||||
set => SetValue(ImageHeightProperty, value);
|
||||
}
|
||||
public bool IsFavorite
|
||||
{
|
||||
get => (bool)GetValue(IsFavoriteProperty);
|
||||
set => SetValue(IsFavoriteProperty, value);
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
public string BookmarkId
|
||||
{
|
||||
get => (string)GetValue(BookmarkIdProperty);
|
||||
set => SetValue(BookmarkIdProperty, value);
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
public long Id { get; internal set; }
|
||||
private string[] tags;
|
||||
[JsonProperty]
|
||||
public string[] Tags
|
||||
{
|
||||
get => tags;
|
||||
set
|
||||
{
|
||||
tags = value;
|
||||
if (value != null)
|
||||
{
|
||||
TagDescription = string.Join(' ', tags);
|
||||
}
|
||||
else
|
||||
{
|
||||
TagDescription = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
[JsonProperty]
|
||||
public DateTime CreatedTime { get; set; }
|
||||
[JsonProperty]
|
||||
public DateTime UpdatedTime { get; set; }
|
||||
[JsonProperty]
|
||||
public string UserId { get; set; }
|
||||
[JsonProperty]
|
||||
public string UserName { get; set; }
|
||||
[JsonProperty]
|
||||
public string Source { get; set; }
|
||||
[JsonProperty]
|
||||
public string PreviewUrl { get; set; }
|
||||
[JsonProperty]
|
||||
public string RawUrl { get; set; }
|
||||
[JsonProperty]
|
||||
public bool IsRawPage { get; set; }
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
[JsonProperty]
|
||||
public int Width
|
||||
{
|
||||
get => width;
|
||||
set
|
||||
{
|
||||
width = value;
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
ImageHeight = new GridLength(PREVIEW_WIDTH * height / width);
|
||||
}
|
||||
}
|
||||
}
|
||||
[JsonProperty]
|
||||
public int Height
|
||||
{
|
||||
get => height;
|
||||
set
|
||||
{
|
||||
height = value;
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
ImageHeight = new GridLength(PREVIEW_WIDTH * height / width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal GalleryItem() { }
|
||||
|
||||
public GalleryItem(long id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var source = string.IsNullOrEmpty(Source) ? RawUrl : Source;
|
||||
return $"{Id}, {source}";
|
||||
}
|
||||
}
|
||||
}
|
283
Gallery.Share/Util/NetHelper.cs
Normal file
283
Gallery.Share/Util/NetHelper.cs
Normal file
@ -0,0 +1,283 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
148
Gallery.Share/Util/ParallelTask.cs
Normal file
148
Gallery.Share/Util/ParallelTask.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Gallery.Util
|
||||
{
|
||||
public class ParallelTask : IDisposable
|
||||
{
|
||||
public static ParallelTask Start(string tag, int from, int toExclusive, int maxCount, Predicate<int> action, int tagIndex = -1, WaitCallback complete = null)
|
||||
{
|
||||
if (toExclusive <= from)
|
||||
{
|
||||
if (complete != null)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(complete);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var task = new ParallelTask(tag, from, toExclusive, maxCount, action, tagIndex, complete);
|
||||
task.Start();
|
||||
return task;
|
||||
}
|
||||
|
||||
private readonly object sync = new();
|
||||
private int count;
|
||||
private bool disposed;
|
||||
|
||||
public int TagIndex { get; private set; }
|
||||
private readonly string tag;
|
||||
private readonly int max;
|
||||
private readonly int from;
|
||||
private readonly int to;
|
||||
private readonly Predicate<int> action;
|
||||
private readonly WaitCallback complete;
|
||||
|
||||
private ParallelTask(string tag, int from, int to, int maxCount, Predicate<int> action, int tagIndex, WaitCallback complete)
|
||||
{
|
||||
if (maxCount <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maxCount));
|
||||
}
|
||||
max = maxCount;
|
||||
if (from >= to)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(from));
|
||||
}
|
||||
TagIndex = tagIndex;
|
||||
this.tag = tag;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.action = action;
|
||||
this.complete = complete;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(DoStart);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private void DoStart(object state)
|
||||
{
|
||||
#if DEBUG
|
||||
const long TIMEOUT = 60000L;
|
||||
var sw = new System.Diagnostics.Stopwatch();
|
||||
long lastElapsed = 0;
|
||||
sw.Start();
|
||||
#endif
|
||||
for (int i = from; i < to; i++)
|
||||
{
|
||||
var index = i;
|
||||
while (count >= max)
|
||||
{
|
||||
#if DEBUG
|
||||
var elapsed = sw.ElapsedMilliseconds;
|
||||
if (elapsed - lastElapsed > TIMEOUT)
|
||||
{
|
||||
lastElapsed = elapsed;
|
||||
Log.Print($"WARNING: parallel task ({tag}), {count} tasks in queue, cost too much time ({elapsed:n0}ms)");
|
||||
}
|
||||
#endif
|
||||
if (disposed)
|
||||
{
|
||||
#if DEBUG
|
||||
sw.Stop();
|
||||
Log.Print($"parallel task determinate, disposed ({tag}), cost time ({elapsed:n0}ms)");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
Thread.Sleep(16);
|
||||
}
|
||||
lock (sync)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
ThreadPool.QueueUserWorkItem(o =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!action(index))
|
||||
{
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"parallel.start ({tag})", $"failed to run action, index: {index}, error: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
count--;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
while (count > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
var elapsed = sw.ElapsedMilliseconds;
|
||||
if (elapsed - lastElapsed > TIMEOUT)
|
||||
{
|
||||
lastElapsed = elapsed;
|
||||
Log.Print($"WARNING: parallel task ({tag}), {count} ending tasks in queue, cost too much time ({elapsed:n0}ms)");
|
||||
}
|
||||
#endif
|
||||
if (disposed)
|
||||
{
|
||||
#if DEBUG
|
||||
sw.Stop();
|
||||
Log.Print($"parallel task determinate, disposed ({tag}), ending cost time ({elapsed:n0}ms)");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
Thread.Sleep(16);
|
||||
}
|
||||
#if DEBUG
|
||||
sw.Stop();
|
||||
Log.Print($"parallel task done ({tag}), cost time ({sw.ElapsedMilliseconds:n0}ms)");
|
||||
#endif
|
||||
complete?.Invoke(null);
|
||||
}
|
||||
}
|
||||
}
|
98
Gallery.Share/Util/Store.cs
Normal file
98
Gallery.Share/Util/Store.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Util
|
||||
{
|
||||
public static class Store
|
||||
{
|
||||
public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
|
||||
public static readonly string CacheFolder = FileSystem.CacheDirectory;
|
||||
|
||||
private const string imageFolder = "img-original";
|
||||
private const string previewFolder = "img-preview";
|
||||
|
||||
public static async Task<ImageSource> LoadRawImage(string url)
|
||||
{
|
||||
return await LoadImageAsync(url, null, PersonalFolder, imageFolder, force: true);
|
||||
}
|
||||
|
||||
public static async Task<ImageSource> LoadPreviewImage(string url, bool downloading, bool force = false)
|
||||
{
|
||||
return await LoadImage(url, CacheFolder, previewFolder, downloading, force: force);
|
||||
}
|
||||
|
||||
private static async Task<ImageSource> LoadImage(string url, string working, string folder, bool downloading, bool force = false)
|
||||
{
|
||||
var file = Path.Combine(working, folder, Path.GetFileName(url));
|
||||
ImageSource image;
|
||||
if (!force && File.Exists(file))
|
||||
{
|
||||
image = ImageSource.FromFile(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
image = null;
|
||||
}
|
||||
if (downloading && image == null)
|
||||
{
|
||||
file = await NetHelper.DownloadImage(url, working, folder);
|
||||
if (file != null)
|
||||
{
|
||||
return ImageSource.FromFile(file);
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private static async Task<ImageSource> LoadImageAsync(string url, string id, string working, string folder, bool force = false)
|
||||
{
|
||||
var file = Path.Combine(working, folder, Path.GetFileName(url));
|
||||
ImageSource image;
|
||||
if (!force && File.Exists(file))
|
||||
{
|
||||
image = ImageSource.FromFile(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
image = null;
|
||||
}
|
||||
if (image == null)
|
||||
{
|
||||
file = await NetHelper.DownloadImageAsync(url, id, working, folder);
|
||||
if (file != null)
|
||||
{
|
||||
image = ImageSource.FromFile(file);
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Config
|
||||
{
|
||||
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
public const string DownloadThreadsKey = "download_threads";
|
||||
public const string IsProxiedKey = "is_proxied";
|
||||
public const string ProxyHostKey = "proxy_host";
|
||||
public const string ProxyPortKey = "proxy_port";
|
||||
|
||||
public const int MaxThreads = 8;
|
||||
public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36";
|
||||
public const string AcceptLanguage = "zh-cn";
|
||||
public const string AcceptImage = "image/png,image/*,*/*;q=0.8";
|
||||
|
||||
public static int DownloadThreads;
|
||||
public static WebProxy Proxy;
|
||||
}
|
||||
|
||||
public static class Routes
|
||||
{
|
||||
public const string Gallery = "gallery";
|
||||
public const string Option = "option";
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user