Exemple #1
0
        private static void ReportMissingFile(string localPath, string path)
        {
            if (path == null)
            {
                path = "(was null)";
            }

            // we have any number of incidences where something asks for a page after we've navigated from it. E.g. BL-3715, BL-3769.
            // I suspect our disposal algorithm is just flawed: the page is removed from the _url cache as soon as we navigated away,
            // which is too soon. But that will take more research and we're trying to finish 3.7.
            // So for now, let's just not to bother the user about an error that is only going to effect thumbnailing.
            if (IsSimulatedFileUrl(localPath))
            {
                //even beta users should not be confronted with this
                // localization not really needed because this is seen only by beta testers.
                NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.Beta, "Page expired", "Server no longer has this page in the memory: " + localPath);
            }
            else if (IsImageTypeThatCanBeReturned(localPath))
            {
                // Complain quietly about missing image files.  See http://issues.bloomlibrary.org/youtrack/issue/BL-3938.
                // The user visible message needs to be localized.  The detailed message is more developer oriented, so should stay in English.  (BL-4151)
                var userMsg   = LocalizationManager.GetString("WebServer.Warning.NoImageFile", "Cannot Find Image File");
                var detailMsg = String.Format("Server could not find the image file {0}. LocalPath was {1}{2}", path, localPath, System.Environment.NewLine);
                NonFatalProblem.Report(ModalIf.None, PassiveIf.All, userMsg, detailMsg);
            }
            else
            {
                // The user visible message needs to be localized.  The detailed message is more developer oriented, so should stay in English.  (BL-4151)
                var userMsg   = LocalizationManager.GetString("WebServer.Warning.NoFile", "Cannot Find File");
                var detailMsg = String.Format("Server could not find the file {0}. LocalPath was {1}{2}", path, localPath, System.Environment.NewLine);
                NonFatalProblem.Report(ModalIf.Beta, PassiveIf.All, userMsg, detailMsg);
            }
        }
Exemple #2
0
        private static void ReportL10NMissingString(string id, string englishText)
        {
            if (ApplicationUpdateSupport.ChannelName.StartsWith("Developer"))
            {
                //It would be a nice improvement to l10n to allow us to write directly to the source-code TMX file, so that the
                //developer just has to check it in. But for now, we can write out a TMX element to the "local" TMX which the developer
                //can put in the distribution one. We prefix it with CopyToDistributionTmx_, which he will have to remove, because
                //otherwise the next time we look for this string, it would get found and we would lose the ability to point out the
                //problem to the developer.
                LocalizationManager.GetDynamicString("Bloom", "CopyToDistributionTmx_" + id, englishText);

                var longMsg =
                    String.Format(
                        "Dear Developer: Ignore this if you are looking at a 3rd-party book that we don't ship with Bloom." +
                        " Please add this dynamic string to the english.tmx file: Id=\"{0}\" English =\"{1}\". " +
                        "The code at this time cannot add this for you, but we have created an element in your local TMX which you can copy over." +
                        " Search for CopyToDistributionTmx_, and remember to remove that from the ID. It needs to be " +
                        "added to the en.tmx, so that it can show up in the list of things to be localized even " +
                        "when the user has not encountered this part of the interface yet.",
                        id,
                        englishText);
                NonFatalProblem.Report(ModalIf.None, PassiveIf.Alpha, "Missing l10n: " + englishText, longMsg);
            }
            else
            {
                NonFatalProblem.Report(ModalIf.None, PassiveIf.Alpha, "Missing l10n: " + englishText,
                                       "Ignore this if you are looking at a 3rd-party book that does not ship with Bloom directly. " +
                                       "Otherwise, please report that " + id + " needs to be " +
                                       "added to the en.tmx, so that it can show up in the list of things to be localized even " +
                                       "when the user has not encountered this part of the interface yet.");
            }
        }
Exemple #3
0
        private void ExportToSpreadsheet(ApiRequest request)
        {
            var book     = _bookSelection.CurrentSelection;
            var bookPath = book.GetPathHtmlFile();

            try
            {
                var    dom                = new HtmlDom(XmlHtmlConverter.GetXmlDomFromHtmlFile(bookPath, false));
                var    exporter           = new SpreadsheetExporter(_webSocketServer, book.CollectionSettings);
                string outputParentFolder = request.RequiredPostDynamic().parentFolderPath;
                string outputFolder       = Path.Combine(outputParentFolder, Path.GetFileNameWithoutExtension(bookPath));
                SetSpreadsheetFolder(book, outputFolder);
                string imagesFolderPath = Path.GetDirectoryName(bookPath);

                exporter.ExportToFolderWithProgress(dom, imagesFolderPath, outputFolder, outputFilePath =>
                {
                    if (outputFilePath != null)
                    {
                        PathUtilities.OpenFileInApplication(outputFilePath);
                    }
                });
            }
            catch (Exception ex)
            {
                var msg = LocalizationManager.GetString("Spreadsheet:ExportFailed", "Export failed: ");
                NonFatalProblem.Report(ModalIf.All, PassiveIf.None, msg + ex.Message, exception: ex);
            }
            request.PostSucceeded();
        }
