Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
473 lines (367 sloc) 13.9 KB
package main
import (
"launchpad.net/go-unityscopes/v2"
"log"
"encoding/json"
"net/http"
"fmt"
"math"
"strings"
"net/url"
)
const searchCategoryTemplate = `{
"schema-version": 1,
"template": {
"category-layout": "grid",
"card-size": "large",
"card-layout": "horizontal"
},
"components": {
"title": "title",
"subtitle": "subtitle",
"overlay-color": "#FFFFFFFF",
"art": {
"field": "art",
"aspect-ratio": 1
}
}
}`
const uappexplorer = "<%= api %>";
type App struct {
Author string `json:"author"`
BayesianAverage float64 `json:"bayesian_average"`
Description string `json:"description"`
IconHash string `json:"icon_hash"`
License string `json:"license"`
Name string `json:"name"`
Permissions []string `json:"permissions"`
Points int64 `json:"points"`
Screenshots []string `json:"screenshots"`
Tagline string `json:"tagline"`
Title string `json:"title"`
Types []string `json:"types"`
Version string `json:"version"`
}
type Data struct {
Apps []App `json:"apps"`
}
type Response struct {
Data Data `json:"data"`
}
type EResponse struct {
Apps []App `json:"data"`
}
type ActionInfo struct {
Id string `json:"id"`
Label string `json:"label"`
Icon string `json:"icon,omitempty"`
Uri string `json:"uri,omitempty"`
}
var scope_interface scopes.Scope
type UAppExplorerScope struct {
base *scopes.ScopeBase
}
func (s *UAppExplorerScope) getEssentials(result interface {}) error {
resp, err := http.Get(fmt.Sprintf("%s/api/apps/essentials", uappexplorer))
if err != nil {
return err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(result)
}
func (s *UAppExplorerScope) get(search string, sort string, limit int, category string, typ string, result interface{}) error {
var uri string = fmt.Sprintf("%s/api/apps?limit=%d", uappexplorer, limit)
if search != "" {
uri = fmt.Sprintf("%s&search=%s", uri, strings.Replace(search, " ", "%20", -1))
sort = "relevance"
}
if sort != "" {
uri = fmt.Sprintf("%s&sort=%s", uri, sort)
}
query := map[string] string {}
if category != "" {
query["categories"] = category
}
if typ != "" {
query["types"] = typ
}
if category != "" || typ != "" {
jsonQuery, _ := json.Marshal(query)
uri = fmt.Sprintf("%s&query=%s", uri, url.QueryEscape(string(jsonQuery)))
}
log.Println(uri)
resp, err := http.Get(uri)
if err != nil {
return err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(result)
}
func (s *UAppExplorerScope) Preview(result *scopes.Result, metadata *scopes.ActionMetadata, reply *scopes.PreviewReply, cancelled <-chan bool) error {
typeMap := map[string] string {
"application": "App",
"scope": "Scope",
"webapp": "Web App",
"snappy": "Snap",
"snappy_oem": "OEM Snap",
"snappy_os": "OS Snap",
"snappy_kernel": "Kernel Snap",
"snappy_gadget": "Gadget Snap",
"snappy_framework": "Framework Snap",
"snappy_application": "Snap",
"unknown": "Unknown",
}
var types string = ""
var permissions string = ""
var app App
if err := result.Get("app", &app); err != nil {
log.Println(err)
}
for _, typ := range app.Types {
if types == "" {
types = typeMap[typ]
} else {
types = fmt.Sprintf("%s, %s", types, typeMap[typ])
}
}
for _, permission := range app.Permissions {
permission = strings.Replace(permission, "-", " ", -1)
permission = strings.Replace(permission, "_", " ", -1)
permission = strings.Title(permission)
if permissions == "" {
permissions = permission
} else {
permissions = fmt.Sprintf("%s, %s", permissions, permission)
}
}
columnLayout1 := scopes.NewColumnLayout(1)
columnLayout1.AddColumn("header", "icon", "author", "hearts", "stars",
"types", "actions", "more_apps", "actions2", "screenshots", "description",
"permissions", "license", "version",)
columnLayout2 := scopes.NewColumnLayout(2)
columnLayout2.AddColumn("header", "icon", "screenshots", "permissions",
"license", "version")
columnLayout2.AddColumn("author", "hearts", "stars", "types", "actions",
"more_apps", "actions2", "description")
reply.RegisterLayout(columnLayout1, columnLayout2)
headerWidget := scopes.NewPreviewWidget("header", "header")
headerWidget.AddAttributeValue("title", app.Title)
iconWidget := scopes.NewPreviewWidget("icon", "image")
iconWidget.AddAttributeMapping("source", "art")
typesWidget := scopes.NewPreviewWidget("types", "text")
typesWidget.AddAttributeValue("text", fmt.Sprintf("<b>Type:</b> %s", types))
authorWidget := scopes.NewPreviewWidget("author", "text")
authorWidget.AddAttributeValue("text", fmt.Sprintf("<b>Author:</b> %s", app.Author))
heartsWidget := scopes.NewPreviewWidget("hearts", "text")
heartsWidget.AddAttributeValue("text", fmt.Sprintf("<b>Hearts:</b> %d", app.Points))
//Very hacky looking round to the nearest 0.5
starRating := map[string] float64 {
"rating": math.Floor(app.BayesianAverage * 2 + 0.5) / 2,
}
var reviews []map[string] float64
reviews = append(reviews, starRating)
starsWidget := scopes.NewPreviewWidget("stars", "reviews")
starsWidget.AddAttributeValue("reviews", reviews)
var buttons []ActionInfo
buttons = append(buttons, ActionInfo{Id: "view", Uri: fmt.Sprintf("%s/app/%s", uappexplorer, app.Name), Label: "View"})
buttons = append(buttons, ActionInfo{Id: "install", Uri: "scope://com.canonical.scopes.clickstore?q=" + app.Title, Label: "Install"})
actionsWidget := scopes.NewPreviewWidget("actions", "actions")
actionsWidget.AddAttributeValue("actions", buttons)
moreWidget := scopes.NewPreviewWidget("more_apps", "text")
moreWidget.AddAttributeValue("text", fmt.Sprintf("More apps by %s:", app.Author))
var buttons2 []ActionInfo
uri := fmt.Sprintf("scope://uappexplorer-scope.bhdouglass_uappexplorer-scope?q=author:%s", strings.Replace(app.Author, " ", "%20", -1))
buttons2 = append(buttons2, ActionInfo{Id: "more", Uri: uri, Label: "More"})
actionsWidget2 := scopes.NewPreviewWidget("actions2", "actions")
actionsWidget2.AddAttributeValue("actions", buttons2)
screenshotsWidget := scopes.NewPreviewWidget("screenshots", "gallery")
screenshotsWidget.AddAttributeValue("sources", app.Screenshots)
descriptionWidget := scopes.NewPreviewWidget("description", "text")
descriptionWidget.AddAttributeValue("text", app.Description)
licenseWidget := scopes.NewPreviewWidget("license", "text")
licenseWidget.AddAttributeValue("text", fmt.Sprintf("<b>License:</b> %s", app.License))
versionWidget := scopes.NewPreviewWidget("version", "text")
versionWidget.AddAttributeValue("text", fmt.Sprintf("<b>Version:</b> %s", app.Version))
permissionsWidget := scopes.NewPreviewWidget("permissions", "text")
permissionsWidget.AddAttributeValue("text", fmt.Sprintf("<b>Permissions:</b> %s", permissions))
return reply.PushWidgets(headerWidget, iconWidget, typesWidget, authorWidget,
heartsWidget, starsWidget, actionsWidget, moreWidget, actionsWidget2,
screenshotsWidget, descriptionWidget, licenseWidget, versionWidget,
permissionsWidget)
}
func (s *UAppExplorerScope) addApps(apps []App, category *scopes. Category, reply *scopes.SearchReply) error {
for _, app := range apps {
result := scopes.NewCategorisedResult(category)
result.SetURI(fmt.Sprintf("%s/app/%s", uappexplorer, app.Name))
result.SetTitle(app.Title)
result.Set("subtitle", app.Tagline)
result.Set("app", app)
result.SetArt(fmt.Sprintf("%s/api/icon/%s/%s.png", uappexplorer, app.IconHash, app.Name))
if err := reply.Push(result); err != nil {
return err
}
}
return nil
}
func (s *UAppExplorerScope) CreateDepartments(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply) error {
category_dept, err := scopes.NewDepartment("", query, "Category")
if err == nil {
dept0, _ := scopes.NewDepartment("books-comics", query, "Books & Comics")
category_dept.AddSubdepartment(dept0)
dept1, _ := scopes.NewDepartment("business", query, "Business")
category_dept.AddSubdepartment(dept1)
dept2, _ := scopes.NewDepartment("communication", query, "Communication")
category_dept.AddSubdepartment(dept2)
dept3, _ := scopes.NewDepartment("developer-tools", query, "Developer Tools")
category_dept.AddSubdepartment(dept3)
dept4, _ := scopes.NewDepartment("education", query, "Education")
category_dept.AddSubdepartment(dept4)
dept5, _ := scopes.NewDepartment("entertainment", query, "Entertainment")
category_dept.AddSubdepartment(dept5)
dept6, _ := scopes.NewDepartment("finance", query, "Finance")
category_dept.AddSubdepartment(dept6)
dept7, _ := scopes.NewDepartment("food-drink", query, "Food & Drink")
category_dept.AddSubdepartment(dept7)
dept8, _ := scopes.NewDepartment("games", query, "Games")
category_dept.AddSubdepartment(dept8)
dept9, _ := scopes.NewDepartment("graphics", query, "Graphics")
category_dept.AddSubdepartment(dept9)
dept10, _ := scopes.NewDepartment("health-fitness", query, "Health & Fitness")
category_dept.AddSubdepartment(dept10)
dept11, _ := scopes.NewDepartment("lifestyle", query, "Lifestyle")
category_dept.AddSubdepartment(dept11)
dept12, _ := scopes.NewDepartment("media-video", query, "Media & Video")
category_dept.AddSubdepartment(dept12)
dept13, _ := scopes.NewDepartment("medical", query, "Medical")
category_dept.AddSubdepartment(dept13)
dept14, _ := scopes.NewDepartment("music-audio", query, "Music & Audio")
category_dept.AddSubdepartment(dept14)
dept15, _ := scopes.NewDepartment("news-magazines", query, "News & Magazines")
category_dept.AddSubdepartment(dept15)
dept16, _ := scopes.NewDepartment("personalisation", query, "Personalisation")
category_dept.AddSubdepartment(dept16)
dept17, _ := scopes.NewDepartment("productivity", query, "Productivity")
category_dept.AddSubdepartment(dept17)
dept18, _ := scopes.NewDepartment("reference", query, "Reference")
category_dept.AddSubdepartment(dept18)
dept19, _ := scopes.NewDepartment("science-engineering", query, "Science & Engineering")
category_dept.AddSubdepartment(dept19)
dept20, _ := scopes.NewDepartment("shopping", query, "Shopping")
category_dept.AddSubdepartment(dept20)
dept21, _ := scopes.NewDepartment("social-networking", query, "Social Networking")
category_dept.AddSubdepartment(dept21)
dept22, _ := scopes.NewDepartment("sports", query, "Sports")
category_dept.AddSubdepartment(dept22)
dept23, _ := scopes.NewDepartment("travel-local", query, "Travel & Local")
category_dept.AddSubdepartment(dept23)
dept24, _ := scopes.NewDepartment("universal-accessaccessibility", query, "Universal Access/Accessibility")
category_dept.AddSubdepartment(dept24)
dept25, _ := scopes.NewDepartment("accessories", query, "Utilities")
category_dept.AddSubdepartment(dept25)
dept26, _ := scopes.NewDepartment("weather", query, "Weather")
category_dept.AddSubdepartment(dept26)
reply.RegisterDepartments(category_dept)
}
return err
}
func (s *UAppExplorerScope) Search(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply, cancelled <-chan bool) error {
q := query.QueryString()
log.Println(fmt.Sprintf("query: %s", q))
log.Println(fmt.Sprintf("dept id: %s", query.DepartmentID()))
typeMap := map[string] string {
"all": "All Phone Apps & Scopes",
"application": "Apps",
"scope": "Scopes",
"webapp": "Web Apps",
"snappy": "All Snaps",
"snappy_oem": "OEM Snaps",
"snappy_os": "OS Snaps",
"snappy_kernel": "Kernel Snaps",
"snappy_gadget": "Gadget Snaps",
"snappy_framework": "Framework Snaps",
"snappy_application": "App Snaps",
}
var res Response
var eres EResponse
type_filter := scopes.NewOptionSelectorFilter("types", "Types", false)
type_filter.DisplayHints = 1 //scopes.FilterDisplayPrimary doesn't work here?
for key, value := range typeMap {
type_filter.AddOption(key, value)
}
//type_filter.ActiveOptions(query.FilterState()) ends in a panic, using this ugly hack instead
var typ string = "";
if query.FilterState()["types"] != nil {
typ = fmt.Sprintf("%s", query.FilterState()["types"])
typ = strings.Replace(typ, "[", "", -1)
typ = strings.Replace(typ, "]", "", -1)
}
log.Println(fmt.Sprintf("type: %s", typ))
if (typ == "all") {
typ = "" //treat "all" as if it were the default query
}
var name string = "Apps"
var newName string = "New Apps"
if typ != "" {
name = typeMap[typ]
}
if q == "" {
name = "Top Apps"
if typ == "snappy" {
name = "Top Snaps"
newName = "New Snaps"
} else if typ != "" {
name = fmt.Sprintf("Top %s", typeMap[typ])
newName = fmt.Sprintf("New %s", typeMap[typ])
}
}
if err := reply.PushFilters([]scopes.Filter{type_filter}, query.FilterState()); err != nil {
log.Println(err)
}
if err := s.CreateDepartments(query, metadata, reply); err != nil {
log.Println(err)
}
top := reply.RegisterCategory("apps", name, "", searchCategoryTemplate)
if err := s.get(q, "-points", 20, query.DepartmentID(), typ, &res); err != nil {
log.Println(err)
return err
}
if err := s.addApps(res.Data.Apps, top, reply); err != nil {
log.Println(err)
return err
}
if q == "" {
n := reply.RegisterCategory("new", newName, "", searchCategoryTemplate)
if err := s.get("", "-published_date", 10, query.DepartmentID(), typ, &res); err != nil {
log.Println(err)
return err
}
if err := s.addApps(res.Data.Apps, n, reply); err != nil {
log.Println(err)
return err
}
if (query.DepartmentID() == "" && typ == "") {
essentials := reply.RegisterCategory("essentials", "Essentials", "", searchCategoryTemplate)
if err := s.getEssentials(&eres); err != nil {
log.Println(err)
return err
}
if err := s.addApps(eres.Apps, essentials, reply); err != nil {
log.Println(err)
return err
}
}
}
return nil
}
func (s *UAppExplorerScope) SetScopeBase(base *scopes.ScopeBase) {
s.base = base
}
func main() {
log.Println("starting up")
scope := &UAppExplorerScope{}
if err := scopes.Run(scope); err != nil {
log.Fatalln(err)
}
}