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); } } }
/// <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); }
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); } }
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(); }
/// <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 } }
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); } }
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); } }
/// <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); }
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. }
/// <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); }