<commit_before>// Copyright 2012 The go-config Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Author Nelson

// config package privide:
// 		atomic storage object
//		file configuration save, load and watcher
//		xml and Json encoder, decoder interface
package config

import (
	"errors"
	"io/ioutil"
	"reflect"
	"sync"
	"time"

	"github.com/howeyc/fsnotify"
)

type NotifyAction int

const (
	NA_Continue = iota
	NA_Restart
	NA_Stop
)

type NotifyHandler func(Sender *FileConfig, event *fsnotify.FileEvent) NotifyAction

var (
	implError     = errors.New("imtemplment of coder is wrong, cant return nil when not error")
	coderNilError = errors.New("config Coder is nil")
	saveNilError  = errors.New("can not load or save nil configuration value")
)

// Coder interface define encode and decode function
// Encode encodes configuration object to byte slice
// Decode decodes byte slice to configuration object
type Coder interface {
	Encode(v interface{}) ([]byte, error)
	Decode(data []byte, v interface{}) error
}

// A FileConfig which is used for both reading and writing configration file.
// base is Config object which provide storage. Writing and reading simultaneously are safety.
// The coder is Coder interface which encode and decode configration object
type FileConfig struct {
	base          Config
	fileName      string
	coder         Coder
	lk            sync.RWMutex
	done          chan bool
	watcherStared bool
}

// NewFileConfig creates FileConfig object
func NewFileConfig(fileName string, coder Coder) *FileConfig {
	fc := new(FileConfig)
	fc.fileName = fileName
	fc.coder = coder
	fc.done = make(chan bool)
	return fc
}

// WatcherStared is a perperty which shows the file watcher is started or not
func (fc *FileConfig) WatcherStared() bool {
	fc.lk.RLock()
	defer fc.lk.RUnlock()
	return fc.watcherStared
}

// StopWatcher stop the file watcher
// the StopWatcher should be call when you no longer need fc object if WatcherStared
func (fc *FileConfig) StopWatcher() {
	fc.lk.Lock()
	defer fc.lk.Unlock()

	if fc.watcherStared == true {
		fc.done <- true
		select {
		case <-fc.done:
		case <-time.After(2 * time.Second):
		}
	}
	return
}

