public SubdirSelector(DownloadDBModel dbm)
        {
            InitializeComponent();

            Titles = new List <string>();

            var comp    = new Strings.NaturalComparer();
            var folders = Directory.GetDirectories(dbm.Directory).ToList();

            Title     = dbm.ShortInfo;
            superpath = dbm.Directory;
            folders.Sort((x, y) => comp.Compare(x, y));
            folders.ForEach(x => Titles.Add(Path.GetFileName(x)));

            BindingContext = this;
        }
        public DownloadElement(DownloadDBModel dbm)
        {
            InitializeComponent();

            Commands.SetTap(Body, new Command(async() =>
            {
                await(Application.Current.MainPage as MainPage).NaviInstance.PushAsync(new DownloadInfoPage(dbm));
            }));

            Spinner.IsVisible = false;

            if (!string.IsNullOrWhiteSpace(dbm.Url) && (dbm.Url.StartsWith("http://") || dbm.Url.StartsWith("https://")))
            {
                SetupFavicon(dbm.Url);
            }

            if (!string.IsNullOrWhiteSpace(dbm.ShortInfo))
            {
                Info.Text = dbm.ShortInfo;
            }
            else
            {
                Info.Text = dbm.Url;
            }

            ProgressText.IsVisible = true;
            ProgressText.Text      = "날짜";

            ProgressProgressText.IsVisible = true;
            ProgressProgressText.Text      = dbm.StartsTime.ToString();

            switch (dbm.State)
            {
            case DownloadDBState.Aborted:
                Status.Text = "다운로드 취소됨";
                break;

            case DownloadDBState.Downloaded:
                Status.Text = "다운로드됨";
                break;

            case DownloadDBState.ErrorOccured:
                Status.Text = "다운로드 중 오류발생";
                break;

            case DownloadDBState.Downloading:
                Status.Text = "다운로드 중단됨";
                break;

            case DownloadDBState.Forbidden:
                Status.Text = "다운로드 금지됨";
                break;
            }

            if (!string.IsNullOrWhiteSpace(dbm.ThumbnailCahce))
            {
                Task.Run(() =>
                {
                    Thread.Sleep(500);
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        var thumbnail           = dbm.ThumbnailCahce;
                        Thumbnail.HeightRequest = Height - 8;
                        Thumbnail.IsVisible     = true;
                        Thumbnail.Source        = thumbnail;
                    });
                });
            }
        }
        public DownloadElement(string url)
        {
            InitializeComponent();

            DownloadInfo.DownloadStarts = DateTime.Now;
            Info.Text = url;

            int hitomi_id = 0;

            if (int.TryParse(url, out hitomi_id))
            {
                url = "https://hitomi.la/galleries/" + url + ".html";
            }

            var dbm = new DownloadDBModel();

            dbm.Url        = url;
            dbm.StartsTime = DownloadInfo.DownloadStarts;
            dbm.State      = DownloadDBState.Downloading;
            DownloadDBManager.Instance.Add(dbm);

            Commands.SetTap(Body, new Command(async() =>
            {
                await(Application.Current.MainPage as MainPage).NaviInstance.PushAsync(new DownloadInfoPage(dbm));
            }));

            if (!url.StartsWith("http://") && !url.StartsWith("https://"))
            {
                Status.Text       = "옳바른 URL이 아닙니다!";
                Status.TextColor  = Color.Red;
                Spinner.IsVisible = false;
                dbm.State         = DownloadDBState.ErrorOccured;
                DownloadDBManager.Instance.Add(dbm);
                return;
            }

            SetupFavicon(url);

            Task.Run(() =>
            {
                var extractor = ExtractorManager.Instance.GetExtractor(url);
                if (extractor == null)
                {
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Status.Text       = "적절한 다운로드작업을 찾을 수 없습니다!";
                        Status.TextColor  = Color.Red;
                        Spinner.IsVisible = false;
                    });
                    dbm.State = DownloadDBState.ErrorOccured;
                    DownloadDBManager.Instance.Update(dbm);
                    return;
                }

                if (extractor.IsForbidden)
                {
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Status.Text       = "정책상 금지된 작업입니다.";
                        Status.TextColor  = Color.Red;
                        Spinner.IsVisible = false;
                    });
                    dbm.State = DownloadDBState.Forbidden;
                    DownloadDBManager.Instance.Update(dbm);
                    return;
                }

                WAIT_ANOTHER_TASKS:

                if (DownloadAvailable == 4)
                {
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Status.Text = $"다른 작업이 끝나길 기다리는 중 입니다...";
                    });
                    while (DownloadAvailable >= 4)
                    {
                        Thread.Sleep(1000);
                    }
                }

                if (Interlocked.Increment(ref DownloadAvailable) > 4)
                {
                    goto WAIT_ANOTHER_TASKS;
                }

                Device.BeginInvokeOnMainThread(() =>
                {
                    Info.Text     = extractor.GetType().Name.Replace("Extractor", "") + " (" + "/" + string.Join("/", url.Split('/').Skip(3)) + ")";
                    dbm.ShortInfo = Info.Text;
                    DownloadDBManager.Instance.Update(dbm);
                    Status.Text = "다운로드 정보를 추출 중 입니다...";
                });

                var option = extractor.RecommendOption(url);

                long extracting_progress_max     = 0;
                long extracting_cumulative_count = 0;

                option.ProgressMax = (count) =>
                {
                    extracting_progress_max = count;
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        ProgressProgressText.IsVisible = false;
                        Progress.IsVisible             = true;
                    });
                };

                option.PostStatus = (count) =>
                {
                    var val = Interlocked.Add(ref extracting_cumulative_count, count);
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        if (extracting_progress_max != 0)
                        {
                            Progress.Progress = val / (double)extracting_progress_max;
                            Status.Text       = $"추출중...[{val}/{extracting_progress_max}]";
                        }
                        else
                        {
                            Status.Text = $"추출중...[{val}개 항목 추출됨]";
                        }
                    });
                };

                option.SimpleInfoCallback = (info) =>
                {
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Info.Text     = $"{info}";
                        dbm.ShortInfo = info;
                        DownloadDBManager.Instance.Update(dbm);
                    });
                };

                option.ThumbnailCallback = (thumbnail) =>
                {
                    Task.Run(async() =>
                    {
                        var ttask      = NetTask.MakeDefault(thumbnail.Url);
                        ttask.Priority = new NetPriority {
                            Type = NetPriorityType.Trivial
                        };
                        ttask.Filename     = Path.Combine(AppProvider.ApplicationPath, (url + "*thumbnail" + dbm.Id).GetHashMD5() + Path.GetExtension(thumbnail.Filename));
                        ttask.Headers      = thumbnail.Headers;
                        ttask.Referer      = thumbnail.Referer;
                        ttask.Cookie       = thumbnail.Cookie;
                        ttask.Accept       = thumbnail.Accept;
                        ttask.UserAgent    = thumbnail.UserAgent;
                        dbm.ThumbnailCahce = ttask.Filename;
                        await NetTools.DownloadFileAsync(ttask);
                        DownloadDBManager.Instance.Update(dbm);

                        Device.BeginInvokeOnMainThread(() =>
                        {
                            Thumbnail.HeightRequest = Height - 8;
                            Thumbnail.IsVisible     = true;
                            Thumbnail.Source        = ttask.Filename;
                        });
                    });
                };

                (List <NetTask>, ExtractedInfo)tasks;

                try
                {
                    tasks = extractor.Extract(url, option);
                }
                catch (Exception e)
                {
                    Logs.Instance.PushError(e.Message);
                    Logs.Instance.PushError(e.StackTrace);
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        ProgressProgressText.IsVisible = true;
                        ProgressProgressText.Text      = "";
                        ProgressText.Text  = "";
                        Progress.IsVisible = false;
                        Spinner.IsVisible  = false;
                        Status.Text        = "추출 작업 중 오류가 발생했습니다 :(\n" + e.Message;
                        Status.TextColor   = Color.Red;
                    });
                    Interlocked.Decrement(ref DownloadAvailable);
                    dbm.State = DownloadDBState.ErrorOccured;
                    DownloadDBManager.Instance.Update(dbm);
                    return;
                }

                if (tasks.Item1 == null)
                {
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        ProgressProgressText.IsVisible = true;
                        ProgressProgressText.Text      = "";
                        ProgressText.Text  = "";
                        Progress.IsVisible = false;
                        Spinner.IsVisible  = false;
                        Status.Text        = "다운로드할 내용이 없습니다 :(";
                    });
                    Interlocked.Decrement(ref DownloadAvailable);
                    dbm.State = DownloadDBState.ErrorOccured;
                    DownloadDBManager.Instance.Update(dbm);
                    return;
                }

                if (tasks.Item2 != null)
                {
                    ExtractedInfo = tasks.Item2;
                    CacheManager.Instance.Append(url + "*info" + dbm.Id, ExtractedInfo);
                    dbm.InfoCache = url + "*info" + dbm.Id;
                    DownloadDBManager.Instance.Update(dbm);
                }

                var format = extractor.RecommendFormat(option);

                Device.BeginInvokeOnMainThread(() =>
                {
                    Spinner.IsVisible = false;
                    Status.Text       = "다운로드 중...";
                    ProgressProgressText.IsVisible = false;
                    Progress.IsVisible             = true;
                });

                int download_count  = 0;
                long download_bytes = 0;
                long download_1s    = 0;
                int task_count      = AppProvider.Scheduler.LatestPriority != null ?
                                      AppProvider.Scheduler.LatestPriority.TaskPriority : 0;
                int post_process_count    = 0;
                int post_process_progress = 0;
                bool canceled             = false;

                if (tasks.Item1.Count > 0)
                {
                    var hash_set = new HashSet <string>();
                    tasks.Item1.ForEach(x => hash_set.Add(Path.GetDirectoryName(Path.Combine("/", x.Format.Formatting(format)))));
                    if (hash_set.Count == 1)
                    {
                        dbm.Directory = Path.GetDirectoryName(Path.Combine(Settings.Instance.Model.SuperPath, tasks.Item1[0].Format.Formatting(format)));
                    }
                    else
                    {
                        dbm.Directory = Path.GetDirectoryName(Path.GetDirectoryName(Path.Combine(Settings.Instance.Model.SuperPath, tasks.Item1[0].Format.Formatting(format))));
                    }
                    dbm.CountOfFiles = tasks.Item1.Count;
                    DownloadDBManager.Instance.Update(dbm);
                }

                tasks.Item1.ForEach(task => {
                    task.Priority.TaskPriority = task_count++;
                    task.Filename = Path.Combine(Settings.Instance.Model.SuperPath, task.Format.Formatting(format));
                    if (!Directory.Exists(Path.GetDirectoryName(task.Filename)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(task.Filename));
                    }
                    task.DownloadCallback = (sz) =>
                    {
                        Interlocked.Add(ref download_1s, sz);
                        Interlocked.Add(ref download_bytes, sz);
                    };
                    task.CompleteCallback = () =>
                    {
                        var cur = Interlocked.Increment(ref download_count);
                        Device.BeginInvokeOnMainThread(() =>
                        {
                            Progress.Progress = cur / (double)tasks.Item1.Count;
                        });
                    };
                    task.CancleCallback = () =>
                    {
                        if (!canceled)
                        {
                            dbm.State = DownloadDBState.Aborted;
                            DownloadDBManager.Instance.Update(dbm);
                        }
                        canceled = true;
                    };
                    task.Cancel = CancelSource.Token;
                    if (task.PostProcess != null)
                    {
                        task.StartPostprocessorCallback = () =>
                        {
                            Interlocked.Increment(ref post_process_count);
                        };
                        task.PostProcess.CompletePostprocessor = (index) =>
                        {
                            Interlocked.Increment(ref post_process_progress);
                        };
                    }
                    AppProvider.Scheduler.Add(task);
                });

                while (tasks.Item1.Count != download_count && !canceled)
                {
                    Thread.Sleep(1000);
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Status.Text = $"[{download_count}/{tasks.Item1.Count}] ({convert_bytes2string(download_1s)}/S {convert_bytes2string(download_bytes)})";
                        Interlocked.Exchange(ref download_1s, 0);
                    });
                }

                Interlocked.Decrement(ref DownloadAvailable);

                dbm.State          = DownloadDBState.Downloaded;
                dbm.EndsTime       = DateTime.Now;
                dbm.SizeOfContents = download_bytes;
                DownloadDBManager.Instance.Update(dbm);

                while (post_process_progress != post_process_count && !canceled)
                {
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Status.Text       = $"후처리 작업 중...[{post_process_progress}/{post_process_count}]";
                        Progress.Progress = post_process_progress / (double)post_process_count;
                    });
                    Thread.Sleep(1000);
                }

                if (!canceled)
                {
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Status.Text = "다운로드 완료";
                        ProgressProgressText.IsVisible = true;
                        ProgressProgressText.Text      = "";
                        ProgressText.Text  = "";
                        Progress.IsVisible = false;
                        Plugin.XSnack.CrossXSnack.Current.ShowMessage(Info.Text + " 항목의 다운로드가 완료되었습니다.");
                    });
                }
                else
                {
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Status.Text = "다운로드 취소됨";
                        ProgressProgressText.IsVisible = true;
                        ProgressProgressText.Text      = "";
                        ProgressText.Text  = "";
                        Progress.IsVisible = false;
                        Plugin.XSnack.CrossXSnack.Current.ShowMessage(Info.Text + " 항목의 다운로드가 취소되었습니다.");
                    });
                }

                DownloadInfo.DownloadEnds = DateTime.Now;
            });
        }
        public DownloadInfoPage(DownloadDBModel dbm)
        {
            InitializeComponent();

            DBM = dbm;

            if (!string.IsNullOrWhiteSpace(dbm.ShortInfo))
            {
                Information.Text = dbm.ShortInfo;
            }
            else
            {
                Information.Text = dbm.Url;
            }

            var model = ExtractorManager.Instance.GetExtractor(dbm.Url);

            if (model != null)
            {
                Type.Text = model.GetType().Name.Replace("Extractor", " 추출기");
            }
            else
            {
                Type.Text      = "찾을 수 없음";
                Type.TextColor = Color.Red;
            }

            Date.Text = dbm.StartsTime.ToString();

            switch (dbm.State)
            {
            case DownloadDBState.Aborted:
                State.Text      = "다운로드 취소됨";
                State.TextColor = Color.Orange;
                break;

            case DownloadDBState.Downloaded:
                State.Text = "다운로드됨";
                break;

            case DownloadDBState.ErrorOccured:
                State.Text      = "다운로드 중 오류가 발생함";
                State.TextColor = Color.Red;
                break;

            case DownloadDBState.Forbidden:
                State.Text      = "다운로드 금지됨";
                State.TextColor = Color.Red;
                break;

            case DownloadDBState.Downloading:
                State.Text      = "다운로드 도중 중단됨";
                State.TextColor = Color.Orange;
                break;
            }

            Capacity.Text = $"{dbm.CountOfFiles}개 항목 [{DownloadElement.convert_bytes2string(dbm.SizeOfContents)}]";

            if (!string.IsNullOrWhiteSpace(dbm.Directory))
            {
                Witch.Text = dbm.Directory;
            }
            else
            {
                Witch.Text = "?";
            }

            if (!string.IsNullOrWhiteSpace(dbm.InfoCache))
            {
                if (CacheManager.Instance.Exists(dbm.InfoCache))
                {
                    var ss   = CacheManager.Instance.Find(dbm.InfoCache);
                    var info = JToken.Parse(CacheManager.Instance.Find(dbm.InfoCache))["Type"].ToObject <ExtractedInfo.ExtractedType>();
                    switch (info)
                    {
                    case ExtractedInfo.ExtractedType.WorksComic:
                        Genre.Text = "만화";
                        break;

                    case ExtractedInfo.ExtractedType.Group:
                    case ExtractedInfo.ExtractedType.UserArtist:
                        Genre.Text = "일러스트 및 사진";
                        break;

                    case ExtractedInfo.ExtractedType.Community:
                        Genre.Text = "게시글";
                        break;
                    }
                }
            }

            Thumbnail.Success += (s, e) =>
            {
                var h = e.ImageInformation.OriginalHeight;
                var w = e.ImageInformation.OriginalWidth;

                if (h < 200 && w < 200)
                {
                    ThumbnailFrame.HeightRequest = h;
                    ThumbnailFrame.WidthRequest  = w;
                }
                else
                {
                    ThumbnailFrame.HeightRequest = 300;
                    ThumbnailFrame.WidthRequest  = 300;
                }
            };

            if (!string.IsNullOrWhiteSpace(dbm.ThumbnailCahce))
            {
                Task.Run(() =>
                {
                    Thread.Sleep(100);
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        var thumbnail           = dbm.ThumbnailCahce;
                        Thumbnail.HeightRequest = Height;
                        Thumbnail.IsVisible     = true;
                        Thumbnail.Source        = thumbnail;

                        Background.HeightRequest = Height;
                        Background.WidthRequest  = Width;
                        Background.IsVisible     = true;
                        Background.Source        = thumbnail;
                    });
                });
            }
        }