<commit_before>package river

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/siddontang/go-mysql-elasticsearch/elastic"
	"github.com/siddontang/go-mysql/canal"
	"github.com/siddontang/go-mysql/schema"
	"github.com/siddontang/go/log"
)

const (
	syncInsertDoc = iota
	syncDeleteDoc
	syncUpdateDoc
)

type rowsEventHandler struct {
	r *River
}

func (h *rowsEventHandler) Do(e *canal.RowsEvent) error {
	rule, ok := h.r.rules[ruleKey(e.Table.Schema, e.Table.Name)]
	if !ok {
		return nil
	}

	var reqs []*elastic.BulkRequest
	var err error
	switch e.Action {
	case canal.InsertAction:
		reqs, err = h.r.makeInsertRequest(rule, e.Rows)
	case canal.DeleteAction:
		reqs, err = h.r.makeDeleteRequest(rule, e.Rows)
	case canal.UpdateAction:
		reqs, err = h.r.makeUpdateRequest(rule, e.Rows)
	default:
		return fmt.Errorf("invalid rows action %s", e.Action)
	}

	if err != nil {
		return fmt.Errorf("make %s ES request err %v", e.Action, err)
	}

	if err := h.r.doBulk(reqs); err != nil {
		log.Errorf("do ES bulks err %v, stop", err)
		return canal.ErrHandleInterrupted
	}

	return nil
}

func (h *rowsEventHandler) String() string {
	return "ESRiverRowsEventHandler"
}

// for insert and delete
func (r *River) makeRequest(rule *Rule, action string, rows [][]interface{}) ([]*elastic.BulkRequest, error) {
	reqs := make([]*elastic.BulkRequest, 0, len(rows))

	for _, values := range rows {
		id, err := r.getDocID(rule, values)
		if err != nil {
			return nil, err
		}

		req := &elastic.BulkRequest{Index: rule.Index, Type: rule.Type, ID: id}

		if action == canal.DeleteAction {
			req.Action = elastic.ActionDelete
			r.st.DeleteNum.Add(1)
		} else {
			r.makeInsertReqData(req, rule, values)
			r.st.InsertNum.Add(1)
		}

		reqs = append(reqs, req)
	}

	return reqs, nil
}

func (r *River) makeInsertRequest(rule *Rule, rows [][]interface{}) ([]*elastic.BulkRequest, error) {
	return r.makeRequest(rule, canal.InsertAction, rows)
}

func (r *River) makeDeleteRequest(rule *Rule, rows [][]interface{}) ([]*elastic.BulkRequest, error) {
	return r.makeRequest(rule, canal.DeleteAction, rows)
}

func (r *River) makeUpdateRequest(rule *Rule, rows [][]interface{}) ([]*elastic.BulkRequest, error) {
	if len(rows)%2 != 0 {
		return nil, fmt.Errorf("invalid update rows event, must have 2x rows, but %d", len(rows))
	}

	reqs := make([]*elastic.BulkRequest, 0, len(rows))

	for i := 0; i < len(rows); i += 2 {
		beforeID, err := r.getDocID(rule, rows[i])
		if err != nil {
			return nil, err
		}

		afterID, err := r.getDocID(rule, rows[i+1])

		if err != nil {
			return nil, err
		}

		req := &elastic.BulkRequest{Index: rule.Index, Type: rule.Type, ID: beforeID}

		if beforeID != afterID {
			req.Action = elastic.ActionDelete
			reqs = append(reqs, req)

			req = &elastic.BulkRequest{Index: rule.Index, Type: rule.Type, ID: afterID}
			r.makeInsertReqData(req, rule, rows[i+1])

			r.st.DeleteNum.Add(1)
			r.st.InsertNum.Add(1)
		} else {
			r.makeUpdateReqData(req, rule, rows[i], rows[i+1])
			r.st.UpdateNum.Add(1)
		}

		reqs = append(reqs, req)
	}

	return reqs, nil
}

func (r *River) makeReqColumnData(col *schema.TableColumn, value interface{}) interface{} {
	switch col.Type {
	case schema.TYPE_ENUM:
		switch value := value.(type) {
		case int64:
			// for binlog, ENUM may be int64, but for dump, enum is string
			eNum := value - 1
			if eNum < 0 || eNum >= int64(len(col.EnumValues)) {
				// we insert invalid enum value before, so return empty
				log.Warnf("invalid binlog enum index %d, for enum %v", eNum, col.EnumValues)
				return ""
			}

			return col.EnumValues[eNum]
		}
	case schema.TYPE_SET:
		switch value := value.(type) {
		case int64:
			// for binlog, SET may be int64, but for dump, SET is string
			bitmask := value
			sets := make([]string, 0, len(col.SetValues))
			for i, s := range col.SetValues {
				if bitmask&int64(1<<uint(i)) > 0 {
					sets = append(sets, s)
				}
			}
			return strings.Join(sets, ",")
		}
	}

	return value
}

func (r *River) makeInsertReqData(req *elastic.BulkRequest, rule *Rule, values []interface{}) {
	req.Data = make(map[string]interface{}, len(values))
	req.Action = elastic.ActionIndex

	for i, c := range rule.TableInfo.Columns {
		if name, ok := rule.FieldMapping[c.Name]; ok {
			// has custom field mapping
			req.Data[name] = r.makeReqColumnData(&c, values[i])
		} else {
			req.Data[c.Name] = r.makeReqColumnData(&c, values[i])
		}
	}
}

func (r *River) makeUpdateReqData(req *elastic.BulkRequest, rule *Rule,
	beforeValues []interface{}, afterValues []interface{}) {
	req.Data = make(map[string]interface{}, len(beforeValues))

	// maybe dangerous if something wrong delete before?
	req.Action = elastic.ActionUpdate

	for i, c := range rule.TableInfo.Columns {
		if beforeValues[i] == afterValues[i] {
			//nothing changed
			continue
		}
		if name, ok := rule.FieldMapping[c.Name]; ok {
			// has custom field mapping
			req.Data[name] = r.makeReqColumnData(&c, afterValues[i])
		} else {
			req.Data[c.Name] = r.makeReqColumnData(&c, afterValues[i])
		}
	}
}

// Get primary keys in one row and format them into a string
// PK must not be nil
func (r *River) getDocID(rule *Rule, row []interface{}) (string, error) {
	pks, err := canal.GetPKValues(rule.TableInfo, row)
	if err != nil {
		return "", err
	}

	var buf bytes.Buffer

	sep := ""
	for i, value := range pks {
		if value == nil {
			return "", fmt.Errorf("The %ds PK value is nil", i)
		}

		buf.WriteString(fmt.Sprintf("%s%v", sep, value))
		sep = ":"
	}

	return buf.String(), nil
}

func (r *River) doBulk(reqs []*elastic.BulkRequest) error {
	if len(reqs) == 0 {
		return nil
	}

	if resp, err := r.es.Bulk(reqs); err != nil {
		log.Errorf("sync docs err %v after binlog %s", err, r.canal.SyncedPosition())
		return err
	} else if resp.Errors {
		for i := 0; i < len(resp.Items); i++ {
			for action, item := range resp.Items[i] {
				if len(item.Error) > 0 {
					log.Errorf("%s index: %s, type: %s, id: %s, status: %d, error: %s",
						action, item.Index, item.Type, item.ID, item.Status, item.Error)
				}
			}
		}
	}

	return nil
}
<commit_msg>try to fix panic error for blob []byte comparison<commit_after>package river

import (
	"bytes"
	"fmt"
	"reflect"
	"strings"

	"github.com/siddontang/go-mysql-elasticsearch/elastic"
	"github.com/siddontang/go-mysql/canal"
	"github.com/siddontang/go-mysql/schema"
	"github.com/siddontang/go/log"
)

const (
	syncInsertDoc = iota
	syncDeleteDoc
	syncUpdateDoc
)

type rowsEventHandler struct {
	r *River
}

func (h *rowsEventHandler) Do(e *canal.RowsEvent) error {
	rule, ok := h.r.rules[ruleKey(e.Table.Schema, e.Table.Name)]
	if !ok {
		return nil
	}

	var reqs []*elastic.BulkRequest
	var err error
	switch e.Action {
	case canal.InsertAction:
		reqs, err = h.r.makeInsertRequest(rule, e.Rows)
	case canal.DeleteAction:
		reqs, err = h.r.makeDeleteRequest(rule, e.Rows)
	case canal.UpdateAction:
		reqs, err = h.r.makeUpdateRequest(rule, e.Rows)
	default:
		return fmt.Errorf("invalid rows action %s", e.Action)
	}

	if err != nil {
		return fmt.Errorf("make %s ES request err %v", e.Action, err)
	}

	if err := h.r.doBulk(reqs); err != nil {
		log.Errorf("do ES bulks err %v, stop", err)
		return canal.ErrHandleInterrupted
	}

	return nil
}

func (h *rowsEventHandler) String() string {
	return "ESRiverRowsEventHandler"
}

// for insert and delete
func (r *River) makeRequest(rule *Rule, action string, rows [][]interface{}) ([]*elastic.BulkRequest, error) {
	reqs := make([]*elastic.BulkRequest, 0, len(rows))

	for _, values := range rows {
		id, err := r.getDocID(rule, values)
		if err != nil {
			return nil, err
		}

		req := &elastic.BulkRequest{Index: rule.Index, Type: rule.Type, ID: id}

		if action == canal.DeleteAction {
			req.Action = elastic.ActionDelete
			r.st.DeleteNum.Add(1)
		} else {
			r.makeInsertReqData(req, rule, values)
			r.st.InsertNum.Add(1)
		}

		reqs = append(reqs, req)
	}

	return reqs, nil
}

func (r *River) makeInsertRequest(rule *Rule, rows [][]interface{}) ([]*elastic.BulkRequest, error) {
	return r.makeRequest(rule, canal.InsertAction, rows)
}

func (r *River) makeDeleteRequest(rule *Rule, rows [][]interface{}) ([]*elastic.BulkRequest, error) {
	return r.makeRequest(rule, canal.DeleteAction, rows)
}

func (r *River) makeUpdateRequest(rule *Rule, rows [][]interface{}) ([]*elastic.BulkRequest, error) {
	if len(rows)%2 != 0 {
		return nil, fmt.Errorf("invalid update rows event, must have 2x rows, but %d", len(rows))
	}

	reqs := make([]*elastic.BulkRequest, 0, len(rows))

	for i := 0; i < len(rows); i += 2 {
		beforeID, err := r.getDocID(rule, rows[i])
		if err != nil {
			return nil, err
		}

		afterID, err := r.getDocID(rule, rows[i+1])

		if err != nil {
			return nil, err
		}

		req := &elastic.BulkRequest{Index: rule.Index, Type: rule.Type, ID: beforeID}

		if beforeID != afterID {
			req.Action = elastic.ActionDelete
			reqs = append(reqs, req)

			req = &elastic.BulkRequest{Index: rule.Index, Type: rule.Type, ID: afterID}
			r.makeInsertReqData(req, rule, rows[i+1])

			r.st.DeleteNum.Add(1)
			r.st.InsertNum.Add(1)
		} else {
			r.makeUpdateReqData(req, rule, rows[i], rows[i+1])
			r.st.UpdateNum.Add(1)
		}

		reqs = append(reqs, req)
	}

	return reqs, nil
}

func (r *River) makeReqColumnData(col *schema.TableColumn, value interface{}) interface{} {
	switch col.Type {
	case schema.TYPE_ENUM:
		switch value := value.(type) {
		case int64:
			// for binlog, ENUM may be int64, but for dump, enum is string
			eNum := value - 1
			if eNum < 0 || eNum >= int64(len(col.EnumValues)) {
				// we insert invalid enum value before, so return empty
				log.Warnf("invalid binlog enum index %d, for enum %v", eNum, col.EnumValues)
				return ""
			}

			return col.EnumValues[eNum]
		}
	case schema.TYPE_SET:
		switch value := value.(type) {
		case int64:
			// for binlog, SET may be int64, but for dump, SET is string
			bitmask := value
			sets := make([]string, 0, len(col.SetValues))
			for i, s := range col.SetValues {
				if bitmask&int64(1<<uint(i)) > 0 {
					sets = append(sets, s)
				}
			}
			return strings.Join(sets, ",")
		}
	}

	return value
}

func (r *River) makeInsertReqData(req *elastic.BulkRequest, rule *Rule, values []interface{}) {
	req.Data = make(map[string]interface{}, len(values))
	req.Action = elastic.ActionIndex

	for i, c := range rule.TableInfo.Columns {
		if name, ok := rule.FieldMapping[c.Name]; ok {
			// has custom field mapping
			req.Data[name] = r.makeReqColumnData(&c, values[i])
		} else {
			req.Data[c.Name] = r.makeReqColumnData(&c, values[i])
		}
	}
}

