Live Connect ile SkyDrive'a programatik file upload

0 dakikada yazıldı

37405 defa okundu

Düzenle

Build konferansında SkyDrive'a ait yeni API'lerin açılacağı ve Beta
olarak sunulduğu duyurusunun yapıldığı anda hemen aklımda kenara not
almıştım, "Ben bu işe bir bakiyim" diye :) SkyDrive malum kullanıcı
başına 25GB alan veriyor ve bu çoğu senaryoda özellikle free cloud
storage anlamına gelip güzel bir back-up lokasyonu olabiliyor. Özellikle
geliştirdiğiniz son kullanıcı ürünlerinde backup işlevselliği için çok
uygun bir seçenek.

Örnek senaryo

Şimdi varsayalım ki elimizde bir CMS (Content Management System) var.
Bunu müşterilerimize satıyoruz. CMS'in kendi iç backup sistemi,
scheduler'ı vs olacaktır. Fakat genelde bu backuplar hep CMS'in
bulunduğu sunucuda tutulur ki bu aşırı mantıklı sayılmaz :) Yani
sunucunun başına birşey gelse backuplar da gider. Backup'ları alıp
scheduled bir şekilde başka bir sunucuya göndermek akıllıca olur ama bu
sefer de sunucu masrafları artar, entegrasyon sorunlu olabilir vs vs...

Tüm bu sorunları çözmek adına CMS yönetim panelinde bir bölüm tanımlayıp
backup için kullanıcıların LiveID'leri ile SkyDrive hesaplarını CMS'e
bağlamalarını isteyebilirsiniz. Böylece CMS artık istediği zaman söz
konusu SkyDrive hesabına bağlanığ backupları kopyalar ve herhangi bir
sorun olduğunda cloud'da backupımız olduğunu biliriz.

Peki nasıl yapacağız tüm bunları?

SkyDrive'ın yeni SDK'sı ile beraber Windows 8 Metro ve WP7 için bazı
yardımcı kütüphaneler geliyor fakat ben bu kütüphaneleri pek beğenmedim
şu anda. O nedenle direk REST API'leri sağlayan SkyDrive'ın API'lerin
doğrudan ulaşacağız. Ben örnek boyunca bir WPF uygulaması kullanacağım
fakat bunu platform spesifik pek birşey kullanmayacağım için ister
Metro, ister WP7 tarafına da geçirebilirsiniz.

Live Connect API için Application Tanımı

SkyDrive ile konuşmak için Live Connect API'lerini kullanacağız.
Hazırlayacağımız uygulamanın SkyDrive ve LiveConnect sisteminde tanımlı
olması lazım ki API'lere ulaşıp, kullanabilelim. Bunun için hemen
http://go.microsoft.com/fwlink/?LinkId=193157 adresini ziyaret
edebilirsiniz.

Uygulamamızı Live Connect için tanımlarken...
Uygulamamızı Live Connect için tanımlarken...

Siteye gittiğinizde ilk yapmanız gereken uygulamanızı bir isim verip
dilini seçmek. Dil kısmı çok kritik değil nitekim uygulama adı da çok
kritik değil ama özellikle uygulama adı daha sonraları kullanıcılarınız
kendi LiveID'leri ile uygulamanızı bağlarken gösterilecek isim olacak.
Bu adımın hemen sonrasında size ClientID ve Secret Key verilecektir.

ClientID'miz ve Secret Key'imiz karşımızda.
ClientID'miz ve Secret Key'imiz karşımızda.

Client ID ve Secret Key çok önemli. Uygulamanız REST API'lerini
kullanırken bunlara ihtiyaç duyacak. Bir anlamda bu bilgiler
uygulamamızın içine gömülü olacak.

Mobile App seçeneği çok kritik.
Mobile App seçeneği çok kritik.

Web sitesinden çıkmadan son olarak yapmamız gereken bir ayar daha var.
HemenAPI Settings bölümüne geçip "Mobile client app" seçeneğini
"Yes" olarak değiştirmeniz gerek. Web uygulamalarında bir redirect
domain sayfası kullanılarak Login sonrası bu adrese yönlendirme
yapılması sağlanabiliyor ama bizimki bir web uygulaması olmadığı için
biz böyle özel bir yönlendirme istemiyoruz.

