Beispiel #1
0
 public void Image_FromFile_GivesImage()
 {
     using (Bitmap bitmap = new Bitmap(10, 10))
         using (var temp = TempFile.CreateAndGetPathButDontMakeTheFile())
         {
             bitmap.Save(temp.Path);
             using (var pi = PalasoImage.FromFile(temp.Path))
             {
                 Assert.AreEqual(10, pi.Image.Width);
             }
         }
 }
Beispiel #2
0
        /// <summary>
        /// Creates the .bloomd and bloomdigital folders
        /// </summary>
        private static CreateArtifactsExitCode CreateBloomDigitalArtifacts(string bookPath, string creator, string zippedBloomDOutputPath, string unzippedBloomDigitalOutputPath)
        {
#if DEBUG
            // Useful for allowing debugging of Bloom while running the harvester
            //MessageBox.Show("Attach debugger now");
#endif
            var exitCode = CreateArtifactsExitCode.Success;

            using (var tempBloomD = TempFile.CreateAndGetPathButDontMakeTheFile())
            {
                if (String.IsNullOrEmpty(zippedBloomDOutputPath))
                {
                    zippedBloomDOutputPath = tempBloomD.Path;
                }

                BookServer bookServer = _projectContext.BookServer;

                using (var folderForUnzipped = new TemporaryFolder("BloomCreateArtifacts_Unzipped"))
                {
                    // Ensure directory exists, just in case.
                    Directory.CreateDirectory(Path.GetDirectoryName(zippedBloomDOutputPath));

                    // Make the bloomd
                    string unzippedPath = Publish.Android.BloomReaderFileMaker.CreateBloomDigitalBook(
                        zippedBloomDOutputPath,
                        bookPath,
                        bookServer,
                        System.Drawing.Color.Azure,                 // TODO: What should this be?
                        new Bloom.web.NullWebSocketProgress(),
                        folderForUnzipped,
                        creator);

                    // Currently the zipping process does some things we actually need, like making the cover picture
                    // transparent (BL-7437). Eventually we plan to separate the preparation and zipping steps (BL-7445).
                    // Until that is done, the most reliable way to get an unzipped BloomD for our preview is to actually
                    // unzip the BloomD.
                    if (!String.IsNullOrEmpty(unzippedBloomDigitalOutputPath))
                    {
                        SIL.IO.RobustIO.DeleteDirectory(unzippedBloomDigitalOutputPath, recursive: true);                           // In case the folder isn't already empty

                        // Ensure directory exists, just in case.
                        Directory.CreateDirectory(Path.GetDirectoryName(unzippedBloomDigitalOutputPath));

                        ZipFile.ExtractToDirectory(zippedBloomDOutputPath, unzippedBloomDigitalOutputPath);

                        exitCode |= RenameBloomDigitalFiles(unzippedBloomDigitalOutputPath);
                    }
                }
            }

            return(exitCode);
        }
Beispiel #3
0
 public void Export_Nominal_WritesExpectedContents()
 {
     using (var temp = TempFile.CreateAndGetPathButDontMakeTheFile())
     {
         AudacityExporter.Export(temp.Path, CreateTier());
         Debug.WriteLine(File.ReadAllText(temp.Path));
         var lines = GetLines(temp).ToArray();
         //Assert.AreEqual(3/*segments*/+1/*blank*/, lines.Count());
         Assert.AreEqual(3, lines.Count());
         Assert.AreEqual("0.000000\t1.000000\tone", lines[0]);
         Assert.AreEqual("1.456000\t2.679000\ttwo", lines[1]);                //2.6790 instead of 2.6789 because somewhere else in saymore, the value gets rounded. Not and export issue.
         Assert.AreEqual("2.000000\t3.000000\tthree", lines[2]);
     }
 }
 public void FileDidNotExist_CreatesCorrectFile()
 {
     using (TempFile logFile = TempFile.CreateAndGetPathButDontMakeTheFile())
     {
         using (ChorusNotesMergeEventListener log = new ChorusNotesMergeEventListener(logFile.Path))
         {
             log.ConflictOccurred(new DummyConflict());
             log.ConflictOccurred(new DummyConflict());
         }
         XmlDocument doc = new XmlDocument();
         doc.Load(logFile.Path);
         Assert.AreEqual(2, doc.SelectNodes("notes/annotation").Count);
     }
 }
Beispiel #5
0
        public void NavigateRawHtml(string html)
        {
            if (InvokeRequired)
            {
                Invoke(new Action <string>(NavigateRawHtml), html);
                return;
            }

            var tf = TempFile.CreateAndGetPathButDontMakeTheFile();

            File.WriteAllText(tf.Path, html);
            SetNewTempFile(tf);
            _url = _tempHtmlFile.Path;
            UpdateDisplay();
        }