// StartWatcher start a file watcher to notify file changed
// StartWatcher call the handle once the file changed
func (fc *FileConfig) StartWatcher(handler NotifyHandler) error {
	fc.lk.Lock()
	defer fc.lk.Unlock()

	// access instence var directly
	if fc.watcherStared {
		return nil
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	// access instance var directly
	err = watcher.Watch(fc.fileName)
	if err != nil {
		watcher.Close()
		return err
	}

	go func() {
		fc.watcherStared = true
		defer func() {
			fc.watcherStared = false
		}()
		defer watcher.Close()
		for {
			select {
			case ev := <-watcher.Event:
				if handler != nil {
					action := handler(fc, ev)
					switch action {
					case NA_Stop:
						return
					case NA_Restart:
						watcher.RemoveWatch(fc.FileName())
						watcher.Watch(fc.FileName())
					}
				}
			case <-watcher.Error:
				return
			case <-fc.done:
				fc.done <- true
				return
			}
		}
	}()

	return nil
}

// LoadFromFile load content from file and decodes it to object
// save the decoded object to internal storage
// v: decoded object receiveer, must be a pointer to object
// You should call fc function after file changed to refrash the data;
// otherwise you can call ConfValue to get object from internal storage
func (fc *FileConfig) LoadFromFile(v interface{}) error {
	fc.lk.RLock()
	defer fc.lk.RUnlock()

	if v == nil {
		return saveNilError
	}

	if fc.coder == nil {
		return coderNilError
	}

	data, err := ioutil.ReadFile(fc.fileName)
	if err != nil {
		return err
	}

	err = fc.coder.Decode(data, v)
	if err != nil {
		return err
	}
	if v == nil {
		return implError
	}

	fc.base.Set(reflect.ValueOf(v).Elem().Interface())
	return nil
}

// SaveToFile encode v object to byte slice and save encoded object to file
// encoded object save to internal storage simultaneously.
// writes encoded data to a file named by FileName property.
// If the file does not exist, SaveToFile creates it; otherwise truncates it before writing.
func (fc *FileConfig) SaveToFile(v interface{}) error {
	fc.lk.Lock()
	defer fc.lk.Unlock()

	if v == nil {
		return saveNilError
	}

	data, err := fc.coder.Encode(v)
	if err != nil {
		return err
	}
	fc.base.Set(v)

	return ioutil.WriteFile(fc.fileName, data, 0666)
}

// FileName property getter
func (fc *FileConfig) FileName() string {
	fc.lk.RLock()
	defer fc.lk.RUnlock()
	return fc.fileName
}

// SetFileName set config filename
func (fc *FileConfig) SetFileName(name string) {
	fc.lk.Lock()
	defer fc.lk.Unlock()

	if fc.fileName != name {
		fc.fileName = name
	}
}

// Value get config value from internal storage
func (fc *FileConfig) Value() interface{} {
	return fc.base.Get()
}
<commit_msg>Changed coments issues<commit_after>// Copyright 2012 The go-config Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Author Nelson

// config package privide:
// 		atomic storage object
//		file configuration save, load and watcher
//		xml and Json encoder, decoder interface
package config

import (
	"errors"
	"io/ioutil"
	"reflect"
	"sync"
	"time"

	"github.com/howeyc/fsnotify"
)

type NotifyAction int

const (
	NA_Continue = iota
	NA_Restart
	NA_Stop
)

type NotifyHandler func(Sender *FileConfig, event *fsnotify.FileEvent) NotifyAction

var (
	implError     = errors.New("imtemplment of coder is wrong, cant return nil when not error")
	coderNilError = errors.New("config Coder is nil")
	saveNilError  = errors.New("can not load or save nil configuration value")
)

// Coder interface define encode and decode function
// Encode encodes configuration object to byte slice
// Decode decodes byte slice to configuration object
type Coder interface {
	Encode(v interface{}) ([]byte, error)
	Decode(data []byte, v interface{}) error
}

// A FileConfig which is used for both reading and writing configration file.
// base is Config object which provide storage. Writing and reading simultaneously are safety.
// The coder is Coder interface which encode and decode configration object
type FileConfig struct {
	base          Config
	fileName      string
	coder         Coder
	lk            sync.RWMutex
	done          chan bool
	watcherStared bool
}

// NewFileConfig creates FileConfig object
func NewFileConfig(fileName string, coder Coder) *FileConfig {
	fc := new(FileConfig)
	fc.fileName = fileName
	fc.coder = coder
	fc.done = make(chan bool)
	return fc
}

// WatcherStared is a perperty which shows the file watcher is started or not
func (fc *FileConfig) WatcherStared() bool {
	fc.lk.RLock()
	defer fc.lk.RUnlock()
	return fc.watcherStared
}

// StopWatcher stop the file watcher
// the StopWatcher should be call when you no longer need fc object if WatcherStared
func (fc *FileConfig) StopWatcher() {
	fc.lk.Lock()
	defer fc.lk.Unlock()

	if fc.watcherStared == true {
		fc.done <- true
		select {
		case <-fc.done:
		case <-time.After(2 * time.Second):
		}
	}
	return
}

// StartWatcher start a file watcher to notify file changed
// StartWatcher call the handle once the file changed
func (fc *FileConfig) StartWatcher(handler NotifyHandler) error {
	fc.lk.Lock()
	defer fc.lk.Unlock()

	// access instence var directly
	if fc.watcherStared {
		return nil
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	// access instance var directly
	err = watcher.Watch(fc.fileName)
	if err != nil {
		watcher.Close()
		return err
	}

	go func() {
		fc.watcherStared = true
		defer func() {
			fc.watcherStared = false
		}()
		defer watcher.Close()
		for {
			select {
			case ev := <-watcher.Event:
				if handler != nil {
					action := handler(fc, ev)
					switch action {
					case NA_Stop:
						return
					case NA_Restart:
						watcher.RemoveWatch(fc.FileName())
						watcher.Watch(fc.FileName())
					}
				}
			case <-watcher.Error:
				return
			case <-fc.done:
				fc.done <- true
				return
			}
		}
	}()

	return nil
}

// LoadFromFile load content from file and decodes it to object
// save the decoded object to internal storage
// v: decoded object receiveer, must be a pointer to object
// You should call fc function after file changed to refrash the data;
// otherwise you can call Value to get object from internal storage
func (fc *FileConfig) LoadFromFile(v interface{}) error {
	fc.lk.RLock()
	defer fc.lk.RUnlock()

	if v == nil {
		return saveNilError
	}

	if fc.coder == nil {
		return coderNilError
	}

	data, err := ioutil.ReadFile(fc.fileName)
	if err != nil {
		return err
	}

	err = fc.coder.Decode(data, v)
	if err != nil {
		return err
	}
	if v == nil {
		return implError
	}

	fc.base.Set(reflect.ValueOf(v).Elem().Interface())
	return nil
}

// SaveToFile encode v object to byte slice and save encoded object to file
// encoded object save to internal storage simultaneously.
// writes encoded data to a file named by FileName property.
// If the file does not exist, SaveToFile creates it; otherwise truncates it before writing.
func (fc *FileConfig) SaveToFile(v interface{}) error {
	fc.lk.Lock()
	defer fc.lk.Unlock()

	if v == nil {
		return saveNilError
	}

	data, err := fc.coder.Encode(v)
	if err != nil {
		return err
	}
	fc.base.Set(v)

	return ioutil.WriteFile(fc.fileName, data, 0666)
}

// FileName property getter
func (fc *FileConfig) FileName() string {
	fc.lk.RLock()
	defer fc.lk.RUnlock()
	return fc.fileName
}

// SetFileName set config filename
func (fc *FileConfig) SetFileName(name string) {
	fc.lk.Lock()
	defer fc.lk.Unlock()

	if fc.fileName != name {
		fc.fileName = name
	}
}

// Value get config value from internal storage
func (fc *FileConfig) Value() interface{} {
	return fc.base.Get()
}
<|endoftext|>


<commit_before>package log4go

import (
  "fmt"
  "os"
  "path/filepath"
  "sync"
  "time"
)

const (
  RollNone RollFrequency = iota
  RollMinutely // nice for testing
  RollHourly
  RollDaily
  RollWeekly
)

type RollFrequency uint8

type fileAppender struct {
  lock sync.Mutex
  f *os.File
  nextRollTime time.Time
  rollFrequency RollFrequency
  rollsToSave uint
}

var fileAppenderMap = make(map[string]*fileAppender)
var fileAppenderMapLock = sync.Mutex{}

func (a *fileAppender) Append(msg string, level LogLevel, tstamp time.Time) {
  a.lock.Lock()
  defer a.lock.Unlock()

  if a.shouldRoll(tstamp) {
    a.doRoll()
  }

  // sanity check here, in case file couldn't be reopened after rolling
  if a.f != nil {
    a.f.Write([]byte(msg))
  }
}

func (a *fileAppender) shouldRoll(tstamp time.Time) bool {
  if a.rollFrequency == RollNone {
    return false
  } else {
    return !tstamp.Before(a.nextRollTime)
  }
}

func (a *fileAppender) doRoll() {
  absoluteFilename := a.f.Name()
  dir, filename := filepath.Split(absoluteFilename)
  a.f.Close()

  lastTime := calculatePreviousRollTime(a.nextRollTime, a.rollFrequency)
  a.nextRollTime = calculateNextRollTime(a.nextRollTime, a.rollFrequency)

  var timeFormat string
  if a.rollFrequency == RollMinutely || a.rollFrequency == RollHourly {
    timeFormat = "2006-01-02T15-04"
  } else {
    timeFormat = "2006-01-02"
  }

  archiveFilename := filepath.Join(dir, fmt.Sprintf("%s.%s", filename, lastTime.Format(timeFormat)))
  os.Rename(absoluteFilename, archiveFilename)
  a.f, _ = os.Create(absoluteFilename)
}

func calculateNextRollTime(t time.Time, freq RollFrequency) time.Time {
  if freq == RollMinutely {
    t = t.Truncate(time.Minute)
    return t.Add(time.Minute)
  } else if freq == RollHourly {
    t = t.Truncate(time.Hour)
    return t.Add(time.Hour)
  } else {
    t = t.Truncate(time.Hour)
    day := t.Day()
    for ; t.Day() == day; t = t.Add(time.Hour) {}
    if freq == RollDaily {
      return t
    } else {
      for ; t.Day() != day; t = t.Add(time.Hour) {}
      return t
    }
  }
}

func calculatePreviousRollTime(t time.Time, freq RollFrequency) time.Time {
  if freq == RollMinutely {
    return t.Add(-time.Minute)
  } else if freq == RollHourly {
    return t.Add(-time.Hour)
  } else if freq == RollDaily {
    t = t.Add(-time.Hour*12)
    for day := t.Day(); t.Day() == day; t = t.Add(-time.Hour) {}
    return t.Add(time.Hour)
  } else {
    t = t.Add(-time.Hour*156)
    for day := t.Day(); t.Day() == day; t = t.Add(-time.Hour) {}
    return t.Add(time.Hour)
  }
}

func durationForRollFrequency(freq RollFrequency) time.Duration {
  var d time.Duration
  switch freq {
  case RollMinutely:
    d, _ = time.ParseDuration("1m")
  case RollHourly:
    d, _ = time.ParseDuration("1h")
  case RollDaily:
    d, _ = time.ParseDuration("24h")
  case RollWeekly:
    d, _ = time.ParseDuration("168h")
  }
  return d
}

func NewFileLogger(dir string, filename string, level LogLevel, timePrefix string) (_ Log4Go, err error) {
  expandedDir, err := filepath.Abs(filepath.Dir(dir))
  if err != nil {
    return nil, err
  }

  fullFilename := filepath.Join(expandedDir, filename)

  fileAppenderMapLock.Lock()
  defer fileAppenderMapLock.Unlock()

  var appender *fileAppender = fileAppenderMap[fullFilename]
  if appender == nil {
    logfile, err := os.Create(fullFilename)
    if err != nil {
      return nil, err
    }
    appender = &fileAppender{sync.Mutex{}, logfile, time.Now(), RollNone, 0}
    fileAppenderMap[fullFilename] = appender
  }

  result := stdLogger{
    appender,
    level,
    timePrefix,
  }
  return Log4Go(&result), nil
}

func NewRollingFileLogger(dir string, filename string, level LogLevel, timePrefix string, freq RollFrequency, save uint) (_ Log4Go, err error) {
  expandedDir, err := filepath.Abs(filepath.Dir(dir))
  if err != nil {
    return nil, err
  }

  fullFilename := filepath.Join(expandedDir, filename)

  fileAppenderMapLock.Lock()
  defer fileAppenderMapLock.Unlock()

  var appender *fileAppender = fileAppenderMap[fullFilename]
  if appender == nil {
    logfile, err := os.Create(fullFilename)
    if err != nil {
      return nil, err
    }
    appender = &fileAppender{sync.Mutex{}, logfile, calculateNextRollTime(time.Now(), freq), freq, save}
    fileAppenderMap[fullFilename] = appender
  }

  result := stdLogger{
    appender,
    level,
    timePrefix,
  }
  return Log4Go(&result), nil
}
<commit_msg>clarified variable name<commit_after>package log4go

import (
  "fmt"
  "os"
  "path/filepath"
  "sync"
  "time"
)

const (
  RollNone RollFrequency = iota
  RollMinutely // nice for testing
  RollHourly
  RollDaily
  RollWeekly
)

type RollFrequency uint8

type fileAppender struct {
  lock sync.Mutex
  f *os.File
  nextRollTime time.Time
  rollFrequency RollFrequency
  rollsToSave uint
}

var fileAppenderMap = make(map[string]*fileAppender)
var fileAppenderMapLock = sync.Mutex{}

func (a *fileAppender) Append(msg string, level LogLevel, tstamp time.Time) {
  a.lock.Lock()
  defer a.lock.Unlock()

  if a.shouldRoll(tstamp) {
    a.doRoll()
  }

  // sanity check here, in case file couldn't be reopened after rolling
  if a.f != nil {
    a.f.Write([]byte(msg))
  }
}

func (a *fileAppender) shouldRoll(tstamp time.Time) bool {
  if a.rollFrequency == RollNone {
    return false
  } else {
    return !tstamp.Before(a.nextRollTime)
  }
}

func (a *fileAppender) doRoll() {
  absoluteFilename := a.f.Name()
  dir, filename := filepath.Split(absoluteFilename)
  a.f.Close()

  lastTime := calculatePreviousRollTime(a.nextRollTime, a.rollFrequency)
  a.nextRollTime = calculateNextRollTime(a.nextRollTime, a.rollFrequency)

  var timeFormat string
  if a.rollFrequency == RollMinutely || a.rollFrequency == RollHourly {
    timeFormat = "2006-01-02T15-04"
  } else {
    timeFormat = "2006-01-02"
  }

  archiveFilename := filepath.Join(dir, fmt.Sprintf("%s.%s", filename, lastTime.Format(timeFormat)))
  os.Rename(absoluteFilename, archiveFilename)
  a.f, _ = os.Create(absoluteFilename)
}

func calculateNextRollTime(t time.Time, freq RollFrequency) time.Time {
  if freq == RollMinutely {
    t = t.Truncate(time.Minute)
    return t.Add(time.Minute)
  } else if freq == RollHourly {
    t = t.Truncate(time.Hour)
    return t.Add(time.Hour)
  } else {
    t = t.Truncate(time.Hour)
    day := t.Day()
    for ; t.Day() == day; t = t.Add(time.Hour) {}
    if freq == RollDaily {
      return t
    } else {
      for ; t.Day() != day; t = t.Add(time.Hour) {}
      return t
    }
  }
}

func calculatePreviousRollTime(t time.Time, freq RollFrequency) time.Time {
  if freq == RollMinutely {
    return t.Add(-time.Minute)
  } else if freq == RollHourly {
    return t.Add(-time.Hour)
  } else if freq == RollDaily {
    t = t.Add(-time.Hour*12)
    for day := t.Day(); t.Day() == day; t = t.Add(-time.Hour) {}
    return t.Add(time.Hour)
  } else {
    t = t.Add(-time.Hour*156)
    for day := t.Day(); t.Day() == day; t = t.Add(-time.Hour) {}
    return t.Add(time.Hour)
  }
}

func durationForRollFrequency(freq RollFrequency) time.Duration {
  var d time.Duration
  switch freq {
  case RollMinutely:
    d, _ = time.ParseDuration("1m")
  case RollHourly:
    d, _ = time.ParseDuration("1h")
  case RollDaily:
    d, _ = time.ParseDuration("24h")
  case RollWeekly:
    d, _ = time.ParseDuration("168h")
  }
  return d
}

func NewFileLogger(dir string, filename string, level LogLevel, timePrefix string) (_ Log4Go, err error) {
  expandedDir, err := filepath.Abs(filepath.Dir(dir))
  if err != nil {
    return nil, err
  }

  fullFilename := filepath.Join(expandedDir, filename)

  fileAppenderMapLock.Lock()
  defer fileAppenderMapLock.Unlock()

  var appender *fileAppender = fileAppenderMap[fullFilename]
  if appender == nil {
    logfile, err := os.Create(fullFilename)
    if err != nil {
      return nil, err
    }
    appender = &fileAppender{sync.Mutex{}, logfile, time.Now(), RollNone, 0}
    fileAppenderMap[fullFilename] = appender
  }

  result := stdLogger{
    appender,
    level,
    timePrefix,
  }
  return Log4Go(&result), nil
}

func NewRollingFileLogger(dir string, filename string, level LogLevel, timePrefix string, freq RollFrequency, oldLogsToSave uint) (_ Log4Go, err error) {
  expandedDir, err := filepath.Abs(filepath.Dir(dir))
  if err != nil {
    return nil, err
  }

  fullFilename := filepath.Join(expandedDir, filename)

  fileAppenderMapLock.Lock()
  defer fileAppenderMapLock.Unlock()

  var appender *fileAppender = fileAppenderMap[fullFilename]
  if appender == nil {
    logfile, err := os.Create(fullFilename)
    if err != nil {
      return nil, err
    }
    appender = &fileAppender{sync.Mutex{}, logfile, calculateNextRollTime(time.Now(), freq), freq, oldLogsToSave}
    fileAppenderMap[fullFilename] = appender
  }

  result := stdLogger{
    appender,
    level,
    timePrefix,
  }
  return Log4Go(&result), nil
}
<|endoftext|>


<commit_before>package main

import "testing"

var testData = `
stackname: exampleStack
region: us-east-1
foobars:
 - foo: 1
   bar:
    - one
    - two
    - three

 - foo: 2
   bar:
    - one1
    - two2
    - three3
`

func testYamlParser(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}
}

func testFileReader(t *testing.T) {}

func testTemplateParser(t *testing.T) {}
<commit_msg>cleaning up<commit_after><|endoftext|>


<commit_before>// Package httpgzip is a simple wrapper around http.FileServer that looks for
// a gzip compressed version of a file and serves that if the client requested
// gzip content
package httpgzip

import (
	"mime"
	"net/http"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

const (
	acceptEncoding   = "Accept-Encoding"
	contentEncoding  = "Content-Encoding"
	contentType      = "Content-Type"
	contentLength    = "Content-Length"
	anyEncoding      = "*"
	gzipEncoding     = "gzip"
	gzExt            = ".gz"
	brEncoding       = "br"
	brExt            = "br"
	identityEncoding = "identity"
	acceptSplit      = ","
	partSplit        = ";"
	weightPrefix     = "q="
	indexPage        = "index.html"
)

type encodings []encoding

func (e encodings) Len() int {
	return len(e)
}

func (e encodings) Less(i, j int) bool {
	if e[i].weight == e[j].weight {
		if e[j].encoding == brEncoding { // prefer brotli
			return true
		}
		if e[i].encoding == brEncoding {
			return false
		}
		if e[j].encoding == gzipEncoding {
			return true
		}
		return false
	}
	return e[j].weight < e[i].weight
}

func (e encodings) Swap(i, j int) {
	e[i], e[j] = e[j], e[i]
}

type encoding struct {
	encoding string
	weight   float32
}

type fileServer struct {
	root http.FileSystem
	h    http.Handler
}

// FileServer creates a wrapper around http.FileServer using the given
// http.FileSystem
func FileServer(root http.FileSystem) http.Handler {
	return fileServer{
		root,
		http.FileServer(root),
	}
}

func (f fileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	acceptHeader := r.Header.Get(acceptEncoding)
	accepts := make(encodings, 0, strings.Count(acceptHeader, acceptSplit)+1)
Loop:
	for _, accept := range strings.Split(acceptHeader, acceptSplit) {
		parts := strings.Split(strings.TrimSpace(accept), partSplit)
		var (
			weight float64 = 1
			err    error
		)
		for _, part := range parts[1:] {
			if strings.HasPrefix(strings.TrimSpace(part), weightPrefix) {
				weight, err = strconv.ParseFloat(part[len(weightPrefix):], 32)
				if err != nil || weight < 0 || weight > 1 { // return an malformed header response?
					continue Loop
				}
				break
			}
		}
		accepts = append(accepts, encoding{
			encoding: strings.ToLower(strings.TrimSpace(parts[0])),
			weight:   float32(weight),
		})
	}
	sort.Sort(accepts)
	allowIdentity := true
	for _, accept := range accepts {
		switch accept.encoding {
		case brEncoding:
			if f.serveFile(w, r, brExt, brEncoding) {
				return
			}
		case gzipEncoding:
			if f.serveFile(w, r, gzExt, gzipEncoding) {
				return
			}
		case identityEncoding:
			allowIdentity = accept.weight != 0
			break
		case anyEncoding:
			allowIdentity = accept.weight != 0
		}
	}
	if allowIdentity {
		f.h.ServeHTTP(w, r)
	} else {
		w.WriteHeader(http.StatusNotAcceptable)
	}
}

func (f fileServer) serveFile(w http.ResponseWriter, r *http.Request, ext, encoding string) bool {
	p := path.Clean(r.URL.Path)
	m := p
	nf, err := f.root.Open(p + ext)
	if strings.HasSuffix(p, "/") {
		m += indexPage
		if err != nil {
			nf, err = f.root.Open(p + ext)
			p += indexPage
		}
	}
	if err == nil {
		if ctype := mime.TypeByExtension(filepath.Ext(m)); ctype != "" {
			s, err := nf.Stat()
			if err == nil {
				w.Header().Set(contentType, ctype)
				w.Header().Set(contentLength, strconv.FormatInt(s.Size(), 10))
				w.Header().Set(contentEncoding, encoding)
				r.URL.Path = p + ext
				f.h.ServeHTTP(w, r)
				return true
			}
		}
	}
	return false
}
<commit_msg>qvalue can be up to (but not including) 2 and a maximum of 3 d.p.<commit_after>// Package httpgzip is a simple wrapper around http.FileServer that looks for
// a gzip compressed version of a file and serves that if the client requested
// gzip content
package httpgzip

import (
	"mime"
	"net/http"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

const (
	acceptEncoding   = "Accept-Encoding"
	contentEncoding  = "Content-Encoding"
	contentType      = "Content-Type"
	contentLength    = "Content-Length"
	anyEncoding      = "*"
	gzipEncoding     = "gzip"
	gzExt            = ".gz"
	brEncoding       = "br"
	brExt            = "br"
	identityEncoding = "identity"
	acceptSplit      = ","
	partSplit        = ";"
	weightPrefix     = "q="
	indexPage        = "index.html"
)

type encodings []encoding

func (e encodings) Len() int {
	return len(e)
}

func (e encodings) Less(i, j int) bool {
	if e[i].weight == e[j].weight {
		if e[j].encoding == brEncoding { // prefer brotli
			return true
		}
		if e[i].encoding == brEncoding {
			return false
		}
		if e[j].encoding == gzipEncoding {
			return true
		}
		return false
	}
	return e[j].weight < e[i].weight
}

func (e encodings) Swap(i, j int) {
	e[i], e[j] = e[j], e[i]
}

type encoding struct {
	encoding string
	weight   uint16
}

type fileServer struct {
	root http.FileSystem
	h    http.Handler
}

// FileServer creates a wrapper around http.FileServer using the given
// http.FileSystem
func FileServer(root http.FileSystem) http.Handler {
	return fileServer{
		root,
		http.FileServer(root),
	}
}

func (f fileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	acceptHeader := r.Header.Get(acceptEncoding)
	accepts := make(encodings, 0, strings.Count(acceptHeader, acceptSplit)+1)
Loop:
	for _, accept := range strings.Split(acceptHeader, acceptSplit) {
		parts := strings.Split(strings.TrimSpace(accept), partSplit)
		var (
			weight float64 = 1
			err    error
		)
		for _, part := range parts[1:] {
			if strings.HasPrefix(strings.TrimSpace(part), weightPrefix) {
				weight, err = strconv.ParseFloat(part[len(weightPrefix):], 32)
				if err != nil || weight < 0 || weight >= 2 { // return an malformed header response?
					continue Loop
				}
				break
			}
		}
		accepts = append(accepts, encoding{
			encoding: strings.ToLower(strings.TrimSpace(parts[0])),
			weight:   uint16(weight * 1000),
		})
	}
	sort.Sort(accepts)
	allowIdentity := true
	for _, accept := range accepts {
		switch accept.encoding {
		case brEncoding:
			if f.serveFile(w, r, brExt, brEncoding) {
				return
			}
		case gzipEncoding:
			if f.serveFile(w, r, gzExt, gzipEncoding) {
				return
			}
		case identityEncoding:
			allowIdentity = accept.weight != 0
			break
		case anyEncoding:
			allowIdentity = accept.weight != 0
		}
	}
	if allowIdentity {
		f.h.ServeHTTP(w, r)
	} else {
		w.WriteHeader(http.StatusNotAcceptable)
	}
}

func (f fileServer) serveFile(w http.ResponseWriter, r *http.Request, ext, encoding string) bool {
	p := path.Clean(r.URL.Path)
	m := p
	nf, err := f.root.Open(p + ext)
	if strings.HasSuffix(p, "/") {
		m += indexPage
		if err != nil {
			nf, err = f.root.Open(p + ext)
			p += indexPage
		}
	}
	if err == nil {
		if ctype := mime.TypeByExtension(filepath.Ext(m)); ctype != "" {
			s, err := nf.Stat()
			if err == nil {
				w.Header().Set(contentType, ctype)
				w.Header().Set(contentLength, strconv.FormatInt(s.Size(), 10))
				w.Header().Set(contentEncoding, encoding)
				r.URL.Path = p + ext
				f.h.ServeHTTP(w, r)
				return true
			}
		}
	}
	return false
}
<|endoftext|>


<commit_before>// Package httpgzip is a simple wrapper around http.FileServer that looks for
// a gzip compressed version of a file and serves that if the client requested
// gzip content
package httpgzip

import (
	"mime"
	"net/http"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

const (
	acceptEncoding   = "Accept-Encoding"
	contentEncoding  = "Content-Encoding"
	contentType      = "Content-Type"
	contentLength    = "Content-Length"
	anyEncoding      = "*"
	gzipEncoding     = "gzip"
	gzExt            = ".gz"
	identityEncoding = "identity"
	acceptSplit      = ","
	partSplit        = ";"
	weightPrefix     = "q="
	indexPage        = "index.html"
)

type encodings []encoding

func (e encodings) Len() int {
	return len(e)
}

func (e encodings) Less(i, j int) bool {
	return e[i].weight < e[j].weight
}

func (e encodings) Swap(i, j int) {
	e[i], e[j] = e[j], e[i]
}

type encoding struct {
	encoding string
	weight   float32
}

type fileServer struct {
	root http.FileSystem
	h    http.Handler
}

// FileServer creates a wrapper around http.FileServer using the given
// http.FileSystem
func FileServer(root http.FileSystem) http.Handler {
	return fileServer{
		root,
		http.FileServer(root),
	}
}

func (f fileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	acceptHeader := r.Header.Get(acceptEncoding)
	accepts := make(encodings, 0, strings.Count(acceptHeader, acceptSplit)+1)
Loop:
	for _, accept := range strings.Split(acceptHeader, acceptSplit) {
		parts := strings.Split(strings.TrimSpace(accept), partSplit)
		var (
			weight float64 = 1
			err    error
		)
		for _, part := range parts[1:] {
			if strings.HasPrefix(strings.TrimSpace(part), weightPrefix) {
				weight, err = strconv.ParseFloat(part[len(weightPrefix):], 32)
				if err != nil || weight < 0 || weight > 1 { // return an malformed header response?
					continue Loop
				}
				break
			}
		}
		accepts = append(accepts, encoding{
			encoding: strings.ToLower(strings.TrimSpace(parts[0])),
			weight:   float32(weight),
		})
	}
	sort.Sort(accepts)
	allowIdentity := true
	for _, accept := range accepts {
		switch accept.encoding {
		case gzipEncoding:
			if f.serveFile(w, r, gzExt, gzipEncoding) {
				return
			}
		case identityEncoding:
			allowIdentity = accept.weight != 0
			break
		case anyEncoding:
			allowIdentity = accept.weight != 0
		}
	}
	if allowIdentity {
		f.h.ServeHTTP(w, r)
	} else {
		w.WriteHeader(http.StatusNotAcceptable)
	}
}

func (f fileServer) serveFile(w http.ResponseWriter, r *http.Request, ext, encoding string) bool {
	p := path.Clean(r.URL.Path)
	m := p
	nf, err := f.root.Open(p + ext)
	if strings.HasSuffix(p, "/") {
		m += indexPage
		if err != nil {
			nf, err = f.root.Open(p + ext)
			p += indexPage
		}
	}
	if err == nil {
		if ctype := mime.TypeByExtension(filepath.Ext(m)); ctype != "" {
			s, err := nf.Stat()
			if err == nil {
				w.Header().Set(contentType, ctype)
				w.Header().Set(contentLength, strconv.FormatInt(s.Size(), 10))
				w.Header().Set(contentEncoding, encoding)
				r.URL.Path = p + ext
				f.h.ServeHTTP(w, r)
				return true
			}
		}
	}
	return false
}
<commit_msg>need to reverse the sort order so largest weight is first<commit_after>// Package httpgzip is a simple wrapper around http.FileServer that looks for
// a gzip compressed version of a file and serves that if the client requested
// gzip content
package httpgzip

import (
	"mime"
	"net/http"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

const (
	acceptEncoding   = "Accept-Encoding"
	contentEncoding  = "Content-Encoding"
	contentType      = "Content-Type"
	contentLength    = "Content-Length"
	anyEncoding      = "*"
	gzipEncoding     = "gzip"
	gzExt            = ".gz"
	identityEncoding = "identity"
	acceptSplit      = ","
	partSplit        = ";"
	weightPrefix     = "q="
	indexPage        = "index.html"
)

type encodings []encoding

func (e encodings) Len() int {
	return len(e)
}

func (e encodings) Less(i, j int) bool {
	return e[j].weight < e[i].weight
}

func (e encodings) Swap(i, j int) {
	e[i], e[j] = e[j], e[i]
}

type encoding struct {
	encoding string
	weight   float32
}

type fileServer struct {
	root http.FileSystem
	h    http.Handler
}

// FileServer creates a wrapper around http.FileServer using the given
// http.FileSystem
func FileServer(root http.FileSystem) http.Handler {
	return fileServer{
		root,
		http.FileServer(root),
	}
}

func (f fileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	acceptHeader := r.Header.Get(acceptEncoding)
	accepts := make(encodings, 0, strings.Count(acceptHeader, acceptSplit)+1)
Loop:
	for _, accept := range strings.Split(acceptHeader, acceptSplit) {
		parts := strings.Split(strings.TrimSpace(accept), partSplit)
		var (
			weight float64 = 1
			err    error
		)
		for _, part := range parts[1:] {
			if strings.HasPrefix(strings.TrimSpace(part), weightPrefix) {
				weight, err = strconv.ParseFloat(part[len(weightPrefix):], 32)
				if err != nil || weight < 0 || weight > 1 { // return an malformed header response?
					continue Loop
				}
				break
			}
		}
		accepts = append(accepts, encoding{
			encoding: strings.ToLower(strings.TrimSpace(parts[0])),
			weight:   float32(weight),
		})
	}
	sort.Sort(accepts)
	allowIdentity := true
	for _, accept := range accepts {
		switch accept.encoding {
		case gzipEncoding:
			if f.serveFile(w, r, gzExt, gzipEncoding) {
				return
			}
		case identityEncoding:
			allowIdentity = accept.weight != 0
			break
		case anyEncoding:
			allowIdentity = accept.weight != 0
		}
	}
	if allowIdentity {
		f.h.ServeHTTP(w, r)
	} else {
		w.WriteHeader(http.StatusNotAcceptable)
	}
}

func (f fileServer) serveFile(w http.ResponseWriter, r *http.Request, ext, encoding string) bool {
	p := path.Clean(r.URL.Path)
	m := p
	nf, err := f.root.Open(p + ext)
	if strings.HasSuffix(p, "/") {
		m += indexPage
		if err != nil {
			nf, err = f.root.Open(p + ext)
			p += indexPage
		}
	}
	if err == nil {
		if ctype := mime.TypeByExtension(filepath.Ext(m)); ctype != "" {
			s, err := nf.Stat()
			if err == nil {
				w.Header().Set(contentType, ctype)
				w.Header().Set(contentLength, strconv.FormatInt(s.Size(), 10))
				w.Header().Set(contentEncoding, encoding)
				r.URL.Path = p + ext
				f.h.ServeHTTP(w, r)
				return true
			}
		}
	}
	return false
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"os"
	"path/filepath"
	"testing"
)

var _ = fmt.Println
var _ = os.Stderr

func checkGuess(t *testing.T, wd, input, expected string) {
	testWd = wd
	inputPath = input
	guessProjectPath()

	if projectPath != expected {
		t.Errorf("Unexpected Project Path. \n Got: %q\nExpected: %q\n", projectPath, expected)
	}

	reset()
}

func reset() {
	testWd = ""
	inputPath = ""
	projectPath = ""
}

func TestProjectPath(t *testing.T) {
	wd, _ := os.Getwd()

	checkGuess(t, "", "github.com/spf13/hugo", filepath.Join(wd, "github.com", "spf13", "hugo"))
	checkGuess(t, "", "spf13/hugo", filepath.Join(wd, "spf13", "hugo"))
	checkGuess(t, "", "/bar/foo", filepath.Join(wd, "bar", "foo"))
	checkGuess(t, "/bar/foo", "baz", filepath.Join("/", "bar", "foo", "baz"))
	checkGuess(t, "/bar/foo/cmd", "", filepath.Join("/", "bar", "foo"))
	checkGuess(t, "/bar/foo/command", "", filepath.Join("/", "bar", "foo"))
	checkGuess(t, "/bar/foo/commands", "", filepath.Join("/", "bar", "foo"))
	checkGuess(t, "github.com/spf13/hugo/../hugo", "", filepath.Join("github.com", "spf13", "hugo"))
}
<commit_msg>Fixed tests for how they are run<commit_after>package cmd

import (
	"fmt"
	"os"
	"path/filepath"
	"testing"
)

var _ = fmt.Println
var _ = os.Stderr

func checkGuess(t *testing.T, wd, input, expected string) {
	testWd = wd
	inputPath = input
	guessProjectPath()

	if projectPath != expected {
		t.Errorf("Unexpected Project Path. \n Got: %q\nExpected: %q\n", projectPath, expected)
	}

	reset()
}

func reset() {
	testWd = ""
	inputPath = ""
	projectPath = ""
}

func TestProjectPath(t *testing.T) {
	checkGuess(t, "", filepath.Join("github.com", "spf13", "hugo"), filepath.Join(getSrcPath(), "github.com", "spf13", "hugo"))
	checkGuess(t, "", filepath.Join("spf13", "hugo"), filepath.Join(getSrcPath(), "github.com", "spf13", "hugo"))
	checkGuess(t, "", filepath.Join("/", "bar", "foo"), filepath.Join(getSrcPath(), "bar", "foo"))
	checkGuess(t, "/bar/foo", "baz", filepath.Join("/", "bar", "foo", "baz"))
	checkGuess(t, "/bar/foo/cmd", "", filepath.Join("/", "bar", "foo"))
	checkGuess(t, "/bar/foo/command", "", filepath.Join("/", "bar", "foo"))
	checkGuess(t, "/bar/foo/commands", "", filepath.Join("/", "bar", "foo"))
	checkGuess(t, "github.com/spf13/hugo/../hugo", "", filepath.Join("github.com", "spf13", "hugo"))
}
<|endoftext|>


<commit_before>// Copyright 2018 Istio 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.

//go:generate $GOPATH/src/istio.io/istio/bin/go-bindata.sh --nocompress --nometadata --pkg ctrlz -o assets.gen.go assets/...

// Package ctrlz implements Istio's introspection facility. When components
// integrate with ControlZ, they automatically gain an IP port which allows operators
// to visualize and control a number of aspects of each process, including controlling
// logging scopes, viewing command-line options, memory use, etc. Additionally,
// the port implements a REST API allowing access and control over the same state.
//
// ControlZ is designed around the idea of "topics". A topic corresponds to the different
// parts of the UI. There are a set of built-in topics representing the core introspection
// functionality, and each component that uses ControlZ can add new topics specialized
// for their purpose.
package ctrlz

import (
	"html/template"
	"net"
	"net/http"
	"os"

	"github.com/gorilla/mux"

	"sync"

	"fmt"
	"time"

	"istio.io/istio/pkg/ctrlz/fw"
	"istio.io/istio/pkg/ctrlz/topics"
	"istio.io/istio/pkg/log"
)

var coreTopics = []fw.Topic{
	topics.ScopeTopic(),
	topics.MemTopic(),
	topics.EnvTopic(),
	topics.ProcTopic(),
	topics.ArgsTopic(),
	topics.VersionTopic(),
	topics.MetricsTopic(),
}

var allTopics []fw.Topic
var topicMutex sync.Mutex
var listeningTestProbe func()

// Server represents a running ControlZ instance.
type Server struct {
	shutdown   sync.WaitGroup
	httpServer http.Server
	listener   net.Listener
}

func augmentLayout(layout *template.Template, page string) *template.Template {
	return template.Must(layout.Parse(string(MustAsset(page))))
}

func registerTopic(router *mux.Router, layout *template.Template, t fw.Topic) {
	htmlRouter := router.NewRoute().PathPrefix("/" + t.Prefix() + "z").Subrouter()
	jsonRouter := router.NewRoute().PathPrefix("/" + t.Prefix() + "j").Subrouter()

	tmpl := template.Must(template.Must(layout.Clone()).Parse("{{ define \"title\" }}" + t.Title() + "{{ end }}"))
	t.Activate(fw.NewContext(htmlRouter, jsonRouter, tmpl))
}

// getLocalIP returns a non loopback local IP of the host
func getLocalIP() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return ""
	}

	for _, address := range addrs {
		// check the address type and if it is not a loopback then return it
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String()
			}
		}
	}
	return ""
}

type topic struct {
	Name string
	URL  string
}

func getTopics() []topic {
	var topics []topic

	topicMutex.Lock()
	defer topicMutex.Unlock()

	for _, t := range allTopics {
		topics = append(topics, topic{Name: t.Title(), URL: "/" + t.Prefix() + "z/"})
	}

	return topics
}

// RegisterTopic registers a new Control-Z topic for the current process.
func RegisterTopic(t fw.Topic) {
	topicMutex.Lock()
	defer topicMutex.Unlock()

	allTopics = append(allTopics, t)
}

// Run starts up the ControlZ listeners.
//
// ControlZ uses the set of standard core topics, the
// supplied custom topics, as well as any topics registered
// via the RegisterTopic function.
func Run(o *Options, customTopics []fw.Topic) (*Server, error) {
	if o.Port == 0 {
		// disabled
		s := &Server{}
		return s, nil
	}

	topicMutex.Lock()

	for _, t := range coreTopics {
		allTopics = append(allTopics, t)
	}

	for _, t := range customTopics {
		allTopics = append(allTopics, t)
	}

	topicMutex.Unlock()

	exec, _ := os.Executable()
	instance := exec + " - " + getLocalIP()

	funcs := template.FuncMap{
		"getTopics": getTopics,
	}

	baseLayout := template.Must(template.New("base").Parse(string(MustAsset("assets/templates/layouts/base.html"))))
	baseLayout = baseLayout.Funcs(funcs)
	baseLayout = template.Must(baseLayout.Parse("{{ define \"instance\" }}" + instance + "{{ end }}"))
	_ = augmentLayout(baseLayout, "assets/templates/modules/header.html")
	_ = augmentLayout(baseLayout, "assets/templates/modules/sidebar.html")
	_ = augmentLayout(baseLayout, "assets/templates/modules/last-refresh.html")
	mainLayout := augmentLayout(template.Must(baseLayout.Clone()), "assets/templates/layouts/main.html")

	router := mux.NewRouter()
	for _, t := range allTopics {
		registerTopic(router, mainLayout, t)
	}

	registerHome(router, mainLayout)

	addr := o.Address
	if addr == "*" {
		addr = ""
	}

	s := &Server{
		httpServer: http.Server{
			ReadTimeout:    10 * time.Second,
			WriteTimeout:   10 * time.Second,
			MaxHeaderBytes: 1 << 20,
			Addr:           fmt.Sprintf("%s:%d", addr, o.Port),
			Handler:        router,
		},
	}

	var err error
	if s.listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", addr, o.Port)); err != nil {
		log.Errorf("Unable to start ControlZ: %v", err)
		return nil, err
	}

	s.shutdown.Add(1)
	go s.listen()

	return s, nil
}

