<commit_before>// Copyright 2015 Comcast Cable Communications Management, LLC

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Started from https://raw.githubusercontent.com/jordan-wright/gophish

package routes

import (
	"github.com/Comcast/traffic_control/traffic_ops/goto2/api"
	"github.com/Comcast/traffic_control/traffic_ops/goto2/auth"
	"github.com/Comcast/traffic_control/traffic_ops/goto2/crconfig"
	"github.com/Comcast/traffic_control/traffic_ops/goto2/csconfig"
	output "github.com/Comcast/traffic_control/traffic_ops/goto2/output_format"

	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
)

// CreateAdminRouter creates the routes for handling requests to the web interface.
// This function returns an http.Handler to be used in http.ListenAndServe().
func CreateRouter() http.Handler {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/login", auth.Login).Methods("POST")

	router.HandleFunc("/api/2.0/{table}", auth.Use(apiHandler, auth.DONTRequireLogin)).Methods("OPTIONS")
	router.HandleFunc("/api/2.0/{table}", auth.Use(apiHandler, auth.RequireLogin)).Methods("GET", "POST", "OPTIONS")
	router.HandleFunc("/api/2.0/{table}/{id}", auth.Use(apiHandler, auth.RequireLogin)).Methods("GET", "PUT", "DELETE", "OPTIONS")

	router.HandleFunc("/config/cr/{cdn}/CRConfig.json", auth.Use(handleCRConfig, auth.RequireLogin))
	router.HandleFunc("/config/csconfig/{hostname}", auth.Use(handleCSConfig, auth.RequireLogin))

	return auth.Use(router.ServeHTTP, auth.GetContext)
}

func handleCRConfig(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	cdn := vars["cdn"]
	resp, _ := crconfig.GetCRConfig(cdn)
	enc := json.NewEncoder(w)
	enc.Encode(resp)
}

func handleCSConfig(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	hostName := vars["hostname"]
	resp, _ := csconfig.GetCSConfig(hostName)
	enc := json.NewEncoder(w)
	enc.Encode(resp)
}

func setHeaders(w http.ResponseWriter) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE")
	w.Header().Set("Access-Control-Allow-Headers", "Origin, Authorization, X-Requested-With, Content-Type")
	// w.Header().Set("X-JvD", "boo")
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	// maybe it is better to just pass (w http.ResponseWriter, r *http.Request) to the actions,
	// and have the action funcs write to w without returning?
	// TODO: handle admin/oper can CUD, rest can r
	// TODO: handle deliveryservice_tmuser for portal
	setHeaders(w)
	if r.Method == "OPTIONS" {
		w.WriteHeader(http.StatusOK)
		return
	}
	vars := mux.Vars(r)
	table := vars["table"]
	id := -1
	if vars["id"] != "" {
		num, err := strconv.Atoi(vars["id"])
		if err != nil {
			fmt.Println("error 323222")
		}
		id = num
	}
	body, err := ioutil.ReadAll(r.Body)
	response, err := api.Action(table, r.Method, id, body)
	if err != nil {
		fmt.Println("error 42 ", err)
	}
	jresponse := output.MakeApiResponse(response, nil, err)
	w.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(w)
	enc.Encode(jresponse)
}
<commit_msg>DONTRequireLogin on OPTIONS {id}<commit_after>// Copyright 2015 Comcast Cable Communications Management, LLC

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Started from https://raw.githubusercontent.com/jordan-wright/gophish

package routes

import (
	"github.com/Comcast/traffic_control/traffic_ops/goto2/api"
	"github.com/Comcast/traffic_control/traffic_ops/goto2/auth"
	"github.com/Comcast/traffic_control/traffic_ops/goto2/crconfig"
	"github.com/Comcast/traffic_control/traffic_ops/goto2/csconfig"
	output "github.com/Comcast/traffic_control/traffic_ops/goto2/output_format"

	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/gorilla/mux"
)

// CreateAdminRouter creates the routes for handling requests to the web interface.
// This function returns an http.Handler to be used in http.ListenAndServe().
func CreateRouter() http.Handler {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/login", auth.Login).Methods("POST")

	router.HandleFunc("/api/2.0/{table}", auth.Use(apiHandler, auth.DONTRequireLogin)).Methods("OPTIONS")
	router.HandleFunc("/api/2.0/{table}/{id}", auth.Use(apiHandler, auth.DONTRequireLogin)).Methods("OPTIONS")
	router.HandleFunc("/api/2.0/{table}", auth.Use(apiHandler, auth.RequireLogin)).Methods("GET", "POST", "OPTIONS")
	router.HandleFunc("/api/2.0/{table}/{id}", auth.Use(apiHandler, auth.RequireLogin)).Methods("GET", "PUT", "DELETE", "OPTIONS")

	router.HandleFunc("/config/cr/{cdn}/CRConfig.json", auth.Use(handleCRConfig, auth.RequireLogin))
	router.HandleFunc("/config/csconfig/{hostname}", auth.Use(handleCSConfig, auth.RequireLogin))

	return auth.Use(router.ServeHTTP, auth.GetContext)
}

func handleCRConfig(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	cdn := vars["cdn"]
	resp, _ := crconfig.GetCRConfig(cdn)
	enc := json.NewEncoder(w)
	enc.Encode(resp)
}

func handleCSConfig(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	hostName := vars["hostname"]
	resp, _ := csconfig.GetCSConfig(hostName)
	enc := json.NewEncoder(w)
	enc.Encode(resp)
}

func setHeaders(w http.ResponseWriter) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE")
	w.Header().Set("Access-Control-Allow-Headers", "Origin, Authorization, X-Requested-With, Content-Type")
	// w.Header().Set("X-JvD", "boo")
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	// maybe it is better to just pass (w http.ResponseWriter, r *http.Request) to the actions,
	// and have the action funcs write to w without returning?
	// TODO: handle admin/oper can CUD, rest can r
	// TODO: handle deliveryservice_tmuser for portal
	setHeaders(w)
	if r.Method == "OPTIONS" {
		w.WriteHeader(http.StatusOK)
		return
	}
	vars := mux.Vars(r)
	table := vars["table"]
	id := -1
	if vars["id"] != "" {
		num, err := strconv.Atoi(vars["id"])
		if err != nil {
			fmt.Println("error 323222")
		}
		id = num
	}
	body, err := ioutil.ReadAll(r.Body)
	response, err := api.Action(table, r.Method, id, body)
	if err != nil {
		fmt.Println("error 42 ", err)
	}
	jresponse := output.MakeApiResponse(response, nil, err)
	w.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(w)
	enc.Encode(jresponse)
}
<|endoftext|>


<commit_before>package routes

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"

	gmux "github.com/gorilla/mux"
)

var (
	// Package global that holds a map of our templates.
	//
	tmpls map[string]*template.Template
)

// package initialization, sets up our tmpls map
func init() {
	log.Println("Initializing template map")
	if tmpls == nil {
		tmpls = make(map[string]*template.Template)
	}

	templatesDir := "templates/"

	layouts, err := filepath.Glob(templatesDir + "layouts/*.html")
	if err != nil {
		log.Fatal(err)
	}

	includes, err := filepath.Glob(templatesDir + "includes/*.html")
	if err != nil {
		log.Fatal(err)
	}

	for _, layout := range layouts {
		files := append(includes, layout)
		tmpls[filepath.Base(layout)] = template.Must(template.ParseFiles(files...))
		log.Printf("Added to tmpl map: %s\n", filepath.Base(layout))
	}
}

