Ejemplo n.º 1
0
        public void Start(Book.Book book, CollectionSettings collectionSettings, Color backColor)
        {
            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);
                    // 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.Error(id: "BadBookRequest",
                                    message: "Got a book request we could not process. Possibly the device is running an incompatible version of BloomReader?");

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

            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 = collectionSettings.Language1Iso639Code,
                BookVersion   = Book.Book.MakeVersionCode(File.ReadAllText(pathHtmlFile), pathHtmlFile)
            };

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

            _progress.Message(id: "WifiInstructions1",
                              message: "On the Android, run Bloom Reader, open the menu and choose 'Receive Books from computer'.");
            _progress.Message(id: "WifiInstructions2",
                              message: "You can do this on as many devices as you like. Make sure each device is connected to the same network as this computer.");
        }
Ejemplo n.º 2
0
 private void Work()
 {
     _progress.Message(idSuffix: "beginAdvertising", message: "Advertising book to Bloom Readers on local network...");
     try
     {
         while (true)
         {
             if (!Paused)
             {
                 UpdateAdvertisementBasedOnCurrentIpAddress();
                 _client.BeginSend(_sendBytes, _sendBytes.Length, _endPoint, SendCallback, _client);
             }
             Thread.Sleep(1000);
         }
     }
     catch (ThreadAbortException)
     {
         _progress.Message(idSuffix: "Stopped", message: "Stopped Advertising.");
         _client.Close();
     }
     catch (Exception error)
     {
         // not worth localizing
         _progress.MessageWithoutLocalizing($"Error in Advertiser: {error.Message}", ProgressKind.Error);
     }
 }
        public static string StageBloomD(Book.Book book, BookServer bookServer, WebSocketProgress progress, Color backColor, AndroidPublishSettings settings = null)
        {
            progress.Message("PublishTab.Epub.PreparingPreview", "Preparing Preview");                  // message shared with Epub publishing
            if (settings?.LanguagesToInclude != null)
            {
                var message = new LicenseChecker().CheckBook(book, settings.LanguagesToInclude.ToArray());
                if (message != null)
                {
                    progress.MessageWithoutLocalizing(message, MessageKind.Error);
                    return(null);
                }
            }

            _stagingFolder?.Dispose();
            if (AudioProcessor.IsAnyCompressedAudioMissing(book.FolderPath, book.RawDom))
            {
                progress.Message("CompressingAudio", "Compressing audio files");
                AudioProcessor.TryCompressingAudioAsNeeded(book.FolderPath, book.RawDom);
            }
            // We don't use the folder found here, but this method does some checks we want done.
            BookStorage.FindBookHtmlInFolder(book.FolderPath);
            _stagingFolder = new TemporaryFolder(StagingFolder);
            var modifiedBook = BloomReaderFileMaker.PrepareBookForBloomReader(book.FolderPath, bookServer, _stagingFolder, progress, settings: settings);

            progress.Message("Common.Done", "Shown in a list of messages when Bloom has completed a task.", "Done");
            return(modifiedBook.FolderPath.ToLocalhost());
        }
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 file transfer (MTP)...");

                _androidDeviceUsbConnection.OneReadyDeviceFound    = HandleOneReadyDeviceFound;
                _androidDeviceUsbConnection.OneReadyDeviceNotFound = HandleOneReadyDeviceNotFound;
                // 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);
            }
        }
        /// <summary>
        /// This is the core of sending a book to a device. We need a book and a bookServer in order to come up
        /// with the .bloomd file.
        /// We are either simply saving the .bloomd to destFileName, or else we will make a temporary .bloomd file and
        /// actually send it using sendAction.
        /// We report important progress on the progress control. This includes reporting that we are starting
        /// the actual transmission using startingMessageAction, which is passed the safe file name (for checking pre-existence
        /// in UsbPublisher) and the book title (typically inserted into the message).
        /// If a confirmAction is passed (currently only by UsbPublisher), we use it check for a successful transfer
        /// before reporting completion (except for file save, where the current message is inappropriate).
        /// This is an awkward case where the three ways of publishing are similar enough that
        /// it's annoying and dangerous to have three entirely separate methods but somewhat awkward to combine them.
        /// Possibly we could eventually make them more similar, e.g., it would simplify things if they all said
        /// "Sending X to Y", though I'm not sure that would be good i18n if Y is sometimes a device name
        /// and sometimes a path.
        /// </summary>
        /// <param name="book"></param>
        /// <param name="destFileName"></param>
        /// <param name="sendAction"></param>
        /// <param name="progress"></param>
        /// <param name="bookServer"></param>
        /// <param name="startingMessageFunction"></param>
        public static void SendBook(Book.Book book, BookServer bookServer, string destFileName, Action <string, string> sendAction, WebSocketProgress progress, Func <string, string, string> startingMessageFunction,
                                    Func <string, bool> confirmFunction, Color backColor, AndroidPublishSettings settings = null)
        {
            var bookTitle = book.Title;

            progress.MessageUsingTitle("PackagingBook", "Packaging \"{0}\" for use with Bloom Reader...", bookTitle, MessageKind.Progress);

            // compress audio if needed, with progress message
            if (AudioProcessor.IsAnyCompressedAudioMissing(book.FolderPath, book.RawDom))
            {
                progress.Message("CompressingAudio", "Compressing audio files");
                AudioProcessor.TryCompressingAudioAsNeeded(book.FolderPath, book.RawDom);
            }
            var publishedFileName = bookTitle + BookCompressor.ExtensionForDeviceBloomBook;

            if (startingMessageFunction != null)
            {
                progress.MessageWithoutLocalizing(startingMessageFunction(publishedFileName, bookTitle));
            }
            if (destFileName == null)
            {
                // wifi or usb...make the .bloomd in a temp folder.
                using (var bloomdTempFile = TempFile.WithFilenameInTempFolder(publishedFileName))
                {
                    BloomReaderFileMaker.CreateBloomDigitalBook(bloomdTempFile.Path, book, bookServer, backColor, progress, settings);
                    sendAction(publishedFileName, bloomdTempFile.Path);
                    if (confirmFunction != null && !confirmFunction(publishedFileName))
                    {
                        throw new ApplicationException("Book does not exist after write operation.");
                    }
                    progress.MessageUsingTitle("BookSent", "You can now read \"{0}\" in Bloom Reader!", bookTitle, MessageKind.Note);
                }
            }
            else
            {
                // save file...user has supplied name, there is no further action.
                Debug.Assert(sendAction == null, "further actions are not supported when passing a path name");
                BloomReaderFileMaker.CreateBloomDigitalBook(destFileName, book, bookServer, backColor, progress, settings);
                progress.Message("PublishTab.Epub.Done", "Done", useL10nIdPrefix: false);                       // share message string with epub publishing
            }
        }
