Ejemplo n.º 1
0
        /// <summary>
        /// Attempt to send the book to the device
        /// </summary>
        public void SendBookAsync(Book.Book book, Color backColor, AndroidPublishSettings settings = null)
        {
            try
            {
                var backgroundWorker = new BackgroundWorker();
                backgroundWorker.DoWork += (sender, args) => { SendBookDoWork(book, backColor, settings); };

                backgroundWorker.RunWorkerCompleted += (sender, args) =>
                {
                    if (args.Error != null)
                    {
                        FailSendBook(args.Error);
                    }
                    else
                    {
                        Stopped();
                    }
                };
                backgroundWorker.RunWorkerAsync();
            }
            catch (Exception e)
            {
                FailSendBook(e);
            }
        }
Ejemplo n.º 2
0
        private void HandleOneReadyDeviceFound(Book.Book book, Color backColor, AndroidPublishSettings settings = null)
        {
            _progress.MessageWithParams(idSuffix: "Connected",
                                        message: "Connected to {0} via USB...",
                                        comment: "{0} is a the name of the device Bloom connected to",
                                        progressKind: ProgressKind.Progress,
                                        parameters: _androidDeviceUsbConnection.GetDeviceName());

            SendBookAsync(book, backColor, settings);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Attempt to establish a connection with an Android device; then send it the book.
        /// </summary>
        public void ConnectAndSendToOneDevice(Book.Book book, Color backColor, AndroidPublishSettings settings = null)
        {
            _stopLookingForDevice = false;
            _device = null;

            //The UX here is to only allow one device plugged in a time.
            while (!_stopLookingForDevice && _device == null)
            {
                var devices = EnumerateAllDevices();
                if (!ConnectAndSendToOneDeviceInternal(devices, book, backColor, settings))
                {
                    Thread.Sleep(1000);
                }
            }
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Attempt to connect to a device
        /// </summary>
        /// <param name="book"></param>
        public void Connect(Book.Book book, Color backColor, AndroidPublishSettings settings = null)
        {
            try
            {
                // Calls to this come from JavaScript, not sure they will always be on the UI thread.
                // Before I added this, I definitely saw race conditions with more than one thread trying
                // to figure out what was connected.
                lock (this)
                {
                    PublishToAndroidApi.CheckBookLayout(book, _progress);
                    if (_connectionHandler != null)
                    {
                        // we're in an odd state...should only be able to click the button that calls this
                        // while stopped.
                        // Try to really get into the right state in case the user tries again.
                        _androidDeviceUsbConnection.StopFindingDevice();
                        return;
                    }
                    // Create this while locked...once we have it, can't enter the main logic of this method
                    // on another thread.
                    _connectionHandler = new BackgroundWorker();
                }
                _progress.Message(idSuffix: "LookingForDevice",
                                  comment: "This is a progress message; MTP is an acronym for the system that allows computers to access files on devices.",
                                  message: "Looking for an Android device connected by USB cable and set up for MTP...");

                _androidDeviceUsbConnection.OneReadyDeviceFound    = HandleFoundAReadyDevice;
                _androidDeviceUsbConnection.OneReadyDeviceNotFound = HandleFoundOneNonReadyDevice;
                // Don't suppress the first message after (re)starting.
                _previousDeviceNotFoundReportType = DeviceNotFoundReportType.Unknown;


                _connectionHandler.DoWork             += (sender, args) => _androidDeviceUsbConnection.ConnectAndSendToOneDevice(book, backColor, settings);
                _connectionHandler.RunWorkerCompleted += (sender, args) =>
                {
                    if (args.Error != null)
                    {
                        UsbFailConnect(args.Error);
                    }
                    _connectionHandler = null;                     // now OK to try to connect again.
                };
                _connectionHandler.RunWorkerAsync();
            }
            catch (Exception e)
            {
                UsbFailConnect(e);
            }
        }
Ejemplo n.º 5
0
 protected virtual void SendBookDoWork(Book.Book book, Color backColor, AndroidPublishSettings settings = null)
 {
     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,
                                  settings: settings);
     PublishToAndroidApi.ReportAnalytics("usb", book);
 }
Ejemplo n.º 6
0
        public void Start(Book.Book book, CollectionSettings collectionSettings, Color backColor, AndroidPublishSettings publishSettings = null)
        {
            if (_wifiAdvertiser != null)
            {
                Stop();
            }

            // This listens for a BloomReader to request a book.
            // It requires a firewall hole allowing Bloom to receive messages on _portToListen.
            // We initialize it before starting the Advertiser to avoid any chance of a race condition
            // where a BloomReader manages to request an advertised book before we start the listener.
            _wifiListener = new BloomReaderUDPListener();
            _wifiListener.NewMessageReceived += (sender, args) =>
            {
                var json = Encoding.UTF8.GetString(args.Data);
                try
                {
                    dynamic settings = JsonConvert.DeserializeObject(json);
                    // The property names used here must match the ones in BloomReader, doInBackground method of SendMessage,
                    // a private class of NewBookListenerService.
                    var androidIpAddress = (string)settings.deviceAddress;

                    var androidName = (string)settings.deviceName;
                    // This prevents the device (or other devices) from queuing up requests while we're busy with this one.
                    // In effect, the Android is only allowed to request a retry after we've given up this try at sending.
                    // Of course, there are async effects from network latency. But if we do get another request while
                    // handling this one, we will ignore it, since StartSendBook checks for a transfer in progress.
                    _wifiAdvertiser.Paused = true;
                    StartSendBookOverWiFi(book, androidIpAddress, androidName, backColor, publishSettings);
                    // Returns immediately. But we don't resume advertisements until the async send completes.
                }
                // If there's something wrong with the JSON (maybe an obsolete or newer version of reader?)
                // just ignore the request.
                catch (Exception ex) when(ex is JsonReaderException || ex is JsonSerializationException)
                {
                    _progress.Message(idSuffix: "BadBookRequest",
                                      message: "Got a book request we could not process. Possibly the device is running an incompatible version of BloomReader?",
                                      progressKind: ProgressKind.Error);

                    //this is too technical/hard to translate
                    _progress.MessageWithoutLocalizing($" Request contains {json}; trying to interpret as JSON we got {ex.Message}", kind: ProgressKind.Error);
                }
            };

            var pathHtmlFile = book.GetPathHtmlFile();

            _wifiAdvertiser = new WiFiAdvertiser(_progress)
            {
                BookTitle     = BookStorage.SanitizeNameForFileSystem(book.Title),             // must be the exact same name as the file we will send if requested
                TitleLanguage = book.BookData.Language1.Iso639Code,
                BookVersion   = Book.Book.MakeVersionCode(File.ReadAllText(pathHtmlFile), pathHtmlFile)
            };

            PublishToAndroidApi.CheckBookLayout(book, _progress);
            _wifiAdvertiser.Start();

            var part1 = LocalizationManager.GetDynamicString(appId: "Bloom", id: "PublishTab.Android.Wifi.Progress.WifiInstructions1",
                                                             englishText: "On the Android, run Bloom Reader, open the menu and choose 'Receive Books via WiFi'.");
            var part2 = LocalizationManager.GetDynamicString(appId: "Bloom", id: "PublishTab.Android.Wifi.Progress.WifiInstructions2",
                                                             englishText: "You can do this on as many devices as you like. Make sure each device is connected to the same network as this computer.");

            // can only have one instruction up at a time, so we concatenate these
            _progress.MessageWithoutLocalizing(part1 + " " + part2, ProgressKind.Instruction);
        }
Ejemplo n.º 7
0
 private void StartSendBookOverWiFi(Book.Book book, string androidIpAddress, string androidName, Color backColor, AndroidPublishSettings settings = null)
 {
     try
     {
         StartSendBookToClientOnLocalSubNet(book, androidIpAddress, androidName, backColor, settings);
     }
     catch (Exception e)
     {
         ReportException(e);
     }
 }
Ejemplo n.º 8
0
 /// <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);
 }
