public static void Save(Book.Book book, BookServer bookServer, Color backColor, WebSocketProgress progress, AndroidPublishSettings settings = null) { var progressWithL10N = progress.WithL10NPrefix("PublishTab.Android.File.Progress."); var folder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); if (!string.IsNullOrWhiteSpace(Settings.Default.BloomDeviceFileExportFolder) && Directory.Exists(Settings.Default.BloomDeviceFileExportFolder)) { folder = Settings.Default.BloomDeviceFileExportFolder; } var initialPath = Path.Combine(folder, book.Storage.FolderName + BookCompressor.BloomPubExtensionWithDot); var bloomdFileDescription = LocalizationManager.GetString("PublishTab.Android.bloomdFileFormatLabel", "Bloom Book for Devices", "This is shown in the 'Save' dialog when you save a bloom book in the format that works with the Bloom Reader Android App"); var filter = $"{bloomdFileDescription}|*{BookCompressor.BloomPubExtensionWithDot}"; var destFileName = Utils.MiscUtils.GetOutputFilePathOutsideCollectionFolder(initialPath, filter); if (String.IsNullOrEmpty(destFileName)) { return; } Settings.Default.BloomDeviceFileExportFolder = Path.GetDirectoryName(destFileName); PublishToAndroidApi.CheckBookLayout(book, progress); PublishToAndroidApi.SendBook(book, bookServer, destFileName, null, progressWithL10N, (publishedFileName, bookTitle) => progressWithL10N.GetMessageWithParams("Saving", "{0} is a file path", "Saving as {0}", destFileName), null, backColor, settings: settings); PublishToAndroidApi.ReportAnalytics("file", book); }
public static void Save(Book.Book book, BookServer bookServer, Color backColor, WebSocketProgress progress, AndroidPublishSettings settings = null) { var progressWithL10N = progress.WithL10NPrefix("PublishTab.Android.File.Progress."); using (var dlg = new DialogAdapters.SaveFileDialogAdapter()) { dlg.DefaultExt = BookCompressor.ExtensionForDeviceBloomBook; var bloomdFileDescription = LocalizationManager.GetString("PublishTab.Android.bloomdFileFormatLabel", "Bloom Book for Devices", "This is shown in the 'Save' dialog when you save a bloom book in the format that works with the Bloom Reader Android App"); dlg.Filter = $"{bloomdFileDescription}|*{BookCompressor.ExtensionForDeviceBloomBook}"; dlg.FileName = Path.GetFileName(book.FolderPath) + BookCompressor.ExtensionForDeviceBloomBook; if (!string.IsNullOrWhiteSpace(Settings.Default.BloomDeviceFileExportFolder) && Directory.Exists(Settings.Default.BloomDeviceFileExportFolder)) { dlg.InitialDirectory = Settings.Default.BloomDeviceFileExportFolder; //(otherwise leave to default save location) } dlg.OverwritePrompt = true; if (DialogResult.OK == dlg.ShowDialog()) { Settings.Default.BloomDeviceFileExportFolder = Path.GetDirectoryName(dlg.FileName); PublishToAndroidApi.CheckBookLayout(book, progress); PublishToAndroidApi.SendBook(book, bookServer, dlg.FileName, null, progressWithL10N, (publishedFileName, bookTitle) => progressWithL10N.GetMessageWithParams("Saving", "{0} is a file path", "Saving as {0}", dlg.FileName), null, backColor, settings: settings); PublishToAndroidApi.ReportAnalytics("file", book); } } }
/// <summary> /// Send the book to a client over local network, typically WiFi (at least on Android end). /// This is currently called on the UDPListener thread. /// Enhance: if we spin off another thread to do the transfer, especially if we create the file /// and read it into memory once and share the content, we can probably serve multiple /// requesting devices much faster. Currently, we are only handling one request at a time, /// since we pause advertising while sending and ignore requests that come in during sending. /// If the user switches away from the Android tab while a transfer /// is in progress, the thread will continue and complete the request. Quitting Bloom /// is likely to leave the transfer incomplete. /// </summary> /// <param name="book"></param> /// <param name="androidIpAddress"></param> /// <param name="androidName"></param> private void StartSendBookToClientOnLocalSubNet(Book.Book book, string androidIpAddress, string androidName, Color backColor) { // Locked in case more than one thread at a time can handle incoming packets, though I don't think // this is true. Also, Stop() on the main thread cares whether _wifiSender is null. lock (this) { // We only support one send at a time. If we somehow get more than one request, we ignore the other. // The device will retry soon if still listening and we are still advertising. if (_wifiSender != null) // indicates transfer in progress { return; } // now THIS transfer is 'in progress' as far as any thread checking this is concerned. _wifiSender = new WebClient(); } _wifiSender.UploadDataCompleted += (sender, args) => { // Runs on the async transfer thread AFTER the transfer initiated below. if (args.Error != null) { ReportException(args.Error); } // Should we report if canceled? Thinking not, we typically only cancel while shutting down, // it's probably too late for a useful report. // To avoid contention with Stop(), which may try to cancel the send if it finds // an existing wifiSender, and may destroy the advertiser we are trying to restart. lock (this) { Debug.WriteLine($"upload completed, sender is {_wifiSender}, cancelled is {args.Cancelled}"); if (_wifiSender != null) // should be null only in a desperate abort-the-thread situation. { _wifiSender.Dispose(); _wifiSender = null; } if (_wifiAdvertiser != null) { _wifiAdvertiser.Paused = false; } } }; // Now we actually start the send...but using an async API, so there's no long delay here. PublishToAndroidApi.SendBook(book, _bookServer, null, (publishedFileName, bloomDPath) => { var androidHttpAddress = "http://" + androidIpAddress + ":5914"; // must match BloomReader SyncServer._serverPort. _wifiSender.UploadDataAsync(new Uri(androidHttpAddress + "/putfile?path=" + Uri.EscapeDataString(publishedFileName)), File.ReadAllBytes(bloomDPath)); }, _progress, (publishedFileName, bookTitle) => _progress.GetMessageWithParams(id: "Sending", comment: "{0} is the name of the book, {1} is the name of the device", message: "Sending \"{0}\" to device {1}", parameters: new object[] { bookTitle, androidName }), null, backColor); PublishToAndroidApi.ReportAnalytics("wifi", book); }
// internal virtual for testing only protected virtual void SendBookDoWork(Book.Book book, Color backColor) { PublishToAndroidApi.SendBook(book, _bookServer, null, (publishedFileName, path) => { _lastPublishedBloomdSize = GetSizeOfBloomdFile(path); _androidDeviceUsbConnection.SendBook(path); }, _progress, (publishedFileName, bookTitle) => _androidDeviceUsbConnection.BookExists(publishedFileName) ? _progress.GetTitleMessage("ReplacingBook", "Replacing existing \"{0}\"...", bookTitle) : _progress.GetTitleMessage("SendingBook", "Sending \"{0}\" to your Android device...", bookTitle), publishedFileName => _androidDeviceUsbConnection.BookExists(publishedFileName), backColor); PublishToAndroidApi.ReportAnalytics("usb", book); }
/// <summary> /// Send the book to a client over local network, typically WiFi (at least on Android end). /// This is currently called on the UDPListener thread. /// Enhance: if we spin off another thread to do the transfer, especially if we create the file /// and read it into memory once and share the content, we can probably serve multiple /// requesting devices much faster. Currently, we are only handling one request at a time, /// since we pause advertising while sending and ignore requests that come in during sending. /// If the user switches away from the Android tab while a transfer /// is in progress, the thread will continue and complete the request. Quitting Bloom /// is likely to leave the transfer incomplete. /// </summary> private void StartSendBookToClientOnLocalSubNet(Book.Book book, string androidIpAddress, string androidName, Color backColor, AndroidPublishSettings settings = null) { // Locked in case more than one thread at a time can handle incoming packets, though I don't think // this is true. Also, Stop() on the main thread cares whether _wifiSender is null. lock (this) { // We only support one send at a time. If we somehow get more than one request, we ignore the other. // The device will retry soon if still listening and we are still advertising. if (_wifiSender != null) // indicates transfer in progress { return; } // now THIS transfer is 'in progress' as far as any thread checking this is concerned. _wifiSender = new WebClient(); } _wifiSender.UploadDataCompleted += WifiSenderUploadCompleted; // Now we actually start the send...but using an async API, so there's no long delay here. PublishToAndroidApi.SendBook(book, _bookServer, null, (publishedFileName, bloomDPath) => { var androidHttpAddress = "http://" + androidIpAddress + ":5914"; // must match BloomReader SyncServer._serverPort. _wifiSender.UploadDataAsync(new Uri(androidHttpAddress + "/putfile?path=" + Uri.EscapeDataString(publishedFileName)), File.ReadAllBytes(bloomDPath)); Debug.WriteLine($"upload started to http://{androidIpAddress}:5914 ({androidName}) for {publishedFileName}"); }, _progress, (publishedFileName, bookTitle) => _progress.GetMessageWithParams(idSuffix: "Sending", comment: "{0} is the name of the book, {1} is the name of the device", message: "Sending \"{0}\" to device {1}", parameters: new object[] { bookTitle, androidName }), null, backColor, settings: settings); // Occasionally preparing a book for sending will, despite our best efforts, result in a different sha. // For example, it might change missing or out-of-date mp3 files. In case the sha we just computed // is different from the one we're advertising, update the advertisement, so at least subsequent // advertisements will conform to the version the device just got. _wifiAdvertiser.BookVersion = BookCompressor.LastVersionCode; lock (this) { // The UploadDataCompleted event handler quit working at Bloom 4.6.1238 Alpha (Windows test build). // The data upload still works, but the event handler is *NEVER* called. Trying to revise the upload // by using UploadDataTaskAsync with async/await did not work any better: the await never happened. // To get around this bug, we introduce a timer that periodically checks the IsBusy flag of the // _wifiSender object. It's a hack, but I haven't come up with anything better in two days of // looking at this problem. // See https://issues.bloomlibrary.org/youtrack/issue/BL-7227 for details. if (_uploadTimer == null) { _uploadTimer = new System.Timers.Timer { Interval = 500.0, Enabled = false }; _uploadTimer.Elapsed += (sender, args) => { if (_wifiSender != null && _wifiSender.IsBusy) { return; } _uploadTimer.Stop(); Debug.WriteLine("upload timed out, appears to be finished"); WifiSenderUploadCompleted(_uploadTimer, null); }; } _uploadTimer.Start(); } PublishToAndroidApi.ReportAnalytics("wifi", book); }