<commit_before>package kiwi

/* Copyright (c) 2016, Alexander I.Grafov aka Axel
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of kvlog nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

ॐ तारे तुत्तारे तुरे स्व

All tests consists of three parts:

- arrange structures and initialize objects for use in tests
- act on testing object
- check and assert on results

These parts separated by empty lines in each test function.
*/

import (
	"bytes"
	"strings"
	"testing"
)

// Test of log with fatal level without a key.
func TestLoggerLevels_LogFatal_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Fatal("The sample message.")

	out.Flush()
	println(output.String())
	if strings.TrimSpace(output.String()) != "level=\"fatal\" \"The sample message.\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with fatal level with a key.
func TestLoggerLevels_LogFatalWKey_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Fatal("msg", "The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "msg=\"The sample message.\" level=\"fatal\"" {
		t.Fail()
	}
}

// Test of log with fatal level without a key.
func TestLoggerLevels_LogCrit_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Crit("The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "\"The sample message.\" level=\"critical\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with fatal level with a key.
func TestLoggerLevels_LogCritWKey_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Crit("msg", "The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "msg=\"The sample message.\" level=\"critical\"" {
		println(output.String())
		t.Fail()
	}
}
<commit_msg>Add more unit tests.<commit_after>package kiwi

/* Copyright (c) 2016, Alexander I.Grafov aka Axel
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of kvlog nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

ॐ तारे तुत्तारे तुरे स्व

All tests consists of three parts:

- arrange structures and initialize objects for use in tests
- act on testing object
- check and assert on results

These parts separated by empty lines in each test function.
*/

import (
	"bytes"
	"strings"
	"testing"
)

// Test of log with fatal level with empty value. Useless but function allow it.
func TestLoggerLevels_LogFatalEmpty_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Fatal()

	out.Flush()
	if strings.TrimSpace(output.String()) != "level=\"fatal\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with fatal level without a key.
func TestLoggerLevels_LogFatal_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Fatal("The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "level=\"fatal\" \"The sample message.\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with fatal level with a key.
func TestLoggerLevels_LogFatalWKey_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Fatal("msg", "The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "msg=\"The sample message.\" level=\"fatal\"" {
		t.Fail()
	}
}

// Test of log with critical level without a key.
func TestLoggerLevels_LogCrit_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Crit("The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "\"The sample message.\" level=\"critical\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with critical level with a key.
func TestLoggerLevels_LogCritWKey_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Crit("msg", "The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "msg=\"The sample message.\" level=\"critical\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with error level without a key.
func TestLoggerLevels_LogError_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Error("The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "\"The sample message.\" level=\"error\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with error level with a key.
func TestLoggerLevels_LogErrorWKey_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Error("msg", "The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "msg=\"The sample message.\" level=\"error\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with warning level without a key.
func TestLoggerLevels_LogWarn_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Warn("The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "\"The sample message.\" level=\"warning\"" {
		println(output.String())
		t.Fail()
	}
}

// Test of log with warning level with a key.
func TestLoggerLevels_LogWarnWKey_Logfmt(t *testing.T) {
	output := bytes.NewBufferString("")
	log := New()
	out := SinkTo(output, UseLogfmt()).Start()
	defer out.Close()

	log.Warn("msg", "The sample message.")

	out.Flush()
	if strings.TrimSpace(output.String()) != "msg=\"The sample message.\" level=\"warning\"" {
		println(output.String())
		t.Fail()
	}
}
<|endoftext|>


<commit_before><commit_msg>doc formatting<commit_after><|endoftext|>


<commit_before>// Copyright 2010  The "go-linoise" Authors
//
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE file.
//
// This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License
// for more details.

package linoise

import (
	"bufio"
	"fmt"
	"os"
	"strings"
	"utf8"

	"github.com/kless/go-term/term"
)

// Values by default
var (
	Input  *os.File = os.Stdin
	Output *os.File = os.Stdout
	PS1    = "linoise$ "
	PS2    = "> "
)

// ASCII codes
const (
	_ESC       = 27 // Escape: Ctrl-[ (033 in octal)
	_L_BRACKET = 91 // Left square bracket: [
)

// ANSI terminal escape controls
var (
	cursorToleft   = []byte("\033[0G")        // Cursor to left edge.
	delScreen      = []byte("\033[2J")        // Erase the screen.
	toleftDelRight = []byte("\033[0K\033[0G") // Cursor to left; erase to right.
)

var (
	newLine = []byte{'\n'}
	ctrlC   = []int("^C")
	ctrlD   = []int("^D")
)

// === Type
// ===

// Represents a line.
type Line struct {
	//	isQuestion bool
	useHistory bool
	ps1Len     int      // Primary prompt size
	ps1        string   // Primary prompt
	ps2        string   // Command continuations
	*buffer             // Text buffer
	hist       *history // History file
}

// Gets a line type using the given prompt as primary.
func NewLinePrompt(prompt string, hist *history) *Line {
	term.MakeRaw()

	return &Line{
		//		false,
		hasHistory(hist),
		len(prompt),
		prompt,
		PS2,
		newBuffer(),
		hist,
	}
}

// Gets a line type using the primary prompt by default.
func NewLine(hist *history) *Line {
	term.MakeRaw()

	return &Line{
		//		false,
		hasHistory(hist),
		len(PS1),
		PS1,
		PS2,
		newBuffer(),
		hist,
	}
}

// Restores terminal settings.
func (ln *Line) RestoreTerm() {
	term.RestoreTerm()
}

// Tests if it has an history file.
func hasHistory(h *history) bool {
	if h == nil {
		return false
	}
	return true
}

// === Output
// ===

// Returns a slice of the contents of the buffer.
func (ln *Line) toBytes() []byte {
	chars := make([]byte, ln.size*utf8.UTFMax)
	var end, runeLen int

	// === Each character (as integer) is encoded to []byte
	for i := 0; i < ln.size; i++ {
		if i != 0 {
			runeLen = utf8.EncodeRune(ln.data[i], chars[end:])
			end += runeLen
		} else {
			runeLen = utf8.EncodeRune(ln.data[i], chars)
			end = runeLen
		}
	}
	return chars[:end]
}

// Returns the contents of the buffer as a string.
func (ln *Line) toString() string { return string(ln.data[:ln.size]) }

// Prints the primary prompt.
func (ln *Line) prompt() (err os.Error) {
	ln.cursor, ln.size = 0, 0

	if _, err = Output.Write(cursorToleft); err != nil {
		return err
	}
	if _, err = fmt.Fprint(Output, ln.ps1); err != nil {
		return err
	}
	if _, err = Output.Write(toleftDelRight); err != nil {
		return err
	}
	// Move cursor after prompt.
	if _, err = fmt.Fprintf(Output, "\033[%dC", ln.ps1Len); err != nil {
		return err
	}

	return nil
}

// Refreshes the line.
func (ln *Line) refresh() (err os.Error) {
	if _, err = Output.Write(cursorToleft); err != nil {
		return err
	}
	if _, err = fmt.Fprint(Output, ln.ps1); err != nil {
		return err
	}
	if _, err = Output.Write(ln.toBytes()); err != nil {
		return err
	}
	if _, err = Output.Write(toleftDelRight); err != nil {
		return err
	}
	// Move cursor to original position.
	if _, err = fmt.Fprintf(Output, "\033[%dC", ln.ps1Len+ln.cursor); err != nil {
		return err
	}

	return nil
}

// === Get
// ===

func (ln *Line) Read() (line string, err os.Error) {
	var anotherLine []int  // For lines got from history.
	var isHistoryUsed bool // If the history has been accessed.

	in := bufio.NewReader(Input) // Read input.
	seq := make([]byte, 2)       // For escape sequences.
	seq2 := make([]byte, 2)      // Extended escape sequences.

	// Primary prompt.
	if err = ln.prompt(); err != nil {
		return "", err
	}

	for {
		rune, _, err := in.ReadRune()
		if err != nil {
			return "", err
		}

		switch rune {
		default:
			useRefresh, err := ln.Insert(rune)
			if err != nil {
				return "", err
			}

			if useRefresh {
				ln.refresh()
			}
			continue

		case 13: // enter
			line = ln.toString()

			if ln.useHistory {
				ln.hist.Add(line)
			}

			if _, err = Output.Write(newLine); err != nil {
				return "", err
			}
			return strings.TrimSpace(line), nil

		case 127, 8: // backspace, Ctrl-h
			if ln.DeletePrev() {
				goto _refresh
			}
			continue

		case 9: // horizontal tab
			// TODO: disabled by now
			continue

		case 3: // Ctrl-c
			useRefresh, err := ln.InsertRunes(ctrlC)

			if err != nil {
				return "", err
			}
			if useRefresh {
				ln.refresh()
			}

			if _, err = Output.Write(newLine); err != nil {
				return "", err
			}

			goto _deleteLine

		case 4: // Ctrl-d
			useRefresh, err := ln.InsertRunes(ctrlD)

			if err != nil {
				return "", err
			}
			if useRefresh {
				ln.refresh()
			}

			if _, err = Output.Write(newLine); err != nil {
				return "", err
			}
			if _, err = Output.Write(cursorToleft); err != nil {
				return "", err
			}

			return "", ErrCtrlD

		// Escape sequence
		case _ESC:
			if _, err = in.Read(seq); err != nil {
				return "", err
			}
			//fmt.Print(" >", seq) //!!! For DEBUG

			if seq[0] == _L_BRACKET {
				switch seq[1] {
				case 68:
					goto _leftArrow
				case 67:
					goto _rightArrow
				case 65, 66: // Up, Down
					goto _upDownArrow
				}

				// Extended escape.
				if seq[1] > 48 && seq[1] < 55 {
					if _, err = in.Read(seq2); err != nil {
						return "", err
					}
					//fmt.Print(" >>", seq2) //!!! For DEBUG

					// TODO: doesn't works
					if seq[1] == 51 && seq2[0] == 126 { // Delete
						if ln.Delete() {
							goto _refresh
						}
					}
				}
			}
			if seq[0] == 79 {
				switch seq[1] {
				case 72: // Home
					goto _start
				case 70: // End
					goto _end
				}
			}
			continue

		case 20: // Ctrl-t, swap actual character by the previous one.
			if ln.Swap() {
				goto _refresh
			}
			continue

		case 21: // Ctrl+u, delete the whole line.
			goto _deleteLine

		case 11: // Ctrl+k, delete from current to end of line.
			ln.size = ln.cursor
			goto _refresh

		case 1: // Ctrl+a, go to the start of the line.
			goto _start

		case 5: // Ctrl+e, go to the end of the line.
			goto _end

		case 2: // Ctrl-b
			goto _leftArrow

		case 6: // Ctrl-f
			goto _rightArrow

		case 16: // Ctrl-p
			seq[1] = 65
			goto _upDownArrow

		case 14: // Ctrl-n
			seq[1] = 66
			goto _upDownArrow
		}
			continue // To be safe.

	_upDownArrow: // Up and down arrow: history
		if !ln.useHistory {
			continue
		}

		// Up
		if seq[1] == 65 {
			anotherLine, err = ln.hist.Prev()
			// Down
		} else {
			anotherLine, err = ln.hist.Next()
		}
		if err != nil {
			continue
		}

		// Update the current history entry before to overwrite it with
		// the next one.
		// TODO: it has to be removed before of to be saved the history
		if !isHistoryUsed {
			ln.hist.Add(ln.toString())
		}
		isHistoryUsed = true

		ln.grow(len(anotherLine))
		ln.size = len(anotherLine)
		ln.cursor = len(anotherLine)
		copy(ln.data[0:], anotherLine)
		goto _refresh

	_leftArrow:
		if ln.Left() {
			goto _refresh
		}
		continue

	_rightArrow:
		if ln.Right() {
			goto _refresh
		}
		continue

	_start:
		ln.cursor = 0
		goto _refresh

	_end:
		ln.cursor = ln.size
		goto _refresh

	_deleteLine:
		ln.cursor, ln.size = 0, 0
		//goto _refresh

	_refresh:
		ln.refresh()

		continue

	_error:
		return "", err
	}
	return "", nil
}

<commit_msg>Fixed addition of new lines.<commit_after>// Copyright 2010  The "go-linoise" Authors
//
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE file.
//
// This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License
// for more details.

package linoise

import (
	"bufio"
	"fmt"
	"os"
	"strings"
	"utf8"

	"github.com/kless/go-term/term"
)

// Values by default
var (
	Input  *os.File = os.Stdin
	Output *os.File = os.Stdout
	PS1    = "linoise$ "
	PS2    = "> "
)

// ASCII codes
const (
	_ESC       = 27 // Escape: Ctrl-[ (033 in octal)
	_L_BRACKET = 91 // Left square bracket: [
)

// ANSI terminal escape controls
var (
	cursorToleft   = []byte("\033[0G")        // Cursor to left edge.
	delScreen      = []byte("\033[2J")        // Erase the screen.
	toleftDelRight = []byte("\033[0K\033[0G") // Cursor to left; erase to right.
)

var (
	_NL = []byte{'\n'}
	ctrlC   = []int("^C")
	ctrlD   = []int("^D")
)

// === Type
// ===

// Represents a line.
type Line struct {
	//	isQuestion bool
	useHistory bool
	ps1Len     int      // Primary prompt size
	ps1        string   // Primary prompt
	ps2        string   // Command continuations
	*buffer             // Text buffer
	hist       *history // History file
}

// Gets a line type using the given prompt as primary.
func NewLinePrompt(prompt string, hist *history) *Line {
	term.MakeRaw()

	return &Line{
		//		false,
		hasHistory(hist),
		len(prompt),
		prompt,
		PS2,
		newBuffer(),
		hist,
	}
}

// Gets a line type using the primary prompt by default.
func NewLine(hist *history) *Line {
	term.MakeRaw()

	return &Line{
		//		false,
		hasHistory(hist),
		len(PS1),
		PS1,
		PS2,
		newBuffer(),
		hist,
	}
}

// Restores terminal settings.
func (ln *Line) RestoreTerm() {
	term.RestoreTerm()
}

// Tests if it has an history file.
func hasHistory(h *history) bool {
	if h == nil {
		return false
	}
	return true
}

// === Output
// ===

// Returns a slice of the contents of the buffer.
func (ln *Line) toBytes() []byte {
	chars := make([]byte, ln.size*utf8.UTFMax)
	var end, runeLen int

	// === Each character (as integer) is encoded to []byte
	for i := 0; i < ln.size; i++ {
		if i != 0 {
			runeLen = utf8.EncodeRune(ln.data[i], chars[end:])
			end += runeLen
		} else {
			runeLen = utf8.EncodeRune(ln.data[i], chars)
			end = runeLen
		}
	}
	return chars[:end]
}

// Returns the contents of the buffer as a string.
func (ln *Line) toString() string { return string(ln.data[:ln.size]) }

// Prints a new line.
func (ln *Line) newLine() (err os.Error) {
	if _, err = Output.Write(_NL); err != nil {
		return
	}
	if _, err = Output.Write(cursorToleft); err != nil {
		return
	}

	return nil
}

// Prints the primary prompt.
func (ln *Line) prompt() (err os.Error) {
	ln.cursor, ln.size = 0, 0

	if _, err = Output.Write(cursorToleft); err != nil {
		return err
	}
	if _, err = fmt.Fprint(Output, ln.ps1); err != nil {
		return err
	}
	if _, err = Output.Write(toleftDelRight); err != nil {
		return err
	}
	// Move cursor after prompt.
	if _, err = fmt.Fprintf(Output, "\033[%dC", ln.ps1Len); err != nil {
		return err
	}

	return nil
}

// Refreshes the line.
func (ln *Line) refresh() (err os.Error) {
	if _, err = Output.Write(cursorToleft); err != nil {
		return err
	}
	if _, err = fmt.Fprint(Output, ln.ps1); err != nil {
		return err
	}
	if _, err = Output.Write(ln.toBytes()); err != nil {
		return err
	}
	if _, err = Output.Write(toleftDelRight); err != nil {
		return err
	}
	// Move cursor to original position.
	if _, err = fmt.Fprintf(Output, "\033[%dC", ln.ps1Len+ln.cursor); err != nil {
		return err
	}

	return nil
}

// === Get
// ===

func (ln *Line) Read() (line string, err os.Error) {
	var anotherLine []int  // For lines got from history.
	var isHistoryUsed bool // If the history has been accessed.

	in := bufio.NewReader(Input) // Read input.
	seq := make([]byte, 2)       // For escape sequences.
	seq2 := make([]byte, 2)      // Extended escape sequences.

	// Primary prompt.
	if err = ln.prompt(); err != nil {
		return "", err
	}

	for {
		rune, _, err := in.ReadRune()
		if err != nil {
			return "", err
		}

		switch rune {
		default:
			useRefresh, err := ln.Insert(rune)
			if err != nil {
				return "", err
			}

			if useRefresh {
				ln.refresh()
			}
			continue

		case 13: // enter
			line = ln.toString()

			if ln.useHistory {
				ln.hist.Add(line)
			}

			if err = ln.newLine(); err != nil {
				return "", err
			}

			return strings.TrimSpace(line), nil

		case 127, 8: // backspace, Ctrl-h
			if ln.DeletePrev() {
				goto _refresh
			}
			continue

		case 9: // horizontal tab
			// TODO: disabled by now
			continue

		case 3: // Ctrl-c
			useRefresh, err := ln.InsertRunes(ctrlC)

			if err != nil {
				return "", err
			}
			if useRefresh {
				ln.refresh()
			}

			if _, err = Output.Write(_NL); err != nil {
				return "", err
			}
			if err = ln.prompt(); err != nil {
				return "", err
			}

			continue

		case 4: // Ctrl-d
			useRefresh, err := ln.InsertRunes(ctrlD)

			if err != nil {
				return "", err
			}
			if useRefresh {
				ln.refresh()
			}

			if err = ln.newLine(); err != nil {
				return "", err
			}

			return "", ErrCtrlD

		// Escape sequence
		case _ESC:
			if _, err = in.Read(seq); err != nil {
				return "", err
			}
			//fmt.Print(" >", seq) //!!! For DEBUG

			if seq[0] == _L_BRACKET {
				switch seq[1] {
				case 68:
					goto _leftArrow
				case 67:
					goto _rightArrow
				case 65, 66: // Up, Down
					goto _upDownArrow
				}

				// Extended escape.
				if seq[1] > 48 && seq[1] < 55 {
					if _, err = in.Read(seq2); err != nil {
						return "", err
					}
					//fmt.Print(" >>", seq2) //!!! For DEBUG

					// TODO: doesn't works
					if seq[1] == 51 && seq2[0] == 126 { // Delete
						if ln.Delete() {
							goto _refresh
						}
					}
				}
			}
			if seq[0] == 79 {
				switch seq[1] {
				case 72: // Home
					goto _start
				case 70: // End
					goto _end
				}
			}
			continue

		case 20: // Ctrl-t, swap actual character by the previous one.
			if ln.Swap() {
				goto _refresh
			}
			continue

		case 21: // Ctrl+u, delete the whole line.
			goto _deleteLine

		case 11: // Ctrl+k, delete from current to end of line.
			ln.size = ln.cursor
			goto _refresh

		case 1: // Ctrl+a, go to the start of the line.
			goto _start

		case 5: // Ctrl+e, go to the end of the line.
			goto _end

		case 2: // Ctrl-b
			goto _leftArrow

		case 6: // Ctrl-f
			goto _rightArrow

		case 16: // Ctrl-p
			seq[1] = 65
			goto _upDownArrow

		case 14: // Ctrl-n
			seq[1] = 66
			goto _upDownArrow
		}
			continue // To be safe.

	_upDownArrow: // Up and down arrow: history
		if !ln.useHistory {
			continue
		}

		// Up
		if seq[1] == 65 {
			anotherLine, err = ln.hist.Prev()
			// Down
		} else {
			anotherLine, err = ln.hist.Next()
		}
		if err != nil {
			continue
		}

		// Update the current history entry before to overwrite it with
		// the next one.
		// TODO: it has to be removed before of to be saved the history
		if !isHistoryUsed {
			ln.hist.Add(ln.toString())
		}
		isHistoryUsed = true

		ln.grow(len(anotherLine))
		ln.size = len(anotherLine)
		ln.cursor = len(anotherLine)
		copy(ln.data[0:], anotherLine)
		goto _refresh

	_leftArrow:
		if ln.Left() {
			goto _refresh
		}
		continue

	_rightArrow:
		if ln.Right() {
			goto _refresh
		}
		continue

	_start:
		ln.cursor = 0
		goto _refresh

	_end:
		ln.cursor = ln.size
		goto _refresh

	_deleteLine:
		ln.cursor, ln.size = 0, 0
		//goto _refresh

	_refresh:
		ln.refresh()

		continue

	_error:
		return "", err
	}
	return "", nil
}

<|endoftext|>


<commit_before>package irckit

import (
	"errors"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gorilla/websocket"
	"github.com/mattermost/platform/model"
	"github.com/sorcix/irc"
)

func NewUserMM(c net.Conn, srv Server) *User {
	u := NewUser(&conn{
		Conn:    c,
		Encoder: irc.NewEncoder(c),
		Decoder: irc.NewDecoder(c),
	})
	u.Srv = srv

	// used for login
	mattermostService := &User{Nick: "mattermost", User: "mattermost", Real: "ghost", Host: "abchost", channels: map[Channel]struct{}{}}
	mattermostService.MmGhostUser = true
	srv.Add(mattermostService)
	go srv.Handle(mattermostService)
	return u
}

func (u *User) loginToMattermost(url string, team string, email string, pass string) error {
	// login to mattermost
	MmClient := model.NewClient("https://" + url)
	myinfo, err := MmClient.LoginByEmail(team, email, pass)
	if err != nil {
		return err
	}
	u.MmUser = myinfo.Data.(*model.User)

	myinfo, err = MmClient.GetMyTeam("")
	u.MmTeam = myinfo.Data.(*model.Team)

	// setup websocket connection
	wsurl := "wss://" + url + "/api/v1/websocket"
	header := http.Header{}
	header.Set(model.HEADER_AUTH, "BEARER "+MmClient.AuthToken)
	WsClient, _, _ := websocket.DefaultDialer.Dial(wsurl, header)

	u.MmClient = MmClient
	u.MmWsClient = WsClient
	go u.WsReceiver()

	// populating users
	mmusers, _ := u.MmClient.GetProfiles(u.MmUser.TeamId, "")
	u.MmUsers = mmusers.Data.(map[string]*model.User)

	// populating channels
	mmchannels, _ := MmClient.GetChannels("")
	u.MmChannels = mmchannels.Data.(*model.ChannelList)

	mmchannels, _ = MmClient.GetMoreChannels("")
	u.MmMoreChannels = mmchannels.Data.(*model.ChannelList)

	// fetch users and channels from mattermost
	u.addUsersToChannels()

	return nil
}

func (u *User) addUsersToChannels() {
	var mmConnected bool
	srv := u.Srv
	// already connected to a mm server ? add teamname as suffix
	if _, ok := srv.HasChannel("#town-square"); ok {
		mmConnected = true
	}
	rate := time.Second / 2
	throttle := time.Tick(rate)

	for _, mmchannel := range u.MmChannels.Channels {

		// exclude direct messages
		if strings.Contains(mmchannel.Name, "__") {
			continue
		}
		<-throttle
		go func(mmchannel *model.Channel) {
			edata, _ := u.MmClient.GetChannelExtraInfo(mmchannel.Id, "")
			if mmConnected {
				mmchannel.Name = mmchannel.Name + "-" + u.MmTeam.Name
			}
			for _, d := range edata.Data.(*model.ChannelExtra).Members {
				if mmConnected {
					d.Username = d.Username + "-" + u.MmTeam.Name
				}
				// join all the channels we're on on MM
				if d.Id == u.MmUser.Id {
					ch := srv.Channel("#" + mmchannel.Name)
					ch.Join(u)
					// we're done here
					continue
				}

				cghost, ok := srv.HasUser(d.Username)
				if !ok {
					ghost := &User{Nick: d.Username, User: d.Id,
						Real: "ghost", Host: u.MmClient.Url, channels: map[Channel]struct{}{}}

					ghost.MmGhostUser = true
					logger.Info("adding", ghost.Nick, "to #"+mmchannel.Name)
					srv.Add(ghost)
					go srv.Handle(ghost)
					ch := srv.Channel("#" + mmchannel.Name)
					ch.Join(ghost)
				} else {
					ch := srv.Channel("#" + mmchannel.Name)
					ch.Join(cghost)
				}
			}
		}(mmchannel)
	}

	// add all users, also who are not on channels
	for _, mmuser := range u.MmUsers {
		// do not add our own nick
		if mmuser.Id == u.MmUser.Id {
			continue
		}
		_, ok := srv.HasUser(mmuser.Username)
		if !ok {
			if mmConnected {
				mmuser.Username = mmuser.Username + "-" + u.MmTeam.Name
			}
			ghost := &User{Nick: mmuser.Username, User: mmuser.Id,
				Real: "ghost", Host: u.MmClient.Url, channels: map[Channel]struct{}{}}
			ghost.MmGhostUser = true
			logger.Info("adding", ghost.Nick, "without a channel")
			srv.Add(ghost)
			go srv.Handle(ghost)
		}
	}
}

type MmInfo struct {
	MmGhostUser    bool
	MmClient       *model.Client
	MmWsClient     *websocket.Conn
	Srv            Server
	MmUsers        map[string]*model.User
	MmUser         *model.User
	MmChannels     *model.ChannelList
	MmMoreChannels *model.ChannelList
	MmTeam         *model.Team
}

func (u *User) WsReceiver() {
	var rmsg model.Message
	for {
		if err := u.MmWsClient.ReadJSON(&rmsg); err != nil {
			logger.Critical(err)
			os.Exit(1)
		}
		logger.Debugf("%#v", rmsg)
		if rmsg.Action == model.ACTION_POSTED {
			data := model.PostFromJson(strings.NewReader(rmsg.Props["post"]))
			logger.Debug("receiving userid", data.UserId)
			if data.UserId == u.MmUser.Id {
				// our own message
				continue
			}
			// we don't have the user, refresh the userlist
			if u.MmUsers[data.UserId] == nil {
				mmusers, _ := u.MmClient.GetProfiles(u.MmUser.TeamId, "")
				u.MmUsers = mmusers.Data.(map[string]*model.User)
			}
			ghost, _ := u.Srv.HasUser(u.MmUsers[data.UserId].Username)
			rcvchannel := u.getMMChannelName(data.ChannelId)
			if strings.Contains(rcvchannel, "__") {
				var rcvuser string
				rcvusers := strings.Split(rcvchannel, "__")
				if rcvusers[0] != u.MmUser.Id {
					rcvuser = u.MmUsers[rcvusers[0]].Username
				} else {
					rcvuser = u.MmUsers[rcvusers[1]].Username
				}

				u.Encode(&irc.Message{
					Prefix:   &irc.Prefix{Name: rcvuser, User: rcvuser, Host: rcvuser},
					Command:  irc.PRIVMSG,
					Params:   []string{u.Nick},
					Trailing: data.Message,
				})
				//u.Srv.Publish(&event{UserMsgEvent, u.Srv, nil, u, msg})
				continue
			}

			ch := u.Srv.Channel("#" + u.getMMChannelName(data.ChannelId))
			msgs := strings.Split(data.Message, "\n")
			for _, m := range msgs {
				ch.Message(ghost, m)
			}
			//ch := srv.Channel("#" + data.Channel)

			//mychan[0].Message(ghost, data.Message)
			logger.Debug(u.MmUsers[data.UserId].Username, ":", data.Message)
			logger.Debugf("%#v", data)
		}
	}
}

func (u *User) getMMChannelName(id string) string {
	for _, channel := range u.MmChannels.Channels {
		if channel.Id == id {
			return channel.Name
		}
	}
	return ""
}

func (u *User) getMMChannelId(name string) string {
	for _, channel := range append(u.MmChannels.Channels, u.MmMoreChannels.Channels...) {
		if channel.Name == name {
			return channel.Id
		}
	}
	return ""
}

func (u *User) getMMUserId(name string) string {
	for id, u := range u.MmUsers {
		if u.Username == name {
			return id
		}
	}
	return ""
}

func (u *User) MsgUser(toUser *User, msg string) {
	u.Encode(&irc.Message{
		Prefix:   toUser.Prefix(),
		Command:  irc.PRIVMSG,
		Params:   []string{u.Nick},
		Trailing: msg,
	})
}

func (u *User) handleMMDM(toUser *User, msg string) {
	var channel string
	// We don't have a DM with this user yet.
	if u.getMMChannelId(toUser.User+"__"+u.MmUser.Id) == "" && u.getMMChannelId(u.MmUser.Id+"__"+toUser.User) == "" {
		// create DM channel
		_, err := u.MmClient.CreateDirectChannel(map[string]string{"user_id": toUser.User})
		if err != nil {
			logger.Debugf("direct message to %#v failed: %s", toUser, err)
		}
		// update our channels
		mmchannels, _ := u.MmClient.GetChannels("")
		u.MmChannels = mmchannels.Data.(*model.ChannelList)
	}

	// build the channel name
	if toUser.User > u.MmUser.Id {
		channel = u.MmUser.Id + "__" + toUser.User
	} else {
		channel = toUser.User + "__" + u.MmUser.Id
	}
	// build & send the message
	post := &model.Post{ChannelId: u.getMMChannelId(channel), Message: msg}
	u.MmClient.CreatePost(post)
}

func (u *User) handleMMServiceBot(toUser *User, msg string) {
	commands := strings.Fields(msg)
	switch commands[0] {
	case "LOGIN":
		{
			data := strings.Split(msg, " ")
			if len(data) != 5 {
				u.MsgUser(toUser, "need LOGIN <server> <team> <login> <pass>")
				return
			}
			err := u.loginToMattermost(data[1], data[2], data[3], data[4])
			if err != nil {
				u.MsgUser(toUser, "login failed")
				return
			}
			u.MsgUser(toUser, "login OK")
		}
	default:
		u.MsgUser(toUser, "need LOGIN <server> <team> <login> <pass>")
	}

}

func (u *User) syncMMChannel(id string, name string) {
	var mmConnected bool
	srv := u.Srv
	// already connected to a mm server ? add teamname as suffix
	if _, ok := srv.HasChannel("#town-square"); ok {
		mmConnected = true
	}

	edata, _ := u.MmClient.GetChannelExtraInfo(id, "")
	for _, d := range edata.Data.(*model.ChannelExtra).Members {
		if mmConnected {
			d.Username = d.Username + "-" + u.MmTeam.Name
		}
		// join all the channels we're on on MM
		if d.Id == u.MmUser.Id {
			ch := srv.Channel("#" + name)
			ch.Join(u)
			// we're done here
			continue
		}

		cghost, ok := srv.HasUser(d.Username)
		if !ok {
			ghost := &User{Nick: d.Username, User: d.Id,
				Real: "ghost", Host: u.MmClient.Url, channels: map[Channel]struct{}{}}

			ghost.MmGhostUser = true
			logger.Info("adding", ghost.Nick, "to #"+name)
			srv.Add(ghost)
			go srv.Handle(ghost)
			ch := srv.Channel("#" + name)
			ch.Join(ghost)
		} else {
			ch := srv.Channel("#" + name)
			ch.Join(cghost)
		}
	}
}

func (u *User) joinMMChannel(channel string) error {
	if u.getMMChannelId(strings.Replace(channel, "#", "", 1)) == "" {
		return errors.New("failed to join")
	}
	_, err := u.MmClient.JoinChannel(u.getMMChannelId(strings.Replace(channel, "#", "", 1)))
	if err != nil {
		return errors.New("failed to join")
	}
	u.syncMMChannel(u.getMMChannelId(strings.Replace(channel, "#", "", 1)), strings.Replace(channel, "#", "", 1))
	return nil
}
<commit_msg>Add support for JOIN/PART from Mattermost<commit_after>package irckit

import (
	"errors"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gorilla/websocket"
	"github.com/mattermost/platform/model"
	"github.com/sorcix/irc"
)

func NewUserMM(c net.Conn, srv Server) *User {
	u := NewUser(&conn{
		Conn:    c,
		Encoder: irc.NewEncoder(c),
		Decoder: irc.NewDecoder(c),
	})
	u.Srv = srv

	// used for login
	mattermostService := &User{Nick: "mattermost", User: "mattermost", Real: "ghost", Host: "abchost", channels: map[Channel]struct{}{}}
	mattermostService.MmGhostUser = true
	srv.Add(mattermostService)
	go srv.Handle(mattermostService)
	return u
}

func (u *User) loginToMattermost(url string, team string, email string, pass string) error {
	// login to mattermost
	MmClient := model.NewClient("https://" + url)
	myinfo, err := MmClient.LoginByEmail(team, email, pass)
	if err != nil {
		return err
	}
	u.MmUser = myinfo.Data.(*model.User)

	myinfo, err = MmClient.GetMyTeam("")
	u.MmTeam = myinfo.Data.(*model.Team)

	// setup websocket connection
	wsurl := "wss://" + url + "/api/v1/websocket"
	header := http.Header{}
	header.Set(model.HEADER_AUTH, "BEARER "+MmClient.AuthToken)
	WsClient, _, _ := websocket.DefaultDialer.Dial(wsurl, header)

	u.MmClient = MmClient
	u.MmWsClient = WsClient
	go u.WsReceiver()

	// populating users
	mmusers, _ := u.MmClient.GetProfiles(u.MmUser.TeamId, "")
	u.MmUsers = mmusers.Data.(map[string]*model.User)

	// populating channels
	mmchannels, _ := MmClient.GetChannels("")
	u.MmChannels = mmchannels.Data.(*model.ChannelList)

	mmchannels, _ = MmClient.GetMoreChannels("")
	u.MmMoreChannels = mmchannels.Data.(*model.ChannelList)

	// fetch users and channels from mattermost
	u.addUsersToChannels()

	return nil
}

func (u *User) addUsersToChannels() {
	var mmConnected bool
	srv := u.Srv
	// already connected to a mm server ? add teamname as suffix
	if _, ok := srv.HasChannel("#town-square"); ok {
		mmConnected = true
	}
	rate := time.Second / 2
	throttle := time.Tick(rate)

	for _, mmchannel := range u.MmChannels.Channels {

		// exclude direct messages
		if strings.Contains(mmchannel.Name, "__") {
			continue
		}
		<-throttle
		go func(mmchannel *model.Channel) {
			edata, _ := u.MmClient.GetChannelExtraInfo(mmchannel.Id, "")
			if mmConnected {
				mmchannel.Name = mmchannel.Name + "-" + u.MmTeam.Name
			}
			for _, d := range edata.Data.(*model.ChannelExtra).Members {
				if mmConnected {
					d.Username = d.Username + "-" + u.MmTeam.Name
				}
				// join all the channels we're on on MM
				if d.Id == u.MmUser.Id {
					ch := srv.Channel("#" + mmchannel.Name)
					ch.Join(u)
					// we're done here
					continue
				}

				cghost, ok := srv.HasUser(d.Username)
				if !ok {
					ghost := &User{Nick: d.Username, User: d.Id,
						Real: "ghost", Host: u.MmClient.Url, channels: map[Channel]struct{}{}}

					ghost.MmGhostUser = true
					logger.Info("adding", ghost.Nick, "to #"+mmchannel.Name)
					srv.Add(ghost)
					go srv.Handle(ghost)
					ch := srv.Channel("#" + mmchannel.Name)
					ch.Join(ghost)
				} else {
					ch := srv.Channel("#" + mmchannel.Name)
					ch.Join(cghost)
				}
			}
		}(mmchannel)
	}

	// add all users, also who are not on channels
	for _, mmuser := range u.MmUsers {
		// do not add our own nick
		if mmuser.Id == u.MmUser.Id {
			continue
		}
		_, ok := srv.HasUser(mmuser.Username)
		if !ok {
			if mmConnected {
				mmuser.Username = mmuser.Username + "-" + u.MmTeam.Name
			}
			ghost := &User{Nick: mmuser.Username, User: mmuser.Id,
				Real: "ghost", Host: u.MmClient.Url, channels: map[Channel]struct{}{}}
			ghost.MmGhostUser = true
			logger.Info("adding", ghost.Nick, "without a channel")
			srv.Add(ghost)
			go srv.Handle(ghost)
		}
	}
}

type MmInfo struct {
	MmGhostUser    bool
	MmClient       *model.Client
	MmWsClient     *websocket.Conn
	Srv            Server
	MmUsers        map[string]*model.User
	MmUser         *model.User
	MmChannels     *model.ChannelList
	MmMoreChannels *model.ChannelList
	MmTeam         *model.Team
}

func (u *User) WsReceiver() {
	var rmsg model.Message
	for {
		if err := u.MmWsClient.ReadJSON(&rmsg); err != nil {
			logger.Critical(err)
			os.Exit(1)
		}
		logger.Debugf("%#v", rmsg)
		if rmsg.Action == model.ACTION_POSTED {
			data := model.PostFromJson(strings.NewReader(rmsg.Props["post"]))
			logger.Debug("receiving userid", data.UserId)
			if data.UserId == u.MmUser.Id {
				// our own message
				continue
			}
			// we don't have the user, refresh the userlist
			if u.MmUsers[data.UserId] == nil {
				mmusers, _ := u.MmClient.GetProfiles(u.MmUser.TeamId, "")
				u.MmUsers = mmusers.Data.(map[string]*model.User)
			}
			ghost, _ := u.Srv.HasUser(u.MmUsers[data.UserId].Username)
			rcvchannel := u.getMMChannelName(data.ChannelId)
			if strings.Contains(rcvchannel, "__") {
				var rcvuser string
				rcvusers := strings.Split(rcvchannel, "__")
				if rcvusers[0] != u.MmUser.Id {
					rcvuser = u.MmUsers[rcvusers[0]].Username
				} else {
					rcvuser = u.MmUsers[rcvusers[1]].Username
				}

				u.Encode(&irc.Message{
					Prefix:   &irc.Prefix{Name: rcvuser, User: rcvuser, Host: rcvuser},
					Command:  irc.PRIVMSG,
					Params:   []string{u.Nick},
					Trailing: data.Message,
				})
				//u.Srv.Publish(&event{UserMsgEvent, u.Srv, nil, u, msg})
				continue
			}

			ch := u.Srv.Channel("#" + u.getMMChannelName(data.ChannelId))
			msgs := strings.Split(data.Message, "\n")
			for _, m := range msgs {
				ch.Message(ghost, m)
			}
			//ch := srv.Channel("#" + data.Channel)

			//mychan[0].Message(ghost, data.Message)
			logger.Debug(u.MmUsers[data.UserId].Username, ":", data.Message)
			logger.Debugf("%#v", data)
		}
		if rmsg.Action == model.ACTION_USER_REMOVED {
			if u.MmUsers[rmsg.UserId] == nil {
				mmusers, _ := u.MmClient.GetProfiles(u.MmUser.TeamId, "")
				u.MmUsers = mmusers.Data.(map[string]*model.User)
			}
			ghost, _ := u.Srv.HasUser(u.MmUsers[rmsg.UserId].Username)
			ch := u.Srv.Channel("#" + u.getMMChannelName(rmsg.ChannelId))
			ch.Part(ghost, "")
		}
		if rmsg.Action == model.ACTION_USER_ADDED {
			if u.MmUsers[rmsg.UserId] == nil {
				mmusers, _ := u.MmClient.GetProfiles(u.MmUser.TeamId, "")
				u.MmUsers = mmusers.Data.(map[string]*model.User)
			}
			ghost, _ := u.Srv.HasUser(u.MmUsers[rmsg.UserId].Username)
			ch := u.Srv.Channel("#" + u.getMMChannelName(rmsg.ChannelId))
			ch.Join(ghost)
		}
	}
}

func (u *User) getMMChannelName(id string) string {
	for _, channel := range u.MmChannels.Channels {
		if channel.Id == id {
			return channel.Name
		}
	}
	return ""
}

func (u *User) getMMChannelId(name string) string {
	for _, channel := range append(u.MmChannels.Channels, u.MmMoreChannels.Channels...) {
		if channel.Name == name {
			return channel.Id
		}
	}
	return ""
}

func (u *User) getMMUserId(name string) string {
	for id, u := range u.MmUsers {
		if u.Username == name {
			return id
		}
	}
	return ""
}

func (u *User) MsgUser(toUser *User, msg string) {
	u.Encode(&irc.Message{
		Prefix:   toUser.Prefix(),
		Command:  irc.PRIVMSG,
		Params:   []string{u.Nick},
		Trailing: msg,
	})
}

func (u *User) handleMMDM(toUser *User, msg string) {
	var channel string
	// We don't have a DM with this user yet.
	if u.getMMChannelId(toUser.User+"__"+u.MmUser.Id) == "" && u.getMMChannelId(u.MmUser.Id+"__"+toUser.User) == "" {
		// create DM channel
		_, err := u.MmClient.CreateDirectChannel(map[string]string{"user_id": toUser.User})
		if err != nil {
			logger.Debugf("direct message to %#v failed: %s", toUser, err)
		}
		// update our channels
		mmchannels, _ := u.MmClient.GetChannels("")
		u.MmChannels = mmchannels.Data.(*model.ChannelList)
	}

	// build the channel name
	if toUser.User > u.MmUser.Id {
		channel = u.MmUser.Id + "__" + toUser.User
	} else {
		channel = toUser.User + "__" + u.MmUser.Id
	}
	// build & send the message
	post := &model.Post{ChannelId: u.getMMChannelId(channel), Message: msg}
	u.MmClient.CreatePost(post)
}

func (u *User) handleMMServiceBot(toUser *User, msg string) {
	commands := strings.Fields(msg)
	switch commands[0] {
	case "LOGIN":
		{
			data := strings.Split(msg, " ")
			if len(data) != 5 {
				u.MsgUser(toUser, "need LOGIN <server> <team> <login> <pass>")
				return
			}
			err := u.loginToMattermost(data[1], data[2], data[3], data[4])
			if err != nil {
				u.MsgUser(toUser, "login failed")
				return
			}
			u.MsgUser(toUser, "login OK")
		}
	default:
		u.MsgUser(toUser, "need LOGIN <server> <team> <login> <pass>")
	}

}

func (u *User) syncMMChannel(id string, name string) {
	var mmConnected bool
	srv := u.Srv
	// already connected to a mm server ? add teamname as suffix
	if _, ok := srv.HasChannel("#town-square"); ok {
		mmConnected = true
	}

	edata, _ := u.MmClient.GetChannelExtraInfo(id, "")
	for _, d := range edata.Data.(*model.ChannelExtra).Members {
		if mmConnected {
			d.Username = d.Username + "-" + u.MmTeam.Name
		}
		// join all the channels we're on on MM
		if d.Id == u.MmUser.Id {
			ch := srv.Channel("#" + name)
			ch.Join(u)
			// we're done here
			continue
		}

		cghost, ok := srv.HasUser(d.Username)
		if !ok {
			ghost := &User{Nick: d.Username, User: d.Id,
				Real: "ghost", Host: u.MmClient.Url, channels: map[Channel]struct{}{}}

			ghost.MmGhostUser = true
			logger.Info("adding", ghost.Nick, "to #"+name)
			srv.Add(ghost)
			go srv.Handle(ghost)
			ch := srv.Channel("#" + name)
			ch.Join(ghost)
		} else {
			ch := srv.Channel("#" + name)
			ch.Join(cghost)
		}
	}
}

func (u *User) joinMMChannel(channel string) error {
	if u.getMMChannelId(strings.Replace(channel, "#", "", 1)) == "" {
		return errors.New("failed to join")
	}
	_, err := u.MmClient.JoinChannel(u.getMMChannelId(strings.Replace(channel, "#", "", 1)))
	if err != nil {
		return errors.New("failed to join")
	}
	u.syncMMChannel(u.getMMChannelId(strings.Replace(channel, "#", "", 1)), strings.Replace(channel, "#", "", 1))
	return nil
}
<|endoftext|>


<commit_before>// Package bench contains benchmarks for gathering system information.
// These packages are not directly comparable because of the differences
// in what they gather, but I wanted to see some numbers.
//
// This will only work on linux systems due to limitations of
// github.com/mohae/joefriday.
package bench

import (
	"testing"

	"github.com/DataDog/gohai/memory"
	"github.com/cloudfoundry/gosigar"
	gopsutilmem "github.com/shirou/gopsutil/mem"
)

func BenchmarkJoeFridayMemInfoCat(b *testing.B) {
	var inf *MemInfo
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoCat()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoCatToJSON(b *testing.B) {
	var inf []byte
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoCatToJSON()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoCatToFlatbuffers(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataCat()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoCatToFlatbuffersReuseBuilder(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataCatReuseBldr()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoRead(b *testing.B) {
	var inf *MemInfo
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoRead()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoReadToJSON(b *testing.B) {
	var inf []byte
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoReadToJSON()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoReadToFlatbuffers(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataRead()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoReadToFlatbuffersReuseBuilder(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataReadReuseBldr()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoReadReuseBufioReader(b *testing.B) {
	var inf *MemInfo
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoReadReuseR()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoReadReuseBufioReaderToJSON(b *testing.B) {
	var inf []byte
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoReadReuseRToJSON()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoReadReuseBufioReaderToFlatbuffers(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataReadReuseR()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoReadReuseBufioReaderToFlatbuffersReuseBuilder(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataReuseRReuseBldr()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoToFlatbuffersReuseBldrReuseBuilder(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemInfoToFlatbuffersReuseBldr()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoToFlatbuffersMinAllocs(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemInfoToFlatbuffersMinAllocs()
	}
	_ = data
}

func BenchmarkGohaiMem(b *testing.B) {
	type Collector interface {
		Name() string
		Collect() (interface{}, error)
	}
	var collector = &memory.Memory{}
	var c interface{}
	for i := 0; i < b.N; i++ {
		c, _ = collector.Collect()
	}
	_ = c
}

func BenchmarkGoSigarMem(b *testing.B) {
	var mem sigar.Mem
	for i := 0; i < b.N; i++ {
		mem.Get()
	}
	_ = mem
}

func BenchmarkGopsutilMem(b *testing.B) {
	var mem *gopsutilmem.VirtualMemoryStat
	for i := 0; i < b.N; i++ {
		mem, _ = gopsutilmem.VirtualMemory()
	}
	_ = mem
}
<commit_msg>add EricLagergren/go-gnulib/sysinfo mem to benchmark<commit_after>// Package bench contains benchmarks for gathering system information.
// These packages are not directly comparable because of the differences
// in what they gather, but I wanted to see some numbers.
//
// This will only work on linux systems due to limitations of
// github.com/mohae/joefriday.
package bench

import (
	"testing"

	"github.com/DataDog/gohai/memory"
	"github.com/EricLagergren/go-gnulib/sysinfo"
	"github.com/cloudfoundry/gosigar"
	gopsutilmem "github.com/shirou/gopsutil/mem"
)

func BenchmarkJoeFridayMemInfoCat(b *testing.B) {
	var inf *MemInfo
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoCat()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoCatToJSON(b *testing.B) {
	var inf []byte
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoCatToJSON()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoCatToFlatbuffers(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataCat()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoCatToFlatbuffersReuseBuilder(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataCatReuseBldr()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoRead(b *testing.B) {
	var inf *MemInfo
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoRead()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoReadToJSON(b *testing.B) {
	var inf []byte
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoReadToJSON()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoReadToFlatbuffers(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataRead()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoReadToFlatbuffersReuseBuilder(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataReadReuseBldr()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoReadReuseBufioReader(b *testing.B) {
	var inf *MemInfo
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoReadReuseR()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoReadReuseBufioReaderToJSON(b *testing.B) {
	var inf []byte
	for i := 0; i < b.N; i++ {
		inf, _ = GetMemInfoReadReuseRToJSON()
	}
	_ = inf
}

func BenchmarkJoeFridayMemInfoReadReuseBufioReaderToFlatbuffers(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataReadReuseR()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoReadReuseBufioReaderToFlatbuffersReuseBuilder(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemDataReuseRReuseBldr()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoToFlatbuffersReuseBldrReuseBuilder(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemInfoToFlatbuffersReuseBldr()
	}
	_ = data
}

func BenchmarkJoeFridayMemInfoToFlatbuffersMinAllocs(b *testing.B) {
	var data []byte
	for i := 0; i < b.N; i++ {
		data, _ = GetMemInfoToFlatbuffersMinAllocs()
	}
	_ = data
}

func BenchmarkGohaiMem(b *testing.B) {
	type Collector interface {
		Name() string
		Collect() (interface{}, error)
	}
	var collector = &memory.Memory{}
	var c interface{}
	for i := 0; i < b.N; i++ {
		c, _ = collector.Collect()
	}
	_ = c
}

func BenchmarkGoSigarMem(b *testing.B) {
	var mem sigar.Mem
	for i := 0; i < b.N; i++ {
		mem.Get()
	}
	_ = mem
}

func BenchmarkGopsutilMem(b *testing.B) {
	var mem *gopsutilmem.VirtualMemoryStat
	for i := 0; i < b.N; i++ {
		mem, _ = gopsutilmem.VirtualMemory()
	}
	_ = mem
}

func BenchmarkGnulibSysinfo(b *testing.B) {
	var memA, memT int64
	for i := 0; i < b.N; i++ {
		memA = sysinfo.PhysmemAvailable()
		memT = sysinfo.PhysmemTotal()
	}
	_ = memA
	_ = memT
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	log "github.com/Sirupsen/logrus"
	"github.com/elazarl/goproxy"
	"github.com/garyburd/redigo/redis"
)

type DBClient struct {
	cache Cache
	http  *http.Client
}

// request holds structure for request
type request struct {
	details requestDetails
}

// requestDetails stores information about request, it's used for creating unique hash and also as a payload structure
type requestDetails struct {
	Path        string `json:"path"`
	Method      string `json:"method"`
	Destination string `json:"destination"`
	Query       string `json:"query"`
}

// hash returns unique hash key for request
func (r *request) hash() string {
	h := md5.New()
	io.WriteString(h, fmt.Sprintf("%s%s%s%s", r.details.Destination, r.details.Path, r.details.Method, r.details.Query))
	return fmt.Sprintf("%x", h.Sum(nil))
}

// res structure hold response body from external service, body is not decoded and is supposed
// to be bytes, however headers should provide all required information for later decoding
// by the client.
type response struct {
	Status  int               `json:"status"`
	Body    []byte            `json:"body"`
	Headers map[string]string `json:"headers"`
}

// Payload structure holds request and response structure
type Payload struct {
	Response response       `json:"response"`
	Request  requestDetails `json:"request"`
	ID       string         `json:"id"`
}

// recordRequest saves request for later playback
func (d *DBClient) recordRequest(req *http.Request) (*http.Response, error) {

	// forwarding request
	resp, err := d.doRequest(req)

	// do not wait for response - spawning goroutine
	go d.save(req, resp)

	// return new response or error here
	return resp, err
}

// save gets request fingerprint, extracts request body, status code and headers, then saves it to cache
func (d *DBClient) save(req *http.Request, resp *http.Response) {
	// record request here
	key := getRequestFingerprint(req)

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)

	responseObj := response{
		Status:  resp.StatusCode,
		Body:    body,
		Headers: getHeadersMap(resp.Header),
	}

	log.WithFields(log.Fields{
		"path":          req.URL.Path,
		"rawQuery":      req.URL.RawQuery,
		"requestMethod": req.Method,
		"destination":   req.Host,
		"hashKey":       key,
	}).Info("Recording")

	requestObj := requestDetails{
		Path:        req.URL.Path,
		Method:      req.Method,
		Destination: req.Host,
		Query:       req.URL.RawQuery,
	}

	payload := Payload{
		Response: responseObj,
		Request:  requestObj,
		ID:       key,
	}
	// converting it to json bytes
	bts, err := json.Marshal(payload)

	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to marshal json")
	} else {
		d.cache.set(key, bts)
	}
}

// getAllRecords returns all stored
func (d *DBClient) getAllRecords() ([]Payload, error) {
	var payloads []Payload

	keys, err := d.cache.getAllKeys()

	if err == nil {

		jsonStrs, err := d.cache.getAllValues(keys)

		if err != nil {
			log.WithFields(log.Fields{
				"error": err.Error(),
			}).Error("Failed to get all values")
		} else {

			for _, v := range jsonStrs {
				var pl Payload
				err = json.Unmarshal([]byte(v), &pl)
				if err != nil {
					log.WithFields(log.Fields{
						"error": err.Error(),
						"json":  v,
					}).Warning("Failed to deserialize json")
				} else {
					payloads = append(payloads, pl)
				}
			}
		}

		return payloads, err
	} else {
		return payloads, err
	}

}

// getRequestFingerprint returns request hash
func getRequestFingerprint(req *http.Request) string {
	details := requestDetails{Path: req.URL.Path, Method: req.Method, Destination: req.Host, Query: req.URL.RawQuery}
	r := request{details: details}
	return r.hash()
}

// getHeadersMap converts map[string][]string to map[string]string structure
func getHeadersMap(hds map[string][]string) map[string]string {
	headers := make(map[string]string)
	for key, value := range hds {
		headers[key] = value[0]
	}
	return headers
}

// getResponse returns stored response from cache
func (d *DBClient) getResponse(req *http.Request) *http.Response {
	log.Info("Returning response")

	key := getRequestFingerprint(req)
	var payload Payload

	payloadBts, err := redis.Bytes(d.cache.get(key))

	if err == nil {
		log.Info("Decoding bytes")
		// getting cache response
		err = json.Unmarshal(payloadBts, &payload)
		if err != nil {
			log.Error(err)
			// what now?
		}

		newResponse := &http.Response{}
		newResponse.Request = req
		// adding headers
		newResponse.Header = make(http.Header)
		if len(payload.Response.Headers) > 0 {
			for k, v := range payload.Response.Headers {
				newResponse.Header.Set(k, v)
			}
		}
		newResponse.Header.Set("Gen-Proxy", "Playback")
		// adding body
		buf := bytes.NewBuffer(payload.Response.Body)
		newResponse.ContentLength = int64(buf.Len())
		newResponse.Body = ioutil.NopCloser(buf)

		newResponse.StatusCode = payload.Response.Status

		log.WithFields(log.Fields{
			"key":        key,
			"status":     payload.Response.Status,
			"bodyLength": newResponse.ContentLength,
		}).Info("Response found, returning")

		return newResponse

	} else {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to retrieve response from cache")
		// return error? if we return nil - proxy forwards request to original destination
		return goproxy.NewResponse(req,
			goproxy.ContentTypeText, http.StatusPreconditionFailed,
			"Coudldn't find recorded request, please record it first!")
	}

}

// doRequest performs original request and returns response that should be returned to client and error (if there is one)
func (d *DBClient) doRequest(request *http.Request) (*http.Response, error) {
	// We can't have this set. And it only contains "/pkg/net/http/" anyway
	request.RequestURI = ""

	resp, err := d.http.Do(request)

	if err != nil {
		log.WithFields(log.Fields{
			"error":  err.Error(),
			"host":   request.Host,
			"method": request.Method,
			"path":   request.URL.Path,
		}).Error("Could not forward request.")
		return nil, err
	}

	log.WithFields(log.Fields{}).Info("Request forwarded!")

	resp.Header.Set("Gen-proxy", "Was-Here")
	return resp, nil

}
<commit_msg>added function to get raw bytes of all values to skip marshaling<commit_after>package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	log "github.com/Sirupsen/logrus"
	"github.com/elazarl/goproxy"
	"github.com/garyburd/redigo/redis"
)

type DBClient struct {
	cache Cache
	http  *http.Client
}

// request holds structure for request
type request struct {
	details requestDetails
}

// requestDetails stores information about request, it's used for creating unique hash and also as a payload structure
type requestDetails struct {
	Path        string `json:"path"`
	Method      string `json:"method"`
	Destination string `json:"destination"`
	Query       string `json:"query"`
}

// hash returns unique hash key for request
func (r *request) hash() string {
	h := md5.New()
	io.WriteString(h, fmt.Sprintf("%s%s%s%s", r.details.Destination, r.details.Path, r.details.Method, r.details.Query))
	return fmt.Sprintf("%x", h.Sum(nil))
}

// res structure hold response body from external service, body is not decoded and is supposed
// to be bytes, however headers should provide all required information for later decoding
// by the client.
type response struct {
	Status  int               `json:"status"`
	Body    []byte            `json:"body"`
	Headers map[string]string `json:"headers"`
}

// Payload structure holds request and response structure
type Payload struct {
	Response response       `json:"response"`
	Request  requestDetails `json:"request"`
	ID       string         `json:"id"`
}

// recordRequest saves request for later playback
func (d *DBClient) recordRequest(req *http.Request) (*http.Response, error) {

	// forwarding request
	resp, err := d.doRequest(req)

	// do not wait for response - spawning goroutine
	go d.save(req, resp)

	// return new response or error here
	return resp, err
}

// save gets request fingerprint, extracts request body, status code and headers, then saves it to cache
func (d *DBClient) save(req *http.Request, resp *http.Response) {
	// record request here
	key := getRequestFingerprint(req)

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)

	responseObj := response{
		Status:  resp.StatusCode,
		Body:    body,
		Headers: getHeadersMap(resp.Header),
	}

	log.WithFields(log.Fields{
		"path":          req.URL.Path,
		"rawQuery":      req.URL.RawQuery,
		"requestMethod": req.Method,
		"destination":   req.Host,
		"hashKey":       key,
	}).Info("Recording")

	requestObj := requestDetails{
		Path:        req.URL.Path,
		Method:      req.Method,
		Destination: req.Host,
		Query:       req.URL.RawQuery,
	}

	payload := Payload{
		Response: responseObj,
		Request:  requestObj,
		ID:       key,
	}
	// converting it to json bytes
	bts, err := json.Marshal(payload)

	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to marshal json")
	} else {
		d.cache.set(key, bts)
	}
}

// getAllRecordsRaw returns raw (json string) for all records
func (d *DBClient) getAllRecordsRaw() ([]string, error) {
	keys, err := d.cache.getAllKeys()

	if err == nil {

		jsonStrs, err := d.cache.getAllValues(keys)

		if err != nil {
			log.WithFields(log.Fields{
				"error": err.Error(),
			}).Error("Failed to get all values")
			return nil, err
		} else {
			return jsonStrs, nil
		}

	} else {
		return nil, err
	}
}

// getAllRecords returns all stored
func (d *DBClient) getAllRecords() ([]Payload, error) {
	var payloads []Payload

	jsonStrs, err := d.getAllRecordsRaw()

	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to get all values")
	} else {

		for _, v := range jsonStrs {
			var pl Payload
			err = json.Unmarshal([]byte(v), &pl)
			if err != nil {
				log.WithFields(log.Fields{
					"error": err.Error(),
					"json":  v,
				}).Warning("Failed to deserialize json")
			} else {
				payloads = append(payloads, pl)
			}
		}
	}

	return payloads, err

}

// getRequestFingerprint returns request hash
func getRequestFingerprint(req *http.Request) string {
	details := requestDetails{Path: req.URL.Path, Method: req.Method, Destination: req.Host, Query: req.URL.RawQuery}
	r := request{details: details}
	return r.hash()
}

// getHeadersMap converts map[string][]string to map[string]string structure
func getHeadersMap(hds map[string][]string) map[string]string {
	headers := make(map[string]string)
	for key, value := range hds {
		headers[key] = value[0]
	}
	return headers
}

// getResponse returns stored response from cache
func (d *DBClient) getResponse(req *http.Request) *http.Response {
	log.Info("Returning response")

	key := getRequestFingerprint(req)
	var payload Payload

	payloadBts, err := redis.Bytes(d.cache.get(key))

	if err == nil {
		log.Info("Decoding bytes")
		// getting cache response
		err = json.Unmarshal(payloadBts, &payload)
		if err != nil {
			log.Error(err)
			// what now?
		}

		newResponse := &http.Response{}
		newResponse.Request = req
		// adding headers
		newResponse.Header = make(http.Header)
		if len(payload.Response.Headers) > 0 {
			for k, v := range payload.Response.Headers {
				newResponse.Header.Set(k, v)
			}
		}
		newResponse.Header.Set("Gen-Proxy", "Playback")
		// adding body
		buf := bytes.NewBuffer(payload.Response.Body)
		newResponse.ContentLength = int64(buf.Len())
		newResponse.Body = ioutil.NopCloser(buf)

		newResponse.StatusCode = payload.Response.Status

		log.WithFields(log.Fields{
			"key":        key,
			"status":     payload.Response.Status,
			"bodyLength": newResponse.ContentLength,
		}).Info("Response found, returning")

		return newResponse

	} else {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to retrieve response from cache")
		// return error? if we return nil - proxy forwards request to original destination
		return goproxy.NewResponse(req,
			goproxy.ContentTypeText, http.StatusPreconditionFailed,
			"Coudldn't find recorded request, please record it first!")
	}

}

// doRequest performs original request and returns response that should be returned to client and error (if there is one)
func (d *DBClient) doRequest(request *http.Request) (*http.Response, error) {
	// We can't have this set. And it only contains "/pkg/net/http/" anyway
	request.RequestURI = ""

	resp, err := d.http.Do(request)

	if err != nil {
		log.WithFields(log.Fields{
			"error":  err.Error(),
			"host":   request.Host,
			"method": request.Method,
			"path":   request.URL.Path,
		}).Error("Could not forward request.")
		return nil, err
	}

	log.WithFields(log.Fields{}).Info("Request forwarded!")

	resp.Header.Set("Gen-proxy", "Was-Here")
	return resp, nil

}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package logging contains a Google Cloud Logging client.
//
// This package is experimental and subject to API changes.
package logging // import "google.golang.org/cloud/logging"

import (
	"errors"
	"io"
	"log"
	"sync"
	"time"

	"golang.org/x/net/context"
	api "google.golang.org/api/logging/v1beta3"
	"google.golang.org/cloud"
	"google.golang.org/cloud/internal/transport"
)

// Scope is the OAuth2 scope necessary to use Google Cloud Logging.
const Scope = api.CloudPlatformScope

// Level is the log level.
type Level int

const (
	// Default means no assigned severity level.
	Default Level = iota
	Debug
	Info
	Warning
	Error
	Critical
	Alert
	Emergency
	nLevel
)

var levelName = [nLevel]string{
	Default:   "",
	Debug:     "DEBUG",
	Info:      "INFO",
	Warning:   "WARNING",
	Error:     "ERROR",
	Critical:  "CRITICAL",
	Alert:     "ALERT",
	Emergency: "EMERGENCY",
}

func (v Level) String() string {
	return levelName[v]
}

// Client is a Google Cloud Logging client.
// It must be constructed via NewClient.
type Client struct {
	svc     *api.Service
	logs    *api.ProjectsLogsEntriesService
	projID  string
	logName string
	writer  [nLevel]io.Writer
	logger  [nLevel]*log.Logger

	mu          sync.Mutex
	queued      []*api.LogEntry
	curFlush    *flushCall  // currently in-flight flush
	flushTimer  *time.Timer // nil before first use
	timerActive bool        // whether flushTimer is armed
	inFlight    int         // number of log entries sent to API service but not yet ACKed

	// For testing:
	timeNow func() time.Time // optional

	// ServiceName may be "appengine.googleapis.com",
	// "compute.googleapis.com" or "custom.googleapis.com".
	//
	// The default is "custom.googleapis.com".
	//
	// The service name is only used by the API server to
	// determine which of the labels are used to index the logs.
	ServiceName string

	// CommonLabels are metadata labels that apply to all log
	// entries in this request, so that you don't have to repeat
	// them in each log entry's metadata.labels field. If any of
	// the log entries contains a (key, value) with the same key
	// that is in CommonLabels, then the entry's (key, value)
	// overrides the one in CommonLabels.
	CommonLabels map[string]string

	// BufferLimit is the maximum number of items to keep in memory
	// before flushing. Zero means automatic. A value of 1 means to
	// flush after each log entry.
	// The default is currently 10,000.
	BufferLimit int

	// FlushAfter optionally specifies a threshold count at which buffered
	// log entries are flushed, even if the BufferInterval has not yet
	// been reached.
	// The default is currently 10.
	FlushAfter int

	// BufferInterval is the maximum amount of time that an item
	// should remain buffered in memory before being flushed to
	// the logging service.
	// The default is currently 1 second.
	BufferInterval time.Duration

	// Overflow is a function which runs when the Log function
	// overflows its configured buffer limit. If nil, the log
	// entry is dropped. The return value from Overflow is
	// returned by Log.
	Overflow func(*Client, Entry) error
}

func (c *Client) flushAfter() int {
	if v := c.FlushAfter; v > 0 {
		return v
	}
	return 10
}

func (c *Client) bufferInterval() time.Duration {
	if v := c.BufferInterval; v > 0 {
		return v
	}
	return time.Second
}

func (c *Client) bufferLimit() int {
	if v := c.BufferLimit; v > 0 {
		return v
	}
	return 10000
}

func (c *Client) serviceName() string {
	if v := c.ServiceName; v != "" {
		return v
	}
	return "custom.googleapis.com"
}

func (c *Client) now() time.Time {
	if now := c.timeNow; now != nil {
		return now()
	}
	return time.Now()
}

// Writer returns an io.Writer for the provided log level.
//
// Each Write call on the returned Writer generates a log entry.
//
// This Writer accessor does not allocate, so callers do not need to
// cache.
func (c *Client) Writer(v Level) io.Writer { return c.writer[v] }

// Logger returns a *log.Logger for the provided log level.
//
// A Logger for each Level is pre-allocated by NewClient with an empty
// prefix and no flags.  This Logger accessor does not allocate.
// Callers wishing to use alternate flags (such as log.Lshortfile) may
// mutate the returned Logger with SetFlags. Such mutations affect all
// callers in the program.
func (c *Client) Logger(v Level) *log.Logger { return c.logger[v] }

type levelWriter struct {
	level Level
	c     *Client
}

func (w levelWriter) Write(p []byte) (n int, err error) {
	return len(p), w.c.Log(Entry{
		Level:   w.level,
		Payload: string(p),
	})
}

// Entry is a log entry.
type Entry struct {
	// Time is the time of the entry. If the zero value, the current time is used.
	Time time.Time

	// Level is log entry's severity level.
	// The zero value means no assigned severity level.
	Level Level

	// Payload must be either a string, []byte, or something that
	// marshals via the encoding/json package to a JSON object
	// (and not any other type of JSON value).
	Payload interface{}

	// Labels optionally specifies key/value labels for the log entry.
	// Depending on the Client's ServiceName, these are indexed differently
	// by the Cloud Logging Service.
	// See https://cloud.google.com/logging/docs/logs_index
	// The Client.Log method takes ownership of this map.
	Labels map[string]string

	// TODO: de-duping id
}

func (c *Client) apiEntry(e Entry) (*api.LogEntry, error) {
	t := e.Time
	if t.IsZero() {
		t = c.now()
	}

	ent := &api.LogEntry{
		Metadata: &api.LogEntryMetadata{
			Timestamp:   t.UTC().Format(time.RFC3339Nano),
			ServiceName: c.serviceName(),
			Severity:    e.Level.String(),
			Labels:      e.Labels,
		},
	}
	switch p := e.Payload.(type) {
	case string:
		ent.TextPayload = p
	case []byte:
		ent.TextPayload = string(p)
	default:
		ent.StructPayload = api.LogEntryStructPayload(p)
	}
	return ent, nil
}

// LogSync logs e synchronously without any buffering.
// This is mostly intended for debugging or critical errors.
func (c *Client) LogSync(e Entry) error {
	ent, err := c.apiEntry(e)
	if err != nil {
		return err
	}
	_, err = c.logs.Write(c.projID, c.logName, &api.WriteLogEntriesRequest{
		CommonLabels: c.CommonLabels,
		Entries:      []*api.LogEntry{ent},
	}).Do()
	return err
}

var ErrOverflow = errors.New("logging: log entry overflowed buffer limits")

// Log queues an entry to be sent to the logging service, subject to the
// Client's parameters. By default, the log will be flushed within
// one second.
// Log only returns an error if the entry is invalid or the queue is at
// capacity. If the queue is at capacity and the entry can't be added,
// Log returns either ErrOverflow when c.Overflow is nil, or the
// value returned by c.Overflow.
func (c *Client) Log(e Entry) error {
	ent, err := c.apiEntry(e)
	if err != nil {
		return err
	}

	c.mu.Lock()
	buffered := len(c.queued) + c.inFlight

	if buffered >= c.bufferLimit() {
		c.mu.Unlock()
		if fn := c.Overflow; fn != nil {
			return fn(c, e)
		}
		return ErrOverflow
	}
	defer c.mu.Unlock()

	c.queued = append(c.queued, ent)
	if len(c.queued) >= c.flushAfter() {
		c.scheduleFlushLocked(0)
		return nil
	}
	c.scheduleFlushLocked(c.bufferInterval())
	return nil
}

// c.mu must be held.
//
// d will be one of two values: either c.BufferInterval (or its
// default value) or 0.
func (c *Client) scheduleFlushLocked(d time.Duration) {
	if c.inFlight > 0 {
		// For now to keep things simple, only allow one HTTP
		// request in flight at a time.
		return
	}
	switch {
	case c.flushTimer == nil:
		// First flush.
		c.timerActive = true
		c.flushTimer = time.AfterFunc(d, c.timeoutFlush)
	case c.timerActive && d == 0:
		// Make it happen sooner.  For example, this is the
		// case of transitioning from a 1 second flush after
		// the 1st item to an immediate flush after the 10th
		// item.
		c.flushTimer.Reset(0)
	case !c.timerActive:
		c.timerActive = true
		c.flushTimer.Reset(d)
	default:
		// else timer was already active, also at d > 0,
		// so we don't touch it and let it fire as previously
		// scheduled.
	}
}

// timeoutFlush runs in its own goroutine (from time.AfterFunc) and
// flushes c.queued.
func (c *Client) timeoutFlush() {
	c.mu.Lock()
	c.timerActive = false
	c.mu.Unlock()
	if err := c.Flush(); err != nil {
		// schedule another try
		// TODO: smarter back-off?
		c.mu.Lock()
		c.scheduleFlushLocked(5 * time.Second)
		c.mu.Unlock()
	}
}

// Ping reports whether the client's connection to Google Cloud
// Logging and the authentication configuration are valid.
func (c *Client) Ping() error {
	_, err := c.logs.Write(c.projID, c.logName, &api.WriteLogEntriesRequest{
		Entries: []*api.LogEntry{},
	}).Do()
	return err
}

// Flush flushes any buffered log entries.
func (c *Client) Flush() error {
	var numFlush int
	c.mu.Lock()
	for {
		// We're already flushing (or we just started flushing
		// ourselves), so wait for it to finish.
		if f := c.curFlush; f != nil {
			wasEmpty := len(c.queued) == 0
			c.mu.Unlock()
			<-f.donec // wait for it
			numFlush++
			// Terminate whenever there's an error, we've
			// already flushed twice (one that was already
			// in-flight when flush was called, and then
			// one we instigated), or the queue was empty
			// when we released the locked (meaning this
			// in-flight flush removes everything present
			// when Flush was called, and we don't need to
			// kick off a new flush for things arriving
			// afterward)
			if f.err != nil || numFlush == 2 || wasEmpty {
				return f.err
			}
			// Otherwise, re-obtain the lock and loop,
			// starting over with seeing if a flush is in
			// progress, which might've been started by a
			// different goroutine before aquiring this
			// lock again.
			c.mu.Lock()
			continue
		}

		// Terminal case:
		if len(c.queued) == 0 {
			c.mu.Unlock()
			return nil
		}

		c.startFlushLocked()
	}
}

// requires c.mu be held.
func (c *Client) startFlushLocked() {
	if c.curFlush != nil {
		panic("internal error: flush already in flight")
	}
	if len(c.queued) == 0 {
		panic("internal error: no items queued")
	}
	logEntries := c.queued
	c.inFlight = len(logEntries)
	c.queued = nil

	flush := &flushCall{
		donec: make(chan struct{}),
	}
	c.curFlush = flush
	go func() {
		defer close(flush.donec)
		_, err := c.logs.Write(c.projID, c.logName, &api.WriteLogEntriesRequest{
			CommonLabels: c.CommonLabels,
			Entries:      logEntries,
		}).Do()
		flush.err = err
		c.mu.Lock()
		defer c.mu.Unlock()
		c.inFlight = 0
		c.curFlush = nil
		if err != nil {
			c.queued = append(c.queued, logEntries...)
		} else if len(c.queued) > 0 {
			c.scheduleFlushLocked(c.bufferInterval())
		}
	}()

}

const prodAddr = "https://logging.googleapis.com/"

const userAgent = "gcloud-golang-logging/20150922"

// NewClient returns a new log client, logging to the named log in the
// provided project.
//
// The exported fields on the returned client may be modified before
// the client is used for logging. Once log entries are in flight,
// the fields must not be modified.
func NewClient(ctx context.Context, projectID, logName string, opts ...cloud.ClientOption) (*Client, error) {
	httpClient, endpoint, err := transport.NewHTTPClient(ctx, append([]cloud.ClientOption{
		cloud.WithEndpoint(prodAddr),
		cloud.WithScopes(api.CloudPlatformScope),
		cloud.WithUserAgent(userAgent),
	}, opts...)...)
	if err != nil {
		return nil, err
	}
	svc, err := api.New(httpClient)
	if err != nil {
		return nil, err
	}
	svc.BasePath = endpoint
	c := &Client{
		svc:     svc,
		logs:    api.NewProjectsLogsEntriesService(svc),
		logName: logName,
		projID:  projectID,
	}
	for i := range c.writer {
		level := Level(i)
		c.writer[level] = levelWriter{level, c}
		c.logger[level] = log.New(c.writer[level], "", 0)
	}
	return c, nil
}

// flushCall is an in-flight or completed flush.
type flushCall struct {
	donec chan struct{} // closed when response is in
	err   error         // error is valid after wg is Done
}
<commit_msg>logging: reduce the Scope required to use the logging client.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package logging contains a Google Cloud Logging client.
//
// This package is experimental and subject to API changes.
package logging // import "google.golang.org/cloud/logging"

import (
	"errors"
	"io"
	"log"
	"sync"
	"time"

	"golang.org/x/net/context"
	api "google.golang.org/api/logging/v1beta3"
	"google.golang.org/cloud"
	"google.golang.org/cloud/internal/transport"
)

// Scope is the OAuth2 scope necessary to use Google Cloud Logging.
const Scope = api.LoggingWriteScope

// Level is the log level.
type Level int

const (
	// Default means no assigned severity level.
	Default Level = iota
	Debug
	Info
	Warning
	Error
	Critical
	Alert
	Emergency
	nLevel
)

var levelName = [nLevel]string{
	Default:   "",
	Debug:     "DEBUG",
	Info:      "INFO",
	Warning:   "WARNING",
	Error:     "ERROR",
	Critical:  "CRITICAL",
	Alert:     "ALERT",
	Emergency: "EMERGENCY",
}

func (v Level) String() string {
	return levelName[v]
}

// Client is a Google Cloud Logging client.
// It must be constructed via NewClient.
type Client struct {
	svc     *api.Service
	logs    *api.ProjectsLogsEntriesService
	projID  string
	logName string
	writer  [nLevel]io.Writer
	logger  [nLevel]*log.Logger

	mu          sync.Mutex
	queued      []*api.LogEntry
	curFlush    *flushCall  // currently in-flight flush
	flushTimer  *time.Timer // nil before first use
	timerActive bool        // whether flushTimer is armed
	inFlight    int         // number of log entries sent to API service but not yet ACKed

	// For testing:
	timeNow func() time.Time // optional

	// ServiceName may be "appengine.googleapis.com",
	// "compute.googleapis.com" or "custom.googleapis.com".
	//
	// The default is "custom.googleapis.com".
	//
	// The service name is only used by the API server to
	// determine which of the labels are used to index the logs.
	ServiceName string

	// CommonLabels are metadata labels that apply to all log
	// entries in this request, so that you don't have to repeat
	// them in each log entry's metadata.labels field. If any of
	// the log entries contains a (key, value) with the same key
	// that is in CommonLabels, then the entry's (key, value)
	// overrides the one in CommonLabels.
	CommonLabels map[string]string

	// BufferLimit is the maximum number of items to keep in memory
	// before flushing. Zero means automatic. A value of 1 means to
	// flush after each log entry.
	// The default is currently 10,000.
	BufferLimit int

	// FlushAfter optionally specifies a threshold count at which buffered
	// log entries are flushed, even if the BufferInterval has not yet
	// been reached.
	// The default is currently 10.
	FlushAfter int

	// BufferInterval is the maximum amount of time that an item
	// should remain buffered in memory before being flushed to
	// the logging service.
	// The default is currently 1 second.
	BufferInterval time.Duration

	// Overflow is a function which runs when the Log function
	// overflows its configured buffer limit. If nil, the log
	// entry is dropped. The return value from Overflow is
	// returned by Log.
	Overflow func(*Client, Entry) error
}

func (c *Client) flushAfter() int {
	if v := c.FlushAfter; v > 0 {
		return v
	}
	return 10
}

func (c *Client) bufferInterval() time.Duration {
	if v := c.BufferInterval; v > 0 {
		return v
	}
	return time.Second
}

func (c *Client) bufferLimit() int {
	if v := c.BufferLimit; v > 0 {
		return v
	}
	return 10000
}

func (c *Client) serviceName() string {
	if v := c.ServiceName; v != "" {
		return v
	}
	return "custom.googleapis.com"
}

func (c *Client) now() time.Time {
	if now := c.timeNow; now != nil {
		return now()
	}
	return time.Now()
}

// Writer returns an io.Writer for the provided log level.
//
// Each Write call on the returned Writer generates a log entry.
//
// This Writer accessor does not allocate, so callers do not need to
// cache.
func (c *Client) Writer(v Level) io.Writer { return c.writer[v] }

// Logger returns a *log.Logger for the provided log level.
//
// A Logger for each Level is pre-allocated by NewClient with an empty
// prefix and no flags.  This Logger accessor does not allocate.
// Callers wishing to use alternate flags (such as log.Lshortfile) may
// mutate the returned Logger with SetFlags. Such mutations affect all
// callers in the program.
func (c *Client) Logger(v Level) *log.Logger { return c.logger[v] }

type levelWriter struct {
	level Level
	c     *Client
}

func (w levelWriter) Write(p []byte) (n int, err error) {
	return len(p), w.c.Log(Entry{
		Level:   w.level,
		Payload: string(p),
	})
}

// Entry is a log entry.
type Entry struct {
	// Time is the time of the entry. If the zero value, the current time is used.
	Time time.Time

	// Level is log entry's severity level.
	// The zero value means no assigned severity level.
	Level Level

	// Payload must be either a string, []byte, or something that
	// marshals via the encoding/json package to a JSON object
	// (and not any other type of JSON value).
	Payload interface{}

	// Labels optionally specifies key/value labels for the log entry.
	// Depending on the Client's ServiceName, these are indexed differently
	// by the Cloud Logging Service.
	// See https://cloud.google.com/logging/docs/logs_index
	// The Client.Log method takes ownership of this map.
	Labels map[string]string

	// TODO: de-duping id
}

func (c *Client) apiEntry(e Entry) (*api.LogEntry, error) {
	t := e.Time
	if t.IsZero() {
		t = c.now()
	}

	ent := &api.LogEntry{
		Metadata: &api.LogEntryMetadata{
			Timestamp:   t.UTC().Format(time.RFC3339Nano),
			ServiceName: c.serviceName(),
			Severity:    e.Level.String(),
			Labels:      e.Labels,
		},
	}
	switch p := e.Payload.(type) {
	case string:
		ent.TextPayload = p
	case []byte:
		ent.TextPayload = string(p)
	default:
		ent.StructPayload = api.LogEntryStructPayload(p)
	}
	return ent, nil
}

// LogSync logs e synchronously without any buffering.
// This is mostly intended for debugging or critical errors.
func (c *Client) LogSync(e Entry) error {
	ent, err := c.apiEntry(e)
	if err != nil {
		return err
	}
	_, err = c.logs.Write(c.projID, c.logName, &api.WriteLogEntriesRequest{
		CommonLabels: c.CommonLabels,
		Entries:      []*api.LogEntry{ent},
	}).Do()
	return err
}

var ErrOverflow = errors.New("logging: log entry overflowed buffer limits")

// Log queues an entry to be sent to the logging service, subject to the
// Client's parameters. By default, the log will be flushed within
// one second.
// Log only returns an error if the entry is invalid or the queue is at
// capacity. If the queue is at capacity and the entry can't be added,
// Log returns either ErrOverflow when c.Overflow is nil, or the
// value returned by c.Overflow.
func (c *Client) Log(e Entry) error {
	ent, err := c.apiEntry(e)
	if err != nil {
		return err
	}

	c.mu.Lock()
	buffered := len(c.queued) + c.inFlight

	if buffered >= c.bufferLimit() {
		c.mu.Unlock()
		if fn := c.Overflow; fn != nil {
			return fn(c, e)
		}
		return ErrOverflow
	}
	defer c.mu.Unlock()

	c.queued = append(c.queued, ent)
	if len(c.queued) >= c.flushAfter() {
		c.scheduleFlushLocked(0)
		return nil
	}
	c.scheduleFlushLocked(c.bufferInterval())
	return nil
}

// c.mu must be held.
//
// d will be one of two values: either c.BufferInterval (or its
// default value) or 0.
func (c *Client) scheduleFlushLocked(d time.Duration) {
	if c.inFlight > 0 {
		// For now to keep things simple, only allow one HTTP
		// request in flight at a time.
		return
	}
	switch {
	case c.flushTimer == nil:
		// First flush.
		c.timerActive = true
		c.flushTimer = time.AfterFunc(d, c.timeoutFlush)
	case c.timerActive && d == 0:
		// Make it happen sooner.  For example, this is the
		// case of transitioning from a 1 second flush after
		// the 1st item to an immediate flush after the 10th
		// item.
		c.flushTimer.Reset(0)
	case !c.timerActive:
		c.timerActive = true
		c.flushTimer.Reset(d)
	default:
		// else timer was already active, also at d > 0,
		// so we don't touch it and let it fire as previously
		// scheduled.
	}
}

// timeoutFlush runs in its own goroutine (from time.AfterFunc) and
// flushes c.queued.
func (c *Client) timeoutFlush() {
	c.mu.Lock()
	c.timerActive = false
	c.mu.Unlock()
	if err := c.Flush(); err != nil {
		// schedule another try
		// TODO: smarter back-off?
		c.mu.Lock()
		c.scheduleFlushLocked(5 * time.Second)
		c.mu.Unlock()
	}
}

// Ping reports whether the client's connection to Google Cloud
// Logging and the authentication configuration are valid.
func (c *Client) Ping() error {
	_, err := c.logs.Write(c.projID, c.logName, &api.WriteLogEntriesRequest{
		Entries: []*api.LogEntry{},
	}).Do()
	return err
}

// Flush flushes any buffered log entries.
func (c *Client) Flush() error {
	var numFlush int
	c.mu.Lock()
	for {
		// We're already flushing (or we just started flushing
		// ourselves), so wait for it to finish.
		if f := c.curFlush; f != nil {
			wasEmpty := len(c.queued) == 0
			c.mu.Unlock()
			<-f.donec // wait for it
			numFlush++
			// Terminate whenever there's an error, we've
			// already flushed twice (one that was already
			// in-flight when flush was called, and then
			// one we instigated), or the queue was empty
			// when we released the locked (meaning this
			// in-flight flush removes everything present
			// when Flush was called, and we don't need to
			// kick off a new flush for things arriving
			// afterward)
			if f.err != nil || numFlush == 2 || wasEmpty {
				return f.err
			}
			// Otherwise, re-obtain the lock and loop,
			// starting over with seeing if a flush is in
			// progress, which might've been started by a
			// different goroutine before aquiring this
			// lock again.
			c.mu.Lock()
			continue
		}

		// Terminal case:
		if len(c.queued) == 0 {
			c.mu.Unlock()
			return nil
		}

		c.startFlushLocked()
	}
}

// requires c.mu be held.
func (c *Client) startFlushLocked() {
	if c.curFlush != nil {
		panic("internal error: flush already in flight")
	}
	if len(c.queued) == 0 {
		panic("internal error: no items queued")
	}
	logEntries := c.queued
	c.inFlight = len(logEntries)
	c.queued = nil

	flush := &flushCall{
		donec: make(chan struct{}),
	}
	c.curFlush = flush
	go func() {
		defer close(flush.donec)
		_, err := c.logs.Write(c.projID, c.logName, &api.WriteLogEntriesRequest{
			CommonLabels: c.CommonLabels,
			Entries:      logEntries,
		}).Do()
		flush.err = err
		c.mu.Lock()
		defer c.mu.Unlock()
		c.inFlight = 0
		c.curFlush = nil
		if err != nil {
			c.queued = append(c.queued, logEntries...)
		} else if len(c.queued) > 0 {
			c.scheduleFlushLocked(c.bufferInterval())
		}
	}()

}

const prodAddr = "https://logging.googleapis.com/"

const userAgent = "gcloud-golang-logging/20150922"

// NewClient returns a new log client, logging to the named log in the
// provided project.
//
// The exported fields on the returned client may be modified before
// the client is used for logging. Once log entries are in flight,
// the fields must not be modified.
func NewClient(ctx context.Context, projectID, logName string, opts ...cloud.ClientOption) (*Client, error) {
	httpClient, endpoint, err := transport.NewHTTPClient(ctx, append([]cloud.ClientOption{
		cloud.WithEndpoint(prodAddr),
		cloud.WithScopes(api.CloudPlatformScope),
		cloud.WithUserAgent(userAgent),
	}, opts...)...)
	if err != nil {
		return nil, err
	}
	svc, err := api.New(httpClient)
	if err != nil {
		return nil, err
	}
	svc.BasePath = endpoint
	c := &Client{
		svc:     svc,
		logs:    api.NewProjectsLogsEntriesService(svc),
		logName: logName,
		projID:  projectID,
	}
	for i := range c.writer {
		level := Level(i)
		c.writer[level] = levelWriter{level, c}
		c.logger[level] = log.New(c.writer[level], "", 0)
	}
	return c, nil
}

// flushCall is an in-flight or completed flush.
type flushCall struct {
	donec chan struct{} // closed when response is in
	err   error         // error is valid after wg is Done
}
<|endoftext|>


<commit_before>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package logging provides a custom logging implementation for webborer.
package logging

import (
	"fmt"
	"log"
	"os"
	"strings"
)

const (
	LogDebug = iota
	LogInfo
	LogWarning
	LogError
	LogFatal
	logLevelMax
)

var LogLevelStrings = [...]string{
	"DEBUG",
	"INFO",
	"WARNING",
	"ERROR",
	"FATAL",
}

var logLevel = LogWarning
var defaultLogger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)

func ResetLog(logfilePath, logLevel string) {
	if len(logfilePath) > 0 {
		if fp, err := os.Create(logfilePath); err == nil {
			defaultLogger = log.New(fp, "", log.Ldate|log.Ltime|log.Lshortfile)
		} else {
			Logf(LogError, "Unable to open logfile %s.", logfilePath)
		}
	}
	if len(logLevel) > 0 {
		SetLogLevel(logLevel)
	}
}

// Log a formatted string
func Logf(level int, format string, args ...interface{}) {
	if level < logLevel {
		return
	}
	msg := fmt.Sprintf(format, args...)
	msg = fmt.Sprintf("[%s] %s", LogLevelStrings[level], msg)
	defaultLogger.Output(2, msg)
}

// Log at Debug level
func Debugf(format string, args ...interface{}) {
  Logf(LogDebug, format, args...)
}

// Log at Info level
func Infof(format string, args ...interface{}) {
  Logf(LogInfo, format, args...)
}

// Log at Warning level
func Warningf(format string, args ...interface{}) {
  Logf(LogWarning, format, args...)
}
var Warnf = Warningf

// Log at Error level
func Errorf(format string, args ...interface{}) {
  Logf(LogError, format, args...)
}

// Log at Fatal level
func Fatalf(format string, args ...interface{}) {
  Logf(LogFatal, format, args...)
}

func SetLogLevel(level string) {
	level = strings.ToLower(level)
	for i, ll := range LogLevelStrings {
		if strings.ToLower(ll) == level {
			logLevel = i
			return
		}
	}
}
<commit_msg>Fix logdepth.<commit_after>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package logging provides a custom logging implementation for webborer.
package logging

import (
	"fmt"
	"log"
	"os"
	"strings"
)

const (
	LogDebug = iota
	LogInfo
	LogWarning
	LogError
	LogFatal
	logLevelMax
)

var LogLevelStrings = [...]string{
	"DEBUG",
	"INFO",
	"WARNING",
	"ERROR",
	"FATAL",
}

var logLevel = LogWarning
var defaultLogger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)

func ResetLog(logfilePath, logLevel string) {
	if len(logfilePath) > 0 {
		if fp, err := os.Create(logfilePath); err == nil {
			defaultLogger = log.New(fp, "", log.Ldate|log.Ltime|log.Lshortfile)
		} else {
			Logf(LogError, "Unable to open logfile %s.", logfilePath)
		}
	}
	if len(logLevel) > 0 {
		SetLogLevel(logLevel)
	}
}

// Log a formatted string
func Logf(level int, format string, args ...interface{}) {
  realLogf(level, format, args...)
}

func realLogf(level int, format string, args ...interface{}) {
	if level < logLevel {
		return
	}
	msg := fmt.Sprintf(format, args...)
	msg = fmt.Sprintf("[%s] %s", LogLevelStrings[level], msg)
	defaultLogger.Output(3, msg)
}

// Log at Debug level
func Debugf(format string, args ...interface{}) {
  realLogf(LogDebug, format, args...)
}

// Log at Info level
func Infof(format string, args ...interface{}) {
  realLogf(LogInfo, format, args...)
}

// Log at Warning level
func Warningf(format string, args ...interface{}) {
  realLogf(LogWarning, format, args...)
}
var Warnf = Warningf

// Log at Error level
func Errorf(format string, args ...interface{}) {
  realLogf(LogError, format, args...)
}

// Log at Fatal level
func Fatalf(format string, args ...interface{}) {
  realLogf(LogFatal, format, args...)
}

func SetLogLevel(level string) {
	level = strings.ToLower(level)
	for i, ll := range LogLevelStrings {
		if strings.ToLower(ll) == level {
			logLevel = i
			return
		}
	}
}
<|endoftext|>