<commit_before>// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com

package github

const commitsTemplate = `

{{if .HasAuthorStats}}

Contributors

		There
		{{if eq 1 (len .AuthorStats)}}is{{else}}are{{end}}
		{{len .AuthorStats}}
		{{if eq 1 (len .AuthorStats)}}contributor{{else}}contributors{{end}}
		across {{.RepoCount}}
		{{if eq 1 .RepoCount}} repository. {{else}} repositories. {{end}}

		{{range $stats := .AuthorStats}}

{{$stats.Author}}

					{{if gt $stats.OpenIssues 0}}
						has been assigned {{$stats.OpenIssues}}
						{{if eq 1 $stats.OpenIssues}} issue,
					 	{{else}} issues, {{end}}
					 {{end}}
					 {{if gt $stats.ClosedIssues 0}}
						{{$stats.ClosedIssues}} have been closed,
					{{end}}

					{{if gt $stats.CommitCount 0}}
						has made {{$stats.CommitCount}}
						{{if eq 1 $stats.CommitCount}} commit {{else}} commits {{end}}
						on {{len $stats.Repos}} {{if eq 1 (len $stats.Repos)}} branch. {{else}} branches. {{end}}

						{{range $repo := $stats.Repos}}	{{$repo}}, {{end}}
					{{end}}

		{{end}}

{{end}}

{{if .HasCommits}}

Commits

 There are {{len .BranchCommits}} commits by {{len .AuthorStats}} contributors
		across {{.RepoCount}}
		{{if eq 1 .RepoCount}} repository. {{else}} repositories. {{end}}

		{{range $commit := .BranchCommits}}

					{{if $commit.ShowUser}}

{{$commit.Name}}

					{{end}}
					{{$commit.Message}}

					{{if $commit.ShowDate}}{{$commit.Date}}{{end}}

					{{if $commit.ShowBranch}}{{$commit.Repo}}:{{$commit.Branch}}{{end}}

		{{end}}

{{end}}

`
<commit_msg>Show correct number of contributors<commit_after>// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com

package github

const commitsTemplate = `

{{if .HasAuthorStats}}

Contributors

		There
		{{if eq 1 (len .AuthorStats)}}is{{else}}are{{end}}
		{{len .AuthorStats}}
		{{if eq 1 (len .AuthorStats)}}contributor{{else}}contributors{{end}}
		across {{.RepoCount}}
		{{if eq 1 .RepoCount}} repository. {{else}} repositories. {{end}}

		{{range $stats := .AuthorStats}}

{{$stats.Author}}

					{{if gt $stats.OpenIssues 0}}
						has been assigned {{$stats.OpenIssues}}
						{{if eq 1 $stats.OpenIssues}} issue,
					 	{{else}} issues, {{end}}
					 {{end}}
					 {{if gt $stats.ClosedIssues 0}}
						{{$stats.ClosedIssues}} have been closed,
					{{end}}

					{{if gt $stats.CommitCount 0}}
						has made {{$stats.CommitCount}}
						{{if eq 1 $stats.CommitCount}} commit {{else}} commits {{end}}
						on {{len $stats.Repos}} {{if eq 1 (len $stats.Repos)}} branch. {{else}} branches. {{end}}

						{{range $repo := $stats.Repos}}	{{$repo}}, {{end}}
					{{end}}

		{{end}}

{{end}}

{{if .HasCommits}}

Commits

 There are {{len .BranchCommits}} commits by {{.NumContributors}} contributors
		across {{.RepoCount}}
		{{if eq 1 .RepoCount}} repository. {{else}} repositories. {{end}}

		{{range $commit := .BranchCommits}}

					{{if $commit.ShowUser}}

{{$commit.Name}}

					{{end}}
					{{$commit.Message}}

					{{if $commit.ShowDate}}{{$commit.Date}}{{end}}

					{{if $commit.ShowBranch}}{{$commit.Repo}}:{{$commit.Branch}}{{end}}

		{{end}}

{{end}}

`
<|endoftext|>


<commit_before>package main

import (
  "github.com/go-martini/martini"
  "github.com/robfig/cron"
  "fmt"
  "os"
  "os/exec"
)

func main() {
  m := martini.Classic()
  c := cron.New()
  c.AddFunc("0 10 10 * * *", func() {
    KiasuAppend()
    KiasuCommit()
    KiasuPush()
  })
  c.Start()
  m.Run()
}

func KiasuAppend() {
  f, _ := os.OpenFile("kiasu.log", os.O_APPEND|os.O_WRONLY, 0600)
  f.WriteString("kiasu\n")
}

func KiasuCommit() {
  exec.Command("git", "commit", "-am", "I kiasu everyday").Output()
}

func KiasuPush() {
  exec.Command("git", "push", "origin", "master").Output()
}
<commit_msg>remove fmt<commit_after>package main

import (
  "github.com/go-martini/martini"
  "github.com/robfig/cron"
  "os"
  "os/exec"
)

func main() {
  m := martini.Classic()
  c := cron.New()
  c.AddFunc("0 10 10 * * *", func() {
    KiasuAppend()
    KiasuCommit()
    KiasuPush()
  })
  c.Start()
  m.Run()
}

func KiasuAppend() {
  f, _ := os.OpenFile("kiasu.log", os.O_APPEND|os.O_WRONLY, 0600)
  f.WriteString("kiasu\n")
}

func KiasuCommit() {
  exec.Command("git", "commit", "-am", "I kiasu everyday").Output()
}

func KiasuPush() {
  exec.Command("git", "push", "origin", "master").Output()
}
<|endoftext|>


<commit_before>package slice_struct

func Init(elements ...interface{}) []interface{} {
    var struct_slice = Allocate()
    for _, element := range elements {
        struct_slice = Extend(struct_slice, element)
    }
    return struct_slice
}

func Allocate() []interface{} {
    return make([]interface{}, 0, 1)
}

func Extend(slice []interface{}, elements ...interface{}) []interface{} {
    for _, element := range elements {
        n := len(slice)
        if n == cap(slice) {
            // Slice is full; must grow.
            // We double its size and add 1, so if the size is zero we still grow.
            newSlice := make([]interface{}, len(slice), 2 * len(slice) + 1)
            copy(newSlice, slice)
            slice = newSlice
        }
        slice = slice[0 : n + 1]
        slice[n] = element
    }
    return slice
}<commit_msg>test commit<commit_after>package slice_struct

func Init(elements ...interface{}) []interface{} {
	var struct_slice = Allocate()
	for _, element := range elements {
		struct_slice = Extend(struct_slice, element)
	}
	return struct_slice
}

func Allocate() []interface{} {
	return make([]interface{}, 0, 1)
}

func Extend(slice []interface{}, elements ...interface{}) []interface{} {
	for _, element := range elements {
		n := len(slice)
		if n == cap(slice) {
			// Slice is full; must grow.
			// We double its size and add 1, so if the size is zero we still grow.
			newSlice := make([]interface{}, len(slice), 2*len(slice)+1)
			copy(newSlice, slice)
			slice = newSlice
		}
		slice = slice[0 : n+1]
		slice[n] = element
	}
	return slice
}
<|endoftext|>


<commit_before>// Copyright 2011, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
 *  Filename:    queue.go
 *  Package:     dispatch
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     Wed Jul  6 17:30:20 PDT 2011
 *  Description:
 */

//  Package queues defines the Queue interface used in package dispatch,
//  and several Queue implementations.
package queues
import (
)

//  A Task is the interface satisfied by objects passed to a Dispatch.
type Task interface {
    SetFunc(func (id int64))
    Func() func (id int64)
    Type() string                 // Used mostly for debugging
}
//  A Task given to a Dispatch is given a unique id and becomes a
//  RegisteredTask.
type RegisteredTask interface {
    Task() Task
    Func() func (id int64)
    Id()   int64
}

func registeredTaskSearch(rts []RegisteredTask, less func(t RegisteredTask)bool) int {
    var (
        low  = 0
        high = len(rts)
        mid  = (high-low)/2
        t    RegisteredTask
    )
    if high == 0 || less(rts[0]){
        return 0
    }
    if !less(rts[high]) {
        return high
    }
    for low < high {
        t = rts[mid]
        var leftSide = less(t)
        switch leftSide {
        case true:
            high = mid
        case false:
            low = mid
        }
        mid = low + (high-low)/2
    }
    return low
}

//  A Queue is a queue for RegisteredTasks, used by a Dispatch.
type Queue interface {
    Enqueue(task RegisteredTask)  // Insert a DispatchTask
    Dequeue() RegisteredTask      // Remove the next task.
    Len() int                     // Number of items to be processed.
    SetKey(int64, float64)        // Set a task's key (priority queues).
}

//  A naive First In First Out (FIFO) Queue.
type FIFO struct {
    head, tail  int
    length      int
    circ        []RegisteredTask
}
//  Create a new FIFO.
func NewFIFO() *FIFO {
    var q = new(FIFO)
    q.circ = make([]RegisteredTask, 10)
    q.head = 0
    q.tail = 0
    q.length = 0
    return q
}

//  See Queue.
func (dq *FIFO) Len() int {
    return dq.length
}
//  See Queue.
func (dq *FIFO) Enqueue(task RegisteredTask) {
    var n = len(dq.circ)
    if dq.length == len(dq.circ) {
        // Copy the circular slice into a new slice with twice the length.
        var tmp = dq.circ
        dq.circ = make([]RegisteredTask, 2*n)
        for i := 0 ; i < n ; i++ {
            var j = (dq.head+i)%n
            dq.circ[i] = tmp[j]
            tmp[j] = nil
        }
        dq.head = 0
        dq.tail = n
    }
    dq.circ[dq.tail] = task
    dq.tail = (dq.tail+1)%n
    dq.length++
}
//  See Queue.
func (dq *FIFO) Dequeue() RegisteredTask {
    if dq.length == 0 {
        panic("empty")
    }
    var task = dq.circ[dq.head]
    dq.head = (dq.head+1)%dq.length
    dq.length--
    return task
}
//  Does nothing. See Queue.
func (dq *FIFO) SetKey(id int64, k float64) { }

//  A naive Last In First Out (LIFO) Queue (also known as a stack).
type LIFO struct {
    top    int
    stack   []RegisteredTask
}
//  Create a new LIFO.
func NewLIFO() *LIFO {
    var q = new(LIFO)
    q.stack = make([]RegisteredTask, 10)
    q.top = 0
    return q
}

//  See Queue.
func (dq *LIFO) Len() int {
    return dq.top
}
//  See Queue.
func (dq *LIFO) Enqueue(task RegisteredTask) {
    var n = len(dq.stack)
    if dq.top == n {
        var tmpstack = dq.stack
        dq.stack = make([]RegisteredTask, 2*n)
        copy(dq.stack, tmpstack)
    }
    dq.stack[dq.top] = task
    dq.top++
}
//  See Queue.
func (dq *LIFO) Dequeue() RegisteredTask {
    if dq.top == 0 {
        panic("empty")
    }
    dq.top--
    var task = dq.stack[dq.top]
    dq.stack[dq.top] = nil
    return task
}
//  Does nothing. See Queue.
func (dq *LIFO) SetKey(id int64, k float64) { }
<commit_msg>Fix short circuit indexing error in binary search.<commit_after>// Copyright 2011, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
 *  Filename:    queue.go
 *  Package:     dispatch
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     Wed Jul  6 17:30:20 PDT 2011
 *  Description:
 */

