<commit_before>package controllers

import (
    "encoding/json"
    "github.com/orc/db"
    "github.com/orc/sessions"
    "github.com/orc/utils"
    "math"
    "net/http"
    "strconv"
    "strings"
)

func (this *GridHandler) Load(tableName string) {
    if tableName != "events" && !sessions.CheckSession(this.Response, this.Request) {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    isAdmin := this.isAdmin()

    var filters map[string]interface{}
    if this.Request.PostFormValue("_search") == "true" {
        err := json.NewDecoder(strings.NewReader(this.Request.PostFormValue("filters"))).Decode(&filters)
        if err != nil {
            http.Error(this.Response, err.Error(), 400)
            return
        }
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sord := this.Request.PostFormValue("sord")
    sidx := this.Request.FormValue("sidx")
    start := limit*page - limit

    if tableName == "search" {
        var filters map[string]interface{}
        err := json.NewDecoder(strings.NewReader(this.Request.PostFormValue("filters"))).Decode(&filters)
        if err != nil {
            utils.SendJSReply(nil, this.Response)
            return
        }

        model := this.GetModel("faces")
        query := `SELECT DISTINCT faces.id, faces.user_id
            FROM reg_param_vals
            INNER JOIN registrations ON registrations.id = reg_param_vals.reg_id
            INNER JOIN faces ON faces.id = registrations.face_id
            INNER JOIN events ON events.id = registrations.event_id
            INNER JOIN param_values ON param_values.id = reg_param_vals.param_val_id
            INNER JOIN params ON params.id = param_values.param_id`

        where, params, _ := model.WhereByParams(filters, 1)

        if !isAdmin {
            where = ` WHERE events.id = 1 AND `+where
        } else {
            if where != "" {
                where = " WHERE "+where
            }
        }
        where += ` ORDER BY faces.id `+sord
        query += where+` LIMIT $`+strconv.Itoa(len(params)+1)+` OFFSET $`+strconv.Itoa(len(params)+2)+`;`
        rows := db.Query(query, append(params, []interface{}{limit, start}...))

        query = `SELECT COUNT(*)
            FROM (SELECT DISTINCT faces.id, faces.user_id
            FROM reg_param_vals
            INNER JOIN registrations ON registrations.id = reg_param_vals.reg_id
            INNER JOIN faces ON faces.id = registrations.face_id
            INNER JOIN events ON events.id = registrations.event_id
            INNER JOIN param_values ON param_values.id = reg_param_vals.param_val_id
            INNER JOIN params ON params.id = param_values.param_id
            INNER JOIN users ON users.id = faces.user_id`
        query += where+") as count;"
        count := int(db.Query(query, params)[0].(map[string]interface{})["count"].(int))

        var totalPages int
        if count > 0 {
            totalPages = int(math.Ceil(float64(count) / float64(limit)))
        } else {
            totalPages = 0
        }

        result := make(map[string]interface{}, 4)
        result["rows"] = rows
        result["page"] = page
        result["total"] = totalPages
        result["records"] = count

        utils.SendJSReply(result, this.Response)
        return
    }

    model := this.GetModel(tableName)
    where, params, _ := model.Where(filters, 1)

    if tableName == "param_values" && !isAdmin {
        w := " WHERE param_values.param_id in (4, 5, 6, 7)"
        if where != "" {
            where = w+" AND "+where
        } else {
            where = w
        }
    } else {
        if where != "" {
            where = " WHERE "+where
        }
    }

    query := `SELECT `+strings.Join(model.GetColumns(), ", ")+` FROM `+model.GetTableName()+where+` ORDER BY `+sidx+` `+sord+` LIMIT $`+strconv.Itoa(len(params)+1)+` OFFSET $`+strconv.Itoa(len(params)+2)+`;`
    rows := db.Query(query, append(params, []interface{}{limit, start}...))

    query = `SELECT COUNT(*) FROM (SELECT `+model.GetTableName()+`.id FROM `+model.GetTableName()
    query += where+`) as count;`
    count := int(db.Query(query, params)[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 4)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) UserGroupsLoad() {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    query := `SELECT groups.id, groups.name FROM groups
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 ORDER BY $2 LIMIT $3 OFFSET $4;`
    rows := db.Query(query, []interface{}{userId, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT groups.id FROM groups
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1) as count;`
    count := int(db.Query(query, []interface{}{userId})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) GroupsLoad() {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    query := `SELECT groups.id, groups.name FROM groups
        INNER JOIN persons ON persons.group_id = groups.id
        INNER JOIN faces ON faces.id = persons.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 ORDER BY $2 LIMIT $3 OFFSET $4;`
    rows := db.Query(query, []interface{}{userId, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT groups.id FROM groups
        INNER JOIN persons ON persons.group_id = groups.id
        INNER JOIN faces ON faces.id = persons.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1) as count;`
    count := int(db.Query(query, []interface{}{userId})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) RegistrationsLoad(userId_ string) {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    var id int
    if this.isAdmin() {
        id, err = strconv.Atoi(userId_)
        if err != nil {
            http.Error(this.Response, err.Error(), 400)
            return
        }
    } else {
        id = userId
    }

    query := `SELECT registrations.id, registrations.event_id, registrations.status FROM registrations
        INNER JOIN events ON events.id = registrations.event_id
        INNER JOIN faces ON faces.id = registrations.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 ORDER BY $2 LIMIT $3 OFFSET $4;`
    rows := db.Query(query, []interface{}{id, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT registrations.id FROM registrations
        INNER JOIN events ON events.id = registrations.event_id
        INNER JOIN faces ON faces.id = registrations.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1) as count;`
    count := int(db.Query(query, []interface{}{id})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) GroupRegistrationsLoad(isTeam string) {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    isTeam_, err := strconv.ParseBool(isTeam)
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    query := `SELECT group_registrations.id, group_registrations.event_id, group_registrations.group_id FROM group_registrations
        INNER JOIN events ON events.id = group_registrations.event_id
        INNER JOIN groups ON groups.id = group_registrations.group_id
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 AND events.team = $2 ORDER BY $3 LIMIT $4 OFFSET $5;`
    rows := db.Query(query, []interface{}{userId, isTeam_, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT group_registrations.id FROM group_registrations
        INNER JOIN events ON events.id = group_registrations.event_id
        INNER JOIN groups ON groups.id = group_registrations.group_id
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 AND events.team = $2) as count;`
    count := int(db.Query(query, []interface{}{userId, isTeam_})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) PersonsLoad(groupId string) {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    id, err := strconv.Atoi(groupId)
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    faceId := -1
    query := `SELECT groups.face_id FROM groups
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 AND groups.id = $2;`
    err = db.QueryRow(query, []interface{}{userId, id}).Scan(&faceId)

    if (err != nil || faceId == -1) && !this.isAdmin() {
        http.Error(this.Response, "Вы не являетесь владельцем группы", 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    query = `SELECT persons.id, persons.group_id, persons.face_id, persons.status
        FROM persons
        INNER JOIN groups ON groups.id = persons.group_id
        WHERE groups.id = $1 ORDER BY $2 LIMIT $3 OFFSET $4;`
    rows := db.Query(query, []interface{}{id, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT persons.id FROM persons
        INNER JOIN groups ON groups.id = persons.group_id
        WHERE groups.id = $1) as count;`
    count := int(db.Query(query, []interface{}{userId})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}
<commit_msg>check users.id while searching `face`<commit_after>package controllers

import (
    "encoding/json"
    "github.com/orc/db"
    "github.com/orc/sessions"
    "github.com/orc/utils"
    "math"
    "net/http"
    "strconv"
    "strings"
)

func (this *GridHandler) Load(tableName string) {
    if tableName != "events" && !sessions.CheckSession(this.Response, this.Request) {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    isAdmin := this.isAdmin()

    var filters map[string]interface{}
    if this.Request.PostFormValue("_search") == "true" {
        err := json.NewDecoder(strings.NewReader(this.Request.PostFormValue("filters"))).Decode(&filters)
        if err != nil {
            http.Error(this.Response, err.Error(), 400)
            return
        }
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sord := this.Request.PostFormValue("sord")
    sidx := this.Request.FormValue("sidx")
    start := limit*page - limit

    if tableName == "search" {
        var filters map[string]interface{}
        err := json.NewDecoder(strings.NewReader(this.Request.PostFormValue("filters"))).Decode(&filters)
        if err != nil {
            utils.SendJSReply(nil, this.Response)
            return
        }

        model := this.GetModel("faces")
        query := `SELECT DISTINCT faces.id, faces.user_id
            FROM reg_param_vals
            INNER JOIN registrations ON registrations.id = reg_param_vals.reg_id
            INNER JOIN faces ON faces.id = registrations.face_id
            INNER JOIN events ON events.id = registrations.event_id
            INNER JOIN param_values ON param_values.id = reg_param_vals.param_val_id
            INNER JOIN params ON params.id = param_values.param_id
            INNER JOIN users ON users.id = faces.user_id`

        where, params, _ := model.WhereByParams(filters, 1)

        if !isAdmin {
            where = ` WHERE events.id = 1 AND `+where
        } else {
            if where != "" {
                where = " WHERE "+where
            }
        }
        where += ` ORDER BY faces.id `+sord
        query += where+` LIMIT $`+strconv.Itoa(len(params)+1)+` OFFSET $`+strconv.Itoa(len(params)+2)+`;`
        rows := db.Query(query, append(params, []interface{}{limit, start}...))

        query = `SELECT COUNT(*)
            FROM (SELECT DISTINCT faces.id, faces.user_id
            FROM reg_param_vals
            INNER JOIN registrations ON registrations.id = reg_param_vals.reg_id
            INNER JOIN faces ON faces.id = registrations.face_id
            INNER JOIN events ON events.id = registrations.event_id
            INNER JOIN param_values ON param_values.id = reg_param_vals.param_val_id
            INNER JOIN params ON params.id = param_values.param_id
            INNER JOIN users ON users.id = faces.user_id`
        query += where+") as count;"
        count := int(db.Query(query, params)[0].(map[string]interface{})["count"].(int))

        var totalPages int
        if count > 0 {
            totalPages = int(math.Ceil(float64(count) / float64(limit)))
        } else {
            totalPages = 0
        }

        result := make(map[string]interface{}, 4)
        result["rows"] = rows
        result["page"] = page
        result["total"] = totalPages
        result["records"] = count

        utils.SendJSReply(result, this.Response)
        return
    }

    model := this.GetModel(tableName)
    where, params, _ := model.Where(filters, 1)

    if tableName == "param_values" && !isAdmin {
        w := " WHERE param_values.param_id in (4, 5, 6, 7)"
        if where != "" {
            where = w+" AND "+where
        } else {
            where = w
        }
    } else {
        if where != "" {
            where = " WHERE "+where
        }
    }

    query := `SELECT `+strings.Join(model.GetColumns(), ", ")+` FROM `+model.GetTableName()+where+` ORDER BY `+sidx+` `+sord+` LIMIT $`+strconv.Itoa(len(params)+1)+` OFFSET $`+strconv.Itoa(len(params)+2)+`;`
    rows := db.Query(query, append(params, []interface{}{limit, start}...))

    query = `SELECT COUNT(*) FROM (SELECT `+model.GetTableName()+`.id FROM `+model.GetTableName()
    query += where+`) as count;`
    count := int(db.Query(query, params)[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 4)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) UserGroupsLoad() {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    query := `SELECT groups.id, groups.name FROM groups
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 ORDER BY $2 LIMIT $3 OFFSET $4;`
    rows := db.Query(query, []interface{}{userId, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT groups.id FROM groups
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1) as count;`
    count := int(db.Query(query, []interface{}{userId})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) GroupsLoad() {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    query := `SELECT groups.id, groups.name FROM groups
        INNER JOIN persons ON persons.group_id = groups.id
        INNER JOIN faces ON faces.id = persons.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 ORDER BY $2 LIMIT $3 OFFSET $4;`
    rows := db.Query(query, []interface{}{userId, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT groups.id FROM groups
        INNER JOIN persons ON persons.group_id = groups.id
        INNER JOIN faces ON faces.id = persons.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1) as count;`
    count := int(db.Query(query, []interface{}{userId})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) RegistrationsLoad(userId_ string) {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    var id int
    if this.isAdmin() {
        id, err = strconv.Atoi(userId_)
        if err != nil {
            http.Error(this.Response, err.Error(), 400)
            return
        }
    } else {
        id = userId
    }

    query := `SELECT registrations.id, registrations.event_id, registrations.status FROM registrations
        INNER JOIN events ON events.id = registrations.event_id
        INNER JOIN faces ON faces.id = registrations.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 ORDER BY $2 LIMIT $3 OFFSET $4;`
    rows := db.Query(query, []interface{}{id, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT registrations.id FROM registrations
        INNER JOIN events ON events.id = registrations.event_id
        INNER JOIN faces ON faces.id = registrations.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1) as count;`
    count := int(db.Query(query, []interface{}{id})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) GroupRegistrationsLoad(isTeam string) {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    isTeam_, err := strconv.ParseBool(isTeam)
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    query := `SELECT group_registrations.id, group_registrations.event_id, group_registrations.group_id FROM group_registrations
        INNER JOIN events ON events.id = group_registrations.event_id
        INNER JOIN groups ON groups.id = group_registrations.group_id
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 AND events.team = $2 ORDER BY $3 LIMIT $4 OFFSET $5;`
    rows := db.Query(query, []interface{}{userId, isTeam_, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT group_registrations.id FROM group_registrations
        INNER JOIN events ON events.id = group_registrations.event_id
        INNER JOIN groups ON groups.id = group_registrations.group_id
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 AND events.team = $2) as count;`
    count := int(db.Query(query, []interface{}{userId, isTeam_})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}

func (this *Handler) PersonsLoad(groupId string) {
    userId, err := this.CheckSid()
    if err != nil {
        http.Error(this.Response, "Unauthorized", 400)
        return
    }

    limit, err := strconv.Atoi(this.Request.PostFormValue("rows"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    page, err := strconv.Atoi(this.Request.PostFormValue("page"))
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    id, err := strconv.Atoi(groupId)
    if err != nil {
        http.Error(this.Response, err.Error(), 400)
        return
    }

    faceId := -1
    query := `SELECT groups.face_id FROM groups
        INNER JOIN faces ON faces.id = groups.face_id
        INNER JOIN users ON users.id = faces.user_id
        WHERE users.id = $1 AND groups.id = $2;`
    err = db.QueryRow(query, []interface{}{userId, id}).Scan(&faceId)

    if (err != nil || faceId == -1) && !this.isAdmin() {
        http.Error(this.Response, "Вы не являетесь владельцем группы", 400)
        return
    }

    sidx := this.Request.FormValue("sidx")
    start := limit * page - limit

    query = `SELECT persons.id, persons.group_id, persons.face_id, persons.status
        FROM persons
        INNER JOIN groups ON groups.id = persons.group_id
        WHERE groups.id = $1 ORDER BY $2 LIMIT $3 OFFSET $4;`
    rows := db.Query(query, []interface{}{id, sidx, limit, start})

    query = `SELECT COUNT(*) FROM (SELECT persons.id FROM persons
        INNER JOIN groups ON groups.id = persons.group_id
        WHERE groups.id = $1) as count;`
    count := int(db.Query(query, []interface{}{userId})[0].(map[string]interface{})["count"].(int))

    var totalPages int
    if count > 0 {
        totalPages = int(math.Ceil(float64(count) / float64(limit)))
    } else {
        totalPages = 0
    }

    result := make(map[string]interface{}, 2)
    result["rows"] = rows
    result["page"] = page
    result["total"] = totalPages
    result["records"] = count

    utils.SendJSReply(result, this.Response)
}
<|endoftext|>


<commit_before>package socket

import (
	"io"
	"net"
	"encoding/binary"
	"bytes"
	"log"
)

import (
	"io"
	"encoding/binary"
	"bytes"
	"net"
)

// Writer writes raw protoBuf reply messages.
// Each call to Write should send an entire message.
type Writer interface {
	io.Writer
}

// A ResponseWriter interface
type ResponseWriter interface {
	LocalAddr()    net.Addr     // LocalAddr returns net.Addr of the server
	RemoteAddr()   net.Addr     // RemoteAddr returns net.Addr of client that sent the current request.
	WriteMsg(*Msg) error        // WriteMsg writes a reply back to the client.
	Write([]byte)  (int, error) // Write writes a raw buffer back to the client.
	Close() error               // Close closes the connection.
}

type response struct {
	udp            *net.UDPConn      // i/o connection if UDP was used
	tcp            net.Conn          // i/o connection if TCP was used
	udpSession     *SessionUDP       // oob data to get egress interface right
	remoteAddr     net.Addr          // address of the client
	writer         Writer            // writer to output
}

// Write implements the ResponseWriter.Write method.
func (w *response) Write(m []byte) (int, error) {
	switch {
	case w.udp != nil:
		n, err := WriteToSessionUDP(w.udp, m, w.udpSession)

		return n, err
	case w.tcp != nil:
		lm := len(m)
		if lm < 2 {
			return 0, io.ErrShortBuffer
		}
		if lm > maxMsgSize {
			return 0, errMsgLarge
		}

		l := make([]byte, 2, 2+lm)
		binary.BigEndian.PutUint16(l, uint16(lm))
		m = append(l, m...)

		n, err := io.Copy(w.tcp, bytes.NewReader(m))

		return int(n), err
	default:
		log.Alert("Switch Not reached")
	}

	panic("Write switch fatal")
}

// WriteMsg implements the ResponseWriter.WriteMsg method.
func (w *response) WriteMsg(m *Msg) (err error) {
	var data []byte

	// ToDo implement pack/unpack
	//data, err = m.Pack()
	if err != nil {
		return err
	}

	n, err := w.writer.Write(data)
	if err != nil {
		return err
	}
	log.Debugf("MSG Writed: %s bytes", n)

	return nil
}

// LocalAddr implements the ResponseWriter.LocalAddr method.
func (w *response) LocalAddr() net.Addr {
	if w.tcp != nil {
		return w.tcp.LocalAddr()
	}
	return w.udp.LocalAddr()
}

// RemoteAddr implements the ResponseWriter.RemoteAddr method.
func (w *response) RemoteAddr() net.Addr { return w.remoteAddr }

// Close implements the ResponseWriter.Close method
func (w *response) Close() error {
	// Can't close the udp conn, as that is actually the listener.
	if w.tcp != nil {
		e := w.tcp.Close()
		w.tcp = nil
		return e
	}
	return nil
}
<commit_msg>Fix issue with logger<commit_after>package socket

import (
	"io"
	"net"
	"encoding/binary"
	"bytes"
)

// Writer writes raw protoBuf reply messages.
// Each call to Write should send an entire message.
type Writer interface {
	io.Writer
}

// A ResponseWriter interface
type ResponseWriter interface {
	LocalAddr()    net.Addr     // LocalAddr returns net.Addr of the server
	RemoteAddr()   net.Addr     // RemoteAddr returns net.Addr of client that sent the current request.
	WriteMsg(*Msg) error        // WriteMsg writes a reply back to the client.
	Write([]byte)  (int, error) // Write writes a raw buffer back to the client.
	Close() error               // Close closes the connection.
}

type response struct {
	udp            *net.UDPConn      // i/o connection if UDP was used
	tcp            net.Conn          // i/o connection if TCP was used
	udpSession     *SessionUDP       // oob data to get egress interface right
	remoteAddr     net.Addr          // address of the client
	writer         Writer            // writer to output
}

// Write implements the ResponseWriter.Write method.
func (w *response) Write(m []byte) (int, error) {
	switch {
	case w.udp != nil:
		n, err := WriteToSessionUDP(w.udp, m, w.udpSession)

		return n, err
	case w.tcp != nil:
		lm := len(m)
		if lm < 2 {
			return 0, io.ErrShortBuffer
		}
		if lm > maxMsgSize {
			return 0, errMsgLarge
		}

		l := make([]byte, 2, 2+lm)
		binary.BigEndian.PutUint16(l, uint16(lm))
		m = append(l, m...)

		n, err := io.Copy(w.tcp, bytes.NewReader(m))

		return int(n), err
	default:
		log.Alert("Switch Not reached")
	}

	panic("Write switch fatal")
}

// WriteMsg implements the ResponseWriter.WriteMsg method.
func (w *response) WriteMsg(m *Msg) (err error) {
	var data []byte

	// ToDo implement pack/unpack
	//data, err = m.Pack()
	if err != nil {
		return err
	}

	n, err := w.writer.Write(data)
	if err != nil {
		return err
	}
	log.Debugf("MSG Writed: %s bytes", n)

	return nil
}

// LocalAddr implements the ResponseWriter.LocalAddr method.
func (w *response) LocalAddr() net.Addr {
	if w.tcp != nil {
		return w.tcp.LocalAddr()
	}
	return w.udp.LocalAddr()
}

// RemoteAddr implements the ResponseWriter.RemoteAddr method.
func (w *response) RemoteAddr() net.Addr { return w.remoteAddr }

// Close implements the ResponseWriter.Close method
func (w *response) Close() error {
	// Can't close the udp conn, as that is actually the listener.
	if w.tcp != nil {
		e := w.tcp.Close()
		w.tcp = nil
		return e
	}
	return nil
}
<|endoftext|>


<commit_before>package speed

import (
	"errors"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/performancecopilot/speed/bytebuffer"
)

// byte lengths of different components in an mmv file
const (
	HeaderLength         = 40
	TocLength            = 16
	MetricLength         = 104
	ValueLength          = 32
	InstanceLength       = 80
	InstanceDomainLength = 32
	StringBlockLength    = 256
)

// MaxMetricNameLength is the maximum length for a metric name
const MaxMetricNameLength = 63

// MaxDataValueSize is the maximum byte length for a stored metric value, unless it is a string
const MaxDataValueSize = 16

// EraseFileOnStop if set to true, will also delete the memory mapped file
var EraseFileOnStop = false

// Writer defines the interface of a MMV file writer's properties
type Writer interface {
	// a writer must contain a registry of metrics and instance domains
	Registry() Registry

	// writes an mmv file
	Start() error

	// stops writing and cleans up
	Stop() error

	// returns the number of bytes that will be written by the current writer
	Length() int

	// adds a metric to be written
	Register(Metric) error

	// adds an instance domain to be written
	RegisterIndom(InstanceDomain) error

	// adds metric and instance domain from a string
	RegisterString(string, interface{}, MetricSemantics, MetricType, MetricUnit) error

	// update a metric
	Update(Metric, interface{}) error
}

func mmvFileLocation(name string) (string, error) {
	if strings.ContainsRune(name, os.PathSeparator) {
		return "", errors.New("name cannot have path separator")
	}

	tdir, present := config["PCP_TMP_DIR"]
	var loc string
	if present {
		loc = path.Join(rootPath, tdir)
	} else {
		loc = os.TempDir()
	}

	return path.Join(loc, "mmv", name), nil
}

// PCPClusterIDBitLength is the bit length of the cluster id
// for a set of PCP metrics
const PCPClusterIDBitLength = 12

// MMVFlag represents an enumerated type to represent mmv flag values
type MMVFlag int

// values for MMVFlag
const (
	NoPrefixFlag MMVFlag = 1 << iota
	ProcessFlag
	SentinelFlag
)

//go:generate stringer -type=MMVFlag

// PCPWriter implements a writer that can write PCP compatible MMV files
type PCPWriter struct {
	sync.Mutex
	loc       string            // absolute location of the mmv file
	clusterID uint32            // cluster identifier for the writer
	flag      MMVFlag           // write flag
	r         *PCPRegistry      // current registry
	buffer    bytebuffer.Buffer // current Buffer
}

// NewPCPWriter initializes a new PCPWriter object
func NewPCPWriter(name string, flag MMVFlag) (*PCPWriter, error) {
	fileLocation, err := mmvFileLocation(name)
	if err != nil {
		return nil, err
	}

	return &PCPWriter{
		loc:       fileLocation,
		r:         NewPCPRegistry(),
		clusterID: getHash(name, PCPClusterIDBitLength),
		flag:      flag,
		buffer:    nil,
	}, nil
}

// Registry returns a writer's registry
func (w *PCPWriter) Registry() Registry {
	return w.r
}

func (w *PCPWriter) tocCount() int {
	ans := 2

	if w.Registry().InstanceCount() > 0 {
		ans += 2
	}

	if w.Registry().StringCount() > 0 {
		ans++
	}

	return ans
}

// Length returns the byte length of data in the mmv file written by the current writer
func (w *PCPWriter) Length() int {
	return HeaderLength +
		(w.tocCount() * TocLength) +
		(w.Registry().InstanceCount() * InstanceLength) +
		(w.Registry().InstanceDomainCount() * InstanceDomainLength) +
		(w.Registry().MetricCount() * (MetricLength + ValueLength)) +
		(w.Registry().StringCount() * StringBlockLength)
}

func (w *PCPWriter) initializeOffsets() {
	indomoffset := HeaderLength + TocLength*w.tocCount()
	instanceoffset := indomoffset + InstanceDomainLength*w.r.InstanceDomainCount()
	metricsoffset := instanceoffset + InstanceLength*w.r.InstanceCount()
	valuesoffset := metricsoffset + MetricLength*w.r.MetricCount()
	stringsoffset := valuesoffset + ValueLength*w.r.MetricCount()

	w.r.indomoffset = indomoffset
	w.r.instanceoffset = instanceoffset
	w.r.metricsoffset = metricsoffset
	w.r.valuesoffset = valuesoffset
	w.r.stringsoffset = stringsoffset

	initializeSingletonMetricOffsets := func(metric *PCPSingletonMetric) {
		metric.descoffset = metricsoffset
		metricsoffset += MetricLength
		metric.valueoffset = valuesoffset
		valuesoffset += ValueLength

		if metric.shortDescription.val != "" {
			metric.shortDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}

		if metric.longDescription.val != "" {
			metric.longDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}
	}

	for _, indom := range w.r.instanceDomains {
		indom.offset = indomoffset
		indom.instanceOffset = instanceoffset
		indomoffset += InstanceDomainLength

		for _, i := range indom.instances {
			i.descoffset = instanceoffset
			instanceoffset += InstanceLength
		}

		if indom.shortDescription.val != "" {
			indom.shortDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}

		if indom.longDescription.val != "" {
			indom.longDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}
	}

	for _, metric := range w.r.metrics {
		switch metric.(type) {
		case *PCPSingletonMetric:
			initializeSingletonMetricOffsets(metric.(*PCPSingletonMetric))
		}
	}
}

func (w *PCPWriter) writeHeaderBlock() (generation2offset int, generation int64) {
	// tag
	w.buffer.WriteString("MMV")
	w.buffer.SetPos(w.buffer.Pos() + 1) // extra null byte is needed and \0 isn't a valid escape character in go

	// version
	w.buffer.WriteUint32(1)

	// generation
	generation = time.Now().Unix()
	w.buffer.WriteInt64(generation)

	generation2offset = w.buffer.Pos()

	w.buffer.WriteInt64(0)

	// tocCount
	w.buffer.WriteInt32(int32(w.tocCount()))

	// flag mask
	w.buffer.WriteInt32(int32(w.flag))

	// process identifier
	w.buffer.WriteInt32(int32(os.Getpid()))

	// cluster identifier
	w.buffer.WriteUint32(w.clusterID)

	return
}

func (w *PCPWriter) writeSingleToc(pos, identifier, count, offset int) {
	w.buffer.SetPos(pos)
	w.buffer.WriteInt32(int32(identifier))
	w.buffer.WriteInt32(int32(count))
	w.buffer.WriteUint64(uint64(offset))
}

func (w *PCPWriter) writeTocBlock() {
	tocpos := HeaderLength

	// instance domains toc
	if w.Registry().InstanceDomainCount() > 0 {
		// 1 is the identifier for instance domains
		w.writeSingleToc(tocpos, 1, w.r.InstanceDomainCount(), w.r.indomoffset)
		tocpos += TocLength
	}

	// instances toc
	if w.Registry().InstanceCount() > 0 {
		// 2 is the identifier for instances
		w.writeSingleToc(tocpos, 2, w.r.InstanceCount(), w.r.instanceoffset)
		tocpos += TocLength
	}

	// metrics and values toc
	metricsoffset, valuesoffset := w.r.metricsoffset, w.r.valuesoffset
	if w.Registry().MetricCount() == 0 {
		metricsoffset, valuesoffset = 0, 0
	}

	// 3 is the identifier for metrics
	w.writeSingleToc(tocpos, 3, w.r.MetricCount(), metricsoffset)
	tocpos += TocLength

	// 4 is the identifier for values
	w.writeSingleToc(tocpos, 4, w.r.MetricCount(), valuesoffset)
	tocpos += TocLength

	// strings toc
	if w.Registry().StringCount() > 0 {
		// 5 is the identifier for strings
		w.writeSingleToc(tocpos, 5, w.r.StringCount(), w.r.stringsoffset)
	}
}

func (w *PCPWriter) writeInstanceAndInstanceDomainBlock() {
	for _, indom := range w.r.instanceDomains {
		w.buffer.SetPos(indom.offset)
		w.buffer.WriteUint32(indom.ID())
		w.buffer.WriteInt32(int32(indom.InstanceCount()))
		w.buffer.WriteInt64(int64(indom.instanceOffset))

		so, lo := indom.shortDescription.offset, indom.longDescription.offset
		w.buffer.WriteInt64(int64(so))
		w.buffer.WriteInt64(int64(lo))

		if so != 0 {
			w.buffer.SetPos(so)
			w.buffer.WriteString(indom.shortDescription.val)
		}

		if lo != 0 {
			w.buffer.SetPos(lo)
			w.buffer.WriteString(indom.longDescription.val)
		}

		for _, i := range indom.instances {
			w.buffer.SetPos(i.descoffset)
			w.buffer.WriteInt64(int64(indom.offset))
			w.buffer.WriteInt32(0)
			w.buffer.WriteUint32(i.id)
			w.buffer.WriteString(i.name)
		}
	}
}

func (w *PCPWriter) writeMetricDesc(m PCPMetric, pos int) {
	w.buffer.SetPos(pos)

	w.buffer.WriteString(m.Name())
	w.buffer.SetPos(pos + MaxMetricNameLength + 1)
	w.buffer.WriteUint32(m.ID())
	w.buffer.WriteInt32(int32(m.Type()))
	w.buffer.WriteInt32(int32(m.Semantics()))
	w.buffer.WriteUint32(m.Unit().PMAPI())
	if m.Indom() != nil {
		w.buffer.WriteUint32(m.Indom().ID())
	} else {
		w.buffer.WriteInt32(-1)
	}
	w.buffer.WriteInt32(0)

	so, lo := m.ShortDescription().offset, m.LongDescription().offset
	w.buffer.WriteInt64(int64(so))
	w.buffer.WriteInt64(int64(lo))

	if so != 0 {
		w.buffer.SetPos(so)
		w.buffer.WriteString(m.ShortDescription().val)
	}

	if lo != 0 {
		w.buffer.SetPos(lo)
		w.buffer.WriteString(m.LongDescription().val)
	}
}

func (w *PCPWriter) writeSingletonMetric(m *PCPSingletonMetric) {
	w.writeMetricDesc(m, m.descoffset)

	m.update = newupdateClosure(m.valueoffset, w.buffer, m.t)
	m.update(m.val)

	w.buffer.SetPos(m.valueoffset + MaxDataValueSize)
	w.buffer.WriteInt64(int64(m.descoffset))
	w.buffer.WriteInt64(0)
}

func (w *PCPWriter) writeMetricsAndValuesBlock() {
	for _, metric := range w.r.metrics {
		switch metric.(type) {
		case *PCPSingletonMetric:
			w.writeSingletonMetric(metric.(*PCPSingletonMetric))
		}
	}
}

// fillData will fill the Buffer with the mmv file
// data as long as something doesn't go wrong
func (w *PCPWriter) fillData() error {
	generation2offset, generation := w.writeHeaderBlock()
	w.writeTocBlock()
	w.writeInstanceAndInstanceDomainBlock()
	w.writeMetricsAndValuesBlock()

	w.buffer.SetPos(generation2offset)
	w.buffer.WriteUint64(uint64(generation))

	return nil
}

// Start dumps existing registry data
func (w *PCPWriter) Start() error {
	w.Lock()
	defer w.Unlock()

	l := w.Length()

	w.initializeOffsets()

	buffer, err := bytebuffer.NewMemoryMappedBuffer(w.loc, l)
	if err != nil {
		return err
	}
	w.buffer = buffer

	w.fillData()

	w.r.mapped = true

	return nil
}

// Stop removes existing mapping and cleans up
func (w *PCPWriter) Stop() error {
	w.Lock()
	defer w.Unlock()

	w.r.mapped = false

	err := w.buffer.(*bytebuffer.MemoryMappedBuffer).Unmap(EraseFileOnStop)
	w.buffer = nil
	if err != nil {
		return err
	}

	return nil
}

// Register is simply a shorthand for Registry().AddMetric
func (w *PCPWriter) Register(m Metric) error { return w.Registry().AddMetric(m) }

// RegisterIndom is simply a shorthand for Registry().AddInstanceDomain
func (w *PCPWriter) RegisterIndom(indom InstanceDomain) error {
	return w.Registry().AddInstanceDomain(indom)
}

// RegisterString is simply a shorthand for Registry().AddMetricByString
func (w *PCPWriter) RegisterString(str string, val interface{}, s MetricSemantics, t MetricType, u MetricUnit) error {
	_, err := w.Registry().AddSingletonMetricByString(str, val, s, t, u)
	return err
}
<commit_msg>writer: add support for writing instance metrics<commit_after>package speed

import (
	"errors"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/performancecopilot/speed/bytebuffer"
)

// byte lengths of different components in an mmv file
const (
	HeaderLength         = 40
	TocLength            = 16
	MetricLength         = 104
	ValueLength          = 32
	InstanceLength       = 80
	InstanceDomainLength = 32
	StringBlockLength    = 256
)

// MaxMetricNameLength is the maximum length for a metric name
const MaxMetricNameLength = 63

// MaxDataValueSize is the maximum byte length for a stored metric value, unless it is a string
const MaxDataValueSize = 16

// EraseFileOnStop if set to true, will also delete the memory mapped file
var EraseFileOnStop = false

// Writer defines the interface of a MMV file writer's properties
type Writer interface {
	// a writer must contain a registry of metrics and instance domains
	Registry() Registry

	// writes an mmv file
	Start() error

	// stops writing and cleans up
	Stop() error

	// returns the number of bytes that will be written by the current writer
	Length() int

	// adds a metric to be written
	Register(Metric) error

	// adds an instance domain to be written
	RegisterIndom(InstanceDomain) error

	// adds metric and instance domain from a string
	RegisterString(string, interface{}, MetricSemantics, MetricType, MetricUnit) error
}

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

func mmvFileLocation(name string) (string, error) {
	if strings.ContainsRune(name, os.PathSeparator) {
		return "", errors.New("name cannot have path separator")
	}

	tdir, present := config["PCP_TMP_DIR"]
	var loc string
	if present {
		loc = path.Join(rootPath, tdir)
	} else {
		loc = os.TempDir()
	}

	return path.Join(loc, "mmv", name), nil
}

// PCPClusterIDBitLength is the bit length of the cluster id
// for a set of PCP metrics
const PCPClusterIDBitLength = 12

// MMVFlag represents an enumerated type to represent mmv flag values
type MMVFlag int

// values for MMVFlag
const (
	NoPrefixFlag MMVFlag = 1 << iota
	ProcessFlag
	SentinelFlag
)

//go:generate stringer -type=MMVFlag

// PCPWriter implements a writer that can write PCP compatible MMV files
type PCPWriter struct {
	sync.Mutex
	loc       string            // absolute location of the mmv file
	clusterID uint32            // cluster identifier for the writer
	flag      MMVFlag           // write flag
	r         *PCPRegistry      // current registry
	buffer    bytebuffer.Buffer // current Buffer
}

// NewPCPWriter initializes a new PCPWriter object
func NewPCPWriter(name string, flag MMVFlag) (*PCPWriter, error) {
	fileLocation, err := mmvFileLocation(name)
	if err != nil {
		return nil, err
	}

	return &PCPWriter{
		loc:       fileLocation,
		r:         NewPCPRegistry(),
		clusterID: getHash(name, PCPClusterIDBitLength),
		flag:      flag,
		buffer:    nil,
	}, nil
}

// Registry returns a writer's registry
func (w *PCPWriter) Registry() Registry {
	return w.r
}

func (w *PCPWriter) tocCount() int {
	ans := 2

	if w.Registry().InstanceCount() > 0 {
		ans += 2
	}

	if w.Registry().StringCount() > 0 {
		ans++
	}

	return ans
}

// Length returns the byte length of data in the mmv file written by the current writer
func (w *PCPWriter) Length() int {
	return HeaderLength +
		(w.tocCount() * TocLength) +
		(w.Registry().InstanceCount() * InstanceLength) +
		(w.Registry().InstanceDomainCount() * InstanceDomainLength) +
		(w.Registry().MetricCount() * MetricLength) +
		(w.Registry().ValuesCount() * ValueLength) +
		(w.Registry().StringCount() * StringBlockLength)
}

func (w *PCPWriter) initializeOffsets() {
	indomoffset := HeaderLength + TocLength*w.tocCount()
	instanceoffset := indomoffset + InstanceDomainLength*w.r.InstanceDomainCount()
	metricsoffset := instanceoffset + InstanceLength*w.r.InstanceCount()
	valuesoffset := metricsoffset + MetricLength*w.r.MetricCount()
	stringsoffset := valuesoffset + ValueLength*w.r.ValuesCount()

	w.r.indomoffset = indomoffset
	w.r.instanceoffset = instanceoffset
	w.r.metricsoffset = metricsoffset
	w.r.valuesoffset = valuesoffset
	w.r.stringsoffset = stringsoffset

	initializeSingletonMetricOffsets := func(metric *PCPSingletonMetric) {
		metric.descoffset = metricsoffset
		metricsoffset += MetricLength
		metric.valueoffset = valuesoffset
		valuesoffset += ValueLength

		if metric.shortDescription.val != "" {
			metric.shortDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}

		if metric.longDescription.val != "" {
			metric.longDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}
	}

	initializeInstanceMetricOffsets := func(metric *PCPInstanceMetric) {
		metric.descoffset = metricsoffset
		metricsoffset += MetricLength

		for name := range metric.indom.instances {
			metric.vals[name].offset = valuesoffset
			valuesoffset += ValueLength
		}

		if metric.shortDescription.val != "" {
			metric.shortDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}

		if metric.longDescription.val != "" {
			metric.longDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}
	}

	for _, indom := range w.r.instanceDomains {
		indom.offset = indomoffset
		indom.instanceOffset = instanceoffset
		indomoffset += InstanceDomainLength

		for _, i := range indom.instances {
			i.offset = instanceoffset
			instanceoffset += InstanceLength
		}

		if indom.shortDescription.val != "" {
			indom.shortDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}

		if indom.longDescription.val != "" {
			indom.longDescription.offset = stringsoffset
			stringsoffset += StringBlockLength
		}
	}

	for _, metric := range w.r.metrics {
		switch metric.(type) {
		case *PCPSingletonMetric:
			initializeSingletonMetricOffsets(metric.(*PCPSingletonMetric))
		case *PCPInstanceMetric:
			initializeInstanceMetricOffsets(metric.(*PCPInstanceMetric))
		}
	}
}

func (w *PCPWriter) writeHeaderBlock() (generation2offset int, generation int64) {
	// tag
	w.buffer.WriteString("MMV")
	w.buffer.SetPos(w.buffer.Pos() + 1) // extra null byte is needed and \0 isn't a valid escape character in go

	// version
	w.buffer.WriteUint32(1)

	// generation
	generation = time.Now().Unix()
	w.buffer.WriteInt64(generation)

	generation2offset = w.buffer.Pos()

	w.buffer.WriteInt64(0)

	// tocCount
	w.buffer.WriteInt32(int32(w.tocCount()))

	// flag mask
	w.buffer.WriteInt32(int32(w.flag))

	// process identifier
	w.buffer.WriteInt32(int32(os.Getpid()))

	// cluster identifier
	w.buffer.WriteUint32(w.clusterID)

	return
}

func (w *PCPWriter) writeSingleToc(pos, identifier, count, offset int) {
	w.buffer.SetPos(pos)
	w.buffer.WriteInt32(int32(identifier))
	w.buffer.WriteInt32(int32(count))
	w.buffer.WriteUint64(uint64(offset))
}

func (w *PCPWriter) writeTocBlock() {
	tocpos := HeaderLength

	// instance domains toc
	if w.Registry().InstanceDomainCount() > 0 {
		// 1 is the identifier for instance domains
		w.writeSingleToc(tocpos, 1, w.r.InstanceDomainCount(), w.r.indomoffset)
		tocpos += TocLength
	}

	// instances toc
	if w.Registry().InstanceCount() > 0 {
		// 2 is the identifier for instances
		w.writeSingleToc(tocpos, 2, w.r.InstanceCount(), w.r.instanceoffset)
		tocpos += TocLength
	}

	// metrics and values toc
	metricsoffset, valuesoffset := w.r.metricsoffset, w.r.valuesoffset
	if w.Registry().MetricCount() == 0 {
		metricsoffset, valuesoffset = 0, 0
	}

	// 3 is the identifier for metrics
	w.writeSingleToc(tocpos, 3, w.r.MetricCount(), metricsoffset)
	tocpos += TocLength

	// 4 is the identifier for values
	w.writeSingleToc(tocpos, 4, w.r.ValuesCount(), valuesoffset)
	tocpos += TocLength

	// strings toc
	if w.Registry().StringCount() > 0 {
		// 5 is the identifier for strings
		w.writeSingleToc(tocpos, 5, w.r.StringCount(), w.r.stringsoffset)
	}
}

func (w *PCPWriter) writeInstanceAndInstanceDomainBlock() {
	for _, indom := range w.r.instanceDomains {
		w.buffer.SetPos(indom.offset)
		w.buffer.WriteUint32(indom.ID())
		w.buffer.WriteInt32(int32(indom.InstanceCount()))
		w.buffer.WriteInt64(int64(indom.instanceOffset))

		so, lo := indom.shortDescription.offset, indom.longDescription.offset
		w.buffer.WriteInt64(int64(so))
		w.buffer.WriteInt64(int64(lo))

		if so != 0 {
			w.buffer.SetPos(so)
			w.buffer.WriteString(indom.shortDescription.val)
		}

		if lo != 0 {
			w.buffer.SetPos(lo)
			w.buffer.WriteString(indom.longDescription.val)
		}

		for _, i := range indom.instances {
			w.buffer.SetPos(i.offset)
			w.buffer.WriteInt64(int64(indom.offset))
			w.buffer.WriteInt32(0)
			w.buffer.WriteUint32(i.id)
			w.buffer.WriteString(i.name)
		}
	}
}

func (w *PCPWriter) writeMetricDesc(m PCPMetric, pos int) {
	w.buffer.SetPos(pos)

	w.buffer.WriteString(m.Name())
	w.buffer.SetPos(pos + MaxMetricNameLength + 1)
	w.buffer.WriteUint32(m.ID())
	w.buffer.WriteInt32(int32(m.Type()))
	w.buffer.WriteInt32(int32(m.Semantics()))
	w.buffer.WriteUint32(m.Unit().PMAPI())
	if m.Indom() != nil {
		w.buffer.WriteUint32(m.Indom().ID())
	} else {
		w.buffer.WriteInt32(-1)
	}
	w.buffer.WriteInt32(0)

	so, lo := m.ShortDescription().offset, m.LongDescription().offset
	w.buffer.WriteInt64(int64(so))
	w.buffer.WriteInt64(int64(lo))

	if so != 0 {
		w.buffer.SetPos(so)
		w.buffer.WriteString(m.ShortDescription().val)
	}

	if lo != 0 {
		w.buffer.SetPos(lo)
		w.buffer.WriteString(m.LongDescription().val)
	}
}

func (w *PCPWriter) writeSingletonMetric(m *PCPSingletonMetric) {
	w.writeMetricDesc(m, m.descoffset)

	m.update = newupdateClosure(m.valueoffset, w.buffer, m.t)
	m.update(m.val)

	w.buffer.SetPos(m.valueoffset + MaxDataValueSize)
	w.buffer.WriteInt64(int64(m.descoffset))
	w.buffer.WriteInt64(0)
}

func (w *PCPWriter) writeInstanceMetric(m *PCPInstanceMetric) {
	w.writeMetricDesc(m, m.descoffset)

	for name, i := range m.indom.instances {
		ival := m.vals[name]

		ival.update = newupdateClosure(ival.offset, w.buffer, m.t)
		ival.update(ival.val)

		w.buffer.SetPos(ival.offset + MaxDataValueSize)
		w.buffer.WriteInt64(int64(m.descoffset))
		w.buffer.WriteInt64(int64(i.offset))
	}
}

func (w *PCPWriter) writeMetricsAndValuesBlock() {
	for _, metric := range w.r.metrics {
		switch metric.(type) {
		case *PCPSingletonMetric:
			w.writeSingletonMetric(metric.(*PCPSingletonMetric))
		case *PCPInstanceMetric:
			w.writeInstanceMetric(metric.(*PCPInstanceMetric))
		}
	}
}

// fillData will fill the Buffer with the mmv file
// data as long as something doesn't go wrong
func (w *PCPWriter) fillData() error {
	generation2offset, generation := w.writeHeaderBlock()
	w.writeTocBlock()
	w.writeInstanceAndInstanceDomainBlock()
	w.writeMetricsAndValuesBlock()

	w.buffer.SetPos(generation2offset)
	w.buffer.WriteUint64(uint64(generation))

	return nil
}

// Start dumps existing registry data
func (w *PCPWriter) Start() error {
	w.Lock()
	defer w.Unlock()

	l := w.Length()

	w.initializeOffsets()

	buffer, err := bytebuffer.NewMemoryMappedBuffer(w.loc, l)
	if err != nil {
		return err
	}
	w.buffer = buffer

	w.fillData()

	w.r.mapped = true

	return nil
}

// Stop removes existing mapping and cleans up
func (w *PCPWriter) Stop() error {
	w.Lock()
	defer w.Unlock()

	w.r.mapped = false

	err := w.buffer.(*bytebuffer.MemoryMappedBuffer).Unmap(EraseFileOnStop)
	w.buffer = nil
	if err != nil {
		return err
	}

	return nil
}

// Register is simply a shorthand for Registry().AddMetric
func (w *PCPWriter) Register(m Metric) error { return w.Registry().AddMetric(m) }

// RegisterIndom is simply a shorthand for Registry().AddInstanceDomain
func (w *PCPWriter) RegisterIndom(indom InstanceDomain) error {
	return w.Registry().AddInstanceDomain(indom)
}

// RegisterString is simply a shorthand for Registry().AddMetricByString
func (w *PCPWriter) RegisterString(str string, val interface{}, s MetricSemantics, t MetricType, u MetricUnit) error {
	_, err := w.Registry().AddSingletonMetricByString(str, val, s, t, u)
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"time"
)

// Spooler accepts items on the In channel and chunks them into items on the
// Out channel.
type Spooler struct {
	In  chan *FileData
	Out chan []*FileData

	size    int
	timeout time.Duration
}

const (
	// The number of items that can be buffered in the Out channel.
	spoolOutBuffer = 16
)

func NewSpooler(size int, timeout time.Duration) *Spooler {
	return &Spooler{
		In:      make(chan *FileData, size*10),
		Out:     make(chan []*FileData, spoolOutBuffer),
		size:    size,
		timeout: timeout,
	}
}

// Spool accepts items from the In channel and spools them into the Out
// channel. To stop the spooling, close the In channel.
func (s *Spooler) Spool() {
	timer := time.NewTimer(s.timeout)
	currentChunk := make([]*FileData, 0, s.size)
	for {
		select {
		case fileData, ok := <-s.In:
			if ok {
				currentChunk = append(currentChunk, fileData)
				if len(currentChunk) >= s.size {
					s.sendChunk(currentChunk)
					currentChunk = make([]*FileData, 0, s.size)
				}
			} else {
				return
			}
		case <-timer.C:
			if len(currentChunk) > 0 {
				s.sendChunk(currentChunk)
				currentChunk = make([]*FileData, 0, s.size)
			}
		}

		timer.Reset(s.timeout)
	}
}

func (s *Spooler) sendChunk(chunk []*FileData) {
	if len(chunk) > 0 {
		s.Out <- chunk
	}
}
<commit_msg>Never block on adding a small chunk to the spool Out channel<commit_after>package main

import (
	"time"
)

// Spooler accepts items on the In channel and chunks them into items on the
// Out channel.
type Spooler struct {
	In  chan *FileData
	Out chan []*FileData

	size    int
	timeout time.Duration
}

const (
	// The number of items that can be buffered in the Out channel.
	spoolOutBuffer = 4
)

func NewSpooler(size int, timeout time.Duration) *Spooler {
	return &Spooler{
		In:      make(chan *FileData, size*spoolOutBuffer),
		Out:     make(chan []*FileData, spoolOutBuffer),
		size:    size,
		timeout: timeout,
	}
}

// Spool accepts items from the In channel and spools them into the Out
// channel. To stop the spooling, close the In channel.
func (s *Spooler) Spool() {
	timer := time.NewTimer(s.timeout)
	currentChunk := make([]*FileData, 0, s.size)
	for {
		select {
		case fileData, ok := <-s.In:
			if ok {
				currentChunk = append(currentChunk, fileData)
				if len(currentChunk) >= s.size {
					s.Out <- currentChunk
					currentChunk = make([]*FileData, 0, s.size)
				}
			} else {
				return
			}
		case <-timer.C:
			if len(currentChunk) > 0 {
				select {
				case s.Out <- currentChunk:
					currentChunk = make([]*FileData, 0, s.size)
				default:
					// Never block trying to send to the channel because of a timer
					// firing.  Otherwise, small chunks may be added to the channel. If
					// we can't send immediately, we might as well keep spooling to build
					// up a bigger chunk.
				}
			}
		}

		timer.Reset(s.timeout)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

type Spotify struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	ExpiresIn    uint           `json:"expires_in"`
	RefreshToken string         `json:"refresh_token"`
	Auth         SpotifyAuth    `json:"auth"`
	Profile      SpotifyProfile `json:"profile"`
}

type SpotifyProfile struct {
	ExternalUrls map[string]string `json:"external_urls"`
	Href         string            `json:"href"`
	Id           string            `json:"id"`
	Type         string            `json:"type"`
	Uri          string            `json:"uri"`
}

type Playlist struct {
	Id     string         `json:"id"`
	Name   string         `json:"name"`
	Tracks PlaylistTracks `json:"tracks"`
}

type PlaylistTracks struct {
	Limit    int             `json:"limit"`
	Next     string          `json:"next"`
	Offset   int             `json:"offset"`
	Previous string          `json:"previous"`
	Total    int             `json:"total"`
	Items    []PlaylistTrack `json:"items"`
}

type PlaylistTrack struct {
	Track Track `json:"track"`
}

type Playlists struct {
	Items []Playlist `json:"items"`
}

type NewPlaylist struct {
	Name   string `json:"name"`
	Public bool   `json:"public"`
}

type SearchResult struct {
	Tracks SearchTracks `json:"tracks"`
}

type SearchTracks struct {
	Items []Track `json:"items"`
}

type Track struct {
	Id   string `json:"id"`
	Name string `json:"name"`
	Uri  string `json:"uri"`
}

func (playlist *Playlist) Contains(track Track) bool {
	for _, item := range playlist.Tracks.Items {
		if item.Track.Id == track.Id {
			return true
		}
	}
	return false
}

func (playlist *Playlist) String() string {
	return fmt.Sprintf("%s (%s) [%d songs]", playlist.Name, playlist.Id,
		len(playlist.Tracks.Items))
}

func (spotify *Spotify) update(newToken *Spotify) {
	spotify.AccessToken = newToken.AccessToken
	spotify.TokenType = newToken.TokenType
	spotify.ExpiresIn = newToken.ExpiresIn
}

func (spotify *Spotify) updateToken() error {
	formData := url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {spotify.RefreshToken},
	}
	url := "https://accounts.spotify.com/api/token"
	client := &http.Client{}
	req, err := http.NewRequest("POST", url,
		bytes.NewBufferString(formData.Encode()))
	req.Header.Set("Authorization", spotify.Auth.authHeader())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var newToken Spotify
	if err := json.Unmarshal(body, &newToken); err != nil {
		return err
	}
	spotify.update(&newToken)
	return nil
}

func (spotify *Spotify) authHeader() string {
	return spotify.TokenType + " " + spotify.AccessToken
}

type requestFn func() (*http.Response, error)

func (spotify *Spotify) refreshToken(resp *http.Response, err error,
	reqFn requestFn) (*http.Response, error) {
	if resp.StatusCode == 401 {
		if err := spotify.updateToken(); err != nil {
			return nil, err
		}
		if err := spotify.Save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		return reqFn()
	}
	return resp, err
}

func (spotify *Spotify) get(url string) ([]byte, error) {
	getFn := func() (*http.Response, error) {
		client := &http.Client{}
		req, err := http.NewRequest("GET", url, nil)
		req.Header.Set("Authorization", spotify.authHeader())
		if err != nil {
			return nil, err
		}
		return client.Do(req)
	}
	resp, err := getFn()
	if err != nil {
		return nil, err
	}
	resp, err = spotify.refreshToken(resp, err, getFn)
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("Request failed [%d]: %s",
			resp.StatusCode, body)
	}
	return body, err
}

func (spotify *Spotify) post(url string, body []byte) ([]byte, error) {
	postFn := func() (*http.Response, error) {
		client := &http.Client{}
		req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
		req.Header.Set("Authorization", spotify.authHeader())
		req.Header.Set("Content-Type", "application/json")
		if err != nil {
			return nil, err
		}
		return client.Do(req)
	}
	resp, err := postFn()
	if err != nil {
		return nil, err
	}
	resp, err = spotify.refreshToken(resp, err, postFn)
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("Request failed [%d]: %s",
			resp.StatusCode, data)
	}
	return data, err
}

func (spotify *Spotify) Save(filepath string) error {
	json, err := json.Marshal(spotify)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filepath, json, 0600)
	if err != nil {
		return err
	}
	return nil
}

func ReadToken(filepath string) (*Spotify, error) {
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	var spotify Spotify
	if err := json.Unmarshal(data, &spotify); err != nil {
		return nil, err
	}
	return &spotify, nil
}

func (spotify *Spotify) CurrentUser() (*SpotifyProfile, error) {
	url := "https://api.spotify.com/v1/me"
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var profile SpotifyProfile
	if err := json.Unmarshal(body, &profile); err != nil {
		return nil, err
	}
	return &profile, nil
}

func (spotify *Spotify) Playlists() ([]Playlist, error) {
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlists Playlists
	if err := json.Unmarshal(body, &playlists); err != nil {
		return nil, err
	}
	return playlists.Items, nil
}

func (spotify *Spotify) PlaylistById(playlistId string) (*Playlist, error) {
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists/%s",
		spotify.Profile.Id, playlistId)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, nil
}

func (spotify *Spotify) Playlist(name string) (*Playlist, error) {
	playlists, err := spotify.Playlists()
	if err != nil {
		return nil, err
	}
	playlistId := ""
	for _, playlist := range playlists {
		if playlist.Name == name {
			playlistId = playlist.Id
			break
		}
	}
	if playlistId == "" {
		return nil, nil
	}
	return spotify.PlaylistById(playlistId)
}

func (spotify *Spotify) GetOrCreatePlaylist(name string) (*Playlist, error) {
	existing, err := spotify.Playlist(name)
	if err != nil {
		return nil, err
	}
	if existing != nil {
		return existing, nil
	}
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	newPlaylist, err := json.Marshal(NewPlaylist{
		Name:   name,
		Public: false,
	})
	if err != nil {
		return nil, err
	}
	body, err := spotify.post(url, newPlaylist)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, err
}

func (spotify *Spotify) RecentTracks(playlist *Playlist) (
	[]PlaylistTrack, error) {
	u := fmt.Sprintf(
		"https://api.spotify.com/v1/users/%s/playlists/%s/tracks",
		spotify.Profile.Id, playlist.Id)

	getTracks := func(url string) (*PlaylistTracks, error) {
		body, err := spotify.get(url)
		if err != nil {
			return nil, err
		}
		var playlistTracks PlaylistTracks
		if err := json.Unmarshal(body, &playlistTracks); err != nil {
			return nil, err
		}
		return &playlistTracks, nil
	}
	playlistTracks, err := getTracks(u)
	if err != nil {
		return nil, err
	}
	// If more than 100 tracks are returned, get the 100 last ones
	if playlistTracks.Total > 100 {
		offset := playlistTracks.Total - 100
		params := url.Values{"offset": {strconv.Itoa(offset)}}
		offsetUrl := fmt.Sprintf("%s?%s", u, params.Encode())
		playlistTracks, err = getTracks(offsetUrl)
		if err != nil {
			return nil, err
		}
	}
	return playlistTracks.Items, nil
}

func (spotify *Spotify) Search(query string, types string, limit uint) ([]Track,
	error) {
	params := url.Values{
		"q":     {query},
		"type":  {types},
		"limit": {strconv.Itoa(int(limit))},
	}
	url := "https://api.spotify.com/v1/search?" + params.Encode()
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var result SearchResult
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}
	return result.Tracks.Items, nil
}

func (spotify *Spotify) SearchArtistTrack(artist string, track string) ([]Track,
	error) {
	query := fmt.Sprintf("artist:%s track:%s", artist, track)
	tracks, err := spotify.Search(query, "track", 1)
	if err != nil {
		return nil, err
	}
	return tracks, nil
}

func (spotify *Spotify) AddTracks(playlist *Playlist, tracks []Track) error {
	url := fmt.Sprintf(
		"https://api.spotify.com/v1/users/%s/playlists/%s/tracks",
		spotify.Profile.Id, playlist.Id)

	uris := make([]string, len(tracks))
	for idx, track := range tracks {
		uris[idx] = track.Uri
	}
	jsonUris, err := json.Marshal(uris)
	if err != nil {
		return err
	}
	if _, err := spotify.post(url, jsonUris); err != nil {
		return err
	}
	return nil

}

func (spotify *Spotify) AddTrack(playlist *Playlist, track *Track) error {
	return spotify.AddTracks(playlist, []Track{*track})
}

func (track *Track) String() string {
	return fmt.Sprintf("%s (%s)", track.Name, track.Id)
}

func (spotify *Spotify) SetCurrentUser() error {
	profile, err := spotify.CurrentUser()
	if err != nil {
		return err
	}
	spotify.Profile = *profile
	if err := spotify.Save(spotify.Auth.TokenFile); err != nil {
		return err
	}
	return nil
}
<commit_msg>Show correct playlist total<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

type Spotify struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	ExpiresIn    uint           `json:"expires_in"`
	RefreshToken string         `json:"refresh_token"`
	Auth         SpotifyAuth    `json:"auth"`
	Profile      SpotifyProfile `json:"profile"`
}

type SpotifyProfile struct {
	ExternalUrls map[string]string `json:"external_urls"`
	Href         string            `json:"href"`
	Id           string            `json:"id"`
	Type         string            `json:"type"`
	Uri          string            `json:"uri"`
}

type Playlist struct {
	Id     string         `json:"id"`
	Name   string         `json:"name"`
	Tracks PlaylistTracks `json:"tracks"`
}

type PlaylistTracks struct {
	Limit    int             `json:"limit"`
	Next     string          `json:"next"`
	Offset   int             `json:"offset"`
	Previous string          `json:"previous"`
	Total    int             `json:"total"`
	Items    []PlaylistTrack `json:"items"`
}

type PlaylistTrack struct {
	Track Track `json:"track"`
}

type Playlists struct {
	Items []Playlist `json:"items"`
}

type NewPlaylist struct {
	Name   string `json:"name"`
	Public bool   `json:"public"`
}

type SearchResult struct {
	Tracks SearchTracks `json:"tracks"`
}

type SearchTracks struct {
	Items []Track `json:"items"`
}

type Track struct {
	Id   string `json:"id"`
	Name string `json:"name"`
	Uri  string `json:"uri"`
}

func (playlist *Playlist) Contains(track Track) bool {
	for _, item := range playlist.Tracks.Items {
		if item.Track.Id == track.Id {
			return true
		}
	}
	return false
}

func (playlist *Playlist) String() string {
	return fmt.Sprintf("%s (%s) [%d songs]", playlist.Name, playlist.Id,
		playlist.Tracks.Total)
}

func (spotify *Spotify) update(newToken *Spotify) {
	spotify.AccessToken = newToken.AccessToken
	spotify.TokenType = newToken.TokenType
	spotify.ExpiresIn = newToken.ExpiresIn
}

func (spotify *Spotify) updateToken() error {
	formData := url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {spotify.RefreshToken},
	}
	url := "https://accounts.spotify.com/api/token"
	client := &http.Client{}
	req, err := http.NewRequest("POST", url,
		bytes.NewBufferString(formData.Encode()))
	req.Header.Set("Authorization", spotify.Auth.authHeader())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var newToken Spotify
	if err := json.Unmarshal(body, &newToken); err != nil {
		return err
	}
	spotify.update(&newToken)
	return nil
}

func (spotify *Spotify) authHeader() string {
	return spotify.TokenType + " " + spotify.AccessToken
}

type requestFn func() (*http.Response, error)

func (spotify *Spotify) refreshToken(resp *http.Response, err error,
	reqFn requestFn) (*http.Response, error) {
	if resp.StatusCode == 401 {
		if err := spotify.updateToken(); err != nil {
			return nil, err
		}
		if err := spotify.Save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		return reqFn()
	}
	return resp, err
}

func (spotify *Spotify) get(url string) ([]byte, error) {
	getFn := func() (*http.Response, error) {
		client := &http.Client{}
		req, err := http.NewRequest("GET", url, nil)
		req.Header.Set("Authorization", spotify.authHeader())
		if err != nil {
			return nil, err
		}
		return client.Do(req)
	}
	resp, err := getFn()
	if err != nil {
		return nil, err
	}
	resp, err = spotify.refreshToken(resp, err, getFn)
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("Request failed [%d]: %s",
			resp.StatusCode, body)
	}
	return body, err
}

func (spotify *Spotify) post(url string, body []byte) ([]byte, error) {
	postFn := func() (*http.Response, error) {
		client := &http.Client{}
		req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
		req.Header.Set("Authorization", spotify.authHeader())
		req.Header.Set("Content-Type", "application/json")
		if err != nil {
			return nil, err
		}
		return client.Do(req)
	}
	resp, err := postFn()
	if err != nil {
		return nil, err
	}
	resp, err = spotify.refreshToken(resp, err, postFn)
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("Request failed [%d]: %s",
			resp.StatusCode, data)
	}
	return data, err
}

func (spotify *Spotify) Save(filepath string) error {
	json, err := json.Marshal(spotify)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filepath, json, 0600)
	if err != nil {
		return err
	}
	return nil
}

func ReadToken(filepath string) (*Spotify, error) {
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	var spotify Spotify
	if err := json.Unmarshal(data, &spotify); err != nil {
		return nil, err
	}
	return &spotify, nil
}

func (spotify *Spotify) CurrentUser() (*SpotifyProfile, error) {
	url := "https://api.spotify.com/v1/me"
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var profile SpotifyProfile
	if err := json.Unmarshal(body, &profile); err != nil {
		return nil, err
	}
	return &profile, nil
}

func (spotify *Spotify) Playlists() ([]Playlist, error) {
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlists Playlists
	if err := json.Unmarshal(body, &playlists); err != nil {
		return nil, err
	}
	return playlists.Items, nil
}

func (spotify *Spotify) PlaylistById(playlistId string) (*Playlist, error) {
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists/%s",
		spotify.Profile.Id, playlistId)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, nil
}

func (spotify *Spotify) Playlist(name string) (*Playlist, error) {
	playlists, err := spotify.Playlists()
	if err != nil {
		return nil, err
	}
	playlistId := ""
	for _, playlist := range playlists {
		if playlist.Name == name {
			playlistId = playlist.Id
			break
		}
	}
	if playlistId == "" {
		return nil, nil
	}
	return spotify.PlaylistById(playlistId)
}

func (spotify *Spotify) GetOrCreatePlaylist(name string) (*Playlist, error) {
	existing, err := spotify.Playlist(name)
	if err != nil {
		return nil, err
	}
	if existing != nil {
		return existing, nil
	}
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	newPlaylist, err := json.Marshal(NewPlaylist{
		Name:   name,
		Public: false,
	})
	if err != nil {
		return nil, err
	}
	body, err := spotify.post(url, newPlaylist)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, err
}

func (spotify *Spotify) RecentTracks(playlist *Playlist) (
	[]PlaylistTrack, error) {
	u := fmt.Sprintf(
		"https://api.spotify.com/v1/users/%s/playlists/%s/tracks",
		spotify.Profile.Id, playlist.Id)

	getTracks := func(url string) (*PlaylistTracks, error) {
		body, err := spotify.get(url)
		if err != nil {
			return nil, err
		}
		var playlistTracks PlaylistTracks
		if err := json.Unmarshal(body, &playlistTracks); err != nil {
			return nil, err
		}
		return &playlistTracks, nil
	}
	playlistTracks, err := getTracks(u)
	if err != nil {
		return nil, err
	}
	// If more than 100 tracks are returned, get the 100 last ones
	if playlistTracks.Total > 100 {
		offset := playlistTracks.Total - 100
		params := url.Values{"offset": {strconv.Itoa(offset)}}
		offsetUrl := fmt.Sprintf("%s?%s", u, params.Encode())
		playlistTracks, err = getTracks(offsetUrl)
		if err != nil {
			return nil, err
		}
	}
	return playlistTracks.Items, nil
}

func (spotify *Spotify) Search(query string, types string, limit uint) ([]Track,
	error) {
	params := url.Values{
		"q":     {query},
		"type":  {types},
		"limit": {strconv.Itoa(int(limit))},
	}
	url := "https://api.spotify.com/v1/search?" + params.Encode()
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var result SearchResult
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}
	return result.Tracks.Items, nil
}

func (spotify *Spotify) SearchArtistTrack(artist string, track string) ([]Track,
	error) {
	query := fmt.Sprintf("artist:%s track:%s", artist, track)
	tracks, err := spotify.Search(query, "track", 1)
	if err != nil {
		return nil, err
	}
	return tracks, nil
}

func (spotify *Spotify) AddTracks(playlist *Playlist, tracks []Track) error {
	url := fmt.Sprintf(
		"https://api.spotify.com/v1/users/%s/playlists/%s/tracks",
		spotify.Profile.Id, playlist.Id)

	uris := make([]string, len(tracks))
	for idx, track := range tracks {
		uris[idx] = track.Uri
	}
	jsonUris, err := json.Marshal(uris)
	if err != nil {
		return err
	}
	if _, err := spotify.post(url, jsonUris); err != nil {
		return err
	}
	return nil

}

func (spotify *Spotify) AddTrack(playlist *Playlist, track *Track) error {
	return spotify.AddTracks(playlist, []Track{*track})
}

func (track *Track) String() string {
	return fmt.Sprintf("%s (%s)", track.Name, track.Id)
}

func (spotify *Spotify) SetCurrentUser() error {
	profile, err := spotify.CurrentUser()
	if err != nil {
		return err
	}
	spotify.Profile = *profile
	if err := spotify.Save(spotify.Auth.TokenFile); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

type Spotify struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	ExpiresIn    uint           `json:"expires_in"`
	RefreshToken string         `json:"refresh_token"`
	Auth         SpotifyAuth    `json:"auth"`
	Profile      SpotifyProfile `json:"profile"`
}

type SpotifyProfile struct {
	ExternalUrls map[string]string `json:"external_urls"`
	Href         string            `json:"href"`
	Id           string            `json:"id"`
	Type         string            `json:"type"`
	Uri          string            `json:"uri"`
}

type Playlist struct {
	Id     string         `json:"id"`
	Name   string         `json:"name"`
	Tracks PlaylistTracks `json:"tracks"`
}

type PlaylistTracks struct {
	Items []PlaylistTrack `json:"items"`
}

type PlaylistTrack struct {
	Track Track `json:"track"`
}

type Playlists struct {
	Items []Playlist `json:"items"`
}

type NewPlaylist struct {
	Name   string `json:"name"`
	Public bool   `json:"public"`
}

type SearchResult struct {
	Tracks Tracks `json:"tracks"`
}

type Tracks struct {
	Items []Track `json:"items"`
}

type Track struct {
	Id   string `json:"id"`
	Name string `json:"name"`
	Uri  string `json:"uri"`
}

func (playlist *Playlist) contains(track Track) bool {
	for _, item := range playlist.Tracks.Items {
		if item.Track.Id == track.Id {
			return true
		}
	}
	return false
}

func (spotify *Spotify) update(newToken *Spotify) {
	spotify.AccessToken = newToken.AccessToken
	spotify.TokenType = newToken.TokenType
	spotify.ExpiresIn = newToken.ExpiresIn
}

func (spotify *Spotify) refreshToken() error {
	formData := url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {spotify.RefreshToken},
	}
	url := "https://accounts.spotify.com/api/token"
	client := &http.Client{}
	req, err := http.NewRequest("POST", url,
		bytes.NewBufferString(formData.Encode()))
	req.Header.Set("Authorization", spotify.Auth.authHeader())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var newToken Spotify
	if err := json.Unmarshal(body, &newToken); err != nil {
		return err
	}
	spotify.update(&newToken)
	return nil
}

func (spotify *Spotify) authHeader() string {
	return spotify.TokenType + " " + spotify.AccessToken
}

func (spotify *Spotify) doGet(url string) (*http.Response, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", spotify.authHeader())
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (spotify *Spotify) get(url string) ([]byte, error) {
	resp, err := spotify.doGet(url)
	if err != nil {
		return nil, err
	}
	// Check if we need to refresh token
	if resp.StatusCode == 401 {
		if err := spotify.refreshToken(); err != nil {
			return nil, err
		}
		if err := spotify.save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		resp, err = spotify.doGet(url)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return body, err
}

func (spotify *Spotify) doPost(url string, body []byte) (*http.Response,
	error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	req.Header.Set("Authorization", spotify.authHeader())
	req.Header.Set("Content-Type", "application/json")
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (spotify *Spotify) post(url string, body []byte) ([]byte, error) {
	resp, err := spotify.doPost(url, body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == 401 {
		if err := spotify.refreshToken(); err != nil {
			return nil, err
		}
		if err := spotify.save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		resp, err = spotify.doPost(url, body)
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("Request failed [%d]: %s",
			resp.StatusCode, data)
	}
	return data, err
}

func (spotify *Spotify) save(filepath string) error {
	json, err := json.Marshal(spotify)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filepath, json, 0600)
	if err != nil {
		return err
	}
	return nil
}

func ReadToken(filepath string) (*Spotify, error) {
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	var spotify Spotify
	if err := json.Unmarshal(data, &spotify); err != nil {
		return nil, err
	}
	return &spotify, nil
}

func (spotify *Spotify) currentUser() (*SpotifyProfile, error) {
	url := "https://api.spotify.com/v1/me"
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var profile SpotifyProfile
	if err := json.Unmarshal(body, &profile); err != nil {
		return nil, err
	}
	return &profile, nil
}

func (spotify *Spotify) playlists() ([]Playlist, error) {
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlists Playlists
	if err := json.Unmarshal(body, &playlists); err != nil {
		return nil, err
	}
	return playlists.Items, nil
}

func (spotify *Spotify) playlist(name string) (*Playlist, error) {
	playlists, err := spotify.playlists()
	if err != nil {
		return nil, err
	}
	playlistId := ""
	for _, playlist := range playlists {
		if playlist.Name == name {
			playlistId = playlist.Id
			break
		}
	}
	if playlistId == "" {
		return nil, nil
	}
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists/%s",
		spotify.Profile.Id, playlistId)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, nil
}

func (spotify *Spotify) getOrCreatePlaylist(name string) (*Playlist, error) {
	existing, err := spotify.playlist(name)
	if err != nil {
		return nil, err
	}
	if existing != nil {
		return existing, nil
	}
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	newPlaylist, err := json.Marshal(NewPlaylist{
		Name:   name,
		Public: false,
	})
	if err != nil {
		return nil, err
	}
	body, err := spotify.post(url, newPlaylist)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, err
}

func (spotify *Spotify) search(query string, types string, limit uint) ([]Track,
	error) {
	params := url.Values{
		"q":     {query},
		"type":  {types},
		"limit": {strconv.Itoa(int(limit))},
	}
	url := "https://api.spotify.com/v1/search?" + params.Encode()
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var result SearchResult
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}
	return result.Tracks.Items, nil
}

func (spotify *Spotify) searchArtistTrack(artist string, track string) ([]Track,
	error) {
	query := fmt.Sprintf("artist:%s track:%s", artist, track)
	return spotify.search(query, "track", 1)
}

func (spotify *Spotify) addTracks(playlist *Playlist, tracks []Track) error {
	url := fmt.Sprintf(
		"https://api.spotify.com/v1/users/%s/playlists/%s/tracks",
		spotify.Profile.Id, playlist.Id)

	uris := make([]string, len(tracks))
	for idx, track := range tracks {
		uris[idx] = track.Uri
	}
	jsonUris, err := json.Marshal(uris)
	if err != nil {
		return err
	}
	if _, err := spotify.post(url, jsonUris); err != nil {
		return err
	}
	return nil

}
<commit_msg>searchArtistTrack should return zero or one result<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

type Spotify struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	ExpiresIn    uint           `json:"expires_in"`
	RefreshToken string         `json:"refresh_token"`
	Auth         SpotifyAuth    `json:"auth"`
	Profile      SpotifyProfile `json:"profile"`
}

type SpotifyProfile struct {
	ExternalUrls map[string]string `json:"external_urls"`
	Href         string            `json:"href"`
	Id           string            `json:"id"`
	Type         string            `json:"type"`
	Uri          string            `json:"uri"`
}

type Playlist struct {
	Id     string         `json:"id"`
	Name   string         `json:"name"`
	Tracks PlaylistTracks `json:"tracks"`
}

type PlaylistTracks struct {
	Items []PlaylistTrack `json:"items"`
}

type PlaylistTrack struct {
	Track Track `json:"track"`
}

type Playlists struct {
	Items []Playlist `json:"items"`
}

type NewPlaylist struct {
	Name   string `json:"name"`
	Public bool   `json:"public"`
}

type SearchResult struct {
	Tracks Tracks `json:"tracks"`
}

type Tracks struct {
	Items []Track `json:"items"`
}

type Track struct {
	Id   string `json:"id"`
	Name string `json:"name"`
	Uri  string `json:"uri"`
}

func (playlist *Playlist) contains(track Track) bool {
	for _, item := range playlist.Tracks.Items {
		if item.Track.Id == track.Id {
			return true
		}
	}
	return false
}

func (spotify *Spotify) update(newToken *Spotify) {
	spotify.AccessToken = newToken.AccessToken
	spotify.TokenType = newToken.TokenType
	spotify.ExpiresIn = newToken.ExpiresIn
}

func (spotify *Spotify) refreshToken() error {
	formData := url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {spotify.RefreshToken},
	}
	url := "https://accounts.spotify.com/api/token"
	client := &http.Client{}
	req, err := http.NewRequest("POST", url,
		bytes.NewBufferString(formData.Encode()))
	req.Header.Set("Authorization", spotify.Auth.authHeader())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var newToken Spotify
	if err := json.Unmarshal(body, &newToken); err != nil {
		return err
	}
	spotify.update(&newToken)
	return nil
}

func (spotify *Spotify) authHeader() string {
	return spotify.TokenType + " " + spotify.AccessToken
}

func (spotify *Spotify) doGet(url string) (*http.Response, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", spotify.authHeader())
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (spotify *Spotify) get(url string) ([]byte, error) {
	resp, err := spotify.doGet(url)
	if err != nil {
		return nil, err
	}
	// Check if we need to refresh token
	if resp.StatusCode == 401 {
		if err := spotify.refreshToken(); err != nil {
			return nil, err
		}
		if err := spotify.save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		resp, err = spotify.doGet(url)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return body, err
}

func (spotify *Spotify) doPost(url string, body []byte) (*http.Response,
	error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	req.Header.Set("Authorization", spotify.authHeader())
	req.Header.Set("Content-Type", "application/json")
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (spotify *Spotify) post(url string, body []byte) ([]byte, error) {
	resp, err := spotify.doPost(url, body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == 401 {
		if err := spotify.refreshToken(); err != nil {
			return nil, err
		}
		if err := spotify.save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		resp, err = spotify.doPost(url, body)
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("Request failed [%d]: %s",
			resp.StatusCode, data)
	}
	return data, err
}

func (spotify *Spotify) save(filepath string) error {
	json, err := json.Marshal(spotify)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filepath, json, 0600)
	if err != nil {
		return err
	}
	return nil
}

func ReadToken(filepath string) (*Spotify, error) {
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	var spotify Spotify
	if err := json.Unmarshal(data, &spotify); err != nil {
		return nil, err
	}
	return &spotify, nil
}

func (spotify *Spotify) currentUser() (*SpotifyProfile, error) {
	url := "https://api.spotify.com/v1/me"
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var profile SpotifyProfile
	if err := json.Unmarshal(body, &profile); err != nil {
		return nil, err
	}
	return &profile, nil
}

func (spotify *Spotify) playlists() ([]Playlist, error) {
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlists Playlists
	if err := json.Unmarshal(body, &playlists); err != nil {
		return nil, err
	}
	return playlists.Items, nil
}

func (spotify *Spotify) playlist(name string) (*Playlist, error) {
	playlists, err := spotify.playlists()
	if err != nil {
		return nil, err
	}
	playlistId := ""
	for _, playlist := range playlists {
		if playlist.Name == name {
			playlistId = playlist.Id
			break
		}
	}
	if playlistId == "" {
		return nil, nil
	}
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists/%s",
		spotify.Profile.Id, playlistId)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, nil
}

func (spotify *Spotify) getOrCreatePlaylist(name string) (*Playlist, error) {
	existing, err := spotify.playlist(name)
	if err != nil {
		return nil, err
	}
	if existing != nil {
		return existing, nil
	}
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	newPlaylist, err := json.Marshal(NewPlaylist{
		Name:   name,
		Public: false,
	})
	if err != nil {
		return nil, err
	}
	body, err := spotify.post(url, newPlaylist)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, err
}

func (spotify *Spotify) search(query string, types string, limit uint) ([]Track,
	error) {
	params := url.Values{
		"q":     {query},
		"type":  {types},
		"limit": {strconv.Itoa(int(limit))},
	}
	url := "https://api.spotify.com/v1/search?" + params.Encode()
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var result SearchResult
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}
	return result.Tracks.Items, nil
}

func (spotify *Spotify) searchArtistTrack(artist string, track string) (*Track,
	error) {
	query := fmt.Sprintf("artist:%s track:%s", artist, track)
	tracks, err := spotify.search(query, "track", 1)
	if err != nil {
		return nil, err
	}
	if len(tracks) > 0 {
		return &tracks[0], nil
	}
	return nil, fmt.Errorf("Not found")
}

func (spotify *Spotify) addTracks(playlist *Playlist, tracks []Track) error {
	url := fmt.Sprintf(
		"https://api.spotify.com/v1/users/%s/playlists/%s/tracks",
		spotify.Profile.Id, playlist.Id)

	uris := make([]string, len(tracks))
	for idx, track := range tracks {
		uris[idx] = track.Uri
	}
	jsonUris, err := json.Marshal(uris)
	if err != nil {
		return err
	}
	if _, err := spotify.post(url, jsonUris); err != nil {
		return err
	}
	return nil

}

func (spotify *Spotify) addTrack(playlist *Playlist, track *Track) error {
	return spotify.addTracks(playlist, []Track{*track})
}
<|endoftext|>