<commit_before>// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"image/color"
	"runtime"
	"strings"
	"sync"
	"time"

	"gopkg.in/fsnotify.v0"
	"gopkg.in/qml.v1"

	"github.com/limetext/lime-backend/lib"
	"github.com/limetext/lime-backend/lib/keys"
	"github.com/limetext/lime-backend/lib/log"
	"github.com/limetext/lime-backend/lib/render"
	_ "github.com/limetext/lime-backend/lib/sublime"
	"github.com/limetext/lime-backend/lib/textmate"
	"github.com/limetext/lime-backend/lib/util"
	. "github.com/limetext/text"
)

var (
	limeViewComponent qml.Object
	scheme            *textmate.Theme
)

const (
	batching_enabled = true
	qmlMainFile      = "main.qml"
	qmlViewFile      = "LimeView.qml"

	// http://qt-project.org/doc/qt-5.1/qtcore/qt.html#KeyboardModifier-enum
	shift_mod  = 0x02000000
	ctrl_mod   = 0x04000000
	alt_mod    = 0x08000000
	meta_mod   = 0x10000000
	keypad_mod = 0x20000000
)

// keeping track of frontend state
type qmlfrontend struct {
	status_message string
	lock           sync.Mutex
	windows        map[*backend.Window]*frontendWindow
	Console        *frontendView
	qmlDispatch    chan qmlDispatch
}

// Used for batching qml.Changed calls
type qmlDispatch struct{ value, field interface{} }

func (t *qmlfrontend) Window(w *backend.Window) *frontendWindow {
	return t.windows[w]
}

func (t *qmlfrontend) Show(v *backend.View, r Region) {
	// TODO
}

func (t *qmlfrontend) VisibleRegion(v *backend.View) Region {
	// TODO
	return Region{0, v.Buffer().Size()}
}

func (t *qmlfrontend) StatusMessage(msg string) {
	t.lock.Lock()
	defer t.lock.Unlock()
	t.status_message = msg
}

func (t *qmlfrontend) ErrorMessage(msg string) {
	log.Error(msg)
	var q qmlDialog
	q.Show(msg, "StandardIcon.Critical")
}

func (t *qmlfrontend) MessageDialog(msg string) {
	var q qmlDialog
	q.Show(msg, "StandardIcon.Information")
}

func (t *qmlfrontend) OkCancelDialog(msg, ok string) bool {
	var q qmlDialog
	return q.Show(msg, "StandardIcon.Question") == 1
}

func (t *qmlfrontend) scroll(b Buffer) {
	t.Show(backend.GetEditor().Console(), Region{b.Size(), b.Size()})
}

func (t *qmlfrontend) Erased(changed_buffer Buffer, region_removed Region, data_removed []rune) {
	t.scroll(changed_buffer)
}

func (t *qmlfrontend) Inserted(changed_buffer Buffer, region_inserted Region, data_inserted []rune) {
	t.scroll(changed_buffer)
}

// Apparently calling qml.Changed also triggers a re-draw, meaning that typed text is at the
// mercy of how quick Qt happens to be rendering.
// Try setting batching_enabled = false to see the effects of non-batching
func (t *qmlfrontend) qmlBatchLoop() {
	queue := make(map[qmlDispatch]bool)
	t.qmlDispatch = make(chan qmlDispatch, 1000)
	for {
		if len(queue) > 0 {
			select {
			case <-time.After(time.Millisecond * 20):
				// Nothing happened for 20 milliseconds, so dispatch all queued changes
				for k := range queue {
					qml.Changed(k.value, k.field)
				}
				queue = make(map[qmlDispatch]bool)
			case d := <-t.qmlDispatch:
				queue[d] = true
			}
		} else {
			queue[<-t.qmlDispatch] = true
		}
	}
}

func (t *qmlfrontend) qmlChanged(value, field interface{}) {
	if !batching_enabled {
		qml.Changed(value, field)
	} else {
		t.qmlDispatch <- qmlDispatch{value, field}
	}
}

func (t *qmlfrontend) DefaultBg() color.RGBA {
	c := scheme.Spice(&render.ViewRegions{})
	c.Background.A = 0xff
	return color.RGBA(c.Background)
}

func (t *qmlfrontend) DefaultFg() color.RGBA {
	c := scheme.Spice(&render.ViewRegions{})
	c.Foreground.A = 0xff
	return color.RGBA(c.Foreground)
}

// Called when a new view is opened
func (t *qmlfrontend) onNew(v *backend.View) {
	fv := &frontendView{bv: v}
	v.Buffer().AddObserver(fv)
	v.Settings().AddOnChange("blah", fv.onChange)

	fv.Title.Text = v.Buffer().FileName()
	if len(fv.Title.Text) == 0 {
		fv.Title.Text = "untitled"
	}

	w2 := t.windows[v.Window()]
	w2.views = append(w2.views, fv)

	tabs := w2.window.ObjectByName("tabs")
	tab := tabs.Call("addTab", "", limeViewComponent).(qml.Object)
	try_now := func() {
		item := tab.Property("item").(qml.Object)
		if item.Addr() == 0 {
			// Happens as the item isn't actually loaded until we switch to the tab.
			// Hence connecting to the loaded signal
			return
		}
		item.Set("myView", fv)
		item.Set("fontSize", float64(12))
		item.Set("fontFace", v.Settings().Get("font_face", "Helvetica").(string))
	}
	tab.On("loaded", try_now)
	try_now()
	tabs.Set("currentIndex", tabs.Property("count").(int)-1)
}

// called when a view is closed
func (t *qmlfrontend) onClose(v *backend.View) {
	w2 := t.windows[v.Window()]
	for i := range w2.views {
		if w2.views[i].bv == v {
			w2.window.ObjectByName("tabs").Call("removeTab", i)
			copy(w2.views[i:], w2.views[i+1:])
			w2.views = w2.views[:len(w2.views)-1]
			return
		}
	}
	log.Error("Couldn't find closed view...")
}

// called when a view has loaded
func (t *qmlfrontend) onLoad(v *backend.View) {
	w2 := t.windows[v.Window()]
	i := 0
	for i = range w2.views {
		if w2.views[i].bv == v {
			break
		}
	}
	v2 := w2.views[i]
	v2.Title.Text = v.Buffer().FileName()
	tabs := w2.window.ObjectByName("tabs")
	tabs.Set("currentIndex", w2.ActiveViewIndex())
	tab := tabs.Call("getTab", i).(qml.Object)
	tab.Set("title", v2.Title.Text)
}

func (t *qmlfrontend) onSelectionModified(v *backend.View) {
	w2 := t.windows[v.Window()]
	i := 0
	for i = range w2.views {
		if w2.views[i].bv == v {
			break
		}
	}
	v2 := w2.views[i]
	v2.qv.Call("onSelectionModified")
}

// Launches the provided command in a new goroutine
// (to avoid locking up the GUI)
func (t *qmlfrontend) RunCommand(command string) {
	t.RunCommandWithArgs(command, make(backend.Args))
}

func (t *qmlfrontend) RunCommandWithArgs(command string, args backend.Args) {
	ed := backend.GetEditor()
	go ed.RunCommand(command, args)
}

func (t *qmlfrontend) HandleInput(text string, keycode int, modifiers int) bool {
	log.Debug("qmlfrontend.HandleInput: text=%v, key=%x, modifiers=%x", text, keycode, modifiers)
	shift := false
	alt := false
	ctrl := false
	super := false

	if key, ok := lut[keycode]; ok {
		ed := backend.GetEditor()

		if (modifiers & shift_mod) != 0 {
			shift = true
		}
		if (modifiers & alt_mod) != 0 {
			alt = true
		}
		if (modifiers & ctrl_mod) != 0 {
			if runtime.GOOS == "darwin" {
				super = true
			} else {
				ctrl = true
			}
		}
		if (modifiers & meta_mod) != 0 {
			if runtime.GOOS == "darwin" {
				ctrl = true
			} else {
				super = true
			}
		}

		ed.HandleInput(keys.KeyPress{Text: text, Key: key, Shift: shift, Alt: alt, Ctrl: ctrl, Super: super})
		return true
	}
	return false
}

// Quit closes all open windows to de-reference all qml objects
func (t *qmlfrontend) Quit() (err error) {
	// todo: handle changed files that aren't saved.
	for _, v := range t.windows {
		if v.window != nil {
			v.window.Hide()
			v.window.Destroy()
			v.window = nil
		}
	}
	return
}

func (t *qmlfrontend) loop() (err error) {
	backend.OnNew.Add(t.onNew)
	backend.OnClose.Add(t.onClose)
	backend.OnLoad.Add(t.onLoad)
	backend.OnSelectionModified.Add(t.onSelectionModified)

	ed := backend.GetEditor()
	ed.SetFrontend(t)
	ed.LogInput(false)
	ed.LogCommands(false)
	c := ed.Console()
	t.Console = &frontendView{bv: c}
	c.Buffer().AddObserver(t.Console)
	c.Buffer().AddObserver(t)

	ed.AddPackagesPath("shipped", "../packages")
	ed.AddPackagesPath("default", "../packages/Default")
	ed.AddPackagesPath("user", "../packages/User")

	var (
		engine    *qml.Engine
		component qml.Object
		// WaitGroup keeping track of open windows
		wg sync.WaitGroup
	)

	// create and setup a new engine, destroying
	// the old one if one exists.
	//
	// This is needed to re-load qml files to get
	// the new file contents from disc as otherwise
	// the old file would still be what is referenced.
	newEngine := func() (err error) {
		if engine != nil {
			log.Debug("calling destroy")
			// TODO(.): calling this appears to make the editor *very* crash-prone, just let it leak for now
			// engine.Destroy()
			engine = nil
		}
		log.Debug("calling newEngine")
		engine = qml.NewEngine()
		engine.On("quit", t.Quit)
		log.Debug("setvar frontend")
		engine.Context().SetVar("frontend", t)
		log.Debug("setvar editor")
		engine.Context().SetVar("editor", backend.GetEditor())

		log.Debug("loadfile")
		component, err = engine.LoadFile(qmlMainFile)
		if err != nil {
			return err
		}
		limeViewComponent, err = engine.LoadFile(qmlViewFile)
		return
	}
	if err := newEngine(); err != nil {
		log.Error(err)
	}

	backend.OnNewWindow.Add(func(w *backend.Window) {
		fw := &frontendWindow{bw: w}
		t.windows[w] = fw
		if component != nil {
			fw.launch(&wg, component)
		}
	})

	// TODO: should be done backend side
	if sc, err := textmate.LoadTheme("../packages/TextMate-Themes/Monokai.tmTheme"); err != nil {
		log.Error(err)
	} else {
		scheme = sc
	}

	ed.Init()

	// TODO: setting syntax should be done automaticly in backend and after
	// implementing this we could run Init in a go routine and remove the
	// next two line
	v := ed.ActiveWindow().OpenFile("main.go", 0)
	v.SetSyntaxFile("../packages/go-tmbundle/Syntaxes/Go.tmLanguage")

	defer func() {
		fmt.Println(util.Prof)
	}()

	// The rest of code is related to livereloading qml files
	// TODO: this doesnt work currently
	watch, err := fsnotify.NewWatcher()
	if err != nil {
		log.Error("Unable to create file watcher: %s", err)
		return
	}
	defer watch.Close()
	watch.Watch(".")
	defer watch.RemoveWatch(".")

	reloadRequested := false

	go func() {
		for {
			select {
			case ev := <-watch.Event:
				if ev != nil && strings.HasSuffix(ev.Name, ".qml") && ev.IsModify() && !ev.IsAttrib() {
					reloadRequested = true
					t.Quit()
				}
			}
		}
	}()

	for {
		// Reset reload status
		reloadRequested = false

		log.Debug("Waiting for all windows to close")
		// wg would be the WaitGroup all windows belong to, so first we wait for
		// all windows to close.
		wg.Wait()
		log.Debug("All windows closed. reloadRequest: %v", reloadRequested)
		// then we check if there's a reload request in the pipe
		if !reloadRequested || len(t.windows) == 0 {
			// This would be a genuine exit; all windows closed by the user
			break
		}

		// *We* closed all windows because we want to reload freshly changed qml
		// files.
		for {
			log.Debug("Calling newEngine")
			if err := newEngine(); err != nil {
				// Reset reload status
				reloadRequested = false
				log.Error(err)
				for !reloadRequested {
					// This loop allows us to re-try reloading
					// if there was an error in the file this time,
					// we just loop around again when we receive the next
					// reload request (ie on the next save of the file).
					time.Sleep(time.Second)
				}
				continue
			}
			log.Debug("break")
			break
		}
		log.Debug("re-launching all windows")
		// Succeeded loading the file, re-launch all windows
		for _, v := range t.windows {
			v.launch(&wg, component)
		}
	}

	return
}
<commit_msg>temporary workaround for settings problem<commit_after>// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"image/color"
	"runtime"
	"strings"
	"sync"
	"time"

	"gopkg.in/fsnotify.v0"
	"gopkg.in/qml.v1"

	"github.com/limetext/lime-backend/lib"
	"github.com/limetext/lime-backend/lib/keys"
	"github.com/limetext/lime-backend/lib/log"
	"github.com/limetext/lime-backend/lib/render"
	_ "github.com/limetext/lime-backend/lib/sublime"
	"github.com/limetext/lime-backend/lib/textmate"
	"github.com/limetext/lime-backend/lib/util"
	. "github.com/limetext/text"
)