func renderTemplate(w http.ResponseWriter, name string, p *Page) error {
	tmpl, ok := tmpls[name+".html"]
	if !ok {
		return fmt.Errorf("The template %s does not exist.", name)
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	return tmpl.ExecuteTemplate(w, "base", p)
}

// Page represents a basic page
type Page struct {
	Title string
	Body  string
}

// New returns an initialized gorilla *mux.Router with all of our routes
func New() *gmux.Router {
	r := gmux.NewRouter()

	r.HandleFunc("/", indexHandler)
	r.PathPrefix("/static").Handler(staticHandler)
	return r
}

// hello world
func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello murder-hobos!")
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	p := &Page{Title: "Murder Hobos", Body: "Welcome to Murder Hobos!"}
	err := renderTemplate(w, "index", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

// serves static files (css/js)
var staticHandler = http.StripPrefix("/static", http.FileServer(http.Dir("./static")))
<commit_msg>Removed helloworld route<commit_after>package routes

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"

	gmux "github.com/gorilla/mux"
)

var (
	// Package global that holds a map of our templates.
	//
	tmpls map[string]*template.Template
)

// package initialization, sets up our tmpls map
func init() {
	log.Println("Initializing template map")
	if tmpls == nil {
		tmpls = make(map[string]*template.Template)
	}

	templatesDir := "templates/"

	layouts, err := filepath.Glob(templatesDir + "layouts/*.html")
	if err != nil {
		log.Fatal(err)
	}

	includes, err := filepath.Glob(templatesDir + "includes/*.html")
	if err != nil {
		log.Fatal(err)
	}

	for _, layout := range layouts {
		files := append(includes, layout)
		tmpls[filepath.Base(layout)] = template.Must(template.ParseFiles(files...))
		log.Printf("Added to tmpl map: %s\n", filepath.Base(layout))
	}
}

func renderTemplate(w http.ResponseWriter, name string, p *Page) error {
	tmpl, ok := tmpls[name+".html"]
	if !ok {
		return fmt.Errorf("The template %s does not exist.", name)
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	return tmpl.ExecuteTemplate(w, "base", p)
}

// Page represents a basic page
type Page struct {
	Title string
	Body  string
}

// New returns an initialized gorilla *mux.Router with all of our routes
func New() *gmux.Router {
	r := gmux.NewRouter()

	r.HandleFunc("/", indexHandler)
	r.PathPrefix("/static").Handler(staticHandler)
	return r
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	p := &Page{Title: "Murder Hobos", Body: "Welcome to Murder Hobos!"}
	err := renderTemplate(w, "index", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

// serves static files (css/js)
var staticHandler = http.StripPrefix("/static", http.FileServer(http.Dir("./static")))
<|endoftext|>


<commit_before>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package app

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/caixw/typing/data"
	"github.com/caixw/typing/vars"
	"github.com/issue9/logs"
	"github.com/issue9/middleware/compress"
	"github.com/issue9/utils"
)

func (a *app) initRoutes() error {
	handle := func(pattern string, h http.HandlerFunc) error {
		return a.mux.HandleFunc(pattern, a.prepare(h), http.MethodGet)
	}

	// posts/2016/about.html
	if err := handle(vars.PostURL("{slug}"), a.getPost); err != nil {
		return err
	}

	// index.html
	if err := handle(vars.IndexURL(0), a.getPosts); err != nil {
		return err
	}

	// links.html
	if err := handle(vars.LinksURL(), a.getLinks); err != nil {
		return err
	}

	// tags/tag1.html
	if err := handle(vars.TagURL("{slug}", 1), a.getTag); err != nil {
		return err
	}

	// tags.html
	if err := handle(vars.TagsURL(), a.getTags); err != nil {
		return err
	}

	// search.html
	if err := handle(vars.SearchURL("", 1), a.getSearch); err != nil {
		return err
	}

	// themes/...
	if err := handle(vars.ThemesURL("{path}"), a.getThemes); err != nil {
		return err
	}

	// /...
	return handle("/{path}", a.getRaws)
}

// 文章详细页
// /posts/{slug}.html
func (a *app) getPost(w http.ResponseWriter, r *http.Request) {
	id, found := a.paramString(w, r, "slug")
	if !found {
		return
	}

	var post *data.Post
	var next, prev *data.Link
	for index, p := range a.buf.Data.Posts {
		if p.Slug != id {
			continue
		}
		post = p

		if index > 0 {
			p := a.buf.Data.Posts[index-1]
			prev = &data.Link{
				Text: p.Title,
				URL:  p.Permalink,
			}
		}

		index++
		if index < len(a.buf.Data.Posts) {
			p := a.buf.Data.Posts[index]
			next = &data.Link{
				Text: p.Title,
				URL:  p.Permalink,
			}
		}
	} // end for a.data.Posts

	if post == nil {
		logs.Debugf("并未找到与之相对应的文章:%v", id)
		a.getRaws(w, r) // 文章不存在，则查找 raws 目录下是否存在同名文件
		return
	}

	p := a.newPage()
	p.Post = post
	p.NextPage = next
	p.PrevPage = prev
	p.Keywords = post.Keywords
	p.Description = post.Summary
	p.Title = post.Title
	p.Canonical = post.Permalink

	p.render(w, post.Template, nil)
}

// 首页及文章列表页
// /
// /posts.html?page=2
func (a *app) getPosts(w http.ResponseWriter, r *http.Request) {
	page, ok := a.queryInt(w, r, "page", 1)
	if !ok {
		return
	}

	if page < 1 {
		logs.Debugf("请求的页码[%v]小于1\n", page)
		a.renderError(w, http.StatusNotFound) // 页码为负数的表示不存在，跳转到 404 页面
		return
	}

	p := a.newPage()
	if page > 1 { // 非首页，标题显示页码数
		p.Title = fmt.Sprintf("第%v页", page)
	}
	p.Canonical = vars.PostsURL(page)

	start, end, ok := a.getPostsRange(len(a.buf.Data.Posts), page, w)
	if !ok {
		return
	}
	p.Posts = a.buf.Data.Posts[start:end]
	if page > 1 {
		p.PrevPage = &data.Link{
			Text: "前一页",
			URL:  vars.PostsURL(page - 1), // 页码从 1 开始计数
		}
	}
	if end < len(a.buf.Data.Posts) {
		p.PrevPage = &data.Link{
			Text: "下一页",
			URL:  vars.PostsURL(page + 1),
		}
	}

	p.render(w, "posts", nil)
}

// 标签详细页
// /tags/tag1.html?page=2
func (a *app) getTag(w http.ResponseWriter, r *http.Request) {
	slug, ok := a.paramString(w, r, "slug")
	if !ok {
		return
	}

	var tag *data.Tag
	for _, t := range a.buf.Data.Tags {
		if t.Slug == slug {
			tag = t
			break
		}
	}

	if tag == nil {
		logs.Debugf("查找的标签[%v]不存在", slug)
		a.getRaws(w, r) // 标签不存在，则查找该文件是否存在于 raws 目录下。
		return
	}

	page, ok := a.queryInt(w, r, "page", 1)
	if !ok {
		return
	}
	if page < 1 {
		logs.Debugf("请求的页码[%v]小于1", page)
		a.renderError(w, http.StatusNotFound) // 页码为负数的表示不存在，跳转到 404 页面
		return
	}

	p := a.newPage()
	p.Tag = tag
	p.Title = tag.Title
	p.Canonical = vars.TagURL(slug, page)
	p.Description = "标签" + tag.Title + "的介绍"

	start, end, ok := a.getPostsRange(len(tag.Posts), page, w)
	if !ok {
		return
	}
	p.Posts = tag.Posts[start:end]
	if page > 1 {
		p.PrevPage = &data.Link{
			Text: "前一页",
			URL:  vars.TagURL(slug, page-1), // 页码从1开始计数
		}
	}
	if end < len(tag.Posts) {
		p.PrevPage = &data.Link{
			Text: "下一页",
			URL:  vars.TagURL(slug, page+1), // 页码从1开始计数
		}
	}

	p.render(w, "tag", nil)
}

// 友情链接页
// /links.html
func (a *app) getLinks(w http.ResponseWriter, r *http.Request) {
	p := a.newPage()
	p.Title = "友情链接"
	p.Canonical = vars.LinksURL()

	p.render(w, "links", nil)
}

// 标签列表页
// /tags.html
func (a *app) getTags(w http.ResponseWriter, r *http.Request) {
	p := a.newPage()
	p.Title = "标签"
	p.Canonical = vars.TagsURL()

	p.render(w, "tags", nil)
}

// 主题文件
// /themes/...
func (a *app) getThemes(w http.ResponseWriter, r *http.Request) {
	root := http.Dir(a.path.ThemesDir)
	http.StripPrefix(vars.ThemesURL(""), http.FileServer(root)).ServeHTTP(w, r)
}

// /search.html?q=key&page=2
func (a *app) getSearch(w http.ResponseWriter, r *http.Request) {
	p := a.newPage()

	key := r.FormValue("q")
	if len(key) == 0 {
		p.render(w, "search", nil)
		return
	}

	page, ok := a.queryInt(w, r, "page", 1)
	if !ok {
		return
	}
	if page < 1 {
		logs.Debugf("参数 page: %v 小于 1", page)
		a.renderError(w, http.StatusNotFound) // 页码为负数的表示不存在，跳转到 404 页面
		return
	}

	// 查找标题和内容
	posts := make([]*data.Post, 0, a.buf.Data.Config.PageSize)
	for _, v := range a.buf.Data.Posts {
		if strings.Index(v.Title, key) >= 0 || strings.Index(v.Content, key) >= 0 {
			posts = append(posts, v)
		}
	}

	p.Title = "搜索:" + key
	p.Q = key
	p.Keywords = key
	p.Description = "搜索关键字" + key + "的结果集"
	start, end, ok := a.getPostsRange(len(posts), page, w)
	if !ok {
		return
	}
	p.Posts = posts[start:end]
	if page > 1 {
		p.PrevPage = &data.Link{
			Text: "前一页",
			URL:  vars.SearchURL(key, page-1), // 页码从1开始计数
		}
	}
	if end < len(posts) {
		p.PrevPage = &data.Link{
			Text: "下一页",
			URL:  vars.SearchURL(key, page+1),
		}
	}

	p.render(w, "search", nil)

}

// 读取根下的文件
// /...
func (a *app) getRaws(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		a.getPosts(w, r)
		return
	}

	if !utils.FileExists(filepath.Join(a.path.RawsDir, r.URL.Path)) {
		a.renderError(w, http.StatusNotFound)
		return
	}

	prefix := "/"
	root := http.Dir(a.path.RawsDir)
	http.StripPrefix(prefix, http.FileServer(root)).ServeHTTP(w, r)

}

// 确认当前文章列表页选择范围。
func (a *app) getPostsRange(postsSize, page int, w http.ResponseWriter) (start, end int, ok bool) {
	size := a.buf.Data.Config.PageSize
	start = size * (page - 1) // 系统从零开始计数
	if start > postsSize {
		logs.Debugf("请求页码为[%v]，实际文章数量为[%v]\n", page, postsSize)
		a.renderError(w, http.StatusNotFound) // 页码超出范围，不存在
		return 0, 0, false
	}

	end = start + size
	if postsSize < end {
		end = postsSize
	}

	return start, end, true
}

// 每次访问前需要做的预处理工作。
func (a *app) prepare(f http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		logs.Infof("%v: %v", r.UserAgent(), r.URL) // 输出访问日志

		// 直接根据整个博客的最后更新时间来确认 etag
		if r.Header.Get("If-None-Match") == a.buf.Etag {
			logs.Infof("304: %v", r.URL)
			w.WriteHeader(http.StatusNotModified)
			return
		}
		w.Header().Set("Etag", a.buf.Etag)
		w.Header().Set("Content-Language", a.buf.Data.Config.Language)
		compress.New(f, logs.ERROR()).ServeHTTP(w, r)
	}
}
<commit_msg>将搜索关键字为空时，跳转到首页<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package app

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/caixw/typing/data"
	"github.com/caixw/typing/vars"
	"github.com/issue9/logs"
	"github.com/issue9/middleware/compress"
	"github.com/issue9/utils"
)

func (a *app) initRoutes() error {
	handle := func(pattern string, h http.HandlerFunc) error {
		return a.mux.HandleFunc(pattern, a.prepare(h), http.MethodGet)
	}

	// posts/2016/about.html
	if err := handle(vars.PostURL("{slug}"), a.getPost); err != nil {
		return err
	}

	// index.html
	if err := handle(vars.IndexURL(0), a.getPosts); err != nil {
		return err
	}

	// links.html
	if err := handle(vars.LinksURL(), a.getLinks); err != nil {
		return err
	}

	// tags/tag1.html
	if err := handle(vars.TagURL("{slug}", 1), a.getTag); err != nil {
		return err
	}

	// tags.html
	if err := handle(vars.TagsURL(), a.getTags); err != nil {
		return err
	}

	// search.html
	if err := handle(vars.SearchURL("", 1), a.getSearch); err != nil {
		return err
	}

	// themes/...
	if err := handle(vars.ThemesURL("{path}"), a.getThemes); err != nil {
		return err
	}

	// /...
	return handle("/{path}", a.getRaws)
}

// 文章详细页
// /posts/{slug}.html
func (a *app) getPost(w http.ResponseWriter, r *http.Request) {
	id, found := a.paramString(w, r, "slug")
	if !found {
		return
	}

	var post *data.Post
	var next, prev *data.Link
	for index, p := range a.buf.Data.Posts {
		if p.Slug != id {
			continue
		}
		post = p

		if index > 0 {
			p := a.buf.Data.Posts[index-1]
			prev = &data.Link{
				Text: p.Title,
				URL:  p.Permalink,
			}
		}

		index++
		if index < len(a.buf.Data.Posts) {
			p := a.buf.Data.Posts[index]
			next = &data.Link{
				Text: p.Title,
				URL:  p.Permalink,
			}
		}
	} // end for a.data.Posts

	if post == nil {
		logs.Debugf("并未找到与之相对应的文章:%v", id)
		a.getRaws(w, r) // 文章不存在，则查找 raws 目录下是否存在同名文件
		return
	}

	p := a.newPage()
	p.Post = post
	p.NextPage = next
	p.PrevPage = prev
	p.Keywords = post.Keywords
	p.Description = post.Summary
	p.Title = post.Title
	p.Canonical = post.Permalink

	p.render(w, post.Template, nil)
}

// 首页及文章列表页
// /
// /posts.html?page=2
func (a *app) getPosts(w http.ResponseWriter, r *http.Request) {
	page, ok := a.queryInt(w, r, "page", 1)
	if !ok {
		return
	}

	if page < 1 {
		logs.Debugf("请求的页码[%v]小于1\n", page)
		a.renderError(w, http.StatusNotFound) // 页码为负数的表示不存在，跳转到 404 页面
		return
	}

	p := a.newPage()
	if page > 1 { // 非首页，标题显示页码数
		p.Title = fmt.Sprintf("第%v页", page)
	}
	p.Canonical = vars.PostsURL(page)

	start, end, ok := a.getPostsRange(len(a.buf.Data.Posts), page, w)
	if !ok {
		return
	}
	p.Posts = a.buf.Data.Posts[start:end]
	if page > 1 {
		p.PrevPage = &data.Link{
			Text: "前一页",
			URL:  vars.PostsURL(page - 1), // 页码从 1 开始计数
		}
	}
	if end < len(a.buf.Data.Posts) {
		p.PrevPage = &data.Link{
			Text: "下一页",
			URL:  vars.PostsURL(page + 1),
		}
	}

	p.render(w, "posts", nil)
}

// 标签详细页
// /tags/tag1.html?page=2
func (a *app) getTag(w http.ResponseWriter, r *http.Request) {
	slug, ok := a.paramString(w, r, "slug")
	if !ok {
		return
	}

	var tag *data.Tag
	for _, t := range a.buf.Data.Tags {
		if t.Slug == slug {
			tag = t
			break
		}
	}

	if tag == nil {
		logs.Debugf("查找的标签[%v]不存在", slug)
		a.getRaws(w, r) // 标签不存在，则查找该文件是否存在于 raws 目录下。
		return
	}

	page, ok := a.queryInt(w, r, "page", 1)
	if !ok {
		return
	}
	if page < 1 {
		logs.Debugf("请求的页码[%v]小于1", page)
		a.renderError(w, http.StatusNotFound) // 页码为负数的表示不存在，跳转到 404 页面
		return
	}

	p := a.newPage()
	p.Tag = tag
	p.Title = tag.Title
	p.Canonical = vars.TagURL(slug, page)
	p.Description = "标签" + tag.Title + "的介绍"

	start, end, ok := a.getPostsRange(len(tag.Posts), page, w)
	if !ok {
		return
	}
	p.Posts = tag.Posts[start:end]
	if page > 1 {
		p.PrevPage = &data.Link{
			Text: "前一页",
			URL:  vars.TagURL(slug, page-1), // 页码从1开始计数
		}
	}
	if end < len(tag.Posts) {
		p.PrevPage = &data.Link{
			Text: "下一页",
			URL:  vars.TagURL(slug, page+1), // 页码从1开始计数
		}
	}

	p.render(w, "tag", nil)
}

// 友情链接页
// /links.html
func (a *app) getLinks(w http.ResponseWriter, r *http.Request) {
	p := a.newPage()
	p.Title = "友情链接"
	p.Canonical = vars.LinksURL()

	p.render(w, "links", nil)
}

// 标签列表页
// /tags.html
func (a *app) getTags(w http.ResponseWriter, r *http.Request) {
	p := a.newPage()
	p.Title = "标签"
	p.Canonical = vars.TagsURL()

	p.render(w, "tags", nil)
}

// 主题文件
// /themes/...
func (a *app) getThemes(w http.ResponseWriter, r *http.Request) {
	root := http.Dir(a.path.ThemesDir)
	http.StripPrefix(vars.ThemesURL(""), http.FileServer(root)).ServeHTTP(w, r)
}

