From 508bab33957bbff91627a5e5250af7acebfd05ce Mon Sep 17 00:00:00 2001 From: Tsanie Lily Date: Mon, 18 May 2020 21:36:59 +0800 Subject: [PATCH] feature: sync online favorites --- Pixiview/App.cs | 2 +- Pixiview/Illust/FavoritesPage.xaml.cs | 105 ++++++++++++++++++++++++- Pixiview/Illust/ViewIllustPage.xaml.cs | 5 ++ Pixiview/Utils/Extensions.cs | 9 ++- Pixiview/Utils/HttpUtility.cs | 8 +- Pixiview/Utils/Stores.cs | 95 +++++++++++++++++++++- 6 files changed, 215 insertions(+), 9 deletions(-) diff --git a/Pixiview/App.cs b/Pixiview/App.cs index ea67e0c..70a006d 100644 --- a/Pixiview/App.cs +++ b/Pixiview/App.cs @@ -191,7 +191,7 @@ namespace Pixiview var distinct = favObj.Illusts.Where(f => !list.Any(i => i.Id == f.Id)).ToList(); list.InsertRange(0, distinct); - favNow.Illusts = list; + //favNow.Illusts = list; Stores.SaveFavoritesIllusts(); } diff --git a/Pixiview/Illust/FavoritesPage.xaml.cs b/Pixiview/Illust/FavoritesPage.xaml.cs index 09b830d..ed8d998 100644 --- a/Pixiview/Illust/FavoritesPage.xaml.cs +++ b/Pixiview/Illust/FavoritesPage.xaml.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Pixiview.Resources; +using Pixiview.UI; using Pixiview.Utils; using Xamarin.Essentials; using Xamarin.Forms; @@ -81,9 +84,105 @@ namespace Pixiview.Illust return; } await ScrollToTopAsync(scrollView); - flag = false; - lastUpdated = default; - StartLoad(true); + IsLoading = true; + + var offset = 16 - IndicatorMarginTop; + activityLoading.Margin = new Thickness(0, loadingOffset - offset, 0, offset); + activityLoading.Animate("margin", top => + { + activityLoading.Margin = new Thickness(0, top, 0, offset); + }, + loadingOffset - offset, 16 - offset, easing: Easing.CubicOut, finished: (v, r) => + { + Task.Run(() => + { + var list = Stores.LoadOnlineFavorites(); + if (list != null && list.Length > 0) + { + MainThread.BeginInvokeOnMainThread(() => ConfirmNext(list)); + } + else + { + flag = false; + lastUpdated = default; + MainThread.BeginInvokeOnMainThread(() => StartLoad(true)); + } + }); + }); + } + + private async void ConfirmNext(IllustItem[] list) + { + var cancel = ResourceHelper.Cancel; + var combine = ResourceHelper.FavoritesCombine; + var replace = ResourceHelper.FavoritesReplace; + var result = await DisplayActionSheet( + ResourceHelper.FavoritesOperation, + cancel, + combine, + replace); + + if (result == cancel) + { + return; + } + if (result == replace) + { + Stores.GetFavoriteObject().Illusts = new FavoriteList(list); + } + else if (result == combine) + { + // combine + var favNow = Stores.GetFavoriteObject(); + var nows = favNow.Illusts; + list = list.Where(f => !nows.Any(i => i.Id == f.Id)).ToArray(); + for (var i = 0; i < list.Length; i++) + { + list[i].Image = StyleDefinition.DownloadBackground; + } + nows.InsertRange(0, list); + } + else + { + return; + } + + ParallelTask.Start(0, list.Length, Configs.MaxPageThreads, i => + { + var illustItem = list[i]; + var data = Stores.LoadIllustPreloadData(illustItem.Id); + if (data != null && data.illust.TryGetValue(illustItem.Id, out var illust)) + { + illust.CopyToItem(illustItem); + if (data.user.TryGetValue(illust.userId, out var user)) + { + illustItem.ProfileUrl = user.image; + } + return true; + } + App.DebugError("load.favorite", $"cannot fetch preload data, {illustItem.Id}, disposed."); + return false; + }, () => + { + Stores.SaveFavoritesIllusts(); + + MainThread.BeginInvokeOnMainThread(() => + { + var offset = 16 - IndicatorMarginTop; + activityLoading.Animate("margin", top => + { + activityLoading.Margin = new Thickness(0, top, 0, offset); + }, + 16 - offset, loadingOffset - offset, easing: Easing.CubicIn, finished: (v, r) => + { + IsLoading = false; + + flag = false; + lastUpdated = default; + StartLoad(); + }); + }); + }); } private async void ShareFavorites_Clicked(object sender, EventArgs e) diff --git a/Pixiview/Illust/ViewIllustPage.xaml.cs b/Pixiview/Illust/ViewIllustPage.xaml.cs index 2220c28..cc3e16f 100644 --- a/Pixiview/Illust/ViewIllustPage.xaml.cs +++ b/Pixiview/Illust/ViewIllustPage.xaml.cs @@ -514,6 +514,11 @@ namespace Pixiview.Illust } else { + var item = favorites[index]; + if (illust.BookmarkId == null && item.BookmarkId != null) + { + illust.BookmarkId = item.BookmarkId; + } illust.IsFavorite = false; favorites.RemoveAt(index); FavoriteIcon = fontIconNotLove; diff --git a/Pixiview/Utils/Extensions.cs b/Pixiview/Utils/Extensions.cs index 4a8f695..fb982e2 100644 --- a/Pixiview/Utils/Extensions.cs +++ b/Pixiview/Utils/Extensions.cs @@ -95,13 +95,13 @@ namespace Pixiview.Utils public class ParallelTask : IDisposable { - public static ParallelTask Start(int from, int toExclusive, int maxCount, Predicate action) + public static ParallelTask Start(int from, int toExclusive, int maxCount, Predicate action, Action complete = null) { if (toExclusive <= from) { return null; } - var task = new ParallelTask(from, toExclusive, maxCount, action); + var task = new ParallelTask(from, toExclusive, maxCount, action, complete); Task.Run(task.Start); return task; } @@ -114,8 +114,9 @@ namespace Pixiview.Utils private readonly int from; private readonly int to; private readonly Predicate action; + private readonly Action complete; - private ParallelTask(int from, int to, int maxCount, Predicate action) + private ParallelTask(int from, int to, int maxCount, Predicate action, Action complete) { if (maxCount <= 0) { @@ -129,6 +130,7 @@ namespace Pixiview.Utils this.from = from; this.to = to; this.action = action; + this.complete = complete; } private void Start() @@ -182,6 +184,7 @@ namespace Pixiview.Utils }); } App.DebugPrint($"parallel task done"); + complete?.Invoke(); } public void Dispose() diff --git a/Pixiview/Utils/HttpUtility.cs b/Pixiview/Utils/HttpUtility.cs index 7a181cb..066fa9b 100644 --- a/Pixiview/Utils/HttpUtility.cs +++ b/Pixiview/Utils/HttpUtility.cs @@ -17,7 +17,8 @@ namespace Pixiview.Utils HttpContent post = null, Action header = null, Func namehandler = null, - Func action = null) + Func action = null, + Func @return = null) { string content = null; if (post == null && !force && file != null && File.Exists(file)) @@ -89,6 +90,11 @@ namespace Pixiview.Utils { content = action(content); } + if (@return != null) + { + error = null; + return @return(content); + } } catch (Exception ex) { diff --git a/Pixiview/Utils/Stores.cs b/Pixiview/Utils/Stores.cs index 3303879..a735062 100644 --- a/Pixiview/Utils/Stores.cs +++ b/Pixiview/Utils/Stores.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Text; +using System.Text.RegularExpressions; using Newtonsoft.Json; using Pixiview.Illust; using Xamarin.Essentials; @@ -478,6 +479,94 @@ namespace Pixiview.Utils return result; } + private static readonly Regex regexIllust = new Regex( + @"book_id\[\]"" value=""([0-9]+)"".*data-src=""([^""]+)"".*data-id=""([0-9]+)"".*" + + @"data-tags=""([^""]+)"".*data-user-id=""([0-9]+)"".*" + + @"class=""title"" title=""([^""]+)"".*data-user_name=""([^""]+)"".*" + + @"_bookmark-icon-inline"">([0-9]+)", + RegexOptions.Compiled); + + public static IllustItem[] LoadOnlineFavorites() + { + var list = new List(); + int p = 1; + while (p > 0) + { + var url = Configs.UrlFavoriteList; + if (p > 1) + { + url += $"&p={p}"; + } + var array = HttpUtility.LoadObject( + null, + url, + null, + out _, + action: content => + { + // page + p = -1; + var index = content.IndexOf(" 0) + { + var page = content.Substring(index + 45, content.IndexOf('\"', index + 45) - index - 45); + if (int.TryParse(page, out var next)) + { + p = next; + } + } + // list + index = content.IndexOf("
  • "); + if (index < 0) + { + return null; + } + content = content.Substring(index + 23); + index = content.IndexOf("
  • "); + if (index < 0) + { + return null; + } + return content.Substring(0, index); + }, + @return: content => + { + if (content == null) + { + return null; + } + var lines = content.Split("
  • "); + var illusts = new IllustItem[lines.Length]; + for (var i = 0; i < illusts.Length; i++) + { + var line = lines[i]; + var m = regexIllust.Match(line); + if (m.Success) + { + illusts[i] = new IllustItem + { + IsFavorite = true, + BookmarkId = m.Groups[1].Value, + ImageUrl = m.Groups[2].Value, + Id = m.Groups[3].Value, + Tags = m.Groups[4].Value.Split(' '), + UserId = m.Groups[5].Value, + Title = m.Groups[6].Value, + UserName = m.Groups[7].Value, + RatingCount = int.Parse(m.Groups[8].Value) + }; + } + } + return illusts; + }); + if (array != null && array.Length > 0) + { + list.AddRange(array); + } + } + return list.ToArray(); + } + public static ImageSource LoadIllustImage(string url) { return LoadImage(url, PersonalFolder, imageFolder); @@ -561,6 +650,9 @@ namespace Pixiview.Utils { public bool Changed { get; private set; } + public FavoriteList() : base() { } + public FavoriteList(IEnumerable collection) : base(collection) { } + public new void Insert(int index, IllustItem item) { base.Insert(index, item); @@ -603,7 +695,7 @@ namespace Pixiview.Utils public const string QueryDateKey = "query_date"; public const int MaxPageThreads = 3; - public const int MaxThreads = 6; + public const int MaxThreads = 4; public const string Referer = "https://www.pixiv.net/"; public const string RefererIllust = "https://www.pixiv.net/artworks/{0}"; public const string RefererIllustRanking = "https://www.pixiv.net/ranking.php?{0}"; @@ -700,6 +792,7 @@ namespace Pixiview.Utils public static string UrlIllustUgoira => Prefix + "ajax/illust/{0}/ugoira_meta?lang=zh"; public static string UrlIllustRecommendsInit => Prefix + "ajax/illust/{0}/recommend/init?limit=18&lang=zh"; public static string UrlIllustRecommendsList => Prefix + "ajax/illust/recommend/illusts?{0}lang=zh"; + public static string UrlFavoriteList => Prefix + "bookmark.php?rest=show"; public static string BookmarkAdd => Prefix + "ajax/illusts/bookmarks/add"; public static string BookmarkRpc => Prefix + "rpc/index.php";