var (
	limeViewComponent qml.Object
	scheme            *textmate.Theme
)

const (
	batching_enabled = true
	qmlMainFile      = "main.qml"
	qmlViewFile      = "LimeView.qml"

	// http://qt-project.org/doc/qt-5.1/qtcore/qt.html#KeyboardModifier-enum
	shift_mod  = 0x02000000
	ctrl_mod   = 0x04000000
	alt_mod    = 0x08000000
	meta_mod   = 0x10000000
	keypad_mod = 0x20000000
)

// keeping track of frontend state
type qmlfrontend struct {
	status_message string
	lock           sync.Mutex
	windows        map[*backend.Window]*frontendWindow
	Console        *frontendView
	qmlDispatch    chan qmlDispatch
}

// Used for batching qml.Changed calls
type qmlDispatch struct{ value, field interface{} }

func (t *qmlfrontend) Window(w *backend.Window) *frontendWindow {
	return t.windows[w]
}

func (t *qmlfrontend) Show(v *backend.View, r Region) {
	// TODO
}

func (t *qmlfrontend) VisibleRegion(v *backend.View) Region {
	// TODO
	return Region{0, v.Buffer().Size()}
}

func (t *qmlfrontend) StatusMessage(msg string) {
	t.lock.Lock()
	defer t.lock.Unlock()
	t.status_message = msg
}

func (t *qmlfrontend) ErrorMessage(msg string) {
	log.Error(msg)
	var q qmlDialog
	q.Show(msg, "StandardIcon.Critical")
}

func (t *qmlfrontend) MessageDialog(msg string) {
	var q qmlDialog
	q.Show(msg, "StandardIcon.Information")
}

func (t *qmlfrontend) OkCancelDialog(msg, ok string) bool {
	var q qmlDialog
	return q.Show(msg, "StandardIcon.Question") == 1
}

func (t *qmlfrontend) scroll(b Buffer) {
	t.Show(backend.GetEditor().Console(), Region{b.Size(), b.Size()})
}

func (t *qmlfrontend) Erased(changed_buffer Buffer, region_removed Region, data_removed []rune) {
	t.scroll(changed_buffer)
}

func (t *qmlfrontend) Inserted(changed_buffer Buffer, region_inserted Region, data_inserted []rune) {
	t.scroll(changed_buffer)
}

// Apparently calling qml.Changed also triggers a re-draw, meaning that typed text is at the
// mercy of how quick Qt happens to be rendering.
// Try setting batching_enabled = false to see the effects of non-batching
func (t *qmlfrontend) qmlBatchLoop() {
	queue := make(map[qmlDispatch]bool)
	t.qmlDispatch = make(chan qmlDispatch, 1000)
	for {
		if len(queue) > 0 {
			select {
			case <-time.After(time.Millisecond * 20):
				// Nothing happened for 20 milliseconds, so dispatch all queued changes
				for k := range queue {
					qml.Changed(k.value, k.field)
				}
				queue = make(map[qmlDispatch]bool)
			case d := <-t.qmlDispatch:
				queue[d] = true
			}
		} else {
			queue[<-t.qmlDispatch] = true
		}
	}
}

func (t *qmlfrontend) qmlChanged(value, field interface{}) {
	if !batching_enabled {
		qml.Changed(value, field)
	} else {
		t.qmlDispatch <- qmlDispatch{value, field}
	}
}

func (t *qmlfrontend) DefaultBg() color.RGBA {
	c := scheme.Spice(&render.ViewRegions{})
	c.Background.A = 0xff
	return color.RGBA(c.Background)
}

func (t *qmlfrontend) DefaultFg() color.RGBA {
	c := scheme.Spice(&render.ViewRegions{})
	c.Foreground.A = 0xff
	return color.RGBA(c.Foreground)
}

// Called when a new view is opened
func (t *qmlfrontend) onNew(v *backend.View) {
	fv := &frontendView{bv: v}
	v.Buffer().AddObserver(fv)
	v.Settings().AddOnChange("blah", fv.onChange)

	fv.Title.Text = v.Buffer().FileName()
	if len(fv.Title.Text) == 0 {
		fv.Title.Text = "untitled"
	}

	w2 := t.windows[v.Window()]
	w2.views = append(w2.views, fv)

	tabs := w2.window.ObjectByName("tabs")
	tab := tabs.Call("addTab", "", limeViewComponent).(qml.Object)
	try_now := func() {
		item := tab.Property("item").(qml.Object)
		if item.Addr() == 0 {
			// Happens as the item isn't actually loaded until we switch to the tab.
			// Hence connecting to the loaded signal
			return
		}
		item.Set("myView", fv)
		// TODO: v.Settings().Get("font_size", 12)
		// is sometimes returning int sometimes float64
		var fSize float64
		fz := v.Settings().Get("font_size", 12)
		if tmp, ok := fz.(int); ok {
			fSize = float64(tmp)
		} else if tmp1, ok := fz.(float64); ok {
			fSize = tmp1
		}
		item.Set("fontSize", fSize)
		item.Set("fontFace", v.Settings().Get("font_face", "Helvetica").(string))
	}
	tab.On("loaded", try_now)
	try_now()
	tabs.Set("currentIndex", tabs.Property("count").(int)-1)
}

// called when a view is closed
func (t *qmlfrontend) onClose(v *backend.View) {
	w2 := t.windows[v.Window()]
	for i := range w2.views {
		if w2.views[i].bv == v {
			w2.window.ObjectByName("tabs").Call("removeTab", i)
			copy(w2.views[i:], w2.views[i+1:])
			w2.views = w2.views[:len(w2.views)-1]
			return
		}
	}
	log.Error("Couldn't find closed view...")
}

// called when a view has loaded
func (t *qmlfrontend) onLoad(v *backend.View) {
	w2 := t.windows[v.Window()]
	i := 0
	for i = range w2.views {
		if w2.views[i].bv == v {
			break
		}
	}
	v2 := w2.views[i]
	v2.Title.Text = v.Buffer().FileName()
	tabs := w2.window.ObjectByName("tabs")
	tabs.Set("currentIndex", w2.ActiveViewIndex())
	tab := tabs.Call("getTab", i).(qml.Object)
	tab.Set("title", v2.Title.Text)
}

func (t *qmlfrontend) onSelectionModified(v *backend.View) {
	w2 := t.windows[v.Window()]
	i := 0
	for i = range w2.views {
		if w2.views[i].bv == v {
			break
		}
	}
	v2 := w2.views[i]
	v2.qv.Call("onSelectionModified")
}

// Launches the provided command in a new goroutine
// (to avoid locking up the GUI)
func (t *qmlfrontend) RunCommand(command string) {
	t.RunCommandWithArgs(command, make(backend.Args))
}

func (t *qmlfrontend) RunCommandWithArgs(command string, args backend.Args) {
	ed := backend.GetEditor()
	go ed.RunCommand(command, args)
}

func (t *qmlfrontend) HandleInput(text string, keycode int, modifiers int) bool {
	log.Debug("qmlfrontend.HandleInput: text=%v, key=%x, modifiers=%x", text, keycode, modifiers)
	shift := false
	alt := false
	ctrl := false
	super := false

	if key, ok := lut[keycode]; ok {
		ed := backend.GetEditor()

		if (modifiers & shift_mod) != 0 {
			shift = true
		}
		if (modifiers & alt_mod) != 0 {
			alt = true
		}
		if (modifiers & ctrl_mod) != 0 {
			if runtime.GOOS == "darwin" {
				super = true
			} else {
				ctrl = true
			}
		}
		if (modifiers & meta_mod) != 0 {
			if runtime.GOOS == "darwin" {
				ctrl = true
			} else {
				super = true
			}
		}

		ed.HandleInput(keys.KeyPress{Text: text, Key: key, Shift: shift, Alt: alt, Ctrl: ctrl, Super: super})
		return true
	}
	return false
}

// Quit closes all open windows to de-reference all qml objects
func (t *qmlfrontend) Quit() (err error) {
	// todo: handle changed files that aren't saved.
	for _, v := range t.windows {
		if v.window != nil {
			v.window.Hide()
			v.window.Destroy()
			v.window = nil
		}
	}
	return
}

func (t *qmlfrontend) loop() (err error) {
	backend.OnNew.Add(t.onNew)
	backend.OnClose.Add(t.onClose)
	backend.OnLoad.Add(t.onLoad)
	backend.OnSelectionModified.Add(t.onSelectionModified)

	ed := backend.GetEditor()
	ed.SetFrontend(t)
	ed.LogInput(false)
	ed.LogCommands(false)
	c := ed.Console()
	t.Console = &frontendView{bv: c}
	c.Buffer().AddObserver(t.Console)
	c.Buffer().AddObserver(t)

	ed.AddPackagesPath("shipped", "../packages")
	ed.AddPackagesPath("default", "../packages/Default")
	ed.AddPackagesPath("user", "../packages/User")

	var (
		engine    *qml.Engine
		component qml.Object
		// WaitGroup keeping track of open windows
		wg sync.WaitGroup
	)

	// create and setup a new engine, destroying
	// the old one if one exists.
	//
	// This is needed to re-load qml files to get
	// the new file contents from disc as otherwise
	// the old file would still be what is referenced.
	newEngine := func() (err error) {
		if engine != nil {
			log.Debug("calling destroy")
			// TODO(.): calling this appears to make the editor *very* crash-prone, just let it leak for now
			// engine.Destroy()
			engine = nil
		}
		log.Debug("calling newEngine")
		engine = qml.NewEngine()
		engine.On("quit", t.Quit)
		log.Debug("setvar frontend")
		engine.Context().SetVar("frontend", t)
		log.Debug("setvar editor")
		engine.Context().SetVar("editor", backend.GetEditor())

		log.Debug("loadfile")
		component, err = engine.LoadFile(qmlMainFile)
		if err != nil {
			return err
		}
		limeViewComponent, err = engine.LoadFile(qmlViewFile)
		return
	}
	if err := newEngine(); err != nil {
		log.Error(err)
	}

	backend.OnNewWindow.Add(func(w *backend.Window) {
		fw := &frontendWindow{bw: w}
		t.windows[w] = fw
		if component != nil {
			fw.launch(&wg, component)
		}
	})

	// TODO: should be done backend side
	if sc, err := textmate.LoadTheme("../packages/TextMate-Themes/Monokai.tmTheme"); err != nil {
		log.Error(err)
	} else {
		scheme = sc
	}

	ed.Init()

	// TODO: setting syntax should be done automaticly in backend and after
	// implementing this we could run Init in a go routine and remove the
	// next two line
	v := ed.ActiveWindow().OpenFile("main.go", 0)
	v.SetSyntaxFile("../packages/go-tmbundle/Syntaxes/Go.tmLanguage")

	defer func() {
		fmt.Println(util.Prof)
	}()

	// The rest of code is related to livereloading qml files
	// TODO: this doesnt work currently
	watch, err := fsnotify.NewWatcher()
	if err != nil {
		log.Error("Unable to create file watcher: %s", err)
		return
	}
	defer watch.Close()
	watch.Watch(".")
	defer watch.RemoveWatch(".")

	reloadRequested := false

	go func() {
		for {
			select {
			case ev := <-watch.Event:
				if ev != nil && strings.HasSuffix(ev.Name, ".qml") && ev.IsModify() && !ev.IsAttrib() {
					reloadRequested = true
					t.Quit()
				}
			}
		}
	}()

	for {
		// Reset reload status
		reloadRequested = false

		log.Debug("Waiting for all windows to close")
		// wg would be the WaitGroup all windows belong to, so first we wait for
		// all windows to close.
		wg.Wait()
		log.Debug("All windows closed. reloadRequest: %v", reloadRequested)
		// then we check if there's a reload request in the pipe
		if !reloadRequested || len(t.windows) == 0 {
			// This would be a genuine exit; all windows closed by the user
			break
		}

		// *We* closed all windows because we want to reload freshly changed qml
		// files.
		for {
			log.Debug("Calling newEngine")
			if err := newEngine(); err != nil {
				// Reset reload status
				reloadRequested = false
				log.Error(err)
				for !reloadRequested {
					// This loop allows us to re-try reloading
					// if there was an error in the file this time,
					// we just loop around again when we receive the next
					// reload request (ie on the next save of the file).
					time.Sleep(time.Second)
				}
				continue
			}
			log.Debug("break")
			break
		}
		log.Debug("re-launching all windows")
		// Succeeded loading the file, re-launch all windows
		for _, v := range t.windows {
			v.launch(&wg, component)
		}
	}

	return
}
<|endoftext|>