Exemple #4
0
        public static void CopyImageMetadataToWholeBook(string folderPath, HtmlDom dom, Metadata metadata, IProgress progress)
        {
            progress.WriteStatus("Starting...");

            //First update the images themselves

            int completed   = 0;
            var imgElements = GetImagePaths(folderPath);

            foreach (string path in imgElements)
            {
                progress.ProgressIndicator.PercentCompleted = (int)(100.0 * (float)completed / imgElements.Count());
                progress.WriteStatus("Copying to " + Path.GetFileName(path));

                try
                {
                    metadata.WriteIntellectualPropertyOnly(path);
                }
                catch (TagLib.CorruptFileException e)
                {
                    NonFatalProblem.Report(ModalIf.Beta, PassiveIf.All, "Image metadata problem", "Bloom had a problem accessing the metadata portion of this image " + path + "  ref(BL-3214)", e);
                }

                ++completed;
            }

            //Now update the html attributes which echo some of it, and is used by javascript to overlay displays related to
            //whether the info is there or missing or whatever.

            foreach (XmlElement img in dom.SafeSelectNodes("//img"))
            {
                UpdateImgMetdataAttributesToMatchImage(folderPath, img, progress, metadata);
            }
        }
        public static bool Handle(EndpointRegistration endpointRegistration, IRequestInfo info, CollectionSettings collectionSettings, Book.Book currentBook)
        {
            var request = new ApiRequest(info, collectionSettings, currentBook);

            try
            {
                if (Program.RunningUnitTests)
                {
                    endpointRegistration.Handler(request);
                }
                else
                {
                    var label = "";
                    if (endpointRegistration.DoMeasure && (endpointRegistration.FunctionToGetLabel != null))
                    {
                        label = endpointRegistration.FunctionToGetLabel();
                    }
                    else if (endpointRegistration.DoMeasure)
                    {
                        label = endpointRegistration.MeasurementLabel;
                    }
                    using (endpointRegistration.DoMeasure ? PerformanceMeasurement.Global?.Measure(label) : null)
                    {
                        // Note: If the user is still interacting with the application, openForms could change and become empty
                        var formForSynchronizing = Application.OpenForms.Cast <Form>().LastOrDefault();
                        if (endpointRegistration.HandleOnUIThread && formForSynchronizing != null &&
                            formForSynchronizing.InvokeRequired)
                        {
                            InvokeWithErrorHandling(endpointRegistration, formForSynchronizing, request);
                        }
                        else
                        {
                            endpointRegistration.Handler(request);
                        }
                    }
                }
                if (!info.HaveOutput)
                {
                    throw new ApplicationException(string.Format("The EndpointHandler for {0} never called a Succeeded(), Failed(), or ReplyWith() Function.", info.RawUrl.ToString()));
                }
            }
            catch (System.IO.IOException e)
            {
                var shortMsg = String.Format(L10NSharp.LocalizationManager.GetDynamicString("Bloom", "Errors.CannotAccessFile", "Cannot access {0}"), info.RawUrl);
                var longMsg  = String.Format("Bloom could not access {0}.  The file may be open in another program.", info.RawUrl);
                NonFatalProblem.Report(ModalIf.None, PassiveIf.All, shortMsg, longMsg, e);
                request.Failed(shortMsg);
                return(false);
            }
            catch (Exception e)
            {
                //Hard to reproduce, but I got one of these supertooltip disposal errors in a yellow box
                //while switching between publish tabs (e.g. /bloom/api/publish/android/cleanup).
                //I don't think these are worth alarming the user about, so let's be sensitive to what channel we're on.
                NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, "Error in " + info.RawUrl, exception: e);
                request.Failed("Error in " + info.RawUrl);
                return(false);
            }
            return(true);
        }
        /// <summary>
        /// Check that we are still connected to a current team collection. Answer false if we are not,
        /// as well as switching things to the disconnected state.
        /// </summary>
        /// <returns></returns>
        public bool CheckConnection()
        {
            if (CurrentCollection == null)
            {
                return(false);                // we're already disconnected, or not a TC at all.
            }
            TeamCollectionMessage connectionProblem;

            try
            {
                connectionProblem = CurrentCollection.CheckConnection();
            }
            catch (Exception ex)
            {
                NonFatalProblem.ReportSentryOnly(ex);
                // Unless whatever went wrong left us disconnected, we may as well go ahead and try
                // whatever we were about to do.
                return(CurrentCollection != null);
            }

            if (connectionProblem != null)
            {
                MakeDisconnected(connectionProblem, CurrentCollection.RepoDescription);
                return(false);
            }

            return(true);
        }
        private void RefreshPreview(EpubPublishUiSettings newSettings)
        {
            // We have seen some exceptions thrown during refresh that cause a pretty yellow
            // dialog box pop up informing the user, e.g., that the program couldn't find
            // "api/publish/epub/updatePreview".  Rather than confuse the user, we catch such
            // exceptions here and retry a limited number of times.
            // See https://issues.bloomlibrary.org/youtrack/issue/BL-6763.
            Exception exception = null;

            for (int i = 0; i < 3; ++i)
            {
                try
                {
                    if (UpdatePreview(newSettings, true))
                    {
                        _webSocketServer.SendString(kWebsocketContext, kWebsocketEventId_epubReady, _previewSrc);
                    }
                    return;
                }
                catch (Exception e)
                {
                    exception = e;                      // the original stack trace is rather important for post mortem debugging!
                }
            }
            // Notify the user gently that updating the ePUB preview failed.
            NonFatalProblem.Report(ModalIf.None, PassiveIf.All, "Something went wrong while making the ePUB preview.",
                                   "Updating the ePUB preview failed: " + exception.Message, exception);
        }
Exemple #8
0
 private static bool TryLookupUrl(UrlType urlType, out string url)
 {
     url = null;
     // Once the internet has been found missing, don't bother trying it again for the duration of the program.
     if (!_internetAvailable)
     {
         return(false);
     }
     try
     {
         using (var s3Client = new BloomS3Client(null))
         {
             s3Client.Timeout          = TimeSpan.FromMilliseconds(2500.0);
             s3Client.ReadWriteTimeout = TimeSpan.FromMilliseconds(3000.0);
             s3Client.MaxErrorRetry    = 1;
             var  jsonContent = s3Client.DownloadFile(BloomS3Client.BloomDesktopFiles, kUrlLookupFileName);
             Urls urls        = JsonConvert.DeserializeObject <Urls>(jsonContent);
             url = urls.GetUrlById(urlType.ToJsonPropertyString());
             if (!string.IsNullOrWhiteSpace(url))
             {
                 return(true);
             }
             Logger.WriteEvent("Unable to look up URL type " + urlType);
         }
     }
     catch (Exception e)
     {
         _internetAvailable = false;
         var msg = $"Exception while attempting look up of URL type {urlType}";
         Logger.WriteEvent($"{msg}: {e.Message}");
         NonFatalProblem.ReportSentryOnly(e, msg);
     }
     return(false);
 }