//  Package queues defines the Queue interface used in package dispatch,
//  and several Queue implementations.
package queues
import (
)

//  A Task is the interface satisfied by objects passed to a Dispatch.
type Task interface {
    SetFunc(func (id int64))
    Func() func (id int64)
    Type() string                 // Used mostly for debugging
}
//  A Task given to a Dispatch is given a unique id and becomes a
//  RegisteredTask.
type RegisteredTask interface {
    Task() Task
    Func() func (id int64)
    Id()   int64
}

func registeredTaskSearch(rts []RegisteredTask, less func(t RegisteredTask)bool) int {
    var (
        low  = 0
        high = len(rts)
        mid  = (high-low)/2
        t    RegisteredTask
    )
    if high == 0 || less(rts[0]){
        return 0
    }
    if !less(rts[high-1]) {
        return high
    }
    for low < high {
        t = rts[mid]
        var leftSide = less(t)
        switch leftSide {
        case true:
            high = mid
        case false:
            low = mid
        }
        mid = low + (high-low)/2
    }
    return low
}

//  A Queue is a queue for RegisteredTasks, used by a Dispatch.
type Queue interface {
    Enqueue(task RegisteredTask)  // Insert a DispatchTask
    Dequeue() RegisteredTask      // Remove the next task.
    Len() int                     // Number of items to be processed.
    SetKey(int64, float64)        // Set a task's key (priority queues).
}

//  A naive First In First Out (FIFO) Queue.
type FIFO struct {
    head, tail  int
    length      int
    circ        []RegisteredTask
}
//  Create a new FIFO.
func NewFIFO() *FIFO {
    var q = new(FIFO)
    q.circ = make([]RegisteredTask, 10)
    q.head = 0
    q.tail = 0
    q.length = 0
    return q
}

//  See Queue.
func (dq *FIFO) Len() int {
    return dq.length
}
//  See Queue.
func (dq *FIFO) Enqueue(task RegisteredTask) {
    var n = len(dq.circ)
    if dq.length == len(dq.circ) {
        // Copy the circular slice into a new slice with twice the length.
        var tmp = dq.circ
        dq.circ = make([]RegisteredTask, 2*n)
        for i := 0 ; i < n ; i++ {
            var j = (dq.head+i)%n
            dq.circ[i] = tmp[j]
            tmp[j] = nil
        }
        dq.head = 0
        dq.tail = n
    }
    dq.circ[dq.tail] = task
    dq.tail = (dq.tail+1)%n
    dq.length++
}
//  See Queue.
func (dq *FIFO) Dequeue() RegisteredTask {
    if dq.length == 0 {
        panic("empty")
    }
    var task = dq.circ[dq.head]
    dq.head = (dq.head+1)%dq.length
    dq.length--
    return task
}
//  Does nothing. See Queue.
func (dq *FIFO) SetKey(id int64, k float64) { }

//  A naive Last In First Out (LIFO) Queue (also known as a stack).
type LIFO struct {
    top    int
    stack   []RegisteredTask
}
//  Create a new LIFO.
func NewLIFO() *LIFO {
    var q = new(LIFO)
    q.stack = make([]RegisteredTask, 10)
    q.top = 0
    return q
}

//  See Queue.
func (dq *LIFO) Len() int {
    return dq.top
}
//  See Queue.
func (dq *LIFO) Enqueue(task RegisteredTask) {
    var n = len(dq.stack)
    if dq.top == n {
        var tmpstack = dq.stack
        dq.stack = make([]RegisteredTask, 2*n)
        copy(dq.stack, tmpstack)
    }
    dq.stack[dq.top] = task
    dq.top++
}
//  See Queue.
func (dq *LIFO) Dequeue() RegisteredTask {
    if dq.top == 0 {
        panic("empty")
    }
    dq.top--
    var task = dq.stack[dq.top]
    dq.stack[dq.top] = nil
    return task
}
//  Does nothing. See Queue.
func (dq *LIFO) SetKey(id int64, k float64) { }
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2019 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"time"

	log "github.com/Sirupsen/logrus"
	hue "github.com/stefanwichmann/go.hue"
)

// Light represents a light kelvin can automate in your system.
type Light struct {
	ID               int        `json:"id"`
	Name             string     `json:"name"`
	HueLight         HueLight   `json:"-"`
	TargetLightState LightState `json:"targetLightState,omitempty"`
	Scheduled        bool       `json:"scheduled"`
	Reachable        bool       `json:"reachable"`
	On               bool       `json:"on"`
	Tracking         bool       `json:"-"`
	Automatic        bool       `json:"automatic"`
	Schedule         Schedule   `json:"-"`
	Interval         Interval   `json:"interval"`
	Appearance       time.Time  `json:"-"`
}

func (light *Light) updateCurrentLightState(attr hue.LightAttributes) error {
	light.HueLight.updateCurrentLightState(attr)
	light.Reachable = light.HueLight.Reachable
	light.On = light.HueLight.On
	return nil
}

