<commit_before>package home

import (
	"errors"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/belogik/goes"
	"github.com/go-martini/martini"

	"github.com/firstrow/logvoyage/common"
	"github.com/firstrow/logvoyage/web/context"
	"github.com/firstrow/logvoyage/web/widgets"
)

const (
	timeLayout = "2006/01/02 15:04" // Users input time format
	perPage    = 100
)

type DateTimeRange struct {
	Start string
	Stop  string
}

func (this *DateTimeRange) IsValid() bool {
	return this.Start != "" || this.Stop != ""
}

// Represents search request to perform in ES
type SearchRequest struct {
	Text      string   // test to search
	Indexes   []string // ES indexeses to perform search
	Types     []string // search types
	Size      int      // home much objects ES must return
	From      int      // how much objects should ES skip from first
	TimeRange DateTimeRange
}

func buildSearchRequest(text string, indexes []string, types []string, size int, from int, datetime DateTimeRange) SearchRequest {
	return SearchRequest{
		Text:      text,
		Indexes:   indexes,
		From:      from,
		Types:     types,
		Size:      perPage,
		TimeRange: datetime,
	}
}

// Detects time range from request and returns
// elastic compatible format string
func buildTimeRange(req *http.Request) DateTimeRange {
	var timeRange DateTimeRange

	switch req.URL.Query().Get("time") {
	case "15m":
		timeRange.Start = "now-15m"
	case "30m":
		timeRange.Start = "now-30m"
	case "60m":
		timeRange.Start = "now-60m"
	case "12h":
		timeRange.Start = "now-12h"
	case "24h":
		timeRange.Start = "now-24h"
	case "week":
		timeRange.Start = "now-7d"
	case "custom":
		timeStart, err := time.Parse(timeLayout, req.URL.Query().Get("time_start"))
		if err == nil {
			timeRange.Start = timeStart.Format(time.RFC3339)
		}
		timeStop, err := time.Parse(timeLayout, req.URL.Query().Get("time_stop"))
		if err == nil {
			timeRange.Stop = timeStop.Format(time.RFC3339)
		}
	}

	return timeRange
}

// Search logs in elastic.
func search(searchRequest SearchRequest) (goes.Response, error) {
	conn := common.GetConnection()

	var query = map[string]interface{}{
		"from": searchRequest.From,
		"size": searchRequest.Size,
		"sort": map[string]string{
			"datetime": "desc",
		},
	}

	if len(searchRequest.Text) > 0 {
		strconv.Quote(searchRequest.Text)
		query["query"] = map[string]interface{}{
			"query_string": map[string]string{
				"default_field": "message",
				"query":         searchRequest.Text,
			},
		}
	}

	// Build time range query
	if searchRequest.TimeRange.IsValid() {
		datetime := make(map[string]string)
		if searchRequest.TimeRange.Start != "" {
			datetime["gte"] = searchRequest.TimeRange.Start
		}
		if searchRequest.TimeRange.Stop != "" {
			datetime["lte"] = searchRequest.TimeRange.Stop
		}
		query["filter"] = map[string]interface{}{
			"range": map[string]interface{}{
				"datetime": datetime,
			},
		}
	}

	extraArgs := make(url.Values, 0)
	searchResults, err := conn.Search(query, searchRequest.Indexes, searchRequest.Types, extraArgs)

	if err != nil {
		return goes.Response{}, errors.New("No records found.")
	} else {
		return searchResults, nil
	}
}

// This function handles two routes "/" and "/project/:id"
func ProjectSearch(ctx *context.Context, params martini.Params) {
	var types []string
	var project *common.Project

	query_text := ctx.Request.URL.Query().Get("q")
	selected_types := ctx.Request.URL.Query()["types"]

	// Project scope
	if _, err := params["id"]; err {
		project, err := ctx.User.GetProject(params["id"])
		if err != nil {
			ctx.HTML("shared/error", context.ViewData{
				"message": "Project not found",
			})
			return
		}
		if len(project.Types) == 0 {
			ctx.HTML("home/empty_project", context.ViewData{
				"project": project,
			})
			return
		}
		if len(selected_types) > 0 {
			types = selected_types
		} else {
			types = project.Types
		}
	}

	// Pagination
	pagination := widgets.NewPagination(ctx.Request)
	pagination.SetPerPage(perPage)

	// Load records
	searchRequest := buildSearchRequest(
		query_text,
		[]string{ctx.User.GetIndexName()},
		types,
		pagination.GetPerPage(),
		pagination.DetectFrom(),
		buildTimeRange(ctx.Request),
	)
	// Search data in elastic
	data, _ := search(searchRequest)

	pagination.SetTotalRecords(data.Hits.Total)

	var viewName string
	viewData := context.ViewData{
		"project":    project,
		"logs":       data.Hits.Hits,
		"total":      data.Hits.Total,
		"took":       data.Took,
		"types":      types,
		"time":       ctx.Request.URL.Query().Get("time"),
		"time_start": ctx.Request.URL.Query().Get("time_start"),
		"time_stop":  ctx.Request.URL.Query().Get("time_stop"),
		"query_text": query_text,
		"pagination": pagination,
	}

	if ctx.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" {
		viewName = "home/table"
	} else {
		viewName = "home/index"
	}

	ctx.HTML(viewName, viewData)
}
<commit_msg>change return to a pointer<commit_after>package home

import (
	"errors"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/belogik/goes"
	"github.com/go-martini/martini"

	"github.com/firstrow/logvoyage/common"
	"github.com/firstrow/logvoyage/web/context"
	"github.com/firstrow/logvoyage/web/widgets"
)

const (
	timeLayout = "2006/01/02 15:04" // Users input time format
	perPage    = 100
)

type DateTimeRange struct {
	Start string
	Stop  string
}

func (this *DateTimeRange) IsValid() bool {
	return this.Start != "" || this.Stop != ""
}

// Represents search request to perform in ES
type SearchRequest struct {
	Text      string   // test to search
	Indexes   []string // ES indexeses to perform search
	Types     []string // search types
	Size      int      // home much objects ES must return
	From      int      // how much objects should ES skip from first
	TimeRange DateTimeRange
}

func buildSearchRequest(text string, indexes []string, types []string, size int, from int, datetime DateTimeRange) SearchRequest {
	return SearchRequest{
		Text:      text,
		Indexes:   indexes,
		From:      from,
		Types:     types,
		Size:      perPage,
		TimeRange: datetime,
	}
}

// Detects time range from request and returns
// elastic compatible format string
func buildTimeRange(req *http.Request) DateTimeRange {
	var timeRange DateTimeRange

	switch req.URL.Query().Get("time") {
	case "15m":
		timeRange.Start = "now-15m"
	case "30m":
		timeRange.Start = "now-30m"
	case "60m":
		timeRange.Start = "now-60m"
	case "12h":
		timeRange.Start = "now-12h"
	case "24h":
		timeRange.Start = "now-24h"
	case "week":
		timeRange.Start = "now-7d"
	case "custom":
		timeStart, err := time.Parse(timeLayout, req.URL.Query().Get("time_start"))
		if err == nil {
			timeRange.Start = timeStart.Format(time.RFC3339)
		}
		timeStop, err := time.Parse(timeLayout, req.URL.Query().Get("time_stop"))
		if err == nil {
			timeRange.Stop = timeStop.Format(time.RFC3339)
		}
	}

	return timeRange
}

// Search logs in elastic.
func search(searchRequest SearchRequest) (*goes.Response, error) {
	conn := common.GetConnection()

	var query = map[string]interface{}{
		"from": searchRequest.From,
		"size": searchRequest.Size,
		"sort": map[string]string{
			"datetime": "desc",
		},
	}

	if len(searchRequest.Text) > 0 {
		strconv.Quote(searchRequest.Text)
		query["query"] = map[string]interface{}{
			"query_string": map[string]string{
				"default_field": "message",
				"query":         searchRequest.Text,
			},
		}
	}

	// Build time range query
	if searchRequest.TimeRange.IsValid() {
		datetime := make(map[string]string)
		if searchRequest.TimeRange.Start != "" {
			datetime["gte"] = searchRequest.TimeRange.Start
		}
		if searchRequest.TimeRange.Stop != "" {
			datetime["lte"] = searchRequest.TimeRange.Stop
		}
		query["filter"] = map[string]interface{}{
			"range": map[string]interface{}{
				"datetime": datetime,
			},
		}
	}

	extraArgs := make(url.Values, 0)
	searchResults, err := conn.Search(query, searchRequest.Indexes, searchRequest.Types, extraArgs)

	if err != nil {
		return goes.Response{}, errors.New("No records found.")
	} else {
		return searchResults, nil
	}
}

// This function handles two routes "/" and "/project/:id"
func ProjectSearch(ctx *context.Context, params martini.Params) {
	var types []string
	var project *common.Project

	query_text := ctx.Request.URL.Query().Get("q")
	selected_types := ctx.Request.URL.Query()["types"]

	// Project scope
	if _, err := params["id"]; err {
		project, err := ctx.User.GetProject(params["id"])
		if err != nil {
			ctx.HTML("shared/error", context.ViewData{
				"message": "Project not found",
			})
			return
		}
		if len(project.Types) == 0 {
			ctx.HTML("home/empty_project", context.ViewData{
				"project": project,
			})
			return
		}
		if len(selected_types) > 0 {
			types = selected_types
		} else {
			types = project.Types
		}
	}

	// Pagination
	pagination := widgets.NewPagination(ctx.Request)
	pagination.SetPerPage(perPage)

	// Load records
	searchRequest := buildSearchRequest(
		query_text,
		[]string{ctx.User.GetIndexName()},
		types,
		pagination.GetPerPage(),
		pagination.DetectFrom(),
		buildTimeRange(ctx.Request),
	)
	// Search data in elastic
	data, _ := search(searchRequest)

	pagination.SetTotalRecords(data.Hits.Total)

	var viewName string
	viewData := context.ViewData{
		"project":    project,
		"logs":       data.Hits.Hits,
		"total":      data.Hits.Total,
		"took":       data.Took,
		"types":      types,
		"time":       ctx.Request.URL.Query().Get("time"),
		"time_start": ctx.Request.URL.Query().Get("time_start"),
		"time_stop":  ctx.Request.URL.Query().Get("time_stop"),
		"query_text": query_text,
		"pagination": pagination,
	}

	if ctx.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" {
		viewName = "home/table"
	} else {
		viewName = "home/index"
	}

	ctx.HTML(viewName, viewData)
}
<|endoftext|>


<commit_before>// Package Mat64 contains a float64 Matrix object for Go.
package Mat64

import (
	"log"
)

var (
	errColInx   = "Mat64.%s Error: Column index %d is out of range"
	errRowInx   = "Mat64.%s Error: Row index %d is out of range"
	errMismatch = "Mat64.%s Error: Shape mismatch of the matreces"
)

type Mat64 struct {
	NumRows int
	NumCols int
	Vals    []float64
}

// ElementalFn is a function that takes a float64 and returns a
// float64. This function can therefore be applied to each element
// of a Mat64, and can be used to construct a new transformed Mat64.
type ElementalFn func(float64) float64

// New returns a Mat64 object with the given rows and cols
func New(r, c int) *Mat64 {
	return &Mat64{
		NumRows: r,
		NumCols: c,
		Vals:    make([]float64, r*c),
	}
}

// Identity returns an r by r identity matrix for a given r.
func Identity(r int) *Mat64 {
	identity := New(r, r)
	for i := 0; i < r; i++ {
		identity.Vals[i*r+i] = 1.0
	}
	return identity
}