<commit_before>package streaming

import (
	"errors"
	"fmt"
	"github.com/alexandre-normand/glukit/app/glukitio"
	"github.com/alexandre-normand/glukit/app/model"
	"time"
)

type GlucoseReadStreamer struct {
	buf []model.GlucoseRead
	wr  glukitio.GlucoseReadBatchWriter
	d   time.Duration
}

const (
	BUFFER_SIZE = 86400
)

// WriteGlucoseRead writes a single GlucoseRead into the buffer.
func (b *GlucoseReadStreamer) WriteGlucoseRead(c model.GlucoseRead) (nn int, g *GlucoseReadStreamer, err error) {
	return b.WriteGlucoseReads([]model.GlucoseRead{c})
}

// WriteGlucoseReads writes the contents of p into the buffer.
// It returns the number of bytes written.
// If nn < len(p), it also returns an error explaining
// why the write is short. p must be sorted by time (oldest to most recent).
func (b *GlucoseReadStreamer) WriteGlucoseReads(p []model.GlucoseRead) (nn int, g *GlucoseReadStreamer, err error) {
	// Special case, we don't have a recorded value yet so we start our
	// buffer with the date of the first element
	var i int
	for j, c := range p {
		t := c.GetTime()

		if len(b.buf) > 0 && t.Sub(b.buf[0].GetTime()) >= b.d {
			var n int
			for _, read := range p[i:j] {
				b.buf = append(b.buf, read)
				n++
			}
			b, err = b.Flush()

			nn += n

			// If the flush resulted in an error, abort the write
			if err != nil {
				return nn, nil, err
			}

			// Move beginning of next batch
			i = j
		}
	}

	var n int
	for _, read := range p[i:] {
		b.buf = append(b.buf, read)
		n++
	}
	nn += n

	g, err = newGlucoseStreamerDuration(b.buf, b.wr, b.d)
	return nn, g, err
}

func newGlucoseStreamerDuration(buf []model.GlucoseRead, wr glukitio.GlucoseReadBatchWriter, bufferDuration time.Duration) (*GlucoseReadStreamer, error) {
	w := new(GlucoseReadStreamer)
	c := make([]model.GlucoseRead, len(buf))
	if copied := copy(c, buf); copied != len(buf) {
		return nil, errors.New(fmt.Sprintf("Failed to create copy of glucose read batch to create new glucose streamer, copied [%d] elements but expected [%d]", copied, len(buf)))
	}
	w.buf = c

	w.wr = wr
	w.d = bufferDuration

	return w, nil
}

// NewGlucoseStreamerDuration returns a new GlucoseReadStreamer whose buffer has the specified size.
func NewGlucoseStreamerDuration(wr glukitio.GlucoseReadBatchWriter, bufferDuration time.Duration) *GlucoseReadStreamer {
	w := new(GlucoseReadStreamer)
	w.buf = []model.GlucoseRead{}

	w.wr = wr
	w.d = bufferDuration

	return w
}

// Flush writes any buffered data to the underlying glukitio.Writer as a batch.
func (b *GlucoseReadStreamer) Flush() (*GlucoseReadStreamer, error) {
	if len(b.buf) > 0 {
		n, err := b.wr.WriteGlucoseReadBatch(b.buf)
		if n < 1 && err == nil {
			err = glukitio.ErrShortWrite
		} else if err != nil {
			return nil, err
		}
	}

	g, err := newGlucoseStreamerDuration([]model.GlucoseRead{}, b.wr, b.d)

	return g, err
}

// Close flushes the buffer and the inner writer to effectively ensure nothing is left
// unwritten
func (b *GlucoseReadStreamer) Close() error {
	_, err := b.Flush()
	if err != nil {
		return err
	}

	return b.wr.Flush()
}

// Buffered returns the number of bytes that have been written into the current buffer.
func (b *GlucoseReadStreamer) Buffered() int {
	return len(b.buf)
}
<commit_msg>Implement Foundation for Functional Style Streamer with Singly Linked List.<commit_after>package streaming

import (
	"errors"
	"fmt"
	"github.com/alexandre-normand/glukit/app/glukitio"
	"github.com/alexandre-normand/glukit/app/model"
	"time"
)

type GlucoseReadStreamer struct {
	next  *model.GlucoseRead
	value *model.GlucoseRead
	wr    glukitio.GlucoseReadBatchWriter
	d     time.Duration
}

const (
	BUFFER_SIZE = 86400
)

// WriteGlucoseRead writes a single GlucoseRead into the buffer.
func (b *GlucoseReadStreamer) WriteGlucoseRead(c model.GlucoseRead) (nn int, g *GlucoseReadStreamer, err error) {
	return b.WriteGlucoseReads([]model.GlucoseRead{c})
}

// WriteGlucoseReads writes the contents of p into the buffer.
// It returns the number of bytes written.
// If nn < len(p), it also returns an error explaining
// why the write is short. p must be sorted by time (oldest to most recent).
func (b *GlucoseReadStreamer) WriteGlucoseReads(p []model.GlucoseRead) (nn int, g *GlucoseReadStreamer, err error) {
	for j, c := range p {
		t := c.GetTime()

		if b.value != nil && t.Sub(b.value.GetTime()) >= b.d {
			err = b.Flush()
		} else {
			b.next = b.value
			b.value = &c
		}
	}

	var n int
	for _, read := range p[i:] {
		b.buf = append(b.buf, read)
		n++
	}
	nn += n

	g, err = newGlucoseStreamerDuration(b.buf, b.wr, b.d)
	return nn, g, err
}

func newGlucoseStreamerDuration(buf []model.GlucoseRead, wr glukitio.GlucoseReadBatchWriter, bufferDuration time.Duration) (*GlucoseReadStreamer, error) {
	w := new(GlucoseReadStreamer)
	c := make([]model.GlucoseRead, len(buf))
	if copied := copy(c, buf); copied != len(buf) {
		return nil, errors.New(fmt.Sprintf("Failed to create copy of glucose read batch to create new glucose streamer, copied [%d] elements but expected [%d]", copied, len(buf)))
	}
	w.buf = c

	w.wr = wr
	w.d = bufferDuration

	return w, nil
}

// NewGlucoseStreamerDuration returns a new GlucoseReadStreamer whose buffer has the specified size.
func NewGlucoseStreamerDuration(wr glukitio.GlucoseReadBatchWriter, bufferDuration time.Duration) *GlucoseReadStreamer {
	w := new(GlucoseReadStreamer)
	w.buf = []model.GlucoseRead{}

	w.wr = wr
	w.d = bufferDuration

	return w
}

// Flush writes any buffered data to the underlying glukitio.Writer as a batch.
func (b *GlucoseReadStreamer) Flush() (*GlucoseReadStreamer, error) {
	if len(b.buf) > 0 {
		n, err := b.wr.WriteGlucoseReadBatch(b.buf)
		if n < 1 && err == nil {
			err = glukitio.ErrShortWrite
		} else if err != nil {
			return nil, err
		}
	}

	g, err := newGlucoseStreamerDuration([]model.GlucoseRead{}, b.wr, b.d)

	return g, err
}

// Close flushes the buffer and the inner writer to effectively ensure nothing is left
// unwritten
func (b *GlucoseReadStreamer) Close() error {
	_, err := b.Flush()
	if err != nil {
		return err
	}

	return b.wr.Flush()
}

// Buffered returns the number of bytes that have been written into the current buffer.
func (b *GlucoseReadStreamer) Buffered() int {
	return len(b.buf)
}
<|endoftext|>


<commit_before>package middleware

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

	"github.com/webx-top/echo"
)

type (
	StaticOptions struct {
		Path   string `json:"path"` //UrlPath
		Root   string `json:"root"`
		Index  string `json:"index"`
		Browse bool   `json:"browse"`
	}
)

func Static(options ...*StaticOptions) echo.MiddlewareFunc {
	return func(next echo.Handler) echo.Handler {
		// Default options
		opts := new(StaticOptions)
		if len(options) > 0 {
			opts = options[0]
		}
		if opts.Index == "" {
			opts.Index = "index.html"
		}

		opts.Root, _ = filepath.Abs(opts.Root)
		length := len(opts.Path)

		return echo.HandlerFunc(func(c echo.Context) error {
			file := c.Request().URL().Path()
			if len(file) < length || file[0:length] != opts.Path {
				return next.Handle(c)
			}
			file = filepath.Clean(file[length:])
			absFile := filepath.Join(opts.Root, file)
			if !strings.HasPrefix(absFile, opts.Root) {
				return next.Handle(c)
			}
			fi, err := os.Stat(absFile)
			if err != nil {
				return next.Handle(c)
			}
			w := c.Response()
			if fi.IsDir() {
				// Index file
				indexFile := filepath.Join(absFile, opts.Index)
				fi, err = os.Stat(indexFile)
				if err != nil || fi.IsDir() {
					if opts.Browse {
						fs := http.Dir(opts.Root)
						d, err := fs.Open(file)
						if err != nil {
							return echo.ErrNotFound
						}
						defer d.Close()
						dirs, err := d.Readdir(-1)
						if err != nil {
							return err
						}

						// Create a directory index
						w.Header().Set(echo.ContentType, echo.TextHTMLCharsetUTF8)
						if _, err = fmt.Fprintf(w, "
\n"); err != nil {
							return err
						}
						for _, d := range dirs {
							name := d.Name()
							color := "#212121"
							if d.IsDir() {
								color = "#e91e63"
								name += "/"
							}
							if _, err = fmt.Fprintf(w, "%s\n", name, color, name); err != nil {
								return err
							}
						}
						_, err = fmt.Fprintf(w, "
\n")
						return err
					}
					return next.Handle(c)
				} else {
					absFile = indexFile
				}
			}
			w.ServeFile(absFile)
			return nil
		})
	}
}

// Favicon serves the default favicon - GET /favicon.ico.
func Favicon() echo.HandlerFunc {
	return func(c echo.Context) error {
		return nil
	}
}
<commit_msg>improved<commit_after>package middleware

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

	"github.com/webx-top/echo"
)

type (
	StaticOptions struct {
		Path   string `json:"path"` //UrlPath
		Root   string `json:"root"`
		Index  string `json:"index"`
		Browse bool   `json:"browse"`
	}
)

func Static(options ...*StaticOptions) echo.MiddlewareFunc {
	// Default options
	opts := new(StaticOptions)
	if len(options) > 0 {
		opts = options[0]
	}
	if opts.Index == "" {
		opts.Index = "index.html"
	}

	opts.Root, _ = filepath.Abs(opts.Root)
	length := len(opts.Path)

	return func(next echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			file := c.Request().URL().Path()
			if len(file) < length || file[0:length] != opts.Path {
				return next.Handle(c)
			}
			file = filepath.Clean(file[length:])
			absFile := filepath.Join(opts.Root, file)
			if !strings.HasPrefix(absFile, opts.Root) {
				return next.Handle(c)
			}
			fi, err := os.Stat(absFile)
			if err != nil {
				return next.Handle(c)
			}
			w := c.Response()
			if fi.IsDir() {
				// Index file
				indexFile := filepath.Join(absFile, opts.Index)
				fi, err = os.Stat(indexFile)
				if err != nil || fi.IsDir() {
					if opts.Browse {
						fs := http.Dir(opts.Root)
						d, err := fs.Open(file)
						if err != nil {
							return echo.ErrNotFound
						}
						defer d.Close()
						dirs, err := d.Readdir(-1)
						if err != nil {
							return err
						}

						// Create a directory index
						w.Header().Set(echo.ContentType, echo.TextHTMLCharsetUTF8)
						if _, err = fmt.Fprintf(w, "
\n"); err != nil {
							return err
						}
						for _, d := range dirs {
							name := d.Name()
							color := "#212121"
							if d.IsDir() {
								color = "#e91e63"
								name += "/"
							}
							if _, err = fmt.Fprintf(w, "%s\n", name, color, name); err != nil {
								return err
							}
						}
						_, err = fmt.Fprintf(w, "
\n")
						return err
					}
					return next.Handle(c)
				} else {
					absFile = indexFile
				}
			}
			w.ServeFile(absFile)
			return nil
		})
	}
}