Exemple #9
0
        /// <summary>
        /// Encode the given file as an .mp3 file in the same directory.
        /// </summary>
        /// <returns>Path to the new file</returns>
        public string Encode(string sourcePath)
        {
            string destinationPath = Path.ChangeExtension(sourcePath, AudioRecording.kPublishableExtension);

            try
            {
                if (RobustFile.Exists(destinationPath))
                {
                    RobustFile.Delete(destinationPath);
                }
            }
            catch (Exception)
            {
                var shortMsg = LocalizationManager.GetString("LameEncoder.DeleteFailedShort", "Cannot replace mp3 file. Check antivirus");
                var longMsg  = LocalizationManager.GetString("LameEncoder.DeleteFailedLong", "Bloom could not replace an mp3 file. If this continues, check your antivirus.");
                NonFatalProblem.Report(ModalIf.None, PassiveIf.All, shortMsg, longMsg);
                return(null);
            }

            //-a downmix to mono
            string          arguments = $"-a \"{sourcePath}\" \"{destinationPath}\"";
            ExecutionResult result    = CommandLineRunner.Run(GetLamePath(), arguments, null, 60, new NullProgress());

            result.RaiseExceptionIfFailed("");
            return(destinationPath);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="clientContext">This serves a couple of purposes. First, it is used to filter out messages
        /// to those aimed at a particular client context. For example, two screens, both containing progress boxes,
        /// could be on screen at the same time. The ClientContext would tell us which one is supposed to be
        /// printing out messages coming with the "progress" eventId. </param>
        /// <param name="eventId"></param>
        /// <param name="eventBundle"></param>
        public void SendBundle(string clientContext, string eventId, dynamic eventBundle)
        {
            // We're going to take this and add routing info to it, so it's
            // no longer just the "details".
            var eventObject = eventBundle;

            eventObject.clientContext = clientContext;
            eventObject.id            = eventId;

            //note, if there is no open socket, this isn't going to do anything, and
            //that's (currently) fine.
            lock (this)
            {
                // the ToArray() here gives us a copy so that if a socket
                // is removed while we're doing this, it will be ok
                foreach (var socket in _allSockets.ToArray())
                {
                    // see if it's been removed
                    if (_allSockets.Contains(socket))
                    {
                        // it could *still* be closed by the time we execute this,
                        // I don't know if Sending on a closed socket would throw, so we'll catch it in any case
                        try
                        {
                            socket?.Send(eventObject.ToString());
                        }
                        catch (Exception error)
                        {
                            NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, exception: error);
                        }
                    }
                }
            }
        }
Exemple #11
0
        private void Save()
        {
            // We're checking this because the deserialization routine calls the property setters which triggers a save. We don't
            // want to save while loading.
            if (_loading)
            {
                return;
            }
            var prefs = JsonConvert.SerializeObject(this);

            Debug.Assert(!string.IsNullOrWhiteSpace(prefs));

            try
            {
                if (!string.IsNullOrWhiteSpace(prefs))
                {
                    var temp = new TempFileForSafeWriting(_filePath);
                    RobustFile.WriteAllText(temp.TempFilePath, prefs);
                    temp.WriteWasSuccessful();
                }
            }
            catch (Exception error)
            {
                //For https://silbloom.myjetbrains.com/youtrack/issue/BL-3222  we did a real fix for 3.6.
                //But this will cover us for future errors here, which are not worth stopping the user from doing work.
                NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, "Problem saving book preferences", "book.userprefs could not be saved to " + _filePath, error);
            }
        }
Exemple #12
0
        private static void ReportJavascriptError(string detailsJson)
        {
            string detailsMessage;
            string detailsStack;

            try
            {
                var details = DynamicJson.Parse(detailsJson);
                detailsMessage = details.message;
                detailsStack   = details.stack;
            }
            catch (Exception e)
            {
                // Somehow a problem here seems to kill Bloom. So in desperation we catch everything.
                detailsMessage = "Javascript error reporting failed: " + e.Message;
                detailsStack   = detailsJson;
            }

            var ex = new ApplicationException(detailsMessage + Environment.NewLine + detailsStack);

            // For now unimportant JS errors are still quite common, sadly. Per BL-4301, we don't want
            // more than a toast, even for developers.
            // It would seem logical that we should consider Browser.SuppressJavaScriptErrors here,
            // but somehow none are being reported while making an epub preview, which was its main
            // purpose. So I'm leaving that out until we know we need it.
            NonFatalProblem.Report(ModalIf.None, PassiveIf.Alpha, "A JavaScript error occurred", detailsMessage, ex);
        }
Exemple #13
0
        private void HandleJoinTeamCollection(ApiRequest request)
        {
            try
            {
                FolderTeamCollection.JoinCollectionTeam();
                ReactDialog.CloseCurrentModal();

                Analytics.Track("TeamCollectionJoin",
                                new Dictionary <string, string>()
                {
                    { "CollectionId", _settings?.CollectionId },
                    { "CollectionName", _settings?.CollectionName },
                    { "Backend", _tcManager?.CurrentCollection?.GetBackendType() },
                    { "User", CurrentUser }
                });

                request.PostSucceeded();
            }
            catch (Exception e)
            {
                // Not sure what to do here: joining the collection crashed.
                Logger.WriteError("TeamCollectionApi.HandleJoinTeamCollection() crashed", e);
                var msg = LocalizationManager.GetString("TeamCollection.ErrorJoining", "Could not join Team Collection");
                ErrorReport.NotifyUserOfProblem(e, msg);
                NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()}");

                // Since we have already informed the user above, it is better to just report a success here.
                // Otherwise, they will also get a toast.
                request.PostSucceeded();
            }
        }