// Col returns a Mat64, representing a single column of the
// original mat64 object at a given location.
func (m *Mat64) Col(c int) *Mat64 {
	if c >= m.NumCols {
		log.Fatalf(errColInx, "Col", c)
	}
	vec := New(m.NumRows, 1)
	for i := 0; i < m.NumRows; i++ {
		vec.Vals[i] = m.Vals[i*m.NumCols+c]
	}
	return vec
}

// Row returns a Mat64, representing a single row of the
// original Mat64 object at the give location.
func (m *Mat64) Row(r int) *Mat64 {
	if r >= m.NumRows {
		log.Fatalf(errRowInx, "Row", r)
	}
	vec := New(1, m.NumCols)
	for i := 0; i < m.NumCols; i++ {
		vec.Vals[i] = m.Vals[r*m.NumCols+i]
	}
	return vec
}

// At returns the values of the entry in an Mat64 object at
// the specified row and col. It throws errors if an index is out
// of range.
func (m *Mat64) At(r, c int) float64 {
	if r >= m.NumRows {
		log.Fatalf(errRowInx, "At", r)
	}
	if c > m.NumCols {
		log.Fatalf(errColInx, "At", c)
	}
	return m.Vals[r*m.NumCols+c]
}

// Transpose returns a copy of a given matrix with the elements
// mirrored across the diagonal. for example, the element At(i, j) becomes the
// element At(j, i). This function leaves the original matrix intact.
func (m *Mat64) Transpose() *Mat64 {
	transpose := New(m.NumCols, m.NumRows)
	for i := 0; i < m.NumRows; i++ {
		for j := 0; j < m.NumCols; j++ {
			transpose.Vals[j*m.NumRows+i] = m.At(i, j)
		}
	}
	return transpose
}

// Equals checks if two mat objects have the same shape and the
// same entries in each row and column.
func (m *Mat64) Equals(n *Mat64) bool {
	if m.NumRows != n.NumRows || m.NumCols != m.NumCols {
		log.Fatalf(errMismatch, "Equals")
	}
	for i := 0; i < m.NumCols*m.NumRows; i++ {
		if m.Vals[i] != n.Vals[i] {
			return false
		}
	}
	return true
}

// Times returns a new matrix that is the result of
// element-wise multiplication of the matrix by another, leaving
// both original matrices intact.
func (m *Mat64) Times(n *Mat64) *Mat64 {
	if m.NumRows != n.NumRows || m.NumCols != m.NumCols {
		log.Fatalf(errMismatch, "Times")
	}
	o := New(m.NumRows, m.NumCols)
	for i := 0; i < m.NumCols*m.NumRows; i++ {
		o.Vals[i] = m.Vals[i] * n.Vals[i]
	}
	return o
}

// TimesInPlace multiplies a Mat64 by another in place. This means that
// the original matrix is lost.
func (m *Mat64) TimesInPlace(n *Mat64) *Mat64 {
	if m.NumRows != n.NumRows || m.NumCols != m.NumCols {
		log.Fatalf(errMismatch, "Times")
	}
	for i := 0; i < m.NumCols*m.NumRows; i++ {
		m.Vals[i] *= n.Vals[i]
	}
	return m
}

// Apply calls a given Elemental function on each Element
// of a matrix, returning a new transformed matrix.
func (m *Mat64) Apply(f ElementalFn) *Mat64 {
	n := New(m.NumRows, m.NumCols)
	for i := 0; i < m.NumRows*m.NumCols; i++ {
		n.Vals[i] = f(m.Vals[i])
	}
	return n
}

// ApplyInPlace calls a given Elemental function on each Element
// of a matrix, and then returns the transformed matrix.
func (m *Mat64) ApplyInPlace(f ElementalFn) *Mat64 {
	for i := 0; i < m.NumRows*m.NumCols; i++ {
		m.Vals[i] = f(m.Vals[i])
	}
	return m
}

// Dot is the matrix multiplication of two Mat64 objects. Consider
// The following two mat64 objects, pretty printed for illusration:
//
// A = [[1, 0],
//      [0, 1]]
//
// and
//
// B = [[4, 1],
//      [2, 2]]
//
// A.Dot(B) = [[4, 1],
//             [2, 2]]
//
// The number of elements in the first matrix row, must equal the number
// elements in the second matrix column.
func (m *Mat64) Dot(n *Mat64) *Mat64 {
	if m.NumCols != n.NumRows {
		log.Fatalf(errMismatch, "Dot")
	}
	o := New(m.NumRows, n.NumCols)
	items := m.NumCols
	for i := 0; i < m.NumRows; i++ {
		for j := 0; j < n.NumCols; j++ {
			for k := 0; k < items; k++ {
				o.Vals[i*o.NumRows+j] += (m.At(i, k) * n.At(k, j))
			}
		}
	}
	return o
}

// Reset puts all the elements of a Mat64 values set to 0.0.
func (m *Mat64) Reset() *Mat64 {
	for i := 0; i < m.NumCols*m.NumRows; i++ {
		m.Vals[i] = 0.0
	}
	return m
}
<commit_msg>Added dump, but not the test yet. will make the test when I have implemented loading<commit_after>// Package Mat64 contains a float64 Matrix object for Go.
package Mat64

import (
	"log"
	"os"
	"strconv"
)

var (
	errColInx   = "Mat64.%s Error: Column index %d is out of range"
	errRowInx   = "Mat64.%s Error: Row index %d is out of range"
	errMismatch = "Mat64.%s Error: Shape mismatch of the matreces"
)

type Mat64 struct {
	NumRows int
	NumCols int
	Vals    []float64
}

// ElementalFn is a function that takes a float64 and returns a
// float64. This function can therefore be applied to each element
// of a Mat64, and can be used to construct a new transformed Mat64.
type ElementalFn func(float64) float64

// New returns a Mat64 object with the given rows and cols
func New(r, c int) *Mat64 {
	return &Mat64{
		NumRows: r,
		NumCols: c,
		Vals:    make([]float64, r*c),
	}
}

// Identity returns an r by r identity matrix for a given r.
func Identity(r int) *Mat64 {
	identity := New(r, r)
	for i := 0; i < r; i++ {
		identity.Vals[i*r+i] = 1.0
	}
	return identity
}

// Col returns a Mat64, representing a single column of the
// original mat64 object at a given location.
func (m *Mat64) Col(c int) *Mat64 {
	if c >= m.NumCols {
		log.Fatalf(errColInx, "Col", c)
	}
	vec := New(m.NumRows, 1)
	for i := 0; i < m.NumRows; i++ {
		vec.Vals[i] = m.Vals[i*m.NumCols+c]
	}
	return vec
}

// Row returns a Mat64, representing a single row of the
// original Mat64 object at the give location.
func (m *Mat64) Row(r int) *Mat64 {
	if r >= m.NumRows {
		log.Fatalf(errRowInx, "Row", r)
	}
	vec := New(1, m.NumCols)
	for i := 0; i < m.NumCols; i++ {
		vec.Vals[i] = m.Vals[r*m.NumCols+i]
	}
	return vec
}

// At returns the values of the entry in an Mat64 object at
// the specified row and col. It throws errors if an index is out
// of range.
func (m *Mat64) At(r, c int) float64 {
	if r >= m.NumRows {
		log.Fatalf(errRowInx, "At", r)
	}
	if c > m.NumCols {
		log.Fatalf(errColInx, "At", c)
	}
	return m.Vals[r*m.NumCols+c]
}

// Transpose returns a copy of a given matrix with the elements
// mirrored across the diagonal. for example, the element At(i, j) becomes the
// element At(j, i). This function leaves the original matrix intact.
func (m *Mat64) Transpose() *Mat64 {
	transpose := New(m.NumCols, m.NumRows)
	for i := 0; i < m.NumRows; i++ {
		for j := 0; j < m.NumCols; j++ {
			transpose.Vals[j*m.NumRows+i] = m.At(i, j)
		}
	}
	return transpose
}

// Equals checks if two mat objects have the same shape and the
// same entries in each row and column.
func (m *Mat64) Equals(n *Mat64) bool {
	if m.NumRows != n.NumRows || m.NumCols != m.NumCols {
		log.Fatalf(errMismatch, "Equals")
	}
	for i := 0; i < m.NumCols*m.NumRows; i++ {
		if m.Vals[i] != n.Vals[i] {
			return false
		}
	}
	return true
}

// Times returns a new matrix that is the result of
// element-wise multiplication of the matrix by another, leaving
// both original matrices intact.
func (m *Mat64) Times(n *Mat64) *Mat64 {
	if m.NumRows != n.NumRows || m.NumCols != m.NumCols {
		log.Fatalf(errMismatch, "Times")
	}
	o := New(m.NumRows, m.NumCols)
	for i := 0; i < m.NumCols*m.NumRows; i++ {
		o.Vals[i] = m.Vals[i] * n.Vals[i]
	}
	return o
}

// TimesInPlace multiplies a Mat64 by another in place. This means that
// the original matrix is lost.
func (m *Mat64) TimesInPlace(n *Mat64) *Mat64 {
	if m.NumRows != n.NumRows || m.NumCols != m.NumCols {
		log.Fatalf(errMismatch, "Times")
	}
	for i := 0; i < m.NumCols*m.NumRows; i++ {
		m.Vals[i] *= n.Vals[i]
	}
	return m
}

// Apply calls a given Elemental function on each Element
// of a matrix, returning a new transformed matrix.
func (m *Mat64) Apply(f ElementalFn) *Mat64 {
	n := New(m.NumRows, m.NumCols)
	for i := 0; i < m.NumRows*m.NumCols; i++ {
		n.Vals[i] = f(m.Vals[i])
	}
	return n
}

// ApplyInPlace calls a given Elemental function on each Element
// of a matrix, and then returns the transformed matrix.
func (m *Mat64) ApplyInPlace(f ElementalFn) *Mat64 {
	for i := 0; i < m.NumRows*m.NumCols; i++ {
		m.Vals[i] = f(m.Vals[i])
	}
	return m
}

// Dot is the matrix multiplication of two Mat64 objects. Consider
// The following two mat64 objects, pretty printed for illusration:
//
// A = [[1, 0],
//      [0, 1]]
//
// and
//
// B = [[4, 1],
//      [2, 2]]
//
// A.Dot(B) = [[4, 1],
//             [2, 2]]
//
// The number of elements in the first matrix row, must equal the number
// elements in the second matrix column.
func (m *Mat64) Dot(n *Mat64) *Mat64 {
	if m.NumCols != n.NumRows {
		log.Fatalf(errMismatch, "Dot")
	}
	o := New(m.NumRows, n.NumCols)
	items := m.NumCols
	for i := 0; i < m.NumRows; i++ {
		for j := 0; j < n.NumCols; j++ {
			for k := 0; k < items; k++ {
				o.Vals[i*o.NumRows+j] += (m.At(i, k) * n.At(k, j))
			}
		}
	}
	return o
}

// Reset puts all the elements of a Mat64 values set to 0.0.
func (m *Mat64) Reset() *Mat64 {
	return m.ApplyInPlace(func(i float64) float64 { return 0.0 })
}