func (s *Server) listen() {
	log.Infof("ControlZ available at %s", s.listener.Addr().String())

	if listeningTestProbe != nil {
		listeningTestProbe()
	}

	_ = s.httpServer.Serve(s.listener)
	log.Infof("ControlZ terminated")
	s.shutdown.Done()
}

// Close terminates ControlZ.
//
// Close is not normally used by programs that expose ControlZ, it is primarily intended to be
// used by tests.
func (s *Server) Close() {
	if s.listener != nil {
		_ = s.listener.Close()
		s.shutdown.Wait()
	}
}
<commit_msg>Enable more linters. (#12751)<commit_after>// Copyright 2018 Istio 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.

//go:generate $GOPATH/src/istio.io/istio/bin/go-bindata.sh --nocompress --nometadata --pkg ctrlz -o assets.gen.go assets/...

// Package ctrlz implements Istio's introspection facility. When components
// integrate with ControlZ, they automatically gain an IP port which allows operators
// to visualize and control a number of aspects of each process, including controlling
// logging scopes, viewing command-line options, memory use, etc. Additionally,
// the port implements a REST API allowing access and control over the same state.
//
// ControlZ is designed around the idea of "topics". A topic corresponds to the different
// parts of the UI. There are a set of built-in topics representing the core introspection
// functionality, and each component that uses ControlZ can add new topics specialized
// for their purpose.
package ctrlz

import (
	"html/template"
	"net"
	"net/http"
	"os"

	"github.com/gorilla/mux"

	"sync"

	"fmt"
	"time"

	"istio.io/istio/pkg/ctrlz/fw"
	"istio.io/istio/pkg/ctrlz/topics"
	"istio.io/istio/pkg/log"
)

var coreTopics = []fw.Topic{
	topics.ScopeTopic(),
	topics.MemTopic(),
	topics.EnvTopic(),
	topics.ProcTopic(),
	topics.ArgsTopic(),
	topics.VersionTopic(),
	topics.MetricsTopic(),
}

var allTopics []fw.Topic
var topicMutex sync.Mutex
var listeningTestProbe func()

// Server represents a running ControlZ instance.
type Server struct {
	shutdown   sync.WaitGroup
	httpServer http.Server
	listener   net.Listener
}

func augmentLayout(layout *template.Template, page string) *template.Template {
	return template.Must(layout.Parse(string(MustAsset(page))))
}

func registerTopic(router *mux.Router, layout *template.Template, t fw.Topic) {
	htmlRouter := router.NewRoute().PathPrefix("/" + t.Prefix() + "z").Subrouter()
	jsonRouter := router.NewRoute().PathPrefix("/" + t.Prefix() + "j").Subrouter()

	tmpl := template.Must(template.Must(layout.Clone()).Parse("{{ define \"title\" }}" + t.Title() + "{{ end }}"))
	t.Activate(fw.NewContext(htmlRouter, jsonRouter, tmpl))
}

// getLocalIP returns a non loopback local IP of the host
func getLocalIP() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return ""
	}

	for _, address := range addrs {
		// check the address type and if it is not a loopback then return it
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String()
			}
		}
	}
	return ""
}