Ejemplo n.º 6
0
 public static void SendBatchedWarningMessagesToProgress(ISet <string> warningMessages, WebSocketProgress progress)
 {
     if (warningMessages.Any())
     {
         progress.Message("Common.Warning", "Warning", ProgressKind.Warning, false);
     }
     foreach (var warningMessage in warningMessages)
     {
         // Messages are already localized
         progress.MessageWithoutLocalizing(warningMessage, ProgressKind.Warning);
     }
 }
        private string FindAceByDaisyOrTellUser(ApiRequest request)
        {
            _webSocketProgress.Message("FindingAce", "Finding Ace by DAISY on this computer...");
            var whereProgram = Platform.IsWindows ? "where" : "which";
            var npmFileName  = Platform.IsWindows ? "npm.cmd" : "npm";
            var whereResult  = CommandLineRunner.Run(whereProgram, npmFileName, Encoding.ASCII, "", 2, new NullProgress());

            if (!String.IsNullOrEmpty(whereResult.StandardError))
            {
                _webSocketProgress.ErrorWithoutLocalizing(whereResult.StandardError);
            }
            if (!whereResult.StandardOutput.Contains(npmFileName))
            {
                ReportErrorAndFailTheRequest(request, whereResult, "Could not find npm.");
                return(null);
            }

            var fullNpmPath = whereResult.StandardOutput.Split('\n')[0].Trim();
            // note: things like nvm will mess with where the global node_modules lives. The best way seems to be
            // to ask npm:
            var result = CommandLineRunner.Run(npmFileName, "root -g", Encoding.ASCII, Path.GetDirectoryName(fullNpmPath), 10,
                                               new NullProgress());

            const string kCoreError = "Could not get \"npm -g root\" to work. Is Node & npm installed and working?";

            if (result == null)
            {
                // I don't think this could happen, but *something* was null for Sue.
                ReportErrorAndFailTheRequest(request, whereResult, $"{kCoreError} CommandLineRunner.Run() returned null.");
                return(null);
            }
            if (!string.IsNullOrWhiteSpace(result.StandardError))
            {
                ReportErrorAndFailTheRequest(request, whereResult, $"{kCoreError} <br>StandardError:<br>" + result.StandardError);
                return(null);
            }
            if (result.StandardOutput == null)
            {
                ReportErrorAndFailTheRequest(request, whereResult, $"{kCoreError} StandardOutput was null.");
                return(null);
            }

            if (!result.StandardOutput.Contains("node_modules"))
            {
                ReportErrorAndFailTheRequest(request, whereResult, kCoreError);
                return(null);
            }

            var nodeModulesDirectory = result.StandardOutput.Trim();

            if (!Directory.Exists((nodeModulesDirectory)))
            {
                ReportErrorAndFailTheRequest(request, whereResult, "Could not find global node_modules directory");
                return(null);
            }

            // if they installed via npm install -g  @daisy/ace
            var daisyDirectory = Path.Combine(nodeModulesDirectory, "@daisy/ace/bin/");

            if (!Directory.Exists((daisyDirectory)))
            {
                // if they just installed via npm install -g  @daisy/ace-cli
                daisyDirectory = Path.Combine(nodeModulesDirectory, "@daisy/ace-cli/bin/");
                if (!Directory.Exists((daisyDirectory)))
                {
                    ReportErrorAndFailTheRequest(request, whereResult, $"Could not find daisy-ace at {daisyDirectory}.");
                    return(null);
                }
            }
            _webSocketProgress.Message("FoundAce", "Found.");
            return(daisyDirectory);
        }