// Dump prints the content of a Mat64 object to a file, using the given
// delimeter between the elements of a row, and a new line between rows.
// For instance, giving the comma (",") as a delimiter will essentially
// creates a csv file from the Mat64 object.
func (m *Mat64) Dump(fileName, delemiter string) {
	var str string
	for i := 0; i < m.NumRows; i++ {
		for j := 0; j < m.NumCols; j++ {
			str += strconv.FormatFloat(m.Vals[i*m.NumRows+j], 'f', 14, 64)
			str += delimeter
		}
		if i+1 != m.NumRows {
			str += "\n"
		}
	}
	f, err := os.Create(fileName)
	if err != nil {
		log.Fatalf(err)
	}
	defer f.Close()
	_, err = f.WriteString(str)
	if err != nil {
		log.Fatalf(err)
	}
}
<|endoftext|>


<commit_before>package common

import (
	"strings"
	"text/template"

	"github.com/cihangir/gene/stringext"
)

var TemplateFuncs = template.FuncMap{
	"Pointerize":              stringext.Pointerize,
	"ToLowerFirst":            stringext.ToLowerFirst,
	"ToLower":                 strings.ToLower,
	"ToUpperFirst":            stringext.ToUpperFirst,
	"DepunctWithInitialUpper": stringext.DepunctWithInitialUpper,
}
<commit_msg>Common: add DepunctWithInitialLower function into template funcs<commit_after>package common

import (
	"strings"
	"text/template"

	"github.com/cihangir/gene/stringext"
)

var TemplateFuncs = template.FuncMap{
	"Pointerize":              stringext.Pointerize,
	"ToLowerFirst":            stringext.ToLowerFirst,
	"ToLower":                 strings.ToLower,
	"ToUpperFirst":            stringext.ToUpperFirst,
	"DepunctWithInitialUpper": stringext.DepunctWithInitialUpper,
	"DepunctWithInitialLower": stringext.DepunctWithInitialLower,
}
<|endoftext|>


<commit_before>package core

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

/*Model TODO

TODO Tracked has bad performance once very large - replace with struct? Size
argument in make seems not to make a difference.
*/
type Model struct {
	Root       string
	Tracked    map[string]bool
	Objinfo    map[string]staticinfo
	updatechan chan UpdateMessage
}

/*Objectinfo todo*/
type Objectinfo struct {
	directory      bool
	Identification string
	Name           string
	Path           string
	Shadow         bool
	Version        map[string]int
	// Objects        []*Objectinfo `json:",omitempty"`
	Content string `json:",omitempty"`
}

/*
staticinfo stores all information that Tinzenite must keep between calls to
m.Update(). This includes the object ID and version for reapplication, plus
the content hash if required for file content changes detection.
*/
type staticinfo struct {
	Identification string
	Version        map[string]int
	Directory      bool
	Content        string
}

/*
LoadModel loads or creates a model for the given path, depending whether a
model.json exists for it already. Also immediately builds the model for the
first time and stores it.
*/
func LoadModel(path string) (*Model, error) {
	if !IsTinzenite(path) {
		return nil, ErrNotTinzenite
	}
	var m *Model
	data, err := ioutil.ReadFile(path + "/" + TINZENITEDIR + "/" + LOCAL + "/" + MODELJSON)
	if err != nil {
		// if error we must create a new one
		m = &Model{
			Root:    path,
			Tracked: make(map[string]bool),
			Objinfo: make(map[string]staticinfo)}
	} else {
		// load as json
		err = json.Unmarshal(data, &m)
		if err != nil {
			return nil, err
		}
	}
	// ensure that off line updates are caught (note that updatechan won't notify these)
	err = m.Update()
	if err != nil {
		// explicitely return nil because it is a severe error
		return nil, err
	}
	return m, nil
}

/*
Update the complete model state. Will if successful try to store the model to
disk at the end.

TODO: check concurrency allowances?
*/
func (m *Model) Update() error {
	current, err := m.populate()
	var removed, created []string
	if err != nil {
		return err
	}
	for path := range m.Tracked {
		_, ok := current[path]
		if ok {
			// paths that still exist must only be checked for MODIFY
			delete(current, path)
			m.apply(Modify, path)
		} else {
			// REMOVED - paths that don't exist anymore have been removed
			removed = append(removed, path)
		}
	}
	// CREATED - any remaining paths are yet untracked in m.tracked
	for path := range current {
		created = append(created, path)
	}
	// update m.Tracked
	for _, path := range removed {
		delete(m.Tracked, path)
		m.apply(Remove, path)
	}
	for _, path := range created {
		m.Tracked[path] = true
		m.apply(Create, path)
	}
	// finally also store the model for future loads.
	return m.store()
}

/*
Register the channel over which UpdateMessage can be received. Tinzenite will
only ever write to this channel, never read.
*/
func (m *Model) Register(v chan UpdateMessage) {
	m.updatechan = v
}

/*
Read TODO

Should return the JSON representation of this directory
*/
func (m *Model) Read() (*Objectinfo, error) {
	/*TODO*/
	return nil, ErrUnsupported
}

/*
store the model to disk in the correct directory.
*/
func (m *Model) store() error {
	dir := m.Root + "/" + TINZENITEDIR + "/" + LOCAL
	err := makeDirectory(dir)
	if err != nil {
		return err
	}
	jsonBinary, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		return err
	}
	return ioutil.WriteFile(dir+"/"+MODELJSON, jsonBinary, FILEPERMISSIONMODE)
}

/*
getInfo creates the Objectinfo for the given path, so long as the path is
contained in m.Tracked. Directories are NOT traversed!
*/
func (m *Model) getInfo(path string) (*Objectinfo, error) {
	/*TODO incomplete yet*/
	_, exists := m.Tracked[path]
	if !exists {
		return nil, ErrUntracked
	}
	stat, err := os.Lstat(path)
	if err != nil {
		return nil, err
	}
	// TODO lots still to do here!
	object := &Objectinfo{Path: path}
	if stat.IsDir() {
		object.directory = true
	}
	// TODO apply staticinfo!
	return object, ErrUnsupported
}

/*
populate a map[path] for the m.root path. Applies the root Matcher if provided.
*/
func (m *Model) populate() (map[string]bool, error) {
	match, err := CreateMatcher(m.Root)
	if err != nil {
		return nil, err
	}
	tracked := make(map[string]bool)
	filepath.Walk(m.Root, func(subpath string, stat os.FileInfo, inerr error) error {
		// ignore on match
		if match.Ignore(subpath) {
			// SkipDir is okay even if file
			if stat.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}
		tracked[subpath] = true
		return nil
	})
	// doesn't directly assign to m.tracked on purpose so that we can reuse this
	// method elsewhere (for the current structure on m.Update())
	return tracked, nil
}

/*
Apply changes to the internal model state. This method does the true logic on the
model, not touching m.Tracked. NEVER call this method outside of m.Update()!
*/
func (m *Model) apply(op Operation, path string) {
	// whether to send an update on updatechan
	notify := false
	// object for notify
	var infoToNotify staticinfo
	switch op {
	case Create:
		notify = true
		// fetch all values we'll need to store
		id, err := newIdentifier()
		if err != nil {
			log.Println(err.Error())
			return
		}
		stat, err := os.Lstat(path)
		if err != nil {
			log.Println(err.Error())
			return
		}
		hash := ""
		if !stat.IsDir() {
			hash, err = contentHash(path)
			if err != nil {
				log.Println(err.Error())
				return
			}
		}
		stin := staticinfo{
			Identification: id,
			Version:        make(map[string]int),
			Directory:      stat.IsDir(),
			Content:        hash}
		m.Objinfo[path] = stin
		infoToNotify = stin
	case Modify:
		stin, ok := m.Objinfo[path]
		if !ok {
			log.Println("staticinfo lookup failed!")
			return
		}
		// no need for further work here
		if stin.Directory {
			return
		}
		hash, err := contentHash(path)
		if err != nil {
			log.Println(err.Error())
			return
		}
		// if same --> no changes, so done
		if hash == stin.Content {
			return
		}
		// otherwise a change has happened
		notify = true
		// update
		stin.Content = hash
		m.Objinfo[path] = stin
		// TODO update version
		infoToNotify = stin
	case Remove:
		/*TODO: delete logic for multiple peers required!*/
		notify = true
		var ok bool
		infoToNotify, ok = m.Objinfo[path]
		if !ok {
			log.Println("staticinfo lookup failed!")
			notify = false
		}
		delete(m.Objinfo, path)
	default:
		log.Printf("Unimplemented %s for now!\n", op)
	}
	// send the update message
	if notify && m.updatechan != nil {
		/*TODO async select with default --> lost message? but we loose every update
		after the first... hm*/
		m.updatechan <- UpdateMessage{Operation: op, Object: infoToNotify}
	}
}

/*TODO for now only lists all tracked files for debug*/
func (m *Model) String() string {
	var list string
	for path := range m.Tracked {
		list += path + "\n"
	}
	return list
}
<commit_msg>noted todo<commit_after>package core

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

/*Model TODO

TODO Tracked has bad performance once very large - replace with struct? Size
argument in make seems not to make a difference.
*/
type Model struct {
	Root       string
	Tracked    map[string]bool
	Objinfo    map[string]staticinfo
	updatechan chan UpdateMessage
}

/*Objectinfo todo*/
type Objectinfo struct {
	directory      bool
	Identification string
	Name           string
	Path           string
	Shadow         bool
	Version        map[string]int
	// Objects        []*Objectinfo `json:",omitempty"`
	Content string `json:",omitempty"`
}

/*
staticinfo stores all information that Tinzenite must keep between calls to
m.Update(). This includes the object ID and version for reapplication, plus
the content hash if required for file content changes detection.
*/
type staticinfo struct {
	Identification string
	Version        map[string]int
	Directory      bool
	Content        string
}

/*
LoadModel loads or creates a model for the given path, depending whether a
model.json exists for it already. Also immediately builds the model for the
first time and stores it.
*/
func LoadModel(path string) (*Model, error) {
	if !IsTinzenite(path) {
		return nil, ErrNotTinzenite
	}
	var m *Model
	data, err := ioutil.ReadFile(path + "/" + TINZENITEDIR + "/" + LOCAL + "/" + MODELJSON)
	if err != nil {
		// if error we must create a new one
		m = &Model{
			Root:    path,
			Tracked: make(map[string]bool),
			Objinfo: make(map[string]staticinfo)}
	} else {
		// load as json
		err = json.Unmarshal(data, &m)
		if err != nil {
			return nil, err
		}
	}
	// ensure that off line updates are caught (note that updatechan won't notify these)
	err = m.Update()
	if err != nil {
		// explicitely return nil because it is a severe error
		return nil, err
	}
	return m, nil
}

/*
Update the complete model state. Will if successful try to store the model to
disk at the end.

TODO: check concurrency allowances?
*/
func (m *Model) Update() error {
	current, err := m.populate()
	var removed, created []string
	if err != nil {
		return err
	}
	for path := range m.Tracked {
		_, ok := current[path]
		if ok {
			// paths that still exist must only be checked for MODIFY
			delete(current, path)
			m.apply(Modify, path)
		} else {
			// REMOVED - paths that don't exist anymore have been removed
			removed = append(removed, path)
		}
	}
	// CREATED - any remaining paths are yet untracked in m.tracked
	for path := range current {
		created = append(created, path)
	}
	// update m.Tracked
	for _, path := range removed {
		delete(m.Tracked, path)
		m.apply(Remove, path)
	}
	for _, path := range created {
		m.Tracked[path] = true
		m.apply(Create, path)
	}
	// finally also store the model for future loads.
	return m.store()
}

/*
Register the channel over which UpdateMessage can be received. Tinzenite will
only ever write to this channel, never read.
*/
func (m *Model) Register(v chan UpdateMessage) {
	m.updatechan = v
}

