/// <summary>
 /// Create the VM starting from a file downloader
 /// </summary>
 /// <param name="fileDownloader"></param>
 public FirstSlideHeroViewModel(FileDownloadController fileDownloader, Lazy <FullTalkAsStripViewModel> fullVM)
     : this(fileDownloader == null ? null : new PDFFile(fileDownloader), fullVM)
 {
 }
Example #2
0
        /// <summary>
        /// Initialize all of our behaviors.
        /// </summary>
        /// <param name="file"></param>
        public FileUserControlViewModel(IFile file, IBlobCache cache = null)
        {
            File  = file;
            cache = cache ?? Blobs.LocalStorage;

            // Save the document type for the UI
            DocumentTypeString = file.FileType.ToUpper();

            // Setup the actual file downloader
            FileDownloader = new FileDownloadController(file, cache);

            // Now, hook up our UI indicators to the download control.

            FileDownloader.WhenAny(x => x.IsDownloading, x => x.Value)
            .ToProperty(this, x => x.IsDownloading, out _isDownloading, false, RxApp.MainThreadScheduler);

            FileDownloader.WhenAny(x => x.IsDownloaded, x => x.IsDownloading, (x, y) => !x.Value || y.Value)
            .ToProperty(this, x => x.FileNotCachedOrDownloading, out _fileNotCachedOrDownloading, true, RxApp.MainThreadScheduler);

            // Allow them to download a file.
            var canDoDownload = FileDownloader.WhenAny(x => x.IsDownloading, x => x.Value)
                                .Select(x => !x);

            ClickedUs = ReactiveCommand.Create(canDoDownload);

            ClickedUs
            .Where(_ => !FileDownloader.IsDownloaded)
            .InvokeCommand(FileDownloader.DownloadOrUpdate);

            // Opening the file is a bit more complex. It happens only when the user clicks the button a second time.
            // Requires us to write a file to the local cache.
            ClickedUs
            .Where(_ => FileDownloader.IsDownloaded)
            .SelectMany(_ => file.GetFileFromCache(cache))
            .SelectMany(async stream =>
            {
                var fname  = string.Format("{0}.{1}", file.DisplayName.CleanFilename(), file.FileType).CleanFilename();
                var fdate  = await file.GetCacheCreateTime(cache);
                var folder = fdate.HasValue ? fdate.Value.ToString().CleanFilename() : "Unknown Cache Time";

                // Write the file. If it is already written, then we will just return it (e.g. assume it is the same).
                // 0x800700B7 (-2147024713) is the error code for file already exists.
                return(CreateFile(folder, fname)
                       .SelectMany(f => f.OpenStreamForWriteAsync())
                       .SelectMany(async fstream =>
                {
                    try
                    {
                        using (var readerStream = stream.AsStreamForRead())
                        {
                            await readerStream.CopyToAsync(fstream);
                        }
                    }
                    finally
                    {
                        fstream.Dispose();
                    }
                    return default(Unit);
                })
                       .Catch <Unit, Exception>(e =>
                {
                    if (e.HResult == unchecked ((int)0x800700B7))
                    {
                        return Observable.Return(default(Unit));
                    }
                    return Observable.Throw <Unit>(e);
                })
                       .SelectMany(_ => GetExistingFile(folder, fname)));
            })
            .SelectMany(f => f)
            .ObserveOn(RxApp.MainThreadScheduler)
            .SelectMany(f =>
            {
                return(Observable.FromAsync(async _ => await Launcher.LaunchFileAsync(f))
                       .Select(good => Tuple.Create(f, good))
                       .Catch(Observable.Return(Tuple.Create(f, false))));
            })
            .Where(g => g.Item2 == false)
            .ObserveOn(RxApp.MainThreadScheduler)
            .SelectMany(f =>
            {
                return(Observable.FromAsync(async _ => await Launcher.LaunchFileAsync(f.Item1, new LauncherOptions()
                {
                    DisplayApplicationPicker = true
                }))
                       .Catch(Observable.Return(false)));
            })
            .Where(g => g == false)
            .Subscribe(
                g => { throw new InvalidOperationException(string.Format("Unable to open file {0}.", file.DisplayName)); },
                e => { throw new InvalidOperationException(string.Format("Unable to open file {0}.", file.DisplayName), e); }
                );

            // Init the UI from the cache. We want to do one or the other
            // because the download will fetch from the cache first. So no need to
            // fire them both off.

            OnLoaded = ReactiveCommand.Create();
            OnLoaded
            .Where(_ => Settings.AutoDownloadNewMeeting)
            .InvokeCommand(FileDownloader.DownloadOrUpdate);
        }