func (r *River) makeUpdateReqData(req *elastic.BulkRequest, rule *Rule,
	beforeValues []interface{}, afterValues []interface{}) {
	req.Data = make(map[string]interface{}, len(beforeValues))

	// maybe dangerous if something wrong delete before?
	req.Action = elastic.ActionUpdate

	for i, c := range rule.TableInfo.Columns {
		if reflect.DeepEqual(beforeValues[i], afterValues[i]) {
			//nothing changed
			continue
		}
		if name, ok := rule.FieldMapping[c.Name]; ok {
			// has custom field mapping
			req.Data[name] = r.makeReqColumnData(&c, afterValues[i])
		} else {
			req.Data[c.Name] = r.makeReqColumnData(&c, afterValues[i])
		}
	}
}

// Get primary keys in one row and format them into a string
// PK must not be nil
func (r *River) getDocID(rule *Rule, row []interface{}) (string, error) {
	pks, err := canal.GetPKValues(rule.TableInfo, row)
	if err != nil {
		return "", err
	}

	var buf bytes.Buffer

	sep := ""
	for i, value := range pks {
		if value == nil {
			return "", fmt.Errorf("The %ds PK value is nil", i)
		}

		buf.WriteString(fmt.Sprintf("%s%v", sep, value))
		sep = ":"
	}

	return buf.String(), nil
}