func (light *Light) update() (bool, error) {
	// Is the light associated to any schedule?
	if !light.Scheduled {
		return false, nil
	}

	// If the light is not reachable anymore clean up
	if !light.Reachable {
		if light.Tracking {
			log.Printf("💡 Light %s - Light is no longer reachable. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// If the light was turned off clean up
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s - Light was turned off. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s - Light just appeared.", light.Name)
		light.Tracking = true
		light.Appearance = time.Now()

		// Should we auto-enable Kelvin?
		if light.Schedule.enableWhenLightsAppear {
			log.Printf("💡 Light %s - Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				log.Debugf("💡 Light %s - Could not initialize light after %v", light.Name, time.Since(light.Appearance))
				return false, err
			}

			light.Automatic = true
			log.Debugf("💡 Light %s - Light was initialized to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		// return if we should ignore color temperature and brightness
		if light.TargetLightState.ColorTemperature == -1 && light.TargetLightState.Brightness == -1 {
			return false, nil
		}

		// if status == scene state --> Activate Kelvin
		if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
			log.Printf("💡 Light %s - Detected matching target state. Activating Kelvin...", light.Name)
			light.Automatic = true

			// set correct target lightstate on HueLight
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				return false, err
			}
			log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Scene detection)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Did the user manually change the light state?
	if light.HueLight.hasChanged() {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s - Light state has been changed manually after %v: %+v", light.Name, time.Since(light.Appearance), light.HueLight)
		} else {
			log.Printf("💡 Light %s - Light state has been changed manually. Disabling Kelvin...", light.Name)
		}
		light.Automatic = false
		return false, nil
	}

	// Update of lightstate needed?
	if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
		return false, nil
	}

	// Light is turned on and in automatic state. Set target lightstate.
	err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	if err != nil {
		return false, err
	}

	log.Printf("💡 Light %s - Updated light state to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	return true, nil
}

func (light *Light) updateSchedule(schedule Schedule) {
	light.Schedule = schedule
	light.Scheduled = true
	log.Printf("💡 Light %s - Activating schedule for %v (Sunrise: %v, Sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Printf("💡 Light %s - Light has no active interval. Ignoring...", light.Name)
		light.Interval = newInterval // Assign empty interval
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s - Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No target light state to update...", light.Name)
		return
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// Did the target light state change?
	if newLightState.equals(light.TargetLightState) {
		return
	}

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && light.TargetLightState.Brightness == 0 {
		log.Debugf("💡 Light %s - Initialized target light state for the interval %v - %v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), newLightState)
	} else {
		log.Debugf("💡 Light %s - Updated target light state for the interval %v - %v from %+v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), light.TargetLightState, newLightState)
	}

	light.TargetLightState = newLightState
}
<commit_msg>Correctly handle lights in manual mode<commit_after>// MIT License
//
// Copyright (c) 2019 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"time"

	log "github.com/Sirupsen/logrus"
	hue "github.com/stefanwichmann/go.hue"
)

// Light represents a light kelvin can automate in your system.
type Light struct {
	ID               int        `json:"id"`
	Name             string     `json:"name"`
	HueLight         HueLight   `json:"-"`
	TargetLightState LightState `json:"targetLightState,omitempty"`
	Scheduled        bool       `json:"scheduled"`
	Reachable        bool       `json:"reachable"`
	On               bool       `json:"on"`
	Tracking         bool       `json:"-"`
	Automatic        bool       `json:"automatic"`
	Schedule         Schedule   `json:"-"`
	Interval         Interval   `json:"interval"`
	Appearance       time.Time  `json:"-"`
}

func (light *Light) updateCurrentLightState(attr hue.LightAttributes) error {
	light.HueLight.updateCurrentLightState(attr)
	light.Reachable = light.HueLight.Reachable
	light.On = light.HueLight.On
	return nil
}

func (light *Light) update() (bool, error) {
	// Is the light associated to any schedule?
	if !light.Scheduled {
		return false, nil
	}

	// If the light is not reachable anymore clean up
	if !light.Reachable {
		if light.Tracking {
			log.Printf("💡 Light %s - Light is no longer reachable. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// If the light was turned off clean up
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s - Light was turned off. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s - Light just appeared.", light.Name)
		light.Tracking = true
		light.Appearance = time.Now()

		// Should we auto-enable Kelvin?
		if light.Schedule.enableWhenLightsAppear {
			log.Printf("💡 Light %s - Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				log.Debugf("💡 Light %s - Could not initialize light after %v", light.Name, time.Since(light.Appearance))
				return false, err
			}

			light.Automatic = true
			log.Debugf("💡 Light %s - Light was initialized to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		// return if we should ignore color temperature and brightness
		if light.TargetLightState.ColorTemperature == -1 && light.TargetLightState.Brightness == -1 {
			return false, nil
		}

		// if status == scene state --> Activate Kelvin
		if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
			log.Printf("💡 Light %s - Detected matching target state. Activating Kelvin...", light.Name)
			light.Automatic = true

			// set correct target lightstate on HueLight
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				return false, err
			}
			log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Scene detection)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		// Light was changed manually and does not conform to scene detection
		return false, nil
	}

	// Did the user manually change the light state?
	if light.HueLight.hasChanged() {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s - Light state has been changed manually after %v (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, time.Since(light.Appearance), light.HueLight.TargetColorTemperature, light.HueLight.CurrentColorTemperature, light.HueLight.TargetColor, light.HueLight.CurrentColor, light.HueLight.TargetBrightness, light.HueLight.CurrentBrightness)
		} else {
			log.Printf("💡 Light %s - Light state has been changed manually. Disabling Kelvin...", light.Name)
		}
		light.Automatic = false
		return false, nil
	}

	// Update of lightstate needed?
	if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
		return false, nil
	}

	// Light is turned on and in automatic state. Set target lightstate.
	err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	if err != nil {
		return false, err
	}

	log.Printf("💡 Light %s - Updated light state to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	return true, nil
}

func (light *Light) updateSchedule(schedule Schedule) {
	light.Schedule = schedule
	light.Scheduled = true
	log.Printf("💡 Light %s - Activating schedule for %v (Sunrise: %v, Sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Printf("💡 Light %s - Light has no active interval. Ignoring...", light.Name)
		light.Interval = newInterval // Assign empty interval
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s - Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No target light state to update...", light.Name)
		return
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// Did the target light state change?
	if newLightState.equals(light.TargetLightState) {
		return
	}

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && light.TargetLightState.Brightness == 0 {
		log.Debugf("💡 Light %s - Initialized target light state for the interval %v - %v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), newLightState)
	} else {
		log.Debugf("💡 Light %s - Updated target light state for the interval %v - %v from %+v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), light.TargetLightState, newLightState)
	}

	light.TargetLightState = newLightState
}
<|endoftext|>


<commit_before>package nms

import (
	"fmt"
	"strings"
	"time"

	"github.com/briandowns/spinner"

	ui "github.com/gizak/termui"
)

const (
	maxRowTermUI = 45
)

// Widgets represents termui widgets
type Widgets struct {
	header   *ui.Par
	footer   *ui.Par
	menu     *ui.Par
	ifName   *ui.List
	ifStatus *ui.List
	ifDescr  *ui.List
	ifTIn    *ui.List
	ifTOut   *ui.List
	ifPIn    *ui.List
	ifPOut   *ui.List
	ifDIn    *ui.List
	ifDOut   *ui.List
	ifEIn    *ui.List
	ifEOut   *ui.List
}

func initWidgets() *Widgets {
	return &Widgets{
		header:   ui.NewPar(""),
		footer:   ui.NewPar(""),
		menu:     ui.NewPar(""),
		ifName:   ui.NewList(),
		ifStatus: ui.NewList(),
		ifDescr:  ui.NewList(),
		ifTIn:    ui.NewList(),
		ifTOut:   ui.NewList(),
		ifPIn:    ui.NewList(),
		ifPOut:   ui.NewList(),
		ifDIn:    ui.NewList(),
		ifDOut:   ui.NewList(),
		ifEIn:    ui.NewList(),
		ifEOut:   ui.NewList(),
	}
}

func (w *Widgets) updateFrame(c *Client) {
	var (
		h = fmt.Sprintf("──[ myLG ]── Quick NMS SNMP - %s ",
			c.SNMP.Host,
		)
		m = "Press [q] to quit"
	)

	h = h + strings.Repeat(" ", ui.TermWidth()-len(h)+2)

	w.header.Width = ui.TermWidth()
	w.header.Height = 1
	w.header.Y = 1
	w.header.Text = h
	w.header.TextBgColor = ui.ColorCyan
	w.header.TextFgColor = ui.ColorBlack
	w.header.Border = false

	w.footer.Width = ui.TermWidth()
	w.footer.Height = 1
	w.footer.Y = 1
	w.footer.Text = strings.Repeat("─", ui.TermWidth()-6)
	w.footer.TextBgColor = ui.ColorDefault
	w.footer.TextFgColor = ui.ColorCyan
	w.footer.Border = false

	w.menu.Width = ui.TermWidth()
	w.menu.Height = 1
	w.menu.Y = 1
	w.menu.Text = m
	w.menu.TextFgColor = ui.ColorDefault
	w.menu.Border = false

	ui.Render(ui.Body)
}

func (c *Client) snmpShowInterfaceTermUI(filter string, flag map[string]interface{}) error {
	var (
		spin   = spinner.New(spinner.CharSets[26], 220*time.Millisecond)
		s1, s2 [][]string
		idxs   []int
		err    error
	)

	spin.Prefix = "initializing "
	spin.Start()

	if len(strings.TrimSpace(filter)) > 1 {
		idxs = c.snmpGetIdx(filter)
	}

	s1, err = c.snmpGetInterfaces(idxs)
	if err != nil {
		spin.Stop()
		return err
	}
	if len(s1)-1 < 1 {
		spin.Stop()
		return fmt.Errorf("could not find any interface")
	}

	spin.Stop()

	if len(s1) > maxRowTermUI {
		return fmt.Errorf("result can not fit on the screen please try filter")
	}

	ui.DefaultEvtStream = ui.NewEvtStream()
	if err := ui.Init(); err != nil {
		return err
	}
	defer ui.Close()
	w := initWidgets()

	wList := []*ui.List{
		w.ifName,
		w.ifStatus,
		w.ifDescr,
		w.ifTIn,
		w.ifTOut,
		w.ifPIn,
		w.ifPOut,
		w.ifDIn,
		w.ifDOut,
		w.ifEIn,
		w.ifEOut,
	}

	for _, l := range wList {
		l.Items = make([]string, maxRowTermUI+5)
		l.X = 0
		l.Y = 0
		l.Height = len(s1)
		l.Border = false
		l.PaddingRight = 0
		l.PaddingLeft = 0
	}

	for i, v := range s1[0] {
		wList[i].Items[0] = fmt.Sprintf("[%s](fg-magenta,fg-bold)", v)
	}

	for i, v := range s1[1:] {
		w.ifName.Items[i+1] = v[0]
		w.ifStatus.Items[i+1] = ifStatus(v[1])
		w.ifDescr.Items[i+1] = v[2]
		for _, l := range wList[3:] {
			l.Items[i+1] = "-"
		}
	}

	w.updateFrame(c)

	screen := []*ui.Row{
		ui.NewRow(
			ui.NewCol(12, 0, w.header),
		),
		ui.NewRow(
			ui.NewCol(12, 0, w.menu),
		),
		ui.NewRow(
			ui.NewCol(1, 0, w.ifName),
			ui.NewCol(1, 0, w.ifStatus),
			ui.NewCol(2, 0, w.ifDescr),
			ui.NewCol(1, 0, w.ifTIn),
			ui.NewCol(1, 0, w.ifTOut),
			ui.NewCol(1, 0, w.ifPIn),
			ui.NewCol(1, 0, w.ifPOut),
			ui.NewCol(1, 0, w.ifDIn),
			ui.NewCol(1, 0, w.ifDOut),
			ui.NewCol(1, 0, w.ifEIn),
			ui.NewCol(1, 0, w.ifEOut),
		),
		ui.NewRow(
			ui.NewCol(12, 0, w.footer),
		),
	}

	ui.Handle("/timer/1s", func(e ui.Event) {
		t := e.Data.(ui.EvtTimer)
		if t.Count%10 != 0 {
			return
		}

		s2, err = c.snmpGetInterfaces(idxs)
		if err != nil {
			ui.StopLoop()
		}

		for i := range s2[1:] {
			rows := normalize(s1[i+1], s2[i+1], 10)
			for c := range wList {
				wList[c].Items[i+1] = rows[c]
			}
		}

		copy(s1, s2)
		ui.Render(ui.Body)
	})

	ui.Handle("/sys/kbd/q", func(ui.Event) {
		ui.StopLoop()
	})

	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		w.updateFrame(c)
		ui.Body.Width = ui.TermWidth()
		ui.Body.Align()
		ui.Render(ui.Body)
	})

	ui.Body.AddRows(screen...)
	ui.Body.Align()
	ui.Render(ui.Body)

	ui.Loop()
	return nil
}
<commit_msg>err handling, optimized code<commit_after>package nms

import (
	"fmt"
	"strings"
	"time"

	"github.com/briandowns/spinner"

	ui "github.com/gizak/termui"
)

const (
	maxRowTermUI = 45
)

// Widgets represents termui widgets
type Widgets struct {
	header   *ui.Par
	footer   *ui.Par
	menu     *ui.Par
	ifName   *ui.List
	ifStatus *ui.List
	ifDescr  *ui.List
	ifTIn    *ui.List
	ifTOut   *ui.List
	ifPIn    *ui.List
	ifPOut   *ui.List
	ifDIn    *ui.List
	ifDOut   *ui.List
	ifEIn    *ui.List
	ifEOut   *ui.List
}

func initWidgets() *Widgets {
	return &Widgets{
		header:   ui.NewPar(""),
		footer:   ui.NewPar(""),
		menu:     ui.NewPar(""),
		ifName:   ui.NewList(),
		ifStatus: ui.NewList(),
		ifDescr:  ui.NewList(),
		ifTIn:    ui.NewList(),
		ifTOut:   ui.NewList(),
		ifPIn:    ui.NewList(),
		ifPOut:   ui.NewList(),
		ifDIn:    ui.NewList(),
		ifDOut:   ui.NewList(),
		ifEIn:    ui.NewList(),
		ifEOut:   ui.NewList(),
	}
}

func (w *Widgets) updateFrame(c *Client, err string) {
	var (
		h = fmt.Sprintf("──[ myLG ]── Quick NMS SNMP - %s [%s](fg-red,fg-bold)",
			c.SNMP.Host,
			err,
		)
		m = "Press [q] to quit"
	)

	if c := ui.TermWidth() - len(h) + 2 + 18; c > 0 {
		h = h + strings.Repeat(" ", c)
	}

	w.header.Width = ui.TermWidth()
	w.header.Height = 1
	w.header.Y = 0
	w.header.Text = h
	w.header.TextBgColor = ui.ColorCyan
	w.header.TextFgColor = ui.ColorBlack
	w.header.Border = false

	w.footer.Width = ui.TermWidth()
	w.footer.Height = 1
	w.footer.Text = strings.Repeat("─", ui.TermWidth()-6)
	w.footer.TextBgColor = ui.ColorDefault
	w.footer.TextFgColor = ui.ColorCyan
	w.footer.Border = false

	w.menu.Width = ui.TermWidth()
	w.menu.Height = 1
	w.menu.Y = 1
	w.menu.Text = m
	w.menu.TextFgColor = ui.ColorDefault
	w.menu.Border = false

	ui.Render(ui.Body)
}

func (c *Client) snmpShowInterfaceTermUI(filter string, flag map[string]interface{}) error {
	var (
		spin   = spinner.New(spinner.CharSets[26], 220*time.Millisecond)
		span   = []int{1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1}
		s1, s2 [][]string
		rows   []*ui.Row
		idxs   []int
		err    error
	)

	spin.Prefix = "initializing "
	spin.Start()

	if len(strings.TrimSpace(filter)) > 1 {
		idxs = c.snmpGetIdx(filter)
	}

	s1, err = c.snmpGetInterfaces(idxs)
	if err != nil {
		spin.Stop()
		return err
	}
	if len(s1)-1 < 1 {
		spin.Stop()
		return fmt.Errorf("could not find any interface")
	}

	spin.Stop()

	if len(s1) > maxRowTermUI {
		return fmt.Errorf("result can not fit on the screen please try filter")
	}

	ui.DefaultEvtStream = ui.NewEvtStream()
	if err := ui.Init(); err != nil {
		return err
	}
	defer ui.Close()
	w := initWidgets()

	wList := []*ui.List{
		w.ifName,
		w.ifStatus,
		w.ifDescr,
		w.ifTIn,
		w.ifTOut,
		w.ifPIn,
		w.ifPOut,
		w.ifDIn,
		w.ifDOut,
		w.ifEIn,
		w.ifEOut,
	}

	for i, l := range wList {
		l.Items = make([]string, maxRowTermUI+5)
		l.X = 0
		l.Y = 0
		l.Height = len(s1)
		l.Border = false
		l.PaddingLeft = 1

		rows = append(rows, ui.NewCol(span[i], 0, l))
	}

	for i, v := range s1[0] {
		wList[i].Items[0] = fmt.Sprintf("[%s](fg-magenta,fg-bold)", v)
	}

	// initialize cells
	for i, v := range s1[1:] {
		w.ifName.Items[i+1] = v[0]
		w.ifStatus.Items[i+1] = ifStatus(v[1])
		w.ifDescr.Items[i+1] = v[2]
		for _, l := range wList[3:] {
			l.Items[i+1] = "-"
		}
	}

	w.updateFrame(c, "")

	screen := []*ui.Row{
		ui.NewRow(
			ui.NewCol(12, 0, w.header),
		),
		ui.NewRow(
			ui.NewCol(12, 0, w.menu),
		),
		ui.NewRow(rows...),
		ui.NewRow(
			ui.NewCol(12, 0, w.footer),
		),
	}

	ui.Handle("/timer/1s", func(e ui.Event) {
		t := e.Data.(ui.EvtTimer)
		if t.Count%10 != 0 {
			return
		}

		s2, err = c.snmpGetInterfaces(idxs)
		if err != nil {
			w.updateFrame(c, "error: "+err.Error())
			return
		}

		for i := range s2[1:] {
			rows := normalize(s1[i+1], s2[i+1], 10)
			for c := range wList {
				wList[c].Items[i+1] = rows[c]
			}
		}

		copy(s1, s2)
		w.updateFrame(c, "")
		ui.Render(ui.Body)
	})

	ui.Handle("/sys/kbd/q", func(ui.Event) {
		ui.StopLoop()
	})

	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		w.updateFrame(c, "")
		ui.Body.Width = ui.TermWidth()
		ui.Body.Align()
		ui.Render(ui.Body)
	})

	ui.Body.AddRows(screen...)
	ui.Body.Align()
	ui.Render(ui.Body)

	ui.Loop()
	return nil
}
<|endoftext|>


<commit_before>package node

import "golang.org/x/net/html"

// Attr returns attribute of node by given key if any.
func Attr(n *html.Node, key string) (string, bool) {
	for _, a := range n.Attr {
		if a.Key == key {
			return a.Val, true
		}
	}
	return "", false
}

// Children returns slice of nodes where each node is a child of given node and
// filter function returns true for corresponding child node.
func Children(n *html.Node, filter func(*html.Node) bool) []*html.Node {
	nodes := make([]*html.Node, 0)

	if filter == nil {
		filter = func(*html.Node) bool { return true }
	}

	for c := n.FirstChild; c != nil; c = c.NextSibling {
		if !filter(c) {
			continue
		}
		nodes = append(nodes, c)

	}
	return nodes
}
<commit_msg>Playing with Join Data<commit_after>package node

import (
	"bytes"

	"golang.org/x/net/html"
)

// Attr returns attribute of node by given key if any.
func Attr(n *html.Node, key string) (string, bool) {
	for _, a := range n.Attr {
		if a.Key == key {
			return a.Val, true
		}
	}
	return "", false
}

// Children returns slice of nodes where each node is a child of given node and
// filter function returns true for corresponding child node.
func Children(n *html.Node, filter func(*html.Node) bool) []*html.Node {
	nodes := make([]*html.Node, 0)

	if filter == nil {
		filter = func(*html.Node) bool { return true }
	}

	for c := n.FirstChild; c != nil; c = c.NextSibling {
		if !filter(c) {
			continue
		}
		nodes = append(nodes, c)

	}
	return nodes
}

// JoinData returns attribute of node by given key if any.
func JoinData(n ...*html.Node) string {
	var buf bytes.Buffer
	for _, m := range n {
		buf.WriteString(m.Data)
	}
	return buf.String()
}
<|endoftext|>


<commit_before>package filter

import (
	"bufio"
	"bytes"
	"unicode"

	"io"
)

// NewNodeReaderFilter 创建节点过滤器，实现敏感词的过滤
// 从可读流中读取敏感词数据(以指定的分隔符读取数据)
func NewNodeReaderFilter(rd io.Reader, delim byte) DirtyFilter {
	nf := &nodeFilter{
		root: newNode(),
	}
	buf := new(bytes.Buffer)
	io.Copy(buf, rd)
	buf.WriteByte(delim)
	for {
		line, err := buf.ReadString(delim)
		if err != nil {
			break
		}
		if line == "" {
			continue
		}
		nf.addDirtyWords(line)
	}
	buf.Reset()
	return nf
}

// NewNodeChanFilter 创建节点过滤器，实现敏感词的过滤
// 从通道中读取敏感词数据
func NewNodeChanFilter(text <-chan string) DirtyFilter {
	nf := &nodeFilter{
		root: newNode(),
	}
	for v := range text {
		nf.addDirtyWords(v)
	}
	return nf
}

// NewNodeFilter 创建节点过滤器，实现敏感词的过滤
// 从切片中读取敏感词数据
func NewNodeFilter(text []string) DirtyFilter {
	nf := &nodeFilter{
		root: newNode(),
	}
	for i, l := 0, len(text); i < l; i++ {
		nf.addDirtyWords(text[i])
	}
	return nf
}

func newNode() *node {
	return &node{
		child: make(map[rune]*node),
	}
}

type node struct {
	end   bool
	child map[rune]*node
}

type nodeFilter struct {
	root *node
}

func (nf *nodeFilter) addDirtyWords(text string) {
	n := nf.root
	uchars := []rune(text)
	for i, l := 0, len(uchars); i < l; i++ {
		if unicode.IsSpace(uchars[i]) {
			continue
		}
		if _, ok := n.child[uchars[i]]; !ok {
			n.child[uchars[i]] = newNode()
		}
		n = n.child[uchars[i]]
	}
	n.end = true
}

func (nf *nodeFilter) Filter(text string, excludes ...rune) ([]string, error) {
	buf := bytes.NewBufferString(text)
	defer buf.Reset()
	return nf.FilterReader(buf, excludes...)
}

func (nf *nodeFilter) FilterResult(text string, excludes ...rune) (map[string]int, error) {
	buf := bytes.NewBufferString(text)
	defer buf.Reset()
	return nf.FilterReaderResult(buf, excludes...)
}

func (nf *nodeFilter) FilterReader(reader io.Reader, excludes ...rune) ([]string, error) {
	data, err := nf.FilterReaderResult(reader, excludes...)
	if err != nil {
		return nil, err
	}
	var result []string
	for k := range data {
		result = append(result, k)
	}
	return result, nil
}

func (nf *nodeFilter) FilterReaderResult(reader io.Reader, excludes ...rune) (map[string]int, error) {
	var (
		uchars []rune
	)
	data := make(map[string]int)
	bi := bufio.NewReader(reader)
	for {
		ur, _, err := bi.ReadRune()
		if err != nil {
			if err != io.EOF {
				return nil, err
			}
			break
		}
		if nf.checkExclude(ur, excludes...) {
			continue
		}
		if (unicode.IsSpace(ur) || unicode.IsPunct(ur)) && len(uchars) > 0 {
			nf.doFilter(uchars[:], data)
			uchars = nil
			continue
		}
		uchars = append(uchars, ur)
	}
	if len(uchars) > 0 {
		nf.doFilter(uchars, data)
	}
	return data, nil
}

func (nf *nodeFilter) Replace(text string, delim rune) (string, error) {
	uchars := []rune(text)
	idexs := nf.doIndexes(uchars)
	for i := 0; i < len(idexs); i++ {
		uchars[idexs[i]] = rune(delim)
	}
	return string(uchars), nil
}

func (nf *nodeFilter) checkExclude(u rune, excludes ...rune) bool {
	if len(excludes) == 0 {
		return false
	}
	var exist bool
	for i, l := 0, len(excludes); i < l; i++ {
		if u == excludes[i] {
			exist = true
			break
		}
	}
	return exist
}

func (nf *nodeFilter) doFilter(uchars []rune, data map[string]int) {
	var result []string
	ul := len(uchars)
	buf := new(bytes.Buffer)
	n := nf.root
	for i := 0; i < ul; i++ {
		if _, ok := n.child[uchars[i]]; !ok {
			continue
		}
		n = n.child[uchars[i]]
		buf.WriteRune(uchars[i])
		if n.end {
			result = append(result, buf.String())
		}
		for j := i + 1; j < ul; j++ {
			if _, ok := n.child[uchars[j]]; !ok {
				break
			}
			n = n.child[uchars[j]]
			buf.WriteRune(uchars[j])
			if n.end {
				result = append(result, buf.String())
			}
		}
		buf.Reset()
		n = nf.root
	}
	for i, l := 0, len(result); i < l; i++ {
		var c int
		if v, ok := data[result[i]]; ok {
			c = v
		}
		data[result[i]] = c + 1
	}
}

func (nf *nodeFilter) doIndexes(uchars []rune) (idexs []int) {
	var (
		tIdexs []int
		ul     = len(uchars)
		n      = nf.root
	)
	for i := 0; i < ul; i++ {
		if _, ok := n.child[uchars[i]]; !ok {
			continue
		}
		n = n.child[uchars[i]]
		tIdexs = append(tIdexs, i)
		if n.end {
			idexs = nf.appendTo(idexs, tIdexs)
			tIdexs = nil
		}
		for j := i + 1; j < ul; j++ {
			if _, ok := n.child[uchars[j]]; !ok {
				break
			}
			n = n.child[uchars[j]]
			tIdexs = append(tIdexs, j)
			if n.end {
				idexs = nf.appendTo(idexs, tIdexs)
			}
		}
		if tIdexs != nil {
			tIdexs = nil
		}
		n = nf.root
	}
	return
}

func (nf *nodeFilter) appendTo(dst, src []int) []int {
	var t []int
	for i, il := 0, len(src); i < il; i++ {
		var exist bool
		for j, jl := 0, len(dst); j < jl; j++ {
			if src[i] == dst[j] {
				exist = true
				break
			}
		}
		if !exist {
			t = append(t, src[i])
		}
	}
	return append(dst, t...)
}
<commit_msg>Fixed replace<commit_after>package filter

import (
	"bufio"
	"bytes"
	"unicode"

	"io"
)

// NewNodeReaderFilter 创建节点过滤器，实现敏感词的过滤
// 从可读流中读取敏感词数据(以指定的分隔符读取数据)
func NewNodeReaderFilter(rd io.Reader, delim byte) DirtyFilter {
	nf := &nodeFilter{
		root: newNode(),
	}
	buf := new(bytes.Buffer)
	io.Copy(buf, rd)
	buf.WriteByte(delim)
	for {
		line, err := buf.ReadString(delim)
		if err != nil {
			break
		}
		if line == "" {
			continue
		}
		nf.addDirtyWords(line)
	}
	buf.Reset()
	return nf
}

// NewNodeChanFilter 创建节点过滤器，实现敏感词的过滤
// 从通道中读取敏感词数据
func NewNodeChanFilter(text <-chan string) DirtyFilter {
	nf := &nodeFilter{
		root: newNode(),
	}
	for v := range text {
		nf.addDirtyWords(v)
	}
	return nf
}

// NewNodeFilter 创建节点过滤器，实现敏感词的过滤
// 从切片中读取敏感词数据
func NewNodeFilter(text []string) DirtyFilter {
	nf := &nodeFilter{
		root: newNode(),
	}
	for i, l := 0, len(text); i < l; i++ {
		nf.addDirtyWords(text[i])
	}
	return nf
}

func newNode() *node {
	return &node{
		child: make(map[rune]*node),
	}
}

type node struct {
	end   bool
	child map[rune]*node
}

type nodeFilter struct {
	root *node
}

func (nf *nodeFilter) addDirtyWords(text string) {
	n := nf.root
	uchars := []rune(text)
	for i, l := 0, len(uchars); i < l; i++ {
		if unicode.IsSpace(uchars[i]) {
			continue
		}
		if _, ok := n.child[uchars[i]]; !ok {
			n.child[uchars[i]] = newNode()
		}
		n = n.child[uchars[i]]
	}
	n.end = true
}

func (nf *nodeFilter) Filter(text string, excludes ...rune) ([]string, error) {
	buf := bytes.NewBufferString(text)
	defer buf.Reset()
	return nf.FilterReader(buf, excludes...)
}

func (nf *nodeFilter) FilterResult(text string, excludes ...rune) (map[string]int, error) {
	buf := bytes.NewBufferString(text)
	defer buf.Reset()
	return nf.FilterReaderResult(buf, excludes...)
}

func (nf *nodeFilter) FilterReader(reader io.Reader, excludes ...rune) ([]string, error) {
	data, err := nf.FilterReaderResult(reader, excludes...)
	if err != nil {
		return nil, err
	}
	var result []string
	for k := range data {
		result = append(result, k)
	}
	return result, nil
}

func (nf *nodeFilter) FilterReaderResult(reader io.Reader, excludes ...rune) (map[string]int, error) {
	var (
		uchars []rune
	)
	data := make(map[string]int)
	bi := bufio.NewReader(reader)
	for {
		ur, _, err := bi.ReadRune()
		if err != nil {
			if err != io.EOF {
				return nil, err
			}
			break
		}
		if nf.checkExclude(ur, excludes...) {
			continue
		}
		if (unicode.IsSpace(ur) || unicode.IsPunct(ur)) && len(uchars) > 0 {
			nf.doFilter(uchars[:], data)
			uchars = nil
			continue
		}
		uchars = append(uchars, ur)
	}
	if len(uchars) > 0 {
		nf.doFilter(uchars, data)
	}
	return data, nil
}

func (nf *nodeFilter) Replace(text string, delim rune) (string, error) {
	uchars := []rune(text)
	idexs := nf.doIndexes(uchars)
	if len(idexs) == 0 {
		return "", nil
	}
	for i := 0; i < len(idexs); i++ {
		uchars[idexs[i]] = rune(delim)
	}
	return string(uchars), nil
}

func (nf *nodeFilter) checkExclude(u rune, excludes ...rune) bool {
	if len(excludes) == 0 {
		return false
	}
	var exist bool
	for i, l := 0, len(excludes); i < l; i++ {
		if u == excludes[i] {
			exist = true
			break
		}
	}
	return exist
}

func (nf *nodeFilter) doFilter(uchars []rune, data map[string]int) {
	var result []string
	ul := len(uchars)
	buf := new(bytes.Buffer)
	n := nf.root
	for i := 0; i < ul; i++ {
		if _, ok := n.child[uchars[i]]; !ok {
			continue
		}
		n = n.child[uchars[i]]
		buf.WriteRune(uchars[i])
		if n.end {
			result = append(result, buf.String())
		}
		for j := i + 1; j < ul; j++ {
			if _, ok := n.child[uchars[j]]; !ok {
				break
			}
			n = n.child[uchars[j]]
			buf.WriteRune(uchars[j])
			if n.end {
				result = append(result, buf.String())
			}
		}
		buf.Reset()
		n = nf.root
	}
	for i, l := 0, len(result); i < l; i++ {
		var c int
		if v, ok := data[result[i]]; ok {
			c = v
		}
		data[result[i]] = c + 1
	}
}

func (nf *nodeFilter) doIndexes(uchars []rune) (idexs []int) {
	var (
		tIdexs []int
		ul     = len(uchars)
		n      = nf.root
	)
	for i := 0; i < ul; i++ {
		if _, ok := n.child[uchars[i]]; !ok {
			continue
		}
		n = n.child[uchars[i]]
		tIdexs = append(tIdexs, i)
		if n.end {
			idexs = nf.appendTo(idexs, tIdexs)
			tIdexs = nil
		}
		for j := i + 1; j < ul; j++ {
			if _, ok := n.child[uchars[j]]; !ok {
				break
			}
			n = n.child[uchars[j]]
			tIdexs = append(tIdexs, j)
			if n.end {
				idexs = nf.appendTo(idexs, tIdexs)
			}
		}
		if tIdexs != nil {
			tIdexs = nil
		}
		n = nf.root
	}
	return
}

func (nf *nodeFilter) appendTo(dst, src []int) []int {
	var t []int
	for i, il := 0, len(src); i < il; i++ {
		var exist bool
		for j, jl := 0, len(dst); j < jl; j++ {
			if src[i] == dst[j] {
				exist = true
				break
			}
		}
		if !exist {
			t = append(t, src[i])
		}
	}
	return append(dst, t...)
}
<|endoftext|>


<commit_before>package recovery

import (
	"errors"
	"fmt"
)

func panicString(p interface{}) string {
	switch panicMesg := p.(type) {
	case string:
		return panicMesg
	case error:
		return panicMesg.Error()
	case fmt.Stringer:
		return panicMesg.String()
	default:
		return fmt.Sprintf("%+v", panicMesg)
	}
}

func panicError(p interface{}) error {
	if p == nil {
		return nil
	}

	ps := panicString(p)
	if ps == "" {
		ps = fmt.Sprintf("non-nil panic [%T] encountered with no string representation", p)
	}

	return errors.New(ps)
}
<commit_msg>handle nil panic messages<commit_after>package recovery

import (
	"errors"
	"fmt"
)

func panicString(p interface{}) string {
	switch panicMesg := p.(type) {
	case string:
		return panicMesg
	case error:
		return panicMesg.Error()
	case fmt.Stringer:
		return panicMesg.String()
	case nil:
		return ""
	default:
		return fmt.Sprintf("%+v", panicMesg)
	}
}

func panicError(p interface{}) error {
	if p == nil {
		return nil
	}

	ps := panicString(p)
	if ps == "" {
		ps = fmt.Sprintf("non-nil panic [%T] encountered with no string representation", p)
	}

	return errors.New(ps)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/garyburd/redigo/redis"
	"time"
)

const (
	RedisManagerDefaultMaxIdle     = 1
	RedisManagerDefaultMaxActive   = 10
	RedisManagerDefaultIdleTimeout = 30 * time.Second
	RedisManagerDefaultHost        = "127.0.0.1" // No use yet
	RedisManagerDefaultPort        = 6379        // No use yet
	RedisManagerDefaultPassword    = ""          // No use yet
	RedisManagerDefaultDb          = 0           // No use yet
	RedisManagerDefaultExpireTime  = 21600       // 6 hours
)

type RedisManager struct {
	maxIdle     int
	maxActive   int
	idleTimeout time.Duration
	host        string
	port        int
	password    string
	db          int
	pool        *redis.Pool
	expireTime  int64
}

func NewRedisManager(host string, port int, password string, db int) *RedisManager {
	redisMgr := &RedisManager{
		maxIdle:     RedisManagerDefaultMaxIdle,
		maxActive:   RedisManagerDefaultMaxActive,
		idleTimeout: RedisManagerDefaultIdleTimeout,
		host:        host,
		port:        port,
		password:    password,
		db:          db,
		pool:        nil,
		expireTime:  RedisManagerDefaultExpireTime,
	}
	redisMgr.pool = redisMgr.init()
	return redisMgr
}

func NewRedisManagerWithPool(host string, port int, password string, db int, maxIdle int, maxActive int, idleTimeout time.Duration) *RedisManager {
	redisMgr := &RedisManager{
		maxIdle:     maxIdle,
		maxActive:   maxActive,
		idleTimeout: idleTimeout,
		host:        host,
		port:        port,
		password:    password,
		db:          db,
	}
	redisMgr.pool = redisMgr.init()
	return redisMgr
}

func (redisMgr *RedisManager) init() *redis.Pool {
	return &redis.Pool{
		MaxIdle:     1,
		MaxActive:   10,
		IdleTimeout: 30 * time.Second,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", redisMgr.host, redisMgr.port))
			if err != nil {
				return nil, err
			}
			c.Do("SELECT", string(redisMgr.db))
			return c, nil
		},
	}
}

func (redisMgr *RedisManager) getConnection() redis.Conn {
	return redisMgr.pool.Get()
}

func (redisMgr *RedisManager) Set(key string, str string) error {
	c := redisMgr.getConnection()
	defer c.Close()

	_, err := c.Do("SET", key, str)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	return nil
}

func (redisMgr *RedisManager) Get(key string) (string, error) {
	c := redisMgr.getConnection()
	defer c.Close()

	v, err := redis.String(c.Do("GET", key))
	if err != nil {
		log.Error(err.Error())
		return "", err
	}
	return v, nil
}

func (redisMgr *RedisManager) Del(key string) error {
	c := redisMgr.getConnection()
	defer c.Close()

	_, err := c.Do("DEL", key)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	return nil
}

func (redisMgr *RedisManager) SetObject(key string, obj interface{}) {}

func (redisMgr *RedisManager) GetObject(key string, obj interface{}) {}

func main() {
	redisMgr := NewRedisManagerWithPool("127.0.0.1", 6379, "", 0, 1, 10, 30*time.Second)
	redisMgr.Set("test", "huangzeming")
	v, _ := redisMgr.Get("test")
	log.Info(v)
	redisMgr.Del("test")
}
<commit_msg>实现RedisManager的GetObject、SetObject和DelObject方法<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/garyburd/redigo/redis"
	"time"
)

const (
	RedisManagerDefaultMaxIdle     = 1
	RedisManagerDefaultMaxActive   = 10
	RedisManagerDefaultIdleTimeout = 30 * time.Second
	RedisManagerDefaultHost        = "127.0.0.1" // No use yet
	RedisManagerDefaultPort        = 6379        // No use yet
	RedisManagerDefaultPassword    = ""          // No use yet
	RedisManagerDefaultDb          = 0           // No use yet
	RedisManagerDefaultExpireTime  = 21600       // 6 hours
)

const (
	RedisManagerStatusUncheck = iota
	RedisManagerStatusChecked
	RedisManagerStatusDirty
	RedisManagerStatusError
)

type RedisManager struct {
	maxIdle     int
	maxActive   int
	idleTimeout time.Duration
	host        string
	port        int
	password    string
	db          int
	pool        *redis.Pool
	expireTime  int64
}

func NewRedisManager(host string, port int, password string, db int) *RedisManager {
	redisMgr := &RedisManager{
		maxIdle:     RedisManagerDefaultMaxIdle,
		maxActive:   RedisManagerDefaultMaxActive,
		idleTimeout: RedisManagerDefaultIdleTimeout,
		host:        host,
		port:        port,
		password:    password,
		db:          db,
		pool:        nil,
		expireTime:  RedisManagerDefaultExpireTime,
	}
	redisMgr.pool = redisMgr.init()
	return redisMgr
}

func NewRedisManagerWithPool(host string, port int, password string, db int, maxIdle int, maxActive int, idleTimeout time.Duration) *RedisManager {
	redisMgr := &RedisManager{
		maxIdle:     maxIdle,
		maxActive:   maxActive,
		idleTimeout: idleTimeout,
		host:        host,
		port:        port,
		password:    password,
		db:          db,
	}
	redisMgr.pool = redisMgr.init()
	return redisMgr
}

func (redisMgr *RedisManager) init() *redis.Pool {
	return &redis.Pool{
		MaxIdle:     1,
		MaxActive:   10,
		IdleTimeout: 30 * time.Second,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", redisMgr.host, redisMgr.port))
			if err != nil {
				return nil, err
			}
			c.Do("SELECT", string(redisMgr.db))
			return c, nil
		},
	}
}

func (redisMgr *RedisManager) getConnection() redis.Conn {
	return redisMgr.pool.Get()
}

func (redisMgr *RedisManager) getStatusKey(key string) string {
	return key + "/status"
}

func (redisMgr *RedisManager) Set(key string, str string) error {
	c := redisMgr.getConnection()
	defer c.Close()

	_, err := c.Do("SET", key, str)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	return nil
}

func (redisMgr *RedisManager) Get(key string) (string, error) {
	c := redisMgr.getConnection()
	defer c.Close()

	v, err := redis.String(c.Do("GET", key))
	if err != nil {
		log.Error(err.Error())
		return "", err
	}
	return v, nil
}

func (redisMgr *RedisManager) Del(key string) error {
	c := redisMgr.getConnection()
	defer c.Close()

	_, err := c.Do("DEL", key)
	if err != nil {
		log.Error(err.Error())
	}
	return err
}

func (redisMgr *RedisManager) SetObject(key string, obj interface{}) error {
	c := redisMgr.getConnection()
	defer c.Close()

	bytes, e := json.Marshal(obj)
	if e != nil {
		log.Error(e.Error())
		return e
	}
	statusKey := redisMgr.getStatusKey(key)
	status := RedisManagerStatusUncheck

	ok, err := redis.Bool(c.Do("EXISTS", statusKey))
	if err != nil {
		log.Error(err.Error())
		return err
	}
	if ok {
		v, err := redis.Int(c.Do("GET", statusKey))
		if err != nil {
			log.Error(err.Error())
			return err
		}
		if v != RedisManagerStatusChecked {
			status = RedisManagerStatusDirty
		}
	}
	c.Do("MULTI")
	c.Do("SET", key, bytes)
	c.Do("SET", statusKey, status)
	_, err = c.Do("EXEC")
	if err != nil {
		log.Error(err.Error())
	}
	return err
}

func (redisMgr *RedisManager) GetObject(key string, obj interface{}) (int, error) {
	c := redisMgr.getConnection()
	defer c.Close()

	statusKey := redisMgr.getStatusKey(key)

	status := RedisManagerStatusError
	ok, err := redis.Bool(c.Do("EXISTS", statusKey))
	if ok {
		status, err = redis.Int(c.Do("GET", statusKey))
		if err != nil {
			log.Error(err.Error())
			obj = nil
			return RedisManagerStatusError, err
		}
		bytes, err := redis.Bytes(c.Do("GET", key))
		if err != nil {
			log.Error(err.Error())
			obj = nil
			return RedisManagerStatusError, err
		}
		err = json.Unmarshal(bytes, obj)
		if err != nil {
			log.Error(err.Error())
			obj = nil
			return RedisManagerStatusError, err
		}
	} else {
		err = errors.New("RedisManager: has not status")
		log.Error(err.Error())
		obj = nil
	}
	return status, err
}

func (redisMgr *RedisManager) DelObject(key string) error {
	c := redisMgr.getConnection()
	defer c.Close()

	statusKey := redisMgr.getStatusKey(key)

	c.Do("MULTI")
	c.Do("DEL", key)
	c.Do("DEL", statusKey)
	_, err := c.Do("EXEC")
	return err
}

// TODO
func (redisMgr *RedisManager) CheckObject(key string) error {
	c := redisMgr.getConnection()
	defer c.Close()

	return nil
}

type Student struct {
	Id   int
	Name string
}

func main() {
	redisMgr := NewRedisManagerWithPool("127.0.0.1", 6379, "", 0, 1, 10, 30*time.Second)
	redisMgr.Set("test", "huangzeming")
	v, _ := redisMgr.Get("test")
	log.Info(v)
	redisMgr.Del("test")
	student := &Student{
		Id:   1,
		Name: "Ming",
	}
	redisMgr.SetObject("student/1", student)
	obj := &Student{}
	status, _ := redisMgr.GetObject("student/1", obj)
	log.Info(obj)
	log.Info(status)
	redisMgr.DelObject("student/1")
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/phalaaxx/cdb"
)

/* VerifyLocal checks if named mailbox exist in a local cdb database */
func VerifyLocal(name string) bool {
	var value *string
	err := cdb.Lookup(
		LocalCdb,
		func(db *cdb.Reader) (err error) {
			value, err = db.Get(name)
			return err
		},
	)
	if err == nil && value != nil && len(*value) != 0 {
		return true
	}
	return false
}
<commit_msg>Use godb library instead of cdb.<commit_after>package main

import (
	"github.com/phalaaxx/godb"
)

/* VerifyLocal checks if named mailbox exist in a local cdb database */
func VerifyLocal(name string) bool {
	var value *string
	err := godb.CdbLookup(
		LocalCdb,
		func(db *godb.CdbReader) (err error) {
			value, err = db.Get(name)
			return err
		},
	)
	if err == nil && value != nil && len(*value) != 0 {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package nsqhandler

import (
	"sync"
	"time"

	"github.com/apex/log"
)

var start = time.Now()

type PublishFunc func(topic string, body []byte) error

type MarshalFunc func(x interface{}) ([]byte, error)

type Handler struct {
	mu          sync.Mutex
	marshalFunc MarshalFunc
	publishFunc PublishFunc
	topic       string
}

func New(marshalFunc MarshalFunc, publishFunc PublishFunc, topic string) *Handler {
	return &Handler{
		marshalFunc: marshalFunc,
		publishFunc: publishFunc,
		topic:       topic,
	}
}

func (h *Handler) HandleLog(e *log.Entry) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	payload, err := h.marshalFunc(e)
	if err != nil {
		return err
	}
	return h.publishFunc(h.topic, payload)
}
<commit_msg>Add documentation strings to nsqhandler.<commit_after>/*
apex_log_nsq_handler is a handler for github.com/apex/log.  It's intended to act as a transport to allow log.Entry structs to pass through nsq and be reconstructed and passed to another handler on the other side.
*/
package apex_log_nsq_handler

import (
	"sync"

	"github.com/apex/log"
)

// PublishFunc is a function signature for any function that publish a
// message on an a provided nsq topic.  Typically this is
// github.com/nsqio/go-nsq.Producer.Publish, or somethign that wraps
// it.
type PublishFunc func(topic string, body []byte) error

// MarshalFunc is a function signature for any function that can
// marshal an arbitrary struct to a slice of bytes.
type MarshalFunc func(x interface{}) ([]byte, error)

// Handler is a handler that can be passed to github.com/apex/log.SetHandler.
type Handler struct {
	mu          sync.Mutex
	marshalFunc MarshalFunc
	publishFunc PublishFunc
	topic       string
}

// New returns a pointer to an apex_log_nsq_handler.Handler that can
// in turn be passed to github.com/apex/log.SetHandler.
//
// The marshalFunc provided will be used to marshal a
// github.com/apex/log.Entry as the body of a message sent over nsq.
//
// The publishFunc is used to push a message onto the nsq.  For simple
// cases, with only one nsq endpoint using
// github.com/nsqio/go-nsq.Producer.Publish is fine.  For cases with
// multiple producers you'll want to wrap it.  See the examples
// directory for an implementation of this.
//
// The topic is a string determining the nsq topic the messages will
// be published to.
//
func New(marshalFunc MarshalFunc, publishFunc PublishFunc, topic string) *Handler {
	return &Handler{
		marshalFunc: marshalFunc,
		publishFunc: publishFunc,
		topic:       topic,
	}
}

// HandleLog makes Handler fulfil the interface required by
// github.com/apex/log for handlers.  Each individual log entry made
// in client programs will eventually invoke this function when using
// this Handler.
func (h *Handler) HandleLog(e *log.Entry) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	payload, err := h.marshalFunc(e)
	if err != nil {
		return err
	}
	return h.publishFunc(h.topic, payload)
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package leaderelection implements leader election of a set of endpoints.
// It uses an annotation in the endpoints object to store the record of the
// election state. This implementation does not guarantee that only one
// client is acting as a leader (a.k.a. fencing).
//
// A client only acts on timestamps captured locally to infer the state of the
// leader election. The client does not consider timestamps in the leader
// election record to be accurate because these timestamps may not have been
// produced by a local clock. The implemention does not depend on their
// accuracy and only uses their change to indicate that another client has
// renewed the leader lease. Thus the implementation is tolerant to arbitrary
// clock skew, but is not tolerant to arbitrary clock skew rate.
//
// However the level of tolerance to skew rate can be configured by setting
// RenewDeadline and LeaseDuration appropriately. The tolerance expressed as a
// maximum tolerated ratio of time passed on the fastest node to time passed on
// the slowest node can be approximately achieved with a configuration that sets
// the same ratio of LeaseDuration to RenewDeadline. For example if a user wanted
// to tolerate some nodes progressing forward in time twice as fast as other nodes,
// the user could set LeaseDuration to 60 seconds and RenewDeadline to 30 seconds.
//
// While not required, some method of clock synchronization between nodes in the
// cluster is highly recommended. It's important to keep in mind when configuring
// this client that the tolerance to skew rate varies inversely to master
// availability.
//
// Larger clusters often have a more lenient SLA for API latency. This should be
// taken into account when configuring the client. The rate of leader transitions
// should be monitored and RetryPeriod and LeaseDuration should be increased
// until the rate is stable and acceptably low. It's important to keep in mind
// when configuring this client that the tolerance to API latency varies inversely
// to master availability.
//
// DISCLAIMER: this is an alpha API. This library will likely change significantly
// or even be removed entirely in subsequent releases. Depend on this API at
// your own risk.
package leaderelection

import (
	"bytes"
	"context"
	"fmt"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/clock"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	rl "k8s.io/client-go/tools/leaderelection/resourcelock"

	"k8s.io/klog/v2"
)

const (
	JitterFactor = 1.2
)

// NewLeaderElector creates a LeaderElector from a LeaderElectionConfig
func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) {
	if lec.LeaseDuration <= lec.RenewDeadline {
		return nil, fmt.Errorf("leaseDuration must be greater than renewDeadline")
	}
	if lec.RenewDeadline <= time.Duration(JitterFactor*float64(lec.RetryPeriod)) {
		return nil, fmt.Errorf("renewDeadline must be greater than retryPeriod*JitterFactor")
	}
	if lec.LeaseDuration < 1 {
		return nil, fmt.Errorf("leaseDuration must be greater than zero")
	}
	if lec.RenewDeadline < 1 {
		return nil, fmt.Errorf("renewDeadline must be greater than zero")
	}
	if lec.RetryPeriod < 1 {
		return nil, fmt.Errorf("retryPeriod must be greater than zero")
	}
	if lec.Callbacks.OnStartedLeading == nil {
		return nil, fmt.Errorf("OnStartedLeading callback must not be nil")
	}
	if lec.Callbacks.OnStoppedLeading == nil {
		return nil, fmt.Errorf("OnStoppedLeading callback must not be nil")
	}

	if lec.Lock == nil {
		return nil, fmt.Errorf("Lock must not be nil.")
	}
	le := LeaderElector{
		config:  lec,
		clock:   clock.RealClock{},
		metrics: globalMetricsFactory.newLeaderMetrics(),
	}
	le.metrics.leaderOff(le.config.Name)
	return &le, nil
}

type LeaderElectionConfig struct {
	// Lock is the resource that will be used for locking
	Lock rl.Interface

	// LeaseDuration is the duration that non-leader candidates will
	// wait to force acquire leadership. This is measured against time of
	// last observed ack.
	//
	// A client needs to wait a full LeaseDuration without observing a change to
	// the record before it can attempt to take over. When all clients are
	// shutdown and a new set of clients are started with different names against
	// the same leader record, they must wait the full LeaseDuration before
	// attempting to acquire the lease. Thus LeaseDuration should be as short as
	// possible (within your tolerance for clock skew rate) to avoid a possible
	// long waits in the scenario.
	//
	// Core clients default this value to 15 seconds.
	LeaseDuration time.Duration
	// RenewDeadline is the duration that the acting master will retry
	// refreshing leadership before giving up.
	//
	// Core clients default this value to 10 seconds.
	RenewDeadline time.Duration
	// RetryPeriod is the duration the LeaderElector clients should wait
	// between tries of actions.
	//
	// Core clients default this value to 2 seconds.
	RetryPeriod time.Duration

	// Callbacks are callbacks that are triggered during certain lifecycle
	// events of the LeaderElector
	Callbacks LeaderCallbacks

	// WatchDog is the associated health checker
	// WatchDog may be null if its not needed/configured.
	WatchDog *HealthzAdaptor

	// ReleaseOnCancel should be set true if the lock should be released
	// when the run context is cancelled. If you set this to true, you must
	// ensure all code guarded by this lease has successfully completed
	// prior to cancelling the context, or you may have two processes
	// simultaneously acting on the critical path.
	ReleaseOnCancel bool

	// Name is the name of the resource lock for debugging
	Name string
}

// LeaderCallbacks are callbacks that are triggered during certain
// lifecycle events of the LeaderElector. These are invoked asynchronously.
//
// possible future callbacks:
//  * OnChallenge()
type LeaderCallbacks struct {
	// OnStartedLeading is called when a LeaderElector client starts leading
	OnStartedLeading func(context.Context)
	// OnStoppedLeading is called when a LeaderElector client stops leading
	OnStoppedLeading func()
	// OnNewLeader is called when the client observes a leader that is
	// not the previously observed leader. This includes the first observed
	// leader when the client starts.
	OnNewLeader func(identity string)
}

// LeaderElector is a leader election client.
type LeaderElector struct {
	config LeaderElectionConfig
	// internal bookkeeping
	observedRecord    rl.LeaderElectionRecord
	observedRawRecord []byte
	observedTime      time.Time
	// used to implement OnNewLeader(), may lag slightly from the
	// value observedRecord.HolderIdentity if the transition has
	// not yet been reported.
	reportedLeader string

	// clock is wrapper around time to allow for less flaky testing
	clock clock.Clock

	metrics leaderMetricsAdapter

	// name is the name of the resource lock for debugging
	name string
}

// Run starts the leader election loop
func (le *LeaderElector) Run(ctx context.Context) {
	defer runtime.HandleCrash()
	defer func() {
		le.config.Callbacks.OnStoppedLeading()
	}()

	if !le.acquire(ctx) {
		return // ctx signalled done
	}
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	go le.config.Callbacks.OnStartedLeading(ctx)
	le.renew(ctx)
}

// RunOrDie starts a client with the provided config or panics if the config
// fails to validate.
func RunOrDie(ctx context.Context, lec LeaderElectionConfig) {
	le, err := NewLeaderElector(lec)
	if err != nil {
		panic(err)
	}
	if lec.WatchDog != nil {
		lec.WatchDog.SetLeaderElection(le)
	}
	le.Run(ctx)
}

// GetLeader returns the identity of the last observed leader or returns the empty string if
// no leader has yet been observed.
func (le *LeaderElector) GetLeader() string {
	return le.observedRecord.HolderIdentity
}

// IsLeader returns true if the last observed leader was this client else returns false.
func (le *LeaderElector) IsLeader() bool {
	return le.observedRecord.HolderIdentity == le.config.Lock.Identity()
}

// acquire loops calling tryAcquireOrRenew and returns true immediately when tryAcquireOrRenew succeeds.
// Returns false if ctx signals done.
func (le *LeaderElector) acquire(ctx context.Context) bool {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	succeeded := false
	desc := le.config.Lock.Describe()
	klog.Infof("attempting to acquire leader lease  %v...", desc)
	wait.JitterUntil(func() {
		succeeded = le.tryAcquireOrRenew(ctx)
		le.maybeReportTransition()
		if !succeeded {
			klog.V(4).Infof("failed to acquire lease %v", desc)
			return
		}
		le.config.Lock.RecordEvent("became leader")
		le.metrics.leaderOn(le.config.Name)
		klog.Infof("successfully acquired lease %v", desc)
		cancel()
	}, le.config.RetryPeriod, JitterFactor, true, ctx.Done())
	return succeeded
}

// renew loops calling tryAcquireOrRenew and returns immediately when tryAcquireOrRenew fails or ctx signals done.
func (le *LeaderElector) renew(ctx context.Context) {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	wait.Until(func() {
		timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
		defer timeoutCancel()
		err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
			return le.tryAcquireOrRenew(timeoutCtx), nil
		}, timeoutCtx.Done())

		le.maybeReportTransition()
		desc := le.config.Lock.Describe()
		if err == nil {
			klog.V(5).Infof("successfully renewed lease %v", desc)
			return
		}
		le.config.Lock.RecordEvent("stopped leading")
		le.metrics.leaderOff(le.config.Name)
		klog.Infof("failed to renew lease %v: %v", desc, err)
		cancel()
	}, le.config.RetryPeriod, ctx.Done())

	// if we hold the lease, give it up
	if le.config.ReleaseOnCancel {
		le.release()
	}
}