// Favicon serves the default favicon - GET /favicon.ico.
func Favicon() echo.HandlerFunc {
	return func(c echo.Context) error {
		return nil
	}
}
<|endoftext|>


<commit_before>package middleware

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

	"github.com/labstack/echo"
)

type (
	// StaticConfig defines the config for static middleware.
	StaticConfig struct {
		// Root directory from where the static content is served.
		// Required.
		Root string `json:"root"`

		// Index file for serving a directory.
		// Optional. Default value "index.html".
		Index string `json:"index"`

		// Enable HTML5 mode by forwarding all not-found requests to root so that
		// SPA (single-page application) can handle the routing.
		HTML5 bool `json:"html5"`

		// Enable directory browsing.
		// Optional. Default value false.
		Browse bool `json:"browse"`
	}
)

var (
	// DefaultStaticConfig is the default static middleware config.
	DefaultStaticConfig = StaticConfig{
		Index:  "index.html",
		HTML5:  true,
		Browse: false,
	}
)

// Static returns a static middleware to serves static content from the provided
// root directory.
func Static(root string) echo.MiddlewareFunc {
	c := DefaultStaticConfig
	c.Root = root
	return StaticWithConfig(c)
}

// StaticWithConfig returns a static middleware from config.
// See `Static()`.
func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
	// Defaults
	if config.Index == "" {
		config.Index = DefaultStaticConfig.Index
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			fs := http.Dir(config.Root)
			p := c.Request().URL().Path()
			if strings.Contains(c.Path(), "*") { // If serving from a group, e.g. `/static*`.
				p = c.P(0)
			}
			file := path.Clean(p)
			f, err := fs.Open(file)
			if err != nil {
				// HTML5 mode
				err = next(c)
				if he, ok := err.(*echo.HTTPError); ok {
					if config.HTML5 && he.Code == http.StatusNotFound {
						file = ""
						f, err = fs.Open(file)
					} else {
						return err
					}
				} else {
					return err
				}
			}
			defer f.Close()
			fi, err := f.Stat()
			if err != nil {
				return err
			}

			if fi.IsDir() {
				/* NOTE:
				Not checking the Last-Modified header as it caches the response `304` when
				changing different directories for the same path.
				*/
				d := f

				// Index file
				file = path.Join(file, config.Index)
				f, err = fs.Open(file)
				if err == nil {
					// Index file
					if fi, err = f.Stat(); err != nil {
						return err
					}
				} else if err != nil && config.Browse {
					dirs, err := d.Readdir(-1)
					if err != nil {
						return err
					}

					// Create a directory index
					res := c.Response()
					res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
					if _, err = fmt.Fprintf(res, "
\n"); err != nil {
						return err
					}
					for _, d := range dirs {
						name := d.Name()
						color := "#212121"
						if d.IsDir() {
							color = "#e91e63"
							name += "/"
						}
						if _, err = fmt.Fprintf(res, "%s\n", name, color, name); err != nil {
							return err
						}
					}
					_, err = fmt.Fprintf(res, "
\n")
					return err
				} else {
					return next(c)
				}
			}
			return c.ServeContent(f, fi.Name(), fi.ModTime())
		}
	}
}
<commit_msg>Default config for static middleware<commit_after>package middleware

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

	"github.com/labstack/echo"
)

type (
	// StaticConfig defines the config for static middleware.
	StaticConfig struct {
		// Root directory from where the static content is served.
		// Required.
		Root string `json:"root"`

		// Index file for serving a directory.
		// Optional. Default value "index.html".
		Index string `json:"index"`

		// Enable HTML5 mode by forwarding all not-found requests to root so that
		// SPA (single-page application) can handle the routing.
		// Optional. Default value false.
		HTML5 bool `json:"html5"`

		// Enable directory browsing.
		// Optional. Default value false.
		Browse bool `json:"browse"`
	}
)

var (
	// DefaultStaticConfig is the default static middleware config.
	DefaultStaticConfig = StaticConfig{
		Index: "index.html",
	}
)

// Static returns a static middleware to serves static content from the provided
// root directory.
func Static(root string) echo.MiddlewareFunc {
	c := DefaultStaticConfig
	c.Root = root
	return StaticWithConfig(c)
}

// StaticWithConfig returns a static middleware from config.
// See `Static()`.
func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
	// Defaults
	if config.Index == "" {
		config.Index = DefaultStaticConfig.Index
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			fs := http.Dir(config.Root)
			p := c.Request().URL().Path()
			if strings.Contains(c.Path(), "*") { // If serving from a group, e.g. `/static*`.
				p = c.P(0)
			}
			file := path.Clean(p)
			f, err := fs.Open(file)
			if err != nil {
				// HTML5 mode
				err = next(c)
				if he, ok := err.(*echo.HTTPError); ok {
					if config.HTML5 && he.Code == http.StatusNotFound {
						file = ""
						f, err = fs.Open(file)
					} else {
						return err
					}
				} else {
					return err
				}
			}
			defer f.Close()
			fi, err := f.Stat()
			if err != nil {
				return err
			}

			if fi.IsDir() {
				/* NOTE:
				Not checking the Last-Modified header as it caches the response `304` when
				changing different directories for the same path.
				*/
				d := f

				// Index file
				file = path.Join(file, config.Index)
				f, err = fs.Open(file)
				if err == nil {
					// Index file
					if fi, err = f.Stat(); err != nil {
						return err
					}
				} else if err != nil && config.Browse {
					dirs, err := d.Readdir(-1)
					if err != nil {
						return err
					}

					// Create a directory index
					res := c.Response()
					res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
					if _, err = fmt.Fprintf(res, "
\n"); err != nil {
						return err
					}
					for _, d := range dirs {
						name := d.Name()
						color := "#212121"
						if d.IsDir() {
							color = "#e91e63"
							name += "/"
						}
						if _, err = fmt.Fprintf(res, "%s\n", name, color, name); err != nil {
							return err
						}
					}
					_, err = fmt.Fprintf(res, "
\n")
					return err
				} else {
					return next(c)
				}
			}
			return c.ServeContent(f, fi.Name(), fi.ModTime())
		}
	}
}
<|endoftext|>


<commit_before>package otp

import "strings"

func newKey(method, label, secret, issuer string, algo Hash, digits, period, counter int) (*Key, error) {

	k := Key{
		Method:   method,
		Label:    label,
		Secret32: strings.ToUpper(secret),
		Issuer:   issuer,
		Algo:     algo,
		Digits:   digits,
		Period:   period,
		Counter:  counter,
	}

	if err := k.Validate(); err != nil {
		return &k, err
	}

	return &k, nil
}

// NewTOTPKey returns a totp key struct.
func NewTOTPKey(label, secret32, issuer string, algo Hash, digits, period int) (*Key, error) {
	k, err := newKey("totp", label, secret32, issuer, algo, digits, period, 0)
	return k, err
}

// NewHOTPKey returns a hotp key struct.
func NewHOTPKey(label, secret32, issuer string, algo Hash, digits, counter int) (*Key, error) {
	k, err := newKey("hotp", label, secret32, issuer, algo, digits, 0, counter)
	return k, err
}

// NewKey returns a new Key from an otpauth URI.
func NewKey(uri string) (*Key, error) {
	k := Key{}
	if err := k.FromURI(uri); err != nil {
		return &k, err
	}

	if err := k.Validate(); err != nil {
		return &k, err
	}

	return &k, nil
}
<commit_msg>testing docs<commit_after>package otp

import "strings"

func newKey(method, label, secret, issuer string, algo Hash, digits, period, counter int) (*Key, error) {

	k := Key{
		Method:   method,
		Label:    label,
		Secret32: strings.ToUpper(secret),
		Issuer:   issuer,
		Algo:     algo,
		Digits:   digits,
		Period:   period,
		Counter:  counter,
	}

	if err := k.Validate(); err != nil {
		return &k, err
	}

	return &k, nil
}

// NewTOTPKey returns a totp key struct.
func NewTOTPKey(
	label,
	secret32,
	issuer string,
	algo Hash,
	digits,
	period int) (*Key, error) {
	k, err := newKey("totp", label, secret32, issuer, algo, digits, period, 0)
	return k, err
}

// NewHOTPKey returns a hotp key struct.
func NewHOTPKey(label, secret32, issuer string, algo Hash, digits, counter int) (*Key, error) {
	k, err := newKey("hotp", label, secret32, issuer, algo, digits, 0, counter)
	return k, err
}

// NewKey returns a new Key from an otpauth URI.
func NewKey(uri string) (*Key, error) {
	k := Key{}
	if err := k.FromURI(uri); err != nil {
		return &k, err
	}

	if err := k.Validate(); err != nil {
		return &k, err
	}

	return &k, nil
}
<|endoftext|>


<commit_before>package irckit

import (
	"fmt"
	"net"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterircd/bridge"
	"github.com/desertbit/timer"
	"github.com/sorcix/irc"
	"github.com/spf13/viper"
)

// NewUser creates a *User, wrapping a connection with metadata we need for our server.
func NewUser(c Conn) *User {
	return &User{
		Conn: c,
		UserInfo: &bridge.UserInfo{
			Host: "*",
		},
		channels: map[Channel]struct{}{},
		DecodeCh: make(chan *irc.Message),
	}
}

// NewUserNet creates a *User from a net.Conn connection.
func NewUserNet(c net.Conn) *User {
	return NewUser(&conn{
		Conn:    c,
		Encoder: irc.NewEncoder(c),
		Decoder: irc.NewDecoder(c),
	})
}

const defaultCloseMsg = "Closed."

type User struct {
	Conn

	sync.RWMutex
	*bridge.UserInfo

	BufferedMsg *irc.Message
	DecodeCh    chan *irc.Message

	channels map[Channel]struct{}

	v *viper.Viper

	UserBridge
}

func (u *User) ID() string {
	// return strings.ToLower(u.Nick)
	return strings.ToLower(u.User)
}

func (u *User) Prefix() *irc.Prefix {
	return &irc.Prefix{
		Name: u.Nick,
		User: u.User,
		Host: u.Host,
	}
}

func (u *User) Close() error {
	for ch := range u.channels {
		ch.Part(u, defaultCloseMsg)
	}
	return u.Conn.Close()
}

func (u *User) String() string {
	return u.Prefix().String()
}

func (u *User) NumChannels() int {
	u.RLock()
	defer u.RUnlock()
	return len(u.channels)
}

func (u *User) Channels() []Channel {
	u.RLock()
	channels := make([]Channel, 0, len(u.channels))
	for ch := range u.channels {
		channels = append(channels, ch)
	}
	u.RUnlock()
	return channels
}

func (u *User) VisibleTo() []*User {
	seen := map[*User]struct{}{}
	seen[u] = struct{}{}

	channels := u.Channels()
	num := 0
	for _, ch := range channels {
		// Don't include self
		num += ch.Len()
	}

	// Pre-allocate
	users := make([]*User, 0, num)
	if num == 0 {
		return users
	}

	// Get all unique users
	for _, ch := range channels {
		for _, other := range ch.Users() {
			if _, dupe := seen[other]; dupe {
				continue
			}
			seen[other] = struct{}{}
			// TODO: Check visibility (once it's implemented)
			users = append(users, other)
		}
	}
	return users
}

// Encode and send each msg until an error occurs, then returns.
func (u *User) Encode(msgs ...*irc.Message) (err error) {
	if u.Ghost {
		return nil
	}

	for _, msg := range msgs {
		if msg.Command == "PRIVMSG" && (msg.Prefix.Name == "slack" || msg.Prefix.Name == "mattermost") && msg.Prefix.Host == "service" && strings.Contains(msg.Trailing, "token") {
			logger.Debugf("-> %s %s %s", msg.Command, msg.Prefix.Name, "[token redacted]")

			err := u.Conn.Encode(msg)
			if err != nil {
				return err
			}

			continue
		}

		logger.Debugf("-> %s", msg)

		err := u.Conn.Encode(msg)
		if err != nil {
			return err
		}
	}

	return nil
}