// /search.html?q=key&page=2
func (a *app) getSearch(w http.ResponseWriter, r *http.Request) {
	p := a.newPage()

	key := r.FormValue("q")
	if len(key) == 0 {
		http.Redirect(w, r, vars.PostsURL(1), http.StatusPermanentRedirect)
		return
	}

	page, ok := a.queryInt(w, r, "page", 1)
	if !ok {
		return
	}
	if page < 1 {
		logs.Debugf("参数 page: %v 小于 1", page)
		a.renderError(w, http.StatusNotFound) // 页码为负数的表示不存在，跳转到 404 页面
		return
	}

	// 查找标题和内容
	posts := make([]*data.Post, 0, len(a.buf.Data.Posts))
	for _, v := range a.buf.Data.Posts {
		if strings.Index(v.Title, key) >= 0 || strings.Index(v.Content, key) >= 0 {
			posts = append(posts, v)
		}
	}

	p.Title = "搜索:" + key
	p.Q = key
	p.Keywords = key + ",搜索,search"
	p.Description = "搜索关键字" + key + "的结果"
	start, end, ok := a.getPostsRange(len(posts), page, w)
	if !ok {
		return
	}
	p.Posts = posts[start:end]
	if page > 1 {
		p.PrevPage = &data.Link{
			Text: "前一页",
			URL:  vars.SearchURL(key, page-1), // 页码从1开始计数
		}
	}
	if end < len(posts) {
		p.PrevPage = &data.Link{
			Text: "下一页",
			URL:  vars.SearchURL(key, page+1),
		}
	}

	p.render(w, "search", nil)
}

// 读取根下的文件
// /...
func (a *app) getRaws(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		a.getPosts(w, r)
		return
	}

	if !utils.FileExists(filepath.Join(a.path.RawsDir, r.URL.Path)) {
		a.renderError(w, http.StatusNotFound)
		return
	}

	prefix := "/"
	root := http.Dir(a.path.RawsDir)
	http.StripPrefix(prefix, http.FileServer(root)).ServeHTTP(w, r)
}

// 确认当前文章列表页选择范围。
func (a *app) getPostsRange(postsSize, page int, w http.ResponseWriter) (start, end int, ok bool) {
	size := a.buf.Data.Config.PageSize
	start = size * (page - 1) // 系统从零开始计数
	if start > postsSize {
		logs.Debugf("请求页码为[%v]，实际文章数量为[%v]\n", page, postsSize)
		a.renderError(w, http.StatusNotFound) // 页码超出范围，不存在
		return 0, 0, false
	}

	end = start + size
	if postsSize < end {
		end = postsSize
	}

	return start, end, true
}

// 每次访问前需要做的预处理工作。
func (a *app) prepare(f http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		logs.Infof("%v: %v", r.UserAgent(), r.URL) // 输出访问日志

		// 直接根据整个博客的最后更新时间来确认 etag
		if r.Header.Get("If-None-Match") == a.buf.Etag {
			logs.Infof("304: %v", r.URL)
			w.WriteHeader(http.StatusNotModified)
			return
		}
		w.Header().Set("Etag", a.buf.Etag)
		w.Header().Set("Content-Language", a.buf.Data.Config.Language)
		compress.New(f, logs.ERROR()).ServeHTTP(w, r)
	}
}
<|endoftext|>


<commit_before>package appcli

import (
	"io/ioutil"
	"path/filepath"
	"sort"

	"github.com/pkg/errors"
	"github.com/urfave/cli"

	"github.com/rliebz/tusk/config"
	"github.com/rliebz/tusk/config/task"
	"github.com/rliebz/tusk/ui"
)

// newBaseApp creates a basic cli.App with top-level flags.
func newBaseApp() *cli.App {
	app := cli.NewApp()
	app.Usage = "a task runner built with simplicity in mind"
	app.HideVersion = true
	app.HideHelp = true
	app.EnableBashCompletion = true

	app.Flags = append(app.Flags,
		cli.BoolFlag{
			Name:  "h, help",
			Usage: "Show help and exit",
		},
		cli.StringFlag{
			Name:  "f, file",
			Usage: "Set `file` to use as the config file",
		},
		cli.BoolFlag{
			Name:  "q, quiet",
			Usage: "Only print command output and application errors",
		},
		cli.BoolFlag{
			Name:  "s, silent",
			Usage: "Print no output",
		},
		cli.BoolFlag{
			Name:  "v, verbose",
			Usage: "Print verbose output",
		},
		cli.BoolFlag{
			Name:  "V, version",
			Usage: "Print version and exit",
		},
	)

	sort.Sort(cli.FlagsByName(app.Flags))
	return app
}

// newSilentApp creates a cli.App that will never print to stderr / stdout.
func newSilentApp() *cli.App {
	app := newBaseApp()
	app.Writer = ioutil.Discard
	app.ErrWriter = ioutil.Discard
	app.CommandNotFound = func(c *cli.Context, command string) {}
	return app
}

// newMetaApp creates a cli.App containing metadata, which can parse flags.
func newMetaApp(cfgText []byte) (*cli.App, error) {
	cfg, err := config.Parse(cfgText)
	if err != nil {
		return nil, err
	}

	app := newSilentApp()
	app.Metadata = make(map[string]interface{})
	app.Metadata["tasks"] = make(map[string]*task.Task)
	app.Metadata["argsPassed"] = []string{}
	app.Metadata["flagsPassed"] = make(map[string]string)

	if err = addTasks(app, cfg, createMetadataBuildCommand); err != nil {
		return nil, err
	}

	return app, nil
}

// NewApp creates a cli.App that executes tasks.
func NewApp(args []string, meta *config.Metadata) (*cli.App, error) {
	metaApp, err := newMetaApp(meta.CfgText)
	if err != nil {
		return nil, err
	}

	if err = metaApp.Run(args); err != nil {
		return nil, err
	}

	var taskName string
	command, ok := metaApp.Metadata["command"].(*cli.Command)
	if ok {
		taskName = command.Name
	}

	argsPassed, flagsPassed, err := getPassedValues(metaApp)
	if err != nil {
		return nil, err
	}

	cfg, err := config.ParseComplete(meta.CfgText, taskName, argsPassed, flagsPassed)
	if err != nil {
		return nil, err
	}

	app := newBaseApp()
	if cfg.Name != nil {
		app.Name = *cfg.Name
		app.HelpName = *cfg.Name
	}
	if cfg.Usage != nil {
		app.Usage = *cfg.Usage
	}

	if err := addTasks(app, cfg, createExecuteCommand); err != nil {
		return nil, err
	}

	copyFlags(app, metaApp)

	app.BashComplete = createDefaultComplete(app)
	for i := range app.Commands {
		app.Commands[i].BashComplete = createCommandComplete(
			&app.Commands[i], cfg,
		)
	}

	return app, nil
}

// getPassedValues returns the args and flags passed by command line.
func getPassedValues(app *cli.App) ([]string, map[string]string, error) {
	argsPassed, ok := app.Metadata["argsPassed"].([]string)
	if !ok {
		return nil, nil, errors.New("could not read args from metadata")
	}
	flagsPassed, ok := app.Metadata["flagsPassed"].(map[string]string)
	if !ok {
		return nil, nil, errors.New("could not read flags from metadata")
	}

	return argsPassed, flagsPassed, nil
}

// GetConfigMetadata returns a metadata object based on global options passed.
func GetConfigMetadata(args []string) (*config.Metadata, error) {

	var err error
	app := newSilentApp()
	metadata := new(config.Metadata)

	// To prevent app from exiting, the app.Action must return nil on error.
	// The enclosing function will still return the error.
	app.Action = func(c *cli.Context) error {
		fullPath := c.String("file")
		if fullPath != "" {
			metadata.CfgText, err = ioutil.ReadFile(fullPath)
			if err != nil {
				return nil
			}
		} else {
			var found bool
			fullPath, found, err = config.SearchForFile()
			if err != nil {
				return nil
			}

			if found {
				metadata.CfgText, err = ioutil.ReadFile(fullPath)
				if err != nil {
					return nil
				}
			}
		}

		metadata.Directory = filepath.Dir(fullPath)
		metadata.PrintHelp = c.Bool("help")
		metadata.PrintVersion = c.Bool("version")
		setMetadataVerbosity(metadata, c)

		return err
	}

	if runErr := populateMetadata(app, args); runErr != nil {
		return nil, runErr
	}

	return metadata, err
}

func setMetadataVerbosity(metadata *config.Metadata, c *cli.Context) {
	if c.Bool("silent") {
		metadata.Verbosity = ui.VerbosityLevelSilent
	} else if c.Bool("quiet") {
		metadata.Verbosity = ui.VerbosityLevelQuiet
	} else if c.Bool("verbose") {
		metadata.Verbosity = ui.VerbosityLevelVerbose
	} else {
		metadata.Verbosity = ui.VerbosityLevelNormal
	}
}

// populateMetadata runs the app to populate the metadata struct.
func populateMetadata(app *cli.App, args []string) error {
	args = removeCompletionArg(args)

	if err := app.Run(args); err != nil {
		// Ignore flags without arguments during metadata creation
		if isFlagArgumentError(err) {
			return app.Run(args[:len(args)-1])
		}

		return err
	}

	return nil
}
<commit_msg>Update project tagline<commit_after>package appcli

import (
	"io/ioutil"
	"path/filepath"
	"sort"

	"github.com/pkg/errors"
	"github.com/urfave/cli"

	"github.com/rliebz/tusk/config"
	"github.com/rliebz/tusk/config/task"
	"github.com/rliebz/tusk/ui"
)

// newBaseApp creates a basic cli.App with top-level flags.
func newBaseApp() *cli.App {
	app := cli.NewApp()
	app.Usage = "the modern task runner"
	app.HideVersion = true
	app.HideHelp = true
	app.EnableBashCompletion = true

	app.Flags = append(app.Flags,
		cli.BoolFlag{
			Name:  "h, help",
			Usage: "Show help and exit",
		},
		cli.StringFlag{
			Name:  "f, file",
			Usage: "Set `file` to use as the config file",
		},
		cli.BoolFlag{
			Name:  "q, quiet",
			Usage: "Only print command output and application errors",
		},
		cli.BoolFlag{
			Name:  "s, silent",
			Usage: "Print no output",
		},
		cli.BoolFlag{
			Name:  "v, verbose",
			Usage: "Print verbose output",
		},
		cli.BoolFlag{
			Name:  "V, version",
			Usage: "Print version and exit",
		},
	)

	sort.Sort(cli.FlagsByName(app.Flags))
	return app
}

// newSilentApp creates a cli.App that will never print to stderr / stdout.
func newSilentApp() *cli.App {
	app := newBaseApp()
	app.Writer = ioutil.Discard
	app.ErrWriter = ioutil.Discard
	app.CommandNotFound = func(c *cli.Context, command string) {}
	return app
}

// newMetaApp creates a cli.App containing metadata, which can parse flags.
func newMetaApp(cfgText []byte) (*cli.App, error) {
	cfg, err := config.Parse(cfgText)
	if err != nil {
		return nil, err
	}

	app := newSilentApp()
	app.Metadata = make(map[string]interface{})
	app.Metadata["tasks"] = make(map[string]*task.Task)
	app.Metadata["argsPassed"] = []string{}
	app.Metadata["flagsPassed"] = make(map[string]string)

	if err = addTasks(app, cfg, createMetadataBuildCommand); err != nil {
		return nil, err
	}

	return app, nil
}

// NewApp creates a cli.App that executes tasks.
func NewApp(args []string, meta *config.Metadata) (*cli.App, error) {
	metaApp, err := newMetaApp(meta.CfgText)
	if err != nil {
		return nil, err
	}

	if err = metaApp.Run(args); err != nil {
		return nil, err
	}

	var taskName string
	command, ok := metaApp.Metadata["command"].(*cli.Command)
	if ok {
		taskName = command.Name
	}

	argsPassed, flagsPassed, err := getPassedValues(metaApp)
	if err != nil {
		return nil, err
	}

	cfg, err := config.ParseComplete(meta.CfgText, taskName, argsPassed, flagsPassed)
	if err != nil {
		return nil, err
	}

	app := newBaseApp()
	if cfg.Name != nil {
		app.Name = *cfg.Name
		app.HelpName = *cfg.Name
	}
	if cfg.Usage != nil {
		app.Usage = *cfg.Usage
	}

	if err := addTasks(app, cfg, createExecuteCommand); err != nil {
		return nil, err
	}

	copyFlags(app, metaApp)

	app.BashComplete = createDefaultComplete(app)
	for i := range app.Commands {
		app.Commands[i].BashComplete = createCommandComplete(
			&app.Commands[i], cfg,
		)
	}

	return app, nil
}