type topic struct {
	Name string
	URL  string
}

func getTopics() []topic {
	var topics []topic

	topicMutex.Lock()
	defer topicMutex.Unlock()

	for _, t := range allTopics {
		topics = append(topics, topic{Name: t.Title(), URL: "/" + t.Prefix() + "z/"})
	}

	return topics
}

// RegisterTopic registers a new Control-Z topic for the current process.
func RegisterTopic(t fw.Topic) {
	topicMutex.Lock()
	defer topicMutex.Unlock()

	allTopics = append(allTopics, t)
}

// Run starts up the ControlZ listeners.
//
// ControlZ uses the set of standard core topics, the
// supplied custom topics, as well as any topics registered
// via the RegisterTopic function.
func Run(o *Options, customTopics []fw.Topic) (*Server, error) {
	if o.Port == 0 {
		// disabled
		s := &Server{}
		return s, nil
	}

	topicMutex.Lock()
	allTopics = append(allTopics, coreTopics...)
	allTopics = append(allTopics, customTopics...)
	topicMutex.Unlock()

	exec, _ := os.Executable()
	instance := exec + " - " + getLocalIP()

	funcs := template.FuncMap{
		"getTopics": getTopics,
	}

	baseLayout := template.Must(template.New("base").Parse(string(MustAsset("assets/templates/layouts/base.html"))))
	baseLayout = baseLayout.Funcs(funcs)
	baseLayout = template.Must(baseLayout.Parse("{{ define \"instance\" }}" + instance + "{{ end }}"))
	_ = augmentLayout(baseLayout, "assets/templates/modules/header.html")
	_ = augmentLayout(baseLayout, "assets/templates/modules/sidebar.html")
	_ = augmentLayout(baseLayout, "assets/templates/modules/last-refresh.html")
	mainLayout := augmentLayout(template.Must(baseLayout.Clone()), "assets/templates/layouts/main.html")

	router := mux.NewRouter()
	for _, t := range allTopics {
		registerTopic(router, mainLayout, t)
	}

	registerHome(router, mainLayout)

	addr := o.Address
	if addr == "*" {
		addr = ""
	}

	s := &Server{
		httpServer: http.Server{
			ReadTimeout:    10 * time.Second,
			WriteTimeout:   10 * time.Second,
			MaxHeaderBytes: 1 << 20,
			Addr:           fmt.Sprintf("%s:%d", addr, o.Port),
			Handler:        router,
		},
	}

	var err error
	if s.listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", addr, o.Port)); err != nil {
		log.Errorf("Unable to start ControlZ: %v", err)
		return nil, err
	}

	s.shutdown.Add(1)
	go s.listen()

	return s, nil
}