// Decode will receive and return a decoded message, or an error.
// nolint:funlen,gocognit,gocyclo
func (u *User) Decode() {
	if u.Ghost {
		// block
		c := make(chan struct{})
		<-c
	}
	buffer := make(chan *irc.Message)
	stop := make(chan struct{})
	bufferTimeout := u.v.GetInt("PasteBufferTimeout")
	// we need at least 100
	if bufferTimeout < 100 {
		bufferTimeout = 100
	}
	logger.Debugf("using paste buffer timeout: %#v\n", bufferTimeout)
	t := timer.NewTimer(time.Duration(bufferTimeout) * time.Millisecond)
	t.Stop()
	go func(buffer chan *irc.Message, stop chan struct{}) {
		for {
			select {
			case msg := <-buffer:
				// are we starting a new buffer ?
				if u.BufferedMsg == nil {
					u.BufferedMsg = msg
					// start timer now
					t.Reset(time.Duration(bufferTimeout) * time.Millisecond)
				} else {
					re := regexp.MustCompile(`^(?:\@\@|s/)(?:[0-9a-f]{3}|[0-9a-z]{26}|!!)|/`)
					if strings.HasPrefix(msg.Trailing, "\x01ACTION") || re.MatchString(msg.Trailing) {
						// flush buffer
						logger.Debug("flushing buffer because of /me, replies to threads, and message modifications")
						u.BufferedMsg.Trailing = strings.TrimSpace(u.BufferedMsg.Trailing)
						u.DecodeCh <- u.BufferedMsg
						u.BufferedMsg = nil
						// send CTCP message
						u.DecodeCh <- msg
						continue
					}
					// make sure we're sending to the same recipient in the buffer
					if u.BufferedMsg.Params[0] == msg.Params[0] {
						u.BufferedMsg.Trailing += "\n" + msg.Trailing
					} else {
						u.DecodeCh <- msg
					}
				}
			case <-t.C:
				if u.BufferedMsg != nil {
					// trim last newline
					u.BufferedMsg.Trailing = strings.TrimSpace(u.BufferedMsg.Trailing)
					logger.Debugf("flushing buffer: %#v\n", u.BufferedMsg)
					u.DecodeCh <- u.BufferedMsg
					// clear buffer
					u.BufferedMsg = nil
					t.Stop()
				}
			case <-stop:
				logger.Debug("closing decode()")
				return
			}
		}
	}(buffer, stop)
	for {
		msg, err := u.Conn.Decode()
		if err != nil {
			close(stop)
			if err.Error() != "EOF" {
				logger.Errorf("msg: %s err: %s", msg, err)
			}
			break
		}

		if msg == nil {
			continue
		}

		dmsg := fmt.Sprintf("<- %s", msg)
		if msg.Command == "PRIVMSG" && msg.Params != nil && (msg.Params[0] == "slack" || msg.Params[0] == "mattermost") {
			// Don't log sensitive information
			trail := strings.Split(msg.Trailing, " ")
			if (msg.Trailing != "" && trail[0] == "login") || (len(msg.Params) > 1 && msg.Params[1] == "login") {
				dmsg = fmt.Sprintf("<- PRIVMSG %s :login [redacted]", msg.Params[0])
			}
		}
		// PRIVMSG can be buffered
		if msg.Command == "PRIVMSG" {
			logger.Debugf("B: %#v\n", dmsg)
			buffer <- msg
		} else {
			logger.Debug(dmsg)
			u.DecodeCh <- msg
		}
	}
}

