private async Task <OpenResult> OpenFileAsText(string fileString, BookLocation location) { await Task.Delay(0); Logger.Log($"MainEpubReader: file is a text string; convert to html"); EpubBook = TextWizard.TextToEpub(fileString); if (location == null) { var chapter = EpubWizard.GetFirstChapter(EpubBook.TableOfContents); if (chapter != null) { location = EpubChapterData.FromChapter(chapter); } } if (location != null) { Logger.Log($"MainEpubReader: Show Text: About to move to location as needed. {location}"); UserNavigatedToArgument = location; NavigateTo(ControlId, location); // We won't get a hairpin navigation callback } return(OpenResult.OK); }
private void UserDidNavigationRequest(EpubChapterData chapter) { if (chapter == null) { return; } var nav = Navigator.Get(); BookLocation location = null; if (chapter == Chapters[0]) { // First one is special: we always go to the very start of the book, including stuff that's not in // the classic table of contents. For example, Gutenberg Campfire Girls Station Island has a cover that's // not in the TOC but which is in the inner.SpecialResources.HtmlInReadingOrder and which is placed first. // Additionally, that book also has the weird thing that the ID for the first section (in the TOC) is // near the end of the section, so when you click on it, it scrolls to there, but then the next TOC entry // (chapter 1) is visible near the top, and so the selection jumps to there. location = new BookLocation(0, 0); // first HTML page, at the very top. } else { location = EpubChapterData.FromChapter(chapter); } nav.UserNavigatedTo(ControlId, location); }
/// <summary> /// Called when a book is starting to be displayed. Location is the starting location. /// Is used with e.g. the universal note display that shows me all my notes. /// </summary> /// <param name="book"></param> /// <param name="location"></param> /// <returns></returns> public async Task DisplayBook(BookData book, BookLocation location) { CurrBookId = book.BookId; CurrBook = book; CurrLocation = location; await Task.Delay(0); }
public static (string bookId, BookLocation location) ParseUrl(Uri url) { if (url.Scheme != "voracious-reader") { return(null, null); } var id = url.AbsolutePath.Trim('/'); // sigh. Just how life goes. BookLocation location = null; var query = url.Query; if (query.Length > 1 && query[0] == '?') { query = query.Substring(1); } foreach (var queryItem in query.Split(new char[] { '&' })) { var values = queryItem.Split(new char[] { '=' }, 2); if (values[0] == "location" && values.Length >= 2) { // Parse out the json... var json = Uri.UnescapeDataString(values[1]); location = BookLocation.FromJson(json); } else if (values[0] == "id" && values.Length >= 2) { id = Uri.UnescapeDataString(values[1]); } } return(id, location); }
private void DoNextSection() { var location = new BookLocation(CurrHtmlIndex + 1, 0); NavigateToPercent(location); CurrPageIsMonoPage = false; }
public bool DisplayBook(NavigateControlId id, BookData bookData, BookLocation location = null) { if (MainBookHandler == null) { return(false); } // Is the book actually downloaded? if (bookData.DownloadData == null || bookData.DownloadData.CurrFileStatus != DownloadData.FileStatus.Downloaded) { // TODO: download the book return(false); } MainBookHandler.DisplayBook(bookData, location); foreach (var item in SimpleBookHandlers) { // No hairpin selects if (item.Key != id) { item.Value.DisplayBook(bookData, location); } } return(true); }
private BookLocation GetCurrBookLocation() { double pos = !double.IsNaN(CurrSelectPosition) ? CurrSelectPosition : CurrScrollPosition; var location = new BookLocation(CurrHtmlIndex, pos); return(location); }
public async Task DisplayBook(BookData book, BookLocation location) { await Task.Delay(0); // just to make the compiler happy. SetNotes(book); // Location isn't used at all. }
private static Uri AsUri(string bookId, BookLocation location) { var id = Uri.EscapeUriString(bookId); var loc = location == null ? "" : "&location=" + Uri.EscapeUriString(location.ToJson()); var uri = new Uri($"voracious-reader:///?id={id}{loc}"); return(uri); }
public void UpdateProjectRome(NavigateControlId sourceId, BookLocation location) { foreach (var(id, control) in NavigateTos) { if (id != sourceId && id == NavigateControlId.ProjectRome) { control.NavigateTo(sourceId, location); } } }
// Is called when the notes are updated. I don't really display a book. public async Task DisplayBook(BookData book, BookLocation location) { await Task.Delay(0); ReloadAllNotes(); uiBookList.ScrollIntoView(book); uiBookList.SelectedItem = book; // get it selected, too, so it shows up! CurrBook = book; // Location isn't used at all. }
private void DoUserNavigateToAsNeeded() { var location = UserNavigatedToArgument; UserNavigatedToArgument = null; if (location != null) { var nav = Navigator.Get(); nav.UserNavigatedTo(ControlId, location); } }
/// <summary> /// Called by any of the displays when the user has picked a place to navigate to. /// Is never called automatically. The place is a place inside the already-viewed ebook. /// </summary> /// <param name="sourceId"></param> /// <param name="location"></param> public void UserNavigatedTo(NavigateControlId sourceId, BookLocation location) { Logger.Log($"UserNavigatedTo({location})"); foreach (var(id, control) in NavigateTos) { if (id != sourceId) { control.NavigateTo(sourceId, location); } } }
private static BookLocation ToBookLocation(ImageData data) { // Some data.Name are full of @. Others are not. // Prefer the Href value over the Name. It seems to work better for images. // // // TODO: verifying that using Href is OK: var location = new BookLocation(-1, data.Name); var location = new BookLocation(-1, data.Href ?? data.Name); var same = (data.Href == data.Name) ? "SAME" : "DIFFERENT"; Logger.Log($"DBG: Image {same} name={data.Name} href={data.Href ?? "null"}"); return(location); }
private async void OnPrevPage(object sender, RoutedEventArgs e) { if ((CurrScrollPosition <= TopScrollPosition) && (CurrHtmlIndex > 0)) { if (Logger.LogExtraTiming) { Logger.Log($"MainEpubReader:OnPrevPage: move back one page from {CurrHtmlIndex}"); } var location = new BookLocation(CurrHtmlIndex - 1, 100); NavigateToPercent(location); } else { await uiHtml.InvokeScriptAsync("scrollPage", new List <string>() { "-1" }); } }
/// <summary> /// NavigateTo means navigate to a spot in the book. Will also do a navigation with User... /// </summary> /// <param name="sourceId"></param> /// <param name="location"></param> public void NavigateTo(NavigateControlId sourceId, BookLocation location) { var bookdb = BookDataContext.Get(); if (Logger.LogExtraTiming) { Logger.Log($"MainEpubReader: Navigation: to {location}"); } // Save the fact that we navigated to here. Only the main reader saves this information var navigationData = CommonQueries.BookNavigationDataFind(bookdb, BookData.BookId); if (navigationData == null) { navigationData = new BookNavigationData() { BookId = BookData.BookId, CurrStatus = BookNavigationData.UserStatus.Reading }; CommonQueries.BookNavigationDataAdd(bookdb, navigationData, CommonQueries.ExistHandling.IfNotExists); } navigationData.CurrSpot = location.Location; navigationData.CurrStatus = BookNavigationData.UserStatus.Reading; // If I'm navigating then I'm reading? // And now actually navigate. There are two types of navigation: navigation // via tags and navigation by percent. // Both need to be handled. var percent = location.HtmlPercent; if (percent >= 0.0) { if (Logger.LogExtraTiming) { Logger.Log($"MainEpubReader: Navigation: to percent"); } NavigateToPercent(location); } else { if (Logger.LogExtraTiming) { Logger.Log($"MainEpubReader: Navigation: via location, not percent ({location})"); } NavigateToLocation(location); } }
/// <summary> /// Requires that the book is already at the right Html (otherwise the javascript calls won't work) /// </summary> /// <param name="location"></param> /// <returns></returns> public async Task <string> GetChapterBeforePercentAsync(BookLocation location) { // Some books are just text files. string id = ""; try { if (CurrHtml != null && CurrHtml.Contains("getClosestTagNear")) { id = await uiHtml.InvokeScriptAsync("getClosestTagNear", new List <string>() { location.ScrollPercent.ToString() }); } } catch (Exception) { ; // will happen if the book was a text file. } return(id); }
private void NavigateToPercent(BookLocation location) { var navScript = $"scrollToPercent({location.ScrollPercent});\n"; uiAllUpPosition.UpdatePosition(location.HtmlIndex, location.HtmlPercent); if (CurrHtmlIndex == location.HtmlIndex) { // Great! just scroll to the place! Logger.Log($"Main EBOOK: navigate to SAME html"); var task = uiHtml.InvokeScriptAsync("scrollToPercent", new List <string>() { location.ScrollPercent.ToString() }); DoUserNavigateToAsNeeded(); return; } else { if (Logger.LogExtraTiming) { Logger.Log($"Main EBOOK: navigate to other html (htmlindex={location.HtmlIndex})"); } var foundIndex = location.HtmlIndex; var foundHtml = EpubWizard.FindHtmlByIndex(EpubBook, foundIndex); if (Logger.LogExtraTiming) { Logger.Log($"Main EBOOK: navigate to other html {foundIndex}=len {foundHtml?.Length}"); } var html = HtmlFixup.FixupHtmlAll(foundHtml); DeferredNavigation = navScript; CurrHtml = html; CurrHtmlIndex = foundIndex; CurrHtmlFileName = location.HtmlFileName; Logger.Log($"Main EBOOK: about to navigate with deferred navigation {DeferredNavigation}"); uiHtml.NavigateToString(html); } SavePositionEZ(); }
public async void NavigateTo(NavigateControlId sourceId, BookLocation location) { if (!ProjectRomeEnabled) { return; } try { var channel = UserActivityChannel.GetDefault(); var activity = await channel.GetOrCreateUserActivityAsync(CurrBookId); if (!string.IsNullOrEmpty(activity.VisualElements.DisplayText)) { // If the activity wasn't already created, don't create it now! activity.ActivationUri = AsUri(CurrBookId, location); await activity.SaveAsync(); } } catch (Exception) { // Project Rome is pretty delicate; it fails for no good reasons. } }
/// <summary> /// Called when the user has navigated to somewhere in the book. The chapter display /// tries to sync itself to the value. The chapter display depends on the caller being /// fully initialized first! /// </summary> /// <param name="sourceId"></param> /// <param name="location"></param> public async void NavigateTo(NavigateControlId sourceId, BookLocation location) { string chapterid = ""; var nav = Navigator.Get(); if (!double.IsNaN(location.ScrollPercent)) { chapterid = await nav.MainBookHandler.GetChapterBeforePercentAsync(location); } else { chapterid = nav.MainBookHandler.GetChapterContainingId(location.Location, location.HtmlIndex); } EpubChapterData foundChapter = null; if (foundChapter == null && location.HtmlIndex >= 0) { var html = Book.ResourcesHtmlOrdered[location.HtmlIndex]; foreach (var chapter in Chapters) { // FAIL: Intro to Planetary Nebulae the location is html 8, id tit1 which is shared by multiple chapters. if (html.Href.EndsWith(chapter.FileName) && chapter.Anchor == chapterid) { foundChapter = chapter; break; } } } // Most common: there's an id, and it matches a single chapter. if (foundChapter == null) { foreach (var chapter in Chapters) { if (chapter.Anchor == chapterid || chapter.FileName == chapterid) { foundChapter = chapter; break; } } } if (foundChapter == null && location.HtmlIndex >= 0) { var html = Book.ResourcesHtmlOrdered[location.HtmlIndex]; foreach (var chapter in Chapters) { // FAIL: Intro to Planetary Nebulae the location is html 8, id tit1 which is shared by multiple chapters. if (html.Href.EndsWith(chapter.FileName)) { foundChapter = chapter; break; } } } // Worse case scenario, but it's better to display something if (foundChapter == null) { foreach (var chapter in Chapters) { if (string.IsNullOrEmpty(chapterid)) { App.Error($"ChapterDisplay:Navigate({location}) was asked to find an empty chapter"); foundChapter = chapter; break; } } } if (foundChapter == null) { // Truly desperate. if (Chapters.Count > 0) { App.Error($"ChapterDisplay:Navigate({location}) completely failed"); foundChapter = Chapters[0]; } else { App.Error($"ChapterDisplay:Navigate({location}) last ditch completely failed -- no chapters at all!"); } } if (foundChapter != null) { // Select this one uiChapterList.SelectedItem = foundChapter; uiChapterList.ScrollIntoView(foundChapter); return; // all done! } }
private async Task CreateActivityAsync(BookData book, BookLocation location, string imageDataUrl) { if (!ProjectRomeEnabled) { return; } var channel = UserActivityChannel.GetDefault(); var activity = await channel.GetOrCreateUserActivityAsync(book.BookId); activity.VisualElements.DisplayText = $"Reading {book.Title}"; activity.ActivationUri = AsUri(book.BookId, location); var title = Windows.Data.Json.JsonValue.CreateStringValue(book.Title).Stringify(); var authorvalue = book.BestAuthorDefaultIsNull; var author = authorvalue == null ? "\"\"" : Windows.Data.Json.JsonValue.CreateStringValue("By " + authorvalue).Stringify(); var reviewvalue = book?.Review?.Review; var review = reviewvalue == null ? "\"\"" : Windows.Data.Json.JsonValue.CreateStringValue(reviewvalue).Stringify(); var cardJson = @"{ ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"", ""type"": ""AdaptiveCard"", ""version"": ""1.0"", ""body"": [ { ""type"": ""Container"", ""items"": [ { ""type"":""ColumnSet"", ""columns"":[ { ""type"":""Column"", ""width"":""auto"", ""items"":[ { ""type"": ""Image"", ""url"": """ + imageDataUrl + @""", ""size"": ""large"" } ] }, { ""type"":""Column"", ""width"":""auto"", ""items"":[ { ""type"": ""TextBlock"", ""text"": " + title + @", ""weight"": ""bolder"", ""size"": ""large"", ""wrap"": true }, { ""type"": ""TextBlock"", ""text"": " + author + @", ""spacing"": ""none"", ""isSubtle"": true, ""wrap"": true }, { ""type"": ""TextBlock"", ""text"": " + review + @", ""spacing"": ""none"", ""wrap"": true } ] } ] } ] } ] }"; var card = AdaptiveCardBuilder.CreateAdaptiveCardFromJson(cardJson); activity.VisualElements.Content = card; await activity.SaveAsync(); var session = activity.CreateSession(); if (Sessions.ContainsKey(book.BookId)) { Sessions[book.BookId] = session; } else { Sessions.Add(book.BookId, session); } }
private void OnFirstPage(object sender, RoutedEventArgs e) { var location = new BookLocation(0, 0); // section, percent=0 NavigateToPercent(location); }
private async Task <OpenResult> OpenFile(string fullFilePath, BookLocation location) { OpenResult retval; // default is = OpenResult.OtherError; try { SetScreenToLoading(); Logger.Log($"MainEpubReader: about to load book {fullFilePath}"); var fileContents = await FileMethods.ReadBytesAsync(fullFilePath); bool isZip = fullFilePath.ToUpperInvariant().EndsWith(".ZIP"); if (fileContents == null) { // Failure of some sort, but just kind of punt it. retval = OpenResult.RedownloadableError; EpubBook = null; App.Error($"ERROR: book: unable to load file {fullFilePath}"); SetScreenToLoading(LoadFailureScreen); var md = new MessageDialog($"Error: book file is missing: {BookData.Title} ") { Title = "Atttempting to re-download book" }; await md.ShowAsync(); return(retval); } Logger.Log($"MainEpubReader: read raw array {fileContents.Length}"); // There's a chance that the file is really a text file, not an epub. // Make sure it's at least pre bool isEpub = false; Exception epubException = null; try { // All epub files start with PK\3\4 (because they are zip files). // If it's not that, then is must be a text or html file. if (EpubWizard.IsEpub(fileContents) && !isZip) { var inner = EpubReader.Read(fileContents); EpubBook = new EpubBookExt(inner); isEpub = inner != null; } } catch (Exception ex) { isEpub = false; epubException = ex; } if (!isEpub) { if (isZip) { throw epubException; } try { var fileString = System.Text.Encoding.UTF8.GetString(fileContents); if (!fileString.ToLower().Contains("<html")) { retval = await OpenFileAsText(fileString, location); } else { // We only understand text file and epub, nothing else. throw epubException; } } catch (Exception) { throw; // Meh } } Logger.Log($"MainEpubReader: read book length {fileContents.Length}"); SetChapters?.SetChapters(EpubBook, EpubBook.TableOfContents); if (SetChapters == null) { App.Error($"ISSUE: got new book but SetChapters is null for {fullFilePath}"); } await SetImages?.SetImagesAsync(EpubBook.Resources.Images); await SetImages2?.SetImagesAsync(EpubBook.Resources.Images); await SetImages3?.SetImagesAsync(EpubBook.Resources.Images); if (SetImages == null) { App.Error($"ISSUE: got new book but SetImages is null for {fullFilePath}"); } Logger.Log($"MainEpubReader: about to navigate"); if (location == null) { // Old way: go to the first item in table of contents. New way is to go to file=0 percent=0 // but only if there's any actual files //var chapter = EpubWizard.GetFirstChapter(EpubBook.TableOfContents); //if (chapter != null) if (EpubBook.ResourcesHtmlOrdered.Count > 0) { location = new BookLocation(0, 0); // often the first item is the cover page which isn't in the table of contents. //location = EpubChapterData.FromChapter(chapter); // // // location = new BookLocation(chapter.Anchor ?? chapter.FileName); // FAIL: BAEN likes to have file-per-chapter } } if (location != null) { if (Logger.LogExtraTiming) { Logger.Log($"MainEpubReader: OpenFile: About to move to location as needed. {location}"); } UserNavigatedToArgument = location; NavigateTo(ControlId, location); // We won't get a hairpin navigation callback } retval = OpenResult.OK; } catch (Exception ex) { // Simple error recovery: keep the downloaded data, but report it as // no actually downloaded. retval = OpenResult.OtherError; EpubBook = null; App.Error($"ERROR: book: exception {ex.Message} unable to load file {fullFilePath}"); SetScreenToLoading(LoadFailureScreen); var md = new MessageDialog($"Error: unable to open that book. Internal error {ex.Message}") { Title = "Unable to open book" }; await md.ShowAsync(); } return(retval); }
private void NavigateToLocation(BookLocation location) { // Gutenberg: just a location which we have to figure out // BAEN: HtmlFileName and (for sub-chapters) a Location, too. // Location might have just an id in it // OR it might have just an HtmlFileName // USPS: the location is an XHTML file that's encoded: Additional%20Resources.xhtml instead of Additional Resources.html // // OR an id+HtmlIndex. // We actually don't need the index! if (!string.IsNullOrEmpty(location.HtmlFileName)) { // Jump to percent 0.0 after finding the html by name. // navScript is either to an id or just to the top var navScript = string.IsNullOrEmpty(location.Location) ? $"scrollToPercent(0.0)" : $"scrollToId('{location.Location}')"; var(foundHtml, foundIndex, foundHtmlFileName) = EpubWizard.FindHtmlContainingHtmlFileName(EpubBook, location.HtmlFileName); if (foundHtml == null) { App.Error($"ERROR: unable to navigate to htmlFileName={location.HtmlFileName}"); return; // nuts; can't find it. } // If we're jumping to the current spot, meh, don't bother to optimize. // Maybe the user wants to do the full amount of code because of "issues" var html = HtmlFixup.FixupHtmlAll(foundHtml); DeferredNavigation = navScript; uiHtml.NavigateToString(html); CurrHtml = html; CurrHtmlIndex = foundIndex; CurrHtmlFileName = foundHtmlFileName; return; } else { string id = location.Location; // Find the html with the tag // FAIL: BAEN books only navigate by html name. The name will be found in the TOC because there are links! // FAIL: Gutenberg John Thorndyke Cases searching for the shoes.png. The long id @public@vhost@g@gutenberg@html@files@13882@13882-h@[email protected] // is found in the first HTML but only because that HTML includes a list of illustrations which point to an HTML file that includes the // shoes.png file (but that html file has a name that starts with the png but then adds on .wrap-0.html.html. // The code here used to just see if the current HTML includes the id at all; the better code checks to make sure it's a proper href. if (CurrHtml != null && EpubWizard.HtmlStringIdIndexOf(CurrHtml, id, true) >= 0 && CurrHtml.Contains("scrollToId")) { var task = uiHtml.InvokeScriptAsync("scrollToId", new List <string>() { id }); DoUserNavigateToAsNeeded(); return; } var idList = EpubWizard.GetIdVariants(id); var(foundHtml, foundIndex, foundHtmlFileName, foundId) = EpubWizard.FindHtmlContainingId(EpubBook, idList, location.HtmlIndex); if (foundHtml == null) { App.Error($"ERROR: unable to navigate to {id}"); return; // nuts; can't find it. } var navScript = $"scrollToId('{foundId}')"; var html = HtmlFixup.FixupHtmlAll(foundHtml); DeferredNavigation = navScript; uiHtml.NavigateToString(html); CurrHtml = html; CurrHtmlIndex = foundIndex; CurrHtmlFileName = foundHtmlFileName; } SavePositionEZ(); }
/// <summary> /// Called externally in order to display a book; will use the BookNavigationData to move /// to the previous spot. /// </summary> /// <param name="bookId"></param> /// <returns></returns> public async Task DisplayBook(BookData bookData, BookLocation location = null) { // Reset all of the position values. CurrHtml = ""; CurrHtmlIndex = -1; CurrHtmlFileName = null; CurrScrollPosition = double.NaN; CurrSelectPosition = double.NaN; BookData = bookData; var bookdb = BookDataContext.Get(); var dd = bookData.DownloadData ?? CommonQueries.DownloadedBookFind(bookdb, BookData.BookId); var nav = bookData.NavigationData ?? CommonQueries.BookNavigationDataFind(bookdb, BookData.BookId); if (location == null && !string.IsNullOrEmpty(nav?.CurrSpot)) { location = BookLocation.FromJson(nav.CurrSpot); } SetReviewSymbol(); if (dd == null) { return; } var fullpath = dd.FullFilePath; if (fullpath.Contains(@"\source\repos\SimpleEpubReader\SimpleEpubReader\bin\x64\Debug\AppX\Assets\PreinstalledBooks")) { // Whoops. The initial database might incorrectly have a developer path hard-coded. // Replace with correct location. var installationFolder = FolderMethods.InstallationFolder; fullpath = $"{installationFolder}\\Assets\\PreinstalledBooks\\{dd.FileName}"; } else if (fullpath.StartsWith("PreinstalledBooks:")) { // Preinstalled books are in a sort of relative path. It's designed to be an invalid // (or at least incredibly rare) directory. var installationFolder = FolderMethods.InstallationFolder; fullpath = $"{installationFolder}\\Assets\\PreinstalledBooks\\{dd.FileName}"; } var openResult = await OpenFile(fullpath, location); switch (openResult) { case OpenResult.OK: // Set up the uiAllUpPosition var sectionSizes = new List <double>(); foreach (var file in EpubBook.ResourcesHtmlOrdered) { sectionSizes.Add(file.Content.Length); } uiAllUpPosition.SetSectionSizes(sectionSizes); // No need to set to zero; it's already set in the OpenFile!. uiAllUpPosition.UpdatePosition(0, 0); // set to zero! ApplicationView appView = ApplicationView.GetForCurrentView(); appView.Title = bookData.Title.Replace("\n", " -- "); // title is multi-line break; case OpenResult.RedownloadableError: // An error. Mark the book as not downloaded + redownload dd.CurrFileStatus = DownloadData.FileStatus.Unknown; // deleted? gone? corrupt? we really don't know. CommonQueries.BookSaveChanges(bookdb); await BookSearch.DoSwipeDownloadOrReadAsync(BookData); break; default: // An error. Mark the book as not downloaded dd.CurrFileStatus = DownloadData.FileStatus.Unknown; // deleted? gone? corrupt? we really don't know. CommonQueries.BookSaveChanges(bookdb); break; } }