Beispiel #6
0
        /// <summary>
        /// Epub preview cannot play an audio file that contains a mixture of stereo and monaural
        /// sections.  It also cannot play an audio file that contains segments recorded at
        /// different rates.  We concatenate all narration files used on a page together for epubs
        /// to use, so we need to ensure that resulting audio file is all of one type at one rate.
        /// (which we default to monaural at 44100 Hz, which seems to be a standard default)
        /// Scan through the input file list, checking whether each file has been recorded in stereo
        /// or mono.  If it was recorded in stereo, create a monaural version of the file for
        /// concatenating with the other files in the list.  If the file was recorded at a rate
        /// other than 44100 Hz, create a version with that recording rate.
        /// If there's only one file in the list, just return the input list.
        /// If any file fails to convert for any reason, it is returned in the output list.  So the
        /// files in the output list may not really be consistent in reality...
        /// </summary>
        /// <remarks>
        /// See https://issues.bloomlibrary.org/youtrack/issue/BL-9051
        /// and https://issues.bloomlibrary.org/youtrack/issue/BL-9100.
        /// </remarks>
        private static IEnumerable <string> TryEnsureConsistentInputFiles(string ffmpeg, IEnumerable <string> mergeFiles)
        {
            if (mergeFiles.Count() < 2)
            {
                return(mergeFiles);
            }
            var monoFiles   = new List <string>();
            var argsBuilder = new StringBuilder();

            foreach (var file in mergeFiles)
            {
                var args   = $"-hide_banner -i \"{file}\" -f null -";
                var result = CommandLineRunner.Run(ffmpeg, args, "", 60, new NullProgress());
                var output = result.StandardError;
                var match  = Regex.Match(output, "Audio: [^,]*, ([0-9]+) Hz, ([a-z]+), [^,]*, ([0-9]+) kb/s");
                if (match.Success)
                {
                    // Get a mono version at 44100 Hz of the sound file, trying to preserve its quality.
                    var recordRate = match.Groups[1].ToString();
                    var recordType = match.Groups[2].ToString();
                    var bitRate    = match.Groups[3].ToString();
                    if (recordType == "stereo" || recordRate != "44100")
                    {
                        var tempFile = TempFile.CreateAndGetPathButDontMakeTheFile();
                        tempFile.Detach();
                        args = $"-i \"{file}\" -ac 1 -ar 44100 -b:a {bitRate}k \"{tempFile.Path}.mp3\"";
                        Debug.WriteLine($"DEBUG: ffmpeg {args}");
                        result = CommandLineRunner.Run(ffmpeg, args, "", 120, new NullProgress());
                        if (result.ExitCode == 0)
                        {
                            monoFiles.Add(tempFile.Path + ".mp3");
                            continue;
                        }
                        else
                        {
                            Logger.WriteEvent($"Error converting {file} to monaural at 44100 Hz" + Environment.NewLine + result.StandardError);
                            Debug.WriteLine($"Error converting {file} to monaural at 44100 Hz");
                            Debug.WriteLine(result.StandardError);
                        }
                    }
                }
                monoFiles.Add(file);
            }
            return(monoFiles);
        }
        public void FileOutput_WithContent_UsesCanonicalXmlSettings()
        {
            string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
                              + "<notes\r\n"
                              + "\tversion=\"0\">\r\n"
                              + "\t<annotation>Dummy</annotation>\r\n"
                              + "</notes>";

            using (var logFile = TempFile.CreateAndGetPathButDontMakeTheFile())
            {
                using (var log = new ChorusNotesMergeEventListener(logFile.Path))
                {
                    log.ConflictOccurred(new DummyConflict());
                }
                string result = File.ReadAllText(logFile.Path);
                Assert.AreEqual(expected, result);
            }
        }
 public void Export_Nominal_WritesExpectedContents()
 {
     using (var temp = TempFile.CreateAndGetPathButDontMakeTheFile())
     {
         SRTFormatSubTitleExporter.Export(temp.Path, CreateTier());
         Debug.WriteLine(File.ReadAllText(temp.Path));
         var lines = GetLines(temp).ToArray();
         Assert.AreEqual((3 * 4) - 1, lines.Count());
         Assert.AreEqual("1", lines[0]);
         Assert.AreEqual("00:00:00,00 --> 00:00:01,00", lines[1]);
         Assert.AreEqual("one", lines[2]);
         Assert.AreEqual("", lines[3]);                  //blank line
         Assert.AreEqual("2", lines[4]);
         Assert.AreEqual("00:00:01,45 --> 00:00:02,67", lines[5]);
         Assert.AreEqual("two", lines[6]);
         Assert.AreEqual("", lines[7]);                  //blank line
     }
 }
Beispiel #9
0
        public void WriteReadWithFile_WithCanonicalXmlWriterSettings_NormalizesLineEndings()
        {
            const string xmlInput = "<a><b>\nContent</b><b>\r\nOther</b></a>";
            const string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<a>\r\n\t<b>\r\nContent</b>\r\n\t<b>\r\nOther</b>\r\n</a>";

            using (var tempFile = TempFile.CreateAndGetPathButDontMakeTheFile())
            {
                using (var reader = XmlReader.Create(new StringReader(xmlInput)))
                {
                    using (var writer = XmlWriter.Create(tempFile.Path, CanonicalXmlSettings.CreateXmlWriterSettings()))
                    {
                        writer.WriteNode(reader, false);
                    }
                }
                string result = File.ReadAllText(tempFile.Path);
                Console.WriteLine(result);
                Assert.AreEqual(expected, result);
            }
        }
