favorite & view page & etc.

This commit is contained in:
2021-08-12 16:27:42 +08:00
parent 3152f47db5
commit fa0b033f2a
33 changed files with 728 additions and 94 deletions

View File

@ -0,0 +1,30 @@
using System;
using System.Net;
namespace Gallery.Util
{
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 = 4;
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 Favorite = "favorite";
public const string Option = "option";
}
}

View File

@ -1,4 +1,5 @@
using System;
using Gallery.Util.Model;
namespace Gallery.Util
{
@ -63,5 +64,14 @@ namespace Gallery.Util
var ticks = datetime.Ticks;
return (ticks - 621355968000000000L) / 10000000;
}
public static bool SourceEquals(this GalleryItem a, GalleryItem b)
{
if (a == null || b == null)
{
return false;
}
return a.Id == b.Id && a.Source == b.Source;
}
}
}

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using Gallery.Util.Model;
using Xamarin.Forms;
@ -7,17 +8,28 @@ namespace Gallery.Util.Interface
public interface IGallerySource
{
string Name { get; }
string Route { get; }
string FlyoutIconKey { get; }
string HomePage { get; }
bool IsScrollable { get; }
void SetCookie();
void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark);
Task<IEnumerable<GalleryItem>> GetRecentItemsAsync(int page);
Task<string> ResolveImageUrl(GalleryItem item);
}
Task<GalleryItem[]> GetRecentItemsAsync(int page);
public abstract class GallerySourceBase : IGallerySource
{
public abstract string Name { get; }
public abstract string Route { get; }
public abstract string FlyoutIconKey { get; }
public abstract string HomePage { get; }
public virtual bool IsScrollable => true;
public abstract void SetCookie();
public abstract void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark);
public abstract Task<IEnumerable<GalleryItem>> GetRecentItemsAsync(int page);
public virtual Task<string> ResolveImageUrl(GalleryItem item) => Task.FromResult(item.RawUrl);
}
}

View File

@ -80,6 +80,8 @@ namespace Gallery.Util.Model
[JsonProperty]
public string Source { get; set; }
[JsonProperty]
public string SourceUrl { get; set; }
[JsonProperty]
public string PreviewUrl { get; set; }
[JsonProperty]
public string RawUrl { get; set; }
@ -124,7 +126,7 @@ namespace Gallery.Util.Model
public override string ToString()
{
var source = string.IsNullOrEmpty(Source) ? RawUrl : Source;
var source = string.IsNullOrEmpty(SourceUrl) ? RawUrl : SourceUrl;
return $"{Id}, {source}";
}
}

View File

