public FileOperationViewModel(string filePath, IEngine engine)
        {
            //this property doesn't change
            OldFilePath = filePath;

            //filepath property
            _filePath = Observable.FromEventPattern <EventHandler <FileEventArgs>, FileEventArgs>(h => engine.FileMoveStarted += h, h => engine.FileMoveStarted -= h)
                        .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                        .Select(e => e.EventArgs.FilePath)
                        .ToProperty(this, vm => vm.FilePath);

            //state property
            _state = Observable.Merge(Observable.FromEventPattern <EventHandler <FileHashEventArgs>, FileHashEventArgs>(h => engine.FileHashProgress += h, h => engine.FileHashProgress -= h)
                                      .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                      .Select(e => FileOperationState.Verifying),
                                      Observable.FromEventPattern <EventHandler <FileEventArgs>, FileEventArgs>(h => engine.FileMoveStarted += h, h => engine.FileMoveStarted -= h)
                                      .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                      .Select(e => FileOperationState.Moving),
                                      Observable.FromEventPattern <EventHandler <FileEventArgs>, FileEventArgs>(h => engine.FileMoveCompleted += h, h => engine.FileMoveCompleted -= h)
                                      .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                      .Select(e => FileOperationState.Completed),
                                      Observable.FromEventPattern <EventHandler <FileErrorEventArgs>, FileErrorEventArgs>(h => engine.FileMoveError += h, h => engine.FileMoveError -= h)
                                      .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                      .Select(e => FileOperationState.Error))
                     .StartWith(FileOperationState.Detected)
                     .ToProperty(this, vm => vm.State);

            //percentage
            _percentage = Observable.Merge(Observable.FromEventPattern <EventHandler <FileMoveEventArgs>, FileMoveEventArgs>(h => engine.FileMoveProgress += h, h => engine.FileMoveProgress -= h)
                                           .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                           .Select(e => e.EventArgs.Percentage),
                                           Observable.FromEventPattern <EventHandler <FileHashEventArgs>, FileHashEventArgs>(h => engine.FileHashProgress += h, h => engine.FileHashProgress -= h)
                                           .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                           .Select(e => e.EventArgs.Percentage))
                          .ToProperty(this, vm => vm.Percentage);

            //in progress
            _inProgress = _state.AsObservable()
                          .Select(e => e != FileOperationState.Completed && e != FileOperationState.Error)
                          .ToProperty(this, vm => vm.InProgress);

            //error
            _error = Observable.FromEventPattern <EventHandler <FileErrorEventArgs>, FileErrorEventArgs>(h => engine.FileMoveError += h, h => engine.FileMoveError -= h)
                     .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                     .Select(e => e.EventArgs.Exception)
                     .ToProperty(this, vm => vm.Error);

            //tries
            _triesText = Observable.Merge(Observable.FromEventPattern <EventHandler <FileHashEventArgs>, FileHashEventArgs>(h => engine.FileHashProgress += h, h => engine.FileHashProgress -= h)
                                          .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                          .Select(e => string.Format("Attempt {0} of {1}", e.EventArgs.Tries + 1, engine.Config.FileMoveRetries)),
                                          Observable.FromEventPattern <EventHandler <FileMoveEventArgs>, FileMoveEventArgs>(h => engine.FileMoveProgress += h, h => engine.FileMoveProgress -= h)
                                          .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                          .Select(e => string.Format("Attempt {0} of {1}", e.EventArgs.Tries + 1, engine.Config.FileMoveRetries)),
                                          Observable.FromEventPattern <EventHandler <FileErrorEventArgs>, FileErrorEventArgs>(h => engine.FileMoveError += h, h => engine.FileMoveError -= h)
                                          .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                          .Select(e => string.Format("Attempt {0} of {1}", e.EventArgs.Tries + 1, engine.Config.FileMoveRetries)))
                         .StartWith(string.Format("Attempt 1 of {0}", engine.Config.FileMoveRetries))
                         .ToProperty(this, vm => vm.TriesText);

            //source hash
            _sourceFileHash = Observable.FromEventPattern <EventHandler <FileHashEventArgs>, FileHashEventArgs>(h => engine.FileHashProgress += h, h => engine.FileHashProgress -= h)
                              .Where(e => e.EventArgs.OldFilePath == OldFilePath && e.EventArgs.HashFilePath == OldFilePath)
                              .Select(e => e.EventArgs.Hash)
                              .StartWith(string.Empty)
                              .ToProperty(this, vm => vm.SourceFileHash);

            //destination hash
            _destinationFileHash = Observable.FromEventPattern <EventHandler <FileHashEventArgs>, FileHashEventArgs>(h => engine.FileHashProgress += h, h => engine.FileHashProgress -= h)
                                   .Where(e => e.EventArgs.OldFilePath == OldFilePath && e.EventArgs.HashFilePath == e.EventArgs.FilePath)
                                   .Select(e => e.EventArgs.Hash)
                                   .StartWith(string.Empty)
                                   .ToProperty(this, vm => vm.SourceFileHash);

            //retry waiting
            _retryWaiting = Observable.Merge(Observable.FromEventPattern <EventHandler <FileRetryEventArgs>, FileRetryEventArgs>(h => engine.FileRetryWaiting += h, h => engine.FileRetryWaiting -= h)
                                             .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                             .Select(_ => true),
                                             Observable.FromEventPattern <EventHandler <FileTryEventArgs>, FileTryEventArgs>(h => engine.FileRetrying += h, h => engine.FileRetrying -= h)
                                             .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                             .Select(_ => false))
                            .StartWith(false)
                            .ToProperty(this, vm => vm.RetryWaiting);

            //seconds until retry - set as empty at first
            _secondsUntilRetryText = Observable.Empty <string>().ToProperty(this, vm => vm.SecondsUntilRetryText);

            var retryDelaySeconds = Observable.FromEventPattern <EventHandler <FileRetryEventArgs>, FileRetryEventArgs>(h => engine.FileRetryWaiting += h, h => engine.FileRetryWaiting -= h)
                                    .Where(e => e.EventArgs.OldFilePath == OldFilePath)
                                    .Select(e => e.EventArgs.RetryDelay.TotalSeconds);

            retryDelaySeconds.Subscribe(s => {
                //seconds until retry
                _secondsUntilRetryText = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
                                         .Select(x => s - x)
                                         .TakeWhile(x => x > 0)
                                         .StartWith(s)
                                         .Select(x => string.Format("Retrying in {0} seconds...", x))
                                         .ToProperty(this, vm => vm.SecondsUntilRetryText);
            });
        }