Login ile SkyDrive erişim haklarını almak

LiveID'si olan bir kullanıcının SkyDrive erişim haklarını almak için
kesinlikle tarayıcı bazlı bir platform yaratmamız şart. Eğer bir ASP.NET
sitesi ise doğrudan SkyDrive login sayfasına yönlendirip bir önceki
adımda belirtlemediğimiz "Redirect Domain" bilgisini de sitenizin
adresini yazıp konuyu çözebilirsiniz. Eğer bir WPF/Windows uygulaması
yazıyorsanız WebBrowser componenti ile kullanıcıya SkyDrive Login
sayfasını göstermemiz gerek. Bu işlemi kullanıcının sadece bir defa
yapması yeterli olacak. Sonrasında biz artık istediğimiz zaman söz
konusu kullanıcının SkyDrive'ına ulaşabileceğiz.

[VB]

    Private ClientID As String = "000XXXXXXXXXXXXXXX03C"
    Private ClientSecret As String = "9dAkN6XXXXXXXXXXXXXXXXXXXXXeCnRI"

İlk olarak sürekli kullanacağımız ClientID ve ClientSecret'ı
birer değişken olarak tutalım. Aslında bunları birer uygulama ayarı
olarak da saklayabilirsiniz. Karar sizin. (Aslında değişken olmalarına
da gerek yok :))

[VB]

  Browser.Navigate(String.Format("https://oauth.live.com/authorize?" &
                               "client_id={0}&scope=wl.offline_access" &
                               "%20wl.skydrive%20wl.skydrive_update&" &
                               "response_type=code&redirect_uri=" &
                               "https://oauth.live.com/desktop", ClientID))

Uygulamamızın ekranında bir WebBrowser componentini Browser adı ile
bulundurduğumuzu düşünürsek :) yukarıdaki kod ile kullanıcıyı SkyDrive
Login sayfasına yönlendirebiliyoruz. Yönlendirme URL'imiz içerisinde
bazı parametreler dikkatinizi çekecektir. Bunlardan ilki tabi ki klasik
ClientID'miz :) İkinci parametre ise scope bilgisi. Scope bilgisi
ile kullanıcıdan ne tür haklar istediğimizi belirtebiliyoruz. Burada
özellikle wl.offline_access çok
önemli. Offline erişim istemezseniz her seferinde kullanıcıya login
yaptırmak zorunda kalırsınız. Eğer her seferinde kullanıcı login olmak
zorunda olmasın ve biz de kafamıza göre tekrar sonrasında kullanıcının
SkyDrive'ına ulaşabilelim istiyorsak kesinlikle offline access hakkı
istememiz gerek. Örneğimizde istediğimiz diğer haklar wl.skydrive ve
wl.skydrive_update ise kullanıcının skydrive'ını okuma ve yazma
hakları ile ilgili. Live Connect API'leri aslında kullanıcının
takviminden tutun, kontaklarına kadar farklı şeylere ulaşmak için
kullanılabiliyor. Biz sadece SkyDrive'a ulaşacağımız için bu hakları
istiyoruz. Scope'lar konusunda diğer hakların bir
listesineburadan
ulaşabilirsiniz.

Kullanıcı SkyDrive'a login oluyor bize izin vermek için.
Kullanıcı SkyDrive'a login oluyor bize izin vermek için.

Kullanıcı bizim yönlendirdiğimiz URL ile login olduktan sonra karşısına
aşağıdaki gibi bir ekranda istediğimiz hakların listesi ve hangi
uygulamanın istediği bilgisi gösteriliyor. Kullanıcı bu haklara onay
verdiği gibi artık top bizde olacak.

Kullanıcıya vereceği haklar soruluyoru.
Kullanıcıya vereceği haklar soruluyoru.

Haklar da gördüğünüz üzere birincisi "Access your info anytime"
hakkı var :) Yani istediğimiz zaman bu skydrive'a ulaşabilmek istiyoruz.
İkincisi ve üçüncüsü ise direk SkyDrive ile ilgili. SkyDrive'daki
fotoğraflardan tutun tüm dosyalara kadar ulaşabilir ve üçüncü hakka göre
de düzenleme yapabiliriz.