Exemple #14
0
 /// <summary>
 /// When images are copied from LibreOffice, images that were jpegs there are converted to bitmaps for the clipboard.
 /// So when we just saved them as bitmaps (pngs), we dramatically inflated the size of user's image files (and
 /// this then led to memory problems).
 /// So the idea here is just to try and detect that we should would be better off saving the image as a jpeg.
 /// Note that even at 100%, we're still going to lose some quality. So this method is only going to recommend
 /// doing that if the size would be at least 50% less.
 /// </summary>
 public static bool ShouldChangeFormatToJpeg(Image image)
 {
     try
     {
         using (var safetyImage = new Bitmap(image))
         //nb: there are cases (notably http://jira.palaso.org/issues/browse/WS-34711, after cropping a jpeg) where we get out of memory if we are not operating on a copy
         {
             using (var jpegFile = new TempFile())
                 using (var pngFile = new TempFile())
                 {
                     RobustImageIO.SaveImage(image, pngFile.Path, ImageFormat.Png);
                     SaveAsTopQualityJpeg(safetyImage, jpegFile.Path);
                     var jpegInfo = new FileInfo(jpegFile.Path);
                     var pngInfo  = new FileInfo(pngFile.Path);
                     // this is just our heuristic.
                     const double fractionOfTheOriginalThatWouldWarrantChangingToJpeg = .5;
                     return(jpegInfo.Length < (pngInfo.Length * (1.0 - fractionOfTheOriginalThatWouldWarrantChangingToJpeg)));
                 }
         }
     }
     catch (OutOfMemoryException e)
     {
         NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, "Could not attempt conversion to jpeg.", "ref BL-3387", exception: e);
         return(false);
     }
 }
Exemple #15
0
        private void HandleGetLog(ApiRequest request)
        {
            /* keeping this around as a comment to make it easier to work on the display
             *
             *
             * _tcManager.MessageLog.WriteMessage(MessageAndMilestoneType.History, "", "blah blah blah blah");
             * _tcManager.MessageLog.WriteMessage(MessageAndMilestoneType.History, "", "Another message. I just simplified this English, but the surrounding code would lead me to think. I just simplified this English, but the surrounding code would lead me to think.");
             * _tcManager.MessageLog.WriteMessage(MessageAndMilestoneType.Error, "", "An error of some sort. I just simplified this English, but the surrounding code would lead me to think. I just simplified this English, but the surrounding code would lead me to think.");
             * _tcManager.MessageLog.WriteMessage(MessageAndMilestoneType.Error, "", "An error of some sort. I just simplified this English, but the surrounding code would lead me to think. I just simplified this English, but the surrounding code would lead me to think.");
             * _tcManager.MessageLog.WriteMessage(MessageAndMilestoneType.History, "", "Another message.");
             * _tcManager.MessageLog.WriteMessage(MessageAndMilestoneType.NewStuff, "", "a new stuff message.");
             * _tcManager.MessageLog.WriteMessage(MessageAndMilestoneType.History, "", "Another message.");
             */
            try
            {
                if (_tcManager.MessageLog == null)
                {
                    request.Failed();
                    return;
                }

                request.ReplyWithJson(JsonConvert.SerializeObject(_tcManager.MessageLog.GetProgressMessages()));
            }
            catch (Exception e)
            {
                // Not sure what to do here: getting the log should never crash.
                Logger.WriteError("TeamCollectionApi.HandleGetLog() crashed", e);
                NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()}");
                request.Failed("get log failed");
            }
        }
Exemple #16
0
        /// <summary>
        /// API Handler when the Auto Segment button is clicked
        ///
        /// Replies with the text read (with orthography conversion applied) if eSpeak completed successfully, or "' if there was an error.
        /// </summary>
        public void ESpeakPreview(ApiRequest request)
        {
            Logger.WriteEvent("AudioSegmentationApi.ESpeakPreview(): ESpeakPreview started.");

            // Parse the JSON containing the text segmentation data.
            string json = request.RequiredPostJson();
            ESpeakPreviewRequest requestParameters = JsonConvert.DeserializeObject <ESpeakPreviewRequest>(json);

            string requestedLangCode = requestParameters.lang;
            string langCode          = GetBestSupportedLanguage(requestedLangCode);

            Logger.WriteEvent($"AudioSegmentationApi.ESpeakPreview(): langCode={langCode ?? "null"}");

            string text = requestParameters.text;

            text = SanitizeTextForESpeakPreview(text);

            string collectionPath = _bookSelection.CurrentSelection.CollectionSettings.FolderPath;
            string orthographyConversionMappingPath = Path.Combine(collectionPath, $"convert_{requestedLangCode}_to_{langCode}.txt");

            if (File.Exists(orthographyConversionMappingPath))
            {
                text = ApplyOrthographyConversion(text, orthographyConversionMappingPath);
            }

            // Even though you theoretically can pass the text through on the command line, it's better to write it to file.
            // The way the command line handles non-ASCII characters is not guaranteed to be the same as when reading from file.
            // It's more straightforward to just read/write it from file.
            // This causes the audio to match the audio that Aeneas will hear when it calls its eSpeak dependency.
            //   (Well, actually it was difficult to verify the exact audio that Aeneas hears, but for -v el "άλφα", verified reading from file caused audio duration to match, but passing on command line caused discrepancy in audio duration)
            string textToSpeakFullPath = Path.GetTempFileName();

            File.WriteAllText(textToSpeakFullPath, text, Encoding.UTF8);

            // No need to wait for espeak before responding.
            var response = new ESpeakPreviewResponse()
            {
                text     = text,
                lang     = langCode,
                filePath = File.Exists(orthographyConversionMappingPath) ? Path.GetFileName(orthographyConversionMappingPath) : ""
            };
            string responseJson = JsonConvert.SerializeObject(response);

            request.ReplyWithJson(responseJson);

            string stdout;
            string command = $"espeak -v {langCode} -f \"{textToSpeakFullPath}\"";
            bool   success = !DoesCommandCauseError(command, out stdout);

            RobustFile.Delete(textToSpeakFullPath);
            Logger.WriteEvent("AudioSegmentationApi.ESpeakPreview() Completed with success = " + success);
            if (!success)
            {
                var message = L10NSharp.LocalizationManager.GetString(
                    "EditTab.Toolbox.TalkingBookTool.ESpeakPreview.Error",
                    "eSpeak failed.",
                    "This text is shown if an error occurred while running eSpeak. eSpeak is a piece of software that this program uses to do text-to-speech (have the computer read text out loud).");
                NonFatalProblem.Report(ModalIf.None, PassiveIf.All, message, null, null, false);                        // toast without allowing error report.
            }
        }