@ -125,7 +125,7 @@ namespace Gallery.Util
}
}
public static async Task<string> DownloadImageAsync(string url, string id, string working, string folder)
public static async Task<string> DownloadImageAsync(string url, string id, string working, string folder, Action<(int loc, int size)> action = null)
{
try
{
@ -149,7 +149,7 @@ namespace Gallery.Util
{
Timeout = Config.Timeout
};
long size;
int size;
DateTimeOffset lastModified;
using (var request = new HttpRequestMessage(HttpMethod.Head, url))
{
@ -158,7 +158,7 @@ namespace Gallery.Util
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;
size = (int)response.Content.Headers.ContentLength.Value;
lastModified = response.Content.Headers.LastModified.Value;
#if DEBUG
Log.Print($"content length: {size:n0} bytes, last modified: {lastModified}");
@ -167,10 +167,10 @@ namespace Gallery.Util
// segments
const int SIZE = 150000;
var list = new List<(long from, long to)>();
for (long i = 0; i < size; i += SIZE)
var list = new List<(int from, int to)>();
for (var i = 0; i < size; i += SIZE)
{
long to;
int to;
if (i + SIZE >= size)
{
to = size - 1;
@ -198,11 +198,12 @@ namespace Gallery.Util
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));
using var ms = new MemoryStream(data, from, to - from + 1);
response.Content.CopyToAsync(ms).Wait();
#if DEBUG
Log.Print($"downloaded range: from({from:n0}) to ({to:n0})");
#endif
action?.Invoke((to, size));
}
return true;
},

View File

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Gallery.Util.Model;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Forms;
@ -12,17 +15,120 @@ namespace Gallery.Util
public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
public static readonly string CacheFolder = FileSystem.CacheDirectory;
private const string favoriteFile = "favorites.json";
private const string imageFolder = "img-original";
private const string previewFolder = "img-preview";
public static async Task<ImageSource> LoadRawImage(string url)
public static FavoriteList FavoriteList => GetFavoriteList();
public static string FavoriteListPath => Path.Combine(PersonalFolder, favoriteFile);
private static FavoriteList favoriteList;
public static FavoriteList GetFavoriteList(bool force = false)
{
return await LoadImageAsync(url, null, PersonalFolder, imageFolder, force: true);
if (force || favoriteList == null)
{
var favorites = LoadFavoriteList();
if (favorites != null)
{
favoriteList = favorites;
}
else
{
favoriteList = new FavoriteList();
}
}
return favoriteList;
}
public static async Task<ImageSource> LoadPreviewImage(string url, bool downloading, bool force = false)
private static FavoriteList LoadFavoriteList(string file = null)
{
return await LoadImage(url, CacheFolder, previewFolder, downloading, force: force);
if (file == null)
{
file = FavoriteListPath;
}
return LoadObject<FavoriteList>(file);
}
public static void SaveFavoriteList()
{
var file = FavoriteListPath;
WriteObject(file, FavoriteList);
}
private static T LoadObject<T>(string file)
{
string content = null;
if (File.Exists(file))
{
try
{
content = File.ReadAllText(file);
}
catch (Exception ex)
{
Log.Error("file.read", $"failed to read file: {file}, error: {ex.Message}");
}
}
else
{
return default;
}
try
{
return JsonConvert.DeserializeObject<T>(content);
}
catch (Exception ex)
{
Log.Error("file.deserialize", $"failed to parse JSON object, error: {ex.Message}");
return default;
}
}
private static void WriteObject(string file, object obj)
{
var folder = Path.GetDirectoryName(file);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
string content;
try
{
content = JsonConvert.SerializeObject(obj, Formatting.None);
}
catch (Exception ex)
{
Log.Error("object.serialize", $"failed to serialize object, error: {ex.Message}");
return;
}
try
{
File.WriteAllText(file, content, Encoding.UTF8);
}
catch (Exception ex)
{
Log.Error("file.write", $"failed to write file: {file}, error: {ex.Message}");
}
}
public static async Task<ImageSource> LoadRawImage(GalleryItem item, bool force = false, Action<(int loc, int size)> action = null)
{
return await LoadImageAsync(item.RawUrl, null, PersonalFolder, Path.Combine(imageFolder, item.Source), force, action);
}
public static string GetRawImagePath(GalleryItem item)
{
var file = Path.Combine(PersonalFolder, imageFolder, item.Source, Path.GetFileName(item.RawUrl));
if (File.Exists(file))
{
return file;
}
return null;
}
public static async Task<ImageSource> LoadPreviewImage(GalleryItem item, bool downloading, bool force = false)
{
return await LoadImage(item.PreviewUrl, CacheFolder, Path.Combine(previewFolder, item.Source), downloading, force: force);
}
private static async Task<ImageSource> LoadImage(string url, string working, string folder, bool downloading, bool force = false)
@ -48,7 +154,7 @@ namespace Gallery.Util
return image;
}
private static async Task<ImageSource> LoadImageAsync(string url, string id, string working, string folder, bool force = false)
private static async Task<ImageSource> LoadImageAsync(string url, string id, string working, string folder, bool force, Action<(int loc, int size)> action)
{
var file = Path.Combine(working, folder, Path.GetFileName(url));
ImageSource image;
@ -62,7 +168,7 @@ namespace Gallery.Util
}
if (image == null)
{
file = await NetHelper.DownloadImageAsync(url, id, working, folder);
file = await NetHelper.DownloadImageAsync(url, id, working, folder, action);
if (file != null)
{
image = ImageSource.FromFile(file);
@ -72,27 +178,33 @@ namespace Gallery.Util
}
}
public static class Config
public class FavoriteList : List<GalleryItem>
{
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
public bool Changed { get; private set; }
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 FavoriteList() : base() { }
public FavoriteList(IEnumerable<GalleryItem> collection) : base(collection) { }
public const int MaxThreads = 2;
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 new void Insert(int index, GalleryItem item)
{
base.Insert(index, item);
Changed = true;
}
public new void InsertRange(int index, IEnumerable<GalleryItem> collection)
{
base.InsertRange(index, collection);
Changed = true;
}
public new void RemoveAt(int index)
{
base.RemoveAt(index);
Changed = true;
}
public static int DownloadThreads;
public static WebProxy Proxy;
}
public static class Routes
{
public const string Gallery = "gallery";
public const string Option = "option";
public FavoriteList Reload()
{
Changed = false;
return this;
}
}
}