// getPassedValues returns the args and flags passed by command line.
func getPassedValues(app *cli.App) ([]string, map[string]string, error) {
	argsPassed, ok := app.Metadata["argsPassed"].([]string)
	if !ok {
		return nil, nil, errors.New("could not read args from metadata")
	}
	flagsPassed, ok := app.Metadata["flagsPassed"].(map[string]string)
	if !ok {
		return nil, nil, errors.New("could not read flags from metadata")
	}

	return argsPassed, flagsPassed, nil
}

// GetConfigMetadata returns a metadata object based on global options passed.
func GetConfigMetadata(args []string) (*config.Metadata, error) {

	var err error
	app := newSilentApp()
	metadata := new(config.Metadata)

	// To prevent app from exiting, the app.Action must return nil on error.
	// The enclosing function will still return the error.
	app.Action = func(c *cli.Context) error {
		fullPath := c.String("file")
		if fullPath != "" {
			metadata.CfgText, err = ioutil.ReadFile(fullPath)
			if err != nil {
				return nil
			}
		} else {
			var found bool
			fullPath, found, err = config.SearchForFile()
			if err != nil {
				return nil
			}

			if found {
				metadata.CfgText, err = ioutil.ReadFile(fullPath)
				if err != nil {
					return nil
				}
			}
		}

		metadata.Directory = filepath.Dir(fullPath)
		metadata.PrintHelp = c.Bool("help")
		metadata.PrintVersion = c.Bool("version")
		setMetadataVerbosity(metadata, c)

		return err
	}

	if runErr := populateMetadata(app, args); runErr != nil {
		return nil, runErr
	}

	return metadata, err
}

func setMetadataVerbosity(metadata *config.Metadata, c *cli.Context) {
	if c.Bool("silent") {
		metadata.Verbosity = ui.VerbosityLevelSilent
	} else if c.Bool("quiet") {
		metadata.Verbosity = ui.VerbosityLevelQuiet
	} else if c.Bool("verbose") {
		metadata.Verbosity = ui.VerbosityLevelVerbose
	} else {
		metadata.Verbosity = ui.VerbosityLevelNormal
	}
}

// populateMetadata runs the app to populate the metadata struct.
func populateMetadata(app *cli.App, args []string) error {
	args = removeCompletionArg(args)

	if err := app.Run(args); err != nil {
		// Ignore flags without arguments during metadata creation
		if isFlagArgumentError(err) {
			return app.Run(args[:len(args)-1])
		}

		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package chat

import (
	"errors"
	"fmt"
	"io"
	"sort"
	"sync"

	"github.com/shazow/ssh-chat/chat/message"
	"github.com/shazow/ssh-chat/internal/humantime"
	"github.com/shazow/ssh-chat/set"
)

const historyLen = 20
const roomBuffer = 10

// ErrRoomClosed is the error returned when a message is sent to a room that is already
// closed.
var ErrRoomClosed = errors.New("room closed")

// ErrInvalidName is the error returned when a user attempts to join with an invalid name,
// such as empty string.
var ErrInvalidName = errors.New("invalid name")

// Member is a User with per-Room metadata attached to it.
type Member struct {
	*message.User
	IsOp bool
}

// Room definition, also a Set of User Items
type Room struct {
	topic     string
	history   *message.History
	broadcast chan message.Message
	commands  Commands
	closed    bool
	closeOnce sync.Once

	Members *set.Set
}

// NewRoom creates a new room.
func NewRoom() *Room {
	broadcast := make(chan message.Message, roomBuffer)

	return &Room{
		broadcast: broadcast,
		history:   message.NewHistory(historyLen),
		commands:  *defaultCommands,

		Members: set.New(),
	}
}

// SetCommands sets the room's command handlers.
func (r *Room) SetCommands(commands Commands) {
	r.commands = commands
}

// Close the room and all the users it contains.
func (r *Room) Close() {
	r.closeOnce.Do(func() {
		r.closed = true
		r.Members.Each(func(_ string, item set.Item) error {
			item.Value().(*Member).Close()
			return nil
		})
		r.Members.Clear()
		close(r.broadcast)
	})
}

// SetLogging sets logging output for the room's history
func (r *Room) SetLogging(out io.Writer) {
	r.history.SetOutput(out)
}

// HandleMsg reacts to a message, will block until done.
func (r *Room) HandleMsg(m message.Message) {
	switch m := m.(type) {
	case *message.CommandMsg:
		cmd := *m
		err := r.commands.Run(r, cmd)
		if err != nil {
			m := message.NewSystemMsg(fmt.Sprintf("Err: %s", err), cmd.From())
			go r.HandleMsg(m)
		}
	case message.MessageTo:
		user := m.To()
		if _, ok := m.(*message.PrivateMsg); ok {
			fromMsg, _ := m.(message.MessageFrom)
			if fromMsg != nil && user.Ignored.In(fromMsg.From().ID()) {
				// Skip because ignored
				return
			}
		}

		user.Send(m)
	default:
		fromMsg, _ := m.(message.MessageFrom)
		r.history.Add(m)
		r.Members.Each(func(_ string, item set.Item) (err error) {
			user := item.Value().(*Member).User

			if fromMsg != nil && user.Ignored.In(fromMsg.From().ID()) {
				// Skip because ignored
				return
			}

			if _, ok := m.(*message.AnnounceMsg); ok {
				if user.Config().Quiet {
					// Skip announcements
					return
				}
			}
			user.Send(m)
			return
		})
	}
}

// Serve will consume the broadcast room and handle the messages, should be
// run in a goroutine.
func (r *Room) Serve() {
	for m := range r.broadcast {
		go r.HandleMsg(m)
	}
}

// Send message, buffered by a chan.
func (r *Room) Send(m message.Message) {
	r.broadcast <- m
}

// History feeds the room's recent message history to the user's handler.
func (r *Room) History(u *message.User) {
	for _, m := range r.history.Get(historyLen) {
		u.Send(m)
	}
}

// Join the room as a user, will announce.
func (r *Room) Join(u *message.User) (*Member, error) {
	// TODO: Check if closed
	if u.ID() == "" {
		return nil, ErrInvalidName
	}
	member := &Member{User: u}
	err := r.Members.Add(set.Itemize(u.ID(), member))
	if err != nil {
		return nil, err
	}
	r.History(u)
	s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), r.Members.Len())
	r.Send(message.NewAnnounceMsg(s))
	return member, nil
}

// Leave the room as a user, will announce. Mostly used during setup.
func (r *Room) Leave(u *message.User) error {
	err := r.Members.Remove(u.ID())
	if err != nil {
		return err
	}
	s := fmt.Sprintf("%s left. (After %s)", u.Name(), humantime.Since(u.Joined()))
	r.Send(message.NewAnnounceMsg(s))
	return nil
}

// Rename member with a new identity. This will not call rename on the member.
func (r *Room) Rename(oldID string, u message.Identifier) error {
	if u.ID() == "" {
		return ErrInvalidName
	}
	err := r.Members.Replace(oldID, set.Itemize(u.ID(), u))
	if err != nil {
		return err
	}

	s := fmt.Sprintf("%s is now known as %s.", oldID, u.ID())
	r.Send(message.NewAnnounceMsg(s))
	return nil
}

// Member returns a corresponding Member object to a User if the Member is
// present in this room.
func (r *Room) Member(u *message.User) (*Member, bool) {
	m, ok := r.MemberByID(u.ID())
	if !ok {
		return nil, false
	}
	// Check that it's the same user
	if m.User != u {
		return nil, false
	}
	return m, true
}

// MemberByID Gets a member by an id / name
func (r *Room) MemberByID(id string) (*Member, bool) {
	m, err := r.Members.Get(id)
	if err != nil {
		return nil, false
	}
	return m.Value().(*Member), true
}

// IsOp returns whether a user is an operator in this room.
func (r *Room) IsOp(u *message.User) bool {
	m, ok := r.Member(u)
	if !ok {
		return false
	}
	return m.IsOp
}

// Topic of the room.
func (r *Room) Topic() string {
	return r.topic
}

// SetTopic will set the topic of the room.
func (r *Room) SetTopic(s string) {
	r.topic = s
}

// NamesPrefix lists all members' names with a given prefix, used to query
// for autocompletion purposes. Sorted by which user was last active.
func (r *Room) NamesPrefix(prefix string) []string {
	items := r.Members.ListPrefix(prefix)

	// Sort results by recently active
	users := make([]*message.User, 0, len(items))
	for _, item := range items {
		users = append(users, item.Value().(*Member).User)
	}
	sort.Sort(message.RecentActiveUsers(users))

	// Pull out names
	names := make([]string, 0, len(items))
	for _, user := range users {
		names = append(names, user.Name())
	}
	return names
}
<commit_msg>chat: Clean up ignore comparisons<commit_after>package chat

import (
	"errors"
	"fmt"
	"io"
	"sort"
	"sync"

	"github.com/shazow/ssh-chat/chat/message"
	"github.com/shazow/ssh-chat/internal/humantime"
	"github.com/shazow/ssh-chat/set"
)

const historyLen = 20
const roomBuffer = 10

// ErrRoomClosed is the error returned when a message is sent to a room that is already
// closed.
var ErrRoomClosed = errors.New("room closed")

// ErrInvalidName is the error returned when a user attempts to join with an invalid name,
// such as empty string.
var ErrInvalidName = errors.New("invalid name")

// Member is a User with per-Room metadata attached to it.
type Member struct {
	*message.User
	IsOp bool
}

// Room definition, also a Set of User Items
type Room struct {
	topic     string
	history   *message.History
	broadcast chan message.Message
	commands  Commands
	closed    bool
	closeOnce sync.Once

	Members *set.Set
}

// NewRoom creates a new room.
func NewRoom() *Room {
	broadcast := make(chan message.Message, roomBuffer)

	return &Room{
		broadcast: broadcast,
		history:   message.NewHistory(historyLen),
		commands:  *defaultCommands,

		Members: set.New(),
	}
}

// SetCommands sets the room's command handlers.
func (r *Room) SetCommands(commands Commands) {
	r.commands = commands
}

// Close the room and all the users it contains.
func (r *Room) Close() {
	r.closeOnce.Do(func() {
		r.closed = true
		r.Members.Each(func(_ string, item set.Item) error {
			item.Value().(*Member).Close()
			return nil
		})
		r.Members.Clear()
		close(r.broadcast)
	})
}

// SetLogging sets logging output for the room's history
func (r *Room) SetLogging(out io.Writer) {
	r.history.SetOutput(out)
}

// HandleMsg reacts to a message, will block until done.
func (r *Room) HandleMsg(m message.Message) {
	var fromID string
	if fromMsg, ok := m.(message.MessageFrom); ok {
		fromID = fromMsg.From().ID()
	}

	switch m := m.(type) {
	case *message.CommandMsg:
		cmd := *m
		err := r.commands.Run(r, cmd)
		if err != nil {
			m := message.NewSystemMsg(fmt.Sprintf("Err: %s", err), cmd.From())
			go r.HandleMsg(m)
		}
	case message.MessageTo:
		user := m.To()
		if user.Ignored.In(fromID) {
			return // Skip ignored
		}

		user.Send(m)
	default:
		r.history.Add(m)
		r.Members.Each(func(_ string, item set.Item) (err error) {
			user := item.Value().(*Member).User

			if user.Ignored.In(fromID) {
				return // Skip ignored
			}

			if _, ok := m.(*message.AnnounceMsg); ok {
				if user.Config().Quiet {
					return // Skip announcements
				}
			}
			user.Send(m)
			return
		})
	}
}

// Serve will consume the broadcast room and handle the messages, should be
// run in a goroutine.
func (r *Room) Serve() {
	for m := range r.broadcast {
		go r.HandleMsg(m)
	}
}

// Send message, buffered by a chan.
func (r *Room) Send(m message.Message) {
	r.broadcast <- m
}

// History feeds the room's recent message history to the user's handler.
func (r *Room) History(u *message.User) {
	for _, m := range r.history.Get(historyLen) {
		u.Send(m)
	}
}

// Join the room as a user, will announce.
func (r *Room) Join(u *message.User) (*Member, error) {
	// TODO: Check if closed
	if u.ID() == "" {
		return nil, ErrInvalidName
	}
	member := &Member{User: u}
	err := r.Members.Add(set.Itemize(u.ID(), member))
	if err != nil {
		return nil, err
	}
	r.History(u)
	s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), r.Members.Len())
	r.Send(message.NewAnnounceMsg(s))
	return member, nil
}

// Leave the room as a user, will announce. Mostly used during setup.
func (r *Room) Leave(u *message.User) error {
	err := r.Members.Remove(u.ID())
	if err != nil {
		return err
	}
	s := fmt.Sprintf("%s left. (After %s)", u.Name(), humantime.Since(u.Joined()))
	r.Send(message.NewAnnounceMsg(s))
	return nil
}