Exemple #17
0
    public static void AddEvent(Book book, BookHistoryEventType eventType, string message = "")
    {
        if (SIL.PlatformUtilities.Platform.IsLinux)
        {
            return;                 // SQLiteConnection never works on Linux.
        }
        try
        {
            using (var db = GetConnection(book.FolderPath))
            {
                GetOrMakeBookRecord(book, db);

                var evt = new BookHistoryEvent()
                {
                    BookId   = book.ID,
                    Message  = message,
                    UserId   = TeamCollectionManager.CurrentUser,
                    UserName = TeamCollectionManager.CurrentUserFirstName,
                    Type     = eventType,
                    // Be sure to use UTC, otherwise, order will not be preserved properly.
                    When = DateTime.UtcNow
                };

                db.Insert(evt);
                db.Close();
            }
        }
        catch (Exception e)
        {
            NonFatalProblem.Report(ModalIf.None, PassiveIf.All, "Problem writing book history", $"folder={book.FolderPath}",
                                   e);
            // swallow... we don't want to prevent whatever was about to happen.
        }
        BloomWebSocketServer.Instance.SendEvent("bookHistory", "eventAdded");
    }
Exemple #18
0
        private void HandleReportBadZip(ApiRequest request)
        {
            var fileEncoded = request.Parameters["file"];
            var file        = UrlPathString.CreateFromUrlEncodedString(fileEncoded).NotEncoded;

            NonFatalProblem.Report(ModalIf.All, PassiveIf.All, (_tcManager.CurrentCollection as FolderTeamCollection).GetSimpleBadZipFileMessage(file), additionalFilesToInclude: new[] { file });
            request.PostSucceeded();
        }
Exemple #19
0
 private void ProcessPdfFurtherAndSave(ProcessPdfWithGhostscript.OutputType type, string outputPath)
 {
     if (type == ProcessPdfWithGhostscript.OutputType.Printshop &&
         !Bloom.Properties.Settings.Default.AdobeColorProfileEula2003Accepted)
     {
         var prolog = L10NSharp.LocalizationManager.GetString(@"PublishTab.PrologToAdobeEula",
                                                              "Bloom uses Adobe color profiles to convert PDF files from using RGB color to using CMYK color.  This is part of preparing a \"PDF for a print shop\".  You must agree to the following license in order to perform this task in Bloom.",
                                                              @"Brief explanation of what this license is and why the user needs to agree to it");
         using (var dlg = new Bloom.Registration.LicenseDialog("AdobeColorProfileEULA.htm", prolog))
         {
             dlg.Text = L10NSharp.LocalizationManager.GetString(@"PublishTab.AdobeEulaTitle",
                                                                "Adobe Color Profile License Agreement", @"dialog title for license agreement");
             if (dlg.ShowDialog() != DialogResult.OK)
             {
                 var msg = L10NSharp.LocalizationManager.GetString(@"PublishTab.PdfNotSavedWhy",
                                                                   "The PDF file has not been saved because you chose not to allow producing a \"PDF for print shop\".",
                                                                   @"explanation that file was not saved displayed in a message box");
                 var heading = L10NSharp.LocalizationManager.GetString(@"PublishTab.PdfNotSaved",
                                                                       "PDF Not Saved", @"title for the message box");
                 MessageBox.Show(msg, heading, MessageBoxButtons.OK, MessageBoxIcon.Information);
                 return;
             }
         }
         Bloom.Properties.Settings.Default.AdobeColorProfileEula2003Accepted = true;
         Bloom.Properties.Settings.Default.Save();
     }
     using (var progress = new SIL.Windows.Forms.Progress.ProgressDialog())
     {
         progress.ProgressRangeMinimum = 0;
         progress.ProgressRangeMaximum = 100;
         progress.Overview             = L10NSharp.LocalizationManager.GetString(@"PublishTab.PdfMaker.Saving",
                                                                                 "Saving PDF...",
                                                                                 @"Message displayed in a progress report dialog box");
         progress.BackgroundWorker         = new BackgroundWorker();
         progress.BackgroundWorker.DoWork += (object sender, DoWorkEventArgs e) => {
             var pdfProcess = new ProcessPdfWithGhostscript(type, sender as BackgroundWorker);
             pdfProcess.ProcessPdfFile(PdfFilePath, outputPath, false);
         };
         progress.BackgroundWorker.ProgressChanged += (object sender, ProgressChangedEventArgs e) => {
             progress.Progress = e.ProgressPercentage;
             var status = e.UserState as string;
             if (!String.IsNullOrWhiteSpace(status))
             {
                 progress.StatusText = status;
             }
         };
         progress.ShowDialog();                  // will start the background process when loaded/showing
         if (progress.ProgressStateResult != null && progress.ProgressStateResult.ExceptionThatWasEncountered != null)
         {
             string shortMsg = L10NSharp.LocalizationManager.GetString(@"PublishTab.PdfMaker.ErrorSaving",
                                                                       "Error compressing or recoloring the PDF file",
                                                                       @"Message briefly displayed to the user in a toast");
             var longMsg = String.Format("Exception encountered processing the PDF file: {0}", progress.ProgressStateResult.ExceptionThatWasEncountered);
             NonFatalProblem.Report(ModalIf.None, PassiveIf.All, shortMsg, longMsg, progress.ProgressStateResult.ExceptionThatWasEncountered);
         }
     }
 }
        public static IEnumerable <Layout> GetLayoutChoices(HtmlDom dom, IFileLocator fileLocator)
        {
            //here we walk through all the stylesheets, looking for one with the special style which tells us which page/orientations it supports
            foreach (XmlElement link in dom.SafeSelectNodes("//link[@rel='stylesheet']"))
            {
                var fileName = link.GetStringAttribute("href");
                if (fileName.ToLowerInvariant().Contains("mode") || fileName.ToLowerInvariant().Contains("page") ||
                    fileName.ToLowerInvariant().Contains("matter") || fileName.ToLowerInvariant().Contains("languagedisplay") ||
                    fileName.ToLowerInvariant().Contains("origami"))
                {
                    continue;
                }

                fileName = fileName.Replace("file://", "").Replace("%5C", "/").Replace("%20", " ");
                var path = fileLocator.LocateFile(fileName);
                if (string.IsNullOrEmpty(path))
                {
                    // We're looking for a block of json that is typically found in Basic Book.css or a comparable place for
                    // a book based on some other template. Caling code is prepared for not finding this block.
                    // It seems safe to ignore a reference to some missing style sheet.
                    NonFatalProblem.Report(ModalIf.None, PassiveIf.Alpha, "Could not find " + fileName + " while looking for size choices");
                    continue;
                }
                var contents = RobustFile.ReadAllText(path);
                var start    = contents.IndexOf("STARTLAYOUTS");
                if (start < 0)
                {
                    continue;                      //yield break; // continue;//move on to the next stylesheet
                }
                start += "STARTLAYOUTS".Length;
                var end = contents.IndexOf("ENDLAYOUTS", start);
                var s   = contents.Substring(start, end - start);

                IEnumerable <Layout> layouts = null;

                try
                {
                    layouts = Layout.GetConfigurationsFromConfigurationOptionsString(s);
                }
                catch (Exception e)
                {
                    throw new ApplicationException("Problem parsing the 'layouts' comment of " + fileName + ". The contents were\r\n" + s, e);
                }


                foreach (var p in layouts)
                {
                    yield return(p);
                }
                yield break;
            }

            //default to A5Portrait
            yield return(new Layout {
                SizeAndOrientation = FromString("A5Portrait")
            });
        }
