private void SearchList_MouseDoubleClick(object sender, MouseButtonEventArgs e) { if (SearchList.SelectedItems.Count > 0) { (new ArticleInfoWindow( HitomiLegalize.MetadataToArticle( HitomiLegalize.GetMetadataFromMagic((SearchList.SelectedItems[0] as FinderDataGridItemViewModel).아이디).Value) )).Show(); } }
/// <summary> /// 로그를 복구하고, 기존 설정을 덮어씁니다. /// </summary> /// <param name="args"></param> static void ProcessRecoverLog(string[] args) { Console.Instance.WriteLine("This operation deletes existing log contents."); Console.Instance.Write("Are you sure to continue? yes or no) "); var res = System.Console.ReadLine(); if (res != "yes") { Console.Instance.WriteLine("Process canceled."); return; } Console.Instance.WriteLine("Enumerating files..."); var fi = new FileIndexor(); var file_list = new List <FileInfo>(); fi.ListingDirectoryAsync(args[0]).Wait(); fi.Enumerate((string path, List <FileInfo> files) => { foreach (var iz in files) { if (Path.GetExtension(iz.Name) == ".zip") { file_list.Add(iz); } } }); Console.Instance.WriteLine($"{file_list.Count.ToString("0,0")} zip files found!"); file_list.Sort((x, y) => x.LastWriteTime.CompareTo(y.LastWriteTime)); var rx = new Regex(@"^\[(\d+)\]"); foreach (var file in file_list) { if (rx.Match(Path.GetFileNameWithoutExtension(file.Name)).Success) { var id = rx.Match(Path.GetFileNameWithoutExtension(file.Name)).Groups[1].Value; var md = HitomiLegalize.GetMetadataFromMagic(id); if (!md.HasValue) { Console.Instance.WriteLine($"{id} article was not found!"); continue; } HitomiLog.Instance.AddArticle(HitomiLegalize.MetadataToArticle(md.Value), file.LastWriteTime); } } HitomiLog.Instance.Save(); }
private void load() { var vm = DataContext as BookmarkPageDataGridViewModel; vm.Items.Clear(); var ll = new List <BookmarkPageDataGridItemViewModel>(); foreach (var artist in BookmarkModelManager.Instance.Model.artists) { if (artist.Item1 == classify_name) { ll.Add(new BookmarkPageDataGridItemViewModel { 내용 = artist.Item2.content, 형 = "작가", 추가된날짜 = artist.Item2.stamp.ToString(), 경로 = artist.Item2.path, BIM = artist.Item2, 기타 = artist.Item2.etc }); } } foreach (var group in BookmarkModelManager.Instance.Model.groups) { if (group.Item1 == classify_name) { ll.Add(new BookmarkPageDataGridItemViewModel { 내용 = group.Item2.content, 형 = "그룹", 추가된날짜 = group.Item2.stamp.ToString(), 경로 = group.Item2.path, BIM = group.Item2, 기타 = group.Item2.etc }); } } foreach (var article in BookmarkModelManager.Instance.Model.articles) { if (article.Item1 == classify_name) { ll.Add(new BookmarkPageDataGridItemViewModel { 내용 = article.Item2.content + " - " + HitomiLegalize.GetMetadataFromMagic(article.Item2.content)?.Name, 형 = "작품", 추가된날짜 = article.Item2.stamp.ToString(), 경로 = article.Item2.path, BIM = article.Item2, 기타 = article.Item2.etc }); } } ll.Sort((x, y) => SortAlgorithm.ComparePath(y.추가된날짜, x.추가된날짜)); for (int i = 0; i < ll.Count; i++) { ll[i].인덱스 = (i + 1).ToString(); } foreach (var item in ll) { vm.Items.Add(item); } }
/// <summary> /// 레이팅 내림차순으로 정렬한 태그 통계를 가져옵니다. /// </summary> /// <param name="sys"></param> /// <returns></returns> public static List <EloPlayer> GetTagRanking(EloSystem sys) { // 태그 비교를 위해 태그시스템을 생성합니다. EloSystem tag_sys = new EloSystem(); HashSet <string> tags = new HashSet <string>(); foreach (var article in HitomiIndex.Instance.metadata_collection) { if (article.Tags != null) { article.Tags.ToList().ForEach(x => tags.Add(HitomiIndex.Instance.index.Tags[x])); } } var list = tags.ToList(); list.Sort(); tag_sys.AppendPlayer(list.Count); var tag_dic = new Dictionary <string, int>(); for (int i = 0; i < list.Count; i++) { tag_sys.Players[i].Indentity = list[i]; tag_dic.Add(list[i], i); } // 레이팅 시작 foreach (var d in sys.Model.DHistory) { var a1 = HitomiLegalize.GetMetadataFromMagic(d.Item4.ToString()); var a2 = HitomiLegalize.GetMetadataFromMagic(d.Item5.ToString()); if (!a1.HasValue || !a2.HasValue) { continue; } if (a1.Value.Tags == null || a2.Value.Tags == null) { continue; } if (a1.Value.Tags.Length == 0 || a2.Value.Tags.Length == 0) { continue; } HashSet <string> first = new HashSet <string>(); HashSet <string> second = new HashSet <string>(); foreach (var tag in a1.Value.Tags) { first.Add(HitomiIndex.Instance.index.Tags[tag]); } foreach (var tag in a2.Value.Tags) { second.Add(HitomiIndex.Instance.index.Tags[tag]); } // 태그 vs 태그가 아닌 작품 vs 작품의 레이팅이므로 // 1:1 엘로레이팅이아닌 다 vs 다 레이팅 방법을 사용함 double r1 = 0.0; double r2 = 0.0; // 먼저 작품에 포함된 태그들 레이팅의 평균을 가져온다. a1.Value.Tags.ToList().ForEach(x => r1 += tag_sys.Players[tag_dic[HitomiIndex.Instance.index.Tags[x]]].Rating); a2.Value.Tags.ToList().ForEach(x => r2 += tag_sys.Players[tag_dic[HitomiIndex.Instance.index.Tags[x]]].Rating); r1 /= a1.Value.Tags.Length; r2 /= a2.Value.Tags.Length; // 아래 레이팅 결과는 극단적인 경우로 적중확률이 0에 가까움 if (r1 < 0.5) { r1 = 1500.0; } if (r2 < 0.5) { r2 = 1500.0; } // 두 작품의 평균레이팅으로 작품 승률을 계산한다. double e1 = 1 / (1 + Math.Pow(10, (r2 - r1) / 400)); double e2 = 1 / (1 + Math.Pow(10, (r1 - r2) / 400)); // 1일 경우 a1의 승리, 0일 경우 무승부 if (d.Item3 == 1) { // a1이 승리하였으므로 Win을 갱신한다. foreach (var tag in a1.Value.Tags) { if (!second.Contains(HitomiIndex.Instance.index.Tags[tag])) { // 아래 두 과정이 의미하는 바는 다음과 같다. // 리그 오브 레전드의 5vs5 실버 랭크게임을 생각해보자. // 또한, 각 팀을 A팀, B팀이라고 하자. // // 리그 오브 레전드의 매칭 시스템 특성상 A팀과 B팀의 평균 승률은 같을 것이다. 하지만, A팀에는 // 다이아 실력을 가진 대리유저가 있을 수 있으며, 있다고 가정하자. 이때 대리유저의 레이팅은 분명히 // 낮을 것이지만, 우리의 태그 레이팅에는 이러한 인자가 존재할 수 없으니, 각 유저의 실제 실력을 // 해당 유저의 레이팅으로 삼는게, 우리의 레이팅시스템과 비교하기에 적절하다. 태그 레이팅에 이러한 // 인자가 존재할 수 없는 이유는 작품 대 작품 비교 기반의 레이팅 시스템에서는 각 태그의 레이팅을 // 의도적으로 변경하기가 불가능하기 때문이다. 또한, A팀에는 의도적으로 아래 랭크로 내려가려는 // 패작유저가 있다고 가정하자. 그렇다면 두 팀의 평균 승률은 같아 질 수 있다. // // 이때 A팀이 승리하게 된다면, 대리 유저의 승리 기여도가 다른 팀원들에 비해서 매우 높을 것이다. // 하지만, 두 팀의 평균 레이팅에 비해 대리 유저의 레이팅이 높으므로, 대리유저가 레이팅 점수를 많이 // 받는 것은 정당하지 않다. 따라서 우리의 레이팅 시스템은 두 작품의 평균 레이팅과 작품에 포함된 // 태그에 상대적 레이팅을 기반으로 공식을 만들었다. // // 위 예제에서 A팀의 승률이 50%이고, A팀에 대한 대리유저의 상대적 승률이 90%라고 한다면, // 대리유저의 최종 승률은 70%로 계산된다. 90%의 승률보다 낮게 계산된 이유는 팀 기여도가 // 그만큼 커졌기 때문이다. 즉, 대리유저는 승리확률이 70%인 게임에 참여하게 된것이다. // // 우리의 레이팅 시스템에선 항상 두 팀의 승률이 동일한 것은 아니다. 극단적인 경우도 있다. // A팀의 승률이 80%이고, A팀에 대한 대리유저의 상대적 승률이 90%라고 한다면, 대리유저의 최종 // 승률은 85%로 계산된다. 즉, 대리유저는 승리확률이 85%인 게임에 참여하게 된것이며, 이 게임에서 // 패배할 시엔 많은 레이팅 점수를 잃을 테지만, 승리한다해도 적은 레이팅점수만 받게된다. // 이와는 다르게 A팀에 대한 패작유저의 상대적 승률이 10%라고 한다면, 패작유저의 최종 승률은 // 45%로 계산된다. 즉, 이 게임에서 지게된다면 패작유저는 적지 않은 레이팅 점수를 잃게 될 것이다. // 작품의 평균레이팅에 대한 태그의 승률을 계산한다. double ew = 1 / (1 + Math.Pow(10, (r1 - tag_sys.Players[tag_dic[HitomiIndex.Instance.index.Tags[tag]]].R) / 400)); // a1의 예측 승률과 ew 승률의 평균을 업데이트한다. tag_sys.UpdateWin(tag_dic[HitomiIndex.Instance.index.Tags[tag]], (ew + e1) / 2); } } foreach (var tag in a2.Value.Tags) { if (!first.Contains(HitomiIndex.Instance.index.Tags[tag])) { double ew = 1 / (1 + Math.Pow(10, (r2 - tag_sys.Players[tag_dic[HitomiIndex.Instance.index.Tags[tag]]].R) / 400)); tag_sys.UpdateLose(tag_dic[HitomiIndex.Instance.index.Tags[tag]], (ew + e2) / 2); } } } else if (d.Item3 == 0) { foreach (var tag in a1.Value.Tags) { if (!second.Contains(HitomiIndex.Instance.index.Tags[tag])) { double ew = 1 / (1 + Math.Pow(10, (r1 - tag_sys.Players[tag_dic[HitomiIndex.Instance.index.Tags[tag]]].R) / 400)); tag_sys.UpdateDraw(tag_dic[HitomiIndex.Instance.index.Tags[tag]], (ew + e1) / 2); } } foreach (var tag in a2.Value.Tags) { if (!first.Contains(HitomiIndex.Instance.index.Tags[tag])) { double ew = 1 / (1 + Math.Pow(10, (r2 - tag_sys.Players[tag_dic[HitomiIndex.Instance.index.Tags[tag]]].R) / 400)); tag_sys.UpdateDraw(tag_dic[HitomiIndex.Instance.index.Tags[tag]], (ew + e2) / 2); } } } } var result = tag_sys.Players.ToList(); result.Sort((x, y) => y.R.CompareTo(x.R)); return(result); }
private async void UserControl_Drop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); if (files.Length == 0) { return; } var parent = System.IO.Path.GetFileName(System.IO.Path.GetDirectoryName(files[0])); //files.ToList().ForEach(x => LoadFolder(x)); // 폴더인지 확인 // 1. 작가/그룹 폴더인가? // 2. 작품 폴더인가? // 파일 확인 // 1. Zip 파일인가? // 2. 아니면 오류 var artists = new List <Tuple <string, string> >(); var groups = new List <Tuple <string, string> >(); var articles = new List <Tuple <string, string> >(); var err = new List <Tuple <string, string> >(); var regexs = @"^\[(\d+)\], ^\[.*?\((\d+)\).*?\], \((\d+)\)$".Split(',').ToList().Select(x => new Regex(x.Trim())).ToList(); foreach (var file in files) { if (Directory.Exists(file)) { var name = System.IO.Path.GetFileName(file); if (Regex.IsMatch(name, @"^[\w\s\-\.]+$")) { name = name.Replace(".", "").ToLower(); if (HitomiIndex.Instance.tagdata_collection.artist.Any(x => { if (x.Tag.Replace(".", "") == name) { name = x.Tag; return(true); } return(false); })) { artists.Add(new Tuple <string, string>(name, file)); } else if (HitomiIndex.Instance.tagdata_collection.group.Any(x => { if (x.Tag.Replace(".", "") == name) { name = x.Tag; return(true); } return(false); })) { groups.Add(new Tuple <string, string>(name, file)); } else { err.Add(new Tuple <string, string>("디렉토리 - " + name, file)); } } else { string match = ""; if (regexs.Any(x => { if (!x.Match(System.IO.Path.GetFileNameWithoutExtension(name)).Success) { return(false); } match = x.Match(System.IO.Path.GetFileNameWithoutExtension(name)).Groups[1].Value; return(true); })) { if (HitomiLegalize.GetMetadataFromMagic(match).HasValue) { articles.Add(new Tuple <string, string>(match, file)); } else { err.Add(new Tuple <string, string>("디렉토리 - " + name, file)); } } else { err.Add(new Tuple <string, string>("디렉토리 - " + name, file)); } } } else if (System.IO.Path.GetExtension(file).ToLower() == ".zip") { var name = System.IO.Path.GetFileName(file); string match = ""; if (regexs.Any(x => { if (!x.Match(System.IO.Path.GetFileNameWithoutExtension(name)).Success) { return(false); } match = x.Match(System.IO.Path.GetFileNameWithoutExtension(name)).Groups[1].Value; return(true); })) { if (HitomiLegalize.GetMetadataFromMagic(match).HasValue) { articles.Add(new Tuple <string, string>(match, file)); } else { err.Add(new Tuple <string, string>("파일 - " + name, file)); } } else { err.Add(new Tuple <string, string>("파일 - " + name, file)); } } else { err.Add(new Tuple <string, string>("알 수 없는 확장자 - " + System.IO.Path.GetFileName(file), file)); } } var builder = new StringBuilder(); if (artists.Count > 0) { builder.Append("작가\r\n" + string.Join(", ", artists.Select(x => x.Item1)) + "\r\n\r\n"); } if (groups.Count > 0) { builder.Append("그룹\r\n" + string.Join(", ", groups.Select(x => x.Item1)) + "\r\n\r\n"); } if (articles.Count > 0) { builder.Append("작품\r\n" + string.Join(", ", articles.Select(x => x.Item1)) + "\r\n\r\n"); } if (err.Count > 0) { builder.Append("출처를 찾을 수 없는 항목\r\n" + string.Join("\r\n", err.Select(x => x.Item1))); } var dialog = new BookmarkAdd(builder.ToString()); if ((bool)(await DialogHost.Show(dialog, "BookmarkDialog"))) { foreach (var artist in artists) { BookmarkModelManager.Instance.Model.artists.Add(new Tuple <string, BookmarkItemModel>(classify_name, new BookmarkItemModel { content = artist.Item1, path = artist.Item2, stamp = DateTime.Now })); } foreach (var group in groups) { BookmarkModelManager.Instance.Model.groups.Add(new Tuple <string, BookmarkItemModel>(classify_name, new BookmarkItemModel { content = group.Item1, path = group.Item2, stamp = DateTime.Now })); } foreach (var article in articles) { BookmarkModelManager.Instance.Model.articles.Add(new Tuple <string, BookmarkItemModel>(classify_name, new BookmarkItemModel { content = article.Item1, path = article.Item2, stamp = DateTime.Now, etc = parent })); } BookmarkModelManager.Instance.Save(); await Application.Current.Dispatcher.BeginInvoke(new Action( delegate { load(); })); } } }
/// <summary> /// E Hentai Magic Number를 이용해 작품 정보를 가져옵니다. /// </summary> /// <param name="magic_number"></param> /// <returns></returns> public static HArticleModel?GetArticleData(int magic_number) { string magic = magic_number.ToString(); Monitor.Instance.Push($"[HCommander] [1] {magic}"); // // 1. 히토미 데이터로 찾기 // var search_hitomi = HitomiLegalize.GetMetadataFromMagic(magic); if (search_hitomi.HasValue) { // 히토미 데이터가 존재한다면 데이터의 유효 여부를 판단한다. try { var url = $"https://hitomi.la/galleries/{magic}.html"; var request = WebRequest.Create(url); using (var response = request.GetResponse()) { using (var responseStream = response.GetResponseStream()) { // 최종 승인 return(ConvertTo(search_hitomi.Value, url, magic)); } } } catch { } } Monitor.Instance.Push($"[HCommander] [2] {magic}"); // // 2. Hiyobi를 이용해 탐색한다 // if (search_hitomi.HasValue && search_hitomi.Value.Language == "korean") { try { var html = NetCommon.DownloadString(HiyobiCommon.GetInfoAddress(magic)); var article = HiyobiParser.ParseGalleryConents(html); return(ConvertTo(article, HiyobiCommon.GetInfoAddress(magic), magic)); } catch { } } Monitor.Instance.Push($"[HCommander] [3] {magic}"); // // 9.3/4 샰쮘뽣?뛤3쇼뵀?gVA덲탭k융뷠킢쪳1SPS?XF퍵8C샜쁬 // var f = ExHentaiData.data.AsParallel().Where(x => (x >> 40) == magic_number).ToList(); if (f.Count > 0) { try { //var url = $"https://e-hentai.org/g/{magic}/{f[0] ^ 1L * magic_number << 40:x}/"; //var html2 = NetCommon.DownloadExHentaiString(url); //var article = EHentaiParser.ParseArticleData(html2); //return ConvertTo(article, url, magic); throw new Exception(); } catch { var url = $"https://exhentai.org/g/{magic}/{f[0] ^ 1L * magic_number << 40:x}/"; var html2 = NetCommon.DownloadExHentaiString(url); var article = ExHentaiParser.ParseArticleData(html2); return(ConvertTo(article, url, magic)); } } // // 3. GalleryBlock을 이용해 제목을 가져온다. // string title = ""; try { var html = NetCommon.DownloadString($"{HitomiCommon.HitomiGalleryBlock}{magic}.html"); var article = HitomiParser.ParseGalleryBlock(html); title = article.Title; } catch { Monitor.Instance.Push($"[HCommander] [0] {magic}"); return(null); } // // 4. 'Show Expunged Galleries' 옵션을 이용해 Ex-Hentai에서 검색한 후 정보를 가져온다. // try { var html = NetCommon.DownloadExHentaiString($"https://exhentai.org/?f_doujinshi=1&f_manga=1&f_artistcg=1&f_gamecg=1&f_western=1&f_non-h=1&f_imageset=1&f_cosplay=1&f_asianporn=1&f_misc=1&f_search={title}&page=0&f_apply=Apply+Filter&advsearch=1&f_sname=on&f_stags=on&f_sh=on&f_srdd=2"); if (html.Contains($"/{magic}/")) { var url = Regex.Match(html, $"(https://exhentai.org/g/{magic}/\\w+/)").Value; var html2 = NetCommon.DownloadExHentaiString(url); var article = ExHentaiParser.ParseArticleData(html2); return(ConvertTo(article, url, magic)); } } catch { } Monitor.Instance.Push($"[HCommander] [0] {magic}"); return(null); }
public static void ProcessHiyobi(string url, bool unstable = false) { Task.Run(() => { if (url.StartsWith("https://hiyobi.me/manga/info/")) { MainWindow.Instance.Fade_MiddlePopup(true, "접속중..."); var html = NetCommon.DownloadString(url); var articles = HiyobiParser.ParseNonHArticles(html); var title = HiyobiParser.ParseNonHTitle(html); MainWindow.Instance.ModifyText_MiddlePopup($"가져오는중...[0/{articles.Count}]"); for (int i = 0; i < articles.Count; i++) { articles[i].ImagesLink = HitomiParser.GetImageLink(NetCommon.DownloadString(HiyobiCommon.GetDownloadMangaImageAddress(articles[i].Magic))); MainWindow.Instance.ModifyText_MiddlePopup($"가져오는중...[{i + 1}/{articles.Count}]"); } int count = 0; foreach (var article in articles) { string dir = Path.Combine(Path.Combine(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "hiyobi"), DeleteInvalid(title)), DeleteInvalid(article.Title)); Directory.CreateDirectory(dir); var se = Koromo_Copy.Interface.SemaphoreExtends.MakeDefault(); se.Referer = url; count += article.ImagesLink.Count; DownloadSpace.Instance.RequestDownload($"hiyobi-nonh: {article.Title}", article.ImagesLink.Select(x => x.StartsWith("http://") || x.StartsWith("https://") ? x : $"https://aa.hiyobi.me/data_m/{article.Magic}/{x}").ToArray(), article.ImagesLink.Select(x => Path.Combine(dir, !x.StartsWith("http://images-blogger-opensocial.googleusercontent.com/") ? HttpUtility.UrlDecode(HttpUtility.UrlDecode(x.Split('/').Last())) : HttpUtility.UrlDecode(HttpUtility.UrlDecode(HttpUtility.ParseQueryString(new Uri(x).Query).Get("url").Split('/').Last())))).ToArray(), se, dir + '\\', null ); } MainWindow.Instance.FadeOut_MiddlePopup($"{count}개({articles.Count} 작품) 항목 다운로드 시작..."); } else if (url.StartsWith("https://hiyobi.me/info/")) { if (unstable) { MainWindow.Instance.Fade_MiddlePopup(true, $"불안정한 작업 진행중...[{unstable_request}개]"); } else { MainWindow.Instance.Fade_MiddlePopup(true, "접속중..."); } var wc = NetCommon.GetDefaultClient(); wc.Headers.Add(System.Net.HttpRequestHeader.Referer, "https://hiyobi.me/reader/" + url.Split('/').Last()); var imagelink = HitomiParser.GetImageLink(wc.DownloadString(HiyobiCommon.GetDownloadImageAddress(url.Split('/').Last()))); var article = HitomiLegalize.MetadataToArticle(HitomiLegalize.GetMetadataFromMagic(url.Split('/').Last()).Value); //HiyobiParser.ParseGalleryConents(NetCommon.DownloadString(url)); string dir = HitomiCommon.MakeDownloadDirectory(article); var se = Koromo_Copy.Interface.SemaphoreExtends.Default; se.Referer = "https://hiyobi.me/reader/" + url.Split('/').Last(); article.ImagesLink = imagelink; Directory.CreateDirectory(dir); DownloadSpace.Instance.RequestDownload(article.Title, imagelink.Select(y => $"https://hiyobi.me/data/{article.Magic}/{y}").ToArray(), imagelink.Select(y => Path.Combine(dir, y)).ToArray(), se, dir, article); Directory.CreateDirectory(dir); if (unstable) { Interlocked.Decrement(ref unstable_request); } if (unstable && unstable_request != 0) { MainWindow.Instance.Fade_MiddlePopup(true, $"불안정한 작업 진행중...[{unstable_request}개]"); } else { MainWindow.Instance.FadeOut_MiddlePopup($"{imagelink.Count}개 이미지 다운로드 시작..."); } } }); }
private async void Button_Click(object sender, RoutedEventArgs e) { if (!Addr.Text.EndsWith(".txt")) { PB.IsIndeterminate = true; FileIndexor fi = new FileIndexor(); await fi.ListingDirectoryAsync(Addr.Text); PB.IsIndeterminate = false; file_list = new List <string>(); fi.Enumerate((string path, List <FileInfo> files) => { foreach (var iz in files) { if (Path.GetExtension(iz.Name) == ".zip") { file_list.Add(iz.FullName); } } }); append(file_list.Count.ToString("#,#") + "개의 Zip 파일이 검색됨"); } else { var lls = File.ReadAllLines(Addr.Text); var pp = new List <Tuple <string, string> >(); var rx = new Regex(@"^\[(\d+)\]"); foreach (var article in lls) { var f = Path.GetFileNameWithoutExtension(article); if (rx.Match(Path.GetFileNameWithoutExtension(article)).Success) { var id = rx.Match(System.IO.Path.GetFileNameWithoutExtension(article)).Groups[1].Value; var artist = Path.GetFileName(Path.GetDirectoryName(article)); pp.Add(new Tuple <string, string>(id, artist)); } else { append("[NO MATCH] " + article); } } var articles = new List <HitomiArticle>(); foreach (var p in pp) { var aaa = HitomiLegalize.GetMetadataFromMagic(p.Item1); if (!aaa.HasValue) { append("[NOT FOUND] " + p.Item1); continue; } var xxx = HitomiLegalize.MetadataToArticle(aaa.Value); xxx.Artists = new string[] { p.Item2 }; articles.Add(xxx); } await Task.Run(() => { int cnt = 0; foreach (var at in articles) { try { var url = HitomiCommon.GetImagesLinkAddress(at.Magic); var imgs = HitomiParser.GetImageLink(NetCommon.DownloadString(url)); at.ImagesLink = imgs; } catch { append("[FAIL DOWNLOAD] " + at.Magic); } cnt++; Application.Current.Dispatcher.BeginInvoke(new Action( delegate { PB.Value = cnt / (double)articles.Count * 100; })); } int count = 0; foreach (var ha in articles) { if (ha.ImagesLink == null) { continue; } var prefix = HitomiCommon.MakeDownloadDirectory(ha); Directory.CreateDirectory(prefix); DownloadSpace.Instance.RequestDownload(ha.Title, ha.ImagesLink.Select(y => HitomiCommon.GetDownloadImageAddress(ha.Magic, y, ha.HasWebp[y], ha.HasWebp[y] || ha.Hashs[y].Length > 3 ? ha.Hashs[y] : "")).ToArray(), ha.ImagesLink.Select(y => Path.Combine(prefix, y)).ToArray(), Koromo_Copy.Interface.SemaphoreExtends.Default, prefix, ha); count++; } Application.Current.Dispatcher.BeginInvoke(new Action( delegate { if (count > 0) { MainWindow.Instance.FadeOut_MiddlePopup($"{count}{FindResource("msg_download_start")}"); } MainWindow.Instance.Activate(); MainWindow.Instance.FocusDownload(); })); }); } }