/*
Read TODO

Should return the JSON representation of this directory
*/
func (m *Model) Read() (*Objectinfo, error) {
	/*TODO this can be massively parallelized: call getInfo for all objects
	with multiple go routines, then construct the tree afterwards.*/
	return nil, ErrUnsupported
}

/*
store the model to disk in the correct directory.
*/
func (m *Model) store() error {
	dir := m.Root + "/" + TINZENITEDIR + "/" + LOCAL
	err := makeDirectory(dir)
	if err != nil {
		return err
	}
	jsonBinary, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		return err
	}
	return ioutil.WriteFile(dir+"/"+MODELJSON, jsonBinary, FILEPERMISSIONMODE)
}

/*
getInfo creates the Objectinfo for the given path, so long as the path is
contained in m.Tracked. Directories are NOT traversed!
*/
func (m *Model) getInfo(path string) (*Objectinfo, error) {
	/*TODO incomplete yet*/
	_, exists := m.Tracked[path]
	if !exists {
		return nil, ErrUntracked
	}
	stat, err := os.Lstat(path)
	if err != nil {
		return nil, err
	}
	// TODO lots still to do here!
	object := &Objectinfo{Path: path}
	if stat.IsDir() {
		object.directory = true
	}
	// TODO apply staticinfo!
	return object, ErrUnsupported
}

/*
populate a map[path] for the m.root path. Applies the root Matcher if provided.
*/
func (m *Model) populate() (map[string]bool, error) {
	match, err := CreateMatcher(m.Root)
	if err != nil {
		return nil, err
	}
	tracked := make(map[string]bool)
	filepath.Walk(m.Root, func(subpath string, stat os.FileInfo, inerr error) error {
		// ignore on match
		if match.Ignore(subpath) {
			// SkipDir is okay even if file
			if stat.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}
		tracked[subpath] = true
		return nil
	})
	// doesn't directly assign to m.tracked on purpose so that we can reuse this
	// method elsewhere (for the current structure on m.Update())
	return tracked, nil
}

/*
Apply changes to the internal model state. This method does the true logic on the
model, not touching m.Tracked. NEVER call this method outside of m.Update()!
*/
func (m *Model) apply(op Operation, path string) {
	// whether to send an update on updatechan
	notify := false
	// object for notify
	var infoToNotify staticinfo
	switch op {
	case Create:
		notify = true
		// fetch all values we'll need to store
		id, err := newIdentifier()
		if err != nil {
			log.Println(err.Error())
			return
		}
		stat, err := os.Lstat(path)
		if err != nil {
			log.Println(err.Error())
			return
		}
		hash := ""
		if !stat.IsDir() {
			hash, err = contentHash(path)
			if err != nil {
				log.Println(err.Error())
				return
			}
		}
		stin := staticinfo{
			Identification: id,
			Version:        make(map[string]int),
			Directory:      stat.IsDir(),
			Content:        hash}
		m.Objinfo[path] = stin
		infoToNotify = stin
	case Modify:
		stin, ok := m.Objinfo[path]
		if !ok {
			log.Println("staticinfo lookup failed!")
			return
		}
		// no need for further work here
		if stin.Directory {
			return
		}
		hash, err := contentHash(path)
		if err != nil {
			log.Println(err.Error())
			return
		}
		// if same --> no changes, so done
		if hash == stin.Content {
			return
		}
		// otherwise a change has happened
		notify = true
		// update
		stin.Content = hash
		m.Objinfo[path] = stin
		// TODO update version
		infoToNotify = stin
	case Remove:
		/*TODO: delete logic for multiple peers required!*/
		notify = true
		var ok bool
		infoToNotify, ok = m.Objinfo[path]
		if !ok {
			log.Println("staticinfo lookup failed!")
			notify = false
		}
		delete(m.Objinfo, path)
	default:
		log.Printf("Unimplemented %s for now!\n", op)
	}
	// send the update message
	if notify && m.updatechan != nil {
		/*TODO async select with default --> lost message? but we loose every update
		after the first... hm*/
		m.updatechan <- UpdateMessage{Operation: op, Object: infoToNotify}
	}
}

/*TODO for now only lists all tracked files for debug*/
func (m *Model) String() string {
	var list string
	for path := range m.Tracked {
		list += path + "\n"
	}
	return list
}
<|endoftext|>


<commit_before>package filesys

import (
	"context"
	"github.com/chrislusf/seaweedfs/weed/util"
	"os"
	"syscall"
	"time"

	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/fuse"
	"github.com/seaweedfs/fuse/fs"
)

var _ = fs.NodeLinker(&Dir{})
var _ = fs.NodeSymlinker(&Dir{})
var _ = fs.NodeReadlinker(&File{})

func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) {

	oldFile, ok := old.(*File)
	if !ok {
		glog.Errorf("old node is not a file: %+v", old)
	}

	glog.V(4).Infof("Link: %v/%v -> %v/%v", oldFile.dir.FullPath(), oldFile.Name, dir.FullPath(), req.NewName)

	if err := oldFile.maybeLoadEntry(ctx); err != nil {
		return nil, err
	}

	// update old file to hardlink mode
	if len(oldFile.entry.HardLinkId) == 0 {
		oldFile.entry.HardLinkId = util.RandomBytes(16)
		oldFile.entry.HardLinkCounter = 1
	}
	oldFile.entry.HardLinkCounter++
	updateOldEntryRequest := &filer_pb.UpdateEntryRequest{
		Directory:  oldFile.dir.FullPath(),
		Entry:      oldFile.entry,
		Signatures: []int32{dir.wfs.signature},
	}

	// CreateLink 1.2 : update new file to hardlink mode
	request := &filer_pb.CreateEntryRequest{
		Directory: dir.FullPath(),
		Entry: &filer_pb.Entry{
			Name:            req.NewName,
			IsDirectory:     false,
			Attributes:      oldFile.entry.Attributes,
			Chunks:          oldFile.entry.Chunks,
			Extended:        oldFile.entry.Extended,
			HardLinkId:      oldFile.entry.HardLinkId,
			HardLinkCounter: oldFile.entry.HardLinkCounter,
		},
		Signatures: []int32{dir.wfs.signature},
	}

	// apply changes to the filer, and also apply to local metaCache
	err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {

		dir.wfs.mapPbIdFromLocalToFiler(request.Entry)
		defer dir.wfs.mapPbIdFromFilerToLocal(request.Entry)

		if err := filer_pb.UpdateEntry(client, updateOldEntryRequest); err != nil {
			glog.V(0).Infof("Link %v/%v -> %s/%s: %v", oldFile.dir.FullPath(), oldFile.Name, dir.FullPath(), req.NewName, err)
			return fuse.EIO
		}
		dir.wfs.metaCache.UpdateEntry(context.Background(), filer.FromPbEntry(updateOldEntryRequest.Directory, updateOldEntryRequest.Entry))

		if err := filer_pb.CreateEntry(client, request); err != nil {
			glog.V(0).Infof("Link %v/%v -> %s/%s: %v", oldFile.dir.FullPath(), oldFile.Name, dir.FullPath(), req.NewName, err)
			return fuse.EIO
		}
		dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry))

		return nil
	})

	// create new file node
	newNode := dir.newFile(req.NewName, request.Entry)
	newFile := newNode.(*File)
	if err := newFile.maybeLoadEntry(ctx); err != nil {
		return nil, err
	}

	return newFile, err

}

func (dir *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) {

	glog.V(4).Infof("Symlink: %v/%v to %v", dir.FullPath(), req.NewName, req.Target)

	request := &filer_pb.CreateEntryRequest{
		Directory: dir.FullPath(),
		Entry: &filer_pb.Entry{
			Name:        req.NewName,
			IsDirectory: false,
			Attributes: &filer_pb.FuseAttributes{
				Mtime:         time.Now().Unix(),
				Crtime:        time.Now().Unix(),
				FileMode:      uint32((os.FileMode(0777) | os.ModeSymlink) &^ dir.wfs.option.Umask),
				Uid:           req.Uid,
				Gid:           req.Gid,
				SymlinkTarget: req.Target,
			},
		},
		Signatures: []int32{dir.wfs.signature},
	}

	err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {

		dir.wfs.mapPbIdFromLocalToFiler(request.Entry)
		defer dir.wfs.mapPbIdFromFilerToLocal(request.Entry)

		if err := filer_pb.CreateEntry(client, request); err != nil {
			glog.V(0).Infof("symlink %s/%s: %v", dir.FullPath(), req.NewName, err)
			return fuse.EIO
		}

		dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry))

		return nil
	})

	symlink := dir.newFile(req.NewName, request.Entry)

	return symlink, err

}

func (file *File) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {

	if err := file.maybeLoadEntry(ctx); err != nil {
		return "", err
	}

	if os.FileMode(file.entry.Attributes.FileMode)&os.ModeSymlink == 0 {
		return "", fuse.Errno(syscall.EINVAL)
	}

	glog.V(4).Infof("Readlink: %v/%v => %v", file.dir.FullPath(), file.Name, file.entry.Attributes.SymlinkTarget)

	return file.entry.Attributes.SymlinkTarget, nil

}
<commit_msg>add a hard link marker to 16byte + maker, for future extensions<commit_after>package filesys

import (
	"context"
	"github.com/chrislusf/seaweedfs/weed/util"
	"os"
	"syscall"
	"time"

	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/fuse"
	"github.com/seaweedfs/fuse/fs"
)

var _ = fs.NodeLinker(&Dir{})
var _ = fs.NodeSymlinker(&Dir{})
var _ = fs.NodeReadlinker(&File{})

const (
	HARD_LINK_MARKER = '\x01'
)

func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) {

	oldFile, ok := old.(*File)
	if !ok {
		glog.Errorf("old node is not a file: %+v", old)
	}

	glog.V(4).Infof("Link: %v/%v -> %v/%v", oldFile.dir.FullPath(), oldFile.Name, dir.FullPath(), req.NewName)

	if err := oldFile.maybeLoadEntry(ctx); err != nil {
		return nil, err
	}

	// update old file to hardlink mode
	if len(oldFile.entry.HardLinkId) == 0 {
		oldFile.entry.HardLinkId = append(util.RandomBytes(16), HARD_LINK_MARKER)
		oldFile.entry.HardLinkCounter = 1
	}
	oldFile.entry.HardLinkCounter++
	updateOldEntryRequest := &filer_pb.UpdateEntryRequest{
		Directory:  oldFile.dir.FullPath(),
		Entry:      oldFile.entry,
		Signatures: []int32{dir.wfs.signature},
	}

	// CreateLink 1.2 : update new file to hardlink mode
	request := &filer_pb.CreateEntryRequest{
		Directory: dir.FullPath(),
		Entry: &filer_pb.Entry{
			Name:            req.NewName,
			IsDirectory:     false,
			Attributes:      oldFile.entry.Attributes,
			Chunks:          oldFile.entry.Chunks,
			Extended:        oldFile.entry.Extended,
			HardLinkId:      oldFile.entry.HardLinkId,
			HardLinkCounter: oldFile.entry.HardLinkCounter,
		},
		Signatures: []int32{dir.wfs.signature},
	}

	// apply changes to the filer, and also apply to local metaCache
	err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {

		dir.wfs.mapPbIdFromLocalToFiler(request.Entry)
		defer dir.wfs.mapPbIdFromFilerToLocal(request.Entry)

		if err := filer_pb.UpdateEntry(client, updateOldEntryRequest); err != nil {
			glog.V(0).Infof("Link %v/%v -> %s/%s: %v", oldFile.dir.FullPath(), oldFile.Name, dir.FullPath(), req.NewName, err)
			return fuse.EIO
		}
		dir.wfs.metaCache.UpdateEntry(context.Background(), filer.FromPbEntry(updateOldEntryRequest.Directory, updateOldEntryRequest.Entry))

		if err := filer_pb.CreateEntry(client, request); err != nil {
			glog.V(0).Infof("Link %v/%v -> %s/%s: %v", oldFile.dir.FullPath(), oldFile.Name, dir.FullPath(), req.NewName, err)
			return fuse.EIO
		}
		dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry))

		return nil
	})

	// create new file node
	newNode := dir.newFile(req.NewName, request.Entry)
	newFile := newNode.(*File)
	if err := newFile.maybeLoadEntry(ctx); err != nil {
		return nil, err
	}

	return newFile, err

}