Exemple #21
0
        public void HandleChooseFolderLocation(ApiRequest request)
        {
            try
            {
                string sharedFolder;
                // One of the few places that knows we're using a particular implementation
                // of TeamRepo. But we have to know that to create it. And of course the user
                // has to chose a folder to get things started.
                // We'll need a different API or something similar if we ever want to create
                // some other kind of repo.
                using (var dlg = new FolderBrowserDialog())
                {
                    // Default to the Dropbox folder if one is found.
                    var dropboxFolder = DropboxUtils.GetDropboxFolderPath();
                    if (!String.IsNullOrEmpty(dropboxFolder))
                    {
                        dlg.SelectedPath = dropboxFolder;
                    }
                    dlg.ShowNewFolderButton = true;
                    dlg.Description         = LocalizationManager.GetString("TeamCollection.SelectFolder",
                                                                            "Select or create the folder where this collection will be shared");
                    if (DialogResult.OK != dlg.ShowDialog())
                    {
                        request.Failed();
                        return;
                    }

                    sharedFolder = dlg.SelectedPath;
                }
                // We send the result through a websocket rather than simply returning it because
                // if the user is very slow (one site said FF times out after 90s) the browser may
                // abandon the request before it completes. The POST result is ignored and the
                // browser simply listens to the socket.
                // We'd prefer this request to return immediately and set a callback to run
                // when the dialog closes and handle the results, but FolderBrowserDialog
                // does not offer such an API. Instead, we just ignore any timeout
                // in our Javascript code.
                dynamic messageBundle = new DynamicJson();
                messageBundle.repoFolderPath = sharedFolder;
                messageBundle.problem        = ProblemsWithLocation(sharedFolder);
                // This clientContext must match what is being listened for in CreateTeamCollection.tsx
                _socketServer.SendBundle("teamCollectionCreate", "shared-folder-path", messageBundle);

                request.PostSucceeded();
            }
            catch (Exception e)
            {
                // Not sure what to do here: choosing the collection folder should never crash.
                Logger.WriteError("TeamCollectionApi.HandleChooseFolderLocation() crashed", e);
                NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()}");
                request.Failed("choose folder location failed");
            }
        }
        public void MakeBooklet()
        {
            if (IsMakingPdf)
            {
                // Can't start again until this one finishes
                return;
            }
            _model.PdfGenerationSucceeded = false;             // and so it stays unless we generate it successfully.
            if (_uploadRadio.Checked)
            {
                // We aren't going to display it, so don't bother generating it unless the user actually uploads.
                // Unfortunately, the completion of the generation process is normally responsible for putting us into
                // the right display mode for what we generated (or failed to), after this routine puts us into the
                // mode that shows generation is pending. For the upload button case, we want to go straight to the Upload
                // mode, so the upload control appears. This is a bizarre place to do it, but I can't find a better one.
                SetDisplayMode(PublishModel.DisplayModes.Upload);
                return;
            }
            if (_epubRadio.Checked)
            {
                // We aren't going to display it, so don't bother generating it.
                // Unfortunately, the completion of the generation process is normally responsible for putting us into
                // the right display mode for what we generated (or failed to), after this routine puts us into the
                // mode that shows generation is pending. For the ePUB button case, we want to go straight to the ePUB preview.
                SetDisplayMode(PublishModel.DisplayModes.EPUB);
                return;
            }

            SetDisplayMode(PublishModel.DisplayModes.Working);

            using (_progress = new SIL.Windows.Forms.Progress.ProgressDialog())
            {
                _progress.Overview = L10NSharp.LocalizationManager.GetString(@"PublishTab.PdfMaker.Creating",
                                                                             "Creating PDF...",
                                                                             @"Message displayed in a progress report dialog box");
                _progress.BackgroundWorker = _makePdfBackgroundWorker;
                _makePdfBackgroundWorker.ProgressChanged += UpdateProgress;
                _progress.ShowDialog();                 // will start the background process when loaded/showing
                _makePdfBackgroundWorker.ProgressChanged -= UpdateProgress;
                _progress.BackgroundWorker = null;
                if (_progress.ProgressStateResult != null && _progress.ProgressStateResult.ExceptionThatWasEncountered != null)
                {
                    string shortMsg = L10NSharp.LocalizationManager.GetString(@"PublishTab.PdfMaker.ErrorProcessing",
                                                                              "Error creating, compressing, or recoloring the PDF file",
                                                                              @"Message briefly displayed to the user in a toast");
                    var longMsg = String.Format("Exception encountered processing the PDF file: {0}", _progress.ProgressStateResult.ExceptionThatWasEncountered);
                    NonFatalProblem.Report(ModalIf.None, PassiveIf.All, shortMsg, longMsg, _progress.ProgressStateResult.ExceptionThatWasEncountered);
                }
            }
            _progress = null;
        }