Kullanıcı bu ekranda "Yes" dedikten sonra artık SkyDrive sitesinin bize
haber vermesi gerek. Biz herhangi bir redirection tanımlamadık. O
nedenle doğrudan https://oauth.live.com/desktop?code=XXXX diye bir
sayfaya yönlendirme yapılacak. Bu sayfaya yapılan yönlendirmenincode
parametresi ile gidip REST API'lerden erişim anahtarı (access token)
isteyeceğiz.

Aşağıdaki minik kod ile WebBrowser componentinin Navigated eventini
dinliyor ve eğer bizim istediğimiz sayfaya bir yönlendirme varsa
QueryString üzerinden Code bilgisini alıp "access token" isteme
işlemini başlatıyoruz.

[VB]

    Private Sub Browser_Navigated(sender As Object, e As NavigationEventArgs)
                                                                 Handles Browser.Navigated
        If e.Uri.ToString.StartsWith("https://oauth.live.com/desktop?code"Then
            Dim Code = System.Web.HttpUtility.ParseQueryString(e.Uri.Query).Get("code")
            Web.DownloadStringAsync(New Uri(
                 String.Format("https://oauth.live.com/token?" &
                               "client_id={0}&redirect_uri=" &
                               "https://oauth.live.com/desktop&code={1}&" &
                               "grant_type=authorization_code",
                               ClientID, Code)), "gettoken")
        End If
    End Sub

Access Token istemek için oauth.live.com'a bir GET atmamız gerekiyor :)
Yukarıda Web adında gördüğünüz değişken basir bir WebClient
nesnesi. Bu GET ile beraber de ClientID'mizi, redirecturi olarak
desktop uygulamalarının default kullanması gereken desktop URL'ini ve
code kısmına da biraz önce aldığımız Code bilgisini veriyoruz.
Grant_Type olarak bir authorization_code istediğimizi de belirtip
talebi başlatıyoruz. Ben ek olarak Context olsun diye "gettoken"
contexti yolladım ama itiraf etmek gerekirse gereksiz oldu :) Aynı
WebClient'ı kullanarak birkaç talep göndermek gerekir diye düşündüm ama
gerekmedi :) Neyse, belki size lazım olursa Context üzerinden ayrıştırma
şansınız olur.

[VB]

    Private Sub Web_DownloadStringCompleted(sender As Object
                e As DownloadStringCompletedEventArgsHandles Web.DownloadStringCompleted
        If e.UserState = "gettoken" Or e.UserState = "refreshtoken" Then
            Dim jss = New System.Web.Script.Serialization.JavaScriptSerializer
            Dim found = jss.Deserialize(Of Dictionary(Of StringString))(e.Result)
            refresh_token = found("refresh_token")
            access_token = found("access_token")
            upload_location = (jss.Deserialize(Of Dictionary(Of StringObject)) _
                               (Web.DownloadString(String.Format( _
                  "{0}/me/skydrive?access_token={1}", APIBase, access_token)))) _
                    ("upload_location")
            WpfApplication1.MySettings.Default.access_token = access_token
            WpfApplication1.MySettings.Default.upload_location = upload_location
            WpfApplication1.MySettings.Default.refresh_token = refresh_token
            WpfApplication1.MySettings.Default.Save()

            btn.IsEnabled = True
        End If
    End Sub

Yukarıdaki kodu okurken bir UserState kontrolü yaptığımı görüyorsunuz :)
Aslında gerek yok. Refreshtoken olayına biraz gireceğiz ama State
kontrolü yapmasam da olurmuş. Bir önceki adımda yaptığımız GET ile bize
bir JSON dönüyor. Bu JSON içerisinde access_token (erişim anahtarı)
ve refresh_token (yenileme anahtarı) geliyor. Access Token bizim
herhangi bir yere erişebilmek için ihtiyacımız olan anahtarın ta kendisi
fakat Access Token 3600 saniyede expire ediyor. Eğer yeni bir Access
Token
gerekirse Refresh Token ile tekrar bir talep gönderip
yenisini alabiliyoruz. Unutmayın Refresh Token sadece Offline Erişim
istediğinizde sağlanır. (Ki biz de bunu yapmıştık. JSON'un deserialize
ettikten sonra tokenları ben hem değişkenlere hem de sonrasında WPF
uygulamamda ayrıca ayarladığım kullanıcı ayarlarına atıyorum. Buradaki
amaç aslında şu; WPF'in kullanıcı ayarlarına atıp veya belki de
veritabanına atıp Access_Token ile Refresh_Token'ı saklarsanız
uygulamanız kapanlıp açılsa aradan yıllar geçse bile :) tekrar bu
bilgiler ile kullanıcının SkyDrive'ına istediğiniz zaman
ulaşabilirsiniz.

