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."); }
public void Stop() { // Locked to avoid contention with code in the thread that reports a transfer complete, // which disposes of _wifiSender and tries to restart the advertiser. lock (this) { if (_wifiAdvertiser != null) { _wifiAdvertiser.Stop(); _wifiAdvertiser.Dispose(); _wifiAdvertiser = null; } if (_wifiSender != null) { _wifiSender.CancelAsync(); Debug.WriteLine("attempting async cancel send"); } if (_uploadTimer != null) { _uploadTimer.Stop(); _uploadTimer.Dispose(); _uploadTimer = null; } } // To avoid leaving a thread around when quitting, try to wait for the sender to cancel or complete. // We expect another thread to set _wifiSender to null in the UploadDataCompleted event // (which is supposed to be triggered also by canceling). for (int i = 0; i < 30 && _wifiSender != null; i++) { Thread.Sleep(100); } lock (this) { if (_wifiSender != null) { // If it's still null we give up on the Cancel and try to shut it down any way we can. // Note that if the cancelAsync didn't work, as it generally seems not to, this could // cancel a file transfer rather abruptly. But the alternative is to leave the thread // running, possibly after Bloom has otherwise exited, causing problems like BL-5272. // (In practice even aborting this thread doesn't seem to force the file transfer to // stop, nor does anything else I've tried, so we just do our best to make sure // the thread won't outlive the application by much. Not allowing requests to queue // up is one thing that helps. At worst there's only one in progress that either // finishes or aborts before too long.) _wifiSender.Dispose(); _wifiSender = null; Debug.WriteLine("had to force dispose sender"); } } if (_wifiListener != null) { { _wifiListener.StopListener(); _wifiListener = null; } } }