func (u *User) createService(nick string, what string) {
	u.CreateUserFromInfo(
		&bridge.UserInfo{
			Nick:  nick,
			User:  nick,
			Real:  what,
			Host:  "service",
			Ghost: true,
		})
}
<commit_msg>Fix buffer flushing (#398)<commit_after>package irckit

import (
	"fmt"
	"net"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterircd/bridge"
	"github.com/desertbit/timer"
	"github.com/sorcix/irc"
	"github.com/spf13/viper"
)

// NewUser creates a *User, wrapping a connection with metadata we need for our server.
func NewUser(c Conn) *User {
	return &User{
		Conn: c,
		UserInfo: &bridge.UserInfo{
			Host: "*",
		},
		channels: map[Channel]struct{}{},
		DecodeCh: make(chan *irc.Message),
	}
}

// NewUserNet creates a *User from a net.Conn connection.
func NewUserNet(c net.Conn) *User {
	return NewUser(&conn{
		Conn:    c,
		Encoder: irc.NewEncoder(c),
		Decoder: irc.NewDecoder(c),
	})
}

const defaultCloseMsg = "Closed."

type User struct {
	Conn

	sync.RWMutex
	*bridge.UserInfo

	BufferedMsg *irc.Message
	DecodeCh    chan *irc.Message

	channels map[Channel]struct{}

	v *viper.Viper

	UserBridge
}

func (u *User) ID() string {
	// return strings.ToLower(u.Nick)
	return strings.ToLower(u.User)
}

func (u *User) Prefix() *irc.Prefix {
	return &irc.Prefix{
		Name: u.Nick,
		User: u.User,
		Host: u.Host,
	}
}

func (u *User) Close() error {
	for ch := range u.channels {
		ch.Part(u, defaultCloseMsg)
	}
	return u.Conn.Close()
}

func (u *User) String() string {
	return u.Prefix().String()
}

func (u *User) NumChannels() int {
	u.RLock()
	defer u.RUnlock()
	return len(u.channels)
}

func (u *User) Channels() []Channel {
	u.RLock()
	channels := make([]Channel, 0, len(u.channels))
	for ch := range u.channels {
		channels = append(channels, ch)
	}
	u.RUnlock()
	return channels
}

func (u *User) VisibleTo() []*User {
	seen := map[*User]struct{}{}
	seen[u] = struct{}{}

	channels := u.Channels()
	num := 0
	for _, ch := range channels {
		// Don't include self
		num += ch.Len()
	}

	// Pre-allocate
	users := make([]*User, 0, num)
	if num == 0 {
		return users
	}

	// Get all unique users
	for _, ch := range channels {
		for _, other := range ch.Users() {
			if _, dupe := seen[other]; dupe {
				continue
			}
			seen[other] = struct{}{}
			// TODO: Check visibility (once it's implemented)
			users = append(users, other)
		}
	}
	return users
}

// Encode and send each msg until an error occurs, then returns.
func (u *User) Encode(msgs ...*irc.Message) (err error) {
	if u.Ghost {
		return nil
	}

	for _, msg := range msgs {
		if msg.Command == "PRIVMSG" && (msg.Prefix.Name == "slack" || msg.Prefix.Name == "mattermost") && msg.Prefix.Host == "service" && strings.Contains(msg.Trailing, "token") {
			logger.Debugf("-> %s %s %s", msg.Command, msg.Prefix.Name, "[token redacted]")

			err := u.Conn.Encode(msg)
			if err != nil {
				return err
			}

			continue
		}

		logger.Debugf("-> %s", msg)

		err := u.Conn.Encode(msg)
		if err != nil {
			return err
		}
	}

	return nil
}

// Decode will receive and return a decoded message, or an error.
// nolint:funlen,gocognit,gocyclo
func (u *User) Decode() {
	if u.Ghost {
		// block
		c := make(chan struct{})
		<-c
	}
	buffer := make(chan *irc.Message)
	stop := make(chan struct{})
	bufferTimeout := u.v.GetInt("PasteBufferTimeout")
	// we need at least 100
	if bufferTimeout < 100 {
		bufferTimeout = 100
	}
	logger.Debugf("using paste buffer timeout: %#v\n", bufferTimeout)
	t := timer.NewTimer(time.Duration(bufferTimeout) * time.Millisecond)
	t.Stop()
	go func(buffer chan *irc.Message, stop chan struct{}) {
		for {
			select {
			case msg := <-buffer:
				// are we starting a new buffer ?
				if u.BufferedMsg == nil {
					u.BufferedMsg = msg
					// start timer now
					t.Reset(time.Duration(bufferTimeout) * time.Millisecond)
				} else {
					re := regexp.MustCompile(`^(?:\@\@|s/)(?:[0-9a-f]{3}|[0-9a-z]{26}|!!)|^/`)
					if strings.HasPrefix(msg.Trailing, "\x01ACTION") || re.MatchString(msg.Trailing) {
						// flush buffer
						logger.Debug("flushing buffer because of /me, replies to threads, and message modifications")
						u.BufferedMsg.Trailing = strings.TrimSpace(u.BufferedMsg.Trailing)
						u.DecodeCh <- u.BufferedMsg
						u.BufferedMsg = nil
						// send CTCP message
						u.DecodeCh <- msg
						continue
					}
					// make sure we're sending to the same recipient in the buffer
					if u.BufferedMsg.Params[0] == msg.Params[0] {
						u.BufferedMsg.Trailing += "\n" + msg.Trailing
					} else {
						u.DecodeCh <- msg
					}
				}
			case <-t.C:
				if u.BufferedMsg != nil {
					// trim last newline
					u.BufferedMsg.Trailing = strings.TrimSpace(u.BufferedMsg.Trailing)
					logger.Debugf("flushing buffer: %#v\n", u.BufferedMsg)
					u.DecodeCh <- u.BufferedMsg
					// clear buffer
					u.BufferedMsg = nil
					t.Stop()
				}
			case <-stop:
				logger.Debug("closing decode()")
				return
			}
		}
	}(buffer, stop)
	for {
		msg, err := u.Conn.Decode()
		if err != nil {
			close(stop)
			if err.Error() != "EOF" {
				logger.Errorf("msg: %s err: %s", msg, err)
			}
			break
		}

		if msg == nil {
			continue
		}

		dmsg := fmt.Sprintf("<- %s", msg)
		if msg.Command == "PRIVMSG" && msg.Params != nil && (msg.Params[0] == "slack" || msg.Params[0] == "mattermost") {
			// Don't log sensitive information
			trail := strings.Split(msg.Trailing, " ")
			if (msg.Trailing != "" && trail[0] == "login") || (len(msg.Params) > 1 && msg.Params[1] == "login") {
				dmsg = fmt.Sprintf("<- PRIVMSG %s :login [redacted]", msg.Params[0])
			}
		}
		// PRIVMSG can be buffered
		if msg.Command == "PRIVMSG" {
			logger.Debugf("B: %#v\n", dmsg)
			buffer <- msg
		} else {
			logger.Debug(dmsg)
			u.DecodeCh <- msg
		}
	}
}

func (u *User) createService(nick string, what string) {
	u.CreateUserFromInfo(
		&bridge.UserInfo{
			Nick:  nick,
			User:  nick,
			Real:  what,
			Host:  "service",
			Ghost: true,
		})
}
<|endoftext|>


<commit_before>package seriesList

import (
	"fmt"
	"math"
	"sort"
	"strconv"

	"github.com/go-graphite/carbonapi/expr/helper"
	"github.com/go-graphite/carbonapi/expr/interfaces"
	"github.com/go-graphite/carbonapi/expr/types"
	"github.com/go-graphite/carbonapi/pkg/parser"
)

type seriesList struct {
	interfaces.FunctionBase
}

func GetOrder() interfaces.Order {
	return interfaces.Any
}

func New(configFile string) []interfaces.FunctionMetadata {
	res := make([]interfaces.FunctionMetadata, 0)
	f := &seriesList{}
	functions := []string{"divideSeriesLists", "diffSeriesLists", "multiplySeriesLists", "powSeriesLists"}
	for _, n := range functions {
		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
	}
	return res
}

func (f *seriesList) Do(e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
	useConstant := false
	useDenom := false

	defaultValue, err := e.GetFloatNamedOrPosArgDefault("default", 3, math.NaN())
	if err != nil {
		return nil, err
	}

	numerators, err := helper.GetSeriesArg(e.Args()[0], from, until, values)
	if err != nil {
		if err == parser.ErrSeriesDoesNotExist && !math.IsNaN(defaultValue) {
			useConstant = true
			useDenom = true
		} else {
			return nil, err
		}
	}
	if len(numerators) == 0 {
		if !math.IsNaN(defaultValue) {
			useConstant = true
			useDenom = true
		} else {
			return nil, nil
		}
	}
	sort.Slice(numerators, func(i, j int) bool { return numerators[i].Name < numerators[j].Name })

	denominators, err := helper.GetSeriesArg(e.Args()[1], from, until, values)
	if err != nil {
		if err == parser.ErrSeriesDoesNotExist && !math.IsNaN(defaultValue) && !useConstant {
			useConstant = true
		} else {
			return nil, err
		}
	}
	if len(denominators) == 0 {
		if !math.IsNaN(defaultValue) && !useConstant {
			useConstant = true
		} else {
			return nil, nil
		}
	}
	sort.Slice(numerators, func(i, j int) bool { return denominators[i].Name < denominators[j].Name })

	sizeMatch := len(denominators) == len(numerators) || len(denominators) == 1
	useMatching, err := e.GetBoolNamedOrPosArgDefault("matching", 2, !useConstant && !sizeMatch)
	if err != nil {
		return nil, err
	}

	var results []*types.MetricData
	functionName := e.Target()[:len(e.Target())-len("Lists")]

	var compute func(l, r float64) float64

	switch e.Target() {
	case "divideSeriesLists":
		compute = func(l, r float64) float64 { return l / r }
	case "multiplySeriesLists":
		compute = func(l, r float64) float64 { return l * r }
	case "diffSeriesLists":
		compute = func(l, r float64) float64 { return l - r }
	case "powSeriesLists":
		compute = func(l, r float64) float64 { return math.Pow(l, r) }
	}

	if useConstant {
		var single []*types.MetricData
		if useDenom {
			single = denominators
		} else {
			single = numerators
		}
		for _, s := range single {
			r := *s
			r.Name = fmt.Sprintf("%s(%s,%s)", functionName, s.Name, s.Name)
			r.Values = make([]float64, len(s.Values))
			for i, v := range s.Values {
				if math.IsNaN(v) {
					r.Values[i] = math.NaN()
					continue
				}

				if e.Target() == "divideSeriesLists" {
					if (useDenom && v == 0) || (!useDenom && defaultValue == 0) {
						r.Values[i] = math.NaN()
						continue
					}
				}
				if useDenom {
					r.Values[i] = compute(defaultValue, v)
				} else {
					r.Values[i] = compute(v, defaultValue)
				}

			}
			results = append(results, &r)
		}
		return results, nil
	}

	var denomMap map[string]*types.MetricData
	if useMatching {
		denomMap = make(map[string]*types.MetricData, len(denominators))
		for _, s := range denominators {
			denomMap[s.Name] = s
		}
	}

	var denominator *types.MetricData

	for i, numerator := range numerators {
		pairFound := false
		if useMatching {
			denominator, pairFound = denomMap[numerator.Name]
			if !pairFound && math.IsNaN(defaultValue) {
				continue
			}
		} else {
			pairFound = true
			if len(denominators) == 1 {
				denominator = denominators[0]
			} else {
				denominator = denominators[i]
			}
		}
		if pairFound {
			if numerator.StepTime != denominator.StepTime || len(numerator.Values) != len(denominator.Values) {
				return nil, fmt.Errorf("series %s must have the same length as %s", numerator.Name, denominator.Name)
			}
		}
		r := *numerator
		var denomName string
		if pairFound {
			denomName = denominator.Name
		} else {
			denomName = strconv.FormatFloat(defaultValue, 'f', -1, 64)
		}
		r.Name = fmt.Sprintf("%s(%s,%s)", functionName, numerator.Name, denomName)
		r.Values = make([]float64, len(numerator.Values))

		for i, v := range numerator.Values {
			denomIsAbsent := pairFound && math.IsNaN(denominator.Values[i])
			if math.IsNaN(numerator.Values[i]) || denomIsAbsent {
				r.Values[i] = math.NaN()
				continue
			}

			denomValue := defaultValue
			if pairFound {
				denomValue = denominator.Values[i]
			}

			switch e.Target() {
			case "divideSeriesLists":
				if denominator.Values[i] == 0 {
					r.Values[i] = math.NaN()
					continue
				}
				r.Values[i] = compute(v, denomValue)
			default:
				r.Values[i] = compute(v, denomValue)
			}
		}
		results = append(results, &r)
	}
	return results, nil
}

// Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
func (f *seriesList) Description() map[string]types.FunctionDescription {
	return map[string]types.FunctionDescription{
		"divideSeriesLists": {
			Description: "Iterates over a two lists and divides list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
			Function:    "divideSeriesLists(dividendSeriesList, divisorSeriesList)",
			Group:       "Combine",
			Module:      "graphite.render.functions",
			Name:        "divideSeriesLists",
			Params: []types.FunctionParam{
				{
					Name:     "dividendSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "divisorSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "default",
					Required: false,
					Type:     types.Float,
				},
			},
		},
		"diffSeriesLists": {
			Description: "Iterates over a two lists and substracts list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
			Function:    "diffSeriesLists(firstSeriesList, secondSeriesList)",
			Group:       "Combine",
			Module:      "graphite.render.functions.custom",
			Name:        "diffSeriesLists",
			Params: []types.FunctionParam{
				{
					Name:     "firstSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "secondSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "default",
					Required: false,
					Type:     types.Float,
				},
			},
		},
		"multiplySeriesLists": {
			Description: "Iterates over a two lists and multiplies list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
			Function:    "multiplySeriesLists(sourceSeriesList, factorSeriesList)",
			Group:       "Combine",
			Module:      "graphite.render.functions.custom",
			Name:        "multiplySeriesLists",
			Params: []types.FunctionParam{
				{
					Name:     "sourceSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "factorSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "default",
					Required: false,
					Type:     types.Float,
				},
			},
		},
		"powSeriesLists": {
			Description: "Iterates over a two lists and do list1[0} in power of list2[0}, list1[1} in power of  list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
			Function:    "powSeriesLists(sourceSeriesList, factorSeriesList)",
			Group:       "Combine",
			Module:      "graphite.render.functions.custom",
			Name:        "powSeriesLists",
			Params: []types.FunctionParam{
				{
					Name:     "sourceSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "factorSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "default",
					Required: false,
					Type:     types.Float,
				},
			},
		},
	}
}
<commit_msg>fix sorting in seriesLists function<commit_after>package seriesList

import (
	"fmt"
	"math"
	"sort"
	"strconv"

	"github.com/go-graphite/carbonapi/expr/helper"
	"github.com/go-graphite/carbonapi/expr/interfaces"
	"github.com/go-graphite/carbonapi/expr/types"
	"github.com/go-graphite/carbonapi/pkg/parser"
)

type seriesList struct {
	interfaces.FunctionBase
}

func GetOrder() interfaces.Order {
	return interfaces.Any
}

func New(configFile string) []interfaces.FunctionMetadata {
	res := make([]interfaces.FunctionMetadata, 0)
	f := &seriesList{}
	functions := []string{"divideSeriesLists", "diffSeriesLists", "multiplySeriesLists", "powSeriesLists"}
	for _, n := range functions {
		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
	}
	return res
}

func (f *seriesList) Do(e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
	useConstant := false
	useDenom := false

	defaultValue, err := e.GetFloatNamedOrPosArgDefault("default", 3, math.NaN())
	if err != nil {
		return nil, err
	}

	numerators, err := helper.GetSeriesArg(e.Args()[0], from, until, values)
	if err != nil {
		if err == parser.ErrSeriesDoesNotExist && !math.IsNaN(defaultValue) {
			useConstant = true
			useDenom = true
		} else {
			return nil, err
		}
	}
	if len(numerators) == 0 {
		if !math.IsNaN(defaultValue) {
			useConstant = true
			useDenom = true
		} else {
			return nil, nil
		}
	}
	sort.Slice(numerators, func(i, j int) bool { return numerators[i].Name < numerators[j].Name })

	denominators, err := helper.GetSeriesArg(e.Args()[1], from, until, values)
	if err != nil {
		if err == parser.ErrSeriesDoesNotExist && !math.IsNaN(defaultValue) && !useConstant {
			useConstant = true
		} else {
			return nil, err
		}
	}
	if len(denominators) == 0 {
		if !math.IsNaN(defaultValue) && !useConstant {
			useConstant = true
		} else {
			return nil, nil
		}
	}
	sort.Slice(denominators, func(i, j int) bool { return denominators[i].Name < denominators[j].Name })

	sizeMatch := len(denominators) == len(numerators) || len(denominators) == 1
	useMatching, err := e.GetBoolNamedOrPosArgDefault("matching", 2, !useConstant && !sizeMatch)
	if err != nil {
		return nil, err
	}

	var results []*types.MetricData
	functionName := e.Target()[:len(e.Target())-len("Lists")]

	var compute func(l, r float64) float64

	switch e.Target() {
	case "divideSeriesLists":
		compute = func(l, r float64) float64 { return l / r }
	case "multiplySeriesLists":
		compute = func(l, r float64) float64 { return l * r }
	case "diffSeriesLists":
		compute = func(l, r float64) float64 { return l - r }
	case "powSeriesLists":
		compute = func(l, r float64) float64 { return math.Pow(l, r) }
	}

	if useConstant {
		var single []*types.MetricData
		if useDenom {
			single = denominators
		} else {
			single = numerators
		}
		for _, s := range single {
			r := *s
			r.Name = fmt.Sprintf("%s(%s,%s)", functionName, s.Name, s.Name)
			r.Values = make([]float64, len(s.Values))
			for i, v := range s.Values {
				if math.IsNaN(v) {
					r.Values[i] = math.NaN()
					continue
				}

				if e.Target() == "divideSeriesLists" {
					if (useDenom && v == 0) || (!useDenom && defaultValue == 0) {
						r.Values[i] = math.NaN()
						continue
					}
				}
				if useDenom {
					r.Values[i] = compute(defaultValue, v)
				} else {
					r.Values[i] = compute(v, defaultValue)
				}

			}
			results = append(results, &r)
		}
		return results, nil
	}

	var denomMap map[string]*types.MetricData
	if useMatching {
		denomMap = make(map[string]*types.MetricData, len(denominators))
		for _, s := range denominators {
			denomMap[s.Name] = s
		}
	}

	var denominator *types.MetricData

	for i, numerator := range numerators {
		pairFound := false
		if useMatching {
			denominator, pairFound = denomMap[numerator.Name]
			if !pairFound && math.IsNaN(defaultValue) {
				continue
			}
		} else {
			pairFound = true
			if len(denominators) == 1 {
				denominator = denominators[0]
			} else {
				denominator = denominators[i]
			}
		}
		if pairFound {
			if numerator.StepTime != denominator.StepTime || len(numerator.Values) != len(denominator.Values) {
				return nil, fmt.Errorf("series %s must have the same length as %s", numerator.Name, denominator.Name)
			}
		}
		r := *numerator
		var denomName string
		if pairFound {
			denomName = denominator.Name
		} else {
			denomName = strconv.FormatFloat(defaultValue, 'f', -1, 64)
		}
		r.Name = fmt.Sprintf("%s(%s,%s)", functionName, numerator.Name, denomName)
		r.Values = make([]float64, len(numerator.Values))

		for i, v := range numerator.Values {
			denomIsAbsent := pairFound && math.IsNaN(denominator.Values[i])
			if math.IsNaN(numerator.Values[i]) || denomIsAbsent {
				r.Values[i] = math.NaN()
				continue
			}

			denomValue := defaultValue
			if pairFound {
				denomValue = denominator.Values[i]
			}

			switch e.Target() {
			case "divideSeriesLists":
				if denominator.Values[i] == 0 {
					r.Values[i] = math.NaN()
					continue
				}
				r.Values[i] = compute(v, denomValue)
			default:
				r.Values[i] = compute(v, denomValue)
			}
		}
		results = append(results, &r)
	}
	return results, nil
}

// Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
func (f *seriesList) Description() map[string]types.FunctionDescription {
	return map[string]types.FunctionDescription{
		"divideSeriesLists": {
			Description: "Iterates over a two lists and divides list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
			Function:    "divideSeriesLists(dividendSeriesList, divisorSeriesList)",
			Group:       "Combine",
			Module:      "graphite.render.functions",
			Name:        "divideSeriesLists",
			Params: []types.FunctionParam{
				{
					Name:     "dividendSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "divisorSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "default",
					Required: false,
					Type:     types.Float,
				},
			},
		},
		"diffSeriesLists": {
			Description: "Iterates over a two lists and substracts list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
			Function:    "diffSeriesLists(firstSeriesList, secondSeriesList)",
			Group:       "Combine",
			Module:      "graphite.render.functions.custom",
			Name:        "diffSeriesLists",
			Params: []types.FunctionParam{
				{
					Name:     "firstSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "secondSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "default",
					Required: false,
					Type:     types.Float,
				},
			},
		},
		"multiplySeriesLists": {
			Description: "Iterates over a two lists and multiplies list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
			Function:    "multiplySeriesLists(sourceSeriesList, factorSeriesList)",
			Group:       "Combine",
			Module:      "graphite.render.functions.custom",
			Name:        "multiplySeriesLists",
			Params: []types.FunctionParam{
				{
					Name:     "sourceSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "factorSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "default",
					Required: false,
					Type:     types.Float,
				},
			},
		},
		"powSeriesLists": {
			Description: "Iterates over a two lists and do list1[0} in power of list2[0}, list1[1} in power of  list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
			Function:    "powSeriesLists(sourceSeriesList, factorSeriesList)",
			Group:       "Combine",
			Module:      "graphite.render.functions.custom",
			Name:        "powSeriesLists",
			Params: []types.FunctionParam{
				{
					Name:     "sourceSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "factorSeriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "default",
					Required: false,
					Type:     types.Float,
				},
			},
		},
	}
}
<|endoftext|>


<commit_before>package slack

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"

	"github.com/keel-hq/keel/constants"
	"github.com/keel-hq/keel/extension/notification"
	"github.com/keel-hq/keel/types"
	"github.com/keel-hq/keel/version"

	log "github.com/sirupsen/logrus"
)

