private static void ShowToast(string shortUserLevelMessage, Exception exception, string fullDetailedMessage, bool showSendReport = true) { var formForSynchronizing = Application.OpenForms.Cast <Form>().LastOrDefault(); if (formForSynchronizing == null) { return; // can't safely show a toast, may be on wrong thread. } if (formForSynchronizing.InvokeRequired) { formForSynchronizing.BeginInvoke(new Action(() => { ShowToast(shortUserLevelMessage, exception, fullDetailedMessage, showSendReport); })); return; } var toast = new ToastNotifier(); var callToAction = string.Empty; if (showSendReport) { toast.ToastClicked += (s, e) => { ProblemReportApi.ShowProblemDialog(formForSynchronizing, exception, fullDetailedMessage, "nonfatal"); }; callToAction = "Report"; } toast.Image.Image = ToastNotifier.WarningBitmap; toast.Show(shortUserLevelMessage, callToAction, 5); }
/// <summary> /// We keep reader tool settings for each language in %localappdata%/SIL/Bloom. /// e.g., for language kaj, we would expect to find /// ReaderToolsSettings-kaj.json /// ReaderToolsWords-kaj.json /// and possibly a folder with allowed words lists for stages: /// Allowed Words-kaj. /// A typical reader tools BloomPack only has one of each, but we allow for the possibility of more. /// </summary> /// <param name="newlyAddedFolderOfThePack"></param> internal static void CopyReaderToolsSettingsToWhereTheyBelong(string newlyAddedFolderOfThePack) { var destFolder = ProjectContext.GetBloomAppDataFolder(); foreach (var readerSettingsFile in Directory.GetFiles(newlyAddedFolderOfThePack, ReaderToolsSettingsPrefix + "*.json") .Concat(Directory.GetFiles(newlyAddedFolderOfThePack, "ReaderToolsWords-*.json"))) { try { var readerSettingsFileName = Path.GetFileName(readerSettingsFile); RobustFile.Copy(readerSettingsFile, Path.Combine(destFolder, readerSettingsFileName), true); if (readerSettingsFileName.StartsWith(ReaderToolsSettingsPrefix)) { var langCode = Path.GetFileNameWithoutExtension(readerSettingsFileName.Substring(ReaderToolsSettingsPrefix.Length)); var allowedWordsSource = Path.Combine(newlyAddedFolderOfThePack, AllowedWordsFolderName); var allowedWordsDest = Path.Combine(destFolder, AllowedWordsFolderName + "-" + langCode); CopyAllowedWords(allowedWordsSource, allowedWordsDest); } } catch (IOException e) { ProblemReportApi.ShowProblemDialog(null, e, "Problem copying Reader Tools Settings from an installed BloomPack.", "nonfatal"); } } }
// ENHANCE: I think it would be good if ProblemReportApi could be split out. // Part of it is related to serving the API requests needed to make the Problem Report Dialog work. That should stay in ProblemReportApi.cs. // Another part of it is related to bring up a browser dialog. I think that part should be moved here into this HtmlErrorReporter class. // It'll be a big job though. // // Also, ProblemReportApi and this class share some parallel ideas because this class was derived from ProblemReportApi, // but they're not 100% identical because this class revamped some of those ideas. // So those will need to be merged. public void ReportNonFatalException(Exception exception, IRepeatNoticePolicy policy) { // Note: I think it's better to call ProblemReportApi directly instead of through NonFatalProblem first. // Otherwise you have to deal with NonFatalProblem's ModalIf, PassiveIf parameters. // And you also have to worry about whether Sentry will happen twice. ProblemReportApi.ShowProblemDialog(GetControlToUse(), exception, null, ProblemLevel.kNonFatal); }
/// <summary> /// Support the "Report a Problem" button when it shows up in the preview window as part of /// a page reporting that we can't open the book for some reason. /// </summary> private void _previewBrowser_OnBrowserClick(object sender, EventArgs e) { if (GetAnchorHref(e).EndsWith("ReportProblem")) { ProblemReportApi.ShowProblemDialog(_reactBookPreviewControl, null, "", "nonfatal"); } }
internal void HandleBloomBookOrder(string order) { _downloadRequest = order; using (var progressDialog = new ProgressDialog()) { _progressDialog = new ProgressDialogWrapper(progressDialog); progressDialog.CanCancel = true; progressDialog.Overview = LocalizationManager.GetString("Download.DownloadingDialogTitle", "Downloading book"); progressDialog.ProgressRangeMaximum = 14; // a somewhat minimal file count. We will fine-tune it when we know. if (IsUrlOrder(order)) { var link = new BloomLinkArgs(order); progressDialog.StatusText = link.Title; } else { progressDialog.StatusText = Path.GetFileNameWithoutExtension(order); } // We must do the download in a background thread, even though the whole process is doing nothing else, // so we can invoke stuff on the main thread to (e.g.) update the progress bar. BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += OnDoDownload; progressDialog.BackgroundWorker = worker; progressDialog.ShowDialog(); // hidden automatically when task completes if (progressDialog.ProgressStateResult != null && progressDialog.ProgressStateResult.ExceptionThatWasEncountered != null) { var exc = progressDialog.ProgressStateResult.ExceptionThatWasEncountered; ProblemReportApi.ShowProblemDialog(null, exc, "", "fatal"); } } }
public void ReportNonFatalMessageWithStackTrace(string messageFormat, params object[] args) { var stackTrace = new StackTrace(true); var userLevelMessage = String.Format(messageFormat, args); string detailedMessage = FormatMessageWithStackTrace(userLevelMessage, stackTrace); ProblemReportApi.ShowProblemDialog(GetControlToUse(), null, detailedMessage, ProblemLevel.kNonFatal, userLevelMessage); }
/// <summary> /// Make the registry entries Bloom requires. /// We do this every time a version of Bloom runs, so that if more than one is installed the latest wins. /// </summary> internal static void MakeBloomRegistryEntries(string[] programArgs) { if (Program.RunningUnitTests) { return; // unit testing. } // When installed in program files we only do registry entries when we are first installed, // thus keeping them consistent for all users, stored in HKLM. if (SharedByAllUsers() && !IsFirstTimeInstall(programArgs)) { return; } _installInLocalMachine = SharedByAllUsers(); if (Platform.IsLinux) { // This will be done by the package installer. return; } var iconDir = FileLocationUtilities.GetDirectoryDistributedWithApplication(true, "icons"); if (iconDir == null) { // Note: if this happens a lot we'd want to make it localizable. I think that's unlikely, so it may not be worth the // burden on localizers. var exception = new FileNotFoundException( "Bloom was not able to find some of its files. The shortcut icon you clicked on may be out of date. Try deleting it and reinstalling Bloom"); ProblemReportApi.ShowProblemDialog(null, exception, "", "fatal"); // Not sure these lines are reachable. Just making sure. Application.Exit(); return; } // BloomCollection icon CreateIconRegistrySettings("BloomCollection", iconDir, "BloomCollectionIcon.ico", "Bloom Book Collection"); // BloomPack icon CreateIconRegistrySettings("BloomPack", iconDir, "BloomPack.ico", "Bloom Book Pack", "FriendlyTypeName"); // JoinBloomTC icon CreateIconRegistrySettings("JoinBloomTC", iconDir, "JoinBloomTC.ico", "Join Bloom Team Collection"); // This might be part of registering as the executable for various file types? // I don't know what does it in wix but it's one of the things the old wix installer created. var exe = Assembly.GetExecutingAssembly().Location; EnsureRegistryValue(@"bloom\shell\open\command", "\"" + exe + "\" \"%1\""); BeTheExecutableFor(".BloomCollection", "BloomCollection file"); BeTheExecutableFor(".BloomPack", "BloomPack file"); BeTheExecutableFor(".JoinBloomTC", "JoinBloom file"); // Make the OS run Bloom when it sees bloom://somebooktodownload BookDownloadSupport.RegisterForBloomUrlProtocol(_installInLocalMachine); }
public void ReportNonFatalExceptionWithMessage(Exception error, string messageFormat, params object[] args) { var message = String.Format(messageFormat, args); var shortMsg = error?.Data["ProblemReportShortMessage"] as string; var imageFilepath = error?.Data["ProblemImagePath"] as string; string[] extraFilepaths = null; if (!String.IsNullOrEmpty(imageFilepath) && RobustFile.Exists(imageFilepath)) { extraFilepaths = new string[] { imageFilepath }; } ProblemReportApi.ShowProblemDialog(GetControlToUse(), error, message, ProblemLevel.kNonFatal, shortMsg, additionalPathsToInclude: extraFilepaths); }
private static void ShowToast(string shortUserLevelMessage, Exception exception, string fullDetailedMessage, bool showSendReport = true, bool showDetailsOnRequest = false) { // The form is used for the screen shot as well as for synchronizing, so get the shell if possible. // See https://issues.bloomlibrary.org/youtrack/issue/BL-8348. var formForSynchronizing = Application.OpenForms.Cast <Form>().Where(x => x is Bloom.Shell).FirstOrDefault(); if (formForSynchronizing == null) { formForSynchronizing = Application.OpenForms.Cast <Form>().LastOrDefault(); } if (formForSynchronizing == null) { return; // can't safely show a toast, may be on wrong thread. } if (formForSynchronizing.InvokeRequired) { formForSynchronizing.BeginInvoke(new Action(() => { ShowToast(shortUserLevelMessage, exception, fullDetailedMessage, showSendReport); })); return; } var toast = new ToastNotifier(); var callToAction = string.Empty; if (showSendReport) { toast.ToastClicked += (s, e) => { ProblemReportApi.ShowProblemDialog(formForSynchronizing, exception, fullDetailedMessage, "nonfatal", shortUserLevelMessage); }; callToAction = "Report"; } else if (showDetailsOnRequest) { toast.ToastClicked += (s, e) => { ErrorReport.NotifyUserOfProblem(new ShowAlwaysPolicy(), null, default(ErrorResult), "{0}", string.Join(Environment.NewLine, // handle Linux newlines on Windows (and vice-versa) fullDetailedMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))); }; callToAction = "Details"; } toast.Image.Image = ToastNotifier.WarningBitmap; toast.Show(shortUserLevelMessage, callToAction, 5); }
private static void CopyAllowedWords(string allowedWordsSource, string allowedWordsDest) { if (Directory.Exists(allowedWordsSource)) { var sourcePath = ""; var destPath = ""; try { Directory.CreateDirectory(allowedWordsDest); foreach (var allowedWordsFile in Directory.GetFiles(allowedWordsSource)) { sourcePath = allowedWordsFile; destPath = Path.Combine(allowedWordsDest, Path.GetFileName(allowedWordsFile)); RobustFile.Copy(allowedWordsFile, destPath, true); } } catch (IOException e) { var msg = $"Cannot copy {sourcePath} to {destPath}."; ProblemReportApi.ShowProblemDialog(null, e, msg, "nonfatal"); } } }
protected override bool DisplayError(Exception exception) { ProblemReportApi.ShowProblemDialog(Form.ActiveForm, exception, "", "fatal"); return(true); }
/// <summary> /// Always log, possibly inform the user, possibly throw the exception /// </summary> /// <param name="modalThreshold">Will show a modal dialog if the channel is this or lower</param> /// <param name="passiveThreshold">Will toast if channel is this or lower (and didn't modal) and shortUserLevelMessage is defined.</param> /// <param name="shortUserLevelMessage">Simple message that fits in small toast notification</param> /// <param name="moreDetails">Info adds information about the problem, which we get if they report the problem</param> /// <param name="exception"></param> /// <param name="showSendReport">Set to 'false' to eliminate yellow screens and "Report" links on toasts</param> public static void Report(ModalIf modalThreshold, PassiveIf passiveThreshold, string shortUserLevelMessage = null, string moreDetails = null, Exception exception = null, bool showSendReport = true) { s_expectedByUnitTest?.ProblemWasReported(); var channel = ApplicationUpdateSupport.ChannelName.ToLowerInvariant(); try { shortUserLevelMessage = shortUserLevelMessage ?? ""; var fullDetailedMessage = shortUserLevelMessage; if (!string.IsNullOrEmpty(moreDetails)) { fullDetailedMessage = fullDetailedMessage + System.Environment.NewLine + moreDetails; } if (exception == null) { //the code below is simpler if we always have an exception, even this thing that gives //us the stacktrace we would otherwise be missing. Note, you might be tempted to throw //and then catch an exception instead, but for some reason the resulting stack trace //would contain only this method. exception = new ApplicationException(new StackTrace().ToString()); } if (Program.RunningUnitTests) { //It's not clear to me what we can do that works for all unit test scenarios... //We can imagine those for which throwing an exception at this point would be helpful, //but there are others in which say, not finding a file is expected. Either way, //the rest of the test should fail if the problem is real, so doing anything here //would just be a help, not really necessary for getting the test to fail. //So, for now I'm going to just go with doing nothing. return; } //if this isn't going modal even for devs, it's just background noise and we don't want the //thousands of exceptions we were getting as with BL-3280 if (modalThreshold != ModalIf.None) { Analytics.ReportException(exception); } Logger.WriteError("NonFatalProblem: " + fullDetailedMessage, exception); if (Program.RunningInConsoleMode) { // This is "nonfatal", so report as best we can (standard error) and keep going... Console.Error.WriteLine($"Nonfatal problem: {fullDetailedMessage}"); return; } //just convert from PassiveIf to ModalIf so that we don't have to duplicate code var passive = (ModalIf)ModalIf.Parse(typeof(ModalIf), passiveThreshold.ToString()); var formForSynchronizing = Application.OpenForms.Cast <Form>().LastOrDefault(); if (formForSynchronizing is ProgressDialog) { // Targetting ProgressDialog doesn't work so well for toasts, since the dialog tends // to disappear immediately and the user never sees the toast. modalThreshold = passive; } if (Matches(modalThreshold).Any(s => channel.Contains(s))) { try { if (showSendReport) { // N.B.: We should be more careful than ever about when we want 'showSendReport' to be 'true', // since this new "nonfatal" UI doesn't have a "Cancel" button. ProblemReportApi.ShowProblemDialog(Form.ActiveForm, exception, fullDetailedMessage, "nonfatal"); } else { // We don't want any notification (MessageBox or toast) targetting a ProgressDialog, // since the dialog seems to disappear quickly and leave us hanging... and not able to show. // We'll keep the form if it's not a ProgressDialog in order to center our message properly. if (formForSynchronizing is ProgressDialog) { MessageBox.Show(fullDetailedMessage, string.Empty, MessageBoxButtons.OK); } else { MessageBox.Show(formForSynchronizing, fullDetailedMessage, string.Empty, MessageBoxButtons.OK); } } } catch (Exception) { //if we're running when the UI is already shut down, the above is going to throw. //At least if we're running in a debugger, we'll stop here: throw new ApplicationException(fullDetailedMessage + "Error trying to report normally."); } return; } if (!string.IsNullOrEmpty(shortUserLevelMessage) && Matches(passive).Any(s => channel.Contains(s))) { ShowToast(shortUserLevelMessage, exception, fullDetailedMessage, showSendReport); } } catch (Exception errorWhileReporting) { // Don't annoy developers for expected error if the internet is not available. if (errorWhileReporting.Message.StartsWith("Bloom could not retrieve the URL") && Bloom.web.UrlLookup.FastInternetAvailable) { Debug.Fail("error in nonfatalError reporting"); } if (channel.Contains("developer") || channel.Contains("alpha")) { ErrorReport.NotifyUserOfProblem(errorWhileReporting, "Error while reporting non fatal error"); } } }
public void ReportFatalException(Exception e) { ProblemReportApi.ShowProblemDialog(GetControlToUse(), e, null, ProblemLevel.kFatal); Quit(); }
public void ReportNonFatalExceptionWithMessage(Exception error, string messageFormat, params object[] args) { var message = String.Format(messageFormat, args); ProblemReportApi.ShowProblemDialog(GetControlToUse(), error, message, ProblemLevel.kNonFatal); }