Bir Upload yapalım!

Benim tüm bu işlere girmemin nedeni upload yapmaktı :) O nedenle sizinle
upload örneğini paylaşacağım. Fakat upload dışında bu uygulamamız
SkyDrive'da artık her tür hareketi yapabilir, klasör yaratmak, silmek vs
herşey dahil. Detaylar içinLive
SDK
'ye buradan
ulaşabilirsiniz. Bu arada unutmadan şu an için kullandığımız bu API'ler
Beta! Ona göre hareket etmekte fayda var :)

Tam da Beta konusuna değinmişken biraz önce incelediğimiz kodun içindeki
garip "upload_location" konusuna değinelim. Aslında gelin o satırı
tekrar bir sahneye alalım :) çünkü pek insan oğlunun okuyabileceği
şekilde yazmamışım!

[VB]

            upload_location = (jss.Deserialize(Of Dictionary(Of StringObject)) _
                               (Web.DownloadString(String.Format( _
                  "{0}/me/skydrive?access_token={1}", APIBase, access_token)))) _
                    ("upload_location")

Şimdi birincisi belli ki benim bir yerlerde tanımlanmış upload_location
diye bir değişkenim var ve bunu bir şekilde WebClient üzerinden
DownloadString ile bir GET yollayıp aldığım sonuca eşitliyorum. Hatta
arada JSON Deserialize bile var tek satırda oy oy oy :) Demiştim insan
okuyamaz diye :D Neyse konunun özeti şu; access_token ve
refresh_token'ı aldıktan sonra ben hemen bir GET daha yollayıp
SkyDrive'daki Root klasörün upload path'ini alıyorum. Bunun için bu
oauth.live.com'a değil de APIBase diye değişkendeki adrese talep
gönderdiğimi umarım farkına varabilmişsinizdir :) String.Format'taki ilk
parametre :)

[VB]

    Dim APIBase As String = "https://beta.apis.live.net/v5.0" 'https://apis.live.net/v5.0

APIBase'i yukarıdaki şekilde "commenti dahil" tanımlamanızda fayda var
:) Bunun nedeni daha önce de bahsettiğim gibi Beta API'leri kullanacak
olmamız. Şu ana kadar yaptığımız herşey Live API'lerinin canlıda olan
API'leriydi. Şu an itibari ile Beta API'leri çağıracağız. Bu API'ler
yayına geçtiğinde tek yapmanız gereken commentli kısımdaki adresi
kullanmak, o kadar!

VB]

            upload_location = (jss.Deserialize(Of Dictionary(Of StringObject)) _
                               (Web.DownloadString(String.Format( _
                  "{0}/me/skydrive?access_token={1}", APIBase, access_token)))) _
                    ("upload_location")

Bizim şu yukarıdaki GET'e geri dönersek :) /me/skydrive diyerek
skydrive'ın root klasörü ile ilgili bilgi istemiş oluyoruz. Tabi bu
arada access_token'ı da yolluyoruz. Gelen bilginin içerisindeki
upload_location bu klasöre upload yapmak istediğimiz PUT talebimizi
göndereceğimiz yer. 

[VB]

    Sub Upload(FilePath As String)
        Dim filename As String = System.IO.Path.GetFileName(FilePath)
        MessageBox.Show("Başlıyor!!!")
        Web.UploadData(String.Format("{2}/{0}?access_token={1}",
                                     filename, access_token,
                                     upload_location), "PUT",
                                 ReadFile(FilePath))
        MessageBox.Show("Bitti")
    End Sub

Şimdi artık Upload işlemine geçebiliriz. Elimide Upload Location var,
access_token var. Yukarıdaki metodu sistemdeki bir dosyanın Path'ini
verdiğim gibi upload_location'ının sonuna dosya adını ve parametre
olarak da access_token'ı ekleyip PUT işlemini başlatıyor.
MessageBox'ları siz silin bence :) Debug.WriteLine'dan daha çok
seviyorum ben onları çıkardıkları ses nedeniyle :P