// release attempts to release the leader lease if we have acquired it.
func (le *LeaderElector) release() bool {
	if !le.IsLeader() {
		return true
	}
	leaderElectionRecord := rl.LeaderElectionRecord{
		LeaderTransitions: le.observedRecord.LeaderTransitions,
	}
	if err := le.config.Lock.Update(context.TODO(), leaderElectionRecord); err != nil {
		klog.Errorf("Failed to release lock: %v", err)
		return false
	}
	le.observedRecord = leaderElectionRecord
	le.observedTime = le.clock.Now()
	return true
}

// tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired,
// else it tries to renew the lease if it has already been acquired. Returns true
// on success else returns false.
func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {
	now := metav1.Now()
	leaderElectionRecord := rl.LeaderElectionRecord{
		HolderIdentity:       le.config.Lock.Identity(),
		LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),
		RenewTime:            now,
		AcquireTime:          now,
	}

	// 1. obtain or create the ElectionRecord
	oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)
	if err != nil {
		if !errors.IsNotFound(err) {
			klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err)
			return false
		}
		if err = le.config.Lock.Create(ctx, leaderElectionRecord); err != nil {
			klog.Errorf("error initially creating leader election record: %v", err)
			return false
		}
		le.observedRecord = leaderElectionRecord
		le.observedTime = le.clock.Now()
		return true
	}

	// 2. Record obtained, check the Identity & Time
	if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
		le.observedRecord = *oldLeaderElectionRecord
		le.observedRawRecord = oldLeaderElectionRawRecord
		le.observedTime = le.clock.Now()
	}
	if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&
		le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&
		!le.IsLeader() {
		klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)
		return false
	}

	// 3. We're going to try to update. The leaderElectionRecord is set to it's default
	// here. Let's correct it before updating.
	if le.IsLeader() {
		leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
		leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
	} else {
		leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
	}

	// update the lock itself
	if err = le.config.Lock.Update(ctx, leaderElectionRecord); err != nil {
		klog.Errorf("Failed to update lock: %v", err)
		return false
	}

	le.observedRecord = leaderElectionRecord
	le.observedTime = le.clock.Now()
	return true
}

