티스토리 뷰

반응형

Windows 8 RP 버전부터 적용된 BackgroundDownloader를 이용한 다운로드 셈플이다. 기존에 sample 프로젝트를 제공하고 있는데, 실제로 사용하기에는 좀 부족해서 몇가지 기능을 수정, 보완을 했다. 맨 아래 소스를 참고하기 바란다.

 

1. 참고 포스트

BackgroundDownloader class

http://msdn.microsoft.com/en-us/library/windows/apps/windows.networking.backgroundtransfer.backgrounddownloader

Background Transfer sample

http://code.msdn.microsoft.com/windowsapps/Background-Transfer-Sample-d7833f61

A StringFormat converter for Windows 8 Metro

http://blogs.u2u.be/diederik/post/2012/03/19/A-StringFormat-converter-for-Windows-8-Metro.aspx

 

 

2. 결과 화면

시작화면

 

2개의 다운로드를 동시에 진행

 

첫번째 아이템 선택 후 Pause를 눌러서 일시 정지 

 

두번째 아이템 선택 후 일시 정지 후 첫번째 아이템 Resume 버튼을 눌러서 다시 실행

 

두번째 아이템은 취소

 

다운로드된 아이템은 앱의 템프 폴더에 저장

 

 

3. MainPage.xaml

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BackgroundUpDownSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:Common="using:BackgroundUpDownSample.Common"
    x:Class="BackgroundUpDownSample.MainPage"
    IsTabStop="false"
    mc:Ignorable="d">

    <Page.Resources>
        <Common:StringFormatConverter x:Key="StringFormatConverter"/>
        <DataTemplate x:Key="DataTemplate1">
            <Grid d:DesignWidth="322" d:DesignHeight="53">
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <TextBlock TextWrapping="Wrap" Text="{Binding ResultFileName, Mode=OneWay}"/>
                <ProgressBar Grid.Row="1" Value="{Binding DownloadProgressPercent}" HorizontalContentAlignment="Stretch"/>
                <StackPanel Grid.Row="2" Orientation="Horizontal">
                    <TextBlock Text="{Binding BytesReceived, Converter={StaticResource StringFormatConverter}, ConverterParameter='{}{0:N0}'}"/>
                    <TextBlock Text="/"/>
                    <TextBlock Text="{Binding TotalBytesToReceive, Converter={StaticResource StringFormatConverter}, ConverterParameter='{}{0:N0}'}"/>
                    <TextBlock Text="KB"/>
                    <TextBlock Text="{Binding State}" Margin="20,0,0,0" />
                </StackPanel>
            </Grid>
        </DataTemplate>
    </Page.Resources>

 

    <d:DataContext>
        <local:BackgroundViewModel/>
    </d:DataContext>

 

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*"/>
            <ColumnDefinition Width="0.5*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Margin="30,10,10,30">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Download Uri : " VerticalAlignment="Center" Width="100" />
                <TextBox Margin="0,2,0,2" VerticalAlignment="Center" MinWidth="500" TextWrapping="NoWrap" Text="{Binding UriString, Mode=TwoWay}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Local File Name : " VerticalAlignment="Center" Width="100"/>
                <TextBox Margin="0,0,0,0" VerticalAlignment="Center" MinWidth="500" TextWrapping="NoWrap" Text="{Binding FileName, Mode=TwoWay}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                <Button Content="Start" Width="80" Command="{Binding StartCommand, Mode=OneWay}"/>
            </StackPanel>

            <ListBox x:Name="lbActiveDownloads" ItemsSource="{Binding ActiveDownloads}" Margin="0,10,0,0" ItemTemplate="{StaticResource DataTemplate1}"/>

            <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                <Button Content="Pause" Width="90" Command="{Binding PauseCommand, Mode=OneWay}"/>
                <Button Content="Resume" Width="90" Command="{Binding ResumeCommand, Mode=OneWay}"/>
                <Button Content="Cancel" Width="90" Command="{Binding CancelCommand, Mode=OneWay}"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>

 

 

