diff --git a/MangaReader/Account/Grouple.cs b/MangaReader/Account/Grouple.cs index 78ae0618..b92bd1bd 100644 --- a/MangaReader/Account/Grouple.cs +++ b/MangaReader/Account/Grouple.cs @@ -80,7 +80,7 @@ public static void Login() {"j_password", SettingLogin.Password}, {"remember_me", "checked"} }; - lock (ClientLock) + using (TimedLock.Lock(ClientLock)) { try { @@ -101,7 +101,7 @@ public static void Logout() { IsLogined = false; _bookmarsk = null; - lock (ClientLock) + using (TimedLock.Lock(ClientLock)) { Page.GetPage(LogoutUri, Client); } @@ -115,7 +115,7 @@ public static List LoadBookmarks() { var bookmarks = new List(); var document = new HtmlDocument(); - lock (ClientLock) + using (TimedLock.Lock(ClientLock)) { document.LoadHtml(Page.GetPage(BookmarksUri, Client)); } diff --git a/MangaReader/Manga/Acomic/Acomics.cs b/MangaReader/Manga/Acomic/Acomics.cs index ee433cde..98deadce 100644 --- a/MangaReader/Manga/Acomic/Acomics.cs +++ b/MangaReader/Manga/Acomic/Acomics.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using MangaReader.Services; namespace MangaReader.Manga.Acomic @@ -15,39 +14,18 @@ public class Acomics : Mangas public new static Guid Type { get { return Guid.Parse("F090B9A2-1400-4F5E-B298-18CD35341C34"); } } - /// - /// Статус загрузки. - /// - public override bool IsDownloaded - { - get { return downloadedChapters != null && downloadedChapters.Any() && downloadedChapters.All(c => c.IsDownloaded); } - } - - /// - /// Процент загрузки манги. - /// - public override double Downloaded - { - get { return (downloadedChapters != null && downloadedChapters.Any()) ? (downloadedChapters.Count(ch => ch.IsDownloaded) / (double)downloadedChapters.Count) * 100.0 : 0; } - set { } - } - public override List AllowedCompressionModes { - get { return base.AllowedCompressionModes.Where(m => Equals(m, Compression.CompressionMode.Manga)).ToList(); } + get + { + return base.AllowedCompressionModes + .Where(m => this.HasVolumes && Equals(m, Compression.CompressionMode.Volume) || + this.HasChapters && Equals(m, Compression.CompressionMode.Chapter) || + Equals(m, Compression.CompressionMode.Manga)) + .ToList(); + } } - /// - /// Загружаемый список глав. - /// - private List downloadedChapters; - - /// - /// Закешированный список глав. - /// - private List allChapters; - - #endregion #region Методы @@ -63,69 +41,19 @@ public override void Refresh() else if (newName != this.ServerName) this.ServerName = newName; - - this.allChapters = Getter.GetMangaChapters(this.Uri); + Getter.UpdateContentType(this); OnPropertyChanged("IsCompleted"); } - /// - /// Скачать все главы. - /// - public override void Download(string mangaFolder = null, string volumePrefix = null, string chapterPrefix = null) + protected override void UpdateContent() { - if (!this.NeedUpdate) - return; - - this.Refresh(); - - if (mangaFolder == null) - mangaFolder = this.Folder; - - if (this.allChapters == null) - Getter.GetMangaChapters(this.Uri); - - this.downloadedChapters = this.allChapters; - if (Settings.Update) - { - this.downloadedChapters = this.downloadedChapters - .Where(ch => this.Histories.All(m => m.Uri != ch.Uri)) - .ToList(); - } - - if (!this.downloadedChapters.Any()) - return; - - Log.Add("Download start " + this.Name); + this.Pages.Clear(); + this.Chapters.Clear(); + this.Volumes.Clear(); - // Формируем путь к главе вида Папка_манги\Том_001\Глава_0001 - try - { - Parallel.ForEach(this.downloadedChapters, - ch => - { - ch.Download(mangaFolder); - this.AddHistory(ch.Uri); - this.OnPropertyChanged("Downloaded"); - OnDownloadProgressChanged(this); - }); - this.Save(); - Log.Add("Download end " + this.Name); - } - - catch (AggregateException ae) - { - foreach (var ex in ae.InnerExceptions) - Log.Exception(ex); - } - catch (Exception ex) - { - Log.Exception(ex); - } - } + Getter.UpdateContent(this); - public override string ToString() - { - return this.Name; + base.UpdateContent(); } #endregion @@ -140,10 +68,11 @@ public Acomics(Uri url) : this() { this.Uri = url; - this.ServerName = Getter.GetMangaName(url); + this.Refresh(); } - public Acomics() : base() + public Acomics() + : base() { this.CompressionMode = Compression.CompressionMode.Manga; } diff --git a/MangaReader/Manga/Acomic/Chapter.cs b/MangaReader/Manga/Acomic/Chapter.cs index d4e4b3f1..e119441e 100644 --- a/MangaReader/Manga/Acomic/Chapter.cs +++ b/MangaReader/Manga/Acomic/Chapter.cs @@ -1,98 +1,35 @@ using System; -using System.Globalization; -using System.IO; using System.Text.RegularExpressions; -using MangaReader.Services; namespace MangaReader.Manga.Acomic { /// /// Глава. /// - public class Chapter + public class Chapter : MangaReader.Manga.Chapter { - #region Свойства - /// - /// Количество перезапусков загрузки. - /// - private int restartCounter; - - /// - /// Хранилище ссылок на изображения. - /// - public Uri ImageLink; - - /// - /// Название главы. - /// - public string Name; - - /// - /// Ссылка на главу. - /// - public Uri Uri; - - /// - /// Номер главы. - /// - public int Number; - - public bool IsDownloaded = false; - - #endregion - - #region Методы + #region Конструктор /// - /// Скачать главу. + /// Глава манги. /// - /// Папка для файлов. - public void Download(string chapterFolder) + /// Ссылка на главу. + /// Описание главы. + public Chapter(Uri uri, string desc) + : this(uri) { - this.IsDownloaded = false; - - if (restartCounter > 3) - throw new Exception(string.Format("Load failed after {0} counts.", restartCounter)); - - try - { - chapterFolder = Page.MakeValidPath(chapterFolder); - if (!Directory.Exists(chapterFolder)) - Directory.CreateDirectory(chapterFolder); - - var file = Page.DownloadFile(this.ImageLink); - if (!file.Exist) - throw new Exception("Restart chapter download, downloaded file is corrupted, link = " + this.ImageLink); - - var fileName = Number.ToString(CultureInfo.InvariantCulture).PadLeft(4, '0') + "." + file.Extension; - File.WriteAllBytes(string.Concat(chapterFolder, Path.DirectorySeparatorChar, fileName), file.Body); - this.IsDownloaded = true; - } - catch (Exception ex) - { - Log.Exception(ex, this.Uri.OriginalString, this.Name); - ++restartCounter; - Download(chapterFolder); - } + this.Name = desc; } - #endregion - - #region Конструктор - /// /// Глава манги. /// /// Ссылка на главу. - /// Описание главы. - /// Ссылка на изображение. - public Chapter(Uri uri, string desc, Uri link) + public Chapter(Uri uri) + : base(uri) { this.Uri = uri; - this.Name = desc; - this.ImageLink = link; - this.restartCounter = 0; this.Number = Convert.ToInt32(Regex.Match(uri.OriginalString, @"/[-]?[0-9]+", RegexOptions.RightToLeft).Value.Remove(0, 1)); } diff --git a/MangaReader/Manga/Acomic/Getter.cs b/MangaReader/Manga/Acomic/Getter.cs index f66ef069..f9ba080b 100644 --- a/MangaReader/Manga/Acomic/Getter.cs +++ b/MangaReader/Manga/Acomic/Getter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -44,12 +45,88 @@ public static string GetMangaName(Uri uri) return WebUtility.HtmlDecode(name); } + public static void UpdateContentType(Acomics manga) + { + try + { + var document = new HtmlDocument(); + document.LoadHtml(Page.GetPage(new Uri(manga.Uri.OriginalString + @"/content"), Getter.GetAdultClient(manga.Uri))); + manga.HasVolumes = document.DocumentNode.SelectNodes("//h2[@class=\"serial-chapters-head\"]") != null; + manga.HasChapters = document.DocumentNode.SelectNodes("//div[@class=\"chapters\"]//li") != null; + } + catch (Exception){} + } + + /// + /// Получить содержание манги - тома и главы. + /// + /// Манга. + public static void UpdateContent(Acomics manga) + { + var volumes = new List(); + var chapters = new List(); + var pages = new List(); + try + { + var document = new HtmlDocument(); + document.LoadHtml(Page.GetPage(new Uri(manga.Uri.OriginalString + @"/content"), Getter.GetAdultClient(manga.Uri))); + + var volumeNodes = document.DocumentNode.SelectNodes("//h2[@class=\"serial-chapters-head\"]"); + if (volumeNodes != null) + for (int i = 0; i < volumeNodes.Count; i++) + { + var desc = volumeNodes[i].InnerText; + var newVolume = new Volume(desc, volumes.Count + 1); + var subVolumeNodes = volumeNodes[i].ParentNode.ChildNodes[i * 2 + 1].ChildNodes; + AddChapters(subVolumeNodes, newVolume.Chapters); + volumes.Add(newVolume); + } + + if (volumeNodes == null || !volumes.Any()) + { + var chapterNodes = document.DocumentNode.SelectNodes("//div[@class=\"chapters\"]//li"); + AddChapters(chapterNodes, chapters); + } + + var allPages = GetMangaPages(manga.Uri); + var innerChapters = chapters.Count == 0 ? volumes.SelectMany(v => v.Chapters).Cast().ToList() : chapters; + for (int i = 0; i < innerChapters.Count; i++) + { + var current = innerChapters[i].Number; + var next = i + 1 != innerChapters.Count ? innerChapters[i + 1].Number : int.MaxValue; + innerChapters[i].Pages.AddRange(allPages.Where(p => current <= p.Number && p.Number < next)); + innerChapters[i].Number = i + 1; + } + pages.AddRange(allPages.Except(innerChapters.SelectMany(c => c.Pages).Cast())); + } + catch (NullReferenceException ex) { Log.Exception(ex); } + + manga.HasVolumes = volumes.Any(); + manga.HasChapters = volumes.Any() || chapters.Any(); + manga.Volumes.AddRange(volumes); + manga.Chapters.AddRange(chapters); + manga.Pages.AddRange(pages); + } + + private static void AddChapters(HtmlNodeCollection collection, IList chapters) + { + if (collection == null) + return; + + foreach (var chapter in collection) + { + var desc = chapter.InnerText; + var link = new Uri(chapter.ChildNodes[0].ChildNodes[0].Attributes[0].Value); + chapters.Add(new Chapter(link, desc)); + } + } + /// - /// Получить главы манги. + /// Получить страницы манги. /// /// Ссылка на мангу. /// Словарь (ссылка, описание). - public static List GetMangaChapters(Uri uri) + private static List GetMangaPages(Uri uri) { var links = new List { }; var description = new List { }; @@ -82,7 +159,7 @@ public static List GetMangaChapters(Uri uri) catch (NullReferenceException ex) { Log.Exception(ex, "Ошибка получения списка глав.", uri.ToString()); } catch (ArgumentNullException ex) { Log.Exception(ex, "Главы не найдены.", uri.ToString()); } - return links.Select((t, i) => new Chapter(t, description[i], images[i])).ToList(); + return links.Select((t, i) => new MangaPage(t, images[i]) { Name = description[i] }).ToList(); } } } diff --git a/MangaReader/Manga/Acomic/MangaPage.cs b/MangaReader/Manga/Acomic/MangaPage.cs new file mode 100644 index 00000000..9a2e09f3 --- /dev/null +++ b/MangaReader/Manga/Acomic/MangaPage.cs @@ -0,0 +1,14 @@ +using System; +using System.Text.RegularExpressions; + +namespace MangaReader.Manga.Acomic +{ + public class MangaPage : MangaReader.Manga.MangaPage + { + public MangaPage(Uri uri, Uri imageLink) + : base(uri, imageLink) + { + this.Number = Convert.ToInt32(Regex.Match(uri.OriginalString, @"/[-]?[0-9]+", RegexOptions.RightToLeft).Value.Remove(0, 1)); + } + } +} diff --git a/MangaReader/Manga/Chapter.cs b/MangaReader/Manga/Chapter.cs new file mode 100644 index 00000000..7e2cd7d6 --- /dev/null +++ b/MangaReader/Manga/Chapter.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MangaReader.Services; + +namespace MangaReader.Manga +{ + /// + /// Глава. + /// + public class Chapter : IDownloadable + { + #region Свойства + + /// + /// Количество перезапусков загрузки. + /// + private int restartCounter; + + /// + /// Хранилище ссылок на изображения. + /// + public List Pages { get; set; } + + /// + /// Название главы. + /// + public string Name { get; set; } + + /// + /// Ссылка на главу. + /// + public Uri Uri { get; set; } + + /// + /// Номер главы. + /// + public int Number { get; set; } + + /// + /// Статус загрузки. + /// + public bool IsDownloaded + { + get { return this.Pages != null && this.Pages.Any() && this.Pages.All(v => v.IsDownloaded); } + } + + /// + /// Процент загрузки главы. + /// + public double Downloaded + { + get { return (this.Pages != null && this.Pages.Any()) ? this.Pages.Average(ch => ch.Downloaded) : 0; } + set { } + } + + #endregion + + public string Folder + { + get { return this.folderPrefix + this.Number.ToString(CultureInfo.InvariantCulture).PadLeft(4, '0'); } + private set { this.folderPrefix = value; } + } + + private string folderPrefix = Settings.ChapterPrefix; + + public event EventHandler DownloadProgressChanged; + + protected void OnDownloadProgressChanged(Mangas e) + { + var handler = DownloadProgressChanged; + if (handler != null) + handler(this, e); + } + + #region Методы + + /// + /// Скачать главу. + /// + /// Папка для файлов. + public virtual void Download(string downloadFolder) + { + if (restartCounter > 3) + throw new Exception(string.Format("Load failed after {0} counts.", restartCounter)); + + var chapterFolder = Path.Combine(downloadFolder, this.Folder); + + if (this.Pages == null || !this.Pages.Any()) + this.UpdatePages(); + + try + { + chapterFolder = Page.MakeValidPath(chapterFolder); + if (!Directory.Exists(chapterFolder)) + Directory.CreateDirectory(chapterFolder); + + Parallel.ForEach(this.Pages, page => + { + page.Download(chapterFolder); + this.OnDownloadProgressChanged(null); + }); + + } + catch (AggregateException ae) + { + foreach (var ex in ae.InnerExceptions) + Log.Exception(ex, this.Uri.ToString(), this.Name); + ++restartCounter; + Download(downloadFolder); + } + catch (Exception ex) + { + Log.Exception(ex, this.Uri.ToString(), this.Name); + ++restartCounter; + Download(downloadFolder); + } + } + + /// + /// Обновить список страниц. + /// + /// Каждая конкретная глава сама забьет коллекцию this.Pages. + protected virtual void UpdatePages() + { + if (this.Pages == null) + throw new ArgumentNullException("Pages"); + + this.Pages.ForEach(p => p.DownloadProgressChanged += (sender, args) => this.OnDownloadProgressChanged(null)); + } + + #endregion + + #region Конструктор + + public static T Create(Uri uri, string desc) where T : Chapter + { + return (T)Activator.CreateInstance(typeof(T), new object[] {uri, desc}); + } + + public static T Create(Uri uri) where T : Chapter + { + return (T)Activator.CreateInstance(typeof(T), new object[] { uri }); + } + + /// + /// Глава манги. + /// + /// Ссылка на главу. + /// Описание главы. + public Chapter(Uri uri, string desc) : this(uri) + { + this.Name = desc; + } + + /// + /// Глава манги. + /// + /// Ссылка на главу. + public Chapter(Uri uri) + { + this.Uri = uri; + this.Pages = new List(); + this.restartCounter = 0; + } + + #endregion + + } +} \ No newline at end of file diff --git a/MangaReader/Manga/Grouple/Chapter.cs b/MangaReader/Manga/Grouple/Chapter.cs index ad84e77f..ea74b869 100644 --- a/MangaReader/Manga/Grouple/Chapter.cs +++ b/MangaReader/Manga/Grouple/Chapter.cs @@ -1,135 +1,31 @@ using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; using System.Text.RegularExpressions; -using System.Threading.Tasks; -using MangaReader.Services; namespace MangaReader.Manga.Grouple { /// /// Глава. /// - public class Chapter + public class Chapter : MangaReader.Manga.Chapter { #region Свойства - /// - /// Количество перезапусков загрузки. - /// - private int restartCounter; - - /// - /// Хранилище ссылок на изображения. - /// - private List listOfImageLink; - - /// - /// Название главы. - /// - public string Name; - - /// - /// Ссылка на главу. - /// - public Uri Uri; - - /// - /// Номер главы. - /// - public int Number; - /// /// Номер тома. /// public int Volume; - /// - /// Статус загрузки. - /// - public bool IsDownloaded = false; - - /// - /// Процент загрузки главы. - /// - public int Downloaded - { - get { return (this.listOfImageLink != null && this.listOfImageLink.Any()) ? _downloaded * 100 / this.listOfImageLink.Count : 0; } - } - - private int _downloaded; - #endregion - public event EventHandler DownloadProgressChanged; - - protected virtual void OnDownloadProgressChanged(EventArgs e) - { - var handler = DownloadProgressChanged; - if (handler != null) - handler(this, e); - } - #region Методы - /// - /// Скачать главу. - /// - /// Папка для файлов. - public void Download(string chapterFolder) - { - this.IsDownloaded = false; - if (restartCounter > 3) - throw new Exception(string.Format("Load failed after {0} counts.", restartCounter)); - - if (this.listOfImageLink == null) - this.GetAllImagesLink(); - - try - { - chapterFolder = Page.MakeValidPath(chapterFolder); - if (!Directory.Exists(chapterFolder)) - Directory.CreateDirectory(chapterFolder); - - Parallel.ForEach(this.listOfImageLink, link => - { - var file = Page.DownloadFile(link); - if (!file.Exist) - throw new Exception("Restart chapter download, downloaded file is corrupted, link = " + link); - - var index = this.listOfImageLink.FindIndex(l => l == link); - var fileName = index.ToString(CultureInfo.InvariantCulture).PadLeft(4, '0') + "." + file.Extension; - var filePath = string.Concat(chapterFolder, Path.DirectorySeparatorChar, fileName); - file.Save(filePath); - this._downloaded++; - this.DownloadProgressChanged(this, null); - }); - - this.IsDownloaded = true; - } - catch (AggregateException ae) - { - foreach (var ex in ae.InnerExceptions) - Log.Exception(ex, this.Uri.ToString(), this.Name); - ++restartCounter; - Download(chapterFolder); - } - catch (Exception ex) - { - Log.Exception(ex, this.Uri.ToString(), this.Name); - ++restartCounter; - Download(chapterFolder); - } - } - /// /// Заполнить хранилище ссылок. /// - private void GetAllImagesLink() + protected override void UpdatePages() { - this.listOfImageLink = Getter.GetImagesLink(this.Uri); + this.Pages = Getter.GetImagesLink(this.Uri); + base.UpdatePages(); } #endregion @@ -142,10 +38,18 @@ private void GetAllImagesLink() /// Ссылка на главу. /// Описание главы. public Chapter(Uri uri, string desc) + : this(uri) { - this.Uri = uri; this.Name = desc; - this.restartCounter = 0; + } + + /// + /// Глава манги. + /// + /// Ссылка на главу. + public Chapter(Uri uri) + : base(uri) + { this.Volume = Convert.ToInt32(Regex.Match(uri.ToString(), @"vol[-]?[0-9]+").Value.Remove(0, 3)); this.Number = Convert.ToInt32(Regex.Match(uri.ToString(), @"/[-]?[0-9]+", RegexOptions.RightToLeft).Value.Remove(0, 1)); } diff --git a/MangaReader/Manga/Grouple/Getter.cs b/MangaReader/Manga/Grouple/Getter.cs index 1e5afdd8..7f78cee5 100644 --- a/MangaReader/Manga/Grouple/Getter.cs +++ b/MangaReader/Manga/Grouple/Getter.cs @@ -79,7 +79,7 @@ public static string GetTranslateStatus(string mangaMainPage) { var document = new HtmlDocument(); document.LoadHtml(mangaMainPage); - var nodes = document.DocumentNode.SelectNodes("//div[@class=\"subject-meta\"]//p"); + var nodes = document.DocumentNode.SelectNodes("//div[@class=\"subject-meta col-sm-7\"]//p"); if (nodes != null) status = nodes.Aggregate(status, (current, node) => current + Regex.Replace(node.InnerText.Trim(), @"\s+", " ").Replace("\n", "") + Environment.NewLine); @@ -136,9 +136,9 @@ public static Dictionary GetLinksOfMangaChapters(string mangaMainPa /// /// Ссылка на главу. /// Список ссылок на изображения главы. - public static List GetImagesLink(Uri uri) + public static List GetImagesLink(Uri uri) { - var chapterLinksList = new List(); + var chapterLinksList = new List(); var document = new HtmlDocument(); document.LoadHtml(Page.GetPage(uri)); @@ -156,8 +156,10 @@ public static List GetImagesLink(Uri uri) .OfType() .Select(m => m.Groups[1].Value) .Select(s => (!Uri.IsWellFormedUriString(s, UriKind.Absolute)) ? (@"http://" + uri.Host + s) : s) - .Select(s => new Uri(s)) + .Select(s => new MangaPage(uri, new Uri(s))) .ToList(); + var i = 0; + chapterLinksList.ForEach(ch => ch.Number = i++); return chapterLinksList; } } diff --git a/MangaReader/Manga/Grouple/Readmanga.cs b/MangaReader/Manga/Grouple/Readmanga.cs index 44b91ded..663ab524 100644 --- a/MangaReader/Manga/Grouple/Readmanga.cs +++ b/MangaReader/Manga/Grouple/Readmanga.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Linq; using System.Text.RegularExpressions; -using System.Threading.Tasks; using MangaReader.Properties; using MangaReader.Services; @@ -33,43 +30,28 @@ public override bool IsCompleted } } - /// - /// Статус загрузки. - /// - public override bool IsDownloaded + public override List AllowedCompressionModes { - get { return downloadedChapters != null && downloadedChapters.Any() && downloadedChapters.All(c => c.IsDownloaded); } + get { return base.AllowedCompressionModes.Where(m => !Equals(m, Compression.CompressionMode.Manga)).ToList(); } } /// - /// Процент загрузки манги. + /// Признак наличия глав. /// - public override double Downloaded + public override bool HasChapters { - get { return (downloadedChapters != null && downloadedChapters.Any()) ? downloadedChapters.Average(ch => ch.Downloaded) : 0; } + get { return true; } set { } } - public override List AllowedCompressionModes - { - get { return base.AllowedCompressionModes.Where(m => !Equals(m, Compression.CompressionMode.Manga)).ToList(); } - } - - /// - /// Загружаемый список глав. - /// - private List downloadedChapters; - /// - /// Закешированный список глав. + /// Признак наличия томов. /// - private List allChapters; - - /// - /// Список глав, ссылка-описание. - /// - private Dictionary listOfChapters; - + public override bool HasVolumes + { + get { return true; } + set { } + } #endregion @@ -102,93 +84,28 @@ public override void Refresh() else if (newName != this.ServerName) this.ServerName = newName; - this.listOfChapters = Getter.GetLinksOfMangaChapters(page, this.Uri); this.Status = Getter.GetTranslateStatus(page); OnPropertyChanged("IsCompleted"); } - /// - /// Получить список глав. - /// - /// Список глав. - protected internal virtual List GetAllChapters() - { - if (listOfChapters == null) - listOfChapters = Getter.GetLinksOfMangaChapters(Page.GetPage(this.Uri), this.Uri); - this.allChapters = allChapters ?? - (allChapters = listOfChapters.Select(link => new Chapter(link.Key, link.Value)).ToList()); - this.allChapters.ForEach(ch => ch.DownloadProgressChanged += (sender, args) => OnDownloadProgressChanged(this)); - return this.allChapters; - } - - /// - /// Скачать все главы. - /// - public override void Download(string mangaFolder = null, string volumePrefix = null, string chapterPrefix = null) + protected override void UpdateContent() { - if (!this.NeedUpdate) - return; - - this.Refresh(); - - if (mangaFolder == null) - mangaFolder = this.Folder; - if (volumePrefix == null) - volumePrefix = Settings.VolumePrefix; - if (chapterPrefix == null) - chapterPrefix = Settings.ChapterPrefix; - - if (this.allChapters == null) - this.GetAllChapters(); - - this.downloadedChapters = this.allChapters; - if (Settings.Update) - { - this.downloadedChapters = this.downloadedChapters - .Where(ch => this.Histories.All(m => m.Uri != ch.Uri)) - .ToList(); - } - - if (!this.downloadedChapters.Any()) - return; - - Log.Add("Download start " + this.Name); - - // Формируем путь к главе вида Папка_манги\Том_001\Глава_0001 - try - { - Parallel.ForEach(this.downloadedChapters, - ch => - { - ch.DownloadProgressChanged += (sender, args) => this.OnPropertyChanged("Downloaded"); - ch.Download(string.Concat(mangaFolder, - Path.DirectorySeparatorChar, - volumePrefix, - ch.Volume.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0'), - Path.DirectorySeparatorChar, - chapterPrefix, - ch.Number.ToString(CultureInfo.InvariantCulture).PadLeft(4, '0') - )); - this.AddHistory(ch.Uri); - }); - this.Save(); - Log.Add("Download end " + this.Name); - } - - catch (AggregateException ae) - { - foreach (var ex in ae.InnerExceptions) - Log.Exception(ex); - } - catch (Exception ex) - { - Log.Exception(ex); - } - } + this.Volumes.Clear(); + this.Chapters.Clear(); + this.Pages.Clear(); + + var rmVolumes = Getter.GetLinksOfMangaChapters(Page.GetPage(this.Uri), this.Uri) + .Select(cs => new Chapter(cs.Key, cs.Value)) + .GroupBy(c => c.Volume) + .Select(g => + { + var v = new Volume(g.Key); + v.Chapters.AddRange(g); + return v; + }); - public override string ToString() - { - return this.Name; + this.Volumes.AddRange(rmVolumes); + base.UpdateContent(); } #endregion @@ -206,7 +123,8 @@ public Readmanga(Uri url) this.Refresh(); } - public Readmanga() : base() + public Readmanga() + : base() { this.CompressionMode = Compression.CompressionMode.Volume; } diff --git a/MangaReader/Manga/IDownloadable.cs b/MangaReader/Manga/IDownloadable.cs index 242fa996..2d05a0bf 100644 --- a/MangaReader/Manga/IDownloadable.cs +++ b/MangaReader/Manga/IDownloadable.cs @@ -4,6 +4,7 @@ namespace MangaReader.Manga { public interface IDownloadable { + #region Свойства /// @@ -34,8 +35,9 @@ public interface IDownloadable /// /// Скачать все главы. /// - void Download(string mangaFolder = null, string volumePrefix = null, string chapterPrefix = null); + void Download(string folder = null); #endregion + } } diff --git a/MangaReader/Manga/MangaPage.cs b/MangaReader/Manga/MangaPage.cs new file mode 100644 index 00000000..76ca4585 --- /dev/null +++ b/MangaReader/Manga/MangaPage.cs @@ -0,0 +1,112 @@ +using System; +using System.Globalization; +using System.IO; +using MangaReader.Services; + +namespace MangaReader.Manga +{ + public class MangaPage : IDownloadable + { + + #region Свойства + + /// + /// Количество перезапусков загрузки. + /// + private int restartCounter; + + /// + /// Название страницы. + /// + public string Name { get; set; } + + /// + /// Ссылка на страницу. + /// + public Uri Uri { get; set; } + + /// + /// Ссылка на изображение. + /// + public Uri ImageLink { get; set; } + + /// + /// Номер страницы. + /// + public int Number { get; set; } + + public bool IsDownloaded { get; set; } + + public double Downloaded + { + get { return this.IsDownloaded ? 100 : 0; } + set { } + } + + public string Folder { get; private set; } + + public event EventHandler DownloadProgressChanged; + + protected void OnDownloadProgressChanged(Mangas e) + { + var handler = DownloadProgressChanged; + if (handler != null) + handler(this, e); + } + + #endregion + + #region Методы + + /// + /// Скачать страницу. + /// + /// Папка для файлов. + public void Download(string chapterFolder) + { + this.IsDownloaded = false; + + if (restartCounter > 3) + throw new Exception(string.Format("Load failed after {0} counts.", restartCounter)); + + try + { + chapterFolder = Page.MakeValidPath(chapterFolder); + if (!Directory.Exists(chapterFolder)) + Directory.CreateDirectory(chapterFolder); + + var file = Page.DownloadFile(this.ImageLink); + if (!file.Exist) + throw new Exception("Restart download, downloaded file is corrupted, link = " + this.ImageLink); + var fileName = this.Number.ToString(CultureInfo.InvariantCulture).PadLeft(4, '0') + "." + file.Extension; + file.Save(Path.Combine(chapterFolder, fileName)); + this.IsDownloaded = true; + this.OnDownloadProgressChanged(null); + } + catch (Exception ex) + { + Log.Exception(ex, this.Uri.OriginalString); + ++restartCounter; + Download(chapterFolder); + } + } + + #endregion + + #region Конструктор + + /// + /// Глава манги. + /// + /// Ссылка на страницу. + /// Ссылка на изображение. + public MangaPage(Uri uri, Uri imageLink) + { + this.Uri = uri; + this.ImageLink = imageLink; + this.restartCounter = 0; + } + + #endregion + } +} diff --git a/MangaReader/Manga/Mangas.cs b/MangaReader/Manga/Mangas.cs index 3ee0611c..73cb8bc3 100644 --- a/MangaReader/Manga/Mangas.cs +++ b/MangaReader/Manga/Mangas.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Globalization; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Xml.Serialization; using FluentNHibernate.Visitors; using MangaReader.Properties; @@ -78,7 +80,7 @@ public virtual Uri Uri { foreach (var history in this.Histories) { - var historyUri = new UriBuilder(history.Uri) {Host = value.Host}; + var historyUri = new UriBuilder(history.Uri) { Host = value.Host }; historyUri.Path = historyUri.Path.Replace(this.uri.AbsolutePath, value.AbsolutePath); history.Uri = historyUri.Uri; } @@ -112,6 +114,18 @@ public virtual bool? NeedCompress [XmlIgnore] public virtual IList Histories { get; set; } + public virtual List Volumes { get; set; } + + public virtual List ActiveVolumes { get; set; } + + public virtual List Chapters { get; set; } + + public virtual List ActiveChapters { get; set; } + + public virtual List Pages { get; set; } + + public virtual List ActivePages { get; set; } + /// /// Нужно ли обновлять мангу. /// @@ -134,7 +148,7 @@ public virtual bool NeedUpdate .Cast()); public virtual Compression.CompressionMode? CompressionMode { get; set; } - + /// /// Статус корректности манги. /// @@ -148,13 +162,55 @@ public virtual bool IsValid() /// public virtual bool IsCompleted { get; set; } + /// + /// Признак только страниц, даже без глав. + /// + public virtual bool OnlyPages { get { return !this.HasVolumes && !this.HasChapters; } } + + /// + /// Признак наличия глав. + /// + public virtual bool HasChapters { get; set; } + + /// + /// Признак наличия томов. + /// + public virtual bool HasVolumes { get; set; } + #endregion #region DownloadProgressChanged - public virtual bool IsDownloaded { get; set; } + /// + /// Статус загрузки. + /// + public virtual bool IsDownloaded + { + get + { + var isVolumesDownloaded = this.ActiveVolumes != null && this.ActiveVolumes.Any() && + this.ActiveVolumes.All(v => v.IsDownloaded); + var isChaptersDownloaded = this.ActiveChapters != null && this.ActiveChapters.Any() && this.ActiveChapters.All(v => v.IsDownloaded); + var isPagesDownloaded = this.ActivePages != null && this.ActivePages.Any() && this.ActivePages.All(v => v.IsDownloaded); + return isVolumesDownloaded || isChaptersDownloaded || isPagesDownloaded; + } + } + + /// + /// Процент загрузки манги. + /// + public virtual double Downloaded + { + get + { + var volumes = (this.ActiveVolumes != null && this.ActiveVolumes.Any()) ? this.ActiveVolumes.Average(v => v.Downloaded) : double.NaN; + var chapters = (this.ActiveChapters != null && this.ActiveChapters.Any()) ? this.ActiveChapters.Average(ch => ch.Downloaded) : double.NaN; + var pages = (this.ActivePages != null && this.ActivePages.Any()) ? this.ActivePages.Average(ch => ch.Downloaded) : 0; + return double.IsNaN(volumes) ? (double.IsNaN(chapters) ? pages : chapters) : volumes; + } + set { } + } - public virtual double Downloaded { get; set; } public virtual string Folder { @@ -177,9 +233,96 @@ protected virtual void OnDownloadProgressChanged(Mangas manga) handler(this, manga); } - public virtual void Download(string mangaFolder = null, string volumePrefix = null, string chapterPrefix = null) + /// + /// Обновить содержимое манги. + /// + /// Каждая конкретная манга сама забьет коллекцию Volumes\Chapters\Pages. + protected virtual void UpdateContent() { + if (this.Pages == null) + throw new ArgumentNullException("Pages"); + + if (this.Chapters == null) + throw new ArgumentNullException("Chapters"); + if (this.Volumes == null) + throw new ArgumentNullException("Volumes"); + + this.Pages.ForEach(p => p.DownloadProgressChanged += (sender, args) => this.OnDownloadProgressChanged(this)); + this.Chapters.ForEach(ch => ch.DownloadProgressChanged += (sender, args) => this.OnDownloadProgressChanged(this)); + this.Volumes.ForEach(v => v.DownloadProgressChanged += (sender, args) => this.OnDownloadProgressChanged(this)); + } + + public virtual void Download(string mangaFolder = null) + { + if (!this.NeedUpdate) + return; + + this.Refresh(); + + if (mangaFolder == null) + mangaFolder = this.Folder; + + this.UpdateContent(); + + this.ActiveVolumes = this.Volumes; + this.ActiveChapters = this.Chapters; + this.ActivePages = this.Pages; + if (Settings.Update) + { + this.ActivePages = this.ActivePages + .Where(p => this.Histories.All(m => m.Uri != p.Uri)) + .ToList(); + this.ActiveChapters = this.ActiveChapters + .Where(ch => this.Histories.All(m => m.Uri != ch.Uri)) + .ToList(); + this.ActiveVolumes = this.ActiveVolumes + .Where(v => v.Chapters.Any(ch => this.Histories.All(m => m.Uri != ch.Uri))) + .ToList(); + } + + if (!this.ActiveChapters.Any() && !this.ActiveVolumes.Any() && !this.ActivePages.Any()) + return; + + Log.Add("Download start " + this.Name); + + // Формируем путь к главе вида Папка_манги\Том_001\Глава_0001 + try + { + Parallel.ForEach(this.ActiveVolumes, + v => + { + v.DownloadProgressChanged += (sender, args) => this.OnPropertyChanged("Downloaded"); + v.Download(mangaFolder); + v.Chapters.ForEach(ch => this.AddHistory(ch.Uri)); + }); + Parallel.ForEach(this.ActiveChapters, + ch => + { + ch.DownloadProgressChanged += (sender, args) => this.OnPropertyChanged("Downloaded"); + ch.Download(mangaFolder); + this.AddHistory(ch.Uri); + }); + Parallel.ForEach(this.ActivePages, + p => + { + p.DownloadProgressChanged += (sender, args) => this.OnPropertyChanged("Downloaded"); + p.Download(mangaFolder); + this.AddHistory(p.Uri); + }); + this.Save(); + Log.Add("Download end " + this.Name); + } + + catch (AggregateException ae) + { + foreach (var ex in ae.InnerExceptions) + Log.Exception(ex); + } + catch (Exception ex) + { + Log.Exception(ex); + } } #endregion @@ -276,6 +419,11 @@ public override void Save(NHibernate.ISession session, NHibernate.ITransaction t base.Save(session, transaction); } + public override string ToString() + { + return this.Name; + } + /// /// Создать мангу по ссылке. /// @@ -310,6 +458,9 @@ public static Mangas Create(Uri url) public Mangas() { this.Histories = new List(); + this.Chapters = new List(); + this.Volumes = new List(); + this.Pages = new List(); } #endregion diff --git a/MangaReader/Manga/Volume.cs b/MangaReader/Manga/Volume.cs new file mode 100644 index 00000000..e2b26ea7 --- /dev/null +++ b/MangaReader/Manga/Volume.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using MangaReader.Services; + +namespace MangaReader.Manga +{ + public class Volume : IDownloadable + { + public string Name { get; set; } + + public int Number { get; set; } + + public List Chapters { get; set; } + + public List ActiveChapters { get; set; } + + /// + /// Статус загрузки. + /// + public virtual bool IsDownloaded + { + get { return this.ActiveChapters != null && this.ActiveChapters.Any() && this.ActiveChapters.All(v => v.IsDownloaded); } + } + + /// + /// Процент загрузки манги. + /// + public virtual double Downloaded + { + get { return (this.ActiveChapters != null && this.ActiveChapters.Any()) ? this.ActiveChapters.Average(ch => ch.Downloaded) : 0; } + set { } + } + + + public string Folder + { + get { return this.folderPrefix + this.Number.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0'); } + private set { this.folderPrefix = value; } + } + + private string folderPrefix = Settings.VolumePrefix; + + public event EventHandler DownloadProgressChanged; + + protected void OnDownloadProgressChanged(Mangas e) + { + var handler = DownloadProgressChanged; + if (handler != null) + handler(this, e); + } + + public void Download(string mangaFolder) + { + var volumeFolder = Path.Combine(mangaFolder, this.Folder); + + this.ActiveChapters = this.Chapters; + if (Settings.Update) + { + this.ActiveChapters = History.GetNotSavedChapters(this.ActiveChapters); + } + + this.ActiveChapters.ForEach(c => + { + c.DownloadProgressChanged += (sender, args) => this.OnDownloadProgressChanged(args); + c.Download(volumeFolder); + }); + } + + public Volume(string name, int number) + : this(number) + { + this.Name = name; + } + + public Volume(int number) + : this() + { + this.Number = number; + } + + public Volume() + { + this.Chapters = new List(); + } + } +} diff --git a/MangaReader/MangaReader.csproj b/MangaReader/MangaReader.csproj index d7047350..6bbb163e 100644 --- a/MangaReader/MangaReader.csproj +++ b/MangaReader/MangaReader.csproj @@ -42,7 +42,7 @@ prompt 4 false - true + false MixedRecommendedRules.ruleset @@ -64,6 +64,7 @@ x86 prompt MinimumRecommendedRules.ruleset + false Icons\main.ico @@ -137,12 +138,16 @@ + + MangaForm.xaml + + @@ -161,6 +166,7 @@ + Table.xaml @@ -219,7 +225,7 @@ - + Code diff --git a/MangaReader/Mapping/Converting.cs b/MangaReader/Mapping/Converting.cs index ba5152a9..79c1895e 100644 --- a/MangaReader/Mapping/Converting.cs +++ b/MangaReader/Mapping/Converting.cs @@ -1,5 +1,4 @@ -using System; -using MangaReader.Services; +using MangaReader.Services; namespace MangaReader.Mapping { @@ -10,14 +9,29 @@ internal static void ConvertAll(ConverterProcess process) { // 1.* To 1.24 ConvertBaseTo24(process); + Convert24To27(process); } - internal static void ConvertBaseTo24(ConverterProcess process) + private static void Convert24To27(ConverterProcess process) { - var thisVersion = new Version(1, 24, 0, 0); + if (process.Version.CompareTo(Settings.DatabaseVersion) > 0) + { + var readmangaHas = Environment.Session.CreateSQLQuery(@"update Mangas + set HasVolumes = 1, HasChapters = 1 + where HasVolumes is null and HasChapters is null and Type = '2c98bbf4-db46-47c4-ab0e-f207e283142d'"); + readmangaHas.UniqueResult(); + + var acomicsHas = Environment.Session.CreateSQLQuery(@"update Mangas + set HasVolumes = 0, HasChapters = 0 + where HasVolumes is null and HasChapters is null and Type = 'f090b9a2-1400-4f5e-b298-18cd35341c34'"); + acomicsHas.UniqueResult(); + } + } - if (thisVersion.CompareTo(Settings.DatabaseVersion) > 0) + internal static void ConvertBaseTo24(ConverterProcess process) + { + if (process.Version.CompareTo(Settings.DatabaseVersion) > 0) { var readmangaCompressionMode = Environment.Session.CreateSQLQuery(@"update Mangas set CompressionMode = 'Volume' @@ -28,8 +42,6 @@ internal static void ConvertBaseTo24(ConverterProcess process) set CompressionMode = 'Manga' where CompressionMode is null and Type = 'f090b9a2-1400-4f5e-b298-18cd35341c34'"); acomicsCompressionMode.UniqueResult(); - - Settings.DatabaseVersion = thisVersion; } } } diff --git a/MangaReader/Mapping/MangaMap.cs b/MangaReader/Mapping/MangaMap.cs index 212cd9a5..6d0f4fc6 100644 --- a/MangaReader/Mapping/MangaMap.cs +++ b/MangaReader/Mapping/MangaMap.cs @@ -14,6 +14,8 @@ public MangaMap() Map(x => x.LocalName).Not.LazyLoad(); Map(x => x.ServerName).Not.LazyLoad(); Map(x => x.IsNameChanged).Not.LazyLoad(); + Map(x => x.HasVolumes).Not.LazyLoad(); + Map(x => x.HasChapters).Not.LazyLoad(); Map(x => x.Uri).Not.LazyLoad(); Map(x => x.Status).Not.LazyLoad(); Map(x => x.NeedUpdate).Not.LazyLoad(); diff --git a/MangaReader/Properties/AssemblyInfo.cs b/MangaReader/Properties/AssemblyInfo.cs index aa5d5a26..7e54a8d7 100644 --- a/MangaReader/Properties/AssemblyInfo.cs +++ b/MangaReader/Properties/AssemblyInfo.cs @@ -49,4 +49,4 @@ // Можно задать все значения или принять номер построения и номер редакции по умолчанию, // используя "*", как показано ниже: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.26.*")] \ No newline at end of file +[assembly: AssemblyVersion("1.27.*")] \ No newline at end of file diff --git a/MangaReader/Services/Convert.cs b/MangaReader/Services/Convert.cs index 70d35465..24557b3e 100644 --- a/MangaReader/Services/Convert.cs +++ b/MangaReader/Services/Convert.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; namespace MangaReader.Services { @@ -15,11 +16,12 @@ internal class ConverterProcess internal double Percent = 0; internal bool IsIndeterminate = true; internal string Status = string.Empty; + internal Version Version = Settings.DatabaseVersion; } static class Converter { - public static ConverterProcess Process = new ConverterProcess(); + public static ConverterProcess Process = new ConverterProcess() { Version = new Version(1, 27, 0, 0) }; public static ConverterState State = ConverterState.None; @@ -61,6 +63,7 @@ static private void JustConvert() Library.Convert(Process); Log.Add("Convert completed."); + Settings.DatabaseVersion = Process.Version; State = ConverterState.Completed; } } diff --git a/MangaReader/Services/History.cs b/MangaReader/Services/History.cs index ddeaa223..69f24955 100644 --- a/MangaReader/Services/History.cs +++ b/MangaReader/Services/History.cs @@ -33,6 +33,30 @@ public static void AddHistory(this Mangas manga, Uri message) } } + /// + /// Вернуть главы, которые не записаны в историю. + /// + /// Главы. + /// Главы, не записанные в историю. + public static List GetNotSavedChapters(IEnumerable chapters) + { + var result = new List(); + var uris = chapters.Select(c => c.Uri).ToList(); + + using (var session = Mapping.Environment.OpenSession()) + { + result = chapters + .Where(c => uris + .Except(session.Query() + .Where(h => uris.Contains(h.Uri)) + .Select(h => h.Uri)) + .Contains(c.Uri)) + .ToList(); + } + + return result; + } + /// /// Сконвертировать в новый формат. /// diff --git a/MangaReader/Services/Library.cs b/MangaReader/Services/Library.cs index 8a182390..2bfb5b5d 100644 --- a/MangaReader/Services/Library.cs +++ b/MangaReader/Services/Library.cs @@ -7,6 +7,7 @@ using System.Windows.Threading; using Hardcodet.Wpf.TaskbarNotification; using MangaReader.Manga; +using MangaReader.Manga.Acomic; using MangaReader.Properties; using NHibernate.Linq; @@ -53,14 +54,17 @@ public static class Library internal static void ShowInTray(string message, object context) { if (Settings.MinimizeToTray) - lock (TaskbarIconLock) - lock (DispatcherLock) + using (TimedLock.Lock(TaskbarIconLock)) + { + using (TimedLock.Lock(DispatcherLock)) + { formDispatcher.Invoke(() => { taskbarIcon.ShowBalloonTip(Strings.Title, message, BalloonIcon.Info); taskbarIcon.DataContext = context; }); - + } + } } /// @@ -70,8 +74,10 @@ internal static void ShowInTray(string message, object context) /// Состояние. internal static void SetTaskbarState(double? percent = null, TaskbarItemProgressState? state = null) { - lock (TaskbarLock) - lock (DispatcherLock) + using (TimedLock.Lock(TaskbarLock)) + { + using (TimedLock.Lock(DispatcherLock)) + { formDispatcher.Invoke(() => { if (state.HasValue) @@ -79,6 +85,8 @@ internal static void SetTaskbarState(double? percent = null, TaskbarItemProgress if (percent.HasValue) taskBar.ProgressValue = percent.Value; }); + } + } } #region Методы @@ -115,7 +123,7 @@ public static void FilterChanged(Table main) if (main.NameFilter.Text.Any()) query = query.Where(n => (n.IsNameChanged ? n.LocalName : n.ServerName).ToLowerInvariant(). Contains(main.NameFilter.Text.ToLowerInvariant())); - main.FormLibrary.ItemsSource = query.OrderBy(m => m.IsNameChanged ? m.LocalName : m.ServerName).ToList(); + main.FormLibrary.ItemsSource = query.ToList().OrderBy(m => m.Name); } /// @@ -167,9 +175,31 @@ public static void Remove(Mangas manga) /// internal static void Convert(ConverterProcess process) { - if (!File.Exists(DatabaseFile)) - return; + if (File.Exists(DatabaseFile)) + ConvertBaseTo24(process); + + Convert24To27(process); + } + + private static void Convert24To27(ConverterProcess process) + { + if (process.Version.CompareTo(Settings.DatabaseVersion) > 0) + { + process.Percent = 0; + var acomics = Mapping.Environment.Session.Query().ToList(); + if (acomics.Any()) + process.IsIndeterminate = false; + + foreach (var acomic in acomics) + { + process.Percent += 100.0 / acomics.Count; + Getter.UpdateContentType(acomic); + } + } + } + private static void ConvertBaseTo24(ConverterProcess process) + { var database = Serializer>.Load(DatabaseFile) ?? new List(File.ReadAllLines(DatabaseFile)); if (process != null && database.Any()) @@ -210,7 +240,7 @@ public static void Update(IEnumerable mangas, SortDescription sort) { Library.SetTaskbarState(0, TaskbarItemProgressState.Normal); var mangaIndex = 0; - mangas = sort.Direction == ListSortDirection.Ascending ? + mangas = sort.Direction == ListSortDirection.Ascending ? mangas.OrderBy(m => m.Name) : mangas.OrderByDescending(m => m.Name); var listMangas = mangas.Where(m => m.NeedUpdate).ToList(); diff --git a/MangaReader/Services/Log.cs b/MangaReader/Services/Log.cs index cdad2055..e7e40165 100644 --- a/MangaReader/Services/Log.cs +++ b/MangaReader/Services/Log.cs @@ -50,8 +50,10 @@ public static void Exception(Exception ex, params string[] messages) private static void Write(string contents, string path) { Console.WriteLine(contents); - lock (LogLock) + using (TimedLock.Lock(LogLock)) + { File.AppendAllText(path, contents, System.Text.Encoding.UTF8); + } } public Log() diff --git a/MangaReader/Manga/Page.cs b/MangaReader/Services/Page.cs similarity index 100% rename from MangaReader/Manga/Page.cs rename to MangaReader/Services/Page.cs diff --git a/MangaReader/Services/TimedLock.cs b/MangaReader/Services/TimedLock.cs new file mode 100644 index 00000000..3767f0bb --- /dev/null +++ b/MangaReader/Services/TimedLock.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; +// Thanks to Eric Gunnerson for recommending this be a struct rather +// than a class - avoids a heap allocation. +// Thanks to Change Gillespie and Jocelyn Coulmance for pointing out +// the bugs that then crept in when I changed it to use struct... +// Thanks to John Sands for providing the necessary incentive to make +// me invent a way of using a struct in both release and debug builds +// without losing the debug leak tracking. + +namespace MangaReader.Services +{ + public struct TimedLock : IDisposable + { + public static TimedLock Lock(object o) + { + return Lock(o, TimeSpan.FromSeconds(10)); + } + + public static TimedLock Lock(object o, TimeSpan timeout) + { + var tl = new TimedLock(o); + if (!Monitor.TryEnter(o, timeout)) + { +#if DEBUG + GC.SuppressFinalize(tl.leakDetector); +#endif + throw new LockTimeoutException(); + } + + return tl; + } + + private TimedLock(object o) + { + target = o; +#if DEBUG + leakDetector = new Sentinel(); +#endif + } + private object target; + + public void Dispose() + { + Monitor.Exit(target); + + // It's a bad error if someone forgets to call Dispose, + // so in Debug builds, we put a finalizer in to detect + // the error. If Dispose is called, we suppress the + // finalizer. +#if DEBUG + GC.SuppressFinalize(leakDetector); +#endif + } + +#if DEBUG + // (In Debug mode, we make it a class so that we can add a finalizer + // in order to detect when the object is not freed.) + private class Sentinel + { + ~Sentinel() + { + // If this finalizer runs, someone somewhere failed to + // call Dispose, which means we've failed to leave + // a monitor! + System.Diagnostics.Debug.Fail("Undisposed lock"); + } + } + private Sentinel leakDetector; +#endif + + } + public class LockTimeoutException : ApplicationException + { + public LockTimeoutException() + : base("Timeout waiting for lock") + { + } + } +} diff --git a/MangaReader/Update/VersionHistory.txt b/MangaReader/Update/VersionHistory.txt index efe35e7b..506052c2 100644 --- a/MangaReader/Update/VersionHistory.txt +++ b/MangaReader/Update/VersionHistory.txt @@ -1,4 +1,10 @@ -1.26.5548 +1.27.5583 +* Улучшена поддержка сайта Acomics: + + Добавлена поддержка глав и томов + + Добавлена упаковка глав и томов, если они имеются у конкретного комикса +* Общие доработки по процессу скачивания. + +1.26.5548 * Исправлена работа с перемещённой мангой на ридманге\адультманге: + Теперь ссылки автоматически исправляются, если на сайте есть перенаправление по свежему адресу. + История автоматически переименовывается, предотвращая повторное скачивание перенесенной манги.