func (r *River) doBulk(reqs []*elastic.BulkRequest) error {
	if len(reqs) == 0 {
		return nil
	}

	if resp, err := r.es.Bulk(reqs); err != nil {
		log.Errorf("sync docs err %v after binlog %s", err, r.canal.SyncedPosition())
		return err
	} else if resp.Errors {
		for i := 0; i < len(resp.Items); i++ {
			for action, item := range resp.Items[i] {
				if len(item.Error) > 0 {
					log.Errorf("%s index: %s, type: %s, id: %s, status: %d, error: %s",
						action, item.Index, item.Type, item.ID, item.Status, item.Error)
				}
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package browserspeak

import (
	"errors"
	"fmt"
	"strings"
)

type Tag struct {
	name        string
	style       map[string]string
	content     string
	htmlContent string
	attrs       map[string]string
	nextSibling *Tag // siblings
	firstChild  *Tag // first child
}

type Page struct {
	title  string
	root   *Tag
	cursor *Tag
}

func NewPage(title, rootTagName string) *Page {
	var page Page
	page.title = title
	rootTag := NewTag(rootTagName)
	page.root = rootTag
	return &page
}

func NewTag(name string) *Tag {
	var tag Tag
	tag.name = name
	tag.style = make(map[string]string)
	tag.attrs = make(map[string]string)
	tag.nextSibling = nil
	tag.firstChild = nil
	return &tag
}

func (tag *Tag) AddNewTag(name string) *Tag {
	var child *Tag = NewTag(name)
	tag.AddChild(child)
	return child
}

func (tag Tag) AddStyle(key, value string) {
	tag.style[key] = value
}

func (tag Tag) AddAttr(key, value string) {
	tag.attrs[key] = value
}

// Generate CSS that can go in a CSS file, for a given tag
func (tag *Tag) GetCSS() string {
	if len(tag.style) == 0 {
		return ""
	}

	ret := ""

	// If there is an id="name" defined, use that id instead of the tag name
	value, found := tag.attrs["id"]
	if found {
		ret = "#" + value
	} else {
		ret = tag.name
	}
	ret += " {\n"

	// NB! The attributes may appear in any order!
	for key, value := range tag.style {
		ret += "  " + key + ": " + value + ";\n"
	}
	ret += "}\n\n"
	return ret
}

func (tag *Tag) GetAttrString() string {
	ret := ""
	for key, value := range tag.attrs {
		ret += key + "=\"" + value + "\"" + " "
	}
	if len(ret) > 0 {
		ret = ret[:len(ret)-1]
	}
	return ret
}

// Get spaces for indenting based on a given level
func GetSpaces(level int) string {
	spacing := ""
	for i := 1; i < level; i++ {
		spacing += "  "
	}
	return spacing
}

func (tag *Tag) GetFlatHTML(level int) string {
	// For the root tag
	if (len(tag.name) > 0) && (tag.name[0] == '<') {
		return tag.name + "\n" + tag.content + tag.htmlContent
	}
	// For indenting
	spacing := GetSpaces(level)
	// Generate the HTML based on the tag
	attrs := tag.GetAttrString()
	ret := spacing + "<" + tag.name
	if len(attrs) > 0 {
		ret += " " + attrs
	}
	if (len(tag.content) == 0) && (len(tag.htmlContent) == 0) {
		ret += " />"
	} else {
		if len(tag.htmlContent) > 0 {
			ret += ">" + "\n" + tag.content + tag.htmlContent + spacing + "</" + tag.name + ">"
		} else {
			ret += ">" + tag.content + "</" + tag.name + ">"
			// Indented content
			//ret += ">" + "\n" + GetSpaces(level + 1) + tag.content + "\n" + spacing + "</" + tag.name + ">"
		}
	}
	return ret
}

func (tag *Tag) GetChildren() []*Tag {
	var children []*Tag
	current := tag.firstChild
	for current != nil {
		children = append(children, current)
		current = current.nextSibling
	}
	return children
}

func (tag *Tag) AddChild(child *Tag) {
	if tag.firstChild == nil {
		tag.firstChild = child
		return
	}
	lastChild := tag.LastChild()
	child.nextSibling = nil
	lastChild.nextSibling = child
}

func (tag *Tag) AddContent(content string) {
	tag.content += content
}

func (tag *Tag) CountChildren() int {
	child := tag.firstChild
	if child == nil {
		return 0
	}
	count := 1
	if child.nextSibling == nil {
		return count
	}
	child = child.nextSibling
	for child != nil {
		count++
		child = child.nextSibling
	}
	return count
}

func (tag *Tag) CountSiblings() int {
	sib := tag.nextSibling
	if sib == nil {
		return 0
	}
	count := 1
	if sib.nextSibling == nil {
		return count
	}
	sib = sib.nextSibling
	for sib != nil {
		count++
		sib = sib.nextSibling
	}
	return count
}

func (tag *Tag) LastChild() *Tag {
	child := tag.firstChild
	for child.nextSibling != nil {
		child = child.nextSibling
	}
	return child
}

// Find a tag by name, returns an error if not found
func (cursor *Tag) GetTag(name string) (*Tag, error) {
	if strings.Index(cursor.name, name) == 0 {
		return cursor, nil
	}
	couldNotFindError := errors.New("Could not find tag: " + name)
	if cursor.CountChildren() == 0 {
		// No children. Not found so far
		return nil, couldNotFindError
	}

	var child *Tag = cursor.firstChild
	for child != nil {
		found, err := child.GetTag(name)
		if err == nil {
			return found, err
		}
		child = child.nextSibling
	}

	return nil, couldNotFindError
}

func GetHTMLRecursively(cursor *Tag, level int) string {

	if cursor.CountChildren() == 0 {
		return cursor.GetFlatHTML(level) + "\n"
	}

	content := ""
	htmlContent := ""

	level++

	child := cursor.firstChild
	for child != nil {
		htmlContent = GetHTMLRecursively(child, level)
		if len(htmlContent) > 0 {
			content += htmlContent
		}
		child = child.nextSibling
	}

	level--

	cursor.htmlContent = cursor.content + content

	ret := cursor.GetFlatHTML(level)
	if level > 0 {
		ret += "\n"
	}
	return ret
}

func GetCSSRecursively(cursor *Tag) string {
	if cursor.CountChildren() == 0 {
		return cursor.GetCSS()
	}

	style := ""
	cssContent := ""

	child := cursor.firstChild
	for child != nil {
		cssContent = GetCSSRecursively(child)
		if len(cssContent) > 0 {
			style += cssContent
		}
		child = child.nextSibling
	}

	return cursor.GetCSS() + style
}

func (page Page) GetHTML() string {
	return GetHTMLRecursively(page.root, 0)
}

func (page Page) GetCSS() string {
	return GetCSSRecursively(page.root)
}

func (page Page) PrettyPrint() {
	fmt.Println("Page title:", page.title)
	fmt.Println("Page root tag name:", page.root.name)
	rootPointer := page.root
	fmt.Println("Root tag children count:", rootPointer.CountChildren())
	fmt.Printf("HTML:\n%s\n", page.GetHTML())
	fmt.Printf("CSS:\n%s\n", page.GetCSS())
}

<commit_msg>Added GetTag for pages<commit_after>package browserspeak

import (
	"errors"
	"fmt"
	"strings"
)

type Tag struct {
	name        string
	style       map[string]string
	content     string
	htmlContent string
	attrs       map[string]string
	nextSibling *Tag // siblings
	firstChild  *Tag // first child
}

type Page struct {
	title  string
	root   *Tag
	cursor *Tag
}

func NewPage(title, rootTagName string) *Page {
	var page Page
	page.title = title
	rootTag := NewTag(rootTagName)
	page.root = rootTag
	return &page
}

func NewTag(name string) *Tag {
	var tag Tag
	tag.name = name
	tag.style = make(map[string]string)
	tag.attrs = make(map[string]string)
	tag.nextSibling = nil
	tag.firstChild = nil
	return &tag
}

func (tag *Tag) AddNewTag(name string) *Tag {
	var child *Tag = NewTag(name)
	tag.AddChild(child)
	return child
}

func (tag Tag) AddStyle(key, value string) {
	tag.style[key] = value
}

func (tag Tag) AddAttr(key, value string) {
	tag.attrs[key] = value
}

// Generate CSS that can go in a CSS file, for a given tag
func (tag *Tag) GetCSS() string {
	if len(tag.style) == 0 {
		return ""
	}

	ret := ""

	// If there is an id="name" defined, use that id instead of the tag name
	value, found := tag.attrs["id"]
	if found {
		ret = "#" + value
	} else {
		ret = tag.name
	}
	ret += " {\n"

	// NB! The attributes may appear in any order!
	for key, value := range tag.style {
		ret += "  " + key + ": " + value + ";\n"
	}
	ret += "}\n\n"
	return ret
}

func (tag *Tag) GetAttrString() string {
	ret := ""
	for key, value := range tag.attrs {
		ret += key + "=\"" + value + "\"" + " "
	}
	if len(ret) > 0 {
		ret = ret[:len(ret)-1]
	}
	return ret
}

// Get spaces for indenting based on a given level
func GetSpaces(level int) string {
	spacing := ""
	for i := 1; i < level; i++ {
		spacing += "  "
	}
	return spacing
}

func (tag *Tag) GetFlatHTML(level int) string {
	// For the root tag
	if (len(tag.name) > 0) && (tag.name[0] == '<') {
		return tag.name + "\n" + tag.content + tag.htmlContent
	}
	// For indenting
	spacing := GetSpaces(level)
	// Generate the HTML based on the tag
	attrs := tag.GetAttrString()
	ret := spacing + "<" + tag.name
	if len(attrs) > 0 {
		ret += " " + attrs
	}
	if (len(tag.content) == 0) && (len(tag.htmlContent) == 0) {
		ret += " />"
	} else {
		if len(tag.htmlContent) > 0 {
			ret += ">" + "\n" + tag.content + tag.htmlContent + spacing + "</" + tag.name + ">"
		} else {
			ret += ">" + tag.content + "</" + tag.name + ">"
			// Indented content
			//ret += ">" + "\n" + GetSpaces(level + 1) + tag.content + "\n" + spacing + "</" + tag.name + ">"
		}
	}
	return ret
}

func (tag *Tag) GetChildren() []*Tag {
	var children []*Tag
	current := tag.firstChild
	for current != nil {
		children = append(children, current)
		current = current.nextSibling
	}
	return children
}

func (tag *Tag) AddChild(child *Tag) {
	if tag.firstChild == nil {
		tag.firstChild = child
		return
	}
	lastChild := tag.LastChild()
	child.nextSibling = nil
	lastChild.nextSibling = child
}

func (tag *Tag) AddContent(content string) {
	tag.content += content
}

func (tag *Tag) CountChildren() int {
	child := tag.firstChild
	if child == nil {
		return 0
	}
	count := 1
	if child.nextSibling == nil {
		return count
	}
	child = child.nextSibling
	for child != nil {
		count++
		child = child.nextSibling
	}
	return count
}

func (tag *Tag) CountSiblings() int {
	sib := tag.nextSibling
	if sib == nil {
		return 0
	}
	count := 1
	if sib.nextSibling == nil {
		return count
	}
	sib = sib.nextSibling
	for sib != nil {
		count++
		sib = sib.nextSibling
	}
	return count
}

func (tag *Tag) LastChild() *Tag {
	child := tag.firstChild
	for child.nextSibling != nil {
		child = child.nextSibling
	}
	return child
}

func (page *Page) GetTag(name string) (*Tag, error) {
	return page.root.GetTag(name)
}

// Find a tag by name, returns an error if not found
func (cursor *Tag) GetTag(name string) (*Tag, error) {
	if strings.Index(cursor.name, name) == 0 {
		return cursor, nil
	}
	couldNotFindError := errors.New("Could not find tag: " + name)
	if cursor.CountChildren() == 0 {
		// No children. Not found so far
		return nil, couldNotFindError
	}

	var child *Tag = cursor.firstChild
	for child != nil {
		found, err := child.GetTag(name)
		if err == nil {
			return found, err
		}
		child = child.nextSibling
	}

	return nil, couldNotFindError
}

func GetHTMLRecursively(cursor *Tag, level int) string {

	if cursor.CountChildren() == 0 {
		return cursor.GetFlatHTML(level) + "\n"
	}

	content := ""
	htmlContent := ""

	level++

	child := cursor.firstChild
	for child != nil {
		htmlContent = GetHTMLRecursively(child, level)
		if len(htmlContent) > 0 {
			content += htmlContent
		}
		child = child.nextSibling
	}

	level--

	cursor.htmlContent = cursor.content + content

	ret := cursor.GetFlatHTML(level)
	if level > 0 {
		ret += "\n"
	}
	return ret
}

func GetCSSRecursively(cursor *Tag) string {
	if cursor.CountChildren() == 0 {
		return cursor.GetCSS()
	}

	style := ""
	cssContent := ""

	child := cursor.firstChild
	for child != nil {
		cssContent = GetCSSRecursively(child)
		if len(cssContent) > 0 {
			style += cssContent
		}
		child = child.nextSibling
	}

	return cursor.GetCSS() + style
}

func (page Page) GetHTML() string {
	return GetHTMLRecursively(page.root, 0)
}

func (page Page) GetCSS() string {
	return GetCSSRecursively(page.root)
}

func (page Page) PrettyPrint() {
	fmt.Println("Page title:", page.title)
	fmt.Println("Page root tag name:", page.root.name)
	rootPointer := page.root
	fmt.Println("Root tag children count:", rootPointer.CountChildren())
	fmt.Printf("HTML:\n%s\n", page.GetHTML())
	fmt.Printf("CSS:\n%s\n", page.GetCSS())
}

<|endoftext|>


<commit_before>package workstation

import (
	"fmt"
	"io/ioutil"
	"regexp"
	"runtime"
	"strconv"
	"strings"

	log "github.com/sirupsen/logrus"
	"github.com/xshellinc/iotit/device/config"
	"github.com/xshellinc/tools/dialogs"
	"github.com/xshellinc/tools/lib/help"
	"github.com/xshellinc/tools/lib/sudo"
)

// Linux specific workstation type
type linux struct {
	*workstation
	folder string
}

// Initializes linux workstation with unix type
func newWorkstation(disk string) WorkStation {
	m := new(MountInfo)
	var ms []*MountInfo
	w := &workstation{disk, runtime.GOOS, true, m, ms}
	return &linux{workstation: w, folder: config.MountDir}
}

// Lists available mounts
func (l *linux) ListRemovableDisk() ([]*MountInfo, error) {
	fmt.Println("[+] Listing available disks...")
	regex := regexp.MustCompile(`(sd[a-z])$`)
	regexMmcblk := regexp.MustCompile(`(mmcblk[0-9])$`)
	var (
		devDisks []string
		out      = []*MountInfo{}
	)
	files, _ := ioutil.ReadDir("/dev/")
	for _, f := range files {
		fileName := f.Name()
		if regex.MatchString(fileName) || regexMmcblk.MatchString(fileName) {
			devDisks = append(devDisks, fileName)
		}
	}
	for _, devDisk := range devDisks {
		var p = &MountInfo{}
		diskMap := make(map[string]string)

		r, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/removable")
		removable := strings.Trim(string(r), "\n") == "1"

		sd, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/device/type")
		isSdCard := strings.Trim(string(sd), "\n") == "SD"

		m, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/device/model")
		deviceName := strings.Trim(string(m), "\n")

		// if model is empty, try read from /device/name
		if deviceName == "" {
			n, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/device/name")
			deviceName = strings.Trim(string(n), "\n")
		}

		sizeInSectors, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/size")
		deviceSizeInSectors := strings.Trim(string(sizeInSectors), "\n")
		deviceSizeInSectorsParsed, err := strconv.ParseInt(deviceSizeInSectors, 10, 64)
		if err != nil {
			// unexpected, because there are always integer
			deviceSizeInSectorsParsed = 0
		}

		sectorSize, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/device/erase_size")
		deviceSectorSize := strings.Trim(string(sectorSize), "\n")
		deviceSectorSizeParsed, err := strconv.ParseInt(deviceSectorSize, 10, 64)
		if err != nil {
			// unexpected, because there are always integer
			deviceSectorSizeParsed = 0
		}

		deviceSize := deviceSizeInSectorsParsed * deviceSectorSizeParsed

		diskMap["deviceName"] = deviceName
		diskMap["diskName"] = "/dev/" + devDisk
		diskMap["diskNameRaw"] = "/dev/" + devDisk
		diskMap["deviceSize"] = strconv.FormatInt(deviceSize, 10)

		if removable || isSdCard {
			p.deviceName = diskMap["deviceName"]
			p.diskName = diskMap["diskName"]
			p.diskNameRaw = diskMap["diskNameRaw"]
			p.deviceSize = diskMap["deviceSize"]
			out = append(out, p)
		}
	}

	if !(len(out) > 0) {
		return nil, fmt.Errorf("[-] No mounts found.\n[-] Please insert your SD card and start command again")
	}
	l.workstation.mounts = out
	return out, nil
}

// Unmounts the disk
func (l *linux) Unmount() error {
	if l.workstation.writable != false {
		fmt.Printf("[+] Unmounting disk:%s\n", l.workstation.mount.deviceName)
		stdout, err := help.ExecSudo(sudo.InputMaskedPassword, nil, "umount", l.workstation.mount.diskName)
		if err != nil {
			return fmt.Errorf("Error unmounting disk:%s from %s with error %s, stdout: %s", l.workstation.mount.diskName, l.folder, err.Error(), stdout)
		}
	}
	return nil
}

const diskSelectionTries = 3
const writeAttempts = 3
const cleanTemplate = `n
p
1

w
q
`

// CopyToDisk Notifies user to choose a mount, after that it tries to copy the data
func (l *linux) CopyToDisk(img string) (job *help.BackgroundJob, err error) {
	log.Debug("CopyToDisk")
	_, err = l.ListRemovableDisk()
	if err != nil {
		fmt.Println("[-] SD card is not found, please insert an unlocked SD card")
		return nil, err
	}

	var dev *MountInfo
	if len(l.Disk) == 0 {
		rng := make([]string, len(l.workstation.mounts))
		for i, e := range l.workstation.mounts {
			rng[i] = fmt.Sprintf(dialogs.PrintColored("%s")+" - "+dialogs.PrintColored("%s")+" (%s)", e.deviceName, e.diskName, e.deviceSize)
		}
		num := dialogs.SelectOneDialog("Select disk to format: ", rng)
		dev = l.workstation.mounts[num]
	} else {
		for _, e := range l.workstation.mounts {
			if e.diskName == l.Disk {
				dev = e
				break
			}
		}
		if dev == nil {
			return nil, fmt.Errorf("Disk name not recognised, try to list disks with " + dialogs.PrintColored("disks") + " argument")
		}
	}

	l.workstation.mount = dev
	fmt.Printf("[+] Writing image to %s\n", dev.diskName)
	log.WithField("image", img).WithField("mount", "/media/KERNEL").Debugf("Writing image to %s", dev.diskName)

	if err := l.CleanDisk(dev.diskName); err != nil {
		return nil, err
	}

	job = help.NewBackgroundJob()
	go func() {
		defer job.Close()
		job.Active(true)
		help.ExecCmd("unzip", []string{img, "-d", "/media/KERNEL/"})
		fmt.Println("\r[+] Done writing image to /media/KERNEL")
	}()

	return job, nil
}

// Notifies user to chose a mount, after that it tries to write the data with `diskSelectionTries` number of retries
func (l *linux) WriteToDisk(img string) (job *help.BackgroundJob, err error) {
	for attempt := 0; attempt < diskSelectionTries; attempt++ {
		if attempt > 0 && !dialogs.YesNoDialog("Continue?") {
			break
		}

		_, err = l.ListRemovableDisk()
		if err != nil {
			fmt.Println("[-] SD card is not found, please insert an unlocked SD card")
			continue
		}

		var dev *MountInfo
		if len(l.Disk) == 0 {
			fmt.Println("[+] Available mounts: ")

			rng := make([]string, len(l.workstation.mounts))
			for i, e := range l.workstation.mounts {
				rng[i] = fmt.Sprintf(dialogs.PrintColored("%s")+" - "+dialogs.PrintColored("%s"), e.deviceName, e.diskName)
			}
			num := dialogs.SelectOneDialog("Select mount to format: ", rng)
			dev = l.workstation.mounts[num]
		} else {
			for _, e := range l.workstation.mounts {
				if e.diskName == l.Disk {
					dev = e
					break
				}
			}
			if dev == nil {
				return nil, fmt.Errorf("Disk name not recognised, try to list disks with " + dialogs.PrintColored("disks") + " argument")
			}
		}

		if ok, ferr := help.FileModeMask(dev.diskNameRaw, 0200); !ok || ferr != nil {
			if ferr != nil {
				log.Error(ferr)
				return nil, ferr
			} else {
				fmt.Println("[-] Your card seems locked. Please unlock your SD card")
				err = fmt.Errorf("[-] Your card seems locked.\n[-]  Please unlock your SD card and start command again")
			}
		} else {
			l.workstation.mount = dev
			break
		}
	}

	if err != nil {
		return nil, err
	}

	if len(l.Disk) == 0 && !dialogs.YesNoDialog("Are you sure? ") {
		l.workstation.writable = false
		return nil, nil
	}

	fmt.Printf("[+] Writing %s to %s\n", img, l.workstation.mount.diskName)
	fmt.Println("[+] You may need to enter your user password")

	job = help.NewBackgroundJob()

	go func() {
		defer job.Close()

		args := []string{
			"dd",
			fmt.Sprintf("if=%s", img),
			fmt.Sprintf("of=%s", l.workstation.mount.diskName),
			"bs=4M",
		}

		var err error
		for attempt := 0; attempt < writeAttempts; attempt++ {
			if attempt > 0 && !dialogs.YesNoDialog("Continue?") {
				break
			}
			job.Active(true)

			var out, eut []byte
			if out, eut, err = sudo.Exec(sudo.InputMaskedPassword, job.Progress, args...); err != nil {
				help.LogCmdErrors(string(out), string(eut), err, args...)

				job.Active(false)
				fmt.Println("\r[-] Can't write to disk. Please make sure your password is correct")
			} else {
				job.Active(false)
				fmt.Printf("\r[+] Done writing %s to %s \n", img, l.workstation.mount.diskName)
				break
			}
		}

		if err != nil {
			job.Error(err)
		}
	}()

	l.workstation.writable = true
	return job, nil
}

// Ejects the mounted disk
func (l *linux) Eject() error {
	if l.workstation.writable != false {
		fmt.Printf("[+] Eject your sd card :%s\n", l.workstation.mount.diskName)
		eut, err := help.ExecSudo(sudo.InputMaskedPassword, nil, "eject", l.workstation.mount.diskName)
		if err != nil {
			return fmt.Errorf("eject disk failed: %s\n[-] Cause: %s", l.workstation.mount.diskName, eut)
		}
	}
	return nil
}

// CleanDisk does nothing on linux
func (l *linux) CleanDisk(disk string) error {
	log.Debug("CleanDisk")
	if disk == "" {
		return fmt.Errorf("No disk to format")
	}
	job := help.NewBackgroundJob()
	go func() {
		defer job.Close()
		job.Active(true)
		help.ExecCmd("umount", []string{disk})

		// fdisk /dev/mmcblk0 < /tmp/fdisk.cmd
		dst := help.GetTempDir() + help.Separator() + "fdisk.cmd"
		help.CreateFile(dst)
		help.WriteFile(dst, cleanTemplate)

		args := []string{
			"fdisk",
			disk,
			"<",
			dst,
		}
		if _, _, err := sudo.Exec(sudo.InputMaskedPassword, job.Progress, args...); err != nil {
			job.Error(err)
		}

		args = []string{
			"mkdosfs",
			"-n",
			"KERNEL",
			disk,
			"-F",
			"32",
		}
		if _, _, err := sudo.Exec(sudo.InputMaskedPassword, job.Progress, args...); err != nil {
			job.Error(err)
		}
		log.Debug("mkdosfs done")
		help.ExecCmd("mkdir", []string{"/media/KERNEL/"})
		log.Debug("mkdir done")
		help.ExecCmd("mount", []string{disk, "/media/KERNEL/"})
		log.Debug("mount done")
		l.workstation.mount.deviceName = "/media/KERNEL"
		job.Active(false)
	}()

	if err := help.WaitJobAndSpin("You need to have "+dialogs.PrintColored("dosfstools")+" package installed. Formatting", job); err != nil {
		return err
	}

	l.workstation.writable = true
	return nil
}

func (l *linux) PrintDisks() {
	l.workstation.printDisks(l)
}
<commit_msg>Fix linux mounting point names<commit_after>package workstation

import (
	"fmt"
	"io/ioutil"
	"regexp"
	"runtime"
	"strconv"
	"strings"

	log "github.com/sirupsen/logrus"
	"github.com/xshellinc/iotit/device/config"
	"github.com/xshellinc/tools/dialogs"
	"github.com/xshellinc/tools/lib/help"
	"github.com/xshellinc/tools/lib/sudo"
)

// Linux specific workstation type
type linux struct {
	*workstation
	folder string
}

// Initializes linux workstation with unix type
func newWorkstation(disk string) WorkStation {
	m := new(MountInfo)
	var ms []*MountInfo
	w := &workstation{disk, runtime.GOOS, true, m, ms}
	return &linux{workstation: w, folder: config.MountDir}
}

// Lists available mounts
func (l *linux) ListRemovableDisk() ([]*MountInfo, error) {
	fmt.Println("[+] Listing available disks...")
	regex := regexp.MustCompile(`(sd[a-z])$`)
	regexMmcblk := regexp.MustCompile(`(mmcblk[0-9])$`)
	var (
		devDisks []string
		out      = []*MountInfo{}
	)
	files, _ := ioutil.ReadDir("/dev/")
	for _, f := range files {
		fileName := f.Name()
		if regex.MatchString(fileName) || regexMmcblk.MatchString(fileName) {
			devDisks = append(devDisks, fileName)
		}
	}
	for _, devDisk := range devDisks {
		var p = &MountInfo{}
		diskMap := make(map[string]string)

		r, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/removable")
		removable := strings.Trim(string(r), "\n") == "1"

		sd, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/device/type")
		isSdCard := strings.Trim(string(sd), "\n") == "SD"

		m, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/device/model")
		deviceName := strings.Trim(string(m), "\n")

		// if model is empty, try read from /device/name
		if deviceName == "" {
			n, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/device/name")
			deviceName = strings.Trim(string(n), "\n")
		}

		sizeInSectors, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/size")
		deviceSizeInSectors := strings.Trim(string(sizeInSectors), "\n")
		deviceSizeInSectorsParsed, err := strconv.ParseInt(deviceSizeInSectors, 10, 64)
		if err != nil {
			// unexpected, because there are always integer
			deviceSizeInSectorsParsed = 0
		}

		sectorSize, _ := ioutil.ReadFile("/sys/block/" + devDisk + "/device/erase_size")
		deviceSectorSize := strings.Trim(string(sectorSize), "\n")
		deviceSectorSizeParsed, err := strconv.ParseInt(deviceSectorSize, 10, 64)
		if err != nil {
			// unexpected, because there are always integer
			deviceSectorSizeParsed = 0
		}

		deviceSize := deviceSizeInSectorsParsed * deviceSectorSizeParsed

		diskMap["deviceName"] = deviceName
		diskMap["diskName"] = "/dev/" + devDisk
		diskMap["diskNameRaw"] = "/dev/" + devDisk
		diskMap["deviceSize"] = strconv.FormatInt(deviceSize, 10)

		if removable || isSdCard {
			p.deviceName = diskMap["deviceName"]
			p.diskName = diskMap["diskName"]
			p.diskNameRaw = diskMap["diskNameRaw"]
			p.deviceSize = diskMap["deviceSize"]
			out = append(out, p)
		}
	}

	if !(len(out) > 0) {
		return nil, fmt.Errorf("[-] No mounts found.\n[-] Please insert your SD card and start command again")
	}
	l.workstation.mounts = out
	return out, nil
}

// Unmounts the disk
func (l *linux) Unmount() error {
	if l.workstation.writable != false {
		fmt.Printf("[+] Unmounting disk: %s\n", l.workstation.mount.deviceName)
		stdout, err := help.ExecSudo(sudo.InputMaskedPassword, nil, "umount", l.workstation.mount.diskName)
		if err != nil {
			return fmt.Errorf("Error unmounting disk:%s from %s with error %s, stdout: %s", l.workstation.mount.diskName, l.folder, err.Error(), stdout)
		}
	}
	return nil
}

const diskSelectionTries = 3
const writeAttempts = 3
const cleanTemplate = `n
p
1

w
q
`

// CopyToDisk Notifies user to choose a mount, after that it tries to copy the data
func (l *linux) CopyToDisk(img string) (job *help.BackgroundJob, err error) {
	log.Debug("CopyToDisk")
	_, err = l.ListRemovableDisk()
	if err != nil {
		fmt.Println("[-] SD card is not found, please insert an unlocked SD card")
		return nil, err
	}

	var dev *MountInfo
	if len(l.Disk) == 0 {
		rng := make([]string, len(l.workstation.mounts))
		for i, e := range l.workstation.mounts {
			rng[i] = fmt.Sprintf(dialogs.PrintColored("%s")+" - "+dialogs.PrintColored("%s")+" (%s)", e.deviceName, e.diskName, e.deviceSize)
		}
		num := dialogs.SelectOneDialog("Select disk to format: ", rng)
		dev = l.workstation.mounts[num]
	} else {
		for _, e := range l.workstation.mounts {
			if e.diskName == l.Disk {
				dev = e
				break
			}
		}
		if dev == nil {
			return nil, fmt.Errorf("Disk name not recognised, try to list disks with " + dialogs.PrintColored("disks") + " argument")
		}
	}

	l.workstation.mount = dev
	fmt.Printf("[+] Writing image to %s\n", dev.diskName)
	log.WithField("image", img).WithField("mount", "/media/KERNEL").Debugf("Writing image to %s", dev.diskName)

	if err := l.CleanDisk(dev.diskName); err != nil {
		return nil, err
	}

	job = help.NewBackgroundJob()
	go func() {
		defer job.Close()
		job.Active(true)
		help.ExecCmd("unzip", []string{img, "-d", "/media/KERNEL/"})
		fmt.Println("\r[+] Done writing image to /media/KERNEL")
	}()

	return job, nil
}

// Notifies user to chose a mount, after that it tries to write the data with `diskSelectionTries` number of retries
func (l *linux) WriteToDisk(img string) (job *help.BackgroundJob, err error) {
	for attempt := 0; attempt < diskSelectionTries; attempt++ {
		if attempt > 0 && !dialogs.YesNoDialog("Continue?") {
			break
		}

		_, err = l.ListRemovableDisk()
		if err != nil {
			fmt.Println("[-] SD card is not found, please insert an unlocked SD card")
			continue
		}

		var dev *MountInfo
		if len(l.Disk) == 0 {
			fmt.Println("[+] Available mounts: ")

			rng := make([]string, len(l.workstation.mounts))
			for i, e := range l.workstation.mounts {
				rng[i] = fmt.Sprintf(dialogs.PrintColored("%s")+" - "+dialogs.PrintColored("%s"), e.deviceName, e.diskName)
			}
			num := dialogs.SelectOneDialog("Select mount to format: ", rng)
			dev = l.workstation.mounts[num]
		} else {
			for _, e := range l.workstation.mounts {
				if e.diskName == l.Disk {
					dev = e
					break
				}
			}
			if dev == nil {
				return nil, fmt.Errorf("Disk name not recognised, try to list disks with " + dialogs.PrintColored("disks") + " argument")
			}
		}

		if ok, ferr := help.FileModeMask(dev.diskNameRaw, 0200); !ok || ferr != nil {
			if ferr != nil {
				log.Error(ferr)
				return nil, ferr
			} else {
				fmt.Println("[-] Your card seems locked. Please unlock your SD card")
				err = fmt.Errorf("[-] Your card seems locked.\n[-]  Please unlock your SD card and start command again")
			}
		} else {
			l.workstation.mount = dev
			break
		}
	}

	if err != nil {
		return nil, err
	}

	if len(l.Disk) == 0 && !dialogs.YesNoDialog("Are you sure? ") {
		l.workstation.writable = false
		return nil, nil
	}

	fmt.Printf("[+] Writing %s to %s\n", img, l.workstation.mount.diskName)
	fmt.Println("[+] You may need to enter your user password")

	job = help.NewBackgroundJob()

	go func() {
		defer job.Close()

		args := []string{
			"dd",
			fmt.Sprintf("if=%s", img),
			fmt.Sprintf("of=%s", l.workstation.mount.diskName),
			"bs=4M",
		}

		var err error
		for attempt := 0; attempt < writeAttempts; attempt++ {
			if attempt > 0 && !dialogs.YesNoDialog("Continue?") {
				break
			}
			job.Active(true)

			var out, eut []byte
			if out, eut, err = sudo.Exec(sudo.InputMaskedPassword, job.Progress, args...); err != nil {
				help.LogCmdErrors(string(out), string(eut), err, args...)

				job.Active(false)
				fmt.Println("\r[-] Can't write to disk. Please make sure your password is correct")
			} else {
				job.Active(false)
				fmt.Printf("\r[+] Done writing %s to %s \n", img, l.workstation.mount.diskName)
				break
			}
		}

		if err != nil {
			job.Error(err)
		}
	}()

	l.workstation.writable = true
	return job, nil
}

// Ejects the mounted disk
func (l *linux) Eject() error {
	if l.workstation.writable != false {
		fmt.Printf("[+] Eject your sd card :%s\n", l.workstation.mount.diskName)
		eut, err := help.ExecSudo(sudo.InputMaskedPassword, nil, "eject", l.workstation.mount.diskName)
		if err != nil {
			return fmt.Errorf("eject disk failed: %s\n[-] Cause: %s", l.workstation.mount.diskName, eut)
		}
	}
	return nil
}

// CleanDisk does nothing on linux
func (l *linux) CleanDisk(disk string) error {
	log.WithField("disk", disk).Debug("CleanDisk")
	if disk == "" {
		return fmt.Errorf("No disk to format")
	}
	job := help.NewBackgroundJob()
	go func() {
		defer job.Close()
		job.Active(true)
		help.ExecCmd("umount", []string{disk})

		// fdisk /dev/mmcblk0 < /tmp/fdisk.cmd
		dst := help.GetTempDir() + help.Separator() + "fdisk.cmd"
		help.CreateFile(dst)
		help.WriteFile(dst, cleanTemplate)

		args := []string{
			"fdisk",
			disk,
			"<",
			dst,
		}
		if _, _, err := sudo.Exec(sudo.InputMaskedPassword, job.Progress, args...); err != nil {
			job.Error(err)
		}

		args = []string{
			"mkdosfs",
			"-n",
			"KERNEL",
			disk + "p1",
			"-F",
			"32",
		}
		if _, _, err := sudo.Exec(sudo.InputMaskedPassword, job.Progress, args...); err != nil {
			job.Error(err)
		}
		log.Debug("mkdosfs done")
		help.ExecCmd("mkdir", []string{"/media/KERNEL/"})
		log.Debug("mkdir done")
		help.ExecCmd("mount", []string{disk + "p1", "/media/KERNEL/"})
		log.Debug("mount done")
		l.workstation.mount.deviceName = "/media/KERNEL"
		l.workstation.mount.diskName = disk + "p1"
		l.workstation.writable = true
		job.Active(false)
	}()

	if err := help.WaitJobAndSpin("You need to have "+dialogs.PrintColored("dosfstools")+" package installed. Formatting", job); err != nil {
		return err
	}

	l.workstation.writable = true
	return nil
}

func (l *linux) PrintDisks() {
	l.workstation.printDisks(l)
}
<|endoftext|>


<commit_before>// Copyright 2017 The Prometheus Authors
// 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.

// Package promlog defines standardised ways to initialize Go kit loggers
// across Prometheus components.
// It should typically only ever be imported by main packages.
package promlog

import (
	"os"
	"sync"
	"time"

	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/pkg/errors"
)

var (
	// This timestamp format differs from RFC3339Nano by using .000 instead
	// of .999999999 which changes the timestamp from 9 variable to 3 fixed
	// decimals (.130 instead of .130987456).
	timestampFormat = log.TimestampFormat(
		func() time.Time { return time.Now().UTC() },
		"2006-01-02T15:04:05.000Z07:00",
	)
)

// AllowedLevel is a settable identifier for the minimum level a log entry
// must be have.
type AllowedLevel struct {
	s string
	o level.Option
}

func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var s string
	type plain string
	if err := unmarshal((*plain)(&s)); err != nil {
		return err
	}
	if s == "" {
		return nil
	}
	lo := &AllowedLevel{}
	if err := lo.Set(s); err != nil {
		return err
	}
	*l = *lo
	return nil
}

func (l *AllowedLevel) String() string {
	return l.s
}

// Set updates the value of the allowed level.
func (l *AllowedLevel) Set(s string) error {
	switch s {
	case "debug":
		l.o = level.AllowDebug()
	case "info":
		l.o = level.AllowInfo()
	case "warn":
		l.o = level.AllowWarn()
	case "error":
		l.o = level.AllowError()
	default:
		return errors.Errorf("unrecognized log level %q", s)
	}
	l.s = s
	return nil
}

// AllowedFormat is a settable identifier for the output format that the logger can have.
type AllowedFormat struct {
	s string
}

func (f *AllowedFormat) String() string {
	return f.s
}

// Set updates the value of the allowed format.
func (f *AllowedFormat) Set(s string) error {
	switch s {
	case "logfmt", "json":
		f.s = s
	default:
		return errors.Errorf("unrecognized log format %q", s)
	}
	return nil
}

// Config is a struct containing configurable settings for the logger
type Config struct {
	Level  *AllowedLevel
	Format *AllowedFormat
}

// New returns a new leveled oklog logger. Each logged line will be annotated
// with a timestamp. The output always goes to stderr.
func New(config *Config) log.Logger {
	var l log.Logger
	if config.Format != nil && config.Format.s == "json" {
		l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
	} else {
		l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
	}

	l = log.With(l, "ts", timestampFormat, "caller", log.DefaultCaller)

	if config.Level != nil {
		l = level.NewFilter(l, config.Level.o)
	}
	return l
}

// NewDynamic returns a new leveled logger. Each logged line will be annotated
// with a timestamp. The output always goes to stderr. Some properties can be
// changed, like the level.
func NewDynamic(config *Config) *logger {
	var l log.Logger
	if config.Format != nil && config.Format.s == "json" {
		l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
	} else {
		l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
	}
	l = log.With(l, "ts", timestampFormat, "caller", log.DefaultCaller)

	lo := &logger{
		base:    l,
		leveled: l,
	}
	if config.Level != nil {
		lo.SetLevel(config.Level)
	}
	return lo
}

type logger struct {
	base         log.Logger
	leveled      log.Logger
	currentLevel *AllowedLevel
	mtx          sync.Mutex
}

// Log implements logger.Log.
func (l *logger) Log(keyvals ...interface{}) error {
	l.mtx.Lock()
	defer l.mtx.Unlock()
	return l.leveled.Log(keyvals...)
}

// SetLevel changes the log level.
func (l *logger) SetLevel(lvl *AllowedLevel) {
	l.mtx.Lock()
	defer l.mtx.Unlock()
	if lvl != nil {
		if l.currentLevel != nil && l.currentLevel.s != lvl.s {
			_ = l.base.Log("msg", "Log level changed", "prev", l.currentLevel, "current", lvl)
		}
		l.currentLevel = lvl
	}
	l.leveled = level.NewFilter(l.base, lvl.o)
}
<commit_msg>promlog: Fix caller (#334)<commit_after>// Copyright 2017 The Prometheus Authors
// 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.

// Package promlog defines standardised ways to initialize Go kit loggers
// across Prometheus components.
// It should typically only ever be imported by main packages.
package promlog

import (
	"os"
	"sync"
	"time"

	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/pkg/errors"
)

var (
	// This timestamp format differs from RFC3339Nano by using .000 instead
	// of .999999999 which changes the timestamp from 9 variable to 3 fixed
	// decimals (.130 instead of .130987456).
	timestampFormat = log.TimestampFormat(
		func() time.Time { return time.Now().UTC() },
		"2006-01-02T15:04:05.000Z07:00",
	)
)

// AllowedLevel is a settable identifier for the minimum level a log entry
// must be have.
type AllowedLevel struct {
	s string
	o level.Option
}

func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var s string
	type plain string
	if err := unmarshal((*plain)(&s)); err != nil {
		return err
	}
	if s == "" {
		return nil
	}
	lo := &AllowedLevel{}
	if err := lo.Set(s); err != nil {
		return err
	}
	*l = *lo
	return nil
}

func (l *AllowedLevel) String() string {
	return l.s
}

// Set updates the value of the allowed level.
func (l *AllowedLevel) Set(s string) error {
	switch s {
	case "debug":
		l.o = level.AllowDebug()
	case "info":
		l.o = level.AllowInfo()
	case "warn":
		l.o = level.AllowWarn()
	case "error":
		l.o = level.AllowError()
	default:
		return errors.Errorf("unrecognized log level %q", s)
	}
	l.s = s
	return nil
}

// AllowedFormat is a settable identifier for the output format that the logger can have.
type AllowedFormat struct {
	s string
}

func (f *AllowedFormat) String() string {
	return f.s
}

// Set updates the value of the allowed format.
func (f *AllowedFormat) Set(s string) error {
	switch s {
	case "logfmt", "json":
		f.s = s
	default:
		return errors.Errorf("unrecognized log format %q", s)
	}
	return nil
}

// Config is a struct containing configurable settings for the logger
type Config struct {
	Level  *AllowedLevel
	Format *AllowedFormat
}

// New returns a new leveled oklog logger. Each logged line will be annotated
// with a timestamp. The output always goes to stderr.
func New(config *Config) log.Logger {
	var l log.Logger
	if config.Format != nil && config.Format.s == "json" {
		l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
	} else {
		l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
	}

	if config.Level != nil {
		l = log.With(l, "ts", timestampFormat, "caller", log.Caller(5))
		l = level.NewFilter(l, config.Level.o)
	} else {
		l = log.With(l, "ts", timestampFormat, "caller", log.DefaultCaller)
	}
	return l
}

// NewDynamic returns a new leveled logger. Each logged line will be annotated
// with a timestamp. The output always goes to stderr. Some properties can be
// changed, like the level.
func NewDynamic(config *Config) *logger {
	var l log.Logger
	if config.Format != nil && config.Format.s == "json" {
		l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
	} else {
		l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
	}

	lo := &logger{
		base:    l,
		leveled: l,
	}

	if config.Level != nil {
		lo.SetLevel(config.Level)
	}

	return lo
}

type logger struct {
	base         log.Logger
	leveled      log.Logger
	currentLevel *AllowedLevel
	mtx          sync.Mutex
}

// Log implements logger.Log.
func (l *logger) Log(keyvals ...interface{}) error {
	l.mtx.Lock()
	defer l.mtx.Unlock()
	return l.leveled.Log(keyvals...)
}

// SetLevel changes the log level.
func (l *logger) SetLevel(lvl *AllowedLevel) {
	l.mtx.Lock()
	defer l.mtx.Unlock()
	if lvl == nil {
		l.leveled = log.With(l.base, "ts", timestampFormat, "caller", log.DefaultCaller)
		l.currentLevel = nil
		return
	}

	if l.currentLevel != nil && l.currentLevel.s != lvl.s {
		_ = l.base.Log("msg", "Log level changed", "prev", l.currentLevel, "current", lvl)
	}
	l.currentLevel = lvl
	l.leveled = level.NewFilter(log.With(l.base, "ts", timestampFormat, "caller", log.Caller(5)), lvl.o)
}
<|endoftext|>


<commit_before>// Copyright 2017 The Prometheus Authors
// 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.

// Package promlog defines standardised ways to initialize Go kit loggers
// across Prometheus components.
// It should typically only ever be imported by main packages.
package promlog

import (
	"os"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/pkg/errors"
)

// AllowedLevel is a settable identifier for the minimum level a log entry
// must be have.
type AllowedLevel struct {
	s string
	o level.Option
}

func (l *AllowedLevel) String() string {
	return l.s
}

// Set updates the value of the allowed level.
func (l *AllowedLevel) Set(s string) error {
	switch s {
	case "debug":
		l.o = level.AllowDebug()
	case "info":
		l.o = level.AllowInfo()
	case "warn":
		l.o = level.AllowWarn()
	case "error":
		l.o = level.AllowError()
	default:
		return errors.Errorf("unrecognized log level %q", s)
	}
	l.s = s
	return nil
}

// AllowedFormat is a settable identifier for the output format that the logger can have.
type AllowedFormat struct {
	s string
}

func (f *AllowedFormat) String() string {
	return f.s
}

// Set updates the value of the allowed format.
func (f *AllowedFormat) Set(s string) error {
	switch s {
	case "logfmt", "json":
		f.s = s
	default:
		return errors.Errorf("unrecognized log format %q", s)
	}
	return nil
}

type Config struct {
	Level  *AllowedLevel
	Format *AllowedFormat
}

// New returns a new leveled oklog logger. Each logged line will be annotated
// with a timestamp. The output always goes to stderr.
func New(config *Config) log.Logger {
	var l log.Logger
	if config.Format.s == "logfmt" {
		l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
	} else {
		l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
	}

	l = level.NewFilter(l, config.Level.o)
	l = log.With(l, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
	return l
}
<commit_msg>promlog: add comment for Config (#156)<commit_after>// Copyright 2017 The Prometheus Authors
// 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.

// Package promlog defines standardised ways to initialize Go kit loggers
// across Prometheus components.
// It should typically only ever be imported by main packages.
package promlog

import (
	"os"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/pkg/errors"
)

// AllowedLevel is a settable identifier for the minimum level a log entry
// must be have.
type AllowedLevel struct {
	s string
	o level.Option
}

func (l *AllowedLevel) String() string {
	return l.s
}

// Set updates the value of the allowed level.
func (l *AllowedLevel) Set(s string) error {
	switch s {
	case "debug":
		l.o = level.AllowDebug()
	case "info":
		l.o = level.AllowInfo()
	case "warn":
		l.o = level.AllowWarn()
	case "error":
		l.o = level.AllowError()
	default:
		return errors.Errorf("unrecognized log level %q", s)
	}
	l.s = s
	return nil
}

// AllowedFormat is a settable identifier for the output format that the logger can have.
type AllowedFormat struct {
	s string
}

func (f *AllowedFormat) String() string {
	return f.s
}

// Set updates the value of the allowed format.
func (f *AllowedFormat) Set(s string) error {
	switch s {
	case "logfmt", "json":
		f.s = s
	default:
		return errors.Errorf("unrecognized log format %q", s)
	}
	return nil
}

// Config is a struct containing configurable settings for the logger
type Config struct {
	Level  *AllowedLevel
	Format *AllowedFormat
}

// New returns a new leveled oklog logger. Each logged line will be annotated
// with a timestamp. The output always goes to stderr.
func New(config *Config) log.Logger {
	var l log.Logger
	if config.Format.s == "logfmt" {
		l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
	} else {
		l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
	}

	l = level.NewFilter(l, config.Level.o)
	l = log.With(l, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
	return l
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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.

package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"os/user"
	"strconv"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/comebackfs"
	"github.com/jacobsa/comeback/internal/registry"
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/jacobsa/syncutil"
)

var cmdMount = &Command{
	Name: "mount",
}

var fDebugFuse = cmdMount.Flags.Bool(
	"debug_fuse",
	false,
	"Enable fuse debug logging.")

func init() {
	cmdMount.Run = runMount // Break flag-related dependency loop.
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Return the UID and GID of the current process.
func currentUser() (uid uint32, gid uint32, err error) {
	user, err := user.Current()
	if err != nil {
		panic(err)
	}

	// Parse the UID.
	uid64, err := strconv.ParseUint(user.Uid, 10, 32)
	if err != nil {
		err = fmt.Errorf("Parsing UID (%s): %v", user.Uid, err)
		return
	}

	// Parse the GID.
	gid64, err := strconv.ParseUint(user.Gid, 10, 32)
	if err != nil {
		err = fmt.Errorf("Parsing GID (%s): %v", user.Gid, err)
		return
	}

	uid = uint32(uid64)
	gid = uint32(gid64)

	return
}

////////////////////////////////////////////////////////////////////////
// Command
////////////////////////////////////////////////////////////////////////

func runMount(ctx context.Context, args []string) (err error) {
	// Enable invariant checking for the file system.
	syncutil.EnableInvariantChecking()

	// Check usage.
	if len(args) < 1 || len(args) > 2 {
		err = fmt.Errorf("Usage: %s mount_point [score]", os.Args[0])
		return
	}

	mountPoint := args[0]

	// Grab dependencies.
	blobStore := getBlobStore(ctx)

	var hexScore string
	if len(args) > 1 {
		hexScore = args[1]
	}

	// Figure out which score to mount.
	var score blob.Score
	if hexScore != "" {
		score, err = blob.ParseHexScore(hexScore)
		if err != nil {
			err = fmt.Errorf("ParseHexScore(%q): %v", hexScore, err)
			return
		}
	} else {
		r := getRegistry(ctx)

		// List jobs.
		var jobs []registry.CompletedJob
		jobs, err = r.ListBackups(ctx)
		if err != nil {
			err = fmt.Errorf("ListBackups: %v", err)
			return
		}

		if len(jobs) == 0 {
			err = errors.New("No completed jobs found.")
			return
		}

		// Find the job with the newest start time.
		j := jobs[0]
		for _, candidate := range jobs {
			if j.StartTime.Before(candidate.StartTime) {
				j = candidate
			}
		}

		score = j.Score
	}

	log.Printf("Mounting score %s.", score.Hex())

	// Choose permission settings.
	uid, gid, err := currentUser()
	if err != nil {
		err = fmt.Errorf("currentUser: %v", err)
		return
	}

	// Create the file system.
	fs, err := comebackfs.NewFileSystem(uid, gid, score, blobStore)
	if err != nil {
		err = fmt.Errorf("NewFileSystem: %v", err)
		return
	}

	// Mount it.
	cfg := &fuse.MountConfig{
		FSName:      fmt.Sprintf("comeback-%s", score.Hex()),
		ReadOnly:    true,
		ErrorLogger: log.New(os.Stderr, "fuse: ", log.Flags()),

		// Everything is immutable, so let the kernel cache to its heart's content.
		EnableVnodeCaching: true,
	}

	if *fDebugFuse {
		cfg.DebugLogger = log.New(os.Stderr, "debug_fuse: ", log.Flags())
	}

	mfs, err := fuse.Mount(
		mountPoint,
		fuseutil.NewFileSystemServer(fs),
		cfg)

	if err != nil {
		err = fmt.Errorf("Mount: %v", err)
		return
	}

	log.Println("File system mounted.")

	// Wait for unmount.
	err = mfs.Join(ctx)
	if err != nil {
		err = fmt.Errorf("Join: %v", err)
		return
	}

	log.Println("Exiting successfully.")
	return
}
<commit_msg>Set up the skeleton of daemonizing.<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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.

package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"os/user"
	"strconv"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/comebackfs"
	"github.com/jacobsa/comeback/internal/registry"
	"github.com/jacobsa/daemonize"
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/jacobsa/syncutil"
)

var cmdMount = &Command{
	Name: "mount",
}

var fDebugFuse = cmdMount.Flags.Bool(
	"debug_fuse",
	false,
	"Enable fuse debug logging.")

func init() {
	cmdMount.Run = runMount // Break flag-related dependency loop.
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Return the UID and GID of the current process.
func currentUser() (uid uint32, gid uint32, err error) {
	user, err := user.Current()
	if err != nil {
		panic(err)
	}

	// Parse the UID.
	uid64, err := strconv.ParseUint(user.Uid, 10, 32)
	if err != nil {
		err = fmt.Errorf("Parsing UID (%s): %v", user.Uid, err)
		return
	}

	// Parse the GID.
	gid64, err := strconv.ParseUint(user.Gid, 10, 32)
	if err != nil {
		err = fmt.Errorf("Parsing GID (%s): %v", user.Gid, err)
		return
	}

	uid = uint32(uid64)
	gid = uint32(gid64)

	return
}

// When the user runs `comeback mount`, the binary re-executes itself using the
// daemonize package. This environment variable is set when invoking the daemon
// so that it can tell itself apart from the foreground process.
const daemonEnvVar = "COMEBACK_DAEMON"

func isDaemon() (b bool) {
	_, b = os.LookupEnv(daemonEnvVar)
	return
}

////////////////////////////////////////////////////////////////////////
// Command
////////////////////////////////////////////////////////////////////////

// Start the file system daemon and wait for it to mount successfully.
func startDaemon(ctx context.Context, args []string) (err error) {
	// Prompt the user for their password here instead of in the daemon.
	password := getPassword()

	// Re-execute as the daemon, forwarding status output to stderr.
	err = daemonize.Run(
		os.Args[0],
		append([]string{"mount"}, args...),
		[]string{
			fmt.Sprintf("%s=%s", passwordEnvVar, password),
			fmt.Sprintf("%s=", daemonEnvVar),
		},
		os.Stderr)

	if err != nil {
		err = fmt.Errorf("daemonize.Run: %v", err)
		return
	}

	return
}

func runMount(ctx context.Context, args []string) (err error) {
	// If we are not the file system daemon, we should start it and wait for it
	// to mount successfully, then do no more.
	if !isDaemon() {
		err = startDaemon(ctx, args)
		if err != nil {
			err = fmt.Errorf("startDaemon: %v", err)
			return
		}

		return
	}

	// Enable invariant checking for the file system.
	syncutil.EnableInvariantChecking()

	// Check usage.
	if len(args) < 1 || len(args) > 2 {
		err = fmt.Errorf("Usage: %s mount_point [score]", os.Args[0])
		return
	}

	mountPoint := args[0]

	// Grab dependencies.
	blobStore := getBlobStore(ctx)

	var hexScore string
	if len(args) > 1 {
		hexScore = args[1]
	}

	// Figure out which score to mount.
	var score blob.Score
	if hexScore != "" {
		score, err = blob.ParseHexScore(hexScore)
		if err != nil {
			err = fmt.Errorf("ParseHexScore(%q): %v", hexScore, err)
			return
		}
	} else {
		r := getRegistry(ctx)

		// List jobs.
		var jobs []registry.CompletedJob
		jobs, err = r.ListBackups(ctx)
		if err != nil {
			err = fmt.Errorf("ListBackups: %v", err)
			return
		}

		if len(jobs) == 0 {
			err = errors.New("No completed jobs found.")
			return
		}

		// Find the job with the newest start time.
		j := jobs[0]
		for _, candidate := range jobs {
			if j.StartTime.Before(candidate.StartTime) {
				j = candidate
			}
		}

		score = j.Score
	}

	log.Printf("Mounting score %s.", score.Hex())

	// Choose permission settings.
	uid, gid, err := currentUser()
	if err != nil {
		err = fmt.Errorf("currentUser: %v", err)
		return
	}

	// Create the file system.
	fs, err := comebackfs.NewFileSystem(uid, gid, score, blobStore)
	if err != nil {
		err = fmt.Errorf("NewFileSystem: %v", err)
		return
	}

	// Mount it.
	cfg := &fuse.MountConfig{
		FSName:      fmt.Sprintf("comeback-%s", score.Hex()),
		ReadOnly:    true,
		ErrorLogger: log.New(os.Stderr, "fuse: ", log.Flags()),

		// Everything is immutable, so let the kernel cache to its heart's content.
		EnableVnodeCaching: true,
	}

	if *fDebugFuse {
		cfg.DebugLogger = log.New(os.Stderr, "debug_fuse: ", log.Flags())
	}

	mfs, err := fuse.Mount(
		mountPoint,
		fuseutil.NewFileSystemServer(fs),
		cfg)

	if err != nil {
		err = fmt.Errorf("Mount: %v", err)
		return
	}

	log.Println("File system mounted.")

	// Wait for unmount.
	err = mfs.Join(ctx)
	if err != nil {
		err = fmt.Errorf("Join: %v", err)
		return
	}

	log.Println("Exiting successfully.")
	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

type Movie struct {
	Id          string
	Title       string `json:"Title"`
	Director    string `json:"Director"`
	Year        string `json:"Year"`
	Description string `json:"Plot"`
	Genre       string `json:"Genre"`
	ArtworkUrl  string `json:"Poster"`
	ImdbId      string `json:"imdbID"`
}

func (m *Movie) GetImdbInfo(url string) {
	res, err := http.Get(url)
	if err != nil {
		panic(err.Error())
	}
	err = json.NewDecoder(res.Body).Decode(&m)
	if err != nil {
		panic(err.Error())
	}
}

func (m *Movie) ApiUrl() string {
	query := fmt.Sprintf("i=%s&plot=short&r=json", m.Id)
	return BaseApi(query)
}

func (m *Movie) SearchApiUrl() string {
	query := fmt.Sprintf("t=%s&plot=short&r=json", url.QueryEscape(m.Title))
	return BaseApi(query)
}

func BaseApi(q string) string {
	return fmt.Sprintf("http://www.omdbapi.com/?%s", q)
}

func (m *Movie) ParsleyFlags() []string {
	return []string{"--title", m.Title, "--artist", m.Director, "--year", m.Year, "--description", m.Description, "--genre", m.Genre}
}

func (m *Movie) Info() string {
	return fmt.Sprintf("  Title: %s\n  Director: %s\n  Year: %s\n  Plot: %s\n  IMDB ID: %s", m.Title, m.Director, m.Year, m.Description, m.ImdbId)
}

func (m *Movie) HasArtwork() bool {
	return m.ArtworkUrl != ""
}

func FindMovie(id string) Movie {
	m := Movie{Id: id}
	m.GetImdbInfo(m.ApiUrl())
	return m
}

func SearchMovie(title string) Movie {
	m := Movie{Title: title}
	m.GetImdbInfo(m.SearchApiUrl())
	return m
}
<commit_msg>Change Id to ImdbId to remove duplication<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

type Movie struct {
	ImdbId      string `json:"imdbID"`
	Title       string `json:"Title"`
	Director    string `json:"Director"`
	Year        string `json:"Year"`
	Description string `json:"Plot"`
	Genre       string `json:"Genre"`
	ArtworkUrl  string `json:"Poster"`
}

func (m *Movie) GetImdbInfo(url string) {
	res, err := http.Get(url)
	if err != nil {
		panic(err.Error())
	}
	err = json.NewDecoder(res.Body).Decode(&m)
	if err != nil {
		panic(err.Error())
	}
}

func (m *Movie) ApiUrl() string {
	query := fmt.Sprintf("i=%s&plot=short&r=json", m.ImdbId)
	return BaseApi(query)
}

func (m *Movie) SearchApiUrl() string {
	query := fmt.Sprintf("t=%s&plot=short&r=json", url.QueryEscape(m.Title))
	return BaseApi(query)
}

func BaseApi(q string) string {
	return fmt.Sprintf("http://www.omdbapi.com/?%s", q)
}

func (m *Movie) ParsleyFlags() []string {
	return []string{"--title", m.Title, "--artist", m.Director, "--year", m.Year, "--description", m.Description, "--genre", m.Genre}
}

func (m *Movie) Info() string {
	return fmt.Sprintf("  Title: %s\n  Director: %s\n  Year: %s\n  Plot: %s\n  IMDB ID: %s", m.Title, m.Director, m.Year, m.Description, m.ImdbId)
}

func (m *Movie) HasArtwork() bool {
	return m.ArtworkUrl != ""
}

func FindMovie(id string) Movie {
	m := Movie{ImdbId: id}
	m.GetImdbInfo(m.ApiUrl())
	return m
}

func SearchMovie(title string) Movie {
	m := Movie{Title: title}
	m.GetImdbInfo(m.SearchApiUrl())
	return m
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"log"
	"net/http"
	"os"

	"github.com/JackKnifed/moxxi/moxxiconf"
	gorillaHandlers "github.com/gorilla/handlers"
)

func main() {
	listens, accessLogFile, errorLogFile, handlers, err := moxxiConf.LoadConfig()
	if err != nil {
		log.Fatal(err)
	}

	var logger *log.Logger
	var err error
	var errorLog, accessLog *io.Writer

	if errorLogFile != "" {
		errorLog, err = os.OpenFile(errorLogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
		if err != nil {
			errorLog = os.Stderr
		}
	}

	if accessLogFile != "" {
		accessLog, err = os.OpenFile(accessLogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
		if err != nil {
			errorLog = os.Stdout
		}
	}

	logger := log.New(os.Stderr, "", log.LstdFlags|log.LUTC|log.Lshortfile)
	mux := moxxiConf.CreateMux(handlers, logger)

	var errChan chan error

	for _, singleListener := range listens {
		srv := http.Server{
			Addr:         singleListener,
			Handler:      gorillaHandlers.LoggingHandler(accessLog, mux),
			ReadTimeout:  moxxiConf.ConnTimeout,
			WriteTimeout: moxxiConf.ConnTimeout,
		}

		go func() {
			errChan <- srv.ListenAndServe()
		}()
	}

	log.Fatal(<-errChan)
}
<commit_msg>ok, fixed some dumb mistakes in `moxxi.go`<commit_after>package main

import (
	"io"
	"log"
	"net/http"
	"os"

	"github.com/JackKnifed/moxxi/moxxiconf"
	gorillaHandlers "github.com/gorilla/handlers"
)

func main() {
	var err error

	listens, accessLogFile, errorLogFile, handlers, err := moxxiConf.LoadConfig()
	if err != nil {
		log.Fatal(err)
	}

	var errorLog, accessLog io.Writer

	if errorLogFile != "" {
		errorLog, err = os.OpenFile(errorLogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
		if err != nil {
			errorLog = nil
		}
	}
	if errorLog == nil {
		errorLog = os.Stderr
	}

	if accessLogFile != "" {
		accessLog, err = os.OpenFile(accessLogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
		if err != nil {
			errorLog = nil
		}
	}
	if accessLog == nil {
		accessLog = os.Stdout
	}

	logger := log.New(os.Stderr, "", log.LstdFlags|log.LUTC|log.Lshortfile)
	mux := moxxiConf.CreateMux(handlers, logger)

	var errChan chan error

	for _, singleListener := range listens {
		srv := http.Server{
			Addr:         singleListener,
			Handler:      gorillaHandlers.LoggingHandler(accessLog, mux),
			ReadTimeout:  moxxiConf.ConnTimeout,
			WriteTimeout: moxxiConf.ConnTimeout,
		}

		go func() {
			errChan <- srv.ListenAndServe()
		}()
	}

	log.Fatal(<-errChan)
}
<|endoftext|>


<commit_before><commit_msg>nsscache: more idiomatic use of time in tests<commit_after><|endoftext|>


<commit_before>package oci

import (
	"context"
	"strings"

	"github.com/containerd/containerd/containers"
	specs "github.com/opencontainers/runtime-spec/specs-go"
)

// SpecOpts sets spec specific information to a newly generated OCI spec
type SpecOpts func(context.Context, Client, *containers.Container, *specs.Spec) error

// WithProcessArgs replaces the args on the generated spec
func WithProcessArgs(args ...string) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		s.Process.Args = args
		return nil
	}
}

// WithProcessCwd replaces the current working directory on the generated spec
func WithProcessCwd(cwd string) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		s.Process.Cwd = cwd
		return nil
	}
}

// WithHostname sets the container's hostname
func WithHostname(name string) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		s.Hostname = name
		return nil
	}
}

// WithEnv appends environment variables
func WithEnv(environmnetVariables []string) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		if len(environmnetVariables) > 0 {
			s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, environmnetVariables)
		}
		return nil
	}
}

// WithMounts appends mounts
func WithMounts(mounts []specs.Mount) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		s.Mounts = append(s.Mounts, mounts...)
		return nil
	}
}

// replaceOrAppendEnvValues returns the defaults with the overrides either
// replaced by env key or appended to the list
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
	cache := make(map[string]int, len(defaults))
	for i, e := range defaults {
		parts := strings.SplitN(e, "=", 2)
		cache[parts[0]] = i
	}

	for _, value := range overrides {
		// Values w/o = means they want this env to be removed/unset.
		if !strings.Contains(value, "=") {
			if i, exists := cache[value]; exists {
				defaults[i] = "" // Used to indicate it should be removed
			}
			continue
		}

		// Just do a normal set/update
		parts := strings.SplitN(value, "=", 2)
		if i, exists := cache[parts[0]]; exists {
			defaults[i] = value
		} else {
			defaults = append(defaults, value)
		}
	}

	// Now remove all entries that we want to "unset"
	for i := 0; i < len(defaults); i++ {
		if defaults[i] == "" {
			defaults = append(defaults[:i], defaults[i+1:]...)
			i--
		}
	}

	return defaults
}
<commit_msg>Fix typo in variable name<commit_after>package oci