func (le *LeaderElector) maybeReportTransition() {
	if le.observedRecord.HolderIdentity == le.reportedLeader {
		return
	}
	le.reportedLeader = le.observedRecord.HolderIdentity
	if le.config.Callbacks.OnNewLeader != nil {
		go le.config.Callbacks.OnNewLeader(le.reportedLeader)
	}
}

// Check will determine if the current lease is expired by more than timeout.
func (le *LeaderElector) Check(maxTolerableExpiredLease time.Duration) error {
	if !le.IsLeader() {
		// Currently not concerned with the case that we are hot standby
		return nil
	}
	// If we are more than timeout seconds after the lease duration that is past the timeout
	// on the lease renew. Time to start reporting ourselves as unhealthy. We should have
	// died but conditions like deadlock can prevent this. (See #70819)
	if le.clock.Since(le.observedTime) > le.config.LeaseDuration+maxTolerableExpiredLease {
		return fmt.Errorf("failed election to renew leadership on lease %s", le.config.Name)
	}

	return nil
}
<commit_msg>Document blocking behaviour of RunOrDie and Run<commit_after>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package leaderelection implements leader election of a set of endpoints.
// It uses an annotation in the endpoints object to store the record of the
// election state. This implementation does not guarantee that only one
// client is acting as a leader (a.k.a. fencing).
//
// A client only acts on timestamps captured locally to infer the state of the
// leader election. The client does not consider timestamps in the leader
// election record to be accurate because these timestamps may not have been
// produced by a local clock. The implemention does not depend on their
// accuracy and only uses their change to indicate that another client has
// renewed the leader lease. Thus the implementation is tolerant to arbitrary
// clock skew, but is not tolerant to arbitrary clock skew rate.
//
// However the level of tolerance to skew rate can be configured by setting
// RenewDeadline and LeaseDuration appropriately. The tolerance expressed as a
// maximum tolerated ratio of time passed on the fastest node to time passed on
// the slowest node can be approximately achieved with a configuration that sets
// the same ratio of LeaseDuration to RenewDeadline. For example if a user wanted
// to tolerate some nodes progressing forward in time twice as fast as other nodes,
// the user could set LeaseDuration to 60 seconds and RenewDeadline to 30 seconds.
//
// While not required, some method of clock synchronization between nodes in the
// cluster is highly recommended. It's important to keep in mind when configuring
// this client that the tolerance to skew rate varies inversely to master
// availability.
//
// Larger clusters often have a more lenient SLA for API latency. This should be
// taken into account when configuring the client. The rate of leader transitions
// should be monitored and RetryPeriod and LeaseDuration should be increased
// until the rate is stable and acceptably low. It's important to keep in mind
// when configuring this client that the tolerance to API latency varies inversely
// to master availability.
//
// DISCLAIMER: this is an alpha API. This library will likely change significantly
// or even be removed entirely in subsequent releases. Depend on this API at
// your own risk.
package leaderelection