func (dir *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) {

	glog.V(4).Infof("Symlink: %v/%v to %v", dir.FullPath(), req.NewName, req.Target)

	request := &filer_pb.CreateEntryRequest{
		Directory: dir.FullPath(),
		Entry: &filer_pb.Entry{
			Name:        req.NewName,
			IsDirectory: false,
			Attributes: &filer_pb.FuseAttributes{
				Mtime:         time.Now().Unix(),
				Crtime:        time.Now().Unix(),
				FileMode:      uint32((os.FileMode(0777) | os.ModeSymlink) &^ dir.wfs.option.Umask),
				Uid:           req.Uid,
				Gid:           req.Gid,
				SymlinkTarget: req.Target,
			},
		},
		Signatures: []int32{dir.wfs.signature},
	}

	err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {

		dir.wfs.mapPbIdFromLocalToFiler(request.Entry)
		defer dir.wfs.mapPbIdFromFilerToLocal(request.Entry)

		if err := filer_pb.CreateEntry(client, request); err != nil {
			glog.V(0).Infof("symlink %s/%s: %v", dir.FullPath(), req.NewName, err)
			return fuse.EIO
		}

		dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry))

		return nil
	})

	symlink := dir.newFile(req.NewName, request.Entry)

	return symlink, err

}

func (file *File) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {

	if err := file.maybeLoadEntry(ctx); err != nil {
		return "", err
	}

	if os.FileMode(file.entry.Attributes.FileMode)&os.ModeSymlink == 0 {
		return "", fuse.Errno(syscall.EINVAL)
	}

	glog.V(4).Infof("Readlink: %v/%v => %v", file.dir.FullPath(), file.Name, file.entry.Attributes.SymlinkTarget)

	return file.entry.Attributes.SymlinkTarget, nil

}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/koding/bongo"
)

type Channel struct {
	// unique identifier of the channel
	Id int64 `json:"id,string"`

	// Name of the channel
	Name string `json:"name"                         sql:"NOT NULL;TYPE:VARCHAR(200);"`

	// Creator of the channel
	CreatorId int64 `json:"creatorId,string"         sql:"NOT NULL"`

	// Name of the group which channel is belong to
	GroupName string `json:"groupName"               sql:"NOT NULL;TYPE:VARCHAR(200);"`

	// Purpose of the channel
	Purpose string `json:"purpose"`

	// Secret key of the channel for event propagation purposes
	// we can put this key into another table?
	SecretKey string `json:"-"`

	// Type of the channel
	TypeConstant string `json:"typeConstant"         sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Privacy constant of the channel
	PrivacyConstant string `json:"privacyConstant"   sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creation date of the channel
	CreatedAt time.Time `json:"createdAt"            sql:"NOT NULL"`

	// Modification date of the channel
	UpdatedAt time.Time `json:"updatedAt"            sql:"NOT NULL"`

	// Deletion date of the channel
	DeletedAt time.Time `json:"deletedAt"`
}

// to-do check for allowed channels
const (
	// TYPES
	Channel_TYPE_GROUP           = "group"
	Channel_TYPE_TOPIC           = "topic"
	Channel_TYPE_FOLLOWINGFEED   = "followingfeed"
	Channel_TYPE_FOLLOWERS       = "followers"
	Channel_TYPE_CHAT            = "chat"
	Channel_TYPE_PINNED_ACTIVITY = "pinnedactivity"
	Channel_TYPE_PRIVATE_MESSAGE = "privatemessage"
	Channel_TYPE_DEFAULT         = "default"
	// Privacy
	Channel_PRIVACY_PUBLIC  = "public"
	Channel_PRIVACY_PRIVATE = "private"
	// Koding Group Name
	Channel_KODING_NAME = "koding"
)

func NewChannel() *Channel {
	return &Channel{
		Name:            "Channel" + RandomName(),
		CreatorId:       0,
		GroupName:       Channel_KODING_NAME,
		Purpose:         "",
		SecretKey:       "",
		TypeConstant:    Channel_TYPE_DEFAULT,
		PrivacyConstant: Channel_PRIVACY_PRIVATE,
	}
}

func NewPrivateMessageChannel(creatorId int64, groupName string) *Channel {
	c := NewChannel()
	c.GroupName = groupName
	c.CreatorId = creatorId
	c.Name = RandomName()
	c.TypeConstant = Channel_TYPE_PRIVATE_MESSAGE
	c.PrivacyConstant = Channel_PRIVACY_PRIVATE
	c.Purpose = ""
	return c
}

func (c *Channel) BeforeCreate() {
	c.CreatedAt = time.Now().UTC()
	c.UpdatedAt = time.Now().UTC()
	c.DeletedAt = ZeroDate()
}

func (c *Channel) BeforeUpdate() {
	c.UpdatedAt = time.Now()
}

func (c Channel) GetId() int64 {
	return c.Id
}

func (c Channel) TableName() string {
	return "api.channel"
}

func (c *Channel) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *Channel) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c Channel) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c *Channel) Update() error {
	if c.Name == "" || c.GroupName == "" {
		return fmt.Errorf("Validation failed %s - %s", c.Name, c.GroupName)
	}

	return bongo.B.Update(c)
}

func (c *Channel) Create() error {
	if c.Name == "" || c.GroupName == "" || c.TypeConstant == "" {
		return fmt.Errorf("Validation failed %s - %s -%s", c.Name, c.GroupName, c.TypeConstant)
	}

	// golang returns -1 if item not in the string
	if strings.Index(c.Name, " ") > -1 {
		return fmt.Errorf("Channel name %q has empty space in it", c.Name)
	}

	if c.TypeConstant == Channel_TYPE_GROUP ||
		c.TypeConstant == Channel_TYPE_FOLLOWERS /* we can add more types here */ {

		var selector map[string]interface{}
		switch c.TypeConstant {
		case Channel_TYPE_GROUP:
			selector = map[string]interface{}{
				"group_name":    c.GroupName,
				"type_constant": c.TypeConstant,
			}
		case Channel_TYPE_FOLLOWERS:
			selector = map[string]interface{}{
				"creator_id":    c.CreatorId,
				"type_constant": c.TypeConstant,
			}
		}

		// if err is nil
		// it means we already have that channel
		err := c.One(bongo.NewQS(selector))
		if err == nil {
			return nil
			// return fmt.Errorf("%s typed channel is already created before for %s group", c.TypeConstant, c.GroupName)
		}

		if err != gorm.RecordNotFound {
			return err
		}

	}

	return bongo.B.Create(c)
}

func (c *Channel) Delete() error {
	return bongo.B.Delete(c)
}

func (c *Channel) ById(id int64) error {
	return bongo.B.ById(c, id)
}

func (c *Channel) One(q *bongo.Query) error {
	return bongo.B.One(c, c, q)
}

func (c *Channel) Some(data interface{}, q *bongo.Query) error {
	return bongo.B.Some(c, data, q)
}

func (c *Channel) FetchByIds(ids []int64) ([]Channel, error) {
	var channels []Channel

	if len(ids) == 0 {
		return channels, nil
	}

	if err := bongo.B.FetchByIds(c, &channels, ids); err != nil {
		return nil, err
	}
	return channels, nil
}

func (c *Channel) AddParticipant(participantId int64) (*ChannelParticipant, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cp := NewChannelParticipant()
	cp.ChannelId = c.Id
	cp.AccountId = participantId

	err := cp.FetchParticipant()
	if err != nil && err != gorm.RecordNotFound {
		return nil, err
	}

	// if we have this record in DB
	if cp.Id != 0 {
		// if status is not active
		if cp.StatusConstant == ChannelParticipant_STATUS_ACTIVE {
			return nil, errors.New(fmt.Sprintf("Account %d is already a participant of channel %d", cp.AccountId, cp.ChannelId))
		}
		cp.StatusConstant = ChannelParticipant_STATUS_ACTIVE
		if err := cp.Update(); err != nil {
			return nil, err
		}
		return cp, nil
	}

	cp.StatusConstant = ChannelParticipant_STATUS_ACTIVE

	if err := cp.Create(); err != nil {
		return nil, err
	}

	return cp, nil
}

func (c *Channel) RemoveParticipant(participantId int64) error {
	if c.Id == 0 {
		return errors.New("Channel Id is not set")
	}

	cp := NewChannelParticipant()
	cp.ChannelId = c.Id
	cp.AccountId = participantId

	err := cp.FetchParticipant()
	// if user is not in this channel, do nothing
	if err == gorm.RecordNotFound {
		return nil
	}

	if err != nil {
		return err
	}

	if cp.StatusConstant == ChannelParticipant_STATUS_LEFT {
		return nil
	}

	cp.StatusConstant = ChannelParticipant_STATUS_LEFT
	if err := cp.Update(); err != nil {
		return err
	}

	return nil
}

func (c *Channel) FetchParticipantIds() ([]int64, error) {
	var participantIds []int64

	if c.Id == 0 {
		return participantIds, errors.New("Channel Id is not set")
	}

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"channel_id":      c.Id,
			"status_constant": ChannelParticipant_STATUS_ACTIVE,
		},
		Pluck: "account_id",
	}

	cp := NewChannelParticipant()
	err := cp.Some(&participantIds, query)
	if err != nil {
		return nil, err
	}

	return participantIds, nil
}

func (c *Channel) AddMessage(messageId int64) (*ChannelMessageList, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cml := NewChannelMessageList()

	selector := map[string]interface{}{
		"channel_id": c.Id,
		"message_id": messageId,
	}
	err := cml.One(bongo.NewQS(selector))
	if err == nil {
		return nil, errors.New("Message is already in the channel")
	}

	// silence record not found err
	if err != gorm.RecordNotFound {
		return nil, err
	}

	cml.ChannelId = c.Id
	cml.MessageId = messageId

	if err := cml.Create(); err != nil {
		return nil, err
	}

	return cml, nil
}

func (c *Channel) RemoveMessage(messageId int64) (*ChannelMessageList, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cml := NewChannelMessageList()
	selector := map[string]interface{}{
		"channel_id": c.Id,
		"message_id": messageId,
	}
	err := cml.One(bongo.NewQS(selector))
	// one returns error when record not found case
	// but we dont care if it is not there tho
	if err != nil {
		return nil, err
	}

	if err := cml.Delete(); err != nil {
		return nil, err
	}

	return cml, nil
}

