一連寫了四篇筆記,介紹了Live SDK,也實際玩過取得使用者Live ID身分、連上SkyDrive等把戲:

埋了這些哽,其實背後我最想做的,是從WP7程式連上SkyDrive!

手機基於儲存空間的限制,以及跨裝置/平台共享的需求,非常需要結合網路儲存空間做為後盾,而WP7配上微軟體系的SkyDrive,感覺上是很棒的組合。

Live SDK有提供WP7專用的元件庫,下載安裝Live SDK後,可在WP7專案參照中找到Microsoft.Live及Microsoft.Live.Controls,學習中心有篇WP7整合Live SDK教學,是很好的入門。

Live SDK提供SignInButton控制項,表面上是顆登入登出鈕,背地則幫我們處理掉在App內嵌瀏覽器載入Live帳號登入網頁以及同意網頁等繁瑣細節,是整合Live SDK最簡便的做法。如果在Visual Studio工具箱沒看到它,可自行找到Microsoft.Live.Controls.dll加入工具箱(如下圖)。

不過,有一點要注意,由於在App運作時,不像在ASP.NET範例中會有Callback.aspx負責接入Authorization Code再呼叫API取得Access Token。請參考註冊App一文第3點提及的做法,申請Client ID時,Redirect Domain請留白,然後勾選"Mobile client app"為"Yes",如此,此Client ID即可使用httqs://oauth.live.com/desktop做為Redirect Page,並直接取得Access Token,如此在行動裝置上即可獨立運作。

我想試做的玩具是一個SkyDrive簡易瀏覽器,功能跟上一篇的ASP.NET版差不多,取得使用者對wl.skydrive範圍的同意權後,列出使用者SkyDrive下的目錄,點選目錄可展開列出其下的子項目,若是檔案則可進行檢視,不過WP7能支援的檔案格式比PC少,我只先選擇對圖片(jpg, png)及影片(wmv, mp3, mp4)提供檢視功能。

MainPage.xaml放入一個SignInButton及ListBox:

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Mini SkyDrive Explorer" 
                       Style="{StaticResource PhoneTextNormalStyle}"/>
            <my:SignInButton x:Name="btnSignIn" Branding="Skydrive" 
             SessionChanged="btnSignIn_SessionChanged" CientId="0000000016888888" 
             RedirectUri="https://oauth.live.com/desktop" 
             Scopes="wl.signin wl.basic wl.skydrive"/>
        </StackPanel>
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ListBox x:Name="DirList" FontSize="36" 
                     MouseLeftButtonUp="DirList_MouseLeftButtonUp">
            </ListBox>
        </Grid>
    </Grid>

MainPage.xaml.cs則加入程式邏輯,主要原理是在使用者完成登入及同意後(btnSignIn_SessionChanged事件)建立一個LiveConnectClient,即可使用它呼叫REST API執行各種操作。呼叫結果由client_GetCompleted事件解析,相關的註解說明我附加在程式裡,所以直接看Code:

using System;
using System.Collections.Generic;
using System.Windows.Input;
using Microsoft.Phone.Controls;
using Microsoft.Live;
using Microsoft.Live.Controls;
 
namespace MiniSkyDriveExplorer
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }
 
        private LiveConnectClient client = null;
        private void btnSignIn_SessionChanged(object sender, 
            LiveConnectSessionChangedEventArgs e)
        {
            if (e.Status == LiveConnectSessionStatus.Connected)
            {
                DirItem.LiveSession = e.Session;
                client = new LiveConnectClient(e.Session);
                client.GetCompleted += 
                    new EventHandler<LiveOperationCompletedEventArgs>(
                        client_GetCompleted);
                //呼叫me/skydrive,取得根目錄folderId
                client.GetAsync("me/skydrive", 
                    new MyUserState(ApiMethod.SkyDriveProp));
            }
        }
        private const string goUpperSymbol = "\t";
        //所有的REST API呼叫動作完成後,都會觸發此段解析回傳結果
        //為降低範例程式複雜度,以下並未包含防呆容錯的邏輯,實際開發時應補上
        void client_GetCompleted(object 
                                 sender, LiveOperationCompletedEventArgs e)
        {
            MyUserState state = e.UserState as MyUserState;
            if (state == null) return;
            switch (state.Method)
            {
                //取得SkyDrive主資料夾的folderId
                case ApiMethod.SkyDriveProp:
                    //取出id,列出根目錄下的項目
                    ListFiles(e.Result["id"].ToString());
                    break;
                //取得目錄清單
                case ApiMethod.SkyDriveDir:
                    //由data取得陣列
                    List<object> items =
                        e.Result["data"] as List<object>;
                    if (items != null)
                    {
                        DirList.Items.Clear();
                        DirList.DisplayMemberPath = "DisplayName";
                        //加入回上層的邏輯
                        if (folderIdStack.Count > 1)
                        {
                            DirItem di = new DirItem(goUpperSymbol, "[..]");
                            DirList.Items.Add(di);
                        }
 
                        foreach (Dictionary<string, object> item in items)
                        {
                            DirItem di  = new DirItem(
                                item["id"].ToString(),
                                item["name"].ToString()
                            );
                            //資料夾時額外取得子項目數
                            if (di.IsFolder)
                                di.Count = int.Parse(item["count"].ToString());
                            else //檔案則取得下載網址
                                di.SrcUrl = item["source"].ToString();
                            //加入清單
                            DirList.Items.Add(di);
                        }
                    }
                    break;
            }
        }
        //用以保存上層目錄的folderId
        private Stack<string> folderIdStack = new Stack<string>();
        private void ListFiles(string folderId)
        {
            if (client == null) return;
            if (folderId == goUpperSymbol)
            {
                folderIdStack.Pop();
                folderId = folderIdStack.Peek();
            }
            else folderIdStack.Push(folderId);
            client.GetAsync(folderId + "/files", 
                            new MyUserState(ApiMethod.SkyDriveDir));
        }
        private void DirList_MouseLeftButtonUp(object sender, 
                                               MouseButtonEventArgs e)
        {
            DirItem di = DirList.SelectedItem as DirItem;
            //資料夾的話,繼續展開
            if (di.IsFolder || di.Id == goUpperSymbol) ListFiles(di.Id);
            //否則試著下載回來檢視
            else
            {
                DirItem.Current = di; //將此DirItem設為Current
                NavigationService.Navigate(
                    new Uri("/Viewer.xaml", UriKind.Relative));
            }
        }
    }
}