import (
	"bytes"
	"context"
	"fmt"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/clock"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	rl "k8s.io/client-go/tools/leaderelection/resourcelock"

	"k8s.io/klog/v2"
)

const (
	JitterFactor = 1.2
)

// NewLeaderElector creates a LeaderElector from a LeaderElectionConfig
func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) {
	if lec.LeaseDuration <= lec.RenewDeadline {
		return nil, fmt.Errorf("leaseDuration must be greater than renewDeadline")
	}
	if lec.RenewDeadline <= time.Duration(JitterFactor*float64(lec.RetryPeriod)) {
		return nil, fmt.Errorf("renewDeadline must be greater than retryPeriod*JitterFactor")
	}
	if lec.LeaseDuration < 1 {
		return nil, fmt.Errorf("leaseDuration must be greater than zero")
	}
	if lec.RenewDeadline < 1 {
		return nil, fmt.Errorf("renewDeadline must be greater than zero")
	}
	if lec.RetryPeriod < 1 {
		return nil, fmt.Errorf("retryPeriod must be greater than zero")
	}
	if lec.Callbacks.OnStartedLeading == nil {
		return nil, fmt.Errorf("OnStartedLeading callback must not be nil")
	}
	if lec.Callbacks.OnStoppedLeading == nil {
		return nil, fmt.Errorf("OnStoppedLeading callback must not be nil")
	}

	if lec.Lock == nil {
		return nil, fmt.Errorf("Lock must not be nil.")
	}
	le := LeaderElector{
		config:  lec,
		clock:   clock.RealClock{},
		metrics: globalMetricsFactory.newLeaderMetrics(),
	}
	le.metrics.leaderOff(le.config.Name)
	return &le, nil
}

