/// <summary> /// Add a story to the container, that doesn't need saving right away, and don't trigger any events. /// </summary> /// <param name="story"></param> private void LoadStory(WdcInteractiveStory newStory) { lock (_lock) { _AddStory(newStory, false); } }
private void SetFormState(AddStoryFormState newState) { _log.Debug($"Setting form state: {newState}."); switch (newState) { case AddStoryFormState.ReadToGetStory: EnableSaveButton(false); EnableGetStoryButton(true); EnableStoryParmInput(true); break; case AddStoryFormState.GettingStory: _story = null; // Forget the current story SetStoryInfo(string.Empty); // Clear the story info EnableStoryParmInput(false); EnableGetStoryButton(false); EnableSaveButton(false); break; case AddStoryFormState.ReadyToSave: EnableStoryParmInput(true); EnableGetStoryButton(true); EnableSaveButton(true); break; } _formState = newState; }
public void ExportStoryRecentChapters(WdcInteractiveStory story) { _log.Debug("Exporting story recent chapters"); var chaptersStorted = story.Chapters.OrderByDescending(c => c.FirstSeen).ToArray(); var htmlRecentChaptersItems = new StringBuilder(); foreach (var chapter in chaptersStorted) { var chapterIsReady = IsChapterReady(chapter); htmlRecentChaptersItems.AppendLine( GetTemplate("StoryRecentChaptersItem.html") .Replace("{ChapterName}", chapterIsReady ? chapter.Title : CHAPTER_NOT_EXPORTED_PLACEHOLDER) .Replace("{ChapterSeen}", chapter.FirstSeen.ToString()) .Replace("{ChapterPath}", GetPrettyChapterPath(chapter.Path)) .Replace("{ChapterLink}", chapterIsReady ? _chapterPathFilename[chapter.Path] : "#") ); } string htmlContent = GetTemplate("StoryRecentChapters.html") .Replace("{StoryTitle}", story.Name) .Replace("{RecentChapterItems}", htmlRecentChaptersItems.ToString()) .Replace("{HomeLink}", GetHomepageFileName()); var html = GetPageTemplate($"{story.Name} - Recent chapters", "Recent chapters", htmlContent); var fname = GetRecentChaptersFileName(); File.WriteAllText(Path.Combine(_outputDir, fname), html); }
private async Task SyncChapter(WdcInteractiveStory story, WdcInteractiveChapter chapter) { var ct = _ctSource.Token; ct.ThrowIfCancellationRequested(); _log.Debug($"Syncing story '{story.ID}' chapter '{chapter.Path}'"); // Update the story status // TODO // If the last synced value is minimum (always update) or chapter refreshing is enabled and this chapter needs a refresh if ( chapter.LastSynced == DateTime.MinValue || (GetSettings().UpdateKnownChapters&& chapter.LastSynced <= DateTime.Now + new TimeSpan(0, 0, GetSettings().SyncChapterIntervalSeconds, 0)) ) { var remoteChapter = await _wdcReader.GetInteractiveChaper(story.ID, chapter.Path, _wdcClient, ct); ct.ThrowIfCancellationRequested(); // Bring over changes chapter.Author = remoteChapter.Author; chapter.Choices = remoteChapter.Choices; chapter.Content = remoteChapter.Content; chapter.IsEnd = remoteChapter.IsEnd; chapter.SourceChoiceTitle = remoteChapter.SourceChoiceTitle; chapter.Title = remoteChapter.Title; chapter.LastSynced = DateTime.Now; } }
/// <summary> /// Show the Edit story form. /// </summary> /// <param name="story"></param> private void ShowEditStoryForm(WdcInteractiveStory story) { var editStoryForm = _diContainer.GetInstance <EditStoryForm>(); editStoryForm.SetStory(story); editStoryForm.ShowDialog(this); }
public void ExportStoryOutline(WdcInteractiveStory story) { _log.Debug("Exporting story outline"); StringBuilder htmlStoryOutline = new StringBuilder(); foreach (var chapter in story.Chapters) { var chapterIsReady = IsChapterReady(chapter); htmlStoryOutline.AppendLine( GetTemplate("StoryOutlineItem.html") .Replace("{ChapterPathDisplay}", GetPrettyChapterPath(chapter.Path)) .Replace("{ChapterPath}", chapter.Path) .Replace("{ChapterName}", chapterIsReady ? chapter.Title : CHAPTER_NOT_EXPORTED_PLACEHOLDER) .Replace("{ChapterLink}", chapterIsReady ? _chapterPathFilename[chapter.Path] : "#") ); } string htmlContent = GetTemplate("StoryOutline.html") .Replace("{StoryTitle}", story.Name) .Replace("{OutlineContent}", htmlStoryOutline.ToString()) .Replace("{HomeLink}", GetHomepageFileName()); var html = GetPageTemplate($"{story.Name} - Chapter outline", "Chapter outline", htmlContent); var fname = GetOutlineFileName(); File.WriteAllText(Path.Combine(_outputDir, fname), html); }
private WdcStoryContainerWrapper GetNewWrapper(WdcInteractiveStory story) { return(new WdcStoryContainerWrapper() { Story = story, NeedsSave = false }); }
public EditStoryForm(IWdcStoryContainer storyContainer) { _storyContainer = storyContainer; _story = new WdcInteractiveStory(); InitializeComponent(); }
public string SerializeStoryToString(WdcInteractiveStory story) { using (StringWriter sw = new StringWriter()) { _serializer.Serialize(sw, story, _xmlNamespace); return(sw.ToString()); } }
public void SaveStory(WdcInteractiveStory story, string filePath) { _log.Debug($"Saving story '{story.ID}' to: {filePath}"); using (var f = File.Open(filePath, FileMode.Create)) { SerializeStory(story, f); } }
/// <summary> /// Add a story to the container. /// </summary> /// <param name="newStory"></param> public void AddStory(WdcInteractiveStory newStory, bool needsSave) { lock (_lock) { _AddStory(newStory, needsSave); } DoEvent(newStory.ID, WdcStoryContainerEventType.Add); }
public void XmlSimpleSaveAndLoad() { var fname = $"{_testStory.ID}-SimpleSaveAndLoad.xml"; IStoryFileStore fs = new XmlStoryFileStore(); fs.SaveStory(_testStory, fname); WdcInteractiveStory loadedStory = fs.LoadStory(fname); AddFileToClean(fname); }
private void _AddStory(WdcInteractiveStory newStory, bool needsSave) { if (_HasStory(newStory.ID)) { throw new ArgumentException($"A story with the ID of '{newStory.ID}' already exists."); } var sw = GetNewWrapper(newStory); sw.NeedsSave = needsSave; _storyCollection.Add(sw); }
private void SetStoryInfo(WdcInteractiveStory story) { var sb = new StringBuilder(); sb.AppendLine($"Title: {story.Name}"); sb.AppendLine(); sb.AppendLine("Short description:"); sb.AppendLine(story.ShortDescription); sb.AppendLine(); sb.AppendLine(story.Description); SetStoryInfo(sb.ToString()); }
private void UpdateFormFromStory(WdcInteractiveStory story) { if (this.InvokeRequired) { this.Invoke(new UpdateFormFromStoryDelegate(UpdateFormFromStory), story); return; } txtStoryName.Text = _story.Name; txtStoryID.Text = _story.ID; txtStoryShortDescription.Text = _story.ShortDescription; txtStoryDescription.Text = _story.Description; dtpLastUpdated.Value = _story.LastUpdatedInfo; }
private string GetPreviousChapterLink(WdcInteractiveStory story, WdcInteractiveChapter chapter) { if (chapter.Path.Length > 1) { // E.g. 15524 var previousChapterPath = _chapterPathFilename[chapter.Path.Substring(0, chapter.Path.Length - 1)]; return($"This choice: <b>{chapter.SourceChoiceTitle}</b> <a href='{previousChapterPath}'>Go back</a>"); } else { // E.g. 1 return($"<a href='{STORY_HOMEPAGE_FILENAME}'>Go back</a>"); } }
public WdcInteractiveStory GetInteractiveStory(string interactiveID, WdcResponse wdcPayload) { log.DebugFormat("Getting interactive story: {0}", interactiveID); var story = new WdcInteractiveStory(); // Get interactive story title story.ID = interactiveID; story.Url = wdcPayload.Address; story.Name = GetInteractiveStoryTitle(wdcPayload); story.ShortDescription = GetInteractiveStoryShortDescription(wdcPayload); story.Description = GetInteractiveStoryDescription(wdcPayload); story.LastUpdatedInfo = DateTime.Now; return(story); }
public void UpdateStory(WdcInteractiveStory newStory) { lock (_lock) { var existingStory = _GetStory(newStory.ID); if (existingStory == null) { throw new ArgumentOutOfRangeException($"A story with the ID of '{newStory.ID}' does not exist."); } // Copy it over, including all the chapters // Remove the old story, add in the replacement _RemoveStory(newStory.ID, false); _AddStory(newStory, true); } DoEvent(newStory.ID, WdcStoryContainerEventType.Update); }
private async void GetStory() { SetFormState(AddStoryFormState.GettingStory); try { var storyParm = txtStoryParm.Text; if (string.IsNullOrEmpty(storyParm)) { throw new ArgumentNullException("storyParm"); } // If it's a URL, just get the parameter. Uri uriOutput; if (Uri.TryCreate(storyParm, UriKind.Absolute, out uriOutput)) { storyParm = uriOutput.Segments.Last(); // Just get the last part, should be the story ID } _log.Debug($"Getting story info: {storyParm}"); var storyResult = await _wdcReader.GetInteractiveStory(storyParm, _wdcClient, _ctSourceWdcClient.Token); // Made it here, request was successfull _story = storyResult; SetStoryInfo(storyResult); } catch (OperationCanceledException ex) { // Do nothing } catch (Exception ex) { // TODO error handling _log.Warn("Error while trying to get story", ex); ShowError("Error trying to get story", "An error occurred while trying to get the story info.", ex); } finally { SetFormState(_story == null ? AddStoryFormState.ReadToGetStory : AddStoryFormState.ReadyToSave); } }
public void ExportStoryHomepage(WdcInteractiveStory story) { _log.Debug("Exporting story homepage"); string htmlContent = GetTemplate("StoryHomepage.html") .Replace("{StoryTitle}", story.Name) //.Replace("{StoryAuthor}", story.Author.Name) // Author isn't currently supported .Replace("{StoryShortDescription}", story.ShortDescription) .Replace("{StoryDescription}", story.Description) .Replace("{FirstPageLink}", _chapterPathFilename["1"]) .Replace("{StoryOutlineLink}", GetOutlineFileName()) .Replace("{RecentChaptersLink}", GetRecentChaptersFileName()); var html = GetPageTemplate(story.Name, story.Name, htmlContent); var fname = GetHomepageFileName(); File.WriteAllText(Path.Combine(_outputDir, fname), html); }
// Check the list of chapters in WDC. If there are some that we haven't seen before, add placeholder chapters to the story. private async Task SyncStoryChapterList(WdcInteractiveStory story) { var ct = _ctSource.Token; ct.ThrowIfCancellationRequested(); _log.Debug($"Syncing chapter outline for story '{story.ID}'"); this.SetCurrentStatus(StorySyncWorkerState.WorkingOutline, $"Updating story chapter list: {story.ID}"); var remoteChapterList = await _wdcReader.GetInteractiveChapterList(story.ID, _wdcClient, ct); ct.ThrowIfCancellationRequested(); foreach (var uri in remoteChapterList) { var chapterPath = WdcUtil.GetFinalParmFromUrl(uri.ToString()); // Just want the final digits // Do we already have that chapter? if (!story.Chapters.Exists(c => c.Path == chapterPath)) { ct.ThrowIfCancellationRequested(); _log.InfoFormat("Creating placeholder for new chapter: {0}", chapterPath); var newChapter = new WdcInteractiveChapter() { Path = chapterPath, LastSynced = DateTime.MinValue, FirstSeen = DateTime.Now }; story.Chapters.Add(newChapter); } } ct.ThrowIfCancellationRequested(); // Save changes story.LastUpdatedChapterOutline = DateTime.Now; _storyContainer.UpdateStory(story); }
private string GetChapterChoices(WdcInteractiveStory story, WdcInteractiveChapter chapter) { if (chapter.IsEnd || chapter.Choices.Count < 1) { // Is end return(GetTemplate("ChapterChoiceEnd.html")); } else { // There are choices var sbChapterChoices = new StringBuilder(); foreach (var choice in chapter.Choices) { // Does this choice lead to a valid chapter? bool isChoiceValid = story.Chapters.SingleOrDefault(c => c.Path == choice.PathLink) != null; var choiceHtml = String.Empty; // Choice links to a chapter that exists, choice is valid if (isChoiceValid && _chapterPathFilename != null) { choiceHtml = GetTemplate("ChapterChoiceItemValid.html") .Replace("{ChoicePath}", choice.PathLink) .Replace("{ChoiceLink}", _chapterPathFilename[choice.PathLink]) .Replace("{ChoiceName}", choice.Name); } else { // Is either an invalid choice, or the path / filename lookup is missing (could be for a once-off chapter export) choiceHtml = GetTemplate("ChapterChoiceItemInvalid.html") .Replace("{ChoiceName}", choice.Name); } sbChapterChoices.AppendLine(choiceHtml); } return(GetTemplate("ChapterChoiceList.html") .Replace("{ChoiceList}", sbChapterChoices.ToString())); } }
public void ExportStoryChapter(WdcInteractiveStory story, WdcInteractiveChapter chapter, string filename) { _log.DebugFormat("Exporting chapter: {0}", chapter.Path); var chapterIsReady = IsChapterReady(chapter); var htmlContent = GetTemplate("Chapter.html") .Replace("{AuthorName}", chapter.Author.Name) .Replace("{ChapterPath}", chapter.Path) .Replace("{ChapterPathDisplay}", GetPrettyChapterPath(chapter.Path)) .Replace("{SourceChapterChunk}", GetPreviousChapterLink(story, chapter)) .Replace("{ChapterChoices}", GetChapterChoices(story, chapter)) .Replace("{ChapterContent}", chapter.Content) .Replace("{ChapterTitle", chapter.Title) .Replace("{HomeLink}", GetHomepageFileName()); var html = GetPageTemplate($"{story.Name} - {chapter.Title}", chapter.Title, htmlContent); // TODO: Fix the "Go back" link File.WriteAllText(Path.Combine(_outputDir, filename), html); }
public void ExportStory(WdcInteractiveStory story) { _log.InfoFormat("Exporting story '{0}' to path: {1}", story.ID, _outputDir); DoProgressUpdate("Getting ready to export", 0, 0); // Create the missing directories, if need be Directory.CreateDirectory(_outputDir); // Create the chapter path / chapter filename map. // Not sure, but one day these might not be so simple, like if we hit the filename length limit. _chapterPathFilename = new Dictionary <string, string>(); foreach (var chapter in story.Chapters) { _chapterPathFilename.Add(chapter.Path, GetChapterFileName(chapter)); } DoProgressUpdate("Exporting story homepage", 0, _chapterPathFilename.Count); ExportStoryHomepage(story); DoProgressUpdate("Exporting story outline", 0, _progressMax); ExportStoryOutline(story); DoProgressUpdate("Exporting recent chapters", 0, _progressMax); ExportStoryRecentChapters(story); //foreach (var chapter in story.Chapters) for (var i = 0; i < story.Chapters.Count; i++) { var chapter = story.Chapters[i]; var chapterIsReady = IsChapterReady(chapter); DoProgressUpdate($"Exporting chapter {chapter.Path}", i, _progressMax); if (chapterIsReady) { ExportStoryChapter(story, chapter, _chapterPathFilename[chapter.Path]); } } }
// WARNING: is not thread safe private void _AddStoryRow(WdcInteractiveStory story) { var newRow = new DataGridViewRow(); newRow.Tag = story.ID; newRow.Cells.Add(new DataGridViewTextBoxCell() { Value = null }); newRow.Cells.Add(new DataGridViewTextBoxCell() { Value = null }); newRow.Cells.Add(new DataGridViewTextBoxCell() { Value = story.Name, ToolTipText = story.Name }); dgvStories.Rows.Add(newRow); // Update row from sync status UpdateStoryListRow(newRow, _syncWorker.GetStoryStatus(story.ID)); }
// Update the story details private async Task SyncStoryInfo(WdcInteractiveStory story) { var ct = _ctSource.Token; ct.ThrowIfCancellationRequested(); _log.InfoFormat("Syncing story info: {0}", story.ID); var remoteStory = await _wdcReader.GetInteractiveStory(story.ID, _wdcClient, _ctSource.Token); ct.ThrowIfCancellationRequested(); // Bring over changes story.Author = remoteStory.Author; story.Name = remoteStory.Name; story.ShortDescription = remoteStory.ShortDescription; story.Description = remoteStory.Description; story.LastUpdatedInfo = DateTime.Now; // Save changes ct.ThrowIfCancellationRequested(); _storyContainer.UpdateStory(story); }
private async Task SyncStory(WdcInteractiveStory story) { _log.Debug($"Syncing story '{story.ID}'"); var ct = _ctSource.Token; ct.ThrowIfCancellationRequested(); // Update story's status, mark as in progress //SetStoryStatusState(story.ID, StorySyncWorkerStoryState.Working); SetCurrentStatus(StorySyncWorkerState.WorkingStory, $"Working on story: {story.ID}", story.ID); //SetStoryStatusProgress(story.ID, 0, 0); // Catch specific exceptions try { if (CheckIfStoryInfoNeedsSync(story)) { SetStoryStatusState(story.ID, StorySyncWorkerStoryState.Working); SetCurrentStatus(StorySyncWorkerState.WorkingStory, $"Updating story info: {story.ID}", story.ID); await SyncStoryInfo(story); } else { _log.Debug("Story info does not need updating"); } if (CheckIfChapterOutlineNeedsSync(story)) { SetStoryStatusState(story.ID, StorySyncWorkerStoryState.Working); SetCurrentStatus(StorySyncWorkerState.WorkingOutline, $"Updating chapter outline: {story.ID}", story.ID); await SyncStoryChapterList(story); } else { _log.Debug("Story chapter outline does not need updating"); } ct.ThrowIfCancellationRequested(); // Build a list of chapters that need updating SetCurrentStatus(StorySyncWorkerState.WorkingStory, $"Checking for any chapters that need syncing: {story.ID}", story.ID); List <WdcInteractiveChapter> chaptersToSync = new List <WdcInteractiveChapter>(); foreach (var chapter in story.Chapters) { if (CheckIfChapterNeedsSync(chapter)) { chaptersToSync.Add(chapter); } } ct.ThrowIfCancellationRequested(); // If there are no chapters to update, set progress to maximum if (chaptersToSync.Count < 1) { SetStoryStatusProgress(story.ID, story.Chapters.Count, story.Chapters.Count); _log.Debug("No chapters need updating"); } else { SetStoryStatusState(story.ID, StorySyncWorkerStoryState.Working); // About to start working } // Start syncing the chapters for (var i = 0; i < chaptersToSync.Count; i++) { ct.ThrowIfCancellationRequested(); var chapter = chaptersToSync[i]; // Update status SetCurrentStatus(StorySyncWorkerState.WorkingChapter, $"Updating story chapter: {story.ID}, {chapter.Path}", story.ID); SetStoryStatusProgress(story.ID, story.Chapters.Count - (chaptersToSync.Count - i), story.Chapters.Count); // Sync the chapter await SyncChapter(story, chapter); // Save changes after updating the chapter // TODO: is this too much? updating the entire sory when just a single chapter changes? Will it cause issues with stuff listening for container change events? _storyContainer.UpdateStory(story); } // Done sync for this story. Mark story status as idle SetStoryStatusState(story.ID, StorySyncWorkerStoryState.Idle); SetCurrentStatus(StorySyncWorkerState.Idle, $"Story update complete: {story.ID}", string.Empty); } // End of try block catch (InteractivesTemporarilyUnavailableException) { _log.Info($"Encountered ITU message for story: {story.ID}"); SetStoryStatusState(story.ID, StorySyncWorkerStoryState.WaitingItu); } catch (WritingClientHtmlParseException ex) { _log.Warn("Error trying to parse HTML from Writing.com", ex); // Dump the HTML var sbDump = new StringBuilder(); sbDump.AppendLine($"<!-- Exception: {ex.Message} -->"); sbDump.AppendLine($"<!-- URL: {ex.Address} -->"); sbDump.AppendLine(ex.HtmlResult); var dumpFilePath = _fileDumper.DumpFile("HtmlParseError", sbDump.ToString()); // Show an error message var sbExMsg = new StringBuilder(); sbExMsg.AppendLine("A HTML parse exception was encountered with the below details:"); sbExMsg.AppendLine("======================"); sbExMsg.AppendLine(ex.Message); sbExMsg.AppendLine("======================"); sbExMsg.AppendLine("The HTML response has been dumped to this location:"); sbExMsg.AppendLine(dumpFilePath); _gui.ShowMessageBox("HTML parse exception", sbExMsg.ToString(), GuiMessageBoxIcon.Error); // Set the story's status to Error, so it doesn't keep trying to sync and cause more errors SetStoryStatusError(story.ID, ex.Message); } catch (Exception ex) { _log.Warn($"Encountered unhandled exception while syncing story '{story.ID}'", ex); var sb = new StringBuilder(); sb.AppendLine(ex.GetType().Name); sb.AppendLine(ex.Message); SetStoryStatusError(story.ID, sb.ToString()); _gui.ShowExceptionDialog(ex); } SetCurrentStatus(StorySyncWorkerState.Idle, $"Story update complete: {story.ID}", string.Empty); }
public bool CheckIfChapterOutlineNeedsSync(WdcInteractiveStory story) { return(story.LastUpdatedChapterOutline + new TimeSpan(0, 0, GetSettings().SyncChapterOutlineIntervalSeconds, 0) < DateTime.Now); }
public bool CheckIfStoryInfoNeedsSync(WdcInteractiveStory story) { return(story.LastUpdatedInfo + new TimeSpan(0, 0, GetSettings().SyncStoryIntervalSeconds, 0) < DateTime.Now); }
public void SetStory(WdcInteractiveStory newStory) { _story = newStory; _isNewStory = false; UpdateFormFromStory(newStory); }