const timeout = 5 * time.Second

type sender struct {
	slackClient *slack.Client
	channels    []string
	botName     string
}

func init() {
	notification.RegisterSender("slack", &sender{})
}

func (s *sender) Configure(config *notification.Config) (bool, error) {
	var token string
	// Get configuration
	if os.Getenv(constants.EnvSlackToken) != "" {
		token = os.Getenv(constants.EnvSlackToken)
	} else {
		return false, nil
	}
	if os.Getenv(constants.EnvSlackBotName) != "" {
		s.botName = os.Getenv(constants.EnvSlackBotName)
	} else {
		s.botName = "keel"
	}

	if os.Getenv(constants.EnvSlackChannels) != "" {
		channels := os.Getenv(constants.EnvSlackChannels)
		s.channels = strings.Split(channels, ",")
	} else {
		s.channels = []string{"general"}
	}

	s.slackClient = slack.New(token)

	log.WithFields(log.Fields{
		"name":     "slack",
		"channels": s.channels,
	}).Info("extension.notification.slack: sender configured")

	var msg string
	if version.GetKeelVersion().Version != "" {
		msg = fmt.Sprintf("Keel has started. Version: %s", version.GetKeelVersion().Version)
	} else {
		msg = fmt.Sprintf("Keel has started. Revision: %s", version.GetKeelVersion().Revision)
	}

	err := s.Send(types.EventNotification{
		Message:   msg,
		CreatedAt: time.Now(),
		Type:      types.NotificationSystemEvent,
		Level:     types.LevelInfo,
		Channels:  s.channels,
	})
	if err != nil {
		log.WithFields(log.Fields{
			"error":    err,
			"name":     "slack",
			"channels": s.channels,
		}).Error("extension.notification.slack: failed to set greeting message")
	}

	return true, nil
}

func (s *sender) Send(event types.EventNotification) error {
	params := slack.NewPostMessageParameters()
	params.Username = s.botName
	params.IconURL = constants.KeelLogoURL

	attachements := []slack.Attachment{
		slack.Attachment{
			Fallback: event.Message,
			Color:    event.Level.Color(),
			Fields: []slack.AttachmentField{
				slack.AttachmentField{
					Title: event.Type.String(),
					Value: event.Message,
					Short: false,
				},
			},
			Footer: fmt.Sprintf("https://keel.sh %s", version.GetKeelVersion().Version),
			Ts:     json.Number(strconv.Itoa(int(event.CreatedAt.Unix()))),
		},
	}

	chans := s.channels
	if len(event.Channels) > 0 {
		chans = event.Channels
	}

	var mgsOpts []slack.MsgOption

	mgsOpts = append(mgsOpts, slack.MsgOptionPostMessageParameters(params))
	mgsOpts = append(mgsOpts, slack.MsgOptionAttachments(attachements...))

	for _, channel := range chans {
		_, _, err := s.slackClient.PostMessage(channel, mgsOpts...)
		if err != nil {
			log.WithFields(log.Fields{
				"error":   err,
				"channel": channel,
			}).Error("extension.notification.slack: failed to send notification")
		}
	}
	return nil
}
<commit_msg>always show revision too<commit_after>package slack

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"

	"github.com/keel-hq/keel/constants"
	"github.com/keel-hq/keel/extension/notification"
	"github.com/keel-hq/keel/types"
	"github.com/keel-hq/keel/version"

	log "github.com/sirupsen/logrus"
)

const timeout = 5 * time.Second

type sender struct {
	slackClient *slack.Client
	channels    []string
	botName     string
}

func init() {
	notification.RegisterSender("slack", &sender{})
}

func (s *sender) Configure(config *notification.Config) (bool, error) {
	var token string
	// Get configuration
	if os.Getenv(constants.EnvSlackToken) != "" {
		token = os.Getenv(constants.EnvSlackToken)
	} else {
		return false, nil
	}
	if os.Getenv(constants.EnvSlackBotName) != "" {
		s.botName = os.Getenv(constants.EnvSlackBotName)
	} else {
		s.botName = "keel"
	}

	if os.Getenv(constants.EnvSlackChannels) != "" {
		channels := os.Getenv(constants.EnvSlackChannels)
		s.channels = strings.Split(channels, ",")
	} else {
		s.channels = []string{"general"}
	}

	s.slackClient = slack.New(token)

	log.WithFields(log.Fields{
		"name":     "slack",
		"channels": s.channels,
	}).Info("extension.notification.slack: sender configured")

	var msg string
	if version.GetKeelVersion().Version != "" {
		msg = fmt.Sprintf("Keel has started. Version: '%s'. Revision: %s", version.GetKeelVersion().Version, version.GetKeelVersion().Revision)
	} else {
		msg = fmt.Sprintf("Keel has started. Revision: %s", version.GetKeelVersion().Revision)
	}

	err := s.Send(types.EventNotification{
		Message:   msg,
		CreatedAt: time.Now(),
		Type:      types.NotificationSystemEvent,
		Level:     types.LevelInfo,
		Channels:  s.channels,
	})
	if err != nil {
		log.WithFields(log.Fields{
			"error":    err,
			"name":     "slack",
			"channels": s.channels,
		}).Error("extension.notification.slack: failed to set greeting message")
	}

	return true, nil
}