type LeaderElectionConfig struct {
	// Lock is the resource that will be used for locking
	Lock rl.Interface

	// LeaseDuration is the duration that non-leader candidates will
	// wait to force acquire leadership. This is measured against time of
	// last observed ack.
	//
	// A client needs to wait a full LeaseDuration without observing a change to
	// the record before it can attempt to take over. When all clients are
	// shutdown and a new set of clients are started with different names against
	// the same leader record, they must wait the full LeaseDuration before
	// attempting to acquire the lease. Thus LeaseDuration should be as short as
	// possible (within your tolerance for clock skew rate) to avoid a possible
	// long waits in the scenario.
	//
	// Core clients default this value to 15 seconds.
	LeaseDuration time.Duration
	// RenewDeadline is the duration that the acting master will retry
	// refreshing leadership before giving up.
	//
	// Core clients default this value to 10 seconds.
	RenewDeadline time.Duration
	// RetryPeriod is the duration the LeaderElector clients should wait
	// between tries of actions.
	//
	// Core clients default this value to 2 seconds.
	RetryPeriod time.Duration

	// Callbacks are callbacks that are triggered during certain lifecycle
	// events of the LeaderElector
	Callbacks LeaderCallbacks

	// WatchDog is the associated health checker
	// WatchDog may be null if its not needed/configured.
	WatchDog *HealthzAdaptor

	// ReleaseOnCancel should be set true if the lock should be released
	// when the run context is cancelled. If you set this to true, you must
	// ensure all code guarded by this lease has successfully completed
	// prior to cancelling the context, or you may have two processes
	// simultaneously acting on the critical path.
	ReleaseOnCancel bool

	// Name is the name of the resource lock for debugging
	Name string
}