func (s *Server) listen() {
	log.Infof("ControlZ available at %s", s.listener.Addr().String())

	if listeningTestProbe != nil {
		listeningTestProbe()
	}

	_ = s.httpServer.Serve(s.listener)
	log.Infof("ControlZ terminated")
	s.shutdown.Done()
}

// Close terminates ControlZ.
//
// Close is not normally used by programs that expose ControlZ, it is primarily intended to be
// used by tests.
func (s *Server) Close() {
	if s.listener != nil {
		_ = s.listener.Close()
		s.shutdown.Wait()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"compress/gzip"
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"github.com/nightlyone/lockfile"
)

var updateTopic = &Topic{
	Name:        "update",
	Description: "update heroku-cli",
}

var updateCmd = &Command{
	Topic:       "update",
	Description: "updates heroku-cli",
	Args:        []Arg{{Name: "channel", Optional: true}},
	Run: func(ctx *Context) {
		channel := ctx.Args.(map[string]string)["channel"]
		if channel == "" {
			channel = "master"
		}
		Errf("updating plugins... ")
		if err := node.UpdatePackages(); err != nil {
			panic(err)
		}
		Errln("done")
		manifest := getUpdateManifest(channel)
		build := manifest.Builds[runtime.GOOS][runtime.GOARCH]
		Errf("updating to %s (%s)... ", manifest.Version, manifest.Channel)
		update(build.URL, build.Sha1)
		Errln("done")
	},
}

var binPath = filepath.Join(AppDir, "heroku-cli")
var updateLockPath = filepath.Join(AppDir, "updating.lock")
var autoupdateFile = filepath.Join(AppDir, "autoupdate")

func init() {
	if runtime.GOOS == "windows" {
		binPath = binPath + ".exe"
	}
}

// UpdateIfNeeded checks for and performs an autoupdate if there is a new version out.
func UpdateIfNeeded() {
	lock := getUpdateLock()
	if !updateNeeded() {
		lock.Unlock()
		return
	}
	doneUpdatingPlugins := make(chan bool)
	go func() {
		node.UpdatePackages()
		doneUpdatingPlugins <- true
	}()
	manifest := getUpdateManifest(Channel)
	if manifest.Version == Version {
		<-doneUpdatingPlugins
		touchAutoupdateFile()
		lock.Unlock()
		return
	}
	if !updatable() {
		Errf("Out of date: You are running %s but %s is out.\n", Version, manifest.Version)
		<-doneUpdatingPlugins
		lock.Unlock()
		return
	}
	// Leave out updating text until heroku-cli is used in place of ruby cli
	// So it doesn't confuse users with 2 different version numbers
	//Errf("Updating to %s... ", manifest.Version)
	build := manifest.Builds[runtime.GOOS][runtime.GOARCH]
	update(build.URL, build.Sha1)
	<-doneUpdatingPlugins
	//Errln("done")

	// these are deferred but won't be called because of os.Exit
	touchAutoupdateFile()
	lock.Unlock()
	execBin()
	os.Exit(0)
}

// Attempts to get the lockfile
// Blocks if unavailable
func getUpdateLock() lockfile.Lockfile {
	lock, err := lockfile.New(updateLockPath)
	if err != nil {
		Errln("Cannot initialize update lockfile.")
		panic(err)
	}
	start := time.Now()
	for {
		if lock.TryLock() == nil {
			return lock
		}
		time.Sleep(10 * time.Millisecond)
		if time.Since(start) > 30*time.Second {
			// In a timeout, assume the last updating process timed out
			os.Remove(updateLockPath)
		}
	}
}

func updateNeeded() bool {
	if Version == "dev" {
		return false
	}
	f, err := os.Stat(autoupdateFile)
	if err != nil {
		return true
	}
	return time.Since(f.ModTime()) > 1*time.Hour
}

func touchAutoupdateFile() {
	out, err := os.OpenFile(autoupdateFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	out.WriteString(time.Now().String())
}

type manifest struct {
	Channel, Version string
	Builds           map[string]map[string]struct {
		URL, Sha1 string
	}
}

func getUpdateManifest(channel string) manifest {
	res, err := http.Get("http://d1gvo455cekpjp.cloudfront.net/" + channel + "/manifest.json")
	if err != nil {
		panic(err)
	}
	var m manifest
	json.NewDecoder(res.Body).Decode(&m)
	return m
}

func updatable() bool {
	path, err := filepath.Abs(os.Args[0])
	if err != nil {
		Errln(err)
	}
	return path == binPath
}

func update(url, sha1 string) {
	// on windows we can't remove an existing file or remove the running binary
	// so we download the file to binName.new
	// move the running binary to binName.old (deleting any existing file first)
	// rename the downloaded file to binName
	if err := downloadBin(binPath+".new", url); err != nil {
		panic(err)
	}
	if fileSha1(binPath+".new") != sha1 {
		panic("SHA mismatch")
	}
	os.Remove(binPath + ".old")
	if err := os.Rename(binPath, binPath+".old"); err != nil {
		panic(err)
	}
	if err := os.Rename(binPath+".new", binPath); err != nil {
		panic(err)
	}
}

func downloadBin(path, url string) error {
	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
	if err != nil {
		return err
	}
	defer out.Close()
	client := &http.Client{}
	req, err := http.NewRequest("GET", url+".gz", nil)
	if err != nil {
		return err
	}
	req.Header.Add("Accept-Encoding", "gzip")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	uncompressed, err := gzip.NewReader(resp.Body)
	if err != nil {
		return err
	}
	_, err = io.Copy(out, uncompressed)
	return err
}

func fileSha1(path string) string {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", sha1.Sum(data))
}

func execBin() {
	cmd := exec.Command(binPath, os.Args[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Run()
}
<commit_msg>update timeout<commit_after>package main

import (
	"compress/gzip"
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"github.com/nightlyone/lockfile"
)

var updateTopic = &Topic{
	Name:        "update",
	Description: "update heroku-cli",
}

var updateCmd = &Command{
	Topic:       "update",
	Description: "updates heroku-cli",
	Args:        []Arg{{Name: "channel", Optional: true}},
	Run: func(ctx *Context) {
		channel := ctx.Args.(map[string]string)["channel"]
		if channel == "" {
			channel = "master"
		}
		Errf("updating plugins... ")
		if err := node.UpdatePackages(); err != nil {
			panic(err)
		}
		Errln("done")
		manifest := getUpdateManifest(channel)
		build := manifest.Builds[runtime.GOOS][runtime.GOARCH]
		Errf("updating to %s (%s)... ", manifest.Version, manifest.Channel)
		update(build.URL, build.Sha1)
		Errln("done")
	},
}

var binPath = filepath.Join(AppDir, "heroku-cli")
var updateLockPath = filepath.Join(AppDir, "updating.lock")
var autoupdateFile = filepath.Join(AppDir, "autoupdate")

func init() {
	if runtime.GOOS == "windows" {
		binPath = binPath + ".exe"
	}
}

// UpdateIfNeeded checks for and performs an autoupdate if there is a new version out.
func UpdateIfNeeded() {
	lock := getUpdateLock()
	if !updateNeeded() {
		lock.Unlock()
		return
	}
	doneUpdatingPlugins := make(chan bool)
	go func() {
		node.UpdatePackages()
		doneUpdatingPlugins <- true
	}()
	manifest := getUpdateManifest(Channel)
	if manifest.Version == Version {
		select {
		case <-time.After(time.Second * 30):
			Errln("Timed out while updating")
		case <-doneUpdatingPlugins:
		}
		touchAutoupdateFile()
		lock.Unlock()
		return
	}
	if !updatable() {
		Errf("Out of date: You are running %s but %s is out.\n", Version, manifest.Version)
		<-doneUpdatingPlugins
		lock.Unlock()
		return
	}
	// Leave out updating text until heroku-cli is used in place of ruby cli
	// So it doesn't confuse users with 2 different version numbers
	//Errf("Updating to %s... ", manifest.Version)
	build := manifest.Builds[runtime.GOOS][runtime.GOARCH]
	update(build.URL, build.Sha1)
	select {
	case <-time.After(time.Second * 30):
		Errln("Timed out while updating")
	case <-doneUpdatingPlugins:
	}
	//Errln("done")

	// these are deferred but won't be called because of os.Exit
	touchAutoupdateFile()
	lock.Unlock()
	execBin()
	os.Exit(0)
}

// Attempts to get the lockfile
// Blocks if unavailable
func getUpdateLock() lockfile.Lockfile {
	lock, err := lockfile.New(updateLockPath)
	if err != nil {
		Errln("Cannot initialize update lockfile.")
		panic(err)
	}
	start := time.Now()
	for {
		if lock.TryLock() == nil {
			return lock
		}
		time.Sleep(10 * time.Millisecond)
		if time.Since(start) > 30*time.Second {
			// In a timeout, assume the last updating process timed out
			os.Remove(updateLockPath)
		}
	}
}

func updateNeeded() bool {
	if Version == "dev" {
		return false
	}
	f, err := os.Stat(autoupdateFile)
	if err != nil {
		return true
	}
	return time.Since(f.ModTime()) > 1*time.Hour
}

func touchAutoupdateFile() {
	out, err := os.OpenFile(autoupdateFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	out.WriteString(time.Now().String())
}

type manifest struct {
	Channel, Version string
	Builds           map[string]map[string]struct {
		URL, Sha1 string
	}
}

func getUpdateManifest(channel string) manifest {
	res, err := http.Get("http://d1gvo455cekpjp.cloudfront.net/" + channel + "/manifest.json")
	if err != nil {
		panic(err)
	}
	var m manifest
	json.NewDecoder(res.Body).Decode(&m)
	return m
}

func updatable() bool {
	path, err := filepath.Abs(os.Args[0])
	if err != nil {
		Errln(err)
	}
	return path == binPath
}

func update(url, sha1 string) {
	// on windows we can't remove an existing file or remove the running binary
	// so we download the file to binName.new
	// move the running binary to binName.old (deleting any existing file first)
	// rename the downloaded file to binName
	if err := downloadBin(binPath+".new", url); err != nil {
		panic(err)
	}
	if fileSha1(binPath+".new") != sha1 {
		panic("SHA mismatch")
	}
	os.Remove(binPath + ".old")
	if err := os.Rename(binPath, binPath+".old"); err != nil {
		panic(err)
	}
	if err := os.Rename(binPath+".new", binPath); err != nil {
		panic(err)
	}
}

func downloadBin(path, url string) error {
	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
	if err != nil {
		return err
	}
	defer out.Close()
	client := &http.Client{}
	req, err := http.NewRequest("GET", url+".gz", nil)
	if err != nil {
		return err
	}
	req.Header.Add("Accept-Encoding", "gzip")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	uncompressed, err := gzip.NewReader(resp.Body)
	if err != nil {
		return err
	}
	_, err = io.Copy(out, uncompressed)
	return err
}

func fileSha1(path string) string {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", sha1.Sum(data))
}

func execBin() {
	cmd := exec.Command(binPath, os.Args[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/PuerkitoBio/goquery"
	"github.com/go-redis/redis"
	"github.com/mmcdole/gofeed"
	"strconv"
	"strings"
	"time"
)

func bgmTrack(id string, ttl int) {
	rssURL := "https://bgm.tv/feed/user/" + id + "/timeline"
	if ttl < 10 {
		ttl = 10
	}
	logger.Debug(rssURL, ttl)
	keyLock := "bgm_" + id + "_lock"
	keyLast := "bgm_" + id + "_last"
	for {
		err := redisClient.Get(keyLock).Err()
		if err == nil {
			time.Sleep(time.Second)
			continue
		} else if err != nil && err != redis.Nil {
			logger.Error("get lock", err)
			time.Sleep(time.Second)
			continue
		}

		if redisClient.Set(keyLock, 0, 10*time.Second).Err() != nil {
			logger.Error("set lock first", err)
		}

		fp := gofeed.NewParser()
		feed, err := fp.ParseURL(rssURL)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Second)
			continue
		}
		last, err := redisClient.Get(keyLast).Int64()
		if err != nil {
			logger.Error("get last", err)
			last = 0
		}
		var latest int64
		for _, item := range feed.Items {
			if item.GUID == "" {
				logger.Error("guid not found for", item.Title)
				continue
			}
			tokens := strings.Split(item.GUID, "/")
			guid := tokens[len(tokens)-1]
			id, err := strconv.ParseInt(guid, 10, 64)
			if err != nil {
				logger.Error("guid:", item.GUID)
				continue
			}
			if id > latest {
				latest = id
			}
			if last == 0 {
				last = id
				break
			}
			if id <= last {
				break
			}
			des := strings.Split(item.Description, `"`)
			if len(des) < 2 {
				logger.Info("could not get url:", strconv.Quote(item.Description))
				continue
			}
			text := getBangumiUpdate(item.Title, des[1])
			logger.Info(text)
			go twitterBot.Client.Statuses.Update(text, nil)
		}
		if redisClient.Set(keyLast, latest, 0).Err() != nil {
			logger.Error("set last", err)
		}
		if redisClient.Set(keyLock, 0, time.Duration(ttl)*time.Second).Err() != nil {
			logger.Error("set lock", err)
		}
		time.Sleep(1 * time.Second)
	}
}

func getBangumiUpdate(title, url string) string {
	_title := []rune(title)
	action := string(_title[0:2])
	content := string(_title[2:])
	emoji := emojiBangumi[action]

	subject := getSubjectFromEP(url)
	if subject == "" {
		return emoji + " " + title + " " + url + " #Bangumi"
	}
	return emoji + " " + action + "「" + subject + "」" + content + " " + url + " #Bangumi"
}

func getSubjectFromEP(url string) string {
	tokens := strings.Split(url, "/")
	t := tokens[len(tokens)-2]
	if t != "ep" {
		return ""
	}
	doc, err := goquery.NewDocument(url)
	if err != nil {
		logger.Error(err)
		return ""
	}
	return doc.Find("div#headerSubject h1.nameSingle a").Text()

}
<commit_msg>adjust log<commit_after>package main

import (
	"github.com/PuerkitoBio/goquery"
	"github.com/go-redis/redis"
	"github.com/mmcdole/gofeed"
	"strconv"
	"strings"
	"time"
)

func bgmTrack(id string, ttl int) {
	rssURL := "https://bgm.tv/feed/user/" + id + "/timeline"
	if ttl < 10 {
		ttl = 10
	}
	logger.Debug(rssURL, ttl)
	keyLock := "bgm_" + id + "_lock"
	keyLast := "bgm_" + id + "_last"
	for {
		err := redisClient.Get(keyLock).Err()
		if err == nil {
			time.Sleep(time.Second)
			continue
		} else if err != nil && err != redis.Nil {
			logger.Error("get lock", err)
			time.Sleep(time.Second)
			continue
		}

		if redisClient.Set(keyLock, 0, 10*time.Second).Err() != nil {
			logger.Warning("lock before", err)
		}

		fp := gofeed.NewParser()
		feed, err := fp.ParseURL(rssURL)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Second)
			continue
		}
		last, err := redisClient.Get(keyLast).Int64()
		if err != nil {
			logger.Error("get last", err)
			last = 0
		}
		var latest int64
		for _, item := range feed.Items {
			if item.GUID == "" {
				logger.Error("guid not found for", item.Title)
				continue
			}
			tokens := strings.Split(item.GUID, "/")
			guid := tokens[len(tokens)-1]
			id, err := strconv.ParseInt(guid, 10, 64)
			if err != nil {
				logger.Error("guid:", item.GUID)
				continue
			}
			if id > latest {
				latest = id
			}
			if last == 0 {
				last = id
				break
			}
			if id <= last {
				break
			}
			des := strings.Split(item.Description, `"`)
			if len(des) < 2 {
				logger.Warning("could not get url:", strconv.Quote(item.Description))
				continue
			}
			text := getBangumiUpdate(item.Title, des[1])
			logger.Info(text)
			go twitterBot.Client.Statuses.Update(text, nil)
		}
		if redisClient.Set(keyLast, latest, 0).Err() != nil {
			logger.Error("set last", err)
		}
		if redisClient.Set(keyLock, 0, time.Duration(ttl)*time.Second).Err() != nil {
			logger.Warning("lock after", err)
		}
		time.Sleep(1 * time.Second)
	}
}

func getBangumiUpdate(title, url string) string {
	_title := []rune(title)
	action := string(_title[0:2])
	content := string(_title[2:])
	emoji := emojiBangumi[action]

	subject := getSubjectFromEP(url)
	if subject == "" {
		return emoji + " " + title + " " + url + " #Bangumi"
	}
	return emoji + " " + action + "「" + subject + "」" + content + " " + url + " #Bangumi"
}

func getSubjectFromEP(url string) string {
	tokens := strings.Split(url, "/")
	t := tokens[len(tokens)-2]
	if t != "ep" {
		return ""
	}
	doc, err := goquery.NewDocument(url)
	if err != nil {
		logger.Error(err)
		return ""
	}
	return doc.Find("div#headerSubject h1.nameSingle a").Text()

}
<|endoftext|>


<commit_before>package bin

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/rjeczalik/which"
)

func min(i, j int) int {
	if i < j {
		return i
	}
	return j
}

func max(i, j int) int {
	if i > j {
		return i
	}
	return j
}

var (
	home     string
	parallel = max(runtime.GOMAXPROCS(-1), runtime.NumCPU())
)

func init() {
	if u, err := user.Current(); err == nil {
		home = u.HomeDir
	}
}

func appenduniq(a *[]string) func(...string) {
	dups := make(map[string]struct{})
	return func(s ...string) {
		for _, s := range s {
			if _, ok := dups[s]; !ok {
				*a = append(*a, s)
				dups[s] = struct{}{}
			}
		}
	}
}

func splitandmap(env string, fn func(string) string) (s []string) {
	var err error
	for _, dir := range strings.Split(os.Getenv(env), string(os.PathListSeparator)) {
		if dir = fn(dir); dir != "" {
			if dir, err = filepath.Abs(dir); err == nil {
				s = append(s, dir)
			}
		}
	}
	return
}

// TODO(rjeczalik): handle directory symlinks
func searchpaths() (s []string) {
	appends := appenduniq(&s)
	if u, err := user.Current(); err == nil {
		// Use $PATH and $GOBIN directories only if they're in $HOME.
		ishome := func(dir string) string {
			if strings.HasPrefix(dir, u.HomeDir) {
				return dir
			}
			return ""
		}
		appends(splitandmap("PATH", ishome)...)
		appends(splitandmap("GOBIN", ishome)...)
	}
	isdir := func(dir string) string {
		dir = filepath.Join(dir, "bin")
		if fi, err := os.Stat(dir); err == nil && fi.IsDir() {
			return dir
		}
		return ""
	}
	appends(splitandmap("GOPATH", isdir)...)
	return
}

func splitdirpkg(a []string) (dirs, pkgs []string) {
	var (
		appenddirs = appenduniq(&dirs)
		appendpkgs = appenduniq(&pkgs)
	)
	for _, a := range a {
		switch {
		case a == ".":
			if wd, err := os.Getwd(); err == nil {
				appenddirs(wd)
			}
		default:
			if fi, err := os.Stat(a); err == nil {
				if !fi.IsDir() {
					a = filepath.Dir(a)
				}
				appenddirs(a)
			} else if os.IsNotExist(err) {
				appendpkgs(a)
			}
		}
	}
	return
}

// Bin represents single Go executable.
type Bin struct {
	Path     string
	Package  string
	CanWrite bool
	err      error
}

// Err returns any error a Bin may hold. The error may be set by PATH lookup
// routines or when guessing import path of the executable fails.
func (b Bin) Err() error { return b.err }

// BinSlice attaches the methods of Interface to []Bin, sorting in increasing
// Bin.Path order.
type BinSlice []Bin

// Implements sort.Interface.
func (b BinSlice) Sort()              { sort.Sort(b) }
func (b BinSlice) Len() int           { return len(b) }
func (b BinSlice) Less(i, j int) bool { return b[i].Path < b[j].Path }
func (b BinSlice) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }

// CanWrite returns true if a directory or a file specified by the path can
// be written.
func CanWrite(path string) bool {
	fi, err := os.Stat(path)
	if err != nil {
		return false
	}
	dir := path
	if !fi.IsDir() {
		dir = filepath.Dir(path)
	}
	f, err := ioutil.TempFile(dir, filepath.Base(path))
	if err != nil {
		return false
	}
	f.Close()
	if !fi.IsDir() {
		err, _ = os.Rename(path, f.Name()), os.Rename(f.Name(), path)
	}
	os.Remove(f.Name())
	return (err == nil)
}

// IsExecutable returns true if a file under the given path looks like binary
// file that has executable rights. It's right most of the time.
func IsExecutable(path string) bool {
	return isExecutable(path)
}

// IsBinary returns true if a file is binary.
func IsBinary(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()
	var p [32]byte
	n, err := f.Read(p[:])
	if n == 0 || (err != nil && err != io.EOF) {
		return false
	}
	// TODO(rjeczalik): detect shebang (e.g. filter out compiled Python)?
	return !strings.Contains(http.DetectContentType(p[:n]), "text/plain")
}

// Search looks for Go executables in all the directories specified by
// the dirs slice. If dirs is nil or empty, executables are looked up
// in directories specified by the $GOPATH, $GOBIN and $PATH environment
// variables.
// The lookup is performed on multiple goroutines. Setting GOMAXPROCS may speed
// up this function.
func Search(dirs []string) ([]Bin, error) {
	bin, _, err := searchSymlink(dirs, false)
	return bin, err
}

// Search looks for Go executables in all the directories specified by
// the dirs slice resolving any symlinks it discovers. If dirs is nil or empty,
// executables are looked up in directories specified by the $GOPATH, $GOBIN
// and $PATH environment variables.
// The lookup is performed on multiple goroutines. Setting GOMAXPROCS may speed
// up this function.
// TODO: The symlink part is not implemented yet, map is always nil/empty.
func SearchSymlink(dirs []string) ([]Bin, map[string][]Bin, error) {
	return searchSymlink(dirs, true)
}

// Source go-gets packages used to build `b` binaries into `gopath` $GOPATH.
func Source(b []Bin, gopath string) error {
	cmd := exec.Command("go", "get", "-v", "-t")
	for i := range b {
		cmd.Args = append(cmd.Args, b[i].Package)
	}
	cmd.Env = environ(gopath, filepath.Join(gopath, "bin"))
	cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	fmt.Println(cmd.Args)
	return cmd.Run()
}

// Update checks out repositories for each Go executable in b slice in a temporary
// directory, builds new executable and replaces it with the old one.
// The update is performed on multiple goroutines. Setting GOMAXPROCS may speed
// up this function.
func Update(b []Bin, log func(*Bin, time.Duration, error)) {
	type kv struct {
		k string
		v []string
	}
	var (
		builds = make(map[string][]string, len(b))
		mtx    sync.Mutex // protects b
		seterr = func(t time.Time, err error, paths ...string) {
			mtx.Lock()
			for _, path := range paths {
				for i := range b {
					if b[i].Path == path {
						log(&b[i], time.Now().Sub(t), err)
						b[i].err = err
					}
				}
			}
			mtx.Unlock()
		}
		fmterr = func(err error, p []byte) error {
			return fmt.Errorf("%v\n\t%s", err, bytes.Replace(p, []byte{'\n'}, []byte("\n\t"), -1))
		}
	)
	for i := range b {
		if b[i].CanWrite {
			if v, ok := builds[b[i].Package]; ok {
				builds[b[i].Package] = append(v, b[i].Path)
			} else {
				builds[b[i].Package] = []string{b[i].Path}
			}
		}
	}
	var (
		ch = make(chan kv, len(builds))
		wg sync.WaitGroup
	)
	for k, v := range builds {
		wg.Add(1)
		ch <- kv{k: k, v: v}
	}
	for i := min(parallel, len(builds)); i > 0; i-- {
		go func() {
			for kv := range ch {
				wrk, err := ioutil.TempDir("", "gobin")
				if err != nil {
					seterr(time.Now(), err, kv.v...)
					continue
				}
				var (
					bin  = filepath.Join(wrk, "bin")
					env  = environ(wrk, bin)
					fail = func(err error, s ...string) {
						seterr(time.Now(), err, s...)
						os.RemoveAll(wrk)
						wg.Done()
					}
				)
				t := time.Now()
				build := exec.Command("go", "get", kv.k)
				build.Env = env
				if p, err := build.CombinedOutput(); err != nil {
					fail(fmterr(err, p), kv.v...)
					continue
				}
				install := exec.Command("go", "install", kv.k)
				install.Env = env
				if p, err := install.CombinedOutput(); err != nil {
					fail(fmterr(err, p), kv.v...)
					continue
				}
				exe := filepath.Join(bin, filepath.Base(kv.v[0]))
				for _, path := range kv.v {
					if err = copyfile(path, exe); err != nil {
						seterr(time.Now(), err, path)
					}
				}
				seterr(t, nil, kv.v...)
				os.RemoveAll(wrk)
				wg.Done()
			}
		}()
	}
	wg.Wait()
	close(ch)
}

func environ(gopath, gobin string) []string {
	var s = os.Environ()
	for i := range s {
		switch {
		case strings.HasPrefix(s[i], "GOPATH="):
			s[i] = "GOPATH=" + gopath
		case strings.HasPrefix(s[i], "GOBIN="):
			s[i] = "GOBIN=" + gobin
		}
	}
	return s
}

func copyfile(dst, src string) error {
	fsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fsrc.Close()
	fdst, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer fdst.Close()
	_, err = io.Copy(fdst, fsrc)
	return err
}

func importpkg(path string) (string, error) {
	ex, err := which.NewExec(path)
	if err != nil {
		return "", err
	}
	if ex.Type.GOOS != runtime.GOOS || ex.Type.GOARCH != runtime.GOARCH {
		return "", errors.New("bin: cross-compiling is not supported yet")
	}
	return ex.Import()
}

func searchSymlink(args []string, symlink bool) (b []Bin, s map[string][]Bin, err error) {
	type skv struct {
		k string
		v Bin
	}
	type binpath struct {
		path     string
		canwrite bool
	}
	var (
		mtx sync.Mutex // protects b
		sch chan skv
		bs  map[string]Bin
	)
	dirs, pkgs := splitdirpkg(args)
	if dirs == nil || len(dirs) == 0 {
		dirs = searchpaths()
	}
	if dirs == nil || len(dirs) == 0 {
		return nil, nil, errors.New("bin: couldn't find any search paths")
	}
	if symlink {
		s, sch, bs = make(map[string][]Bin), make(chan skv), make(map[string]Bin)
		defer close(sch)
		go func() {
			for kv := range sch {
				if v, ok := s[kv.k]; ok {
					s[kv.k] = append(v, kv.v)
				} else {
					s[kv.k] = []Bin{kv.v}
				}
			}
		}()
	}
	// TODO(rjeczalik): cap(ch) = max(count files in dirs)
	ch, wg := make(chan binpath, 128), sync.WaitGroup{}
	for i := 0; i < parallel; i++ {
		go func() {
			for p := range ch {
				pkg, err := importpkg(p.path)
				if err != nil {
					wg.Done()
					continue
				}
				mtx.Lock()
				b = append(b, Bin{Path: p.path, Package: pkg, CanWrite: p.canwrite})
				mtx.Unlock()
				wg.Done()
			}
		}()
	}
	// TODO(rjeczalik): handle file symlinks
	for _, dir := range dirs {
		fi, err := os.Open(dir)
		if err != nil {
			continue
		}
		files, err := fi.Readdir(0)
		if err != nil {
			fi.Close()
			continue
		}
		canwrite := CanWrite(dir)
		for _, fi := range files {
			path := filepath.Join(dir, fi.Name())
			if !fi.IsDir() && fi.Mode().IsRegular() && IsExecutable(path) && IsBinary(path) {
				wg.Add(1)
				ch <- binpath{path: path, canwrite: canwrite}
			}
		}
		fi.Close()
	}
	wg.Wait()
	close(ch)
	var fb = make([]Bin, 0, len(b))
	if len(pkgs) != 0 {
		for i := range b {
			for _, pkg := range pkgs {
				if strings.HasPrefix(b[i].Package, pkg) {
					fb = append(fb, b[i])
				}
			}
		}
		b, _ = fb, bs
	}
	BinSlice(b).Sort()
	return
}
<commit_msg>Lookup executable name in PATH<commit_after>package bin

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/rjeczalik/which"
)