為了讓程式更結構化一點,我宣告了一個很簡單的SkyDrive項目物件模型: (即上面MainPage.xaml.cs中所出現DirItem、MyUserState物件的由來)

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Live;
 
namespace MiniSkyDriveExplorer
{
    #region 用以記錄REST API呼叫資訊的狀態物件
    public enum ApiMethod
    {
        SkyDriveProp, // me/skydrive
        SkyDriveDir,  // folderId/files
        SkyDriveGetImage,
        SkyDriveGetMedia
    }
    public class MyUserState
    {
        public ApiMethod Method;
        public string FileName;
        public MyUserState(ApiMethod method)
        {
            Method = method;
        }
    }
    #endregion
 
    #region 簡單的目錄物件
    public class DirItem
    {
        //是否為資料夾
        public bool IsFolder
        {
            get { return Id.StartsWith("folder"); }
        }
        //顯示名稱
        public string Name;
        //foderId或fileId
        public string Id;
        //上層資料夾的folderId
        public string ParentId;
        //若為資料夾時,標示其下項目數
        public int Count;
        //下載來源URL
        public string SrcUrl;
        //顯示名稱,資料夾為[folderName](count),檔案則為fileName
        public string DisplayName
        {
            get
            {
                return IsFolder && !Name.Equals("..") ?
                    string.Format("[{0}]({1})", Name, Count) : Name;
            }
        }
        public DirItem(string id, string name)
        {
            Id = id;
            Name = name;
        }
        //用此靜態屬性當作頁面間的傳遞媒介
        public static DirItem Current = null;
        //供跨頁面共用LiveConnectSession
        public static LiveConnectSession LiveSession = null;
    }
    #endregion
}

除了SkyDrive清單展示,我另外做了一個Viewer.xaml,用來呈現SkyDrive中的圖片(png, jpg)及媒體檔(wmv, mp3, mp4)。
註: Windows Phone模擬器不支援部分媒體格式,測試前可先看一下MSDN文件,其中有些已註明This codec is unsupported in Windows Phone Emulator的格式,測試前請認清,以免在模擬器上猛試不成白花時間! (謎之聲: 這條註解怎麼隱含著強烈的怨念?)

要從SkyDrive下載檔案,可以透過LiveConnectClient.DownloadAsync輕鬆達成,在client_DownloadCompleted事件中可以直接取得byte[],十分方便。媒體檔的部分,我還沒試出可以串流播放的做法,所以先採行的方式是先將下載結果存成IsolatedStorageFile,再叫出MediaPlayerLauncher進行播放。程式範例如下:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using Microsoft.Live;
using System.Windows.Media.Imaging;
using System.IO.IsolatedStorage;
using Microsoft.Phone.Tasks;
 
namespace MiniSkyDriveExplorer
{
    public partial class Viewer : PhoneApplicationPage
    {
        public Viewer()
        {
            InitializeComponent();
        }
 