// LeaderCallbacks are callbacks that are triggered during certain
// lifecycle events of the LeaderElector. These are invoked asynchronously.
//
// possible future callbacks:
//  * OnChallenge()
type LeaderCallbacks struct {
	// OnStartedLeading is called when a LeaderElector client starts leading
	OnStartedLeading func(context.Context)
	// OnStoppedLeading is called when a LeaderElector client stops leading
	OnStoppedLeading func()
	// OnNewLeader is called when the client observes a leader that is
	// not the previously observed leader. This includes the first observed
	// leader when the client starts.
	OnNewLeader func(identity string)
}

// LeaderElector is a leader election client.
type LeaderElector struct {
	config LeaderElectionConfig
	// internal bookkeeping
	observedRecord    rl.LeaderElectionRecord
	observedRawRecord []byte
	observedTime      time.Time
	// used to implement OnNewLeader(), may lag slightly from the
	// value observedRecord.HolderIdentity if the transition has
	// not yet been reported.
	reportedLeader string

	// clock is wrapper around time to allow for less flaky testing
	clock clock.Clock

	metrics leaderMetricsAdapter

	// name is the name of the resource lock for debugging
	name string
}

// Run starts the leader election loop. Run will not return
// before leader election loop is stopped by ctx or it has
// stopped holding the leader lease
func (le *LeaderElector) Run(ctx context.Context) {
	defer runtime.HandleCrash()
	defer func() {
		le.config.Callbacks.OnStoppedLeading()
	}()

	if !le.acquire(ctx) {
		return // ctx signalled done
	}
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	go le.config.Callbacks.OnStartedLeading(ctx)
	le.renew(ctx)
}

// RunOrDie starts a client with the provided config or panics if the config
// fails to validate. RunOrDie blocks until leader election loop is
// stopped by ctx or it has stopped holding the leader lease
func RunOrDie(ctx context.Context, lec LeaderElectionConfig) {
	le, err := NewLeaderElector(lec)
	if err != nil {
		panic(err)
	}
	if lec.WatchDog != nil {
		lec.WatchDog.SetLeaderElection(le)
	}
	le.Run(ctx)
}

// GetLeader returns the identity of the last observed leader or returns the empty string if
// no leader has yet been observed.
func (le *LeaderElector) GetLeader() string {
	return le.observedRecord.HolderIdentity
}

// IsLeader returns true if the last observed leader was this client else returns false.
func (le *LeaderElector) IsLeader() bool {
	return le.observedRecord.HolderIdentity == le.config.Lock.Identity()
}

// acquire loops calling tryAcquireOrRenew and returns true immediately when tryAcquireOrRenew succeeds.
// Returns false if ctx signals done.
func (le *LeaderElector) acquire(ctx context.Context) bool {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	succeeded := false
	desc := le.config.Lock.Describe()
	klog.Infof("attempting to acquire leader lease  %v...", desc)
	wait.JitterUntil(func() {
		succeeded = le.tryAcquireOrRenew(ctx)
		le.maybeReportTransition()
		if !succeeded {
			klog.V(4).Infof("failed to acquire lease %v", desc)
			return
		}
		le.config.Lock.RecordEvent("became leader")
		le.metrics.leaderOn(le.config.Name)
		klog.Infof("successfully acquired lease %v", desc)
		cancel()
	}, le.config.RetryPeriod, JitterFactor, true, ctx.Done())
	return succeeded
}

// renew loops calling tryAcquireOrRenew and returns immediately when tryAcquireOrRenew fails or ctx signals done.
func (le *LeaderElector) renew(ctx context.Context) {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	wait.Until(func() {
		timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
		defer timeoutCancel()
		err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
			return le.tryAcquireOrRenew(timeoutCtx), nil
		}, timeoutCtx.Done())

		le.maybeReportTransition()
		desc := le.config.Lock.Describe()
		if err == nil {
			klog.V(5).Infof("successfully renewed lease %v", desc)
			return
		}
		le.config.Lock.RecordEvent("stopped leading")
		le.metrics.leaderOff(le.config.Name)
		klog.Infof("failed to renew lease %v: %v", desc, err)
		cancel()
	}, le.config.RetryPeriod, ctx.Done())

	// if we hold the lease, give it up
	if le.config.ReleaseOnCancel {
		le.release()
	}
}

// release attempts to release the leader lease if we have acquired it.
func (le *LeaderElector) release() bool {
	if !le.IsLeader() {
		return true
	}
	leaderElectionRecord := rl.LeaderElectionRecord{
		LeaderTransitions: le.observedRecord.LeaderTransitions,
	}
	if err := le.config.Lock.Update(context.TODO(), leaderElectionRecord); err != nil {
		klog.Errorf("Failed to release lock: %v", err)
		return false
	}
	le.observedRecord = leaderElectionRecord
	le.observedTime = le.clock.Now()
	return true
}

// tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired,
// else it tries to renew the lease if it has already been acquired. Returns true
// on success else returns false.
func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {
	now := metav1.Now()
	leaderElectionRecord := rl.LeaderElectionRecord{
		HolderIdentity:       le.config.Lock.Identity(),
		LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),
		RenewTime:            now,
		AcquireTime:          now,
	}

	// 1. obtain or create the ElectionRecord
	oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)
	if err != nil {
		if !errors.IsNotFound(err) {
			klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err)
			return false
		}
		if err = le.config.Lock.Create(ctx, leaderElectionRecord); err != nil {
			klog.Errorf("error initially creating leader election record: %v", err)
			return false
		}
		le.observedRecord = leaderElectionRecord
		le.observedTime = le.clock.Now()
		return true
	}

	// 2. Record obtained, check the Identity & Time
	if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
		le.observedRecord = *oldLeaderElectionRecord
		le.observedRawRecord = oldLeaderElectionRawRecord
		le.observedTime = le.clock.Now()
	}
	if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&
		le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&
		!le.IsLeader() {
		klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)
		return false
	}

	// 3. We're going to try to update. The leaderElectionRecord is set to it's default
	// here. Let's correct it before updating.
	if le.IsLeader() {
		leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
		leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
	} else {
		leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
	}

	// update the lock itself
	if err = le.config.Lock.Update(ctx, leaderElectionRecord); err != nil {
		klog.Errorf("Failed to update lock: %v", err)
		return false
	}

	le.observedRecord = leaderElectionRecord
	le.observedTime = le.clock.Now()
	return true
}

func (le *LeaderElector) maybeReportTransition() {
	if le.observedRecord.HolderIdentity == le.reportedLeader {
		return
	}
	le.reportedLeader = le.observedRecord.HolderIdentity
	if le.config.Callbacks.OnNewLeader != nil {
		go le.config.Callbacks.OnNewLeader(le.reportedLeader)
	}
}

// Check will determine if the current lease is expired by more than timeout.
func (le *LeaderElector) Check(maxTolerableExpiredLease time.Duration) error {
	if !le.IsLeader() {
		// Currently not concerned with the case that we are hot standby
		return nil
	}
	// If we are more than timeout seconds after the lease duration that is past the timeout
	// on the lease renew. Time to start reporting ourselves as unhealthy. We should have
	// died but conditions like deadlock can prevent this. (See #70819)
	if le.clock.Since(le.observedTime) > le.config.LeaseDuration+maxTolerableExpiredLease {
		return fmt.Errorf("failed election to renew leadership on lease %s", le.config.Name)
	}

	return nil
}
<|endoftext|>