func min(i, j int) int {
	if i < j {
		return i
	}
	return j
}

func max(i, j int) int {
	if i > j {
		return i
	}
	return j
}

var (
	home     string
	parallel = max(runtime.GOMAXPROCS(-1), runtime.NumCPU())
)

func init() {
	if u, err := user.Current(); err == nil {
		home = u.HomeDir
	}
}

func appenduniq(a *[]string) func(...string) {
	dups := make(map[string]struct{})
	return func(s ...string) {
		for _, s := range s {
			if _, ok := dups[s]; !ok {
				*a = append(*a, s)
				dups[s] = struct{}{}
			}
		}
	}
}

func splitandmap(env string, fn func(string) string) (s []string) {
	var err error
	for _, dir := range strings.Split(os.Getenv(env), string(os.PathListSeparator)) {
		if dir = fn(dir); dir != "" {
			if dir, err = filepath.Abs(dir); err == nil {
				s = append(s, dir)
			}
		}
	}
	return
}

// TODO(rjeczalik): handle directory symlinks
func searchpaths() (s []string) {
	appends := appenduniq(&s)
	if u, err := user.Current(); err == nil {
		// Use $PATH and $GOBIN directories only if they're in $HOME.
		ishome := func(dir string) string {
			if strings.HasPrefix(dir, u.HomeDir) {
				return dir
			}
			return ""
		}
		appends(splitandmap("PATH", ishome)...)
		appends(splitandmap("GOBIN", ishome)...)
	}
	isdir := func(dir string) string {
		dir = filepath.Join(dir, "bin")
		if fi, err := os.Stat(dir); err == nil && fi.IsDir() {
			return dir
		}
		return ""
	}
	appends(splitandmap("GOPATH", isdir)...)
	return
}