Example #3
0
 /// <summary>
 /// Dummy ctor - we don't do anythign with this in WP
 /// </summary>
 /// <param name="fileDownloadController"></param>
 /// <param name="timeSpan"></param>
 public FileSlideListViewModel(FileDownloadController fileDownloadController, Util.TimePeriod timeSpan)
 {
 }
Example #4
0
        /// <summary>
        /// Get ourselves setup and going given a file source.
        /// </summary>
        /// <param name="fileSource"></param>
        public PDFFile(FileDownloadController fileSource)
        {
            // Each time a new file shows up, get the file and decode it.
            var isDownloaded = fileSource
                               .WhenAny(x => x.IsDownloaded, x => x.Value)
                               .Where(dwn => dwn == true)
                               .Select(_ => default(Unit));

            var newFile = fileSource
                          .FileDownloadedAndCached;

            // Load it up as a real PDF document. Make sure we don't do it more than once.
            // Note the publish below - otherwise we will miss it going by if it happens too
            // fast.
            var cacheKey = Observable.Merge(isDownloaded, newFile)
                           .SelectMany(_ => fileSource.File.GetCacheCreateTime(fileSource.Cache))
                           .Select(date => string.Format("{0}-{1}", fileSource.File.UniqueKey, date.ToString()))
                           .DistinctUntilChanged();

            // This will render a document each time it is called. Note the
            // the Replay at the end. We want to use the same file for everyone. And, each time
            // a new file comes through, the cacheKey should be updated, and that should cause
            // this to be re-subscribed. So this is good ONLY FOR ONE FILE at a time. Re-subscribe to
            // get a new version of the file.
            // -> Check that we don't need a RefCount - if we did, we'd have to be careful that getting the # of pages
            // didn't cause one load, and then the rendering caused another load. The sequence might matter...
            // -> The Take(1) is to make sure we do this only once. Otherwise this sequence could remain open forever,
            //    and that will cause problems with the GetOrFetchObject, which expects to use only the last time in the sequence
            //    it looks at!
            Func <IObservable <PdfDocument> > pdfObservableFactory = () =>
                                                                     fileSource.WhenAny(x => x.IsDownloaded, x => x.Value)
                                                                     .Where(downhere => downhere == true)
                                                                     .Take(1)
                                                                     .SelectMany(_ => fileSource.File.GetFileFromCache(fileSource.Cache))
                                                                     .SelectMany(stream => PdfDocument.LoadFromStreamAsync(stream))
                                                                     .Catch <PdfDocument, Exception>(ex =>
            {
                Debug.WriteLine("The PDF rendering failed: {0}", ex.Message);
                return(Observable.Empty <PdfDocument>());
            })
                                                                     .PublishLast().ConnectAfterSubscription();

            // Finally, build the combination of these two guys.
            // Make sure that we don't keep re-creating this. We want to make sure
            // that only one version of the file (from pdfObservableFactory) is
            // generated. So do a Publish at the end here.
            var ck = cacheKey
                     .Select(key => Tuple.Create(key, pdfObservableFactory())).Replay(1);

            _pdfAndCacheKey = ck;

            // The number of pages is complex in that we will need to fetch the file and render it if we've not already
            // cached it.
            Func <IObservable <PdfDocument>, IObservable <int> > fetchNumberOfPages = docs => docs.Select(d => (int)d.PageCount);

            _pdfAndCacheKey
            .SelectMany(info => fileSource.Cache.GetOrFetchObject(string.Format("{0}-NumberOfPages", info.Item1),
                                                                  () => fetchNumberOfPages(info.Item2),
                                                                  DateTime.Now + Settings.CacheFilesTime))
            .ToProperty(this, x => x.NumberOfPages, out _nPages, 0);

            // TODO: this should probably be a RefCount - otherwise this right here causes fetches
            // from all sorts of places (like the cache). Won't trigger a download, so it isn't too bad.
            ck.Connect();
        }