func (s *sender) Send(event types.EventNotification) error {
	params := slack.NewPostMessageParameters()
	params.Username = s.botName
	params.IconURL = constants.KeelLogoURL

	attachements := []slack.Attachment{
		slack.Attachment{
			Fallback: event.Message,
			Color:    event.Level.Color(),
			Fields: []slack.AttachmentField{
				slack.AttachmentField{
					Title: event.Type.String(),
					Value: event.Message,
					Short: false,
				},
			},
			Footer: fmt.Sprintf("https://keel.sh %s", version.GetKeelVersion().Version),
			Ts:     json.Number(strconv.Itoa(int(event.CreatedAt.Unix()))),
		},
	}

	chans := s.channels
	if len(event.Channels) > 0 {
		chans = event.Channels
	}

	var mgsOpts []slack.MsgOption

	mgsOpts = append(mgsOpts, slack.MsgOptionPostMessageParameters(params))
	mgsOpts = append(mgsOpts, slack.MsgOptionAttachments(attachements...))

	for _, channel := range chans {
		_, _, err := s.slackClient.PostMessage(channel, mgsOpts...)
		if err != nil {
			log.WithFields(log.Fields{
				"error":   err,
				"channel": channel,
			}).Error("extension.notification.slack: failed to send notification")
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package navmesh

import (
	"container/heap"
	. "github.com/spate/vectormath"
	"math"
)

import ()

// Triangle Heap
type WeightedTriangle struct {
	id     int32 // triangle id
	weight float32
}

type TriangleHeap struct {
	triangles []WeightedTriangle
}

func (th *TriangleHeap) Len() int {
	return len(th.triangles)
}

func (th *TriangleHeap) Less(i, j int) bool {
	return th.triangles[i].weight < th.triangles[j].weight
}

func (th *TriangleHeap) Swap(i, j int) {
	th.triangles[i], th.triangles[j] = th.triangles[j], th.triangles[i]
}

func (th *TriangleHeap) Push(x interface{}) {
	th.triangles = append(th.triangles, x.(WeightedTriangle))
}

func (th *TriangleHeap) Pop() interface{} {
	n := len(th.triangles)
	x := th.triangles[n-1]
	th.triangles = th.triangles[:n-1]
	return x
}

type Mesh struct {
	Vertices  []Point3   // vertices
	Triangles [][3]int32 // triangles
}

// Dijkstra
type Dijkstra struct {
	Matrix map[int32][]WeightedTriangle // all edge for nodes
}

// create neighbour matrix
func (d *Dijkstra) CreateMatrixFromMesh(mesh Mesh) {
	d.Matrix = make(map[int32][]WeightedTriangle)
	for i := 0; i < len(mesh.Triangles); i++ {
		for j := 0; j < len(mesh.Triangles); j++ {
			if i == j {
				continue
			}

			if len(intersect(mesh.Triangles[i], mesh.Triangles[j])) == 2 {
				x1 := (mesh.Vertices[mesh.Triangles[i][0]].X + mesh.Vertices[mesh.Triangles[i][1]].X + mesh.Vertices[mesh.Triangles[i][2]].X) / 3.0
				y1 := (mesh.Vertices[mesh.Triangles[i][0]].Y + mesh.Vertices[mesh.Triangles[i][1]].Y + mesh.Vertices[mesh.Triangles[i][2]].Y) / 3.0
				x2 := (mesh.Vertices[mesh.Triangles[j][0]].X + mesh.Vertices[mesh.Triangles[j][1]].X + mesh.Vertices[mesh.Triangles[j][2]].X) / 3.0
				y2 := (mesh.Vertices[mesh.Triangles[j][0]].Y + mesh.Vertices[mesh.Triangles[j][1]].Y + mesh.Vertices[mesh.Triangles[j][2]].Y) / 3.0
				weight := float32(math.Sqrt(float64((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))))
				d.Matrix[int32(i)] = append(d.Matrix[int32(i)], WeightedTriangle{int32(j), weight})
			}
		}
	}
}

func intersect(a [3]int32, b [3]int32) []int32 {
	var inter []int32
	for i := range a {
		for j := range b {
			if a[i] == b[j] {
				inter = append(inter, a[i])
			}
		}
	}
	return inter
}

func (d *Dijkstra) Run(src_id int32) map[int32]int32 {
	// triangle heap
	h := &TriangleHeap{}
	heap.Init(h)

	// min distance records
	dist := make(map[int32]float32)

	// previous map
	prev := make(map[int32]int32)

	// visit map
	visited := make(map[int32]bool)

	// set initial distance to each node as MaxFloat32
	for k := range d.Matrix {
		dist[k] = math.MaxFloat32
	}

	// source vertex, the first vertex in Heap
	heap.Push(h, WeightedTriangle{src_id, 0})
	dist[src_id] = 0.0

	for h.Len() > 0 { // for every un-visited vertex, try relaxing the path
		// pop the min element
		cur := h.Pop().(WeightedTriangle)
		if visited[cur.id] {
			continue
		}
		// current known shortest distance to u
		dist_u := dist[cur.id]
		// mark the vertex as visited.
		visited[cur.id] = true

		// for each neighbor v of u:
		for _, v := range d.Matrix[cur.id] {
			alt := dist_u + v.weight
			//if alt < dist[v.id] && !visited[v.id] {
			if alt < dist[v.id] {
				dist[v.id] = alt
				prev[v.id] = cur.id
				heap.Push(h, WeightedTriangle{v.id, alt})
			}
		}
	}
	return prev
}
<commit_msg>update<commit_after>package navmesh

import (
	"container/heap"
	. "github.com/spate/vectormath"
	"math"
)

import ()

// Triangle Heap
type WeightedTriangle struct {
	id     int32 // triangle id
	weight float32
}

type TriangleHeap struct {
	triangles []WeightedTriangle
}

func (th *TriangleHeap) Len() int {
	return len(th.triangles)
}

func (th *TriangleHeap) Less(i, j int) bool {
	return th.triangles[i].weight < th.triangles[j].weight
}

func (th *TriangleHeap) Swap(i, j int) {
	th.triangles[i], th.triangles[j] = th.triangles[j], th.triangles[i]
}

func (th *TriangleHeap) Push(x interface{}) {
	th.triangles = append(th.triangles, x.(WeightedTriangle))
}

func (th *TriangleHeap) Pop() interface{} {
	n := len(th.triangles)
	x := th.triangles[n-1]
	th.triangles = th.triangles[:n-1]
	return x
}

type Mesh struct {
	Vertices  []Point3   // vertices
	Triangles [][3]int32 // triangles
}

// Dijkstra
type Dijkstra struct {
	Matrix map[int32][]WeightedTriangle // all edge for nodes
}

// create neighbour matrix
func (d *Dijkstra) CreateMatrixFromMesh(mesh Mesh) {
	d.Matrix = make(map[int32][]WeightedTriangle)
	for i := 0; i < len(mesh.Triangles); i++ {
		for j := 0; j < len(mesh.Triangles); j++ {
			if i == j {
				continue
			}

			if len(intersect(mesh.Triangles[i], mesh.Triangles[j])) == 2 {
				x1 := (mesh.Vertices[mesh.Triangles[i][0]].X + mesh.Vertices[mesh.Triangles[i][1]].X + mesh.Vertices[mesh.Triangles[i][2]].X) / 3.0
				y1 := (mesh.Vertices[mesh.Triangles[i][0]].Y + mesh.Vertices[mesh.Triangles[i][1]].Y + mesh.Vertices[mesh.Triangles[i][2]].Y) / 3.0
				x2 := (mesh.Vertices[mesh.Triangles[j][0]].X + mesh.Vertices[mesh.Triangles[j][1]].X + mesh.Vertices[mesh.Triangles[j][2]].X) / 3.0
				y2 := (mesh.Vertices[mesh.Triangles[j][0]].Y + mesh.Vertices[mesh.Triangles[j][1]].Y + mesh.Vertices[mesh.Triangles[j][2]].Y) / 3.0
				weight := float32(math.Sqrt(float64((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))))
				d.Matrix[int32(i)] = append(d.Matrix[int32(i)], WeightedTriangle{int32(j), weight})
			}
		}
	}
}

func intersect(a [3]int32, b [3]int32) []int32 {
	var inter []int32
	for i := range a {
		for j := range b {
			if a[i] == b[j] {
				inter = append(inter, a[i])
			}
		}
	}
	return inter
}

func (d *Dijkstra) Run(src_id int32) map[int32]int32 {
	// triangle heap
	h := &TriangleHeap{}
	heap.Init(h)

	// min distance records
	dist := make(map[int32]float32)

	// previous map
	prev := make(map[int32]int32)

	// visit map
	visited := make(map[int32]bool)

	// set initial distance to each node as MaxFloat32
	for k := range d.Matrix {
		dist[k] = math.MaxFloat32
	}

	// source vertex, the first vertex in Heap
	heap.Push(h, WeightedTriangle{src_id, 0})
	dist[src_id] = 0.0

	for h.Len() > 0 { // for every un-visited vertex, try relaxing the path
		// pop the min element
		cur := h.Pop().(WeightedTriangle)
		if visited[cur.id] {
			continue
		}
		// current known shortest distance to u
		dist_u := dist[cur.id]
		// mark the vertex as visited.
		visited[cur.id] = true

		// for each neighbor v of u:
		for _, v := range d.Matrix[cur.id] {
			alt := dist_u + v.weight
			if alt < dist[v.id] {
				dist[v.id] = alt
				prev[v.id] = cur.id
				heap.Push(h, WeightedTriangle{v.id, alt})
			}
		}
	}
	return prev
}
<|endoftext|>


<commit_before>package director

import (
	// "fmt"
	"time"
)

const (
	FOREVER = -1
	ONCE    = 1
)

// A Looper is used in place of a direct call to "for {}" and implements some
// controls over how the loop will be run. The Loop() function is the main
// call used by dependant routines. Common patterns like Quit and Done
// channels are easily implemented in a Looper.
type Looper interface {
	Loop(fn func() error)
	Wait() error
	Done(err error)
	Quit()
}

// A TimedLooper is a Looper that runs on a timed schedule, using a Timer
// underneath. It also implements Quit and Done channels to allow external
// routines to more easily control and synchronize the loop.
//
// If you pass in a DoneChan at creation time, it will send a true on the
// channel when the loop has completed. If you pass in a quitChan it can
// be used to tell the loop to exit on completion of the next iteration.
// Using both Done and Quit channels allows full external control of the
// loop.
type TimedLooper struct {
	Count     int
	Interval  time.Duration
	DoneChan  chan error
	quitChan  chan bool
	Immediate bool
}

// Same as a TimedLooper, except it will execute an iteration of the loop
// immediately after calling on Loop() (as opposed to waiting until the tick)
func NewTimedLooper(count int, interval time.Duration, done chan error) *TimedLooper {
	return &TimedLooper{
		Count:     count,
		Interval:  interval,
		DoneChan:  done,
		quitChan:  make(chan bool),
		Immediate: false,
	}
}

func NewImmediateTimedLooper(count int, interval time.Duration, done chan error) *TimedLooper {
	return &TimedLooper{
		Count:     count,
		Interval:  interval,
		DoneChan:  done,
		quitChan:  make(chan bool),
		Immediate: true,
	}
}

func (l *TimedLooper) Wait() error {
	return <-l.DoneChan
}

// Signal a dependant routine that we're done with our work
func (l *TimedLooper) Done(err error) {
	if l.DoneChan != nil {
		l.DoneChan <- err
	}
}

// The main method of the Looper. This call takes a function with a single
// return value, an error. If the error is nil, the Looper will run the
// next iteration. If it's an error, it will not run the next iteration,
// will clean up any internals that need to be, and will invoke done().
func (l *TimedLooper) Loop(fn func() error) {
	i := 0

	var stop bool

	stopFunc := func(err error) {
		l.Done(err)
		stop = true
	}

	runIteration := func() {
		err := fn()
		if err != nil {
			stopFunc(err)
			return
		}

		// We have to make sure not to increment if we started
		// at -1 otherwise we quit on maxint rollover.
		if l.Count != FOREVER {
			i = i + 1
			if i >= l.Count {
				stopFunc(nil)
				return
			}
		}

		select {
		case <-l.quitChan:
			stopFunc(nil)
			return
		default:
		}
	}

	// Immediatelly run our function if we've been instantiated via
	// NewImmediateTimedLooper
	if l.Immediate {
		runIteration()
	}

	ticks := time.Tick(l.Interval)
	for range ticks {
		if stop {
			break
		}

		runIteration()
	}
}

// Quit() signals to the Looper to not run the next iteration and to
// call done() and return as quickly as possible. It is does not
// intervene between iterations.
func (l *TimedLooper) Quit() {
	go func() {
		l.quitChan <- true
	}()
}

// A FreeLooper is like a TimedLooper but doesn't wait between iterations.
type FreeLooper struct {
	Count    int
	DoneChan chan error
	quitChan chan bool
}

func NewFreeLooper(count int, done chan error) *FreeLooper {
	return &FreeLooper{
		Count:    count,
		DoneChan: done,
		quitChan: make(chan bool),
	}
}

func (l *FreeLooper) Wait() error {
	return <-l.DoneChan
}

// This is used internally, but can also be used by controlling routines
// to signal that a job is completed. The FreeLooper doesn's support its
// use outside the internals.
func (l *FreeLooper) Done(err error) {
	if l.DoneChan != nil {
		l.DoneChan <- err
	}
}

func (l *FreeLooper) Loop(fn func() error) {
	i := 0

	for {
		err := fn()
		if err != nil {
			l.Done(err)
			return
		}

		// We have to make sure not to increment if we started
		// at -1 otherwise we quit on maxint rollover.
		if l.Count != FOREVER {
			i = i + 1
			if i >= l.Count {
				l.Done(nil)
				return
			}
		}

		select {
		case <-l.quitChan:
			l.Done(nil)
			return
		default:
		}
	}
}

// Quit() signals to the Looper to not run the next iteration and to
// call Done() and return as quickly as possible. It is does not
// intervene between iterations.
func (l *FreeLooper) Quit() {
	go func() {
		l.quitChan <- true
	}()
}
<commit_msg>Update documentation.<commit_after>package director

import (
	// "fmt"
	"time"
)

const (
	FOREVER = -1
	ONCE    = 1
)

// A Looper is used in place of a direct call to "for {}" and implements some
// controls over how the loop will be run. The Loop() function is the main
// call used by dependant routines. Common patterns like Quit and Done
// channels are easily implemented in a Looper.
type Looper interface {
	Loop(fn func() error)
	Wait() error
	Done(err error)
	Quit()
}

// A TimedLooper is a Looper that runs on a timed schedule, using a Timer
// underneath. It also implements Quit and Done channels to allow external
// routines to more easily control and synchronize the loop.
//
// If you pass in a DoneChan at creation time, it will send a nil on the
// channel when the loop has completed successfully or an error if the loop
// resulted in an error condition.
//
// If you pass in a quitChan it can be used to tell the loop to exit on completion
// of the next iteration.  Using both Done and Quit channels allows full external
// control of the loop.

type TimedLooper struct {
	Count     int
	Interval  time.Duration
	DoneChan  chan error
	quitChan  chan bool
	Immediate bool
}

// Same as a TimedLooper, except it will execute an iteration of the loop
// immediately after calling on Loop() (as opposed to waiting until the tick)
func NewTimedLooper(count int, interval time.Duration, done chan error) *TimedLooper {
	return &TimedLooper{
		Count:     count,
		Interval:  interval,
		DoneChan:  done,
		quitChan:  make(chan bool),
		Immediate: false,
	}
}

func NewImmediateTimedLooper(count int, interval time.Duration, done chan error) *TimedLooper {
	return &TimedLooper{
		Count:     count,
		Interval:  interval,
		DoneChan:  done,
		quitChan:  make(chan bool),
		Immediate: true,
	}
}

func (l *TimedLooper) Wait() error {
	return <-l.DoneChan
}

// Signal a dependant routine that we're done with our work
func (l *TimedLooper) Done(err error) {
	if l.DoneChan != nil {
		l.DoneChan <- err
	}
}

// The main method of the Looper. This call takes a function with a single
// return value, an error. If the error is nil, the Looper will run the
// next iteration. If it's an error, it will not run the next iteration,
// will clean up any internals that need to be, and will invoke done().
func (l *TimedLooper) Loop(fn func() error) {
	i := 0

	var stop bool

	stopFunc := func(err error) {
		l.Done(err)
		stop = true
	}

	runIteration := func() {
		err := fn()
		if err != nil {
			stopFunc(err)
			return
		}

		// We have to make sure not to increment if we started
		// at -1 otherwise we quit on maxint rollover.
		if l.Count != FOREVER {
			i = i + 1
			if i >= l.Count {
				stopFunc(nil)
				return
			}
		}

		select {
		case <-l.quitChan:
			stopFunc(nil)
			return
		default:
		}
	}

	// Immediatelly run our function if we've been instantiated via
	// NewImmediateTimedLooper
	if l.Immediate {
		runIteration()
	}

	ticks := time.Tick(l.Interval)
	for range ticks {
		if stop {
			break
		}

		runIteration()
	}
}

// Quit() signals to the Looper to not run the next iteration and to
// call done() and return as quickly as possible. It is does not
// intervene between iterations.
func (l *TimedLooper) Quit() {
	go func() {
		l.quitChan <- true
	}()
}

// A FreeLooper is like a TimedLooper but doesn't wait between iterations.
type FreeLooper struct {
	Count    int
	DoneChan chan error
	quitChan chan bool
}

func NewFreeLooper(count int, done chan error) *FreeLooper {
	return &FreeLooper{
		Count:    count,
		DoneChan: done,
		quitChan: make(chan bool),
	}
}

func (l *FreeLooper) Wait() error {
	return <-l.DoneChan
}

// This is used internally, but can also be used by controlling routines
// to signal that a job is completed. The FreeLooper doesn's support its
// use outside the internals.
func (l *FreeLooper) Done(err error) {
	if l.DoneChan != nil {
		l.DoneChan <- err
	}
}

func (l *FreeLooper) Loop(fn func() error) {
	i := 0

	for {
		err := fn()
		if err != nil {
			l.Done(err)
			return
		}

		// We have to make sure not to increment if we started
		// at -1 otherwise we quit on maxint rollover.
		if l.Count != FOREVER {
			i = i + 1
			if i >= l.Count {
				l.Done(nil)
				return
			}
		}

		select {
		case <-l.quitChan:
			l.Done(nil)
			return
		default:
		}
	}
}

// Quit() signals to the Looper to not run the next iteration and to
// call Done() and return as quickly as possible. It is does not
// intervene between iterations. It is a non-blocking operation.
func (l *FreeLooper) Quit() {
	go func() {
		l.quitChan <- true
	}()
}
<|endoftext|>