Son olarak dosyayı byte arraye çeviren ReadFile metodumu da paylaşiyim.

[VB]

    Public Shared Function ReadFile(filePath As StringAs Byte()
        Dim buffer As Byte()
        Dim fileStream As New FileStream(filePath, FileMode.Open, FileAccess.Read)
        Try
            Dim length As Integer = CInt(fileStream.Length)
            buffer = New Byte(length - 1) {}
            Dim count As Integer
            Dim sum As Integer = 0
            While (count = fileStream.Read(buffer, sum, length - sum)) > 0
                sum += count
            End While
        Finally
            fileStream.Close()
        End Try
        Return buffer
    End Function

Peki ya RefreshToken sonradan nasıl kullanılıyor?

Uygulamanızın bir önceki açılışında bir access_token ve refresh_token
aldığını (kullanıcının logini sonrasında) ve tekrar açıldığında da
bunlara devam etmesi gerektiğini düşünürsek aslında tek yapmamız gereken
hemen eldeki accees_token ile refresh_token'ı Live API'lere gönderip
yenisini istemek.

[VB]

  Web.DownloadStringAsync(New Uri(
            String.Format("https://oauth.live.com/token?client\_id={0}" &
                          "&redirect_uri=https://oauth.live.com/desktop" &
                          "&client_secret={1}&refresh_token={2}&" &
                          "grant_type=refresh_token", ClientID,
                          ClientSecret, refresh_token)),
        "refreshtoken")

Yukarıdaki GET talebimiz eldeki ClientID, ClientSecret ve
RefresthToken'ı göndererek yeni bir access_token ve refresh_token
istiyor. Özellikle grant_type'daki değişikliğe dikkat.

[VB]

    Private Sub Web_DownloadStringCompleted(sender As Object
                e As DownloadStringCompletedEventArgsHandles Web.DownloadStringCompleted
        If e.UserState = "gettoken" Or e.UserState = "refreshtoken" Then
            Dim jss = New System.Web.Script.Serialization.JavaScriptSerializer
            Dim found = jss.Deserialize(Of Dictionary(Of StringString))(e.Result)
            refresh_token = found("refresh_token")
            access_token = found("access_token")
            upload_location = (jss.Deserialize(Of Dictionary(Of StringObject)) _
                               (Web.DownloadString(String.Format( _
                  "{0}/me/skydrive?access_token={1}", APIBase, access_token)))) _
                    ("upload_location")

            WpfApplication1.MySettings.Default.access_token = access_token
            WpfApplication1.MySettings.Default.upload_location = upload_location
            WpfApplication1.MySettings.Default.refresh_token = refresh_token
            WpfApplication1.MySettings.Default.Save()

            btn.IsEnabled = True
        End If
    End Sub

RefreshToken ile gelen bir talebin sonucu ilk token talebinin sonucu ile
aynı. Yani geri gelen JSON aynı :) O nedenle ben direk aynı Completed
eventini bıraktım gitti :) İşte tam da bu yüzden UserState gereksiz oldu
:)

İşlem Tamam

Dokümantasyonu şu an için muhteşem güzel olmasa da API'ler epey
stabiller Beta olmalarına rağmen. O nedenle rahatlıkla development
yapılabilir. SkyDrive'ın özellikle önümüzdeki dönemde WP7.5 ve Windows 8
ile beraber epey entegrasyonla daha da değer kazanacağını düşünürsek
entegrasyon manzaralarını düşünmek anlamlı olabilir. Unutmadan :) Upload
işleminde dosya formatlarında sınırlar var. Örneğin ZIP
gönderemiyorsunuz ama ZIP'i JPG yapınca gönderiyor :) Benim için çok
dert olmadı açıkçası. Diğer yandan göndereceğiniz dosyalar büyükse
WebClient'ın UploadDataAsync asenkron metodunu da kullanabilirsiniz.

Umarım işinize yarar! Hepinize kolay gelsin ;)

Bu makale Live Connect SDK SkyDrive Developer Preview / BetaREST
API'leri kullanılarak hazırlanmıştır.