Exemple #1
0
        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");
                }
            }
        }
Exemple #2
0
 /// <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");
     }
 }
Exemple #3
0
 // 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>
        /// 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");
                }
            }
        }
Exemple #5
0
        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);
        }
Exemple #6
0
        /// <summary>
        /// Sets up the Bloom server and registers the Problem Report API handler to it
        /// </summary>
        private void SetupApiHandler(BookSelection bookSelection)
        {
            _server = new BloomServer(bookSelection);
            var controller = new ProblemReportApi(bookSelection);

            controller.RegisterWithApiHandler(_server.ApiHandler);
        }
Exemple #7
0
        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);
        }
Exemple #8
0
        public void ReportHeadingHtml_GivenPreEncodedHtmlInSummary_ReturnsRawHtmlDirectly()
        {
            bool isSummaryPreEncoded = true;

            SetupApiHandler(GetDefaultBookSelection());

            ProblemReportApi.GatherReportInfoExceptScreenshot(null, "Fake Details", "Fake Problem: See <a href=\"http://bloomlibrary.org\">Bloom Library</a> for help", isSummaryPreEncoded);
            var result = ApiTest.GetString(_server, "problemReport/reportHeadingHtml");

            Assert.That(result, Is.EqualTo("Fake Problem: See <a href=\"http://bloomlibrary.org\">Bloom Library</a> for help"));
        }
Exemple #9
0
        public void ReportHeadingHtml_GivenUnencodedHtmlInSummary_EncodesTheHtml()
        {
            bool isSummaryPreEncoded = false;

            SetupApiHandler(GetDefaultBookSelection());

            ProblemReportApi.GatherReportInfoExceptScreenshot(null, "Fake Details", "Fake Problem: See <a href=\"http://bloomlibrary.org\">Bloom Library</a> for help", isSummaryPreEncoded);
            var result = ApiTest.GetString(_server, "problemReport/reportHeadingHtml");

            Assert.That(result, Is.EqualTo("Fake Problem: See &lt;a href=&quot;http://bloomlibrary.org&quot;&gt;Bloom Library&lt;/a&gt; for help"));
        }
        /// <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);
        }
Exemple #11
0
        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);
        }
Exemple #12
0
        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);
 }
Exemple #15
0
        /// <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");
                }
            }
        }
Exemple #16
0
        public void ReportNonFatalExceptionWithMessage(Exception error, string messageFormat, params object[] args)
        {
            var message = String.Format(messageFormat, args);

            ProblemReportApi.ShowProblemDialog(GetControlToUse(), error, message, ProblemLevel.kNonFatal);
        }
Exemple #17
0
        private void ShowNotifyDialog(string severity, string messageText, Exception exception,
                                      string reportButtonLabel, string secondaryButtonLabel)
        {
            // Before we do anything that might be "risky", put the problem in the log.
            ProblemReportApi.LogProblem(exception, messageText, severity);

            // ENHANCE: Allow the caller to pass in the control, which would be at the front of this.
            //System.Windows.Forms.Control control = Form.ActiveForm ?? FatalExceptionHandler.ControlOnUIThread;
            var control        = GetControlToUse();
            var isSyncRequired = false;

            SafeInvoke.InvokeIfPossible("Show Error Reporter", control, isSyncRequired, () =>
            {
                // Uses a browser dialog to show the problem report
                try
                {
                    StartupScreenManager.CloseSplashScreen();                     // if it's still up, it'll be on top of the dialog

                    var message = GetMessage(messageText, exception);

                    if (!Api.BloomServer.ServerIsListening)
                    {
                        // There's no hope of using the HtmlErrorReporter dialog if our server is not yet running.
                        // We'll likely get errors, maybe Javascript alerts, that won't lead to a clean fallback to
                        // the exception handler below. Besides, failure of HtmlErrorReporter in these circumstances
                        // is expected; we just want to cleanly report the original problem, not to report a
                        // failure of error handling.

                        // Note: HtmlErrorReporter supports up to 3 buttons (OK, Report, and [Secondary action]), but the fallback reporter only supports a max of two.
                        // Well, just going to have to drop the secondary action.

                        ShowFallbackProblemDialog(severity, exception, messageText, null, false);
                        return;
                    }

                    object props = new { level = ProblemLevel.kNotify, reportLabel = reportButtonLabel, secondaryLabel = secondaryButtonLabel, message = message };

                    // Precondition: we must be on the UI thread for Gecko to work.
                    using (var dlg = BrowserDialogFactory.CreateReactDialog("problemReportBundle", props))
                    {
                        dlg.FormBorderStyle = FormBorderStyle.FixedToolWindow; // Allows the window to be dragged around
                        dlg.ControlBox      = true;                            // Add controls like the X button back to the top bar
                        dlg.Text            = "";                              // Remove the title from the WinForms top bar

                        dlg.Width = 620;

                        // 360px was experimentally determined as what was needed for the longest known text for NotifyUserOfProblem
                        // (which is "Before saving, Bloom did an integrity check of your book [...]" from BookStorage.cs)
                        // You can make this height taller if need be.
                        // A scrollbar will appear if the height is not tall enough for the text
                        dlg.Height = 360;

                        // ShowDialog will cause this thread to be blocked (because it spins up a modal) until the dialog is closed.
                        BloomServer.RegisterThreadBlocking();

                        try
                        {
                            dlg.ShowDialog();

                            // Take action if the user clicked a button other than Close
                            if (dlg.CloseSource == "closedByAlternateButton" && OnSecondaryActionPressed != null)
                            {
                                OnSecondaryActionPressed(exception, message);
                            }
                            else if (dlg.CloseSource == "closedByReportButton")
                            {
                                if (OnReportButtonPressed != null)
                                {
                                    OnReportButtonPressed(exception, message);
                                }
                                else
                                {
                                    DefaultOnReportPressed(exception, message);
                                }
                            }

                            // Note: With the way LibPalaso's ErrorReport is designed,
                            // its intention is that after OnShowDetails is invoked and closed, you will not come back to the Notify Dialog
                            // This code has been implemented to follow that model
                            //
                            // But now that we have more options, it might be nice to come back to this dialog.
                            // If so, you'd need to add/update some code in this section.
                        }
                        finally
                        {
                            ResetToDefaults();
                            BloomServer.RegisterThreadUnblocked();
                        }
                    }
                }
                catch (Exception errorReporterException)
                {
                    Logger.WriteError("*** HtmlErrorReporter threw an exception trying to display", errorReporterException);
                    // At this point our problem reporter has failed for some reason, so we want the old WinForms handler
                    // to report both the original error for which we tried to open our dialog and this new one where
                    // the dialog itself failed.
                    // In order to do that, we create a new exception with the original exception (if there was one) as the
                    // inner exception. We include the message of the exception we just caught. Then we call the
                    // old WinForms fatal exception report directly.
                    // In any case, both of the errors will be logged by now.
                    var message = "Bloom's error reporting failed: " + errorReporterException.Message;

                    // Fallback to Winforms in case of trouble getting the browser to work
                    var fallbackReporter = new WinFormsErrorReporter();
                    // Food for thought: is it really fatal of the Notify Dialog had an exception? Maybe NonFatal makes more sense
                    fallbackReporter.ReportFatalException(new ApplicationException(message, exception ?? errorReporterException));
                }
            });
        }
Exemple #18
0
 public void ReportFatalException(Exception e)
 {
     ProblemReportApi.ShowProblemDialog(GetControlToUse(), e, null, ProblemLevel.kFatal);
     Quit();
 }