func (c *Channel) FetchChannelIdByNameAndGroupName(name, groupName string) (int64, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"name":       name,
			"group_name": groupName,
		},
		Pagination: *bongo.NewPagination(1, 0),
		Pluck:      "id",
	}
	var ids []int64
	if err := c.Some(&ids, query); err != nil {
		return 0, err
	}

	if ids == nil {
		return 0, gorm.RecordNotFound
	}

	if len(ids) == 0 {
		return 0, gorm.RecordNotFound
	}

	return ids[0], nil
}

func (c *Channel) Search(q *Query) ([]Channel, error) {

	if q.GroupName == "" {
		return nil, fmt.Errorf("Query doesnt have any Group info %+v", q)
	}

	var channels []Channel

	query := bongo.B.DB.Table(c.TableName()).Limit(q.Limit)

	query = query.Where("type_constant = ?", q.Type)
	query = query.Where("privacy_constant = ?", Channel_PRIVACY_PUBLIC)
	query = query.Where("group_name = ?", q.GroupName)
	query = query.Where("name like ?", q.Name+"%")

	if err := query.Find(&channels).Error; err != nil {
		return nil, err
	}

	if channels == nil {
		return make([]Channel, 0), nil
	}

	return channels, nil
}

func (c *Channel) List(q *Query) ([]Channel, error) {

	if q.GroupName == "" {
		return nil, fmt.Errorf("Query doesnt have any Group info %+v", q)
	}

	var channels []Channel

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"group_name": q.GroupName,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	if q.Type != "" {
		query.Selector["type_constant"] = q.Type
	}

	err := c.Some(&channels, query)
	if err != nil {
		return nil, err
	}

	if channels == nil {
		return make([]Channel, 0), nil
	}

	return channels, nil
}

func (c *Channel) FetchLastMessage() (*ChannelMessage, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cml := NewChannelMessageList()
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"channel_id": c.Id,
		},
		Sort: map[string]string{
			"added_at": "DESC",
		},
		Pagination: *bongo.NewPagination(1, 0),
		Pluck:      "message_id",
	}

	var messageIds []int64
	err := cml.Some(&messageIds, query)
	if err != nil {
		return nil, err
	}

	if messageIds == nil || len(messageIds) == 0 {
		return nil, nil
	}

	cm := NewChannelMessage()
	if err := cm.ById(messageIds[0]); err != nil {
		return nil, err
	}

	return cm, nil
}
<commit_msg>Social: pinned channel checker for given channel ids is added<commit_after>package models

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/koding/bongo"
)

type Channel struct {
	// unique identifier of the channel
	Id int64 `json:"id,string"`

	// Name of the channel
	Name string `json:"name"                         sql:"NOT NULL;TYPE:VARCHAR(200);"`

	// Creator of the channel
	CreatorId int64 `json:"creatorId,string"         sql:"NOT NULL"`

	// Name of the group which channel is belong to
	GroupName string `json:"groupName"               sql:"NOT NULL;TYPE:VARCHAR(200);"`

	// Purpose of the channel
	Purpose string `json:"purpose"`

	// Secret key of the channel for event propagation purposes
	// we can put this key into another table?
	SecretKey string `json:"-"`

	// Type of the channel
	TypeConstant string `json:"typeConstant"         sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Privacy constant of the channel
	PrivacyConstant string `json:"privacyConstant"   sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creation date of the channel
	CreatedAt time.Time `json:"createdAt"            sql:"NOT NULL"`

	// Modification date of the channel
	UpdatedAt time.Time `json:"updatedAt"            sql:"NOT NULL"`

	// Deletion date of the channel
	DeletedAt time.Time `json:"deletedAt"`
}

// to-do check for allowed channels
const (
	// TYPES
	Channel_TYPE_GROUP           = "group"
	Channel_TYPE_TOPIC           = "topic"
	Channel_TYPE_FOLLOWINGFEED   = "followingfeed"
	Channel_TYPE_FOLLOWERS       = "followers"
	Channel_TYPE_CHAT            = "chat"
	Channel_TYPE_PINNED_ACTIVITY = "pinnedactivity"
	Channel_TYPE_PRIVATE_MESSAGE = "privatemessage"
	Channel_TYPE_DEFAULT         = "default"
	// Privacy
	Channel_PRIVACY_PUBLIC  = "public"
	Channel_PRIVACY_PRIVATE = "private"
	// Koding Group Name
	Channel_KODING_NAME = "koding"
)

func NewChannel() *Channel {
	return &Channel{
		Name:            "Channel" + RandomName(),
		CreatorId:       0,
		GroupName:       Channel_KODING_NAME,
		Purpose:         "",
		SecretKey:       "",
		TypeConstant:    Channel_TYPE_DEFAULT,
		PrivacyConstant: Channel_PRIVACY_PRIVATE,
	}
}

func NewPrivateMessageChannel(creatorId int64, groupName string) *Channel {
	c := NewChannel()
	c.GroupName = groupName
	c.CreatorId = creatorId
	c.Name = RandomName()
	c.TypeConstant = Channel_TYPE_PRIVATE_MESSAGE
	c.PrivacyConstant = Channel_PRIVACY_PRIVATE
	c.Purpose = ""
	return c
}

func (c *Channel) BeforeCreate() {
	c.CreatedAt = time.Now().UTC()
	c.UpdatedAt = time.Now().UTC()
	c.DeletedAt = ZeroDate()
}

func (c *Channel) BeforeUpdate() {
	c.UpdatedAt = time.Now()
}

func (c Channel) GetId() int64 {
	return c.Id
}

func (c Channel) TableName() string {
	return "api.channel"
}

func (c *Channel) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *Channel) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c Channel) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c *Channel) Update() error {
	if c.Name == "" || c.GroupName == "" {
		return fmt.Errorf("Validation failed %s - %s", c.Name, c.GroupName)
	}

	return bongo.B.Update(c)
}

func (c *Channel) Create() error {
	if c.Name == "" || c.GroupName == "" || c.TypeConstant == "" {
		return fmt.Errorf("Validation failed %s - %s -%s", c.Name, c.GroupName, c.TypeConstant)
	}

	// golang returns -1 if item not in the string
	if strings.Index(c.Name, " ") > -1 {
		return fmt.Errorf("Channel name %q has empty space in it", c.Name)
	}

	if c.TypeConstant == Channel_TYPE_GROUP ||
		c.TypeConstant == Channel_TYPE_FOLLOWERS /* we can add more types here */ {

		var selector map[string]interface{}
		switch c.TypeConstant {
		case Channel_TYPE_GROUP:
			selector = map[string]interface{}{
				"group_name":    c.GroupName,
				"type_constant": c.TypeConstant,
			}
		case Channel_TYPE_FOLLOWERS:
			selector = map[string]interface{}{
				"creator_id":    c.CreatorId,
				"type_constant": c.TypeConstant,
			}
		}

		// if err is nil
		// it means we already have that channel
		err := c.One(bongo.NewQS(selector))
		if err == nil {
			return nil
			// return fmt.Errorf("%s typed channel is already created before for %s group", c.TypeConstant, c.GroupName)
		}

		if err != gorm.RecordNotFound {
			return err
		}

	}

	return bongo.B.Create(c)
}

func (c *Channel) Delete() error {
	return bongo.B.Delete(c)
}

func (c *Channel) ById(id int64) error {
	return bongo.B.ById(c, id)
}

func (c *Channel) One(q *bongo.Query) error {
	return bongo.B.One(c, c, q)
}

func (c *Channel) Some(data interface{}, q *bongo.Query) error {
	return bongo.B.Some(c, data, q)
}

func (c *Channel) FetchByIds(ids []int64) ([]Channel, error) {
	var channels []Channel

	if len(ids) == 0 {
		return channels, nil
	}

	if err := bongo.B.FetchByIds(c, &channels, ids); err != nil {
		return nil, err
	}
	return channels, nil
}

func (c *Channel) AddParticipant(participantId int64) (*ChannelParticipant, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cp := NewChannelParticipant()
	cp.ChannelId = c.Id
	cp.AccountId = participantId

	err := cp.FetchParticipant()
	if err != nil && err != gorm.RecordNotFound {
		return nil, err
	}

	// if we have this record in DB
	if cp.Id != 0 {
		// if status is not active
		if cp.StatusConstant == ChannelParticipant_STATUS_ACTIVE {
			return nil, errors.New(fmt.Sprintf("Account %d is already a participant of channel %d", cp.AccountId, cp.ChannelId))
		}
		cp.StatusConstant = ChannelParticipant_STATUS_ACTIVE
		if err := cp.Update(); err != nil {
			return nil, err
		}
		return cp, nil
	}

	cp.StatusConstant = ChannelParticipant_STATUS_ACTIVE

	if err := cp.Create(); err != nil {
		return nil, err
	}

	return cp, nil
}

func (c *Channel) RemoveParticipant(participantId int64) error {
	if c.Id == 0 {
		return errors.New("Channel Id is not set")
	}

	cp := NewChannelParticipant()
	cp.ChannelId = c.Id
	cp.AccountId = participantId

	err := cp.FetchParticipant()
	// if user is not in this channel, do nothing
	if err == gorm.RecordNotFound {
		return nil
	}

	if err != nil {
		return err
	}

	if cp.StatusConstant == ChannelParticipant_STATUS_LEFT {
		return nil
	}

	cp.StatusConstant = ChannelParticipant_STATUS_LEFT
	if err := cp.Update(); err != nil {
		return err
	}

	return nil
}

func (c *Channel) FetchParticipantIds() ([]int64, error) {
	var participantIds []int64

	if c.Id == 0 {
		return participantIds, errors.New("Channel Id is not set")
	}

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"channel_id":      c.Id,
			"status_constant": ChannelParticipant_STATUS_ACTIVE,
		},
		Pluck: "account_id",
	}

	cp := NewChannelParticipant()
	err := cp.Some(&participantIds, query)
	if err != nil {
		return nil, err
	}

	return participantIds, nil
}

func (c *Channel) AddMessage(messageId int64) (*ChannelMessageList, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cml := NewChannelMessageList()

	selector := map[string]interface{}{
		"channel_id": c.Id,
		"message_id": messageId,
	}
	err := cml.One(bongo.NewQS(selector))
	if err == nil {
		return nil, errors.New("Message is already in the channel")
	}

	// silence record not found err
	if err != gorm.RecordNotFound {
		return nil, err
	}

	cml.ChannelId = c.Id
	cml.MessageId = messageId

	if err := cml.Create(); err != nil {
		return nil, err
	}

	return cml, nil
}

func (c *Channel) RemoveMessage(messageId int64) (*ChannelMessageList, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cml := NewChannelMessageList()
	selector := map[string]interface{}{
		"channel_id": c.Id,
		"message_id": messageId,
	}
	err := cml.One(bongo.NewQS(selector))
	// one returns error when record not found case
	// but we dont care if it is not there tho
	if err != nil {
		return nil, err
	}

	if err := cml.Delete(); err != nil {
		return nil, err
	}

	return cml, nil
}

func (c *Channel) FetchChannelIdByNameAndGroupName(name, groupName string) (int64, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"name":       name,
			"group_name": groupName,
		},
		Pagination: *bongo.NewPagination(1, 0),
		Pluck:      "id",
	}
	var ids []int64
	if err := c.Some(&ids, query); err != nil {
		return 0, err
	}

	if ids == nil {
		return 0, gorm.RecordNotFound
	}

	if len(ids) == 0 {
		return 0, gorm.RecordNotFound
	}

	return ids[0], nil
}