4. MainPage.xaml.cs

 

    public sealed partial class MainPage : Page
    {
        //뷰 모델
        public BackgroundViewModel ViewModel
        {
            get { return this.DataContext as BackgroundViewModel; }
            set
            {
                this.DataContext = value;
            }
        }

        public MainPage()
        {
            this.InitializeComponent();
            //뷰모델 생성
            ViewModel = new BackgroundViewModel();
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            //이벤트 연결
            lbActiveDownloads.SelectionChanged += ViewModel.SelectionChanged;

            //백그라운드 다운로드 아이템 복구
            ViewModel.DiscoverActiveDownloadsAsync();
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            lbActiveDownloads.SelectionChanged -= ViewModel.SelectionChanged;
        }
    }

 

5. BackgroundViewModel.cs

 

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using BackgroundUpDownSample.Common;
using Windows.Networking.BackgroundTransfer;
using Windows.Storage;
using Windows.UI.Xaml.Controls;

 

namespace BackgroundUpDownSample
{
    public class DownloadOprationItem : BindableBase
    {
        /// <summary>
        /// 취소 토큰
        /// </summary>
        public CancellationTokenSource CancelToken { get; set; }

        /// <summary>
        /// 생성자
        /// </summary>
        public DownloadOprationItem()
        {
            CancelToken = new CancellationTokenSource();
        }

        /// <summary>
        /// 취소 메소드
        /// </summary>
        public void Cancel()
        {
            CancelToken.Cancel();
            CancelToken.Dispose();
        }