Exemple #23
0
        // overridden in tests
        internal virtual void ReportMissingFile(string localPath, string path)
        {
            if (path == null)
            {
                path = "(was null)";
            }
            var stuffToIgnore = new[] {
                //browser/debugger stuff
                "favicon.ico", ".map",
                // Audio files may well be missing because we look for them as soon
                // as we define an audio ID, but they wont' exist until we record something.
                "/audio/",
                // PageTemplatesApi creates a path containing this for a missing template.
                // it gets reported inside the page chooser dialog.
                "missingpagetemplate",
                // This is readium stuff that we don't ship with, because they are needed by the original reader to support display and implementation
                // of controls we hide for things like adding books to collection, displaying the collection, playing audio (that last we might want back one day).
                EpubMaker.kEPUBExportFolder.ToLowerInvariant()
            };

            if (stuffToIgnore.Any(s => (localPath.ToLowerInvariant().Contains(s))))
            {
                return;
            }

            // we have any number of incidences where something asks for a page after we've navigated from it. E.g. BL-3715, BL-3769.
            // I suspect our disposal algorithm is just flawed: the page is removed from the _url cache as soon as we navigated away,
            // which is too soon. But that will take more research and we're trying to finish 3.7.
            // So for now, let's just not to bother the user about an error that is only going to effect thumbnailing.
            if (IsSimulatedFileUrl(localPath))
            {
                //even beta users should not be confronted with this
                // localization not really needed because this is seen only by beta testers.
                NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.Beta, "Page expired", "Server no longer has this page in the memory: " + localPath);
            }
            else if (IsImageTypeThatCanBeReturned(localPath))
            {
                // Complain quietly about missing image files.  See http://issues.bloomlibrary.org/youtrack/issue/BL-3938.
                // The user visible message needs to be localized.  The detailed message is more developer oriented, so should stay in English.  (BL-4151)
                var userMsg   = LocalizationManager.GetString("WebServer.Warning.NoImageFile", "Cannot Find Image File");
                var detailMsg = String.Format("Server could not find the image file {0}. LocalPath was {1}{2}", path, localPath, System.Environment.NewLine);
                NonFatalProblem.Report(ModalIf.None, PassiveIf.All, userMsg, detailMsg);
            }
            else
            {
                // The user visible message needs to be localized.  The detailed message is more developer oriented, so should stay in English.  (BL-4151)
                var userMsg   = LocalizationManager.GetString("WebServer.Warning.NoFile", "Cannot Find File");
                var detailMsg = String.Format("Server could not find the file {0}. LocalPath was {1}{2}", path, localPath, System.Environment.NewLine);
                NonFatalProblem.Report(ModalIf.Beta, PassiveIf.All, userMsg, detailMsg);
            }
        }
Exemple #24
0
        public static string GetXMatterDirectory(string nameOfXMatterPack, IFileLocator fileLocator, string errorMsg, bool throwIfError, bool silent = false)
        {
            var directoryName = nameOfXMatterPack + "-XMatter";

            if (Program.RunningHarvesterMode)
            {
                // Get a new file locator that also searches the Custom XMatter directory.
                // This allows the Harvseter to preserve custom branding if those books are uploaded to web. (See BL-BL-9084)
                var extraSearchPaths = new string[]  { BloomFileLocator.GetCustomXMatterDirectory() };
                fileLocator = fileLocator.CloneAndCustomize(extraSearchPaths);
            }

            if (silent)
            {
                // Using LocateDirectoryWithThrow is quite expensive for directories we don't find...the Exception it creates, which we don't use,
                // includes a concatenation of a long list of paths it searched in. (It's quite common now to search for an xmatter directory
                // we don't often find, such as looking for one called Traditional-Device when publishing something with Traditional xmatter
                // on a device.
                try
                {
                    var result = fileLocator.LocateDirectory(directoryName);
                    if (result == null || !Directory.Exists(result))
                    {
                        return(null);
                    }
                    return(result);
                }
                catch (ApplicationException)
                {
                    return(null);
                }
            }
            try
            {
                return(fileLocator.LocateDirectoryWithThrow(directoryName));
            }
            catch (ApplicationException error)
            {
                if (silent)
                {
                    return(null);
                }
                var frontBackMatterProblem = LocalizationManager.GetString("Errors.XMatterProblemLabel", "Front/Back Matter Problem", "This shows in the 'toast' that pops up to notify the user of a non-fatal problem.");
                NonFatalProblem.Report(ModalIf.None, PassiveIf.All, frontBackMatterProblem, errorMsg, error);
                if (throwIfError)
                {
                    throw new ApplicationException(errorMsg);
                }
            }
            return(null);
        }
        private static void DisplayProblem(Exception e, string message, bool showSendReport = true)
        {
            var action      = new Action(() => NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, message, null, e, showSendReport));
            var shellWindow = ShellWindow;

            if (shellWindow != null)
            {
                shellWindow.Invoke(action);
            }
            else
            {
                action.Invoke();
            }
        }
Exemple #26
0
 public void HandleRepoFolderPath(ApiRequest request)
 {
     try
     {
         Debug.Assert(request.HttpMethod == HttpMethods.Get, "only get is implemented for the teamCollection/repoFolderPath api endpoint");
         request.ReplyWithText(_tcManager.CurrentCollectionEvenIfDisconnected?.RepoDescription ?? "");
     }
     catch (Exception e)
     {
         // Not sure what to do here: getting the repo's folder path should never crash.
         Logger.WriteError("TeamCollectionApi.HandleRepoFolderPath() crashed", e);
         NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()}");
         request.Failed("get repo folder path failed");
     }
 }