import (
	"context"
	"strings"

	"github.com/containerd/containerd/containers"
	specs "github.com/opencontainers/runtime-spec/specs-go"
)

// SpecOpts sets spec specific information to a newly generated OCI spec
type SpecOpts func(context.Context, Client, *containers.Container, *specs.Spec) error

// WithProcessArgs replaces the args on the generated spec
func WithProcessArgs(args ...string) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		s.Process.Args = args
		return nil
	}
}

// WithProcessCwd replaces the current working directory on the generated spec
func WithProcessCwd(cwd string) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		s.Process.Cwd = cwd
		return nil
	}
}

// WithHostname sets the container's hostname
func WithHostname(name string) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		s.Hostname = name
		return nil
	}
}

// WithEnv appends environment variables
func WithEnv(environmentVariables []string) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		if len(environmentVariables) > 0 {
			s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, environmentVariables)
		}
		return nil
	}
}

// WithMounts appends mounts
func WithMounts(mounts []specs.Mount) SpecOpts {
	return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
		s.Mounts = append(s.Mounts, mounts...)
		return nil
	}
}

// replaceOrAppendEnvValues returns the defaults with the overrides either
// replaced by env key or appended to the list
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
	cache := make(map[string]int, len(defaults))
	for i, e := range defaults {
		parts := strings.SplitN(e, "=", 2)
		cache[parts[0]] = i
	}

	for _, value := range overrides {
		// Values w/o = means they want this env to be removed/unset.
		if !strings.Contains(value, "=") {
			if i, exists := cache[value]; exists {
				defaults[i] = "" // Used to indicate it should be removed
			}
			continue
		}

		// Just do a normal set/update
		parts := strings.SplitN(value, "=", 2)
		if i, exists := cache[parts[0]]; exists {
			defaults[i] = value
		} else {
			defaults = append(defaults, value)
		}
	}

	// Now remove all entries that we want to "unset"
	for i := 0; i < len(defaults); i++ {
		if defaults[i] == "" {
			defaults = append(defaults[:i], defaults[i+1:]...)
			i--
		}
	}

	return defaults
}
<|endoftext|>


