[Scala] google-api-java-client 存取 Google Calendar 範例。

話說為了下一版的 Maidroid Reminder 要加入的與 Google Calendar 同步的功能,這一陣子在研究Google Data API。

原本的 gdata-java-client 雖然比較完整方便,但我卻是一直無法讓他正常地在 Scala/Android/Proguard 的組合下運作,要我回去用 Java 寫 Android 程式我也不想幹,於是只好試著看看有沒有啥替代方案。

目前看到的是兩個,一個是用 CalDev,也就是 iCal 的格式,函式庫方面可以用 ical4j,也在去年實驗過可以在 Scala/Android/Proguard 的方案上使用,但相較之下,由於 Scala 本身處理 XML 的能力很強,所以還是希望可以找以 Atom 為基礎的解決方案。

於是乎,就試著用比較新,但仍然在實驗中的 google-api-java-client,但很不幸的,文件少到可憐,而雖然有完整的範例程式,但程式碼卻有點大,不太好入門。

對我而言,我最主要的只要能取得行事曆的 XML 格式的 Feed 就好,Parser 的部份我可以直接用 Scala 來幹掉, 所以我試著追了一下範例程式碼,並且將其簡化到只剩最最最基本的使用者認證和取得 Atom Feed 的部份,成果如下,而這段程式也確定可以在 Scala/Android/Proguard 的組合下運作。

簡單來說,要做的就是以下幾個步驟:

  1. 建立 Transport
  2. 用帳號密碼登入
  3. 設定網址及建立 Request(必須在登入後,否則會出現 Forbidden Exception)
  4. 執行 Request 並取得 XML Feed

廢話不多說,以下就是程式碼:

import com.google.api.client.googleapis.GoogleTransport
import com.google.api.client.googleapis.auth.clientlogin.ClientLogin
import com.google.api.client.googleapis.GoogleHeaders
import com.google.api.client.http.HttpRequest
import com.google.api.client.http.HttpTransport
import com.google.api.client.http.HttpResponse

import scala.xml.XML
import scala.xml.PrettyPrinter

/**
 * 取得一個 Feed 的基本步驟
 *
 *    1. 建立 Transport
 *    2. 用帳號密碼登入
 *    3. 設定網址及建立 Request(必須在登入後,否則會出現 Forbidden Exception)
 *    4. 執行 Request 並取得 XML Feed
 *
 */
object GoogleCalendarSample
{
    def main (args:Array[String]) {
        val username = "username@gmail.com" // 請改成你的帳號
        val password = "XXXXX"              // 請改成你的密碼

        val calendarURL = "http://www.google.com/calendar/feeds/default/owncalendars/full"
        val transport = createTransport("My-Calendar-1")

        login (transport, username, password)

        val request = buildGetRequest (transport, calendarURL)
        val response = RedirectHandler.execute(request)
        val xml = XML.load(response.getContent)

        val xmlPrinter = new PrettyPrinter (100, 4)
        println (xmlPrinter.format(xml))
    }

    /**
     *  建立 Transport 以便將來產生 Http Request
     *
     *  @param applicationName  你的應用程式名稱
     *  @param gdataVersion     要使用的 GData Protocal 版本
     */
    def createTransport (applicationName: String, gdataVersion:String = "2"): HttpTransport = {
        val transport = GoogleTransport.create()
        val headers = transport.defaultHeaders.asInstanceOf[GoogleHeaders]

        headers.setApplicationName (applicationName)
        headers.gdataVersion = gdataVersion

        transport
    }

    /**
     *  進行帳密認證
     *
     *  @param transport    要進行任證的 Transport
     *  @param username     帳號(GMail 信箱)
     *  @param password     密碼
     *  @param gDataType    要使用的 GData 服務代稱,Google Calendar 是 "cl",其他不確定
     */
    def login (transport: HttpTransport, username: String, password: String, gDataType: String = "cl") = {
        val login = new ClientLogin
        login.authTokenType = gDataType
        login.username = username
        login.password = password
        login.authenticate().setAuthorizationHeader (transport)
    }

    /**
     * 建立 Http Request 並設定 URL
     *
     * @param transport     要使用的 Transport
     * @param url           要連線的網址
     */
    def buildGetRequest (transport: HttpTransport, url: String): HttpRequest = {
        val request = transport.buildGetRequest()

        request.setUrl (url)
        request
    }

}

//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////

import com.google.api.client.googleapis.GoogleUrl
import com.google.api.client.http.HttpExecuteIntercepter
import com.google.api.client.http.HttpResponseException

// 下面的這兩個類別是我從 http://0rz.tw/eSQSh 抄來後改寫的,別問我這兩個類別在做什麼,
// 因為我自己也搞不懂。
class SessionIntercepter(transport: HttpTransport, url: GoogleUrl) extends HttpExecuteIntercepter {
    var gsessionid: String = url.getFirst("gsessionid").asInstanceOf[String]

    transport.removeIntercepters(classOf[SessionIntercepter])
    transport.intercepters.add (0, this)

    def intercept(request: HttpRequest) {
        request.url.set("gsessionid", this.gsessionid);
    }
}

object RedirectHandler {

    def execute (request: HttpRequest): HttpResponse = try {
        request.execute()
    } catch {
        case e: HttpResponseException if e.response.statusCode == 302 =>
            val url = new GoogleUrl (e.response.headers.location)
            request.url = url
            new SessionIntercepter (request.transport, url)
            e.response.ignore
            request.execute()
        case e =>
            throw e
    }
}
建立 Transport
 *    2. 用帳號密碼登入
 *    3. 設定網址及建立 Request(必須在登入後,否則會出現 Forbidden Exception)
 *    4. 執行 Request 並取得 XML Feed

回響