public async Task RunAsync(CancellationToken token)
        {
            try
            {
                Log.Info("Starting up");

                _monitor.JournalFileWatch.Where(x => x.Action == JournalWatchAction.Started).Subscribe(
                    e => _notifier.Notify(NotificationPriority.High, NotificationEventType.FileSystem, $"Started watching '{e.File.FullName}'")
                    );

                _statusManager.ControlStateObservable
                .Throttle(TimeSpan.FromSeconds(1))
                .Subscribe(_ => _notifier.Notify(NotificationPriority.High, NotificationEventType.Update, "Updated control status"));

                var startTime = _state.LastEntrySeen ?? DateTimeOffset.MinValue;
                var source    = new JournalEntrySource(_parser, startTime, _monitor);

                var publisher   = new JournalEntryPublisher(source);
                var publication = publisher.Observable.Publish();

                _statusManager.SubscribeTo(publication);
                _processor.StartListening(token);

                publication.Subscribe(t => _updateSubject.OnNext(t.Entry.Timestamp));

                Updates
                .Subscribe(e =>
                {
                    var lastEntry   = e.Value;
                    var lastChecked = e.Timestamp;
                    Log.Info($"Updated at {lastChecked}, last entry stamped {lastEntry}");
                    _state.Update(lastChecked, lastEntry);
                });

                using (publication.Connect())
                {
                    while (!token.IsCancellationRequested)
                    {
                        await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);

                        publisher.Poll();
                    }
                }
            }
            catch (Exception e)
            {
                Log.Fatal(e);
                throw;
            }
        }
        public void Run(CancellationToken token)
        {
            Log.Info("Starting up");

            var username = _config["Username"];
            var password = _config["Password"];

            // Try username and password from configuration, if possible
            try
            {
                var nowAuthenticated = _client.Authenticate(username, password);
            }
            catch (MatrixException ex)
            {
                Log.Warn(ex.Message);
            }

            var startTime = _state.LastEntrySeen ?? DateTimeOffset.MinValue;
            var source    = new JournalEntrySource(_parser, startTime, _logMonitor, _liveMonitor);

            var publisher   = new JournalEntryPublisher(source);
            var publication = publisher.Observable.Publish();

            _gameContext.SubscribeTo(publication);
            _location.SubscribeTo(publication);
            _ship.SubscribeTo(publication);
            _session.SubscribeTo(publication);

            _logMonitor.JournalFileWatch.Subscribe(
                e =>
            {
                switch (e.Action)
                {
                case JournalWatchAction.Started:
                    _notifier.Notify(NotificationPriority.High, NotificationEventType.FileSystem, $"Started watching '{e.File.FullName}'");
                    break;

                case JournalWatchAction.Stopped:
                    _notifier.Notify(NotificationPriority.Medium, NotificationEventType.FileSystem, $"Stopped watching '{e.File.FullName}'");
                    break;
                }
            });

            _location.Observable
            .Subscribe(l =>
            {
                if (_location.TryBuildUri(_gameContext.CommanderName, _gameContext.GameVersion, out var uri))
                {
                    _notifier.Notify(NotificationPriority.Low, NotificationEventType.Update, $"Pushed {nameof(LocationState)} update");
                    _client.Push(uri, l);
                }
            });

            _ship.Observable
            .Subscribe(s =>
            {
                if (_ship.TryBuildUri(_gameContext.CommanderName, _gameContext.GameVersion, out var uri))
                {
                    _notifier.Notify(NotificationPriority.Low, NotificationEventType.Update, $"Pushed {nameof(ShipState)} update");
                    _client.Push(uri, s);
                }
            });

            _session.Observable
            .Subscribe(s =>
            {
                if (_session.TryBuildUri(_gameContext.CommanderName, _gameContext.GameVersion, out var uri))
                {
                    _notifier.Notify(NotificationPriority.Low, NotificationEventType.Update, $"Pushed {nameof(SessionState)} update");
                    _client.Push(uri, s);
                }
            });

            Updates
            .Subscribe(x =>
            {
                var lastEntry   = x.Value;
                var lastChecked = x.Timestamp;
                _state.Update(lastChecked, lastEntry);
                _notifier.Notify(NotificationPriority.Low, NotificationEventType.JournalEntry, "Journal entries applied");
            });

            var readyForNextBatch = new ManualResetEventSlim(true);

            using (publication.Connect())
            {
                while (!token.IsCancellationRequested)
                {
                    Task.Delay(TimeSpan.FromSeconds(1)).Wait();

                    publisher.Poll();

                    readyForNextBatch.Reset();

                    _client.StartUploading(token).Subscribe(t =>
                    {
                        _notifier.Notify(NotificationPriority.Low, NotificationEventType.Update, "Uploaded data");
                        _updateSubject.OnNext(t);
                    }, async ex =>
                    {
                        if (ex is MatrixAuthenticationException)
                        {
                            _notifier.Notify(NotificationPriority.High, NotificationEventType.Error, "Authentication failure");
                            Log.Warn(ex.Message);
                            // Block further processing until we at least attempt authentication
                            try
                            {
                                var authenticated = await _authenticator.RequestAuthentication();
                            }
                            catch (MatrixException mex)
                            {
                                _notifier.Notify(NotificationPriority.Medium, NotificationEventType.Error, "Authentication failure");
                                Log.Warn(mex.Message);
                            }
                        }
                        else
                        {
                            Log.Error(ex.Message);
                        }
                        readyForNextBatch.Set();
                    }, () =>
                    {
                        // Upload was successful, can proceed with another poll
                        Log.Debug("Completed pending uploads");
                        readyForNextBatch.Set();
                    }, token);

                    // Wait until either no longer blocked, or the app is being shut down
                    WaitHandle.WaitAny(new[] { readyForNextBatch.WaitHandle, token.WaitHandle });
                }
            }
        }