        private DownloadOperation downloadOp;
        /// <summary>
        /// 다운로드 오퍼레이션
        /// </summary>
        public DownloadOperation DownloadOp
        {
            get { return downloadOp; }
            set
            {
                downloadOp = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// 다운로드 Uri
        /// </summary>
        public string RequestUri
        {
            get { return DownloadOp.RequestedUri.ToString(); }
        }

        /// <summary>
        /// 로컬 파일명
        /// </summary>
        public string ResultFileName
        {
            get { return DownloadOp.ResultFile.Name; }
        }

        private double downloadProgressPercent;
        /// <summary>
        /// 다운로드 퍼센트
        /// </summary>
        public double DownloadProgressPercent
        {
            get { return downloadProgressPercent; }
            set
            {
                downloadProgressPercent = value;
                OnPropertyChanged();
            }
        }

        private ulong totalBytesToReceive;
        /// <summary>
        /// 총크기
        /// </summary>
        public ulong TotalBytesToReceive
        {
            get { return totalBytesToReceive; }
            set
            {
                totalBytesToReceive = value;
                OnPropertyChanged();
            }
        }

        private ulong bytesReceived;
        /// <summary>
        /// 수신크기
        /// </summary>
        public ulong BytesReceived
        {
            get { return bytesReceived; }
            set
            {
                bytesReceived = value;
                OnPropertyChanged();
            }
        }

        private string state;
        /// <summary>
        /// 상태 메시지
        /// </summary>
        public string State
        {
            get { return state; }
            set
            {
                state = value;
                OnPropertyChanged();
            }
        }

    }

    public class BackgroundViewModel : BindableBase
    {
        private ObservableCollection<DownloadOprationItem> activeDownloads;
        /// <summary>
        /// 현재 백그라운드 다운로드 오퍼레이션들
        /// </summary>
        public ObservableCollection<DownloadOprationItem> ActiveDownloads
        {
            get { return activeDownloads; }
            set
            {
                activeDownloads = value;
                OnPropertyChanged();
            }
        }

        private DownloadOprationItem currentDownloadOperation;
        /// <summary>
        /// 현재 다운로드 오퍼레이션
        /// </summary>
        public DownloadOprationItem CurrentDownloadOperation
        {
            get { return currentDownloadOperation; }
            set
            {
                currentDownloadOperation = value;
                OnPropertyChanged();
            }
        }

        private string uriString;
        /// <summary>
        /// 다운로드 Uri
        /// </summary>
        public string UriString
        {
            get { return uriString; }
            set
            {
                uriString = value;
                OnPropertyChanged();
            }
        }

        private string fileName;
        /// <summary>
        /// 파일 이름
        /// </summary>
        public string FileName
        {
            get { return fileName; }
            set
            {
                fileName = value;
                OnPropertyChanged();
            }
        }
       
        /// <summary>
        /// 생성자
        /// </summary>
        public BackgroundViewModel()
        {
            ActiveDownloads = new ObservableCollection<DownloadOprationItem>();
        }

        /// <summary>
        /// 백그라운드 다운로드 복구
        /// </summary>
        /// <returns></returns>
        public async Task DiscoverActiveDownloadsAsync()
        {
            try
            {
                //백그라운드 다운로더에서 현재 다운로드 정보를 반환 받아
                IReadOnlyList<DownloadOperation> downloads = await BackgroundDownloader.GetCurrentDownloadsAsync();

                //다운로드 카운트가 있으면
                if (downloads.Count > 0)
                {
                    //테스크 리스트를 하나 만들고
                    List<Task> tasks = new List<Task>();
                    foreach (DownloadOperation download in downloads)
                    {
                        //다운로드를 테스크에 추가하고
                        tasks.Add(HandleDownloadAsync(download, false));
                    }

                    //모든 테스크가 완료되면 리턴 한다.
                    await Task.WhenAll(tasks);
                }
            }
            catch (Exception ex)
            {
            }
        }

 

        // 다운로드 시작

        public async void StartDownload(Uri source, string destination)
        {
            try
            {
                if (destination == null || destination == "")
                {
                    //파일명이 없으면 원본 파일 명을 사용한다.
                    var lastSegment = source.Segments.LastOrDefault();
                    destination = Uri.UnescapeDataString(lastSegment);
                }

                //템프 폴더에 다운로드
                var path = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path;
                StorageFile destTempFile = await Windows.Storage.ApplicationData.Current.TemporaryFolder.CreateFileAsync(destination, CreationCollisionOption.GenerateUniqueName);

                BackgroundDownloader downloader = new BackgroundDownloader();
                DownloadOperation download = downloader.CreateDownload(source, destTempFile);

                // Attach progress and completion handlers.
                await HandleDownloadAsync(download, true);
            }
            catch (Exception ex)
            {
            }
        }

 

        /// <summary>
        /// 다운로드 관리
        /// </summary>
        private async Task HandleDownloadAsync(DownloadOperation download, bool start)
        {
            var downloadItem = new DownloadOprationItem { DownloadOp = download };
            try
            {
                // 액티브 다운로드 리스트에 추가
                ActiveDownloads.Add(downloadItem);

                // 프로그래스 콜백 하나 만들고
                Progress<DownloadOperation> progressCallback = new Progress<DownloadOperation>(DownloadProgress);
                if (start)
                {
                    //CurrentDownloadOperation = downloadItem;
                    downloadItem.State = downloadItem.DownloadOp.Progress.Status.ToString();
                    // true이면 바로 시작
                    await download.StartAsync().AsTask(downloadItem.CancelToken.Token, progressCallback);
                }
                else
                {
                    downloadItem.State = downloadItem.DownloadOp.Progress.Status.ToString();
                    // false이면 캔슬토큰과 프로그래스만 붙여 놓고
                    await download.AttachAsync().AsTask(downloadItem.CancelToken.Token, progressCallback);
                }

                ResponseInformation response = download.GetResponseInformation();
            }
            catch (TaskCanceledException)
            {
                downloadItem.State = downloadItem.DownloadOp.Progress.Status.ToString();
            }
            catch (Exception ex)
            {
                downloadItem.State = "error";
            }
            finally
            {
                //ActiveDownloads.Remove(downloadItem);
                downloadItem.State = downloadItem.DownloadOp.Progress.Status.ToString();
            }
        }

 

        // 다운로드 프로그래스 처리
        private void DownloadProgress(DownloadOperation download)
        {
            double percent = 100;
            //선택된 다운로드 아이템을 찾고
            var di = ActiveDownloads.FirstOrDefault(p => p.DownloadOp.Guid == download.Guid);

            if (download.Progress.TotalBytesToReceive > 0)
            {
                percent = download.Progress.BytesReceived * 100 / download.Progress.TotalBytesToReceive;
                if (di != null)
                {
                    //다운로드 아이템의 상태, 다운로드 퍼센트, 수신 사이즈, 총 사이즈 입력
                    di.State = download.Progress.Status.ToString();
                    di.DownloadProgressPercent = percent;
                    di.BytesReceived = download.Progress.BytesReceived / 1024;
                    di.TotalBytesToReceive = download.Progress.TotalBytesToReceive / 1024;
                }
            }

            if (download.Progress.HasRestarted && di != null)
            {
                di.State = download.Progress.Status.ToString();
            }

            if (download.Progress.HasResponseChanged && di != null)
            {
                di.State = download.Progress.Status.ToString();
                //di.State = "response updated";
                // We've received new response headers from the server.
                //MarshalLog(" - Response updated; Header count: " + download.GetResponseInformation().Headers.Count);

                // If you want to stream the response data this is a good time to start.
                // download.GetResultStreamAt(0);
            }
        }

 

        private ICommand startCommand;
        /// <summary>
        /// 다운로드 스타트
        /// </summary>
        public ICommand StartCommand
        {
            get
            {
                if (startCommand == null)
                {
                    startCommand = new DelegateCommand(
                        _ =>
                        {
                            StartDownload(new Uri(UriString), FileName);
                        });
                }
                return startCommand;
            }
        }

        /// <summary>
        /// 셀렉트 체인지 이벤트 처리
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            CurrentDownloadOperation = e.AddedItems.FirstOrDefault() as DownloadOprationItem;
            CheckCanExecuteChanged();
        }

        /// <summary>
        /// 커맨드 사용가능 여부 체크
        /// </summary>
        private void CheckCanExecuteChanged()
        {
            (PauseCommand as DelegateCommand).RaiseCanExecuteChanged();
            (ResumeCommand as DelegateCommand).RaiseCanExecuteChanged();
            (CancelCommand as DelegateCommand).RaiseCanExecuteChanged();
        }

        private ICommand pauseCommand;
        /// <summary>
        /// 일시 정지
        /// </summary>
        public ICommand PauseCommand
        {
            get
            {
                if (pauseCommand == null)
                {
                    pauseCommand = new DelegateCommand(
                        _ =>
                        {
                            CurrentDownloadOperation.DownloadOp.Pause();
                            CheckCanExecuteChanged();
                        },
                        _ => IsCurrentDownloadOperation());
                }
                return pauseCommand;
            }
        }

        private ICommand resumeCommand;
        /// <summary>
        /// 다시 시작
        /// </summary>
        public ICommand ResumeCommand
        {
            get
            {
                if (resumeCommand == null)
                {
                    resumeCommand = new DelegateCommand(
                        _ =>
                        {
                            CurrentDownloadOperation.DownloadOp.Resume();
                            CheckCanExecuteChanged();
                        },
                        _ => IsCurrentDownloadOperation());
                }
                return resumeCommand;
            }
        }

        private ICommand cancelCommand;
        /// <summary>
        /// 취소
        /// </summary>
        public ICommand CancelCommand
        {
            get
            {
                if (cancelCommand == null)
                {
                    cancelCommand = new DelegateCommand(
                        _ =>
                        {
                            CurrentDownloadOperation.Cancel();
                            CheckCanExecuteChanged();
                            CurrentDownloadOperation.DownloadProgressPercent = 0;
                        },
                        _ => IsCurrentDownloadOperation());
                }
                return cancelCommand;
            }
        }

        /// <summary>
        /// 버튼 사용 가능여부 반환 함수
        /// </summary>
        /// <returns></returns>
        private bool IsCurrentDownloadOperation()
        {
            bool returnValue = false;

            if (CurrentDownloadOperation == null)
            {
                returnValue = false;
            }
            else
            {
                returnValue = true;
            }
            return returnValue;
        }
    }
}

 

 

6. 추가적인 설명을 하면 좋을 것 같기는 한데..

소스의 주석을 참고하여, 분석하고 생각해서 본인의 것으로 만들어 보기를 바란다.

 

 

7. 소스

BackgroundUpDownSample.zip

반응형
댓글