<commit_before>package plugin

import (
	"fmt"

	weaveapi "github.com/weaveworks/weave/api"
	"github.com/weaveworks/weave/common/docker"
	weavenet "github.com/weaveworks/weave/net"
)

type watcher struct {
	client *docker.Client
	weave  *weaveapi.Client
	driver *driver
}

type Watcher interface {
}

func NewWatcher(client *docker.Client, weave *weaveapi.Client, driver *driver) (Watcher, error) {
	w := &watcher{client: client, weave: weave, driver: driver}
	return w, client.AddObserver(w)
}

func (w *watcher) ContainerStarted(id string) {
	w.driver.debug("ContainerStarted", "%s", id)
	info, err := w.client.InspectContainer(id)
	if err != nil {
		w.driver.warn("ContainerStarted", "error inspecting container %s: %s", id, err)
		return
	}
	// check that it's on our network
	for _, net := range info.NetworkSettings.Networks {
		network, err := w.driver.findNetworkInfo(net.NetworkID)
		if err != nil {
			w.driver.warn("ContainerStarted", "unable to find network %s info: %s", net.NetworkID, err)
			continue
		}
		if network.isOurs {
			if w.driver.dns {
				fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname)
				weaveDomain, _ := w.weave.DNSDomain()
				// Container name is prefixed with '/' so we cut first character
				name := fmt.Sprintf("%s.%s", info.Name[1:], weaveDomain)
				aliases := append([]string(nil), fqdn, name)
				aliases = append(aliases, net.Aliases...)
				for _, alias := range aliases {
					w.driver.debug("ContainerStarted", "going to register %s with weaveDNS", alias)
					if err := w.weave.RegisterWithDNS(id, alias, net.IPAddress); err != nil {
						w.driver.warn("ContainerStarted", "unable to register %s with weaveDNS: %s", id, err)
					}
				}
			}
			netNSPath := weavenet.NSPathByPidWithProc(w.driver.procPath, info.State.Pid)
			if err := weavenet.WithNetNSByPath(netNSPath, func() error {
				return weavenet.ConfigureARP(weavenet.VethName, w.driver.procPath)
			}); err != nil {
				w.driver.warn("ContainerStarted", "unable to configure interfaces: %s", err)
			}
		}
	}
}