func splitdirpkgexe(a []string) (dirs, pkgs, exes []string) {
	var (
		appenddirs = appenduniq(&dirs)
		appendpkgs = appenduniq(&pkgs)
		appendexes = appenduniq(&exes)
	)
LOOP:
	for _, a := range a {
		switch {
		case a == ".":
			if wd, err := os.Getwd(); err == nil {
				appenddirs(wd)
			}
		default:
			if strings.Contains(a, string(os.PathSeparator)) {
				if fi, err := os.Stat(a); err == nil {
					if !fi.IsDir() {
						a = filepath.Dir(a)
					}
					appenddirs(a)
					continue LOOP
				}
			}
			if IsExecutable(a) || !strings.Contains(a, ".") {
				if path, err := exec.LookPath(a); err == nil {
					appendexes(path)
				}
			} else {
				appendpkgs(a)
			}
		}
	}
	return
}

// Bin represents single Go executable.
type Bin struct {
	Path     string
	Package  string
	CanWrite bool
	err      error
}

// Err returns any error a Bin may hold. The error may be set by PATH lookup
// routines or when guessing import path of the executable fails.
func (b Bin) Err() error { return b.err }

// BinSlice attaches the methods of Interface to []Bin, sorting in increasing
// Bin.Path order.
type BinSlice []Bin

// Implements sort.Interface.
func (b BinSlice) Sort()              { sort.Sort(b) }
func (b BinSlice) Len() int           { return len(b) }
func (b BinSlice) Less(i, j int) bool { return b[i].Path < b[j].Path }
func (b BinSlice) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }

// CanWrite returns true if a directory or a file specified by the path can
// be written.
func CanWrite(path string) bool {
	fi, err := os.Stat(path)
	if err != nil {
		return false
	}
	dir := path
	if !fi.IsDir() {
		dir = filepath.Dir(path)
	}
	f, err := ioutil.TempFile(dir, filepath.Base(path))
	if err != nil {
		return false
	}
	f.Close()
	if !fi.IsDir() {
		err, _ = os.Rename(path, f.Name()), os.Rename(f.Name(), path)
	}
	os.Remove(f.Name())
	return (err == nil)
}

// IsExecutable returns true if a file under the given path looks like binary
// file that has executable rights. It's right most of the time.
func IsExecutable(path string) bool {
	return isExecutable(path)
}