func (c *Channel) Search(q *Query) ([]Channel, error) {

	if q.GroupName == "" {
		return nil, fmt.Errorf("Query doesnt have any Group info %+v", q)
	}

	var channels []Channel

	query := bongo.B.DB.Table(c.TableName()).Limit(q.Limit)

	query = query.Where("type_constant = ?", q.Type)
	query = query.Where("privacy_constant = ?", Channel_PRIVACY_PUBLIC)
	query = query.Where("group_name = ?", q.GroupName)
	query = query.Where("name like ?", q.Name+"%")

	if err := query.Find(&channels).Error; err != nil {
		return nil, err
	}

	if channels == nil {
		return make([]Channel, 0), nil
	}

	return channels, nil
}

func (c *Channel) List(q *Query) ([]Channel, error) {

	if q.GroupName == "" {
		return nil, fmt.Errorf("Query doesnt have any Group info %+v", q)
	}

	var channels []Channel

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"group_name": q.GroupName,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	if q.Type != "" {
		query.Selector["type_constant"] = q.Type
	}

	err := c.Some(&channels, query)
	if err != nil {
		return nil, err
	}

	if channels == nil {
		return make([]Channel, 0), nil
	}

	return channels, nil
}

func (c *Channel) FetchLastMessage() (*ChannelMessage, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cml := NewChannelMessageList()
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"channel_id": c.Id,
		},
		Sort: map[string]string{
			"added_at": "DESC",
		},
		Pagination: *bongo.NewPagination(1, 0),
		Pluck:      "message_id",
	}

	var messageIds []int64
	err := cml.Some(&messageIds, query)
	if err != nil {
		return nil, err
	}

	if messageIds == nil || len(messageIds) == 0 {
		return nil, nil
	}

	cm := NewChannelMessage()
	if err := cm.ById(messageIds[0]); err != nil {
		return nil, err
	}

	return cm, nil
}

func (c *Channel) CheckChannelPinned(ids []int64) (bool, error) {
	err := bongo.B.DB.Table(c.TableName()).
		Where(ids).
		Where("type_constant = ?", Channel_TYPE_PINNED_ACTIVITY).
		Where("creator_id = ?", c.CreatorId).
		Limit(1).
		Find(c).Error

	if err != nil {
		if err == gorm.RecordNotFound {

			return false, nil
		}

		return false, err
	}

	return true, nil
}
<|endoftext|>


<commit_before>package vtctld

import (
	"fmt"
	"sort"
	"sync"

	"github.com/youtube/vitess/go/vt/discovery"
	"github.com/youtube/vitess/go/vt/proto/topodata"
	"github.com/youtube/vitess/go/vt/topo/topoproto"

	topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
)

// yLabel is used to keep track of the outer and inner labels of the heatmap.
type yLabel struct {
	Label        label
	NestedLabels []label
}

// label is used to keep track of one label of a heatmap and how many rows it should span.
type label struct {
	Name    string
	Rowspan int
}

// heatmap stores all the needed info to construct the heatmap.
type heatmap struct {
	// Labels has the outer and inner labels for each row.
	Labels []yLabel
	// Data is a 2D array of values of the specified metric.
	Data [][]float64
	// Aliases is a 2D array holding references to the tablet aliases.
	Aliases [][]*topodata.TabletAlias
}

type byTabletUid []*discovery.TabletStats

func (a byTabletUid) Len() int           { return len(a) }
func (a byTabletUid) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byTabletUid) Less(i, j int) bool { return a[i].Tablet.Alias.Uid < a[j].Tablet.Alias.Uid }

const tabletMissing = -1

// tabletStatsCache holds the most recent status update received for
// each tablet. The tablets are indexed by uid, so it is different
// than discovery.TabletStatsCache.
type tabletStatsCache struct {
	// mu guards access to the fields below.
	mu sync.Mutex
	// statuses keeps a map of TabletStats.
	// The first key is the keyspace, the second key is the shard,
	// the third key is the cell, the last key is the tabletType.
	// The keys are strings to allow exposing this map as a JSON object in api.go.
	statuses map[string]map[string]map[string]map[topodatapb.TabletType][]*discovery.TabletStats
	// statusesByAlias is a copy of statuses and will be updated simultaneously.
	// The first key is the string representation of the tablet alias.
	statusesByAlias map[string]*discovery.TabletStats
	// cells counts the number of tablets per cell.
	tabletCountsByCell map[string]int
}

func newTabletStatsCache() *tabletStatsCache {
	return &tabletStatsCache{
		statuses:           make(map[string]map[string]map[string]map[topodatapb.TabletType][]*discovery.TabletStats),
		statusesByAlias:    make(map[string]*discovery.TabletStats),
		tabletCountsByCell: make(map[string]int),
	}
}

// StatsUpdate is part of the discovery.HealthCheckStatsListener interface.
// Upon receiving a new TabletStats, it updates the two maps in tablet_stats_cache.
func (c *tabletStatsCache) StatsUpdate(stats *discovery.TabletStats) {
	c.mu.Lock()
	defer c.mu.Unlock()

	keyspace := stats.Target.Keyspace
	shard := stats.Target.Shard
	cell := stats.Tablet.Alias.Cell
	tabletType := stats.Target.TabletType

	aliasKey := tabletToMapKey(stats)
	ts, ok := c.statusesByAlias[aliasKey]
	if !stats.Up {
		if !ok {
			// Tablet doesn't exist and was recently deleted or changed its type. Panic as this is unexpected behavior.
			panic(fmt.Sprintf("BUG: tablet (%v) doesn't exist", aliasKey))
		}
		// The tablet still exists in our cache but was recently deleted or changed its type. Delete it now.
		c.statuses[keyspace][shard][cell][tabletType] = remove(c.statuses[keyspace][shard][cell][tabletType], stats.Tablet.Alias)
		delete(c.statusesByAlias, aliasKey)
		c.tabletCountsByCell[cell]--
		if c.tabletCountsByCell[cell] == 0 {
			delete(c.tabletCountsByCell, cell)
		}
		return
	}

	if !ok {
		// Tablet isn't tracked yet so just add it.
		shards, ok := c.statuses[keyspace]
		if !ok {
			shards = make(map[string]map[string]map[topodatapb.TabletType][]*discovery.TabletStats)
			c.statuses[keyspace] = shards
		}

		cells, ok := c.statuses[keyspace][shard]
		if !ok {
			cells = make(map[string]map[topodatapb.TabletType][]*discovery.TabletStats)
			c.statuses[keyspace][shard] = cells
		}

		types, ok := c.statuses[keyspace][shard][cell]
		if !ok {
			types = make(map[topodatapb.TabletType][]*discovery.TabletStats)
			c.statuses[keyspace][shard][cell] = types
		}

		tablets, ok := c.statuses[keyspace][shard][cell][tabletType]
		if !ok {
			tablets = make([]*discovery.TabletStats, 0)
			c.statuses[keyspace][shard][cell][tabletType] = tablets
		}

		c.statuses[keyspace][shard][cell][tabletType] = append(c.statuses[keyspace][shard][cell][tabletType], stats)
		sort.Sort(byTabletUid(c.statuses[keyspace][shard][cell][tabletType]))
		c.statusesByAlias[aliasKey] = stats
		c.tabletCountsByCell[cell]++
		return
	}

	// Tablet already exists so just update it in the cache.
	*ts = *stats
}

func tabletToMapKey(stats *discovery.TabletStats) string {
	return stats.Tablet.Alias.String()
}

// remove takes in an array and returns it with the specified element removed
// (leaves the array unchanged if element isn't in the array).
func remove(tablets []*discovery.TabletStats, tabletAlias *topodata.TabletAlias) []*discovery.TabletStats {
	filteredTablets := tablets[:0]
	for _, tablet := range tablets {
		if !topoproto.TabletAliasEqual(tablet.Tablet.Alias, tabletAlias) {
			filteredTablets = append(filteredTablets, tablet)
		}
	}
	return filteredTablets
}

// heatmapData returns a 2D array of data (based on the specified metric) as well as the labels for the heatmap.
func (c *tabletStatsCache) heatmapData(keyspace, cell, tabletType, metric string) (heatmap, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	// Get the metric data.
	var metricFunc func(stats *discovery.TabletStats) float64
	switch metric {
	case "lag":
		metricFunc = replicationLag
	case "cpu":
		metricFunc = cpu
	case "qps":
		metricFunc = qps
	default:
		return heatmap{}, fmt.Errorf("invalid metric: %v Select 'lag', 'cpu', or 'qps'", metric)
	}

	var cells []string
	for cell := range c.tabletCountsByCell {
		cells = append(cells, cell)
	}
	sort.Strings(cells)

	var shards []string
	for s := range c.statuses[keyspace] {
		shards = append(shards, s)
	}
	sort.Strings(shards)

	types := []topodatapb.TabletType{topodatapb.TabletType_MASTER, topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY}

	// TODO(pkulshre): Generalize the following algorithm to support any combination of keyspace-cell-type.
	var heatmapData [][]float64
	var heatmapTabletAliases [][]*topodata.TabletAlias
	var yLabels []yLabel
	// The loop goes through every outer label (in this case, cell).
	for _, cell := range cells {
		perCellYLabel := yLabel{}
		labelSpan := 0

		// This loop goes through every nested label (in this case, tablet type).
		for _, tabletType := range types {
			maxRowLength := 0

			// The loop calculates the maximum number of rows needed.
			for _, shard := range shards {
				tabletsCount := len(c.statuses[keyspace][shard][cell][tabletType])
				if maxRowLength < tabletsCount {
					maxRowLength = tabletsCount
				}
			}

			// dataRowsPerType is a 2D array that will hold the data of the tablets of one (cell, type) combination.
			dataRowsPerType := make([][]float64, maxRowLength)
			// aliasRowsPerType is a 2D array that will hold the aliases of the tablets of one (cell, type) combination.
			aliasRowsPerType := make([][]*topodata.TabletAlias, maxRowLength)
			for i := range dataRowsPerType {
				dataRowsPerType[i] = make([]float64, len(shards))
				aliasRowsPerType[i] = make([]*topodata.TabletAlias, len(shards))
			}

			// Filling in the 2D array with tablet data by columns.
			for shardIndex, shard := range shards {
				for tabletIndex := 0; tabletIndex < maxRowLength; tabletIndex++ {
					// If the key doesn't exist then the tablet must not exist so that data is set to -1 (tabletMissing).
					if tabletIndex < len(c.statuses[keyspace][shard][cell][tabletType]) {
						dataRowsPerType[tabletIndex][shardIndex] = metricFunc(c.statuses[keyspace][shard][cell][tabletType][tabletIndex])
						aliasRowsPerType[tabletIndex][shardIndex] = c.statuses[keyspace][shard][cell][tabletType][tabletIndex].Tablet.Alias
					} else {
						dataRowsPerType[tabletIndex][shardIndex] = tabletMissing
						aliasRowsPerType[tabletIndex][shardIndex] = nil
					}

					// Adding the labels for the yaxis only if it is the first column.
					if shardIndex == 0 && tabletIndex == (maxRowLength-1) {
						tempLabel := label{
							Name:    tabletType.String(),
							Rowspan: maxRowLength,
						}
						perCellYLabel.NestedLabels = append(perCellYLabel.NestedLabels, tempLabel)
						labelSpan += maxRowLength
					}
				}
			}

			for i := 0; i < len(dataRowsPerType); i++ {
				heatmapData = append(heatmapData, dataRowsPerType[i])
				heatmapTabletAliases = append(heatmapTabletAliases, aliasRowsPerType[i])
			}
		}
		perCellYLabel.Label = label{
			Name:    cell,
			Rowspan: labelSpan,
		}
		yLabels = append(yLabels, perCellYLabel)
	}

	return heatmap{
		Data:    heatmapData,
		Labels:  yLabels,
		Aliases: heatmapTabletAliases,
	}, nil
}