func (w *watcher) ContainerDied(id string) {
	// don't need to do this as WeaveDNS removes names on container died anyway
	// (note by the time we get this event we can't see the EndpointID)
}

func (w *watcher) ContainerDestroyed(id string) {}
<commit_msg>Refactor: additional DNS names registration<commit_after>package plugin

import (
	"fmt"

	weaveapi "github.com/weaveworks/weave/api"
	"github.com/weaveworks/weave/common/docker"
	weavenet "github.com/weaveworks/weave/net"
)

type watcher struct {
	client *docker.Client
	weave  *weaveapi.Client
	driver *driver
}

type Watcher interface {
}

func NewWatcher(client *docker.Client, weave *weaveapi.Client, driver *driver) (Watcher, error) {
	w := &watcher{client: client, weave: weave, driver: driver}
	return w, client.AddObserver(w)
}

func (w *watcher) ContainerStarted(id string) {
	w.driver.debug("ContainerStarted", "%s", id)
	info, err := w.client.InspectContainer(id)
	if err != nil {
		w.driver.warn("ContainerStarted", "error inspecting container %s: %s", id, err)
		return
	}

	domain, err := w.weave.DNSDomain()
	if err != nil {
		w.driver.warn("ContainerStarted", "unable to get weave dns domain: %s", err)
	}

	// check that it's on our network
	for _, net := range info.NetworkSettings.Networks {
		network, err := w.driver.findNetworkInfo(net.NetworkID)
		if err != nil {
			w.driver.warn("ContainerStarted", "unable to find network %s info: %s", net.NetworkID, err)
			continue
		}
		if network.isOurs {
			if w.driver.dns {
				fqdn := fmt.Sprintf("%s.%s", info.Config.Hostname, info.Config.Domainname)

				aliases := make([]string, 0, len(net.Aliases)+2)
				aliases = append(aliases, fqdn)

				if len(domain) > 0 && len(info.Name) > 1 && info.Name[0] == "/" {
					name := fmt.Sprintf("%s.%s", info.Name[1:], domain)
					aliases = append(aliases, name)
				}

				aliases = append(aliases, net.Aliases...)

				for _, alias := range aliases {
					w.driver.debug("ContainerStarted", "going to register %s with weaveDNS", alias)
					if err := w.weave.RegisterWithDNS(id, alias, net.IPAddress); err != nil {
						w.driver.warn("ContainerStarted", "unable to register %s with weaveDNS: %s", id, err)
					}
				}
			}
			netNSPath := weavenet.NSPathByPidWithProc(w.driver.procPath, info.State.Pid)
			if err := weavenet.WithNetNSByPath(netNSPath, func() error {
				return weavenet.ConfigureARP(weavenet.VethName, w.driver.procPath)
			}); err != nil {
				w.driver.warn("ContainerStarted", "unable to configure interfaces: %s", err)
			}
		}
	}
}