Ejemplo n.º 9
0
        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);
        }
Ejemplo n.º 10
0
        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);
                }
            }
        }
Ejemplo n.º 11
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);
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Find the one Android device
        /// (indicated by the presence of the Android directory as a direct child of a root storage object)
        /// and send to it.
        /// </summary>
        /// <returns>true if it found one ready device</returns>
        private bool ConnectAndSendToOneDeviceInternal(IEnumerable <IDevice> devices, Book.Book book, Color backColor, AndroidPublishSettings settings = null)
        {
            List <IDevice> applicableDevices = new List <IDevice>();

            foreach (var device in devices)
            {
                var androidFolderPath = GetAndroidFolderPath(device);
                if (androidFolderPath != null)
                {
                    applicableDevices.Add(device);
                    _bloomFolderPath = Path.GetDirectoryName(androidFolderPath) + "\\" + kBloomFolderOnDevice;
                    _device          = device;
                }
            }

            if (applicableDevices.Count == 1)
            {
                try
                {
                    _device.CreateFolderObjectFromPath(_bloomFolderPath);
                }
                catch (Exception e)
                {
                    SIL.Reporting.Logger.WriteError("Unable to create Bloom folder on device.", e);

                    // Treat it as a no-device situation.
                    _bloomFolderPath = null;
                    _device          = null;
                    OneReadyDeviceNotFound?.Invoke(DeviceNotFoundReportType.NoDeviceFound, new List <string>(0));

                    return(false);
                }
                OneReadyDeviceFound(book, backColor, settings);
                return(true);
            }

            _bloomFolderPath = null;
            _device          = null;

            DeviceNotFoundReportType deviceNotFoundReportType =
                applicableDevices.Count > 1
                                ?
                DeviceNotFoundReportType.MoreThanOneReadyDevice
                                :
                DeviceNotFoundReportType.NoDeviceFound;

            OneReadyDeviceNotFound?.Invoke(deviceNotFoundReportType,
                                           devices.Select(d => d.Name).ToList());

            return(false);
        }