func (c *tabletStatsCache) tabletStatsByAlias(tabletAlias *topodatapb.TabletAlias) *discovery.TabletStats {
	c.mu.Lock()
	defer c.mu.Unlock()

	ts, ok := c.statusesByAlias[tabletAlias.String()]
	if !ok {
		return nil
	}
	return ts
}

func replicationLag(stat *discovery.TabletStats) float64 {
	return float64(stat.Stats.SecondsBehindMaster)
}

func cpu(stat *discovery.TabletStats) float64 {
	return stat.Stats.CpuUsage
}

func qps(stat *discovery.TabletStats) float64 {
	return stat.Stats.Qps
}

// compile-time interface check
var _ discovery.HealthCheckStatsListener = (*tabletStatsCache)(nil)
<commit_msg>fixed golint error<commit_after>package vtctld

import (
	"fmt"
	"sort"
	"sync"

	"github.com/youtube/vitess/go/vt/discovery"
	"github.com/youtube/vitess/go/vt/proto/topodata"
	"github.com/youtube/vitess/go/vt/topo/topoproto"

	topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
)

// yLabel is used to keep track of the outer and inner labels of the heatmap.
type yLabel struct {
	Label        label
	NestedLabels []label
}

// label is used to keep track of one label of a heatmap and how many rows it should span.
type label struct {
	Name    string
	Rowspan int
}

// heatmap stores all the needed info to construct the heatmap.
type heatmap struct {
	// Labels has the outer and inner labels for each row.
	Labels []yLabel
	// Data is a 2D array of values of the specified metric.
	Data [][]float64
	// Aliases is a 2D array holding references to the tablet aliases.
	Aliases [][]*topodata.TabletAlias
}

type byTabletUID []*discovery.TabletStats

func (a byTabletUid) Len() int           { return len(a) }
func (a byTabletUid) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byTabletUid) Less(i, j int) bool { return a[i].Tablet.Alias.Uid < a[j].Tablet.Alias.Uid }

const tabletMissing = -1

// tabletStatsCache holds the most recent status update received for
// each tablet. The tablets are indexed by uid, so it is different
// than discovery.TabletStatsCache.
type tabletStatsCache struct {
	// mu guards access to the fields below.
	mu sync.Mutex
	// statuses keeps a map of TabletStats.
	// The first key is the keyspace, the second key is the shard,
	// the third key is the cell, the last key is the tabletType.
	// The keys are strings to allow exposing this map as a JSON object in api.go.
	statuses map[string]map[string]map[string]map[topodatapb.TabletType][]*discovery.TabletStats
	// statusesByAlias is a copy of statuses and will be updated simultaneously.
	// The first key is the string representation of the tablet alias.
	statusesByAlias map[string]*discovery.TabletStats
	// cells counts the number of tablets per cell.
	tabletCountsByCell map[string]int
}

func newTabletStatsCache() *tabletStatsCache {
	return &tabletStatsCache{
		statuses:           make(map[string]map[string]map[string]map[topodatapb.TabletType][]*discovery.TabletStats),
		statusesByAlias:    make(map[string]*discovery.TabletStats),
		tabletCountsByCell: make(map[string]int),
	}
}

// StatsUpdate is part of the discovery.HealthCheckStatsListener interface.
// Upon receiving a new TabletStats, it updates the two maps in tablet_stats_cache.
func (c *tabletStatsCache) StatsUpdate(stats *discovery.TabletStats) {
	c.mu.Lock()
	defer c.mu.Unlock()

	keyspace := stats.Target.Keyspace
	shard := stats.Target.Shard
	cell := stats.Tablet.Alias.Cell
	tabletType := stats.Target.TabletType

	aliasKey := tabletToMapKey(stats)
	ts, ok := c.statusesByAlias[aliasKey]
	if !stats.Up {
		if !ok {
			// Tablet doesn't exist and was recently deleted or changed its type. Panic as this is unexpected behavior.
			panic(fmt.Sprintf("BUG: tablet (%v) doesn't exist", aliasKey))
		}
		// The tablet still exists in our cache but was recently deleted or changed its type. Delete it now.
		c.statuses[keyspace][shard][cell][tabletType] = remove(c.statuses[keyspace][shard][cell][tabletType], stats.Tablet.Alias)
		delete(c.statusesByAlias, aliasKey)
		c.tabletCountsByCell[cell]--
		if c.tabletCountsByCell[cell] == 0 {
			delete(c.tabletCountsByCell, cell)
		}
		return
	}

	if !ok {
		// Tablet isn't tracked yet so just add it.
		shards, ok := c.statuses[keyspace]
		if !ok {
			shards = make(map[string]map[string]map[topodatapb.TabletType][]*discovery.TabletStats)
			c.statuses[keyspace] = shards
		}

		cells, ok := c.statuses[keyspace][shard]
		if !ok {
			cells = make(map[string]map[topodatapb.TabletType][]*discovery.TabletStats)
			c.statuses[keyspace][shard] = cells
		}

		types, ok := c.statuses[keyspace][shard][cell]
		if !ok {
			types = make(map[topodatapb.TabletType][]*discovery.TabletStats)
			c.statuses[keyspace][shard][cell] = types
		}

		tablets, ok := c.statuses[keyspace][shard][cell][tabletType]
		if !ok {
			tablets = make([]*discovery.TabletStats, 0)
			c.statuses[keyspace][shard][cell][tabletType] = tablets
		}

		c.statuses[keyspace][shard][cell][tabletType] = append(c.statuses[keyspace][shard][cell][tabletType], stats)
		sort.Sort(byTabletUid(c.statuses[keyspace][shard][cell][tabletType]))
		c.statusesByAlias[aliasKey] = stats
		c.tabletCountsByCell[cell]++
		return
	}

	// Tablet already exists so just update it in the cache.
	*ts = *stats
}

func tabletToMapKey(stats *discovery.TabletStats) string {
	return stats.Tablet.Alias.String()
}

// remove takes in an array and returns it with the specified element removed
// (leaves the array unchanged if element isn't in the array).
func remove(tablets []*discovery.TabletStats, tabletAlias *topodata.TabletAlias) []*discovery.TabletStats {
	filteredTablets := tablets[:0]
	for _, tablet := range tablets {
		if !topoproto.TabletAliasEqual(tablet.Tablet.Alias, tabletAlias) {
			filteredTablets = append(filteredTablets, tablet)
		}
	}
	return filteredTablets
}

// heatmapData returns a 2D array of data (based on the specified metric) as well as the labels for the heatmap.
func (c *tabletStatsCache) heatmapData(keyspace, cell, tabletType, metric string) (heatmap, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	// Get the metric data.
	var metricFunc func(stats *discovery.TabletStats) float64
	switch metric {
	case "lag":
		metricFunc = replicationLag
	case "cpu":
		metricFunc = cpu
	case "qps":
		metricFunc = qps
	default:
		return heatmap{}, fmt.Errorf("invalid metric: %v Select 'lag', 'cpu', or 'qps'", metric)
	}

	var cells []string
	for cell := range c.tabletCountsByCell {
		cells = append(cells, cell)
	}
	sort.Strings(cells)

	var shards []string
	for s := range c.statuses[keyspace] {
		shards = append(shards, s)
	}
	sort.Strings(shards)

	types := []topodatapb.TabletType{topodatapb.TabletType_MASTER, topodatapb.TabletType_REPLICA, topodatapb.TabletType_RDONLY}

	// TODO(pkulshre): Generalize the following algorithm to support any combination of keyspace-cell-type.
	var heatmapData [][]float64
	var heatmapTabletAliases [][]*topodata.TabletAlias
	var yLabels []yLabel
	// The loop goes through every outer label (in this case, cell).
	for _, cell := range cells {
		perCellYLabel := yLabel{}
		labelSpan := 0

		// This loop goes through every nested label (in this case, tablet type).
		for _, tabletType := range types {
			maxRowLength := 0

			// The loop calculates the maximum number of rows needed.
			for _, shard := range shards {
				tabletsCount := len(c.statuses[keyspace][shard][cell][tabletType])
				if maxRowLength < tabletsCount {
					maxRowLength = tabletsCount
				}
			}

			// dataRowsPerType is a 2D array that will hold the data of the tablets of one (cell, type) combination.
			dataRowsPerType := make([][]float64, maxRowLength)
			// aliasRowsPerType is a 2D array that will hold the aliases of the tablets of one (cell, type) combination.
			aliasRowsPerType := make([][]*topodata.TabletAlias, maxRowLength)
			for i := range dataRowsPerType {
				dataRowsPerType[i] = make([]float64, len(shards))
				aliasRowsPerType[i] = make([]*topodata.TabletAlias, len(shards))
			}

			// Filling in the 2D array with tablet data by columns.
			for shardIndex, shard := range shards {
				for tabletIndex := 0; tabletIndex < maxRowLength; tabletIndex++ {
					// If the key doesn't exist then the tablet must not exist so that data is set to -1 (tabletMissing).
					if tabletIndex < len(c.statuses[keyspace][shard][cell][tabletType]) {
						dataRowsPerType[tabletIndex][shardIndex] = metricFunc(c.statuses[keyspace][shard][cell][tabletType][tabletIndex])
						aliasRowsPerType[tabletIndex][shardIndex] = c.statuses[keyspace][shard][cell][tabletType][tabletIndex].Tablet.Alias
					} else {
						dataRowsPerType[tabletIndex][shardIndex] = tabletMissing
						aliasRowsPerType[tabletIndex][shardIndex] = nil
					}

					// Adding the labels for the yaxis only if it is the first column.
					if shardIndex == 0 && tabletIndex == (maxRowLength-1) {
						tempLabel := label{
							Name:    tabletType.String(),
							Rowspan: maxRowLength,
						}
						perCellYLabel.NestedLabels = append(perCellYLabel.NestedLabels, tempLabel)
						labelSpan += maxRowLength
					}
				}
			}

			for i := 0; i < len(dataRowsPerType); i++ {
				heatmapData = append(heatmapData, dataRowsPerType[i])
				heatmapTabletAliases = append(heatmapTabletAliases, aliasRowsPerType[i])
			}
		}
		perCellYLabel.Label = label{
			Name:    cell,
			Rowspan: labelSpan,
		}
		yLabels = append(yLabels, perCellYLabel)
	}

	return heatmap{
		Data:    heatmapData,
		Labels:  yLabels,
		Aliases: heatmapTabletAliases,
	}, nil
}

func (c *tabletStatsCache) tabletStatsByAlias(tabletAlias *topodatapb.TabletAlias) *discovery.TabletStats {
	c.mu.Lock()
	defer c.mu.Unlock()

	ts, ok := c.statusesByAlias[tabletAlias.String()]
	if !ok {
		return nil
	}
	return ts
}

func replicationLag(stat *discovery.TabletStats) float64 {
	return float64(stat.Stats.SecondsBehindMaster)
}

func cpu(stat *discovery.TabletStats) float64 {
	return stat.Stats.CpuUsage
}

func qps(stat *discovery.TabletStats) float64 {
	return stat.Stats.Qps
}

// compile-time interface check
var _ discovery.HealthCheckStatsListener = (*tabletStatsCache)(nil)
<|endoftext|>