// Rename member with a new identity. This will not call rename on the member.
func (r *Room) Rename(oldID string, u message.Identifier) error {
	if u.ID() == "" {
		return ErrInvalidName
	}
	err := r.Members.Replace(oldID, set.Itemize(u.ID(), u))
	if err != nil {
		return err
	}

	s := fmt.Sprintf("%s is now known as %s.", oldID, u.ID())
	r.Send(message.NewAnnounceMsg(s))
	return nil
}

// Member returns a corresponding Member object to a User if the Member is
// present in this room.
func (r *Room) Member(u *message.User) (*Member, bool) {
	m, ok := r.MemberByID(u.ID())
	if !ok {
		return nil, false
	}
	// Check that it's the same user
	if m.User != u {
		return nil, false
	}
	return m, true
}

// MemberByID Gets a member by an id / name
func (r *Room) MemberByID(id string) (*Member, bool) {
	m, err := r.Members.Get(id)
	if err != nil {
		return nil, false
	}
	return m.Value().(*Member), true
}

// IsOp returns whether a user is an operator in this room.
func (r *Room) IsOp(u *message.User) bool {
	m, ok := r.Member(u)
	if !ok {
		return false
	}
	return m.IsOp
}

// Topic of the room.
func (r *Room) Topic() string {
	return r.topic
}

// SetTopic will set the topic of the room.
func (r *Room) SetTopic(s string) {
	r.topic = s
}

// NamesPrefix lists all members' names with a given prefix, used to query
// for autocompletion purposes. Sorted by which user was last active.
func (r *Room) NamesPrefix(prefix string) []string {
	items := r.Members.ListPrefix(prefix)

	// Sort results by recently active
	users := make([]*message.User, 0, len(items))
	for _, item := range items {
		users = append(users, item.Value().(*Member).User)
	}
	sort.Sort(message.RecentActiveUsers(users))

	// Pull out names
	names := make([]string, 0, len(items))
	for _, user := range users {
		names = append(names, user.Name())
	}
	return names
}
<|endoftext|>


<commit_before>package controllers

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"

	"github.com/eirka/eirka-libs/audit"
	e "github.com/eirka/eirka-libs/errors"
	"github.com/eirka/eirka-libs/redis"
	"github.com/eirka/eirka-libs/user"

	"github.com/eirka/eirka-post/models"
)

// Add tag input on image page
type addTagForm struct {
	Ib    uint `json:"ib" binding:"required"`
	Tag   uint `json:"tag" binding:"required"`
	Image uint `json:"image" binding:"required"`
}

// AddTagController handles the addition of a tag to an image
func AddTagController(c *gin.Context) {
	var err error
	var atf addTagForm

	// get userdata from session middleware
	userdata := c.MustGet("userdata").(user.User)

	err = c.Bind(&atf)
	if err != nil {
		c.JSON(e.ErrorMessage(e.ErrInvalidParam))
		c.Error(err).SetMeta("AddTagController.Bind")
		return
	}

	// Set parameters to AddTagModel
	m := models.AddTagModel{
		Ib:    atf.Ib,
		Tag:   atf.Tag,
		Image: atf.Image,
	}

	// Validate input parameters
	err = m.ValidateInput()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error_message": err.Error()})
		c.Error(err).SetMeta("AddTagController.ValidateInput")
		return
	}

	// Check image for correct ib and tag for duplicate
	err = m.Status()
	if err == e.ErrDuplicateTag || err == e.ErrNotFound {
		c.JSON(http.StatusBadRequest, gin.H{"error_message": err.Error()})
		c.Error(err).SetMeta("AddTagController.Status")
		return
	} else if err != nil {
		c.JSON(e.ErrorMessage(e.ErrInternalError))
		c.Error(err).SetMeta("AddTagController.Status")
		return
	}

	// Post data
	err = m.Post()
	if err != nil {
		c.JSON(e.ErrorMessage(e.ErrInternalError))
		c.Error(err).SetMeta("AddTagController.Post")
		return
	}

	// Initialize cache handle
	cache := redis.RedisCache

	// Delete redis stuff
	tags_key := fmt.Sprintf("%s:%d", "tags", m.Ib)
	tag_key := fmt.Sprintf("%s:%d:%d", "tag", m.Ib, m.Tag)
	image_key := fmt.Sprintf("%s:%d", "image", m.Ib)

	err = cache.Delete(tags_key, tag_key, image_key)
	if err != nil {
		c.JSON(e.ErrorMessage(e.ErrInternalError))
		c.Error(err).SetMeta("AddTagController.cache.Delete")
		return
	}

	c.JSON(http.StatusOK, gin.H{"success_message": audit.AuditAddTag})

	audit := audit.Audit{
		User:   userdata.Id,
		Ib:     m.Ib,
		Type:   audit.BoardLog,
		Ip:     c.ClientIP(),
		Action: audit.AuditAddTag,
		Info:   fmt.Sprintf("%d", m.Image),
	}

	// submit audit
	err = audit.Submit()
	if err != nil {
		c.Error(err).SetMeta("AddTagController.audit.Submit")
	}

	return

}
<commit_msg>add controller tests<commit_after>package controllers

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"

	"github.com/eirka/eirka-libs/audit"
	e "github.com/eirka/eirka-libs/errors"
	"github.com/eirka/eirka-libs/redis"
	"github.com/eirka/eirka-libs/user"

	"github.com/eirka/eirka-post/models"
)

// Add tag input on image page
type addTagForm struct {
	Ib    uint `json:"ib" binding:"required"`
	Tag   uint `json:"tag" binding:"required"`
	Image uint `json:"image" binding:"required"`
}

// AddTagController handles the addition of a tag to an image
func AddTagController(c *gin.Context) {
	var err error
	var atf addTagForm

	// get userdata from session middleware
	userdata := c.MustGet("userdata").(user.User)

	err = c.Bind(&atf)
	if err != nil {
		c.JSON(e.ErrorMessage(e.ErrInvalidParam))
		c.Error(err).SetMeta("AddTagController.Bind")
		return
	}

	// Set parameters to AddTagModel
	m := models.AddTagModel{
		Ib:    atf.Ib,
		Tag:   atf.Tag,
		Image: atf.Image,
	}

	// Validate input parameters
	err = m.ValidateInput()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error_message": err.Error()})
		c.Error(err).SetMeta("AddTagController.ValidateInput")
		return
	}

	// Check image for correct ib and tag for duplicate
	err = m.Status()
	if err == e.ErrDuplicateTag || err == e.ErrNotFound {
		c.JSON(http.StatusBadRequest, gin.H{"error_message": err.Error()})
		c.Error(err).SetMeta("AddTagController.Status")
		return
	} else if err != nil {
		c.JSON(e.ErrorMessage(e.ErrInternalError))
		c.Error(err).SetMeta("AddTagController.Status")
		return
	}

	// Post data
	err = m.Post()
	if err != nil {
		c.JSON(e.ErrorMessage(e.ErrInternalError))
		c.Error(err).SetMeta("AddTagController.Post")
		return
	}

	// Initialize cache handle
	cache := redis.RedisCache

	// Delete redis stuff
	tags_key := fmt.Sprintf("%s:%d", "tags", m.Ib)
	tag_key := fmt.Sprintf("%s:%d:%d", "tag", m.Ib, m.Tag)
	image_key := fmt.Sprintf("%s:%d", "image", m.Ib)

	err = cache.Delete(tags_key, tag_key, image_key)
	if err != nil {
		c.JSON(e.ErrorMessage(e.ErrInternalError))
		c.Error(err).SetMeta("AddTagController.cache.Delete")
		return
	}

	c.JSON(http.StatusOK, gin.H{"success_message": audit.AuditAddTag})

	audit := audit.Audit{
		User:   userdata.Id,
		Ib:     m.Ib,
		Type:   audit.BoardLog,
		Ip:     c.ClientIP(),
		Action: audit.AuditAddTag,
		Info:   fmt.Sprintf("%d", m.Image),
	}

	fmt.Println(audit)

	// submit audit
	err = audit.Submit()
	if err != nil {
		c.Error(err).SetMeta("AddTagController.audit.Submit")
	}

	return

}
<|endoftext|>


<commit_before>package controllers

import (
	"sync"

	"github.com/intervention-engine/fhir/server"
	"github.com/intervention-engine/ie/middleware"
	"github.com/intervention-engine/ie/notifications"
	"github.com/intervention-engine/ie/subscription"
)

func RegisterRoutes(s *server.FHIRServer, selfURL, riskServiceEndpoint string) func() {
	workerChannel := make(chan subscription.ResourceUpdateMessage)
	watch := subscription.GenerateResourceWatch(workerChannel)
	var wg sync.WaitGroup
	wg.Add(1)
	go subscription.NotifySubscribers(workerChannel, selfURL, &wg)

	s.AddMiddleware("Group", middleware.AuthHandler())

	s.AddMiddleware("Patient", middleware.AuthHandler())

	s.AddMiddleware("Condition", watch)

	s.AddMiddleware("MedicationStatement", watch)

	s.AddMiddleware("Encounter", watch)

	s.AddMiddleware("Batch", watch)

	// Setup the notification handler to use the default notification definitions (and then register it)
	notificationHandler := &middleware.NotificationHandler{Registry: notifications.DefaultNotificationDefinitionRegistry}
	s.AddMiddleware("Encounter", notificationHandler.Handle())

	s.Echo.Get("/GroupList/:id", PatientListHandler)
	s.Echo.Post("/InstaCountAll", InstaCountAllHandler)
	s.Echo.Get("/NotificationCount", NotificationCountHandler)
	s.Echo.Get("/Pie/:id", GeneratePieHandler(riskServiceEndpoint))
	s.Echo.Post("/CodeLookup", CodeLookup)

	login := s.Echo.Group("/login")
	login.Post("", LoginHandler)
	login.Delete("", LogoutHandler)

	return func() {
		stopNotifier(workerChannel, &wg)
	}
}

func stopNotifier(wc chan subscription.ResourceUpdateMessage, wg *sync.WaitGroup) {
	close(wc)
	wg.Wait()
}
<commit_msg>Disable auth on routes because new risk services need access to Patient route.  This is TEMPORARY and should be fixed when all routes are secured and tools (like risk services) are updated to pass along credentials.<commit_after>package controllers

import (
	"sync"

	"github.com/intervention-engine/fhir/server"
	"github.com/intervention-engine/ie/middleware"
	"github.com/intervention-engine/ie/notifications"
	"github.com/intervention-engine/ie/subscription"
)

func RegisterRoutes(s *server.FHIRServer, selfURL, riskServiceEndpoint string) func() {
	workerChannel := make(chan subscription.ResourceUpdateMessage)
	watch := subscription.GenerateResourceWatch(workerChannel)
	var wg sync.WaitGroup
	wg.Add(1)
	go subscription.NotifySubscribers(workerChannel, selfURL, &wg)

	// Turn off auth because risk services need to connect to Patient endpoint now.
	// This is TEMPORARARY, as there is a story to apply auth to everything and
	// update risk services and other tools to pass credentials through
	//s.AddMiddleware("Group", middleware.AuthHandler())
	//s.AddMiddleware("Patient", middleware.AuthHandler())

	s.AddMiddleware("Condition", watch)

	s.AddMiddleware("MedicationStatement", watch)

	s.AddMiddleware("Encounter", watch)

	s.AddMiddleware("Batch", watch)

	// Setup the notification handler to use the default notification definitions (and then register it)
	notificationHandler := &middleware.NotificationHandler{Registry: notifications.DefaultNotificationDefinitionRegistry}
	s.AddMiddleware("Encounter", notificationHandler.Handle())

	s.Echo.Get("/GroupList/:id", PatientListHandler)
	s.Echo.Post("/InstaCountAll", InstaCountAllHandler)
	s.Echo.Get("/NotificationCount", NotificationCountHandler)
	s.Echo.Get("/Pie/:id", GeneratePieHandler(riskServiceEndpoint))
	s.Echo.Post("/CodeLookup", CodeLookup)

	login := s.Echo.Group("/login")
	login.Post("", LoginHandler)
	login.Delete("", LogoutHandler)

	return func() {
		stopNotifier(workerChannel, &wg)
	}
}

func stopNotifier(wc chan subscription.ResourceUpdateMessage, wg *sync.WaitGroup) {
	close(wc)
	wg.Wait()
}
<|endoftext|>


<commit_before>// +build !js

package websocket

import (
	"io"
	"net"
	"sync"
	"time"

	ws "github.com/gorilla/websocket"
)