Ejemplo n.º 13
0
 protected override void SendBookDoWork(Bloom.Book.Book book, Color backColor, AndroidPublishSettings settings)
 {
     throw new COMException("MockUsbPublisher threw a fake COMException in SendBookDoWork.", _exceptionToThrow);
 }
        /// <summary>
        /// Find the one with Bloom Reader installed (indicated by the presence of the Bloom directory
        /// as a direct child of a root storage object)
        /// </summary>
        /// <param name="devices"></param>
        /// <returns>true if it found a ready device</returns>
        private bool ConnectAndSendToOneDeviceInternal(IEnumerable <IDevice> devices, Book.Book book, Color backColor, AndroidPublishSettings settings = null)
        {
            List <IDevice> applicableDevices = new List <IDevice>();
            int            totalDevicesFound = 0;

            foreach (var device in devices)
            {
                _bloomFolderPath = GetBloomFolderPath(device);
                if (_bloomFolderPath != null)
                {
                    applicableDevices.Add(device);
                }
                totalDevicesFound++;
            }

            if (applicableDevices.Count == 1)
            {
                _device = applicableDevices[0];
                // Without this, we're depending on the LAST device we tried being the applicable one.
                _bloomFolderPath = GetBloomFolderPath(_device);
                OneReadyDeviceFound(book, backColor, settings);
                return(true);
            }

            _bloomFolderPath = null;

            if (totalDevicesFound > 0 && applicableDevices.Count == 0)
            {
                OneReadyDeviceNotFound(DeviceNotFoundReportType.NoBloomDirectory,
                                       devices.Select(d => d.Name).ToList());
                return(false);
            }

            DeviceNotFoundReportType deviceNotFoundReportType = DeviceNotFoundReportType.NoDeviceFound;

            if (applicableDevices.Count > 1)
            {
                deviceNotFoundReportType = DeviceNotFoundReportType.MoreThanOneReadyDevice;
            }
            OneReadyDeviceNotFound?.Invoke(deviceNotFoundReportType,
                                           devices.Select(d => d.Name).ToList());

            return(false);
        }