func (w *watcher) ContainerDied(id string) {
	// don't need to do this as WeaveDNS removes names on container died anyway
	// (note by the time we get this event we can't see the EndpointID)
}

func (w *watcher) ContainerDestroyed(id string) {}
<|endoftext|>


<commit_before>package rpc

import (
	"context"
	"net"
	"sync"

	"github.com/appleboy/gorush/gorush"
	"github.com/appleboy/gorush/rpc/proto"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"
)

// Server is used to implement gorush grpc server.
type Server struct {
	mu sync.Mutex
	// statusMap stores the serving status of the services this Server monitors.
	statusMap map[string]proto.HealthCheckResponse_ServingStatus
}

// NewServer returns a new Server.
func NewServer() *Server {
	return &Server{
		statusMap: make(map[string]proto.HealthCheckResponse_ServingStatus),
	}
}

// Check implements `service Health`.
func (s *Server) Check(ctx context.Context, in *proto.HealthCheckRequest) (*proto.HealthCheckResponse, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if in.Service == "" {
		// check the server overall health status.
		return &proto.HealthCheckResponse{
			Status: proto.HealthCheckResponse_SERVING,
		}, nil
	}
	if status, ok := s.statusMap[in.Service]; ok {
		return &proto.HealthCheckResponse{
			Status: status,
		}, nil
	}
	return nil, status.Error(codes.NotFound, "unknown service")
}

// Send implements helloworld.GreeterServer
func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*proto.NotificationReply, error) {
	var badge = int(in.Badge)
	notification := gorush.PushNotification{
		Platform:         int(in.Platform),
		Tokens:           in.Tokens,
		Message:          in.Message,
		Title:            in.Title,
		Topic:            in.Topic,
		APIKey:           in.Key,
		Category:         in.Category,
		Sound:            in.Sound,
		ContentAvailable: in.ContentAvailable,
		ThreadID:         in.ThreadID,
		MutableContent:   in.MutableContent,
		Image:            in.Image,
	}

	if badge > 0 {
		notification.Badge = &badge
	}

	if in.Alert != nil {
		notification.Alert = gorush.Alert{
			Title:        in.Alert.Title,
			Body:         in.Alert.Body,
			Subtitle:     in.Alert.Subtitle,
			Action:       in.Alert.Action,
			ActionLocKey: in.Alert.Action,
			LaunchImage:  in.Alert.LaunchImage,
			LocArgs:      in.Alert.LocArgs,
			LocKey:       in.Alert.LocKey,
			TitleLocArgs: in.Alert.TitleLocArgs,
			TitleLocKey:  in.Alert.TitleLocKey,
		}
	}

	if in.Data != nil {
		notification.Data = map[string]interface{}{}
		for k, v := range in.Data.Fields {
			notification.Data[k] = v
		}
	}

	go gorush.SendNotification(ctx, notification)

	return &proto.NotificationReply{
		Success: true,
		Counts:  int32(len(notification.Tokens)),
	}, nil
}