Beispiel #10
0
        public void WriteReadWithFile_WithCanonicalXmlWriterSettings_MatchesExpected()
        {
            string       xmlInput = @"<a attrib1='value1' attrib2='value2'><b>Content</b></a>".Replace('\'', '"');
            const string expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<a\r\n\tattrib1=\"value1\"\r\n\tattrib2=\"value2\">\r\n\t<b>Content</b>\r\n</a>";

            using (var tempFile = TempFile.CreateAndGetPathButDontMakeTheFile())
            {
                using (var reader = XmlReader.Create(new StringReader(xmlInput)))
                {
                    using (var writer = XmlWriter.Create(tempFile.Path, CanonicalXmlSettings.CreateXmlWriterSettings()))
                    {
                        writer.WriteNode(reader, false);
                    }
                }
                string result = File.ReadAllText(tempFile.Path);
                Console.WriteLine(result);
                Assert.AreEqual(expected, result);
            }
        }
Beispiel #11
0
        /// <summary>
        /// Before calling this, ConfigurationData has to be loaded. E.g., by running ShowConfigurationDialog()
        /// </summary>
        /// <param name="bookPath"></param>
        public void ConfigureBook(string bookPath)
        {
            /* setup jquery in chrome console (first open a local file):
             * script = document.createElement("script");
             *      script.setAttribute("src", "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js");
             *
             *
             * Other snippets
             *
             * document.body.appendChild(script);
             *
             * alert(jQuery.parseJSON('{\"message\": \"triscuit\"}').message)
             *
             *
             * alert($().jquery)
             */

            var dom = XmlHtmlConverter.GetXmlDomFromHtmlFile(bookPath, false);

            XmlHtmlConverter.MakeXmlishTagsSafeForInterpretationAsHtml(dom);
            XmlHtmlConverter.SaveDOMAsHtml5(dom, bookPath);

            var b = new GeckoWebBrowser();
            var neededToMakeThingsWork = b.Handle;

            b.Navigate(bookPath);
            Application.DoEvents();

            //Now we call the method which takes that confuration data and adds/removes/updates pages.
            //We have the data as json string, so first we turn it into object for the updateDom's convenience.
            RunJavaScript(b, "updateDom(jQuery.parseJSON('" + GetAllData() + "'))");
            Application.DoEvents();

            //Ok, so we should have a modified DOM now, which we can save back over the top.

            //nice non-ascii paths kill this, so let's go to a temp file first
            var temp = TempFile.CreateAndGetPathButDontMakeTheFile();             //we don't want to wrap this in using

            b.SaveDocument(temp.Path);
            File.Delete(bookPath);
            File.Move(temp.Path, bookPath);
        }
        public DblBundleTextCorpus(ITokenizer <string, int> wordTokenizer, string fileName)
        {
            using (ZipArchive archive = ZipFile.OpenRead(fileName))
            {
                ZipArchiveEntry metadataEntry = archive.GetEntry("metadata.xml");
                using (Stream stream = metadataEntry.Open())
                {
                    var doc = XDocument.Load(stream);
                    if (!SupportedVersions.Contains((string)doc.Root.Attribute("version")))
                    {
                        throw new InvalidOperationException("Unsupported version of DBL bundle.");
                    }

                    ZipArchiveEntry versificationEntry = archive.Entries
                                                         .FirstOrDefault(e => e.Name == "versification.vrs");
                    if (versificationEntry != null)
                    {
                        using (var tempFile = TempFile.CreateAndGetPathButDontMakeTheFile())
                        {
                            versificationEntry.ExtractToFile(tempFile.Path);
                            var abbr = (string)doc.Root.Elements("identification").Elements("abbreviation")
                                       .FirstOrDefault();
                            Versification = Scripture.Versification.Table.Implementation.Load(tempFile.Path, abbr);
                        }
                    }

                    foreach (XElement contentElem in doc.Root.Elements("publications").Elements("publication")
                             .Where(pubElem => (bool?)pubElem.Attribute("default") ?? false).Elements("structure")
                             .Elements("content"))
                    {
                        AddText(new DblBundleText(wordTokenizer, (string)contentElem.Attribute("role"), fileName,
                                                  (string)contentElem.Attribute("src"), Versification));
                    }
                }
            }
        }
        public void RegisterWithApiHandler(BloomApiHandler apiHandler)
        {
            // This is just for storing the user preference of method
            // If we had a couple of these, we could just have a generic preferences api
            // that browser-side code could use.
            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "method", request =>
            {
                if (request.HttpMethod == HttpMethods.Get)
                {
                    var method = Settings.Default.PublishAndroidMethod;
                    if (!new string[] { "wifi", "usb", "file" }.Contains(method))
                    {
                        method = "wifi";
                    }
                    request.ReplyWithText(method);
                }
                else                 // post
                {
                    Settings.Default.PublishAndroidMethod = request.RequiredPostString();
#if __MonoCS__
                    if (Settings.Default.PublishAndroidMethod == "usb")
                    {
                        _progress.MessageWithoutLocalizing("Sorry, this method is not available on Linux yet.");
                    }
#endif
                    request.PostSucceeded();
                }
            }, true);

            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "backColor", request =>
            {
                if (request.HttpMethod == HttpMethods.Get)
                {
                    if (request.CurrentBook != _coverColorSourceBook)
                    {
                        _coverColorSourceBook = request.CurrentBook;
                        ImageUtils.TryCssColorFromString(request.CurrentBook?.GetCoverColor() ?? "", out _thumbnailBackgroundColor);
                    }
                    request.ReplyWithText(ToCssColorString(_thumbnailBackgroundColor));
                }
                else                 // post
                {
                    // ignore invalid colors (very common while user is editing hex)
                    Color newColor;
                    var newColorAsString = request.RequiredPostString();
                    if (ImageUtils.TryCssColorFromString(newColorAsString, out newColor))
                    {
                        _thumbnailBackgroundColor = newColor;
                        request.CurrentBook.SetCoverColor(newColorAsString);
                    }
                    request.PostSucceeded();
                }
            }, true);

            apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "motionBookMode",
                                                      readRequest =>
            {
                // If the user has taken off all possible motion, force not having motion in the
                // Bloom Reader book.  See https://issues.bloomlibrary.org/youtrack/issue/BL-7680.
                if (!readRequest.CurrentBook.HasMotionPages)
                {
                    readRequest.CurrentBook.BookInfo.PublishSettings.BloomPub.Motion = false;
                }
                return(readRequest.CurrentBook.BookInfo.PublishSettings.BloomPub.Motion);
            },
                                                      (writeRequest, value) =>
            {
                writeRequest.CurrentBook.BookInfo.PublishSettings.BloomPub.Motion = value;
                writeRequest.CurrentBook.BookInfo.SavePublishSettings();
                _webSocketServer.SendEvent("publish", "motionChanged");
            }
                                                      , true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "updatePreview", request =>
            {
                MakeBloompubPreview(request, false);
            }, false);


            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "thumbnail", request =>
            {
                var coverImage = request.CurrentBook.GetCoverImagePath();
                if (coverImage == null)
                {
                    request.Failed("no cover image");
                }
                else
                {
                    // We don't care as much about making it resized as making its background transparent.
                    using (var thumbnail = TempFile.CreateAndGetPathButDontMakeTheFile())
                    {
                        if (_thumbnailBackgroundColor == Color.Transparent)
                        {
                            ImageUtils.TryCssColorFromString(request.CurrentBook?.GetCoverColor(), out _thumbnailBackgroundColor);
                        }
                        RuntimeImageProcessor.GenerateEBookThumbnail(coverImage, thumbnail.Path, 256, 256, _thumbnailBackgroundColor);
                        request.ReplyWithImage(thumbnail.Path);
                    }
                }
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "usb/start", request =>
            {
#if !__MonoCS__
                SetState("UsbStarted");
                UpdatePreviewIfNeeded(request);
                _usbPublisher.Connect(request.CurrentBook, _thumbnailBackgroundColor, GetSettings());
#endif
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "usb/stop", request =>
            {
#if !__MonoCS__
                _usbPublisher.Stop(disposing: false);
                SetState("stopped");
#endif
                request.PostSucceeded();
            }, true);
            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "wifi/start", request =>
            {
                SetState("ServingOnWifi");
                UpdatePreviewIfNeeded(request);
                _wifiPublisher.Start(request.CurrentBook, request.CurrentCollectionSettings, _thumbnailBackgroundColor, GetSettings());

                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "wifi/stop", request =>
            {
                _wifiPublisher.Stop();
                SetState("stopped");
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "file/save", request =>
            {
                UpdatePreviewIfNeeded(request);
                FilePublisher.Save(request.CurrentBook, _bookServer, _thumbnailBackgroundColor, _progress, GetSettings());
                SetState("stopped");
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "file/bulkSaveBloomPubsParams", request =>
            {
                request.ReplyWithJson(JsonConvert.SerializeObject(_collectionSettings.BulkPublishBloomPubSettings));
            }, true);

            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "file/bulkSaveBloomPubs", request =>
            {
                // update what's in the collection so that we remember for next time
                _collectionSettings.BulkPublishBloomPubSettings = request.RequiredPostObject <BulkBloomPubPublishSettings>();
                _collectionSettings.Save();

                _bulkBloomPubCreator.PublishAllBooks(_collectionSettings.BulkPublishBloomPubSettings);
                SetState("stopped");
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "textToClipboard", request =>
            {
                PortableClipboard.SetText(request.RequiredPostString());
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "canHaveMotionMode",
                                                      request =>
            {
                return(request.CurrentBook.HasMotionPages);
            },
                                                      null,  // no write action
                                                      false,
                                                      true); // we don't really know, just safe default

            apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "canRotate",
                                                      request =>
            {
                return(request.CurrentBook.BookInfo.PublishSettings.BloomPub.Motion && request.CurrentBook.HasMotionPages);
            },
                                                      null,  // no write action
                                                      false,
                                                      true); // we don't really know, just safe default

            apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "defaultLandscape",
                                                      request =>
            {
                return(request.CurrentBook.GetLayout().SizeAndOrientation.IsLandScape);
            },
                                                      null,  // no write action
                                                      false,
                                                      true); // we don't really know, just safe default
            apiHandler.RegisterEndpointLegacy(kApiUrlPart + "languagesInBook", request =>
            {
                try
                {
                    InitializeLanguagesInBook(request);

                    Dictionary <string, InclusionSetting> textLangsToPublish  = request.CurrentBook.BookInfo.PublishSettings.BloomPub.TextLangs;
                    Dictionary <string, InclusionSetting> audioLangsToPublish = request.CurrentBook.BookInfo.PublishSettings.BloomPub.AudioLangs;

                    var result = "[" + string.Join(",", _allLanguages.Select(kvp =>
                    {
                        string langCode = kvp.Key;

                        bool includeText = false;
                        if (textLangsToPublish != null && textLangsToPublish.TryGetValue(langCode, out InclusionSetting includeTextSetting))
                        {
                            includeText = includeTextSetting.IsIncluded();
                        }

                        bool includeAudio = false;
                        if (audioLangsToPublish != null && audioLangsToPublish.TryGetValue(langCode, out InclusionSetting includeAudioSetting))
                        {
                            includeAudio = includeAudioSetting.IsIncluded();
                        }

                        var value = new LanguagePublishInfo()
                        {
                            code             = kvp.Key,
                            name             = request.CurrentBook.PrettyPrintLanguage(langCode),
                            complete         = kvp.Value,
                            includeText      = includeText,
                            containsAnyAudio = _languagesWithAudio.Contains(langCode),
                            includeAudio     = includeAudio
                        };
                        var json = JsonConvert.SerializeObject(value);
                        return(json);
                    })) + "]";

                    request.ReplyWithText(result);
                }
Beispiel #14
0
        private void ConfigureBookInternal(string bookPath)
        {
            /* setup jquery in chrome console (first open a local file):
             * script = document.createElement("script");
             *              script.setAttribute("src", "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js");
             *
             *
             * Other snippets
             *
             * document.body.appendChild(script);
             *
             * alert(jQuery.parseJSON('{\"message\": \"triscuit\"}').message)
             *
             *
             * alert($().jquery)
             */

            var dom = XmlHtmlConverter.GetXmlDomFromHtmlFile(bookPath, false);

            XmlHtmlConverter.MakeXmlishTagsSafeForInterpretationAsHtml(dom);
            XmlHtmlConverter.SaveDOMAsHtml5(dom, bookPath);

            var b = new GeckoWebBrowser();
            var neededToMakeThingsWork = b.Handle;

            NavigateAndWait(b, bookPath);

            //Now we call the method which takes that confuration data and adds/removes/updates pages.
            //We have the data as json string, so first we turn it into object for the updateDom's convenience.
            RunJavaScript(b, "runUpdate(" + GetAllData() + ")");

            //Ok, so we should have a modified DOM now, which we can save back over the top.

            //nice non-ascii paths kill this, so let's go to a temp file first
            var temp = TempFile.CreateAndGetPathButDontMakeTheFile();             //we don't want to wrap this in using

            b.SaveDocument(temp.Path);
            RobustFile.Delete(bookPath);
            RobustFile.Move(temp.Path, bookPath);

            var sanityCheckDom = XmlHtmlConverter.GetXmlDomFromHtmlFile(bookPath, false);

            // Because the Mozilla code loaded the document from a filename initially, and we later save to a
            // different directory, Geckofx45's SaveDocument writes out the stylesheet links as absolute paths
            // using the file:// protocol markup. When we try to open the new file, Mozilla then complains
            // vociferously about security issues, and refuses to access the stylesheets as far as I can tell.
            // Eventually, several of the stylesheets are cleaned up by being added in again, but a couple of
            // them end up with invalid relative paths because they never get re-added.  So let's go through
            // all the stylesheet links here and remove everything except the bare filenames.
            // See https://silbloom.myjetbrains.com/youtrack/issue/BL-3573 for what happens without this fix.
            foreach (System.Xml.XmlElement link in sanityCheckDom.SafeSelectNodes("//link[@rel='stylesheet']"))
            {
                var href = link.GetAttribute("href");
                if (href.StartsWith("file://"))
                {
                    link.SetAttribute("href", Path.GetFileName(href.Replace("file:///", "").Replace("file://", "")));
                }
            }
            XmlHtmlConverter.SaveDOMAsHtml5(sanityCheckDom, bookPath);

            //NB: this check only makes sense for the calendar, which is the only template we've create that
            // uses this class, and there are no other templates on the drawing board that would use it.
            // If/when we use this for something else, this
            //won't work. But by then, we should be using a version of geckofx that can reliably tell us
            //when it is done with the previous navigation.
            if (sanityCheckDom.SafeSelectNodes("//div[contains(@class,'bloom-page')]").Count < 24) //should be 24 pages
            {
                Logger.WriteMinorEvent(RobustFile.ReadAllText(bookPath));                          //this will come to us if they report it
                throw new ApplicationException("Malformed Calendar (code assumes only calendar uses the Configurator, and they have at least 24 pages)");
            }

            //NB: we *want* exceptions thrown from the above to make it out.
        }
Beispiel #15
0
        /// <summary>
        /// Creates the .bloompub and bloomdigital folders
        /// </summary>
        private static CreateArtifactsExitCode CreateBloomDigitalArtifacts(string bookPath, string creator, string zippedBloomPubOutputPath, string unzippedBloomDigitalOutputPath)
        {
#if DEBUG
            // Useful for allowing debugging of Bloom while running the harvester
            //MessageBox.Show("Attach debugger now");
#endif
            var exitCode = CreateArtifactsExitCode.Success;

            using (var tempBloomPub = TempFile.CreateAndGetPathButDontMakeTheFile())
            {
                if (String.IsNullOrEmpty(zippedBloomPubOutputPath))
                {
                    zippedBloomPubOutputPath = tempBloomPub.Path;
                }

                BookServer bookServer = _projectContext.BookServer;

                var  metadata       = BookMetaData.FromFolder(bookPath);
                bool isTemplateBook = metadata.IsSuitableForMakingShells;

                // Build artifacts the same way from the harvester as on the user's local machine.
                // (similarly to a bulk publish operation)
                // See https://issues.bloomlibrary.org/youtrack/issue/BL-10300.
                var bookInfo = new BookInfo(bookPath, false);
                var settings = AndroidPublishSettings.GetPublishSettingsForBook(bookServer, bookInfo);

                using (var folderForUnzipped = new TemporaryFolder("BloomCreateArtifacts_Unzipped"))
                {
                    // Ensure directory exists, just in case.
                    Directory.CreateDirectory(Path.GetDirectoryName(zippedBloomPubOutputPath));
                    // Make the bloompub
                    string unzippedPath = BloomPubMaker.CreateBloomPub(
                        zippedBloomPubOutputPath,
                        bookPath,
                        bookServer,
                        new Bloom.web.NullWebSocketProgress(),
                        folderForUnzipped,
                        creator,
                        isTemplateBook,
                        settings);

                    // Currently the zipping process does some things we actually need, like making the cover picture
                    // transparent (BL-7437). Eventually we plan to separate the preparation and zipping steps (BL-7445).
                    // Until that is done, the most reliable way to get an unzipped BloomPUB for our preview is to actually
                    // unzip the BloomPUB.
                    if (!String.IsNullOrEmpty(unzippedBloomDigitalOutputPath))
                    {
                        SIL.IO.RobustIO.DeleteDirectory(unzippedBloomDigitalOutputPath, recursive: true);                           // In case the folder isn't already empty

                        // Ensure directory exists, just in case.
                        Directory.CreateDirectory(Path.GetDirectoryName(unzippedBloomDigitalOutputPath));

                        ZipFile.ExtractToDirectory(zippedBloomPubOutputPath, unzippedBloomDigitalOutputPath);

                        exitCode |= RenameBloomDigitalFiles(unzippedBloomDigitalOutputPath);
                    }
                }
            }

            return(exitCode);
        }
        public void RegisterWithApiHandler(BloomApiHandler apiHandler)
        {
            // This is just for storing the user preference of method
            // If we had a couple of these, we could just have a generic preferences api
            // that browser-side code could use.
            apiHandler.RegisterEndpointHandler(kApiUrlPart + "method", request =>
            {
                if (request.HttpMethod == HttpMethods.Get)
                {
                    var method = Settings.Default.PublishAndroidMethod;
                    if (!new string[] { "wifi", "usb", "file" }.Contains(method))
                    {
                        method = "wifi";
                    }
                    request.ReplyWithText(method);
                }
                else                 // post
                {
                    Settings.Default.PublishAndroidMethod = request.RequiredPostString();
#if __MonoCS__
                    if (Settings.Default.PublishAndroidMethod == "usb")
                    {
                        _progress.MessageWithoutLocalizing("Sorry, this method is not available on Linux yet.");
                    }
#endif
                    request.PostSucceeded();
                }
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "backColor", request =>
            {
                if (request.HttpMethod == HttpMethods.Get)
                {
                    if (request.CurrentBook != _coverColorSourceBook)
                    {
                        _coverColorSourceBook = request.CurrentBook;
                        ImageUtils.TryCssColorFromString(request.CurrentBook?.GetCoverColor() ?? "", out _thumbnailBackgroundColor);
                    }
                    request.ReplyWithText(ToCssColorString(_thumbnailBackgroundColor));
                }
                else                 // post
                {
                    // ignore invalid colors (very common while user is editing hex)
                    Color newColor;
                    var newColorAsString = request.RequiredPostString();
                    if (ImageUtils.TryCssColorFromString(newColorAsString, out newColor))
                    {
                        _thumbnailBackgroundColor = newColor;
                        request.CurrentBook.SetCoverColor(newColorAsString);
                    }
                    request.PostSucceeded();
                }
            }, true);

            apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "motionBookMode",
                                                      readRequest =>
            {
                // If the user has taken off all possible motion, force not having motion in the
                // Bloom Reader book.  See https://issues.bloomlibrary.org/youtrack/issue/BL-7680.
                if (!readRequest.CurrentBook.HasMotionPages)
                {
                    readRequest.CurrentBook.MotionMode = false;
                }
                return(readRequest.CurrentBook.MotionMode);
            },
                                                      (writeRequest, value) =>
            {
                writeRequest.CurrentBook.MotionMode = value;
            }
                                                      , true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "updatePreview", request =>
            {
                if (request.HttpMethod == HttpMethods.Post)
                {
                    // This is already running on a server thread, so there doesn't seem to be any need to kick off
                    // another background one and return before the preview is ready. But in case something in C#
                    // might one day kick of a new preview, or we find we do need a background thread,
                    // I've made it a websocket broadcast when it is ready.
                    // If we've already left the publish tab...we can get a few of these requests queued up when
                    // a tester rapidly toggles between views...abandon the attempt
                    if (!PublishHelper.InPublishTab)
                    {
                        request.Failed("aborted, no longer in publish tab");
                        return;
                    }
                    try
                    {
                        UpdatePreview(request);
                        request.PostSucceeded();
                    }
                    catch (Exception e)
                    {
                        request.Failed("Error while updating preview. Message: " + e.Message);
                        NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, "Error while updating preview.", null, e, true);
                    }
                }
            }, false);


            apiHandler.RegisterEndpointHandler(kApiUrlPart + "thumbnail", request =>
            {
                var coverImage = request.CurrentBook.GetCoverImagePath();
                if (coverImage == null)
                {
                    request.Failed("no cover image");
                }
                else
                {
                    // We don't care as much about making it resized as making its background transparent.
                    using (var thumbnail = TempFile.CreateAndGetPathButDontMakeTheFile())
                    {
                        if (_thumbnailBackgroundColor == Color.Transparent)
                        {
                            ImageUtils.TryCssColorFromString(request.CurrentBook?.GetCoverColor(), out _thumbnailBackgroundColor);
                        }
                        RuntimeImageProcessor.GenerateEBookThumbnail(coverImage, thumbnail.Path, 256, 256, _thumbnailBackgroundColor);
                        request.ReplyWithImage(thumbnail.Path);
                    }
                }
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "usb/start", request =>
            {
#if !__MonoCS__
                SetState("UsbStarted");
                UpdatePreviewIfNeeded(request);
                _usbPublisher.Connect(request.CurrentBook, _thumbnailBackgroundColor, GetSettings());
#endif
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "usb/stop", request =>
            {
#if !__MonoCS__
                _usbPublisher.Stop();
                SetState("stopped");
#endif
                request.PostSucceeded();
            }, true);
            apiHandler.RegisterEndpointHandler(kApiUrlPart + "wifi/start", request =>
            {
                SetState("ServingOnWifi");
                UpdatePreviewIfNeeded(request);
                _wifiPublisher.Start(request.CurrentBook, request.CurrentCollectionSettings, _thumbnailBackgroundColor, GetSettings());

                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "wifi/stop", request =>
            {
                _wifiPublisher.Stop();
                SetState("stopped");
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "file/save", request =>
            {
                UpdatePreviewIfNeeded(request);
                FilePublisher.Save(request.CurrentBook, _bookServer, _thumbnailBackgroundColor, _progress, GetSettings());
                SetState("stopped");
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "cleanup", request =>
            {
                Stop();
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterEndpointHandler(kApiUrlPart + "textToClipboard", request =>
            {
                PortableClipboard.SetText(request.RequiredPostString());
                request.PostSucceeded();
            }, true);

            apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "canHaveMotionMode",
                                                      request =>
            {
                return(request.CurrentBook.HasMotionPages);
            },
                                                      null,  // no write action
                                                      false,
                                                      true); // we don't really know, just safe default

            apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "canRotate",
                                                      request =>
            {
                return(request.CurrentBook.MotionMode && request.CurrentBook.HasMotionPages);
            },
                                                      null,  // no write action
                                                      false,
                                                      true); // we don't really know, just safe default

            apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "defaultLandscape",
                                                      request =>
            {
                return(request.CurrentBook.GetLayout().SizeAndOrientation.IsLandScape);
            },
                                                      null,  // no write action
                                                      false,
                                                      true); // we don't really know, just safe default
            apiHandler.RegisterEndpointHandler(kApiUrlPart + "languagesInBook", request =>
            {
                try
                {
                    InitializeLanguagesInBook(request);

                    Dictionary <string, InclusionSetting> textLangsToPublish  = request.CurrentBook.BookInfo.MetaData.TextLangsToPublish.ForBloomPUB;
                    Dictionary <string, InclusionSetting> audioLangsToPublish = request.CurrentBook.BookInfo.MetaData.AudioLangsToPublish.ForBloomPUB;

                    var result = "[" + string.Join(",", _allLanguages.Select(kvp =>
                    {
                        string langCode = kvp.Key;

                        bool includeText = false;
                        if (textLangsToPublish != null && textLangsToPublish.TryGetValue(langCode, out InclusionSetting includeTextSetting))
                        {
                            includeText = includeTextSetting.IsIncluded();
                        }

                        bool includeAudio = false;
                        if (audioLangsToPublish != null && audioLangsToPublish.TryGetValue(langCode, out InclusionSetting includeAudioSetting))
                        {
                            includeAudio = includeAudioSetting.IsIncluded();
                        }

                        var value = new LanguagePublishInfo()
                        {
                            code             = kvp.Key,
                            name             = request.CurrentBook.PrettyPrintLanguage(langCode),
                            complete         = kvp.Value,
                            includeText      = includeText,
                            containsAnyAudio = _languagesWithAudio.Contains(langCode),
                            includeAudio     = includeAudio
                        };
                        var json = JsonConvert.SerializeObject(value);
                        return(json);
                    })) + "]";

                    request.ReplyWithText(result);
                }
        public void RegisterWithServer(EnhancedImageServer server)
        {
            // This is just for storing the user preference of method
            // If we had a couple of these, we could just have a generic preferences api
            // that browser-side code could use.
            server.RegisterEndpointHandler(kApiUrlPart + "method", request =>
            {
                if (request.HttpMethod == HttpMethods.Get)
                {
                    var method = Settings.Default.PublishAndroidMethod;
                    if (!new string[] { "wifi", "usb", "file" }.Contains(method))
                    {
                        method = "wifi";
                    }
                    request.ReplyWithText(method);
                }
                else                 // post
                {
                    Settings.Default.PublishAndroidMethod = request.RequiredPostString();
#if __MonoCS__
                    if (Settings.Default.PublishAndroidMethod == "usb")
                    {
                        _progress.MessageWithoutLocalizing("Sorry, this method is not available on Linux yet.");
                    }
#endif
                    request.PostSucceeded();
                }
            }, true);

            server.RegisterEndpointHandler(kApiUrlPart + "backColor", request =>
            {
                if (request.HttpMethod == HttpMethods.Get)
                {
                    if (request.CurrentBook != _coverColorSourceBook)
                    {
                        _coverColorSourceBook = request.CurrentBook;
                        TryCssColorFromString(request.CurrentBook?.GetCoverColor() ?? "", out _thumbnailBackgroundColor);
                    }
                    request.ReplyWithText(ToCssColorString(_thumbnailBackgroundColor));
                }
                else                 // post
                {
                    // ignore invalid colors (very common while user is editing hex)
                    Color newColor;
                    var newColorAsString = request.RequiredPostString();
                    if (TryCssColorFromString(newColorAsString, out newColor))
                    {
                        _thumbnailBackgroundColor = newColor;
                        request.CurrentBook.SetCoverColor(newColorAsString);
                    }
                    request.PostSucceeded();
                }
            }, true);


            server.RegisterEndpointHandler(kApiUrlPart + "photoStoryMode", request =>
            {
                if (request.HttpMethod == HttpMethods.Get)
                {
                    // this is temporary, just trying to get support for full screen pan & zoom out quickly in 4.2
                    request.ReplyWithText(request.CurrentBook.UsePhotoStoryModeInBloomReader.ToString()
                                          .ToLowerInvariant()); // "false", not "False"
                }
                else                                            // post
                {
                    request.CurrentBook.UsePhotoStoryModeInBloomReader = bool.Parse(request.RequiredPostString());
                    request.PostSucceeded();
                }
            }, true);


            server.RegisterEndpointHandler(kApiUrlPart + "thumbnail", request =>
            {
                var coverImage = request.CurrentBook.GetCoverImagePath();
                if (coverImage == null)
                {
                    request.Failed("no cover image");
                }
                else
                {
                    // We don't care as much about making it resized as making its background transparent.
                    using (var thumbnail = TempFile.CreateAndGetPathButDontMakeTheFile())
                    {
                        if (_thumbnailBackgroundColor == Color.Transparent)
                        {
                            TryCssColorFromString(request.CurrentBook?.GetCoverColor(), out _thumbnailBackgroundColor);
                        }
                        RuntimeImageProcessor.GenerateEBookThumbnail(coverImage, thumbnail.Path, 256, 256, _thumbnailBackgroundColor);
                        request.ReplyWithImage(thumbnail.Path);
                    }
                }
            }, true);

            server.RegisterEndpointHandler(kApiUrlPart + "usb/start", request =>
            {
#if !__MonoCS__
                SetState("UsbStarted");
                _usbPublisher.Connect(request.CurrentBook, _thumbnailBackgroundColor);
#endif
                request.PostSucceeded();
            }, true);

            server.RegisterEndpointHandler(kApiUrlPart + "usb/stop", request =>
            {
#if !__MonoCS__
                _usbPublisher.Stop();
                SetState("stopped");
#endif
                request.PostSucceeded();
            }, true);
            server.RegisterEndpointHandler(kApiUrlPart + "wifi/start", request =>
            {
                _wifiPublisher.Start(request.CurrentBook, request.CurrentCollectionSettings, _thumbnailBackgroundColor);
                SetState("ServingOnWifi");
                request.PostSucceeded();
            }, true);

            server.RegisterEndpointHandler(kApiUrlPart + "wifi/stop", request =>
            {
                _wifiPublisher.Stop();
                SetState("stopped");
                request.PostSucceeded();
            }, true);

            server.RegisterEndpointHandler(kApiUrlPart + "file/save", request =>
            {
                FilePublisher.Save(request.CurrentBook, _bookServer, _thumbnailBackgroundColor, _progress);
                SetState("stopped");
                request.PostSucceeded();
            }, true);

            server.RegisterEndpointHandler(kApiUrlPart + "cleanup", request =>
            {
                Stop();
                request.PostSucceeded();
            }, true);

            server.RegisterEndpointHandler(kApiUrlPart + "textToClipboard", request =>
            {
                PortableClipboard.SetText(request.RequiredPostString());
                request.PostSucceeded();
            }, true);
        }