// Conn implements net.Conn interface for gorilla/websocket.
type Conn struct {
	*ws.Conn
	DefaultMessageType int
	reader             io.Reader
	closeOnce          sync.Once
	mx                 sync.Mutex
}

func (c *Conn) Read(b []byte) (int, error) {
	if c.reader == nil {
		if err := c.prepNextReader(); err != nil {
			return 0, err
		}
	}

	for {
		n, err := c.reader.Read(b)
		switch err {
		case io.EOF:
			c.reader = nil

			if n > 0 {
				return n, nil
			}

			if err := c.prepNextReader(); err != nil {
				return 0, err
			}

			// explicitly looping
		default:
			return n, err
		}
	}
}

func (c *Conn) prepNextReader() error {
	t, r, err := c.Conn.NextReader()
	if err != nil {
		if wserr, ok := err.(*ws.CloseError); ok {
			if wserr.Code == 1000 || wserr.Code == 1005 {
				return io.EOF
			}
		}
		return err
	}

	if t == ws.CloseMessage {
		return io.EOF
	}

	c.reader = r
	return nil
}

func (c *Conn) Write(b []byte) (n int, err error) {
	c.mx.Lock()
	defer c.mx.Unlock()

	if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil {
		return 0, err
	}

	return len(b), nil
}

// Close closes the connection. Only the first call to Close will receive the
// close error, subsequent and concurrent calls will return nil.
// This method is thread-safe.
func (c *Conn) Close() error {
	c.mx.Lock()
	defer c.mx.Unlock()

	var err error
	c.closeOnce.Do(func() {
		err1 := c.Conn.WriteControl(
			ws.CloseMessage,
			ws.FormatCloseMessage(ws.CloseNormalClosure, "closed"),
			time.Now().Add(GracefulCloseTimeout),
		)
		err2 := c.Conn.Close()
		switch {
		case err1 != nil:
			err = err1
		case err2 != nil:
			err = err2
		}
	})
	return err
}

func (c *Conn) LocalAddr() net.Addr {
	return NewAddr(c.Conn.LocalAddr().String())
}

func (c *Conn) RemoteAddr() net.Addr {
	return NewAddr(c.Conn.RemoteAddr().String())
}

func (c *Conn) SetDeadline(t time.Time) error {
	if err := c.SetReadDeadline(t); err != nil {
		return err
	}

	return c.SetWriteDeadline(t)
}

func (c *Conn) SetReadDeadline(t time.Time) error {
	return c.Conn.SetReadDeadline(t)
}

func (c *Conn) SetWriteDeadline(t time.Time) error {
	return c.Conn.SetWriteDeadline(t)
}

// NewConn creates a Conn given a regular gorilla/websocket Conn.
func NewConn(raw *ws.Conn) *Conn {
	return &Conn{
		Conn:               raw,
		DefaultMessageType: ws.BinaryMessage,
	}
}
<commit_msg>Revert "add mutex for write/close"<commit_after>// +build !js

package websocket

import (
	"io"
	"net"
	"sync"
	"time"

	ws "github.com/gorilla/websocket"
)

// Conn implements net.Conn interface for gorilla/websocket.
type Conn struct {
	*ws.Conn
	DefaultMessageType int
	reader             io.Reader
	closeOnce          sync.Once
}

func (c *Conn) Read(b []byte) (int, error) {
	if c.reader == nil {
		if err := c.prepNextReader(); err != nil {
			return 0, err
		}
	}

	for {
		n, err := c.reader.Read(b)
		switch err {
		case io.EOF:
			c.reader = nil

			if n > 0 {
				return n, nil
			}

			if err := c.prepNextReader(); err != nil {
				return 0, err
			}

			// explicitly looping
		default:
			return n, err
		}
	}
}

func (c *Conn) prepNextReader() error {
	t, r, err := c.Conn.NextReader()
	if err != nil {
		if wserr, ok := err.(*ws.CloseError); ok {
			if wserr.Code == 1000 || wserr.Code == 1005 {
				return io.EOF
			}
		}
		return err
	}

	if t == ws.CloseMessage {
		return io.EOF
	}

	c.reader = r
	return nil
}

func (c *Conn) Write(b []byte) (n int, err error) {
	if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil {
		return 0, err
	}

	return len(b), nil
}

// Close closes the connection. Only the first call to Close will receive the
// close error, subsequent and concurrent calls will return nil.
// This method is thread-safe.
func (c *Conn) Close() error {
	var err error
	c.closeOnce.Do(func() {
		err1 := c.Conn.WriteControl(
			ws.CloseMessage,
			ws.FormatCloseMessage(ws.CloseNormalClosure, "closed"),
			time.Now().Add(GracefulCloseTimeout),
		)
		err2 := c.Conn.Close()
		switch {
		case err1 != nil:
			err = err1
		case err2 != nil:
			err = err2
		}
	})
	return err
}

func (c *Conn) LocalAddr() net.Addr {
	return NewAddr(c.Conn.LocalAddr().String())
}

func (c *Conn) RemoteAddr() net.Addr {
	return NewAddr(c.Conn.RemoteAddr().String())
}

func (c *Conn) SetDeadline(t time.Time) error {
	if err := c.SetReadDeadline(t); err != nil {
		return err
	}

	return c.SetWriteDeadline(t)
}

func (c *Conn) SetReadDeadline(t time.Time) error {
	return c.Conn.SetReadDeadline(t)
}

func (c *Conn) SetWriteDeadline(t time.Time) error {
	return c.Conn.SetWriteDeadline(t)
}

// NewConn creates a Conn given a regular gorilla/websocket Conn.
func NewConn(raw *ws.Conn) *Conn {
	return &Conn{
		Conn:               raw,
		DefaultMessageType: ws.BinaryMessage,
	}
}
<|endoftext|>


<commit_before>// Tool to explain transactions either individually, in a ledger or belonging to an account.
package main

import (
	"bufio"
	"bytes"
	"encoding/hex"
	"flag"
	"fmt"
	"os"
	"regexp"
	"strconv"

	"github.com/golang/glog"
	"github.com/rubblelabs/ripple/data"
	"github.com/rubblelabs/ripple/terminal"
	"github.com/rubblelabs/ripple/websockets"
)

const usage = `Usage: explain [tx hash|ledger sequence|ripple address|-] [options]

Examples:

explain 6000000
	Explain all transactions for ledger 6000000

explain rrpNnNLKrartuEqfJGpqyDwPj1AFPg9vn1
	Explain all transactions for account rrpNnNLKrartuEqfJGpqyDwPj1AFPg9vn1

explain 955A4C0B7C66FC97EA4C72634CDCDBF50BB17AAA647EC6C8C592788E5B95173C
	Explain transaction 955A4C0B7C66FC97EA4C72634CDCDBF50BB17AAA647EC6C8C592788E5B95173C

explain -
	Explain binary transactions received through stdin

Options:
`

var argumentRegex = regexp.MustCompile(`(^[0-9a-fA-F]{64}$)|(^\d+$)|(^[r][a-km-zA-HJ-NP-Z0-9]{26,34}$)|(-)`)

var (
	flags        = flag.CommandLine
	host         = flags.String("host", "wss://s-east.ripple.com:443", "websockets host")
	trades       = flag.Bool("t", false, "hide trades")
	balances     = flag.Bool("b", false, "hide balances")
	paths        = flag.Bool("p", false, "hide paths")
	transactions = flag.Bool("tx", false, "hide transactions")
	pageSize     = flag.Int("page_size", 20, "page size for account_tx requests")
)

func showUsage() {
	fmt.Println(usage)
	flags.PrintDefaults()
	os.Exit(1)
}

func checkErr(err error) {
	if err != nil {
		terminal.Println(err.Error(), terminal.Default)
		os.Exit(1)
	}
}

func explain(txm *data.TransactionWithMetaData, flag terminal.Flag) {
	if !*transactions {
		terminal.Println(txm, flag)
	}
	if !*paths {
		for _, path := range txm.PathSet() {
			terminal.Println(path, flag|terminal.Indent)
		}
	}
	if !*trades {
		trades, err := data.NewTradeSlice(txm)
		checkErr(err)
		for _, trade := range trades {
			terminal.Println(trade, flag|terminal.Indent)
		}
	}
	if !*balances {
		balances, err := txm.Balances()
		checkErr(err)
		for _, balance := range balances {
			terminal.Println(balance, flag|terminal.Indent)
		}
	}
}

func main() {
	if len(os.Args) == 1 {
		showUsage()
	}
	flags.Parse(os.Args[2:])
	matches := argumentRegex.FindStringSubmatch(os.Args[1])
	r, err := websockets.NewRemote(*host)
	checkErr(err)
	glog.Infoln("Connected to: ", *host)
	switch {
	case len(matches) == 0:
		showUsage()
	case len(matches[1]) > 0:
		hash, err := data.NewHash256(matches[1])
		checkErr(err)
		fmt.Println("Getting transaction: ", hash.String())
		result, err := r.Tx(*hash)
		checkErr(err)
		explain(&result.TransactionWithMetaData, terminal.Default)
	case len(matches[2]) > 0:
		seq, err := strconv.ParseUint(matches[2], 10, 32)
		checkErr(err)
		ledger, err := r.Ledger(seq, true)
		checkErr(err)
		fmt.Println("Getting transactions for: ", seq)
		for _, txm := range ledger.Ledger.Transactions {
			explain(txm, terminal.Default)
		}
	case len(matches[3]) > 0:
		account, err := data.NewAccountFromAddress(matches[3])
		checkErr(err)
		fmt.Println("Getting transactions for: ", account.String())
		for txm := range r.AccountTx(*account, *pageSize) {
			explain(txm, terminal.ShowLedgerSequence)
		}
	case len(matches[4]) > 0:
		r := bufio.NewReader(os.Stdin)
		for line, err := r.ReadString('\n'); err == nil; line, err = r.ReadString('\n') {
			// TODO: Accept nodeid:nodedata format
			b, err := hex.DecodeString(line[:len(line)-1])
			checkErr(err)
			var nodeid data.Hash256
			v, err := data.ReadPrefix(bytes.NewReader(b), nodeid)
			checkErr(err)
			terminal.Println(v, terminal.Default)
		}
	}
}
<commit_msg>Fix explain tool<commit_after>// Tool to explain transactions either individually, in a ledger or belonging to an account.
package main

import (
	"bufio"
	"bytes"
	"encoding/hex"
	"flag"
	"fmt"
	"os"
	"regexp"
	"strconv"

	"github.com/golang/glog"
	"github.com/rubblelabs/ripple/data"
	"github.com/rubblelabs/ripple/terminal"
	"github.com/rubblelabs/ripple/websockets"
)

const usage = `Usage: explain [tx hash|ledger sequence|ripple address|-] [options]

Examples:

explain 6000000
	Explain all transactions for ledger 6000000

explain rrpNnNLKrartuEqfJGpqyDwPj1AFPg9vn1
	Explain all transactions for account rrpNnNLKrartuEqfJGpqyDwPj1AFPg9vn1

explain 955A4C0B7C66FC97EA4C72634CDCDBF50BB17AAA647EC6C8C592788E5B95173C
	Explain transaction 955A4C0B7C66FC97EA4C72634CDCDBF50BB17AAA647EC6C8C592788E5B95173C

explain -
	Explain binary transactions received through stdin

Options:
`

var argumentRegex = regexp.MustCompile(`(^[0-9a-fA-F]{64}$)|(^\d+$)|(^[r][a-km-zA-HJ-NP-Z0-9]{26,34}$)|(-)`)

var (
	flags        = flag.CommandLine
	host         = flags.String("host", "wss://s-east.ripple.com:443", "websockets host")
	trades       = flag.Bool("t", false, "hide trades")
	balances     = flag.Bool("b", false, "hide balances")
	paths        = flag.Bool("p", false, "hide paths")
	transactions = flag.Bool("tx", false, "hide transactions")
	pageSize     = flag.Int("page_size", 20, "page size for account_tx requests")
)

func showUsage() {
	fmt.Println(usage)
	flags.PrintDefaults()
	os.Exit(1)
}

func checkErr(err error) {
	if err != nil {
		terminal.Println(err.Error(), terminal.Default)
		os.Exit(1)
	}
}

func explain(txm *data.TransactionWithMetaData, flag terminal.Flag) {
	if !*transactions {
		terminal.Println(txm, flag)
	}
	if !*paths {
		for _, path := range txm.PathSet() {
			terminal.Println(path, flag|terminal.Indent)
		}
	}
	if !*trades {
		trades, err := data.NewTradeSlice(txm)
		checkErr(err)
		for _, trade := range trades {
			terminal.Println(trade, flag|terminal.Indent)
		}
	}
	if !*balances {
		balances, err := txm.Balances()
		checkErr(err)
		for _, balance := range balances {
			terminal.Println(balance, flag|terminal.Indent)
		}
	}
}