// IsBinary returns true if a file is binary.
func IsBinary(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()
	var p [32]byte
	n, err := f.Read(p[:])
	if n == 0 || (err != nil && err != io.EOF) {
		return false
	}
	// TODO(rjeczalik): detect shebang (e.g. filter out compiled Python)?
	return !strings.Contains(http.DetectContentType(p[:n]), "text/plain")
}

// Search looks for Go executables in all the directories specified by
// the dirs slice. If dirs is nil or empty, executables are looked up
// in directories specified by the $GOPATH, $GOBIN and $PATH environment
// variables.
// The lookup is performed on multiple goroutines. Setting GOMAXPROCS may speed
// up this function.
func Search(dirs []string) ([]Bin, error) {
	bin, _, err := searchSymlink(dirs, false)
	return bin, err
}

// Search looks for Go executables in all the directories specified by
// the dirs slice resolving any symlinks it discovers. If dirs is nil or empty,
// executables are looked up in directories specified by the $GOPATH, $GOBIN
// and $PATH environment variables.
// The lookup is performed on multiple goroutines. Setting GOMAXPROCS may speed
// up this function.
// TODO: The symlink part is not implemented yet, map is always nil/empty.
func SearchSymlink(dirs []string) ([]Bin, map[string][]Bin, error) {
	return searchSymlink(dirs, true)
}

// Source go-gets packages used to build `b` binaries into `gopath` $GOPATH.
func Source(b []Bin, gopath string) error {
	cmd := exec.Command("go", "get", "-v", "-t")
	for i := range b {
		cmd.Args = append(cmd.Args, b[i].Package)
	}
	cmd.Env = environ(gopath, filepath.Join(gopath, "bin"))
	cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
	return cmd.Run()
}

// Update checks out repositories for each Go executable in b slice in a temporary
// directory, builds new executable and replaces it with the old one.
// The update is performed on multiple goroutines. Setting GOMAXPROCS may speed
// up this function.
func Update(b []Bin, log func(*Bin, time.Duration, error)) {
	type kv struct {
		k string
		v []string
	}
	var (
		builds = make(map[string][]string, len(b))
		mtx    sync.Mutex // protects b
		seterr = func(t time.Time, err error, paths ...string) {
			mtx.Lock()
			for _, path := range paths {
				for i := range b {
					if b[i].Path == path {
						log(&b[i], time.Now().Sub(t), err)
						b[i].err = err
					}
				}
			}
			mtx.Unlock()
		}
		fmterr = func(err error, p []byte) error {
			return fmt.Errorf("%v\n\t%s", err, bytes.Replace(p, []byte{'\n'}, []byte("\n\t"), -1))
		}
	)
	for i := range b {
		if b[i].CanWrite {
			if v, ok := builds[b[i].Package]; ok {
				builds[b[i].Package] = append(v, b[i].Path)
			} else {
				builds[b[i].Package] = []string{b[i].Path}
			}
		}
	}
	var (
		ch = make(chan kv, len(builds))
		wg sync.WaitGroup
	)
	for k, v := range builds {
		wg.Add(1)
		ch <- kv{k: k, v: v}
	}
	for i := min(parallel, len(builds)); i > 0; i-- {
		go func() {
			for kv := range ch {
				wrk, err := ioutil.TempDir("", "gobin")
				if err != nil {
					seterr(time.Now(), err, kv.v...)
					continue
				}
				var (
					bin  = filepath.Join(wrk, "bin")
					env  = environ(wrk, bin)
					fail = func(err error, s ...string) {
						seterr(time.Now(), err, s...)
						os.RemoveAll(wrk)
						wg.Done()
					}
				)
				t := time.Now()
				build := exec.Command("go", "get", kv.k)
				build.Env = env
				if p, err := build.CombinedOutput(); err != nil {
					fail(fmterr(err, p), kv.v...)
					continue
				}
				install := exec.Command("go", "install", kv.k)
				install.Env = env
				if p, err := install.CombinedOutput(); err != nil {
					fail(fmterr(err, p), kv.v...)
					continue
				}
				exe := filepath.Join(bin, filepath.Base(kv.v[0]))
				for _, path := range kv.v {
					if err = copyfile(path, exe); err != nil {
						seterr(time.Now(), err, path)
					}
				}
				seterr(t, nil, kv.v...)
				os.RemoveAll(wrk)
				wg.Done()
			}
		}()
	}
	wg.Wait()
	close(ch)
}

func environ(gopath, gobin string) []string {
	var s = os.Environ()
	for i := range s {
		switch {
		case strings.HasPrefix(s[i], "GOPATH="):
			s[i] = "GOPATH=" + gopath
		case strings.HasPrefix(s[i], "GOBIN="):
			s[i] = "GOBIN=" + gobin
		}
	}
	return s
}

func copyfile(dst, src string) error {
	fsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fsrc.Close()
	fdst, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer fdst.Close()
	_, err = io.Copy(fdst, fsrc)
	return err
}

func importpkg(path string) (string, error) {
	ex, err := which.NewExec(path)
	if err != nil {
		return "", err
	}
	if ex.Type.GOOS != runtime.GOOS || ex.Type.GOARCH != runtime.GOARCH {
		return "", errors.New("bin: cross-compiling is not supported yet")
	}
	return ex.Import()
}

func searchSymlink(args []string, symlink bool) (b []Bin, s map[string][]Bin, err error) {
	type skv struct {
		k string
		v Bin
	}
	type binpath struct {
		path     string
		canwrite bool
	}
	var (
		mtx sync.Mutex // protects b
		sch chan skv
		bs  map[string]Bin
	)
	dirs, pkgs, exes := splitdirpkgexe(args)
	if len(pkgs) == 0 && len(exes) == 0 && len(dirs) == 0 {
		if dirs = searchpaths(); len(dirs) == 0 {
			return nil, nil, errors.New("bin: couldn't find any search paths")
		}
	}
	if symlink {
		s, sch, bs = make(map[string][]Bin), make(chan skv), make(map[string]Bin)
		defer close(sch)
		go func() {
			for kv := range sch {
				if v, ok := s[kv.k]; ok {
					s[kv.k] = append(v, kv.v)
				} else {
					s[kv.k] = []Bin{kv.v}
				}
			}
		}()
	}
	// TODO(rjeczalik): cap(ch) = max(count files in dirs)
	ch, wg := make(chan binpath, max(len(exes), 128)), sync.WaitGroup{}
	for _, exe := range exes {
		wg.Add(1)
		ch <- binpath{path: exe, canwrite: CanWrite(exe)}
	}
	for i := 0; i < parallel; i++ {
		go func() {
			for p := range ch {
				pkg, err := importpkg(p.path)
				if err != nil {
					wg.Done()
					continue
				}
				mtx.Lock()
				b = append(b, Bin{Path: p.path, Package: pkg, CanWrite: p.canwrite})
				mtx.Unlock()
				wg.Done()
			}
		}()
	}
	// TODO(rjeczalik): handle file symlinks
	for _, dir := range dirs {
		fi, err := os.Open(dir)
		if err != nil {
			continue
		}
		files, err := fi.Readdir(0)
		if err != nil {
			fi.Close()
			continue
		}
		canwrite := CanWrite(dir)
		for _, fi := range files {
			path := filepath.Join(dir, fi.Name())
			if !fi.IsDir() && fi.Mode().IsRegular() && IsExecutable(path) && IsBinary(path) {
				wg.Add(1)
				ch <- binpath{path: path, canwrite: canwrite}
			}
		}
		fi.Close()
	}
	wg.Wait()
	close(ch)
	var fb = make([]Bin, 0, len(b))
	if len(pkgs) != 0 {
		for i := range b {
			for _, pkg := range pkgs {
				if strings.HasPrefix(b[i].Package, pkg) {
					fb = append(fb, b[i])
				}
			}
		}
		b, _ = fb, bs
	}
	BinSlice(b).Sort()
	return
}
<|endoftext|>


<commit_before>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/popular"
	"socialapi/workers/api/modules/privatemessage"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(handler),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))
	mux.Handle("GET", "/message/{id}/related", handlerWrapper(message.GetWithRelated, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))
	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	mux.Handle("GET", "/channel/search", handlerWrapper(channel.Search, "channel-search"))
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	mux.Handle("POST", "/channel/{id}/update", handlerWrapper(channel.Update, "channel-update"))
	mux.Handle("POST", "/channel/{id}/delete", handlerWrapper(channel.Delete, "channel-delete"))
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))
	// add a new messages to the channel
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))
	// list participants of the channel
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))
	// add participant to the channel
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))
	// remove participant from the channel
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))
	// update presence info
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/presence", handlerWrapper(participant.Presence, "participant-presence"))
	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))
	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))
	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))
	// list posts of the account
	mux.Handle("GET", "/account/{id}/posts", handlerWrapper(account.ListPosts, "account-post-list"))
	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))
	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))
	// mark as troll
	mux.Handle("POST", "/account/{id}/markastroll", handlerWrapper(account.MarkAsTroll, "account-mark-as-troll"))

	// fetch profile feed
	// mux.Handle("GET", "/account/{id}/profile/feed", handlerWrapper(account.ListProfileFeed, "list-profile-feed"))
	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))
	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))
	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))
	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))
	// get popular topics
	mux.Handle("GET", "/popular/topics/{statisticName}", handlerWrapper(popular.ListTopics, "list-popular-topics"))
	mux.Handle("GET", "/popular/posts/{channelName}/{statisticName}", handlerWrapper(popular.ListPosts, "list-popular-posts"))

	mux.Handle("POST", "/privatemessage/send", handlerWrapper(privatemessage.Send, "privatemessage-send"))
	mux.Handle("GET", "/privatemessage/list", handlerWrapper(privatemessage.List, "privatemessage-list"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<commit_msg>Social: add glance functionality for pinned messages<commit_after>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/popular"
	"socialapi/workers/api/modules/privatemessage"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(handler),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))
	mux.Handle("GET", "/message/{id}/related", handlerWrapper(message.GetWithRelated, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))
	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	mux.Handle("GET", "/channel/search", handlerWrapper(channel.Search, "channel-search"))
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	mux.Handle("POST", "/channel/{id}/update", handlerWrapper(channel.Update, "channel-update"))
	mux.Handle("POST", "/channel/{id}/delete", handlerWrapper(channel.Delete, "channel-delete"))
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))
	// add a new messages to the channel
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))
	// list participants of the channel
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))
	// add participant to the channel
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))
	// remove participant from the channel
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))
	// update presence info
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/presence", handlerWrapper(participant.Presence, "participant-presence"))
	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))
	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))
	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))
	// list posts of the account
	mux.Handle("GET", "/account/{id}/posts", handlerWrapper(account.ListPosts, "account-post-list"))
	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))
	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))
	// mark as troll
	mux.Handle("POST", "/account/{id}/markastroll", handlerWrapper(account.MarkAsTroll, "account-mark-as-troll"))

	// fetch profile feed
	// mux.Handle("GET", "/account/{id}/profile/feed", handlerWrapper(account.ListProfileFeed, "list-profile-feed"))
	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))
	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))
	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))
	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))

	// @todo add tests
	mux.Handle("POST", "/activity/pin/glance", handlerWrapper(activity.Glance, "activity-pinned-message-glance"))
	// get popular topics
	mux.Handle("GET", "/popular/topics/{statisticName}", handlerWrapper(popular.ListTopics, "list-popular-topics"))
	mux.Handle("GET", "/popular/posts/{channelName}/{statisticName}", handlerWrapper(popular.ListPosts, "list-popular-posts"))

	mux.Handle("POST", "/privatemessage/send", handlerWrapper(privatemessage.Send, "privatemessage-send"))
	mux.Handle("GET", "/privatemessage/list", handlerWrapper(privatemessage.List, "privatemessage-list"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<|endoftext|>