Exemple #27
0
 private static bool TryGetUrlDataFromServer()
 {
     // Once the internet has been found missing, don't bother trying it again for the duration of the program.
     // And if we got the data once, it's very unlikely we'll get something new by trying again.
     if (!_internetAvailable || _gotJsonFromServer)
     {
         return(false);
     }
     // It's pathologically possible that two threads at about the same time come here and both send
     // the query. If so, no great harm done...they'll both put the same values into the dictionary.
     // And in practice, it won't happen...one call to this, and only one, happens very early in
     // Bloom's startup code, and after that _gotJsonFromServer will be true.
     // I don't think it's worth the effort to set up locks and guarantee that only on thread
     // sends the request.
     try
     {
         using (var s3Client = new BloomS3Client(null))
         {
             s3Client.Timeout          = TimeSpan.FromMilliseconds(2500.0);
             s3Client.ReadWriteTimeout = TimeSpan.FromMilliseconds(3000.0);
             s3Client.MaxErrorRetry    = 1;
             var  jsonContent = s3Client.DownloadFile(BloomS3Client.BloomDesktopFiles, kUrlLookupFileName);
             Urls urls        = JsonConvert.DeserializeObject <Urls>(jsonContent);
             // cache them all, so we don't have to repeat the server request.
             foreach (UrlType urlType in Enum.GetValues(typeof(UrlType)))
             {
                 var url = urls.GetUrlById(urlType.ToJsonPropertyString());
                 if (!string.IsNullOrWhiteSpace(url))
                 {
                     s_liveUrlCache.AddOrUpdate(urlType, url, (type, s) => s);
                 }
             }
             // Do this only after we populated the dictionary; we definitely don't want
             // another thread to return false because it thinks things are already loaded
             // when the value it wanted isn't in the dictionary.
             _gotJsonFromServer = true;
             return(true);                    // we did the retrieval, it's worth checking the dictionary again.
         }
     }
     catch (Exception e)
     {
         _internetAvailable = false;
         var msg = $"Exception while attempting get URL data from server";
         Logger.WriteEvent($"{msg}: {e.Message}");
         NonFatalProblem.ReportSentryOnly(e, msg);
     }
     return(false);
 }
Exemple #28
0
        public void HandleAttemptLockOfCurrentBook(ApiRequest request)
        {
            if (!_tcManager.CheckConnection())
            {
                request.Failed();
                return;
            }

            try
            {
                // Could be a problem if there's no current book or it's not in the collection folder.
                // But in that case, we don't show the UI that leads to this being called.
                var success = _tcManager.CurrentCollection.AttemptLock(BookFolderName);
                if (success)
                {
                    UpdateUiForBook();

                    Analytics.Track("TeamCollectionCheckoutBook",
                                    new Dictionary <string, string>()
                    {
                        { "CollectionId", _settings?.CollectionId },
                        { "CollectionName", _settings?.CollectionName },
                        { "Backend", _tcManager?.CurrentCollection?.GetBackendType() },
                        { "User", CurrentUser },
                        { "BookId", _bookSelection?.CurrentSelection?.ID },
                        { "BookName", _bookSelection?.CurrentSelection?.Title }
                    });
                }

                request.ReplyWithBoolean(success);
            }
            catch (Exception e)
            {
                var msg = MakeLockFailedMessageFromException(e, BookFolderName);
                // Pushing an error into the log will show the Reload Collection button. It's not obvious this
                // is useful here, since we don't know exactly what went wrong. However, it at least gives the user
                // the option to try it.
                var log = _tcManager?.CurrentCollection?.MessageLog;
                if (log != null)
                {
                    log.WriteMessage(msg);
                }
                Logger.WriteError(msg.TextForDisplay, e);
                NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()}");
                request.Failed("lock failed");
            }
        }
Exemple #29
0
 public void HandleIsTeamCollectionEnabled(ApiRequest request)
 {
     try
     {
         // We don't need any of the Sharing UI if the selected book isn't in the editable
         // collection (or if the collection doesn't have a Team Collection at all).
         request.ReplyWithBoolean(_tcManager.CurrentCollectionEvenIfDisconnected != null &&
                                  (_bookSelection.CurrentSelection == null || _bookSelection.CurrentSelection.IsEditable));
     }
     catch (Exception e)
     {
         // Not sure what to do here: checking whether TeamCollection is enabled should never crash.
         Logger.WriteError("TeamCollectionApi.HandleIsTeamCollectionEnabled() crashed", e);
         NonFatalProblem.ReportSentryOnly(e, $"Something went wrong for {request.LocalPath()}");
         request.Failed("checking if Team Collections are enabled failed");
     }
 }
Exemple #30
0
        /// <summary>
        /// branding folders can optionally contain a branding.json file which aligns with this Settings class
        /// </summary>
        /// <param name="brandingNameOrFolderPath"> Normally, the branding is just a name, which we look up in the official branding folder
        //but unit tests can instead provide a path to the folder.
        /// </param>
        public static Settings GetSettings(string brandingNameOrFolderPath)
        {
            try
            {
                ParseBrandingKey(brandingNameOrFolderPath, out var brandingFolderName, out var flavor);

                // check to see if we have a special branding.json just for this flavor.
                // Note that we could instead add code that allows a single branding.json to
                // have rules that apply only on a flavor basis. As of 4.9, all we have is the
                // ability for a branding.json (and anything else) to use "{flavor}" anywhere in the
                // name of an image; this will often be enough to avoid making a new branding.json.
                // But if we needed to have different boilerplate text, well then we would need to
                // either use this here mechanism (separate json) or implement the ability to add
                // "flavor:" to the rules.
                string settingsPath = null;
                if (!string.IsNullOrEmpty(flavor))
                {
                    settingsPath = BloomFileLocator.GetOptionalBrandingFile(brandingFolderName, "branding[" + flavor + "].json");
                }

                // if not, fall bck to just "branding.json"
                if (string.IsNullOrEmpty(settingsPath))
                {
                    settingsPath = BloomFileLocator.GetOptionalBrandingFile(brandingFolderName, "branding.json");
                }

                if (!string.IsNullOrEmpty(settingsPath))
                {
                    var content  = RobustFile.ReadAllText(settingsPath);
                    var settings = JsonConvert.DeserializeObject <Settings>(content);
                    if (settings == null)
                    {
                        NonFatalProblem.Report(ModalIf.Beta, PassiveIf.All, "Trouble reading branding settings",
                                               "branding.json of the branding " + brandingNameOrFolderPath + " may be corrupt. It had: " + content);
                        return(null);
                    }
                    return(settings);
                }
            }
            catch (Exception e)
            {
                NonFatalProblem.Report(ModalIf.Beta, PassiveIf.All, "Trouble reading branding settings", exception: e);
            }
            return(null);
        }