func main() {
	if len(os.Args) == 1 {
		showUsage()
	}
	flags.Parse(os.Args[2:])
	matches := argumentRegex.FindStringSubmatch(os.Args[1])
	r, err := websockets.NewRemote(*host)
	checkErr(err)
	glog.Infoln("Connected to: ", *host)
	switch {
	case len(matches) == 0:
		showUsage()
	case len(matches[1]) > 0:
		hash, err := data.NewHash256(matches[1])
		checkErr(err)
		fmt.Println("Getting transaction: ", hash.String())
		result, err := r.Tx(*hash)
		checkErr(err)
		explain(&result.TransactionWithMetaData, terminal.Default)
	case len(matches[2]) > 0:
		seq, err := strconv.ParseUint(matches[2], 10, 32)
		checkErr(err)
		ledger, err := r.Ledger(seq, true)
		checkErr(err)
		fmt.Println("Getting transactions for: ", seq)
		for _, txm := range ledger.Ledger.Transactions {
			explain(txm, terminal.Default)
		}
	case len(matches[3]) > 0:
		account, err := data.NewAccountFromAddress(matches[3])
		checkErr(err)
		fmt.Println("Getting transactions for: ", account.String())
		for txm := range r.AccountTx(*account, *pageSize, -1, -1) {
			explain(txm, terminal.ShowLedgerSequence)
		}
	case len(matches[4]) > 0:
		r := bufio.NewReader(os.Stdin)
		for line, err := r.ReadString('\n'); err == nil; line, err = r.ReadString('\n') {
			// TODO: Accept nodeid:nodedata format
			b, err := hex.DecodeString(line[:len(line)-1])
			checkErr(err)
			var nodeid data.Hash256
			v, err := data.ReadPrefix(bytes.NewReader(b), nodeid)
			checkErr(err)
			terminal.Println(v, terminal.Default)
		}
	}
}
<|endoftext|>


<commit_before>package persist

import (
	"github.com/dancannon/gorethink"
	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"github.com/satori/go.uuid"
	"go.pachyderm.com/pachyderm/src/pps"
	"go.pedge.io/google-protobuf"
	"go.pedge.io/pkg/time"
	"go.pedge.io/proto/time"
	"golang.org/x/net/context"
)

const (
	jobsTable        Table = "jobs"
	jobStatusesTable Table = "job_statuses"
	jobLogsTable     Table = "job_logs"
	pipelinesTable   Table = "pipelines"

	idPrimaryKey PrimaryKey = "id"

	pipelineIDIndex Index = "pipeline_id"
	jobIDIndex      Index = "job_id"
	typeIndex       Index = "type"
	nameIndex       Index = "name"
)

type Table string
type PrimaryKey string
type Index string

var (
	marshaller = &jsonpb.Marshaler{}

	tables = []Table{
		jobsTable,
		jobStatusesTable,
		jobLogsTable,
		pipelinesTable,
	}

	tableToTableCreateOpts = map[Table][]gorethink.TableCreateOpts{
		jobsTable: []gorethink.TableCreateOpts{
			gorethink.TableCreateOpts{
				PrimaryKey: idPrimaryKey,
			},
		},
		jobStatusesTable: []gorethink.TableCreateOpts{
			gorethink.TableCreateOpts{
				PrimaryKey: idPrimaryKey,
			},
		},
		jobLogsTable: []gorethink.TableCreateOpts{
			gorethink.TableCreateOpts{
				PrimaryKey: idPrimaryKey,
			},
		},
		pipelinesTable: []gorethink.TableCreateOpts{
			gorethink.TableCreateOpts{
				PrimaryKey: idPrimaryKey,
			},
		},
	}

	tableToIndexes = map[Table][]Index{
		jobsTable: []Index{
			pipelineIDIndex,
		},
		jobStatusesTable: []Index{
			jobIDIndex,
			typeIndex,
		},
		jobLogsTable: []Index{
			jobIDIndex,
		},
		pipelinesTable: []Index{
			nameIndex,
		},
	}
)

// InitDBs prepares a RethinkDB instance to be used by the rethink server.
// Rethink servers will error if they are pointed at databases that haven't had InitDBs run on them.
// InitDBs should only be run once per instance of RethinkDB, it will error if it is called a second time.
func InitDBs(address string, databaseName string) error {
	session, err := gorethink.Connect(gorethink.ConnectOpts{Address: address})
	if err != nil {
		return err
	}
	if _, err := gorethink.DBCreate(databaseName).RunWrite(session); err != nil {
		return err
	}
	for _, table := range tables {
		tableCreateOpts, ok := tableToTableCreateOpts[table]
		if ok {
			if _, err := gorethink.DB(databaseName).TableCreate(table, tableCreateOpts...).RunWrite(session); err != nil {
				return err
			}
		} else {
			if _, err := gorethink.DB(databaseName).TableCreate(table).RunWrite(session); err != nil {
				return err
			}
		}
	}
	for table, indexes := range tableToIndexes {
		for _, index := range indexes {
			if _, err := gorethink.DB(databaseName).Table(table).IndexCreate(index).RunWrite(session); err != nil {
				return err
			}
		}
	}
	return nil
}

type rethinkAPIServer struct {
	session      *gorethink.Session
	databaseName string
	timer        pkgtime.Timer
}

func newRethinkAPIServer(address string, databaseName string) (*rethinkAPIServer, error) {
	session, err := gorethink.Connect(gorethink.ConnectOpts{Address: address})
	if err != nil {
		return nil, err
	}
	return &rethinkAPIServer{
		session,
		databaseName,
		pkgtime.NewSystemTimer(),
	}, nil
}

func (a *rethinkAPIServer) Close() error {
	return a.session.Close()
}

// id cannot be set
// a JobStatus of type created will also be created
func (a *rethinkAPIServer) CreateJob(ctx context.Context, request *Job) (*Job, error) {
	if request.Id != "" {
		return nil, ErrIDSet
	}
	request.Id = newID()
	if err := a.insertMessage(jobsTable, request); err != nil {
		return nil, err
	}
	// TODO(pedge): not transactional
	if _, err := a.CreateJobStatus(
		ctx,
		&JobStatus{
			JobId: request.Id,
			Type:  pps.JobStatusType_JOB_STATUS_TYPE_CREATED,
		},
	); err != nil {
		return nil, err
	}
	return request, nil
}

func (a *rethinkAPIServer) GetJobByID(ctx context.Context, request *google_protobuf.StringValue) (*Job, error) {
	job := &Job{}
	if err := a.getMessageByPrimaryKey(jobsTable, request.Value, job); err != nil {
		return nil, err
	}
	return job, nil
}

func (a *rethinkAPIServer) GetJobsByPipelineID(ctx context.Context, request *google_protobuf.StringValue) (*Jobs, error) {
	jobObjs, err := a.getMessageByIndex(
		jobsTable,
		pipelineIDIndex,
		request.Value,
		func() proto.Message { return &Job{} },
	)
	if err != nil {
		return nil, err
	}
	jobs := make([]*Job, len(jobObjs))
	for i, jobObj := range jobObjs {
		jobs[i] = jobObj.(*Job)
	}
	return &Jobs{
		Job: jobs,
	}, nil
}

// id cannot be set
// timestamp cannot be set
func (a *rethinkAPIServer) CreateJobStatus(ctx context.Context, request *JobStatus) (*JobStatus, error) {
	if request.Id != "" {
		return nil, ErrIDSet
	}
	if request.Timestamp != nil {
		return nil, ErrTimestampSet
	}
	request.Id = newID()
	request.Timestamp = a.now()
	if err := a.insertMessage(jobStatusesTable, request); err != nil {
		return nil, err
	}
	return request, nil
}

// ordered by time, latest to earliest
func (a *rethinkAPIServer) GetJobStatusesByJobID(ctx context.Context, request *google_protobuf.StringValue) (*JobStatuses, error) {
	jobStatusObjs, err := a.getMessageByIndex(
		jobStatusesTable,
		jobIDIndex,
		request.Value,
		func() proto.Message { return &JobStatus{} },
		func(term gorethink.Term) gorethink.Term {
			return term.OrderBy(gorethink.Desc("timestamp"))
		},
	)
	if err != nil {
		return nil, err
	}
	jobStatuses := make([]*JobStatus, len(jobStatusObjs))
	for i, jobStatusObj := range jobStatusObjs {
		jobStatuses[i] = jobStatusObj.(*JobStatus)
	}
	return &JobStatuses{
		JobStatus: jobStatuses,
	}, nil
}

// id cannot be set
func (a *rethinkAPIServer) CreateJobLog(ctx context.Context, request *JobLog) (*JobLog, error) {
	return nil, nil
}

// ordered by time, latest to earliest
func (a *rethinkAPIServer) GetJobLogsByJobID(ctx context.Context, request *google_protobuf.StringValue) (*JobLogs, error) {
	return nil, nil
}

// id and previous_id cannot be set
// name must not already exist
func (a *rethinkAPIServer) CreatePipeline(ctx context.Context, request *Pipeline) (*Pipeline, error) {
	return nil, nil
}

// id and previous_id cannot be set
// timestamp cannot be set
// update by name, name must already exist
func (a *rethinkAPIServer) UpdatePipeline(ctx context.Context, request *Pipeline) (*Pipeline, error) {
	return nil, nil
}

func (a *rethinkAPIServer) GetPipelineByID(ctx context.Context, request *google_protobuf.StringValue) (*Pipeline, error) {
	return nil, nil
}

// ordered by time, latest to earliest
func (a *rethinkAPIServer) GetPipelinesByName(ctx context.Context, request *google_protobuf.StringValue) (*Pipeline, error) {
	return nil, nil
}

func (a *rethinkAPIServer) insertMessage(table Table, message proto.Message) error {
	data, err := marshaller.MarshalToString(message)
	if err != nil {
		return err
	}
	_, err = a.getTerm(table).Insert(gorethink.JSON(data)).RunWrite(a.session)
	return err
}

func (a *rethinkAPIServer) getMessageByPrimaryKey(table Table, Value interface{}, message proto.Message) error {
	cursor, err := a.getTerm(table).Get(Value).ToJSON().Run(a.session)
	if err != nil {
		return err
	}
	data := ""
	if !cursor.Next(&data) {
		return cursor.Err()
	}
	if err := jsonpb.UnmarshalString(data, message); err != nil {
		return err
	}
	return nil
}

func (a *rethinkAPIServer) getMessageByIndex(
	table Table,
	index Index,
	value interface{},
	messageConstructor func() proto.Message,
	modifiers ...func(gorethink.Term) gorethink.Term,
) ([]interface{}, error) {
	term := a.getTerm(table).GetAllByIndex(index, value).Map(func(row gorethink.Term) interface{} {
		return row.ToJSON()
	})
	for _, modifier := range modifiers {
		term = modifier(term)
	}
	cursor, err := term.Run(a.session)
	if err != nil {
		return nil, err
	}
	var data []string
	if err := cursor.All(&data); err != nil {
		return nil, err
	}
	result := make([]interface{}, len(data))
	for i, datum := range data {
		message := messageConstructor()
		if err := jsonpb.UnmarshalString(datum, message); err != nil {
			return nil, err
		}
		result[i] = message
	}
	return result, nil
}

func (a *rethinkAPIServer) getTerm(table Table) gorethink.Term {
	return gorethink.DB(a.databaseName).Table(table)
}

func (a *rethinkAPIServer) now() *google_protobuf.Timestamp {
	return prototime.TimeToTimestamp(a.timer.Now())
}

func rethinkToJSON(row gorethink.Term) interface{} {
	return row.ToJSON()
}

func newID() string {
	return uuid.NewV4().String()
}
<commit_msg>JogLog implementations<commit_after>package persist

import (
	"github.com/dancannon/gorethink"
	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"github.com/satori/go.uuid"
	"go.pachyderm.com/pachyderm/src/pps"
	"go.pedge.io/google-protobuf"
	"go.pedge.io/pkg/time"
	"go.pedge.io/proto/time"
	"golang.org/x/net/context"
)

const (
	jobsTable        Table = "jobs"
	jobStatusesTable Table = "job_statuses"
	jobLogsTable     Table = "job_logs"
	pipelinesTable   Table = "pipelines"

	idPrimaryKey PrimaryKey = "id"

	pipelineIDIndex Index = "pipeline_id"
	jobIDIndex      Index = "job_id"
	typeIndex       Index = "type"
	nameIndex       Index = "name"
)

type Table string
type PrimaryKey string
type Index string