        private string[] imgExts = "png,jpg".Split(',');
        private string[] mediaExts = "wmv,mp3,mp4".Split(',');
        private LiveConnectClient client = null;
 
        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            if (DirItem.LiveSession == null || DirItem.Current == null)
                return;
            //建立LiveConnectClient
            client = new LiveConnectClient(DirItem.LiveSession);
            client.DownloadCompleted += 
                new EventHandler<LiveDownloadCompletedEventArgs>(
                client_DownloadCompleted);
            //清空內容
            ContentPanel.Children.Clear();
            //取得要檢視的檔案
            DirItem di = DirItem.Current;
            ApplicationTitle.Text = di.Name;
            //由副檔名決定開啟方式
            string ext = System.IO.Path.GetExtension(di.Name).ToLower().TrimStart('.');
            if (imgExts.Contains(ext))
                client.DownloadAsync(di.SrcUrl, new MyUserState(ApiMethod.SkyDriveGetImage));
            else if (mediaExts.Contains(ext))
            //針對媒體檔,比較貼心的方式是透過Streaming方式播放
            //此處只簡單示範整個檔案下載完畢後才撥放
                client.DownloadAsync(di.SrcUrl,
                    new MyUserState(ApiMethod.SkyDriveGetMedia) { FileName = di.Name });
            else
                ContentPanel.Children.Add(new TextBox() { Text = "Unsupported File Type" });
        }
 
        void client_DownloadCompleted(object sender, LiveDownloadCompletedEventArgs e)
        {
            MyUserState state = e.UserState as MyUserState;
            if (state == null) return;
            switch (state.Method)
            {
                case ApiMethod.SkyDriveGetImage:
                    BitmapImage bmp = new BitmapImage();
                    bmp.SetSource(e.Result);
                    Image img = new Image();
                    img.Source = bmp;
                    ContentPanel.Children.Add(img);
                    break;
                case ApiMethod.SkyDriveGetMedia:
                    //此處採用將檔案下載儲存後再播放的做法
                    IsolatedStorageFileStream fs =
                        IsolatedStorageFile.GetUserStoreForApplication()
                        .CreateFile(state.FileName);
                    e.Result.CopyTo(fs);
                    fs.Close();
                    MediaPlayerLauncher mpl = new MediaPlayerLauncher()
                    {
                        Location = MediaLocationType.Data,
                        Media = new Uri(state.FileName, UriKind.Relative)
                    };
                    mpl.Show();
                    break;
            }
        }
    }
}

程式實際執行結果如下,按下SignInButton後,畫面會變成Windows Live的登入畫面,使用者輸入Windows Live帳號並同意授權後:

 

程式便可透過me/skydrive REST API取得使用者SkyDrive根目錄的Id,接著使用folderId/files就可取回該目錄下所有子項目的資訊,以ListBox的方式展示出來。

 

檢視圖片及影片的範例:

 

很簡單吧!? 大家一起為WP7 App加入SkyDrive支援吧!


Comments

# by Eclipse

朋友介紹我來這個專欄查詢,我想請問一下有關於netcut這個程式,網路上流傳很多破解的方式包括arp -a那些有的沒的,我試過卻無法將網路恢復,對於破解此軟體的方式不知道您有什麼辦法呢? 我只是想好好的使用網路..

# by jain

看來暗黑大花了不少時間用模擬器測, 然後測不出結果吧 :p

# by 北京-wp开发者

博主你好,我想问你一个问题,就是在windowsPhone调用Live API的时候,能不能实现自动登录功能?就是不需要在手机中跳转到Live登录界面,直接进行操作。谢谢

# by Jeffrey

to 北京-wp开发者, 在Live SDK的設計理念裡,WP7 App不需也不應該接觸Live帳號及密碼,因此使用者會被導向Live登錄介面,經過同意授權後,之後並不需要每次都重新登入/授權。 事實上,使用者們也已開始習慣這類OAuth的運作流程,就是由App轉介到帳號來源的"官方網站"完成登入及授權再開始使用,要是發現不用敲帳號密碼也沒詢問授權,App就可以讀到SkyDrive裡的私人檔案,恐怕反而會心裡毛毛的。:P

# by 李东升

博主你好。看了你5篇笔记,对我帮助很大,知道skydrive开发的流程,我也在网上找了很多资料,但是都没有这5篇文章讲解的清楚。目前我在Android下进行skydrive开发,功能大致为把手机上的文件上传到skydrive上,由于开发进度比较紧张,而且是昨天刚接触skydrive开发,哪里可以找到Android开发的上传文件的例子,我想参考一下?

# by Jeffrey

to 李东升, github上可以找到Android的SkyDrive範例(https://github.com/liveservices/LiveSDK/tree/master/Samples/Android/SkyDriveAndroidSample),要上傳文件,不同平台間呼叫Live SDK API的方式大同小異,以範例為基礎加以修改應不難實現。

# by dsl services provider

I liked this post very much as it has helped me a lot in my research and is quite interesting as well.Great Inspiring blog. Thank you for sharing this information with us.

Post a comment