// RunGRPCServer run gorush grpc server
func RunGRPCServer(ctx context.Context) error {
	if !gorush.PushConf.GRPC.Enabled {
		gorush.LogAccess.Info("gRPC server is disabled.")
		return nil
	}

	s := grpc.NewServer()
	rpcSrv := NewServer()
	proto.RegisterGorushServer(s, rpcSrv)
	proto.RegisterHealthServer(s, rpcSrv)

	// Register reflection service on gRPC server.
	reflection.Register(s)

	lis, err := net.Listen("tcp", ":"+gorush.PushConf.GRPC.Port)
	if err != nil {
		gorush.LogError.Fatalln(err)
		return err
	}
	gorush.LogAccess.Info("gRPC server is running on " + gorush.PushConf.GRPC.Port + " port.")
	go func() {
		select {
		case <-ctx.Done():
			s.GracefulStop() // graceful shutdown
			gorush.LogAccess.Info("shutdown the gRPC server")
		}
	}()
	if err = s.Serve(lis); err != nil {
		gorush.LogError.Fatalln(err)
	}
	return err
}
<commit_msg>Enable sending notifications via gRPC to FCM topic (#531)<commit_after>package rpc

import (
	"context"
	"net"
	"sync"

	"github.com/appleboy/gorush/gorush"
	"github.com/appleboy/gorush/rpc/proto"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"
)

// Server is used to implement gorush grpc server.
type Server struct {
	mu sync.Mutex
	// statusMap stores the serving status of the services this Server monitors.
	statusMap map[string]proto.HealthCheckResponse_ServingStatus
}

// NewServer returns a new Server.
func NewServer() *Server {
	return &Server{
		statusMap: make(map[string]proto.HealthCheckResponse_ServingStatus),
	}
}

// Check implements `service Health`.
func (s *Server) Check(ctx context.Context, in *proto.HealthCheckRequest) (*proto.HealthCheckResponse, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if in.Service == "" {
		// check the server overall health status.
		return &proto.HealthCheckResponse{
			Status: proto.HealthCheckResponse_SERVING,
		}, nil
	}
	if status, ok := s.statusMap[in.Service]; ok {
		return &proto.HealthCheckResponse{
			Status: status,
		}, nil
	}
	return nil, status.Error(codes.NotFound, "unknown service")
}

// Send implements helloworld.GreeterServer
func (s *Server) Send(ctx context.Context, in *proto.NotificationRequest) (*proto.NotificationReply, error) {
	var badge = int(in.Badge)
	notification := gorush.PushNotification{
		Platform:         int(in.Platform),
		Tokens:           in.Tokens,
		Message:          in.Message,
		Title:            in.Title,
		Topic:            in.Topic,
		APIKey:           in.Key,
		Category:         in.Category,
		Sound:            in.Sound,
		ContentAvailable: in.ContentAvailable,
		ThreadID:         in.ThreadID,
		MutableContent:   in.MutableContent,
		Image:            in.Image,
	}

	if badge > 0 {
		notification.Badge = &badge
	}

	if in.Topic != "" && in.Platform == gorush.PlatFormAndroid {
		notification.To = in.Topic
	}

	if in.Alert != nil {
		notification.Alert = gorush.Alert{
			Title:        in.Alert.Title,
			Body:         in.Alert.Body,
			Subtitle:     in.Alert.Subtitle,
			Action:       in.Alert.Action,
			ActionLocKey: in.Alert.Action,
			LaunchImage:  in.Alert.LaunchImage,
			LocArgs:      in.Alert.LocArgs,
			LocKey:       in.Alert.LocKey,
			TitleLocArgs: in.Alert.TitleLocArgs,
			TitleLocKey:  in.Alert.TitleLocKey,
		}
	}

	if in.Data != nil {
		notification.Data = map[string]interface{}{}
		for k, v := range in.Data.Fields {
			notification.Data[k] = v
		}
	}

	go gorush.SendNotification(ctx, notification)

	return &proto.NotificationReply{
		Success: true,
		Counts:  int32(len(notification.Tokens)),
	}, nil
}

// RunGRPCServer run gorush grpc server
func RunGRPCServer(ctx context.Context) error {
	if !gorush.PushConf.GRPC.Enabled {
		gorush.LogAccess.Info("gRPC server is disabled.")
		return nil
	}

	s := grpc.NewServer()
	rpcSrv := NewServer()
	proto.RegisterGorushServer(s, rpcSrv)
	proto.RegisterHealthServer(s, rpcSrv)

	// Register reflection service on gRPC server.
	reflection.Register(s)

	lis, err := net.Listen("tcp", ":"+gorush.PushConf.GRPC.Port)
	if err != nil {
		gorush.LogError.Fatalln(err)
		return err
	}
	gorush.LogAccess.Info("gRPC server is running on " + gorush.PushConf.GRPC.Port + " port.")
	go func() {
		select {
		case <-ctx.Done():
			s.GracefulStop() // graceful shutdown
			gorush.LogAccess.Info("shutdown the gRPC server")
		}
	}()
	if err = s.Serve(lis); err != nil {
		gorush.LogError.Fatalln(err)
	}
	return err
}
<|endoftext|>


<commit_before>package router

import (
	"context"

	"v2ray.com/core/common/net"
)

type Rule struct {
	Tag       string
	Condition Condition
}

func (r *Rule) Apply(ctx context.Context) bool {
	return r.Condition.Apply(ctx)
}

func cidrToCondition(cidr []*CIDR, source bool) (Condition, error) {
	ipv4Net := net.NewIPNetTable()
	ipv6Cond := NewAnyCondition()
	hasIpv6 := false

	for _, ip := range cidr {
		switch len(ip.Ip) {
		case net.IPv4len:
			ipv4Net.AddIP(ip.Ip, byte(ip.Prefix))
		case net.IPv6len:
			hasIpv6 = true
			matcher, err := NewCIDRMatcher(ip.Ip, ip.Prefix, source)
			if err != nil {
				return nil, err
			}
			ipv6Cond.Add(matcher)
		default:
			return nil, newError("invalid IP length").AtWarning()
		}
	}

	if !ipv4Net.IsEmpty() && hasIpv6 {
		cond := NewAnyCondition()
		cond.Add(NewIPv4Matcher(ipv4Net, source))
		cond.Add(ipv6Cond)
		return cond, nil
	} else if !ipv4Net.IsEmpty() {
		return NewIPv4Matcher(ipv4Net, source), nil
	} else {
		return ipv6Cond, nil
	}
}

func (rr *RoutingRule) BuildCondition() (Condition, error) {
	conds := NewConditionChan()

	if len(rr.Domain) > 0 {
		matcher := NewCachableDomainMatcher()
		for _, domain := range rr.Domain {
			matcher.Add(domain)
		}
		conds.Add(matcher)
	}

	if len(rr.Cidr) > 0 {
		cond, err := cidrToCondition(rr.Cidr, false)
		if err != nil {
			return nil, err
		}
		conds.Add(cond)
	}

	if rr.PortRange != nil {
		conds.Add(NewPortMatcher(*rr.PortRange))
	}

	if rr.NetworkList != nil {
		conds.Add(NewNetworkMatcher(rr.NetworkList))
	}

	if len(rr.SourceCidr) > 0 {
		cond, err := cidrToCondition(rr.SourceCidr, true)
		if err != nil {
			return nil, err
		}
		conds.Add(cond)
	}

	if len(rr.UserEmail) > 0 {
		conds.Add(NewUserMatcher(rr.UserEmail))
	}

	if len(rr.InboundTag) > 0 {
		conds.Add(NewInboundTagMatcher(rr.InboundTag))
	}

	if conds.Len() == 0 {
		return nil, newError("this rule has no effective fields").AtWarning()
	}

	return conds, nil
}
<commit_msg>reorder condition list to short circuit IP resolution if possible. Fix #1021<commit_after>package router

import (
	"context"

	"v2ray.com/core/common/net"
)

type Rule struct {
	Tag       string
	Condition Condition
}

func (r *Rule) Apply(ctx context.Context) bool {
	return r.Condition.Apply(ctx)
}

func cidrToCondition(cidr []*CIDR, source bool) (Condition, error) {
	ipv4Net := net.NewIPNetTable()
	ipv6Cond := NewAnyCondition()
	hasIpv6 := false

	for _, ip := range cidr {
		switch len(ip.Ip) {
		case net.IPv4len:
			ipv4Net.AddIP(ip.Ip, byte(ip.Prefix))
		case net.IPv6len:
			hasIpv6 = true
			matcher, err := NewCIDRMatcher(ip.Ip, ip.Prefix, source)
			if err != nil {
				return nil, err
			}
			ipv6Cond.Add(matcher)
		default:
			return nil, newError("invalid IP length").AtWarning()
		}
	}

	if !ipv4Net.IsEmpty() && hasIpv6 {
		cond := NewAnyCondition()
		cond.Add(NewIPv4Matcher(ipv4Net, source))
		cond.Add(ipv6Cond)
		return cond, nil
	} else if !ipv4Net.IsEmpty() {
		return NewIPv4Matcher(ipv4Net, source), nil
	} else {
		return ipv6Cond, nil
	}
}

func (rr *RoutingRule) BuildCondition() (Condition, error) {
	conds := NewConditionChan()

	if len(rr.Domain) > 0 {
		matcher := NewCachableDomainMatcher()
		for _, domain := range rr.Domain {
			matcher.Add(domain)
		}
		conds.Add(matcher)
	}

	if len(rr.UserEmail) > 0 {
		conds.Add(NewUserMatcher(rr.UserEmail))
	}

	if len(rr.InboundTag) > 0 {
		conds.Add(NewInboundTagMatcher(rr.InboundTag))
	}

	if rr.PortRange != nil {
		conds.Add(NewPortMatcher(*rr.PortRange))
	}

	if rr.NetworkList != nil {
		conds.Add(NewNetworkMatcher(rr.NetworkList))
	}

	if len(rr.Cidr) > 0 {
		cond, err := cidrToCondition(rr.Cidr, false)
		if err != nil {
			return nil, err
		}
		conds.Add(cond)
	}

	if len(rr.SourceCidr) > 0 {
		cond, err := cidrToCondition(rr.SourceCidr, true)
		if err != nil {
			return nil, err
		}
		conds.Add(cond)
	}

	if conds.Len() == 0 {
		return nil, newError("this rule has no effective fields").AtWarning()
	}

	return conds, nil
}
<|endoftext|>


<commit_before>package plugins

import (
	"bytes"
	"encoding/json"
	"net/http"

	"github.com/belak/seabird/bot"
	"github.com/belak/sorcix-irc"
)

func init() {
	bot.RegisterPlugin("issues", NewIssuesPlugin)
}

type IssueResult struct {
	Url string `json:"html_url"`
}

type IssuesPlugin struct {
	Token string
}

func NewIssuesPlugin(b *bot.Bot) (bot.Plugin, error) {
	p := &IssuesPlugin{}
	b.Config("github", p)

	b.CommandMux.Event("issue", p.CreateIssue, &bot.HelpInfo{
		"<issue title>",
		"Creates a new issue for seabird. Be nice. Abuse this and it will be removed.",
	})

	return p, nil
}

func (p *IssuesPlugin) CreateIssue(b *bot.Bot, m *irc.Message) {
	go func() {
		title := m.Trailing()
		if title == "" {
			b.MentionReply(m, "Issue title required")
			return
		}

		url := "https://api.github.com/repos/belak/seabird/issues"

		hc := &http.Client{}
		params := map[string]string{
			"title": title,
			"body":  "Filed by " + m.Prefix.Name + " in " + m.Params[0],
		}
		body, err := json.Marshal(params)
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}

		req, err := http.NewRequest("POST", url, bytes.NewReader(body))
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}

		req.Header.Add("Authorization", "token "+p.Token)
		resp, err := hc.Do(req)
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}
		defer resp.Body.Close()

		ir := &IssueResult{}
		err = json.NewDecoder(resp.Body).Decode(ir)
		if err != nil {
			b.MentionReply(m, "Error reading server response")
		}

		b.MentionReply(m, "Issue created. %s", ir.Url)
	}()
}
<commit_msg>Update issues plugin to point to the correct place<commit_after>package plugins

import (
	"bytes"
	"encoding/json"
	"net/http"

	"github.com/belak/seabird/bot"
	"github.com/belak/sorcix-irc"
)

func init() {
	bot.RegisterPlugin("issues", NewIssuesPlugin)
}

type IssueResult struct {
	Url string `json:"html_url"`
}

type IssuesPlugin struct {
	Token string
}

func NewIssuesPlugin(b *bot.Bot) (bot.Plugin, error) {
	p := &IssuesPlugin{}
	b.Config("github", p)

	b.CommandMux.Event("issue", p.CreateIssue, &bot.HelpInfo{
		"<issue title>",
		"Creates a new issue for seabird. Be nice. Abuse this and it will be removed.",
	})

	return p, nil
}

func (p *IssuesPlugin) CreateIssue(b *bot.Bot, m *irc.Message) {
	go func() {
		title := m.Trailing()
		if title == "" {
			b.MentionReply(m, "Issue title required")
			return
		}

		url := "https://api.github.com/repos/seabird-irc/plugins/issues"

		hc := &http.Client{}
		params := map[string]string{
			"title": title,
			"body":  "Filed by " + m.Prefix.Name + " in " + m.Params[0],
		}
		body, err := json.Marshal(params)
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}

		req, err := http.NewRequest("POST", url, bytes.NewReader(body))
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}

		req.Header.Add("Authorization", "token "+p.Token)
		resp, err := hc.Do(req)
		if err != nil {
			b.MentionReply(m, "%s", err)
			return
		}
		defer resp.Body.Close()

		ir := &IssueResult{}
		err = json.NewDecoder(resp.Body).Decode(ir)
		if err != nil {
			b.MentionReply(m, "Error reading server response")
		}

		b.MentionReply(m, "Issue created. %s", ir.Url)
	}()
}
<|endoftext|>