我们知道golang越来越被很多的开发者来开发应用。go语言也可以用于开发Ubuntu Scope。在今天的教程中,我们将详细介绍如何使用go语言来开发我们的Scope。这对于很多的不太熟悉C/C++的开发者来说,无疑是一个福音。对我来说,这个语言也是比较新的。如果大家想学习golang的话,建议大家阅读“Go by Example”。
对于更多的关于Go Scope的开发,可以参阅文章“Go scopes development”。
IDE选择
由于一些原因,目前我们的Ubuntu SDK并没有支持go语言的Scope的开发。可喜的是,我们可以使用Command Line来完成我们的开发。俗话说,好镰不误砍柴功。一个好的工具无疑能帮我们更快更好地开发。我目前使用的编辑器是LiteIDE。这个是转为go语言而设计的IDE,非常简洁而使用,建议大家试用。
安装开发环境
我们首先必须安装
golang包及相关包:
$sudo apt-get install unity-scope-tool golang git bzr python3-scope-harness mercurial
为了说明问题的方便,大家可以先下载我的模版Scope:
$git clone https://gitcafe.com/ubuntu/goscope.git
在自己的Terminal下键入上述的命令,你就可以在你所在的目录下发现一个叫做“goscope”的目录:
liuxg@liuxg:~/release/goscope$ ls -l total 68 -rwxrwxr-x 1 liuxg liuxg 2886 7月 2 12:37 build-click-package.sh -rwxrwxr-x 1 liuxg liuxg 752 7月 2 12:37 build.sh -rwxrwxr-x 1 liuxg liuxg 1144 7月 2 12:37 clean.sh -rw-rw-r-- 1 liuxg liuxg 9835 7月 2 12:37 goscope.go -rw-rw-r-- 1 liuxg liuxg 329 7月 2 12:37 goscope.ini -rw-rw-r-- 1 liuxg liuxg 102 7月 2 12:37 goscope-security.json -rw-rw-r-- 1 liuxg liuxg 8403 7月 2 12:37 icon.jpg -rw-rw-r-- 1 liuxg liuxg 4851 7月 2 12:37 logo.jpg -rw-rw-r-- 1 liuxg liuxg 435 7月 2 12:37 manifest.json -rwxrwxr-x 1 liuxg liuxg 1293 7月 2 12:37 run.sh -rwxrwxr-x 1 liuxg liuxg 1666 7月 2 12:37 setup-chroot-go.sh drwxrwxr-x 3 liuxg liuxg 4096 7月 2 12:37 src
从上面的显示中,我们可以看到除了我们期望的goscope.go文件外,我们多了几个脚本来帮我们完成我们的工作。我们可以按照如下的命令把下载后的script变为可以执行的脚本
$chmod +x *.sh
首先,我们来看一下我们的build.sh。
build.sh
#!/bin/bash # # usage: ./build.sh # it builds project and produces the armhf click package # ./build.sh -d # it builds the project and deploy it to the phone # A developer needs to change the armhf names in the following script according tuo your project # export GOPATH=`pwd` go get launchpad.net/go-unityscopes/v2 ./setup-chroot-go.sh ubuntu-sdk-15.04 vivid ./build-click-package.sh goscope liu-xiao-guo ubuntu-sdk-15.04 vivid if [ $# -eq 1 ] then if [ $1 = "-d" ] then echo "Start to deploy to the phone ..." adb push ./goscope.liu-xiao-guo/goscope.liu-xiao-guo_1.0.0_armhf.click /tmp adb shell "sudo -iu phablet pkcon --allow-untrusted install-local /tmp/goscope.liu-xiao-guo_1.0.0_armhf.click" exit 0 fi fi
go get launchpad.net/go-unityscopes/v2
这个语句的作用是从上面的地址处下载最新的“ go-unityscope”版本2。当你们下载完我的模版后,其实里面已经有一个叫做“ src”的目录。里面已经包含了所有的所需要的文件。
setup-chroot-go.sh
#!/bin/bash # Function that executes a given command and compares its return command with a given one. # In case the expected and the actual return codes are different it exits # the script. # Parameters: # $1: Command to be executed (string) # $2: Expected return code (number),Can be not defined. function executeCommand() { # gets the command CMD=$1 # sets the return code expected # if it's not definedset it to 0 OK_CODE=$2 if [ -n $2 ] then OK_CODE=0 fi # executes the command ${CMD} # checks if the command was executed successfully RET_CODE=$? if [ $RET_CODE -ne $OK_CODE ] then echo "ERROR executing command: \"$CMD\"" echo "Exiting..." exit 1 fi } # ****************************************************************************** # * MAIN * # ****************************************************************************** if [ $# -ne 2 ] then echo "usage: $0 FRAMEWORK_CHROOT SERIES_CHROOT" exit 1 fi CHROOT=$1 SERIES=$2 sudo click chroot -aarmhf -f$CHROOT -s $SERIES create sudo click chroot -aarmhf -f$CHROOT -s $SERIES maint apt-get install golang-go golang-go-linux-arm golang-go-dbus-dev golang-go-xdg-dev golang-gocheck-dev golang-gosqlite-dev golang-uuid-dev libgcrypt20-dev:armhf libglib2.0-dev:armhf libwhoopsie-dev:armhf libdbus-1-dev:armhf libnih-dbus-dev:armhf libsqlite3-dev:armhf crossbuild-essential-armhf echo "Executing go get launchpad.net/go-unityscopes/v2 ...." GOPATH=`pwd` go get launchpad.net/go-unityscopes/v2 echo "Done."
build-click-package.sh
#/bin/bash # Function that executes a given command and compares its return command with a given one. # In case the expected and the actual return codes are different it exits # the script. # Parameters: # $1: Command to be executed (string) # $2: Expected return code (number),may be undefined. function executeCommand() { # gets the command CMD=$1 # sets the return code expected # if it's not definedset it to 0 OK_CODE=$2 if [ -n $2 ] then OK_CODE=0 fi # executes the command eval ${CMD} # checks if the command was executed successfully RET_CODE=$? if [ $RET_CODE -ne $OK_CODE ] then echo "" echo "ERROR executing command: \"$CMD\"" echo "Exiting..." exit 1 fi } # ****************************************************************************** # * MAIN * # ****************************************************************************** if [ $# -ne 4 ] then echo "usage: $0 SCOPE_NAME DEVELOPER_NAME FRAMEWORK_CHROOT SERIES_CHROOT" exit 1 fi SCOPE_NAME=$1 DEVELOPER_NAME=$2 CHROOT=$3 SERIES=$4 CURRENT_DIR=`pwd` FILE_NAME="${SCOPE_NAME}.${DEVELOPER_NAME}" MANIFEST_NAME="${SCOPE_NAME}.${DEVELOPER_NAME}" echo -n "Removing ${FILE_NAME} directory... " executeCommand "rm -rf ./${FILE_NAME}" echo "Done" echo -n "Creating clean ${FILE_NAME} directory... " executeCommand "mkdir -p ${FILE_NAME}/${FILE_NAME}" echo "Done" echo -n "Copying scope ini file... " executeCommand "cp $SCOPE_NAME.ini ${FILE_NAME}/${FILE_NAME}/${FILE_NAME}_${SCOPE_NAME}.ini" echo "Done" echo -n "Copying the logo file ... " executeCommand "cp logo.jpg ${FILE_NAME}/${FILE_NAME}/logo.jpg" echo "Done" echo -n "Copying the icon file ... " executeCommand "cp icon.jpg ${FILE_NAME}/${FILE_NAME}/icon.jpg" echo "Done" echo -n "Setting scope name in ini file..." executeCommand 'sed -i "s/%SCOPE_NAME%/${FILE_NAME}/g" ${FILE_NAME}/${FILE_NAME}/${FILE_NAME}_${SCOPE_NAME}.ini' echo "Done" echo -n "Copying scope json files... " executeCommand "cp *.json ${FILE_NAME}/" echo "Done" echo -n "Setting scope name in manifest file..." executeCommand 'sed -i "s/%SCOPE_NAME%/${MANIFEST_NAME}/g" ${FILE_NAME}/manifest.json' echo "Done" echo -n "Cross compiling ${FILE_NAME}..." executeCommand "click chroot -aarmhf -f$CHROOT -s $SERIES run CGO_ENABLED=1 GOARCH=arm GOARM=7 PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig GOPATH=/usr/share/gocode/:$GOPATH CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ go build -ldflags '-extld=arm-linux-gnueabihf-g++' -o ${FILE_NAME}/${FILE_NAME}/${FILE_NAME}" echo "Done" executeCommand "cd ./${FILE_NAME}" echo -n "Building click package ... " executeCommand "click build ./" echo "Done" executeCommand "cd .."
$./build-click-package.sh goscope liu-xiao-guo ubuntu-sdk-15.04 vivid
这里,我们把项目的Scope名,开发者的名字,framework 及Ubuntud的系列号传入即可。
run.sh
#!/bin/bash # # This is the script to build and run the scope on desktop environment. # A developer needs to change the following variable according to your projectt # SCOPE_NAME=goscope # DEVELOPER_NAME=liu-xiao-guo # # function executeCommand() { # gets the command CMD=$1 # sets the return code expected # if it's not definedset it to 0 OK_CODE=$2 if [ -n $2 ] then OK_CODE=0 fi # executes the command eval ${CMD} # checks if the command was executed successfully RET_CODE=$? if [ $RET_CODE -ne $OK_CODE ] then echo "" echo "ERROR executing command: \"$CMD\"" echo "Exiting..." exit 1 fi } export GOPATH=`pwd` SCOPE_NAME=goscope DEVELOPER_NAME=liu-xiao-guo FILE_NAME="${SCOPE_NAME}.${DEVELOPER_NAME}" echo -n "Removing ${FILE_NAME} directory... " executeCommand "rm -rf ./${FILE_NAME}" echo "Done" echo -n "Copying scope ini file... " executeCommand "cp $SCOPE_NAME.ini ${FILE_NAME}_${SCOPE_NAME}.ini" echo "Done" echo -n "Setting scope name in ini file..." executeCommand 'sed -i "s/%SCOPE_NAME%/${FILE_NAME}/g" ${FILE_NAME}_${SCOPE_NAME}.ini' echo "Done" echo -n "Building the scope" executeCommand "go build -o ${FILE_NAME}" unity-scope-tool ${FILE_NAME}_${SCOPE_NAME}.ini
这个脚本是为了把我们的Scope在Desktop的环境中进行编译,并使用unity-scope-tool来启动我们的Scope:
clean.sh
#!/bin/bash # # This file cleans all of the intermediate files produced during the compilation. For each project # a developer needs to customize the variables # SCOPE_NAME # DEVELOPER_NAME # # function executeCommand() { # gets the command CMD=$1 # sets the return code expected # if it's not definedset it to 0 OK_CODE=$2 if [ -n $2 ] then OK_CODE=0 fi # executes the command eval ${CMD} # checks if the command was executed successfully RET_CODE=$? if [ $RET_CODE -ne $OK_CODE ] then echo "" echo "ERROR executing command: \"$CMD\"" echo "Exiting..." exit 1 fi } SCOPE_NAME=goscope DEVELOPER_NAME=liu-xiao-guo FILE_NAME="${SCOPE_NAME}.${DEVELOPER_NAME}" echo -n "Removing ${FILE_NAME} directory... " executeCommand "rm -rf ./${FILE_NAME}" echo "Done" echo -n "Removing ${FILE_NAME}_${SCOPE_NAME}.ini ..." executeCommand "rm -f ${FILE_NAME}_${SCOPE_NAME}.ini" echo "Done" echo -n "Removing ${FILE_NAME} ..." executeCommand "rm -f ${FILE_NAME}" echo "Done" echo -n "Removing pkg directory ..." executeCommand "rm -rf pkg" echo "Done"
这个脚本是为了我们能够删除我们在编译过程中所产生任何的中间文件。
goscope.go
package main import ( "launchpad.net/go-unityscopes/v2" "log" "encoding/json" "net/url" "net/http" ) const searchCategoryYellow = `{ "schema-version" : 1,"template" : { "category-layout" : "vertical-journal","card-layout": "horizontal","card-size": "small","collapsed-rows": 0 },"components" : { "title" : "title","subtitle":"subtitle","summary":"summary","art":{ "field": "art","aspect-ratio": 1 } } }` const searchCategoryTemplate = `{ "schema-version" : 1,"template" : { "category-layout" : "carousel","card-size": "large","overlay" : true },"components" : { "title" : "title","art" : { "field": "art","aspect-ratio": 1.6,"fill-mode": "fit" } } }` // SCOPE *********************************************************************** var scope_interface scopes.Scope type MyScope struct { BaseURI string Key string URI string Dir string base *scopes.ScopeBase } type WathereResponse struct { WeatherList []Weather `json:"results"` Date string `json:"date"` } type Weather struct { CurrentCity string `json:"currentCity"` Pm25 string `json:"pm25"` IndexList []Index `json:"index"` Weather_datalist []Weather_data `json:"weather_data"` } type Index struct { Title string `json:"title"` Zs string `json:"zs"` Tipt string `json:"tipt"` Des string `json:"des"` } type Weather_data struct { Date string `json:"date"` DayPictureUrl string `json:"dayPictureUrl"` NightPictureUrl string `json:"nightPictureUrl"` Weather string `json:"weather"` Wind string `json:"wind"` Temperature string `json:"temperature"` } func (s *MyScope) buildUrl(url2 string,params map[string]string) string { query := make(url.Values) for key,value := range params { query.Set(key,value) } log.Println(url2 + query.Encode()) return url2 + query.Encode() } // This is used to get results from a webservice func (s *MyScope) get(url string,params map[string]string,result interface{}) error { resp,err := http.Get(s.buildUrl(url,params)) if err != nil { return err } defer resp.Body.Close() decoder := json.NewDecoder(resp.Body) return decoder.Decode(result) } func (s *MyScope) Search(q *scopes.CannedQuery,Metadata *scopes.SearchMetadata,reply *scopes.SearchReply,cancelled <-chan bool) error { root_department := s.CreateDepartments(q,Metadata,reply) reply.RegisterDepartments(root_department) query := q.QueryString() log.Println(query) // Try to get the city name loc := Metadata.Location() city := loc.City; log.Println("city: ",city) if query == "" { if q.DepartmentID() == "" { query = city } else { query = q.DepartmentID() } } log.Println("query: ",query) var response WathereResponse if err := s.get(s.BaseURI,map[string]string{"location": query,"ak": s.Key,"output": "json"},&response); err != nil { return err } else { log.Println("there is no error!") } // log.Println(response) date := response.Date; log.Println("date: ",date) var cat *scopes.Category; if len(q.QueryString()) == 0 && q.DepartmentID() == "" { cat = reply.RegisterCategory("weather",query,"",searchCategoryTemplate) } else { cat = reply.RegisterCategory("weather",searchCategoryTemplate) } for _,data := range response.WeatherList { result := scopes.NewCategorisedResult(cat) result.SetURI(s.URI) // log.Println("Current city:",data.CurrentCity) // log.Println("PM25: ",data.Pm25) var yellocalendar string = "" for _,index := range data.IndexList { // log.Println("title: ",index.Title) // log.Println("zs: ",index.Zs) // log.Println("tipt: ",index.Tipt) // log.Println("Des: ",index.Des) yellocalendar += index.Title + " " yellocalendar += index.Zs + " " yellocalendar += index.Tipt + " " yellocalendar += index.Des } for i,weather := range data.Weather_datalist { // log.Println("date: ",weather.Date) // log.Println("dayPictureUrl: ",weather.DayPictureUrl) // log.Println("nightPictureUrl: ",weather.NightPictureUrl) // log.Println("weather: ",weather.Weather) // log.Println("wind: ",weather.Wind) // log.Println("temperature: ",weather.Temperature) result.SetTitle(weather.Date) result.SetArt(weather.DayPictureUrl) result.Set("wind",weather.Wind) result.Set("weather",weather.Weather) result.Set("temperature",weather.Temperature) if err := reply.Push(result); err != nil { return err } result.SetArt(weather.NightPictureUrl) if err := reply.Push(result); err != nil { return err } // Push the yellow calender now if i == 0 { cat1 := reply.RegisterCategory("weather1","今天天气",searchCategoryYellow) result1 := scopes.NewCategorisedResult(cat1) result1.SetURI(s.URI) result1.SetTitle(date) result1.SetArt(weather.DayPictureUrl) result1.Set("subtitle",weather.Weather + " " + weather.Wind + " " + weather.Temperature + " PMI: " + data.Pm25) result1.Set("summary",yellocalendar) if err := reply.Push(result1); err != nil { return err } } } } return nil } func (s *MyScope) Preview(result *scopes.Result,Metadata *scopes.ActionMetadata,reply *scopes.PreviewReply,cancelled <-chan bool) error { layout1col := scopes.NewColumnLayout(1) layout2col := scopes.NewColumnLayout(2) layout3col := scopes.NewColumnLayout(3) // Single cyolumn layout layout1col.AddColumn("header","image","wind","weather","temperature","summary","actions") // Two column layout layout2col.AddColumn("header") layout2col.AddColumn("image","actions") // Three cokumn layout layout3col.AddColumn("header") layout3col.AddColumn("image","actions") layout3col.AddColumn() // Register the layouts we just created reply.RegisterLayout(layout1col,layout2col,layout3col) header := scopes.NewPreviewWidget("header","header") // It has title and a subtitle properties header.AddAttributeMapping("title","title") header.AddAttributeMapping("subtitle","subtitle") // Define the image section image := scopes.NewPreviewWidget("image","image") // It has a single source property,mapped to the result's art property image.AddAttributeMapping("source","art") // Define the summary section description := scopes.NewPreviewWidget("summary","text") description.AddAttributeMapping("text","summary") wind := scopes.NewPreviewWidget("wind","text") wind.AddAttributeMapping("text","wind") weather := scopes.NewPreviewWidget("weather","text") weather.AddAttributeMapping("text","weather") temperature := scopes.NewPreviewWidget("temperature","text") temperature.AddAttributeMapping("text","temperature") // build variant map. var uri string if err := result.Get("uri",&uri); err != nil { log.Println(err) } tuple1 := make(map[string]interface{}) tuple1["id"] = "open" tuple1["label"] = "Open" tuple1["uri"] = uri actions := scopes.NewPreviewWidget("actions","actions") actions.AddAttributeValue("actions",[]interface{}{tuple1}) var summary string if err := result.Get("summary",&summary); err != nil { log.Println(err) } if len(summary) > 0 { reply.PushWidgets(header,image,description,actions) } else { reply.PushWidgets(header,wind,weather,temperature,actions) } return nil } func (s *MyScope) SetScopeBase(base *scopes.ScopeBase) { s.base = base } func (s *MyScope) GetSubdepartments1(query *scopes.CannedQuery,reply *scopes.SearchReply) *scopes.Department { active_dep,err := scopes.NewDepartment("wuhan","湖北") if err == nil { // active_dep.SetAlternateLabel("Rock Music Alt") department,_ := scopes.NewDepartment("武汉","武汉") active_dep.AddSubdepartment(department) department2,_ := scopes.NewDepartment("宜昌","宜昌") active_dep.AddSubdepartment(department2) department3,_ := scopes.NewDepartment("随州","随州") active_dep.AddSubdepartment(department3) } return active_dep } func (s *MyScope) GetSubdepartments2(query *scopes.CannedQuery,err := scopes.NewDepartment("changsha","湖南") if err == nil { department,_ := scopes.NewDepartment("长沙","长沙") active_dep.AddSubdepartment(department) department2,_ := scopes.NewDepartment("株洲","株洲") active_dep.AddSubdepartment(department2) } return active_dep } func (s *MyScope) CreateDepartments(query *scopes.CannedQuery,reply *scopes.SearchReply) *scopes.Department { department,_ := scopes.NewDepartment("","选择地点") dept1 := s.GetSubdepartments1(query,reply) if dept1 != nil { department.AddSubdepartment(dept1) } dept2 := s.GetSubdepartments2(query,reply) if dept2 != nil { department.AddSubdepartment(dept2) } return department } // MAIN ************************************************************************ func main() { scope := &MyScope { BaseURI: "http://api.map.baidu.com/telematics/v3/weather?",Key: "DdzwVcsGMoYpeg5xQlAFrXQt",URI: "http://www.weather.com.cn/html/weather/101010100.shtml",Dir: "",} scope_interface = scope if err := scopes.Run(scope); err != nil { log.Fatalln(err) } }
这是我们的goscope的go语言程序设计。在这个goscope里,我们使用了百度的天气API来显示天气的数据。整个scope的设计其实和C++的Scope设计比较相似,但是显得更加简洁明了。
我们可以通过如下的命令来把我们的Scope打包并部署到我们的手机上:
$./build.sh -d