private string UploadBook(string bookFolder, IProgress progress, out string parseId, string pdfToInclude = null, bool excludeAudio = true) { // Books in the library should generally show as locked-down, so new users are automatically in localization mode. // Occasionally we may want to upload a new authoring template, that is, a 'book' that is suitableForMakingShells. // Such books must never be locked. // So, typically we will try to lock it. What we want to do is Book.RecordedAsLockedDown = true; Book.Save(). // But all kinds of things have to be set up before we can create a Book. So we duplicate a few bits of code. var htmlFile = BookStorage.FindBookHtmlInFolder(bookFolder); bool wasLocked = false; bool allowLocking = false; HtmlDom domForLocking = null; var metaDataText = MetaDataText(bookFolder); var metadata = BookMetaData.FromString(metaDataText); if (!string.IsNullOrEmpty(htmlFile)) { var xmlDomFromHtmlFile = XmlHtmlConverter.GetXmlDomFromHtmlFile(htmlFile, false); domForLocking = new HtmlDom(xmlDomFromHtmlFile); wasLocked = domForLocking.RecordedAsLockedDown; allowLocking = !metadata.IsSuitableForMakingShells; if (allowLocking && !wasLocked) { domForLocking.RecordAsLockedDown(true); XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile); } } string s3BookId; try { // In case we somehow have a book with no ID, we must have one to upload it. if (string.IsNullOrEmpty(metadata.Id)) { metadata.Id = Guid.NewGuid().ToString(); } // And similarly it should have SOME title. if (string.IsNullOrEmpty(metadata.Title)) { metadata.Title = Path.GetFileNameWithoutExtension(bookFolder); } metadata.SetUploader(UserId); s3BookId = S3BookId(metadata); metadata.DownloadSource = s3BookId; // Any updated ID at least needs to become a permanent part of the book. // The file uploaded must also contain the correct DownloadSource data, so that it can be used // as an 'order' to download the book. // It simplifies unit testing if the metadata file is also updated with the uploadedBy value. // Not sure if there is any other reason to do it (or not do it). // For example, do we want to send/receive who is the latest person to upload? metadata.WriteToFolder(bookFolder); // The metadata is also a book order...but we need it on the server with the desired file name, // because we can't rename on download. The extension must be the one Bloom knows about, // and we want the file name to indicate which book, so use the name of the book folder. var metadataPath = BookMetaData.MetaDataPath(bookFolder); var orderPath = Path.Combine(bookFolder, Path.GetFileName(bookFolder) + BookOrderExtension); RobustFile.Copy(metadataPath, orderPath, true); parseId = ""; try { _s3Client.UploadBook(s3BookId, bookFolder, progress, pdfToInclude, excludeAudio); metadata.BaseUrl = _s3Client.BaseUrl; metadata.BookOrder = _s3Client.BookOrderUrlOfRecentUpload; progress.WriteStatus(LocalizationManager.GetString("PublishTab.Upload.UploadingBookMetadata", "Uploading book metadata", "In this step, Bloom is uploading things like title, languages, and topic tags to the BloomLibrary.org database.")); // Do this after uploading the books, since the ThumbnailUrl is generated in the course of the upload. var response = _parseClient.SetBookRecord(metadata.WebDataJson); parseId = response.ResponseUri.LocalPath; int index = parseId.LastIndexOf('/'); parseId = parseId.Substring(index + 1); if (parseId == "books") { // For NEW books the response URL is useless...need to do a new query to get the ID. var json = _parseClient.GetSingleBookRecord(metadata.Id); parseId = json.objectId.Value; } // if (!UseSandbox) // don't make it seem like there are more uploads than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Success", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title } }); } } catch (WebException e) { DisplayNetworkUploadProblem(e, progress); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } catch (AmazonS3Exception e) { if (e.Message.Contains("The difference between the request time and the current time is too large")) { progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.TimeProblem", "There was a problem uploading your book. This is probably because your computer is set to use the wrong timezone or your system time is badly wrong. See http://www.di-mgt.com.au/wclock/help/wclo_setsysclock.html for how to fix this.")); if (!UseSandbox) { Analytics.Track("UploadBook-Failure-SystemTime"); } } else { DisplayNetworkUploadProblem(e, progress); if (!UseSandbox) { // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } } return(""); } catch (AmazonServiceException e) { DisplayNetworkUploadProblem(e, progress); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } catch (Exception e) { progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.UploadProblemNotice", "There was a problem uploading your book. You may need to restart Bloom or get technical help.")); progress.WriteError(e.Message.Replace("{", "{{").Replace("}", "}}")); progress.WriteVerbose(e.StackTrace); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } } finally { if (domForLocking != null && allowLocking && !wasLocked) { domForLocking.RecordAsLockedDown(false); XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile); } } return(s3BookId); }
public string UploadBook(string bookFolder, IProgress progress, out string parseId) { var metaDataText = MetaDataText(bookFolder); var metadata = BookMetaData.FromString(metaDataText); // In case we somehow have a book with no ID, we must have one to upload it. if (string.IsNullOrEmpty(metadata.Id)) { metadata.Id = Guid.NewGuid().ToString(); } // And similarly it should have SOME title. if (string.IsNullOrEmpty(metadata.Title)) { metadata.Title = Path.GetFileNameWithoutExtension(bookFolder); } metadata.SetUploader(UserId); var s3BookId = S3BookId(metadata); metadata.DownloadSource = s3BookId; // Any updated ID at least needs to become a permanent part of the book. // The file uploaded must also contain the correct DownloadSource data, so that it can be used // as an 'order' to download the book. // It simplifies unit testing if the metadata file is also updated with the uploadedBy value. // Not sure if there is any other reason to do it (or not do it). // For example, do we want to send/receive who is the latest person to upload? metadata.WriteToFolder(bookFolder); // The metadata is also a book order...but we need it on the server with the desired file name, // because we can't rename on download. The extension must be the one Bloom knows about, // and we want the file name to indicate which book, so use the name of the book folder. var metadataPath = BookMetaData.MetaDataPath(bookFolder); var orderPath = Path.Combine(bookFolder, Path.GetFileName(bookFolder) + BookOrderExtension); File.Copy(metadataPath, orderPath, true); parseId = ""; try { _s3Client.UploadBook(s3BookId, bookFolder, progress); metadata.BaseUrl = _s3Client.BaseUrl; metadata.BookOrder = _s3Client.BookOrderUrl; progress.WriteStatus(LocalizationManager.GetString("PublishTab.Upload.UploadingBookMetadata", "Uploading book metadata", "In this step, Bloom is uploading things like title, languages, & topic tags to the bloomlibrary.org database.")); // Do this after uploading the books, since the ThumbnailUrl is generated in the course of the upload. var response = _parseClient.SetBookRecord(metadata.Json); parseId = response.ResponseUri.LocalPath; int index = parseId.LastIndexOf('/'); parseId = parseId.Substring(index + 1); if (parseId == "books") { // For NEW books the response URL is useless...need to do a new query to get the ID. var json = _parseClient.GetSingleBookRecord(metadata.Id); parseId = json.objectId.Value; } // if (!UseSandbox) // don't make it seem like there are more uploads than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Success", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title } }); } } catch (WebException e) { DisplayNetworkUploadProblem(e, progress); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } catch (AmazonServiceException e) { DisplayNetworkUploadProblem(e, progress); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } catch (Exception e) { progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.UploadProblemNotice", "There was a problem uploading your book. You may need to restart Bloom or get technical help.")); progress.WriteError(e.Message.Replace("{", "{{").Replace("}", "}}")); progress.WriteVerbose(e.StackTrace); if (!UseSandbox) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } return(s3BookId); }
private string UploadBook(string bookFolder, IProgress progress, out string parseId, string pdfToInclude = null, ISet <string> audioFilesToInclude = null, IEnumerable <string> videoFilesToInclude = null, string[] languages = null, CollectionSettings collectionSettings = null) { // Books in the library should generally show as locked-down, so new users are automatically in localization mode. // Occasionally we may want to upload a new authoring template, that is, a 'book' that is suitableForMakingShells. // Such books must never be locked. // So, typically we will try to lock it. What we want to do is Book.RecordedAsLockedDown = true; Book.Save(). // But all kinds of things have to be set up before we can create a Book. So we duplicate a few bits of code. var htmlFile = BookStorage.FindBookHtmlInFolder(bookFolder); bool wasLocked = false; bool allowLocking = false; HtmlDom domForLocking = null; var metaDataText = MetaDataText(bookFolder); var metadata = BookMetaData.FromString(metaDataText); if (!String.IsNullOrEmpty(htmlFile)) { var xmlDomFromHtmlFile = XmlHtmlConverter.GetXmlDomFromHtmlFile(htmlFile, false); domForLocking = new HtmlDom(xmlDomFromHtmlFile); wasLocked = domForLocking.RecordedAsLockedDown; allowLocking = !metadata.IsSuitableForMakingShells; if (allowLocking && !wasLocked) { domForLocking.RecordAsLockedDown(true); XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile); } } string s3BookId; try { // In case we somehow have a book with no ID, we must have one to upload it. if (String.IsNullOrEmpty(metadata.Id)) { metadata.Id = Guid.NewGuid().ToString(); } // And similarly it should have SOME title. if (String.IsNullOrEmpty(metadata.Title)) { metadata.Title = Path.GetFileNameWithoutExtension(bookFolder); } metadata.SetUploader(UserId); s3BookId = S3BookId(metadata); #if DEBUG // S3 URL can be reasonably deduced, as long as we have the S3 ID, so print that out in Debug mode. // Format: $"https://s3.amazonaws.com/BloomLibraryBooks{isSandbox}/{s3BookId}/{title}" // Example: https://s3.amazonaws.com/BloomLibraryBooks-Sandbox/[email protected]/8d0d9043-a1bb-422d-aa5b-29726cdcd96a/AutoSplit+Timings var msgBookId = "s3BookId: " + s3BookId; progress.WriteMessage(msgBookId); #endif metadata.DownloadSource = s3BookId; // If the collection has a default bookshelf, make sure the book has that tag. // Also make sure it doesn't have any other bookshelf tags (which would typically be // from a previous default bookshelf upload), including a duplicate of the one // we may be about to add. var tags = (metadata.Tags ?? new string[0]).Where(t => !t.StartsWith("bookshelf:")); if (!String.IsNullOrEmpty(collectionSettings?.DefaultBookshelf)) { tags = tags.Concat(new [] { "bookshelf:" + collectionSettings.DefaultBookshelf }); } metadata.Tags = tags.ToArray(); // Any updated ID at least needs to become a permanent part of the book. // The file uploaded must also contain the correct DownloadSource data, so that it can be used // as an 'order' to download the book. // It simplifies unit testing if the metadata file is also updated with the uploadedBy value. // Not sure if there is any other reason to do it (or not do it). // For example, do we want to send/receive who is the latest person to upload? metadata.WriteToFolder(bookFolder); // The metadata is also a book order...but we need it on the server with the desired file name, // because we can't rename on download. The extension must be the one Bloom knows about, // and we want the file name to indicate which book, so use the name of the book folder. var metadataPath = BookMetaData.MetaDataPath(bookFolder); RobustFile.Copy(metadataPath, BookInfo.BookOrderPath(bookFolder), true); parseId = ""; try { _s3Client.UploadBook(s3BookId, bookFolder, progress, pdfToInclude, audioFilesToInclude, videoFilesToInclude, languages); metadata.BaseUrl = _s3Client.BaseUrl; metadata.BookOrder = _s3Client.BookOrderUrlOfRecentUpload; var metaMsg = LocalizationManager.GetString("PublishTab.Upload.UploadingBookMetadata", "Uploading book metadata", "In this step, Bloom is uploading things like title, languages, and topic tags to the BloomLibrary.org database."); if (IsDryRun) { metaMsg = "(Dry run) Would upload book metadata"; // TODO: localize? } progress.WriteStatus(metaMsg); // Do this after uploading the books, since the ThumbnailUrl is generated in the course of the upload. if (!IsDryRun) { var response = ParseClient.SetBookRecord(metadata.WebDataJson); parseId = response.ResponseUri.LocalPath; int index = parseId.LastIndexOf('/'); parseId = parseId.Substring(index + 1); if (parseId == "books") { // For NEW books the response URL is useless...need to do a new query to get the ID. var json = ParseClient.GetSingleBookRecord(metadata.Id); parseId = json.objectId.Value; } // if (!UseSandbox) // don't make it seem like there are more uploads than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Success", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title } }); } } } catch (WebException e) { DisplayNetworkUploadProblem(e, progress); if (IsProductionRun) // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } catch (AmazonS3Exception e) { if (e.Message.Contains("The difference between the request time and the current time is too large")) { progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.TimeProblem", "There was a problem uploading your book. This is probably because your computer is set to use the wrong timezone or your system time is badly wrong. See http://www.di-mgt.com.au/wclock/help/wclo_setsysclock.html for how to fix this.")); if (IsProductionRun) { Analytics.Track("UploadBook-Failure-SystemTime"); } } else { DisplayNetworkUploadProblem(e, progress); if (IsProductionRun) { // don't make it seem like there are more upload failures than there really are if this a tester pushing to the sandbox Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } } return(""); } catch (AmazonServiceException e) { DisplayNetworkUploadProblem(e, progress); if (IsProductionRun) // don't make it seem like there are more upload failures than there really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } catch (Exception e) { var msg1 = LocalizationManager.GetString("PublishTab.Upload.UploadProblemNotice", "There was a problem uploading your book. You may need to restart Bloom or get technical help."); var msg2 = e.Message.Replace("{", "{{").Replace("}", "}}"); progress.WriteError(msg1); progress.WriteError(msg2); progress.WriteVerbose(e.StackTrace); if (IsProductionRun) // don't make it seem like there are more upload failures than there really are if this a tester pushing to the sandbox { Analytics.Track("UploadBook-Failure", new Dictionary <string, string>() { { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message } }); } return(""); } } finally { if (domForLocking != null && allowLocking && !wasLocked) { domForLocking.RecordAsLockedDown(false); XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile); } } return(s3BookId); }