var (
	marshaller = &jsonpb.Marshaler{}

	tables = []Table{
		jobsTable,
		jobStatusesTable,
		jobLogsTable,
		pipelinesTable,
	}

	tableToTableCreateOpts = map[Table][]gorethink.TableCreateOpts{
		jobsTable: []gorethink.TableCreateOpts{
			gorethink.TableCreateOpts{
				PrimaryKey: idPrimaryKey,
			},
		},
		jobStatusesTable: []gorethink.TableCreateOpts{
			gorethink.TableCreateOpts{
				PrimaryKey: idPrimaryKey,
			},
		},
		jobLogsTable: []gorethink.TableCreateOpts{
			gorethink.TableCreateOpts{
				PrimaryKey: idPrimaryKey,
			},
		},
		pipelinesTable: []gorethink.TableCreateOpts{
			gorethink.TableCreateOpts{
				PrimaryKey: idPrimaryKey,
			},
		},
	}

	tableToIndexes = map[Table][]Index{
		jobsTable: []Index{
			pipelineIDIndex,
		},
		jobStatusesTable: []Index{
			jobIDIndex,
			typeIndex,
		},
		jobLogsTable: []Index{
			jobIDIndex,
		},
		pipelinesTable: []Index{
			nameIndex,
		},
	}
)

// InitDBs prepares a RethinkDB instance to be used by the rethink server.
// Rethink servers will error if they are pointed at databases that haven't had InitDBs run on them.
// InitDBs should only be run once per instance of RethinkDB, it will error if it is called a second time.
func InitDBs(address string, databaseName string) error {
	session, err := gorethink.Connect(gorethink.ConnectOpts{Address: address})
	if err != nil {
		return err
	}
	if _, err := gorethink.DBCreate(databaseName).RunWrite(session); err != nil {
		return err
	}
	for _, table := range tables {
		tableCreateOpts, ok := tableToTableCreateOpts[table]
		if ok {
			if _, err := gorethink.DB(databaseName).TableCreate(table, tableCreateOpts...).RunWrite(session); err != nil {
				return err
			}
		} else {
			if _, err := gorethink.DB(databaseName).TableCreate(table).RunWrite(session); err != nil {
				return err
			}
		}
	}
	for table, indexes := range tableToIndexes {
		for _, index := range indexes {
			if _, err := gorethink.DB(databaseName).Table(table).IndexCreate(index).RunWrite(session); err != nil {
				return err
			}
		}
	}
	return nil
}

type rethinkAPIServer struct {
	session      *gorethink.Session
	databaseName string
	timer        pkgtime.Timer
}

func newRethinkAPIServer(address string, databaseName string) (*rethinkAPIServer, error) {
	session, err := gorethink.Connect(gorethink.ConnectOpts{Address: address})
	if err != nil {
		return nil, err
	}
	return &rethinkAPIServer{
		session,
		databaseName,
		pkgtime.NewSystemTimer(),
	}, nil
}

func (a *rethinkAPIServer) Close() error {
	return a.session.Close()
}

// id cannot be set
// a JobStatus of type created will also be created
func (a *rethinkAPIServer) CreateJob(ctx context.Context, request *Job) (*Job, error) {
	if request.Id != "" {
		return nil, ErrIDSet
	}
	request.Id = newID()
	if err := a.insertMessage(jobsTable, request); err != nil {
		return nil, err
	}
	// TODO(pedge): not transactional
	if _, err := a.CreateJobStatus(
		ctx,
		&JobStatus{
			JobId: request.Id,
			Type:  pps.JobStatusType_JOB_STATUS_TYPE_CREATED,
		},
	); err != nil {
		return nil, err
	}
	return request, nil
}

func (a *rethinkAPIServer) GetJobByID(ctx context.Context, request *google_protobuf.StringValue) (*Job, error) {
	job := &Job{}
	if err := a.getMessageByPrimaryKey(jobsTable, request.Value, job); err != nil {
		return nil, err
	}
	return job, nil
}

func (a *rethinkAPIServer) GetJobsByPipelineID(ctx context.Context, request *google_protobuf.StringValue) (*Jobs, error) {
	jobObjs, err := a.getMessageByIndex(
		jobsTable,
		pipelineIDIndex,
		request.Value,
		func() proto.Message { return &Job{} },
	)
	if err != nil {
		return nil, err
	}
	jobs := make([]*Job, len(jobObjs))
	for i, jobObj := range jobObjs {
		jobs[i] = jobObj.(*Job)
	}
	return &Jobs{
		Job: jobs,
	}, nil
}

// id cannot be set
// timestamp cannot be set
func (a *rethinkAPIServer) CreateJobStatus(ctx context.Context, request *JobStatus) (*JobStatus, error) {
	if request.Id != "" {
		return nil, ErrIDSet
	}
	if request.Timestamp != nil {
		return nil, ErrTimestampSet
	}
	request.Id = newID()
	request.Timestamp = a.now()
	if err := a.insertMessage(jobStatusesTable, request); err != nil {
		return nil, err
	}
	return request, nil
}

// ordered by time, latest to earliest
func (a *rethinkAPIServer) GetJobStatusesByJobID(ctx context.Context, request *google_protobuf.StringValue) (*JobStatuses, error) {
	jobStatusObjs, err := a.getMessageByIndex(
		jobStatusesTable,
		jobIDIndex,
		request.Value,
		func() proto.Message { return &JobStatus{} },
		func(term gorethink.Term) gorethink.Term {
			return term.OrderBy(gorethink.Desc("timestamp"))
		},
	)
	if err != nil {
		return nil, err
	}
	jobStatuses := make([]*JobStatus, len(jobStatusObjs))
	for i, jobStatusObj := range jobStatusObjs {
		jobStatuses[i] = jobStatusObj.(*JobStatus)
	}
	return &JobStatuses{
		JobStatus: jobStatuses,
	}, nil
}

// id cannot be set
// timestamp cannot be set
func (a *rethinkAPIServer) CreateJobLog(ctx context.Context, request *JobLog) (*JobLog, error) {
	if request.Id != "" {
		return nil, ErrIDSet
	}
	if request.Timestamp != nil {
		return nil, ErrTimestampSet
	}
	request.Id = newID()
	request.Timestamp = a.now()
	if err := a.insertMessage(jobLogsTable, request); err != nil {
		return nil, err
	}
	return request, nil
}

// ordered by time, latest to earliest
func (a *rethinkAPIServer) GetJobLogsByJobID(ctx context.Context, request *google_protobuf.StringValue) (*JobLogs, error) {
	jobLogObjs, err := a.getMessageByIndex(
		jobLogsTable,
		jobIDIndex,
		request.Value,
		func() proto.Message { return &JobLog{} },
		func(term gorethink.Term) gorethink.Term {
			return term.OrderBy(gorethink.Desc("timestamp"))
		},
	)
	if err != nil {
		return nil, err
	}
	jobLogs := make([]*JobLog, len(jobLogObjs))
	for i, jobLogObj := range jobLogObjs {
		jobLogs[i] = jobLogObj.(*JobLog)
	}
	return &JobLogs{
		JobLog: jobLogs,
	}, nil
}

// id and previous_id cannot be set
// name must not already exist
func (a *rethinkAPIServer) CreatePipeline(ctx context.Context, request *Pipeline) (*Pipeline, error) {
	return nil, nil
}

// id and previous_id cannot be set
// timestamp cannot be set
// update by name, name must already exist
func (a *rethinkAPIServer) UpdatePipeline(ctx context.Context, request *Pipeline) (*Pipeline, error) {
	return nil, nil
}

func (a *rethinkAPIServer) GetPipelineByID(ctx context.Context, request *google_protobuf.StringValue) (*Pipeline, error) {
	return nil, nil
}

// ordered by time, latest to earliest
func (a *rethinkAPIServer) GetPipelinesByName(ctx context.Context, request *google_protobuf.StringValue) (*Pipeline, error) {
	return nil, nil
}

func (a *rethinkAPIServer) insertMessage(table Table, message proto.Message) error {
	data, err := marshaller.MarshalToString(message)
	if err != nil {
		return err
	}
	_, err = a.getTerm(table).Insert(gorethink.JSON(data)).RunWrite(a.session)
	return err
}

func (a *rethinkAPIServer) getMessageByPrimaryKey(table Table, Value interface{}, message proto.Message) error {
	cursor, err := a.getTerm(table).Get(Value).ToJSON().Run(a.session)
	if err != nil {
		return err
	}
	data := ""
	if !cursor.Next(&data) {
		return cursor.Err()
	}
	if err := jsonpb.UnmarshalString(data, message); err != nil {
		return err
	}
	return nil
}

func (a *rethinkAPIServer) getMessageByIndex(
	table Table,
	index Index,
	value interface{},
	messageConstructor func() proto.Message,
	modifiers ...func(gorethink.Term) gorethink.Term,
) ([]interface{}, error) {
	term := a.getTerm(table).GetAllByIndex(index, value).Map(func(row gorethink.Term) interface{} {
		return row.ToJSON()
	})
	for _, modifier := range modifiers {
		term = modifier(term)
	}
	cursor, err := term.Run(a.session)
	if err != nil {
		return nil, err
	}
	var data []string
	if err := cursor.All(&data); err != nil {
		return nil, err
	}
	result := make([]interface{}, len(data))
	for i, datum := range data {
		message := messageConstructor()
		if err := jsonpb.UnmarshalString(datum, message); err != nil {
			return nil, err
		}
		result[i] = message
	}
	return result, nil
}

func (a *rethinkAPIServer) getTerm(table Table) gorethink.Term {
	return gorethink.DB(a.databaseName).Table(table)
}

func (a *rethinkAPIServer) now() *google_protobuf.Timestamp {
	return prototime.TimeToTimestamp(a.timer.Now())
}

func rethinkToJSON(row gorethink.Term) interface{} {
	return row.ToJSON()
}

func newID() string {
	return uuid.NewV4().String()
}
<|endoftext|>


<commit_before>package api

// StoragePoolsPost represents the fields of a new LXD storage pool
//
// API extension: storage
type StoragePoolsPost struct {
	StoragePoolPut `yaml:",inline"`

	Name   string `json:"name" yaml:"name"`
	Driver string `json:"driver" yaml:"driver"`
}

// StoragePool represents the fields of a LXD storage pool.
//
// API extension: storage
type StoragePool struct {
	StoragePoolPut `yaml:",inline"`

	Name   string   `json:"name" yaml:"name"`
	Driver string   `json:"driver" yaml:"driver"`
	UsedBy []string `json:"used_by" yaml:"used_by"`

	// API extension: clustering
	Status    string   `json:"status" yaml:"status"`
	Locations []string `json:"locations" yaml:"locations"`
}

// StoragePoolPut represents the modifiable fields of a LXD storage pool.
//
// API extension: storage
type StoragePoolPut struct {
	Config map[string]string `json:"config" yaml:"config"`

	// API extension: entity_description
	Description string `json:"description" yaml:"description"`
}

// Writable converts a full StoragePool struct into a StoragePoolPut struct
// (filters read-only fields).
func (storagePool *StoragePool) Writable() StoragePoolPut {
	return storagePool.StoragePoolPut
}
<commit_msg>shared/api/storage/pool: Adds storage pool status contants<commit_after>package api

// StoragePoolStatusPending storage pool is pending creation on other cluster nodes.
const StoragePoolStatusPending = "Pending"

// StoragePoolStatusCreated storage pool is fully created.
const StoragePoolStatusCreated = "Created"

// StoragePoolStatusErrored storage pool is in error status.
const StoragePoolStatusErrored = "Errored"

// StoragePoolStatusUnknown storage pool is in unknown status.
const StoragePoolStatusUnknown = "Unknown"

// StoragePoolsPost represents the fields of a new LXD storage pool
//
// API extension: storage
type StoragePoolsPost struct {
	StoragePoolPut `yaml:",inline"`

	Name   string `json:"name" yaml:"name"`
	Driver string `json:"driver" yaml:"driver"`
}

// StoragePool represents the fields of a LXD storage pool.
//
// API extension: storage
type StoragePool struct {
	StoragePoolPut `yaml:",inline"`

	Name   string   `json:"name" yaml:"name"`
	Driver string   `json:"driver" yaml:"driver"`
	UsedBy []string `json:"used_by" yaml:"used_by"`

	// API extension: clustering
	Status    string   `json:"status" yaml:"status"`
	Locations []string `json:"locations" yaml:"locations"`
}

// StoragePoolPut represents the modifiable fields of a LXD storage pool.
//
// API extension: storage
type StoragePoolPut struct {
	Config map[string]string `json:"config" yaml:"config"`

	// API extension: entity_description
	Description string `json:"description" yaml:"description"`
}

// Writable converts a full StoragePool struct into a StoragePoolPut struct
// (filters read-only fields).
func (storagePool *StoragePool) Writable() StoragePoolPut {
	return storagePool.StoragePoolPut
}
<|endoftext|>