<commit_before>package main

import (
	"github.com/fhs/gompd/mpd"
	"sync"
	"time"
)

type connMessageType int

const (
	syncLibrary connMessageType = iota
	syncPlaylist
	syncCurrent
	prev
	play
	next
)

type connMessage struct {
	request connMessageType
	err     chan error
}

type connRequest int

/*Dial Connects to mpd server.*/
func Dial(network, addr string) (p *Player, err error) {
	// connect to mpd
	p = new(Player)
	p.m = new(sync.Mutex)
	p.stop = make(chan bool)
	p.c = make(chan *connMessage)
	p.network = network
	p.addr = addr
	err = p.connect()
	if err != nil {
		return p, err
	}

	// initialize library
	err = p.syncLibrary()
	if err != nil {
		p.Close()
		return
	}
	// initialize playlist
	err = p.syncPlaylist()
	if err != nil {
		p.Close()
		return
	}
	err = p.syncCurrent()
	if err != nil {
		p.Close()
		return
	}
	go p.connDaemon()
	go p.watch()
	return
}

/*Player represents mpd control interface.*/
type Player struct {
	network          string
	addr             string
	conn             *mpd.Client
	w                *mpd.Watcher
	m                *sync.Mutex
	stop             chan bool
	c                chan *connMessage
	r                chan *connRequest
	current          mpd.Attrs
	currentModified  time.Time
	comments         mpd.Attrs
	commentsModified time.Time
	library          []mpd.Attrs
	libraryModified  time.Time
	playlist         []mpd.Attrs
	playlistModified time.Time
}

func (p *Player) connDaemon() {
loop:
	for {
		select {
		case <-p.stop:
			break loop
		case m := <-p.c:
			switch m.request {
			case prev:
				m.err <- p.conn.Previous()
			case play:
				m.err <- p.conn.Play(-1)
			case next:
				m.err <- p.conn.Next()
			case syncLibrary:
				m.err <- p.syncLibrary()
			case syncPlaylist:
				m.err <- p.syncPlaylist()
			case syncCurrent:
				m.err <- p.syncCurrent()
			}
		}
	}
}

func (p *Player) connect() error {
	if p.w != nil {
		p.w.Close()
	}
	if p.conn != nil {
		p.conn.Close()
	}
	conn, err := mpd.Dial(p.network, p.addr)
	if err != nil {
		return err
	}
	p.conn = conn
	w, err := mpd.NewWatcher(p.network, p.addr, "")
	if err != nil {
		return err
	}
	p.w = w
	return nil
}

func (p *Player) watch() {
	for subsystem := range p.w.Event {
		switch subsystem {
		case "database":
			p.request(syncLibrary)
		case "playlist":
			p.request(syncPlaylist)
		case "player":
			p.request(syncCurrent)
		}
	}
}

func (p *Player) syncCurrent() error {
	p.m.Lock()
	defer p.m.Unlock()
	song, err := p.conn.CurrentSong()
	if err != nil {
		return err
	}
	status, err := p.conn.Status()
	if err != nil {
		return err
	}
	for k, v := range status {
		song[k] = v
	}
	p.currentModified = time.Now()
	if p.comments == nil || p.current["file"] != song["file"] {
		comments, err := p.conn.ReadComments(song["file"])
		if err != nil {
			return err
		}
		p.commentsModified = time.Now()
		p.comments = comments
	}

	p.current = song
	return nil
}

func (p *Player) syncLibrary() error {
	p.m.Lock()
	defer p.m.Unlock()
	library, err := p.conn.ListAllInfo("/")
	if err != nil {
		return err
	}
	p.library = library
	p.libraryModified = time.Now()
	return nil
}

/*Library returns mpd library song list.*/
func (p *Player) Library() ([]mpd.Attrs, time.Time) {
	p.m.Lock()
	defer p.m.Unlock()
	return p.library, p.libraryModified
}

func (p *Player) syncPlaylist() error {
	p.m.Lock()
	defer p.m.Unlock()
	playlist, err := p.conn.PlaylistInfo(-1, -1)
	if err != nil {
		return err
	}
	p.playlist = playlist
	p.playlistModified = time.Now()
	return nil
}

/*Playlist returns json string mpd playlist.*/
func (p *Player) Playlist() ([]mpd.Attrs, time.Time) {
	p.m.Lock()
	defer p.m.Unlock()
	return p.playlist, p.playlistModified
}

/*Current returns json string mpd current song.*/
func (p *Player) Current() (mpd.Attrs, time.Time) {
	p.m.Lock()
	defer p.m.Unlock()
	return p.current, p.currentModified
}

/*Comments returns json string mpd current song comments.*/
func (p *Player) Comments() (mpd.Attrs, time.Time) {
	p.m.Lock()
	defer p.m.Unlock()
	return p.comments, p.commentsModified
}

func (p *Player) request(req connMessageType) error {
	r := new(connMessage)
	r.request = req
	r.err = make(chan error)
	p.c <- r
	return <-r.err
}

/*Prev song.*/
func (p *Player) Prev() error {
	return p.request(prev)
}

/*Play or resume song.*/
func (p *Player) Play() error {
	return p.request(play)
}

/*Pause song.*/
func (p *Player) Pause() error {
	return p.conn.Pause(true)
}

/*Next song.*/
func (p *Player) Next() error {
	return p.request(next)
}

/*Close mpd connection.*/
func (p *Player) Close() error {
	p.stop <- true
	return p.conn.Close()
}
<commit_msg>dereference mpd ptr<commit_after>package main

import (
	"github.com/fhs/gompd/mpd"
	"sync"
	"time"
)

type connMessageType int

const (
	syncLibrary connMessageType = iota
	syncPlaylist
	syncCurrent
	prev
	play
	next
)

type connMessage struct {
	request connMessageType
	err     chan error
}

type connRequest int

/*Dial Connects to mpd server.*/
func Dial(network, addr string) (p *Player, err error) {
	// connect to mpd
	p = new(Player)
	p.m = new(sync.Mutex)
	p.stop = make(chan bool)
	p.c = make(chan *connMessage)
	p.network = network
	p.addr = addr
	err = p.connect()
	if err != nil {
		return p, err
	}

	// initialize library
	err = p.syncLibrary()
	if err != nil {
		p.Close()
		return
	}
	// initialize playlist
	err = p.syncPlaylist()
	if err != nil {
		p.Close()
		return
	}
	err = p.syncCurrent()
	if err != nil {
		p.Close()
		return
	}
	go p.connDaemon()
	go p.watch()
	return
}

/*Player represents mpd control interface.*/
type Player struct {
	network          string
	addr             string
	conn             mpd.Client
	w                mpd.Watcher
	m                *sync.Mutex
	stop             chan bool
	c                chan *connMessage
	r                chan *connRequest
	current          mpd.Attrs
	currentModified  time.Time
	comments         mpd.Attrs
	commentsModified time.Time
	library          []mpd.Attrs
	libraryModified  time.Time
	playlist         []mpd.Attrs
	playlistModified time.Time
}

func (p *Player) connDaemon() {
loop:
	for {
		select {
		case <-p.stop:
			break loop
		case m := <-p.c:
			switch m.request {
			case prev:
				m.err <- p.conn.Previous()
			case play:
				m.err <- p.conn.Play(-1)
			case next:
				m.err <- p.conn.Next()
			case syncLibrary:
				m.err <- p.syncLibrary()
			case syncPlaylist:
				m.err <- p.syncPlaylist()
			case syncCurrent:
				m.err <- p.syncCurrent()
			}
		}
	}
}

func (p *Player) reconnect() error {
	p.w.Close()
	p.conn.Close()
	return p.connect()
}

func (p *Player) connect() error {
	conn, err := mpd.Dial(p.network, p.addr)
	if err != nil {
		return err
	}
	p.conn = *conn
	w, err := mpd.NewWatcher(p.network, p.addr, "")
	if err != nil {
		return err
	}
	p.w = *w
	return nil
}

func (p *Player) watch() {
	for subsystem := range p.w.Event {
		switch subsystem {
		case "database":
			p.request(syncLibrary)
		case "playlist":
			p.request(syncPlaylist)
		case "player":
			p.request(syncCurrent)
		}
	}
}

func (p *Player) syncCurrent() error {
	p.m.Lock()
	defer p.m.Unlock()
	song, err := p.conn.CurrentSong()
	if err != nil {
		return err
	}
	status, err := p.conn.Status()
	if err != nil {
		return err
	}
	for k, v := range status {
		song[k] = v
	}
	p.currentModified = time.Now()
	if p.comments == nil || p.current["file"] != song["file"] {
		comments, err := p.conn.ReadComments(song["file"])
		if err != nil {
			return err
		}
		p.commentsModified = time.Now()
		p.comments = comments
	}

	p.current = song
	return nil
}

func (p *Player) syncLibrary() error {
	p.m.Lock()
	defer p.m.Unlock()
	library, err := p.conn.ListAllInfo("/")
	if err != nil {
		return err
	}
	p.library = library
	p.libraryModified = time.Now()
	return nil
}

/*Library returns mpd library song list.*/
func (p *Player) Library() ([]mpd.Attrs, time.Time) {
	p.m.Lock()
	defer p.m.Unlock()
	return p.library, p.libraryModified
}

func (p *Player) syncPlaylist() error {
	p.m.Lock()
	defer p.m.Unlock()
	playlist, err := p.conn.PlaylistInfo(-1, -1)
	if err != nil {
		return err
	}
	p.playlist = playlist
	p.playlistModified = time.Now()
	return nil
}

/*Playlist returns json string mpd playlist.*/
func (p *Player) Playlist() ([]mpd.Attrs, time.Time) {
	p.m.Lock()
	defer p.m.Unlock()
	return p.playlist, p.playlistModified
}

/*Current returns json string mpd current song.*/
func (p *Player) Current() (mpd.Attrs, time.Time) {
	p.m.Lock()
	defer p.m.Unlock()
	return p.current, p.currentModified
}

/*Comments returns json string mpd current song comments.*/
func (p *Player) Comments() (mpd.Attrs, time.Time) {
	p.m.Lock()
	defer p.m.Unlock()
	return p.comments, p.commentsModified
}

func (p *Player) request(req connMessageType) error {
	r := new(connMessage)
	r.request = req
	r.err = make(chan error)
	p.c <- r
	return <-r.err
}

/*Prev song.*/
func (p *Player) Prev() error {
	return p.request(prev)
}

/*Play or resume song.*/
func (p *Player) Play() error {
	return p.request(play)
}

/*Pause song.*/
func (p *Player) Pause() error {
	return p.conn.Pause(true)
}

/*Next song.*/
func (p *Player) Next() error {
	return p.request(next)
}

/*Close mpd connection.*/
func (p *Player) Close() error {
	p.stop <- true
	return p.conn.Close()
}
<|endoftext|>


<commit_before>// package assert provides convenience assert methods to complement
// the built in go testing library. It's intended to add onto standard
// Go tests. Example usage:
//	func TestSomething(t *testing.T) {
//		i, err := doSomething()
//		assert.NoErr(err)
//		assert.Equal(i, 123, "returned integer")
//	}
package assert

import (
	"fmt"
	"runtime"
	"testing"
	"reflect"
)

// callerStr returns a string representation of the code numFrames stack
// frames above the code that called callerStr
func callerStr(numFrames int) string {
	_, file, line, _ := runtime.Caller(1 + numFrames)
	return fmt.Sprintf("%s:%d", file, line)
}

// callerStrErrorf calls t.Errorf with fmtStr and vals in it, prefixed
// by a callerStr representation of the code numFrames above the caller of
// this function
func callerStrf(numFrames int, fmtStr string, vals ...interface{}) string {
	origStr := fmt.Sprintf(fmtStr, vals...)
	return fmt.Sprintf("%s: %s", callerStr(1+numFrames), origStr)
}

// True fails the test if b is false. on failure, it calls
// t.Errorf(fmtStr, vals...)
func True(t *testing.T, b bool, fmtStr string, vals ...interface{}) {
	if !b {
		t.Errorf(callerStrf(1, fmtStr, vals...))
	}
}

// False is the equivalent of True(t, !b, fmtStr, vals...).
func False(t *testing.T, b bool, fmtStr string, vals ...interface{}) {
	if b {
		t.Errorf(callerStrf(1, fmtStr, vals...))
	}
}

// Nil uses reflect.DeepEqual(i, nil) to determine if i is nil. if it's not,
// Nil calls t.Errorf explaining that the noun i is not nil when it should have
// been
func Nil(t *testing.T, i interface{}, noun string) {
	if !reflect.DeepEqual(i, nil) {
		t.Errorf(callerStrf(1, "the given %s [%+v] was not nil when it should have been", noun, i))
	}
}

// NotNil uses reflect.DeepEqual(i, nil) to determine if i is nil.
// if it is, NotNil calls t.Errorf explaining that the noun i is not nil when
// it shouldn't have been.
func NotNil(t *testing.T, i interface{}, noun string) {
	if reflect.DeepEqual(i, nil) {
		t.Errorf(callerStrf(1, "the given %s was nil when it shouldn't have been", noun))
	}
}

// Err calls t.Errorf if expected is not equal to actual.
// it uses reflect.DeepEqual to determine if the errors are equal
func Err(t *testing.T, expected error, actual error) {
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf(callerStrf(1, "expected error %s but got %s", expected, actual))
	}
}

// if err == nil, ExistsErr calls t.Errorf explaining that the error described by noun was
// nil when it shouldn't have been
func ExistsErr(t *testing.T, err error, noun string) {
	if err == nil {
		t.Errorf(callerStrf(1, "given error for %s was nil when it shouldn't have been", noun))
	}
}

// NoErr calls t.Errorf if e is not nil.
func NoErr(t *testing.T, e error) {
	if e != nil {
		t.Errorf(callerStrf(1, "expected no error but got %s", e))
	}
}

// Equal ensures that the actual value returned from a test was equal to an
// expected. it uses reflect.DeepEqual to do so.
// name is used to describe the values being compared. it's used in the error
// string if actual != expected.
func Equal(t *testing.T, actual, expected interface{}, noun string) {
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf(callerStrf(1, "actual %s [%+v] != expected %s [%+v]", noun, actual, noun, expected))
	}
}
<commit_msg>fixing bug in docs<commit_after>// package assert provides convenience assert methods to complement
// the built in go testing library. It's intended to add onto standard
// Go tests. Example usage:
//	func TestSomething(t *testing.T) {
//		i, err := doSomething()
//		assert.NoErr(err)
//		assert.Equal(i, 123, "returned integer")
//	}
package assert

import (
	"fmt"
	"runtime"
	"testing"
	"reflect"
)

// callerStr returns a string representation of the code numFrames stack
// frames above the code that called callerStr
func callerStr(numFrames int) string {
	_, file, line, _ := runtime.Caller(1 + numFrames)
	return fmt.Sprintf("%s:%d", file, line)
}

// callerStrErrorf calls t.Errorf with fmtStr and vals in it, prefixed
// by a callerStr representation of the code numFrames above the caller of
// this function
func callerStrf(numFrames int, fmtStr string, vals ...interface{}) string {
	origStr := fmt.Sprintf(fmtStr, vals...)
	return fmt.Sprintf("%s: %s", callerStr(1+numFrames), origStr)
}

// True fails the test if b is false. on failure, it calls
// t.Errorf(fmtStr, vals...)
func True(t *testing.T, b bool, fmtStr string, vals ...interface{}) {
	if !b {
		t.Errorf(callerStrf(1, fmtStr, vals...))
	}
}

// False is the equivalent of True(t, !b, fmtStr, vals...).
func False(t *testing.T, b bool, fmtStr string, vals ...interface{}) {
	if b {
		t.Errorf(callerStrf(1, fmtStr, vals...))
	}
}

// Nil uses reflect.DeepEqual(i, nil) to determine if i is nil. if it's not,
// Nil calls t.Errorf explaining that the noun i is not nil when it should have
// been
func Nil(t *testing.T, i interface{}, noun string) {
	if !reflect.DeepEqual(i, nil) {
		t.Errorf(callerStrf(1, "the given %s [%+v] was not nil when it should have been", noun, i))
	}
}

// NotNil uses reflect.DeepEqual(i, nil) to determine if i is nil.
// if it is, NotNil calls t.Errorf explaining that the noun i is nil when it
// shouldn't have been.
func NotNil(t *testing.T, i interface{}, noun string) {
	if reflect.DeepEqual(i, nil) {
		t.Errorf(callerStrf(1, "the given %s was nil when it shouldn't have been", noun))
	}
}

// Err calls t.Errorf if expected is not equal to actual.
// it uses reflect.DeepEqual to determine if the errors are equal
func Err(t *testing.T, expected error, actual error) {
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf(callerStrf(1, "expected error %s but got %s", expected, actual))
	}
}

// if err == nil, ExistsErr calls t.Errorf explaining that the error described by noun was
// nil when it shouldn't have been
func ExistsErr(t *testing.T, err error, noun string) {
	if err == nil {
		t.Errorf(callerStrf(1, "given error for %s was nil when it shouldn't have been", noun))
	}
}

// NoErr calls t.Errorf if e is not nil.
func NoErr(t *testing.T, e error) {
	if e != nil {
		t.Errorf(callerStrf(1, "expected no error but got %s", e))
	}
}

// Equal ensures that the actual value returned from a test was equal to an
// expected. it uses reflect.DeepEqual to do so.
// name is used to describe the values being compared. it's used in the error
// string if actual != expected.
func Equal(t *testing.T, actual, expected interface{}, noun string) {
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf(callerStrf(1, "actual %s [%+v] != expected %s [%+v]", noun, actual, noun, expected))
	}
}
<|endoftext|>


<commit_before><commit_msg>welcome home<commit_after><|endoftext|>


<commit_before><commit_msg>fix server bodylen<commit_after><|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/appleboy/drone-facebook/template"
)

const (
	// DroneIconURL default drone logo url
	DroneIconURL = "https://c1.staticflickr.com/5/4236/34957940160_435d83114f_z.jpg"
	// DroneDesc default drone description
	DroneDesc = "Powered by Drone Discord Plugin"
)

type (
	// GitHub information.
	GitHub struct {
		Workflow  string
		Workspace string
		Action    string
		EventName string
		EventPath string
	}

	// Repo information
	Repo struct {
		FullName  string
		Namespace string
		Name      string
	}

	// Build information
	Build struct {
		Tag      string
		Event    string
		Number   int
		Commit   string
		RefSpec  string
		Branch   string
		Author   string
		Avatar   string
		Message  string
		Email    string
		Status   string
		Link     string
		Started  float64
		Finished float64
	}

	// Config for the plugin.
	Config struct {
		WebhookID    string
		WebhookToken string
		Color        string
		Message      []string
		File         []string
		Drone        bool
		GitHub       bool
	}

	// EmbedFooterObject for Embed Footer Structure.
	EmbedFooterObject struct {
		Text    string `json:"text"`
		IconURL string `json:"icon_url"`
	}

	// EmbedAuthorObject for Embed Author Structure
	EmbedAuthorObject struct {
		Name    string `json:"name"`
		URL     string `json:"url"`
		IconURL string `json:"icon_url"`
	}

	// EmbedFieldObject for Embed Field Structure
	EmbedFieldObject struct {
		Name  string `json:"name"`
		Value string `json:"value"`
	}

	// EmbedObject is for Embed Structure
	EmbedObject struct {
		Title       string             `json:"title"`
		Description string             `json:"description"`
		URL         string             `json:"url"`
		Color       int                `json:"color"`
		Footer      EmbedFooterObject  `json:"footer"`
		Author      EmbedAuthorObject  `json:"author"`
		Fields      []EmbedFieldObject `json:"fields"`
	}

	// Payload struct
	Payload struct {
		Wait      bool          `json:"wait"`
		Content   string        `json:"content"`
		Username  string        `json:"username"`
		AvatarURL string        `json:"avatar_url"`
		TTS       bool          `json:"tts"`
		Embeds    []EmbedObject `json:"embeds"`
	}

	// Plugin values.
	Plugin struct {
		GitHub  GitHub
		Repo    Repo
		Build   Build
		Config  Config
		Payload Payload
	}
)

// Creates a new file upload http request with optional extra params
func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(paramName, filepath.Base(path))
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(part, file)

	for key, val := range params {
		_ = writer.WriteField(key, val)
	}
	err = writer.Close()
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", uri, body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	return req, err
}

// Exec executes the plugin.
func (p *Plugin) Exec() error {
	if p.Config.WebhookID == "" || p.Config.WebhookToken == "" {
		return errors.New("missing discord config")
	}

	if len(p.Config.Message) == 0 {
		object := p.Template()
		p.Payload.Embeds = []EmbedObject{object}
		err := p.SendMessage()
		if err != nil {
			return err
		}
	}

	if len(p.Config.Message) > 0 {
		for _, m := range p.Config.Message {
			txt, err := template.RenderTrim(m, p)
			if err != nil {
				return err
			}

			if len(p.Config.Color) != 0 {
				object := p.DefaultTemplate(txt)
				p.Payload.Embeds = append(p.Payload.Embeds, object)
			} else {
				p.Payload.Content = txt
				err = p.SendMessage()
				if err != nil {
					return err
				}
			}
		}

		if len(p.Payload.Embeds) > 0 {
			err := p.SendMessage()
			if err != nil {
				return err
			}
		}
	}

	for _, f := range p.Config.File {
		err := p.SendFile(f)
		if err != nil {
			return err
		}
	}

	return nil
}

// SendFile upload file to discord
func (p *Plugin) SendFile(file string) error {
	webhookURL := fmt.Sprintf("https://discordapp.com/api/webhooks/%s/%s", p.Config.WebhookID, p.Config.WebhookToken)
	extraParams := map[string]string{}

	if p.Payload.Username != "" {
		extraParams["username"] = p.Payload.Username
	}

	if p.Payload.AvatarURL != "" {
		extraParams["avatar_url"] = p.Payload.AvatarURL
	}

	if p.Payload.TTS {
		extraParams["tts"] = "true"
	}

	request, err := newfileUploadRequest(
		webhookURL,
		extraParams,
		"file",
		file,
	)
	if err != nil {
		return err
	}
	client := &http.Client{}
	_, err = client.Do(request)
	if err != nil {
		return err
	}

	return nil
}

// SendMessage to send discord message.
func (p *Plugin) SendMessage() error {
	webhookURL := fmt.Sprintf("https://discordapp.com/api/webhooks/%s/%s", p.Config.WebhookID, p.Config.WebhookToken)
	b := new(bytes.Buffer)
	if err := json.NewEncoder(b).Encode(p.Payload); err != nil {
		return err
	}
	_, err := http.Post(webhookURL, "application/json; charset=utf-8", b)

	if err != nil {
		return err
	}

	return nil
}

// DefaultTemplate is plugin default template for Drone CI.
func (p *Plugin) DefaultTemplate(title string) EmbedObject {
	return EmbedObject{
		Title: title,
		Color: p.Color(),
	}
}

// Template is plugin default template for Drone CI or GitHub Action.
func (p *Plugin) Template() EmbedObject {
	if p.Config.GitHub {
		message := fmt.Sprintf("%s/%s triggered by %s (%s)",
			p.Repo.FullName,
			p.GitHub.Workflow,
			p.Repo.Namespace,
			p.GitHub.EventName,
		)

		return EmbedObject{
			Title: message,
			Color: p.Color(),
			Footer: EmbedFooterObject{
				Text:    DroneDesc,
				IconURL: DroneIconURL,
			},
		}
	}

	description := ""
	switch p.Build.Event {
	case "push":
		description = fmt.Sprintf("%s pushed to %s", p.Build.Author, p.Build.Branch)
	case "pull_request":
		branch := ""
		if p.Build.RefSpec != "" {
			branch = p.Build.RefSpec
		} else {
			branch = p.Build.Branch
		}
		description = fmt.Sprintf("%s updated pull request %s", p.Build.Author, branch)
	case "tag":
		description = fmt.Sprintf("%s pushed tag %s", p.Build.Author, p.Build.Branch)
	}

	return EmbedObject{
		Title:       p.Build.Message,
		Description: description,
		URL:         p.Build.Link,
		Color:       p.Color(),
		Author: EmbedAuthorObject{
			Name:    p.Build.Author,
			IconURL: p.Build.Avatar,
		},
		Footer: EmbedFooterObject{
			Text:    DroneDesc,
			IconURL: DroneIconURL,
		},
	}
}

// Clear reset to default
func (p *Plugin) Clear() {
	// clear content field.
	p.Payload.Content = ""
	p.Payload.Embeds = []EmbedObject{}
}

// Color code of the embed
func (p *Plugin) Color() int {
	if p.Config.Color != "" {
		p.Config.Color = strings.Replace(p.Config.Color, "#", "", -1)
		if s, err := strconv.ParseInt(p.Config.Color, 16, 32); err == nil {
			return int(s)
		}
	}

	switch p.Build.Status {
	case "success":
		// green
		return 0x1ac600
	case "failure", "error", "killed":
		// red
		return 0xff3232
	default:
		// yellow
		return 0xffd930
	}
}
<commit_msg>docs: ci skip: add ref for upload file<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/appleboy/drone-facebook/template"
)

const (
	// DroneIconURL default drone logo url
	DroneIconURL = "https://c1.staticflickr.com/5/4236/34957940160_435d83114f_z.jpg"
	// DroneDesc default drone description
	DroneDesc = "Powered by Drone Discord Plugin"
)

type (
	// GitHub information.
	GitHub struct {
		Workflow  string
		Workspace string
		Action    string
		EventName string
		EventPath string
	}

	// Repo information
	Repo struct {
		FullName  string
		Namespace string
		Name      string
	}

	// Build information
	Build struct {
		Tag      string
		Event    string
		Number   int
		Commit   string
		RefSpec  string
		Branch   string
		Author   string
		Avatar   string
		Message  string
		Email    string
		Status   string
		Link     string
		Started  float64
		Finished float64
	}

	// Config for the plugin.
	Config struct {
		WebhookID    string
		WebhookToken string
		Color        string
		Message      []string
		File         []string
		Drone        bool
		GitHub       bool
	}

	// EmbedFooterObject for Embed Footer Structure.
	EmbedFooterObject struct {
		Text    string `json:"text"`
		IconURL string `json:"icon_url"`
	}

	// EmbedAuthorObject for Embed Author Structure
	EmbedAuthorObject struct {
		Name    string `json:"name"`
		URL     string `json:"url"`
		IconURL string `json:"icon_url"`
	}

	// EmbedFieldObject for Embed Field Structure
	EmbedFieldObject struct {
		Name  string `json:"name"`
		Value string `json:"value"`
	}

	// EmbedObject is for Embed Structure
	EmbedObject struct {
		Title       string             `json:"title"`
		Description string             `json:"description"`
		URL         string             `json:"url"`
		Color       int                `json:"color"`
		Footer      EmbedFooterObject  `json:"footer"`
		Author      EmbedAuthorObject  `json:"author"`
		Fields      []EmbedFieldObject `json:"fields"`
	}

	// Payload struct
	Payload struct {
		Wait      bool          `json:"wait"`
		Content   string        `json:"content"`
		Username  string        `json:"username"`
		AvatarURL string        `json:"avatar_url"`
		TTS       bool          `json:"tts"`
		Embeds    []EmbedObject `json:"embeds"`
	}

	// Plugin values.
	Plugin struct {
		GitHub  GitHub
		Repo    Repo
		Build   Build
		Config  Config
		Payload Payload
	}
)

// Creates a new file upload http request with optional extra params
// https://matt.aimonetti.net/posts/2013/07/01/golang-multipart-file-upload-example/
func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(paramName, filepath.Base(path))
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(part, file)

	for key, val := range params {
		_ = writer.WriteField(key, val)
	}
	err = writer.Close()
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", uri, body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	return req, err
}

// Exec executes the plugin.
func (p *Plugin) Exec() error {
	if p.Config.WebhookID == "" || p.Config.WebhookToken == "" {
		return errors.New("missing discord config")
	}

	if len(p.Config.Message) == 0 {
		object := p.Template()
		p.Payload.Embeds = []EmbedObject{object}
		err := p.SendMessage()
		if err != nil {
			return err
		}
	}

	if len(p.Config.Message) > 0 {
		for _, m := range p.Config.Message {
			txt, err := template.RenderTrim(m, p)
			if err != nil {
				return err
			}

			if len(p.Config.Color) != 0 {
				object := p.DefaultTemplate(txt)
				p.Payload.Embeds = append(p.Payload.Embeds, object)
			} else {
				p.Payload.Content = txt
				err = p.SendMessage()
				if err != nil {
					return err
				}
			}
		}

		if len(p.Payload.Embeds) > 0 {
			err := p.SendMessage()
			if err != nil {
				return err
			}
		}
	}

	for _, f := range p.Config.File {
		err := p.SendFile(f)
		if err != nil {
			return err
		}
	}

	return nil
}

// SendFile upload file to discord
func (p *Plugin) SendFile(file string) error {
	webhookURL := fmt.Sprintf("https://discordapp.com/api/webhooks/%s/%s", p.Config.WebhookID, p.Config.WebhookToken)
	extraParams := map[string]string{}

	if p.Payload.Username != "" {
		extraParams["username"] = p.Payload.Username
	}

	if p.Payload.AvatarURL != "" {
		extraParams["avatar_url"] = p.Payload.AvatarURL
	}

	if p.Payload.TTS {
		extraParams["tts"] = "true"
	}

	request, err := newfileUploadRequest(
		webhookURL,
		extraParams,
		"file",
		file,
	)
	if err != nil {
		return err
	}
	client := &http.Client{}
	_, err = client.Do(request)
	if err != nil {
		return err
	}

	return nil
}

// SendMessage to send discord message.
func (p *Plugin) SendMessage() error {
	webhookURL := fmt.Sprintf("https://discordapp.com/api/webhooks/%s/%s", p.Config.WebhookID, p.Config.WebhookToken)
	b := new(bytes.Buffer)
	if err := json.NewEncoder(b).Encode(p.Payload); err != nil {
		return err
	}
	_, err := http.Post(webhookURL, "application/json; charset=utf-8", b)

	if err != nil {
		return err
	}

	return nil
}

// DefaultTemplate is plugin default template for Drone CI.
func (p *Plugin) DefaultTemplate(title string) EmbedObject {
	return EmbedObject{
		Title: title,
		Color: p.Color(),
	}
}

// Template is plugin default template for Drone CI or GitHub Action.
func (p *Plugin) Template() EmbedObject {
	if p.Config.GitHub {
		message := fmt.Sprintf("%s/%s triggered by %s (%s)",
			p.Repo.FullName,
			p.GitHub.Workflow,
			p.Repo.Namespace,
			p.GitHub.EventName,
		)

		return EmbedObject{
			Title: message,
			Color: p.Color(),
			Footer: EmbedFooterObject{
				Text:    DroneDesc,
				IconURL: DroneIconURL,
			},
		}
	}

	description := ""
	switch p.Build.Event {
	case "push":
		description = fmt.Sprintf("%s pushed to %s", p.Build.Author, p.Build.Branch)
	case "pull_request":
		branch := ""
		if p.Build.RefSpec != "" {
			branch = p.Build.RefSpec
		} else {
			branch = p.Build.Branch
		}
		description = fmt.Sprintf("%s updated pull request %s", p.Build.Author, branch)
	case "tag":
		description = fmt.Sprintf("%s pushed tag %s", p.Build.Author, p.Build.Branch)
	}

	return EmbedObject{
		Title:       p.Build.Message,
		Description: description,
		URL:         p.Build.Link,
		Color:       p.Color(),
		Author: EmbedAuthorObject{
			Name:    p.Build.Author,
			IconURL: p.Build.Avatar,
		},
		Footer: EmbedFooterObject{
			Text:    DroneDesc,
			IconURL: DroneIconURL,
		},
	}
}

// Clear reset to default
func (p *Plugin) Clear() {
	// clear content field.
	p.Payload.Content = ""
	p.Payload.Embeds = []EmbedObject{}
}

// Color code of the embed
func (p *Plugin) Color() int {
	if p.Config.Color != "" {
		p.Config.Color = strings.Replace(p.Config.Color, "#", "", -1)
		if s, err := strconv.ParseInt(p.Config.Color, 16, 32); err == nil {
			return int(s)
		}
	}

	switch p.Build.Status {
	case "success":
		// green
		return 0x1ac600
	case "failure", "error", "killed":
		// red
		return 0xff3232
	default:
		// yellow
		return 0xffd930
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/getgauge/common"
	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/gauge_messages"
	"github.com/golang/protobuf/proto"
	"net"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	executionScope          = "execution"
	setupScope              = "setup"
	pluginConnectionPortEnv = "plugin_connection_port"
)

type pluginDescriptor struct {
	Id          string
	Version     string
	Name        string
	Description string
	Command     struct {
		Windows []string
		Linux   []string
		Darwin  []string
	}
	Scope      []string
	pluginPath string
}

type pluginHandler struct {
	pluginsMap map[string]*plugin
}

type plugin struct {
	connection net.Conn
	pluginCmd  *exec.Cmd
	descriptor *pluginDescriptor
}

func (plugin *plugin) kill(wg *sync.WaitGroup) error {
	defer wg.Done()
	if plugin.isStillRunning() {

		exited := make(chan bool, 1)
		go func() {
			for {
				if plugin.isStillRunning() {
					time.Sleep(100 * time.Millisecond)
				} else {
					exited <- true
					return
				}
			}
		}()

		select {
		case done := <-exited:
			if done {
				log.Debug("Plugin [%s] with pid [%d] has exited", plugin.descriptor.Name, plugin.pluginCmd.Process.Pid)
			}
		case <-time.After(config.PluginConnectionTimeout()):
			log.Warning("Plugin [%s] with pid [%d] did not exit after %.2f seconds. Forcefully killing it.", plugin.descriptor.Name, plugin.pluginCmd.Process.Pid, config.PluginConnectionTimeout().Seconds())
			return plugin.pluginCmd.Process.Kill()
		}
	}
	return nil
}

func (plugin *plugin) isStillRunning() bool {
	return plugin.pluginCmd.ProcessState == nil || !plugin.pluginCmd.ProcessState.Exited()
}

func isPluginInstalled(pluginName, pluginVersion string) bool {
	pluginsInstallDir, err := common.GetPluginsInstallDir(pluginName)
	if err != nil {
		return false
	}

	thisPluginDir := path.Join(pluginsInstallDir, pluginName)
	if !common.DirExists(thisPluginDir) {
		return false
	}

	if pluginVersion != "" {
		pluginJson := path.Join(thisPluginDir, pluginVersion, common.PluginJsonFile)
		if common.FileExists(pluginJson) {
			return true
		} else {
			return false
		}
	} else {
		return true
	}
}

func getPluginJsonPath(pluginName, version string) (string, error) {
	if !isPluginInstalled(pluginName, version) {
		return "", errors.New(fmt.Sprintf("%s %s is not installed", pluginName, version))
	}

	pluginInstallDir, err := common.GetPluginInstallDir(pluginName, "")
	if err != nil {
		return "", err
	}
	return filepath.Join(pluginInstallDir, common.PluginJsonFile), nil
}

func getPluginDescriptor(pluginId, pluginVersion string) (*pluginDescriptor, error) {
	pluginJson, err := getPluginJsonPath(pluginId, pluginVersion)
	if err != nil {
		return nil, err
	}

	pluginJsonContents, err := common.ReadFileContents(pluginJson)
	if err != nil {
		return nil, err
	}
	var pd pluginDescriptor
	if err = json.Unmarshal([]byte(pluginJsonContents), &pd); err != nil {
		return nil, errors.New(fmt.Sprintf("%s: %s", pluginJson, err.Error()))
	}
	pd.pluginPath = filepath.Dir(pluginJson)

	return &pd, nil
}

func startPlugin(pd *pluginDescriptor, action string, wait bool) (*exec.Cmd, error) {
	command := []string{}
	switch runtime.GOOS {
	case "windows":
		command = pd.Command.Windows
		break
	case "darwin":
		command = pd.Command.Darwin
		break
	default:
		command = pd.Command.Linux
		break
	}
	if len(command) == 0 {
		return nil, errors.New(fmt.Sprintf("Platform specific command not specified: %s.", runtime.GOOS))
	}

	pluginLogger := &pluginLogger{pluginName: pd.Name}
	cmd, err := common.ExecuteCommand(command, pd.pluginPath, pluginLogger, pluginLogger)

	if err != nil {
		return nil, err
	}

	if wait {
		return cmd, cmd.Wait()
	} else {
		go func() {
			cmd.Wait()
		}()
	}

	return cmd, nil
}

func setEnvForPlugin(action string, pd *pluginDescriptor, manifest *manifest, pluginEnvVars map[string]string) error {
	pluginEnvVars[fmt.Sprintf("%s_action", pd.Id)] = action
	pluginEnvVars["test_language"] = manifest.Language
	if err := setEnvironmentProperties(pluginEnvVars); err != nil {
		return err
	}
	return nil
}

func setEnvironmentProperties(properties map[string]string) error {
	for k, v := range properties {
		if err := common.SetEnvVariable(k, v); err != nil {
			return err
		}
	}
	return nil
}

func addPluginToTheProject(pluginName string, pluginArgs map[string]string, manifest *manifest) error {
	pd, err := getPluginDescriptor(pluginName, pluginArgs["version"])
	if err != nil {
		return err
	}

	if isPluginAdded(manifest, pd) {
		return errors.New("Plugin " + pd.Name + " is already added")
	}
	action := setupScope
	if err := setEnvForPlugin(action, pd, manifest, pluginArgs); err != nil {
		return err
	}
	if _, err := startPlugin(pd, action, true); err != nil {
		return err
	}
	manifest.Plugins = append(manifest.Plugins, pd.Id)
	return manifest.save()
}

func isPluginAdded(manifest *manifest, descriptor *pluginDescriptor) bool {
	for _, pluginId := range manifest.Plugins {
		if pluginId == descriptor.Id {
			return true
		}
	}
	return false
}

func startPluginsForExecution(manifest *manifest) (*pluginHandler, []string) {
	warnings := make([]string, 0)
	handler := &pluginHandler{}
	envProperties := make(map[string]string)

	for _, pluginId := range manifest.Plugins {
		pd, err := getPluginDescriptor(pluginId, "")
		if err != nil {
			warnings = append(warnings, fmt.Sprintf("Error starting plugin %s. Failed to get plugin.json. %s", pluginId, err.Error()))
			continue
		}
		if isExecutionScopePlugin(pd) {
			gaugeConnectionHandler, err := newGaugeConnectionHandler(0, nil)
			if err != nil {
				warnings = append(warnings, err.Error())
				continue
			}
			envProperties[pluginConnectionPortEnv] = strconv.Itoa(gaugeConnectionHandler.connectionPortNumber())
			setEnvForPlugin(executionScope, pd, manifest, envProperties)

			pluginCmd, err := startPlugin(pd, executionScope, false)
			if err != nil {
				warnings = append(warnings, fmt.Sprintf("Error starting plugin %s %s. %s", pd.Name, pd.Version, err.Error()))
				continue
			}
			pluginConnection, err := gaugeConnectionHandler.acceptConnection(config.PluginConnectionTimeout(), make(chan error))
			if err != nil {
				warnings = append(warnings, fmt.Sprintf("Error starting plugin %s %s. Failed to connect to plugin. %s", pd.Name, pd.Version, err.Error()))
				pluginCmd.Process.Kill()
				continue
			}
			handler.addPlugin(pluginId, &plugin{connection: pluginConnection, pluginCmd: pluginCmd, descriptor: pd})
		}

	}
	return handler, warnings
}

func isExecutionScopePlugin(pd *pluginDescriptor) bool {
	for _, scope := range pd.Scope {
		if strings.ToLower(scope) == executionScope {
			return true
		}
	}
	return false
}

func (handler *pluginHandler) addPlugin(pluginId string, pluginToAdd *plugin) {
	if handler.pluginsMap == nil {
		handler.pluginsMap = make(map[string]*plugin)
	}
	handler.pluginsMap[pluginId] = pluginToAdd
}

func (handler *pluginHandler) removePlugin(pluginId string) {
	delete(handler.pluginsMap, pluginId)
}

func (handler *pluginHandler) notifyPlugins(message *gauge_messages.Message) {
	for id, plugin := range handler.pluginsMap {
		err := plugin.sendMessage(message)
		if err != nil {
			log.Error("Unable to connect to plugin %s %s. %s\n", plugin.descriptor.Name, plugin.descriptor.Version, err.Error())
			handler.killPlugin(id)
		}
	}
}

func (handler *pluginHandler) killPlugin(pluginId string) {
	plugin := handler.pluginsMap[pluginId]
	log.Debug("Killing Plugin %s %s\n", plugin.descriptor.Name, plugin.descriptor.Version)
	err := plugin.pluginCmd.Process.Kill()
	if err != nil {
		log.Error("Failed to kill plugin %s %s. %s\n", plugin.descriptor.Name, plugin.descriptor.Version, err.Error())
	}
	handler.removePlugin(pluginId)
}

func (handler *pluginHandler) gracefullyKillPlugins() {
	var wg sync.WaitGroup
	for _, plugin := range handler.pluginsMap {
		wg.Add(1)
		go plugin.kill(&wg)
	}
	wg.Wait()
}

func (plugin *plugin) sendMessage(message *gauge_messages.Message) error {
	messageId := common.GetUniqueId()
	message.MessageId = &messageId
	messageBytes, err := proto.Marshal(message)
	if err != nil {
		return err
	}
	err = write(plugin.connection, messageBytes)
	if err != nil {
		return errors.New(fmt.Sprintf("[Warning] Failed to send message to plugin: %d  %s", plugin.descriptor.Id, err.Error()))
	}
	return nil
}
<commit_msg>Adding version check when starting a plugin<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/getgauge/common"
	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/gauge_messages"
	"github.com/golang/protobuf/proto"
	"net"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	executionScope          = "execution"
	setupScope              = "setup"
	pluginConnectionPortEnv = "plugin_connection_port"
)

type pluginDescriptor struct {
	Id          string
	Version     string
	Name        string
	Description string
	Command     struct {
		Windows []string
		Linux   []string
		Darwin  []string
	}
	Scope      []string
	GaugeVersionSupport versionSupport
	pluginPath string
}

type pluginHandler struct {
	pluginsMap map[string]*plugin
}

type plugin struct {
	connection net.Conn
	pluginCmd  *exec.Cmd
	descriptor *pluginDescriptor
}

func (plugin *plugin) kill(wg *sync.WaitGroup) error {
	defer wg.Done()
	if plugin.isStillRunning() {

		exited := make(chan bool, 1)
		go func() {
			for {
				if plugin.isStillRunning() {
					time.Sleep(100 * time.Millisecond)
				} else {
					exited <- true
					return
				}
			}
		}()

		select {
		case done := <-exited:
			if done {
				log.Debug("Plugin [%s] with pid [%d] has exited", plugin.descriptor.Name, plugin.pluginCmd.Process.Pid)
			}
		case <-time.After(config.PluginConnectionTimeout()):
			log.Warning("Plugin [%s] with pid [%d] did not exit after %.2f seconds. Forcefully killing it.", plugin.descriptor.Name, plugin.pluginCmd.Process.Pid, config.PluginConnectionTimeout().Seconds())
			return plugin.pluginCmd.Process.Kill()
		}
	}
	return nil
}

func (plugin *plugin) isStillRunning() bool {
	return plugin.pluginCmd.ProcessState == nil || !plugin.pluginCmd.ProcessState.Exited()
}

func isPluginInstalled(pluginName, pluginVersion string) bool {
	pluginsInstallDir, err := common.GetPluginsInstallDir(pluginName)
	if err != nil {
		return false
	}

	thisPluginDir := path.Join(pluginsInstallDir, pluginName)
	if !common.DirExists(thisPluginDir) {
		return false
	}

	if pluginVersion != "" {
		pluginJson := path.Join(thisPluginDir, pluginVersion, common.PluginJsonFile)
		if common.FileExists(pluginJson) {
			return true
		} else {
			return false
		}
	} else {
		return true
	}
}

func getPluginJsonPath(pluginName, version string) (string, error) {
	if !isPluginInstalled(pluginName, version) {
		return "", errors.New(fmt.Sprintf("%s %s is not installed", pluginName, version))
	}

	pluginInstallDir, err := common.GetPluginInstallDir(pluginName, "")
	if err != nil {
		return "", err
	}
	return filepath.Join(pluginInstallDir, common.PluginJsonFile), nil
}

func getPluginDescriptor(pluginId, pluginVersion string) (*pluginDescriptor, error) {
	pluginJson, err := getPluginJsonPath(pluginId, pluginVersion)
	if err != nil {
		return nil, err
	}

	pluginJsonContents, err := common.ReadFileContents(pluginJson)
	if err != nil {
		return nil, err
	}
	var pd pluginDescriptor
	if err = json.Unmarshal([]byte(pluginJsonContents), &pd); err != nil {
		return nil, errors.New(fmt.Sprintf("%s: %s", pluginJson, err.Error()))
	}
	pd.pluginPath = filepath.Dir(pluginJson)

	return &pd, nil
}

func startPlugin(pd *pluginDescriptor, action string, wait bool) (*exec.Cmd, error) {
	command := []string{}
	switch runtime.GOOS {
	case "windows":
		command = pd.Command.Windows
		break
	case "darwin":
		command = pd.Command.Darwin
		break
	default:
		command = pd.Command.Linux
		break
	}
	if len(command) == 0 {
		return nil, errors.New(fmt.Sprintf("Platform specific command not specified: %s.", runtime.GOOS))
	}

	pluginLogger := &pluginLogger{pluginName: pd.Name}
	cmd, err := common.ExecuteCommand(command, pd.pluginPath, pluginLogger, pluginLogger)

	if err != nil {
		return nil, err
	}

	if wait {
		return cmd, cmd.Wait()
	} else {
		go func() {
			cmd.Wait()
		}()
	}

	return cmd, nil
}

func setEnvForPlugin(action string, pd *pluginDescriptor, manifest *manifest, pluginEnvVars map[string]string) error {
	pluginEnvVars[fmt.Sprintf("%s_action", pd.Id)] = action
	pluginEnvVars["test_language"] = manifest.Language
	if err := setEnvironmentProperties(pluginEnvVars); err != nil {
		return err
	}
	return nil
}

func setEnvironmentProperties(properties map[string]string) error {
	for k, v := range properties {
		if err := common.SetEnvVariable(k, v); err != nil {
			return err
		}
	}
	return nil
}

func addPluginToTheProject(pluginName string, pluginArgs map[string]string, manifest *manifest) error {
	pd, err := getPluginDescriptor(pluginName, pluginArgs["version"])
	if err != nil {
		return err
	}

	if isPluginAdded(manifest, pd) {
		return errors.New("Plugin " + pd.Name + " is already added")
	}

	action := setupScope
	if err := setEnvForPlugin(action, pd, manifest, pluginArgs); err != nil {
		return err
	}
	if _, err := startPlugin(pd, action, true); err != nil {
		return err
	}
	manifest.Plugins = append(manifest.Plugins, pd.Id)
	return manifest.save()
}

func isPluginAdded(manifest *manifest, descriptor *pluginDescriptor) bool {
	for _, pluginId := range manifest.Plugins {
		if pluginId == descriptor.Id {
			return true
		}
	}
	return false
}

func startPluginsForExecution(manifest *manifest) (*pluginHandler, []string) {
	warnings := make([]string, 0)
	handler := &pluginHandler{}
	envProperties := make(map[string]string)

	for _, pluginId := range manifest.Plugins {
		pd, err := getPluginDescriptor(pluginId, "")
		if err != nil {
			warnings = append(warnings, fmt.Sprintf("Error starting plugin %s. Failed to get plugin.json. %s", pluginId, err.Error()))
			continue
		}
		compatibilityErr := checkCompatiblity(currentGaugeVersion, &pd.GaugeVersionSupport)
		if compatibilityErr != nil {
			warnings = append(warnings, fmt.Sprintf("Compatible %s plugin version to current Gauge version %s not found", pd.Name, currentGaugeVersion))
			continue
		}
		if isExecutionScopePlugin(pd) {
			gaugeConnectionHandler, err := newGaugeConnectionHandler(0, nil)
			if err != nil {
				warnings = append(warnings, err.Error())
				continue
			}
			envProperties[pluginConnectionPortEnv] = strconv.Itoa(gaugeConnectionHandler.connectionPortNumber())
			setEnvForPlugin(executionScope, pd, manifest, envProperties)

			pluginCmd, err := startPlugin(pd, executionScope, false)
			if err != nil {
				warnings = append(warnings, fmt.Sprintf("Error starting plugin %s %s. %s", pd.Name, pd.Version, err.Error()))
				continue
			}
			pluginConnection, err := gaugeConnectionHandler.acceptConnection(config.PluginConnectionTimeout(), make(chan error))
			if err != nil {
				warnings = append(warnings, fmt.Sprintf("Error starting plugin %s %s. Failed to connect to plugin. %s", pd.Name, pd.Version, err.Error()))
				pluginCmd.Process.Kill()
				continue
			}
			handler.addPlugin(pluginId, &plugin{connection: pluginConnection, pluginCmd: pluginCmd, descriptor: pd})
		}

	}
	return handler, warnings
}

func isExecutionScopePlugin(pd *pluginDescriptor) bool {
	for _, scope := range pd.Scope {
		if strings.ToLower(scope) == executionScope {
			return true
		}
	}
	return false
}

func (handler *pluginHandler) addPlugin(pluginId string, pluginToAdd *plugin) {
	if handler.pluginsMap == nil {
		handler.pluginsMap = make(map[string]*plugin)
	}
	handler.pluginsMap[pluginId] = pluginToAdd
}

func (handler *pluginHandler) removePlugin(pluginId string) {
	delete(handler.pluginsMap, pluginId)
}

func (handler *pluginHandler) notifyPlugins(message *gauge_messages.Message) {
	for id, plugin := range handler.pluginsMap {
		err := plugin.sendMessage(message)
		if err != nil {
			log.Error("Unable to connect to plugin %s %s. %s\n", plugin.descriptor.Name, plugin.descriptor.Version, err.Error())
			handler.killPlugin(id)
		}
	}
}

func (handler *pluginHandler) killPlugin(pluginId string) {
	plugin := handler.pluginsMap[pluginId]
	log.Debug("Killing Plugin %s %s\n", plugin.descriptor.Name, plugin.descriptor.Version)
	err := plugin.pluginCmd.Process.Kill()
	if err != nil {
		log.Error("Failed to kill plugin %s %s. %s\n", plugin.descriptor.Name, plugin.descriptor.Version, err.Error())
	}
	handler.removePlugin(pluginId)
}

func (handler *pluginHandler) gracefullyKillPlugins() {
	var wg sync.WaitGroup
	for _, plugin := range handler.pluginsMap {
		wg.Add(1)
		go plugin.kill(&wg)
	}
	wg.Wait()
}

func (plugin *plugin) sendMessage(message *gauge_messages.Message) error {
	messageId := common.GetUniqueId()
	message.MessageId = &messageId
	messageBytes, err := proto.Marshal(message)
	if err != nil {
		return err
	}
	err = write(plugin.connection, messageBytes)
	if err != nil {
		return errors.New(fmt.Sprintf("[Warning] Failed to send message to plugin: %d  %s", plugin.descriptor.Id, err.Error()))
	}
	return nil
}
<|endoftext|>


<commit_before><commit_msg>Working with debug<commit_after><|endoftext|>


<commit_before>package fetcher

import (
	"encoding/xml"
	"log"
	"time"
)

const RdfType string = "application/rdf+xml"

// To convert to time.Time, see http://play.golang.org/p/JBi5Qu470_
//
type datetime time.Time

func (t *datetime) UnmarshalText(b []byte) error {
	result, err := time.Parse("2015-01-18T15:03:59+09:00", string(b))
	if err == nil {
		*t = datetime(result)
		return nil
	}

	var t2 time.Time
	err = t2.UnmarshalText(b)
	if err == nil {
		*t = datetime(t2)
		return nil
	}

	return err
}

func (t datetime) Time() time.Time {
	return (time.Time)(t)
}

type FeedItem struct {
	Title         string   `xml:"title"`
	Link          string   `xml:"link"`
	Description   string   `xml:"description"`
	Date          datetime `xml:"http://purl.org/dc/elements/1.1/ date"`
	Subject       string   `xml:"http://purl.org/dc/elements/1.1/ subject"`
	Bookmarkcount int32    `xml:"http://www.hatena.ne.jp/info/xmlns# bookmarkcount"`
}

type Rdf struct {
	Items []FeedItem `xml:"item"`
}

func NewRdfFetcher() *Fetcher {
	f := NewFetcher()
	f.AcceptTypes = []string{"application/rdf+xml", "application/xml"}
	return f
}

func (f *Fetcher) FetchRdf(url string) *Rdf {
	if f.Error != nil {
		log.Println("error exists: %v", f.Error)
		return nil
	}

	res := f.Fetch(url)
	if res == nil {
		return nil
	}
	defer res.Body.Close()

	rdf := &Rdf{Items: []FeedItem{}}
	f.Error = xml.NewDecoder(res.Body).Decode(&rdf)
	if f.Error != nil {
		log.Println("Rdf Parse Failed: %v", f.Error)
		return rdf
	}

	return rdf
}
<commit_msg>make fetcher.datetime public<commit_after>package fetcher

import (
	"encoding/xml"
	"log"
	"time"
)

const RdfType string = "application/rdf+xml"

// To convert to time.Time, see http://play.golang.org/p/JBi5Qu470_
//
type Datetime time.Time

func (t *Datetime) UnmarshalText(b []byte) error {
	result, err := time.Parse("2015-01-18T15:03:59+09:00", string(b))
	if err == nil {
		*t = Datetime(result)
		return nil
	}

	var t2 time.Time
	err = t2.UnmarshalText(b)
	if err == nil {
		*t = Datetime(t2)
		return nil
	}

	return err
}

func (t Datetime) Time() time.Time {
	return (time.Time)(t)
}

type FeedItem struct {
	Title         string   `xml:"title"`
	Link          string   `xml:"link"`
	Description   string   `xml:"description"`
	Date          Datetime `xml:"http://purl.org/dc/elements/1.1/ date"`
	Subject       string   `xml:"http://purl.org/dc/elements/1.1/ subject"`
	Bookmarkcount int32    `xml:"http://www.hatena.ne.jp/info/xmlns# bookmarkcount"`
}

type Rdf struct {
	Items []FeedItem `xml:"item"`
}

func NewRdfFetcher() *Fetcher {
	f := NewFetcher()
	f.AcceptTypes = []string{"application/rdf+xml", "application/xml"}
	return f
}

func (f *Fetcher) FetchRdf(url string) *Rdf {
	if f.Error != nil {
		log.Println("error exists: %v", f.Error)
		return nil
	}

	res := f.Fetch(url)
	if res == nil {
		return nil
	}
	defer res.Body.Close()

	rdf := &Rdf{Items: []FeedItem{}}
	f.Error = xml.NewDecoder(res.Body).Decode(&rdf)
	if f.Error != nil {
		log.Println("Rdf Parse Failed: %v", f.Error)
		return rdf
	}

	return rdf
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strconv"
	"time"
)

const (
	// POKET_API_URL - Pocket API URL for get articles
	POKET_API_URL = "https://getpocket.com/v3/get?consumer_key=%s&access_token=%s&state=unread"

	// POKET_RSS_URL - Pocket RSS for get articles
	POKET_RSS_URL = "https://getpocket.com/users/%s/feed/unread"
)

// ----------------------------------------------------------------------------

// Article - one article from pocket
type Article struct {
	Title          string `json:"resolved_title" xml:"title"`
	URL            string `json:"resolved_url" xml:"link"`
	Date           string `json:"time_added" xml:"pubDate"`
	IsFavorite     bool   `json:"favorite"`
	Timestamp      int64  `json:"timestamp"`
	FileName       string `json:"filename"`          // local filename in cache
	IsDownloaded   bool   `json:"is_downloaded"`     // download to cache
	IsUploadedInDB bool   `json:"is_uploaded_in_db"` // uploaded to dropbox
}

// ArticleAPI - one article from pocket (for parse API out)
type ArticleAPI struct {
	Title    string `json:"resolved_title" xml:"title"`
	URL      string `json:"resolved_url" xml:"link"`
	Date     string `json:"time_added" xml:"pubDate"`
	Favorite string `json:"favorite"`
}

// Articles - list
type Articles []Article

// InfoJSON - save local info about all articles
type InfoJSON struct {
	Timestamp int64    `json:"timestamp"` // last exec time
	Items     Articles `json:"articles"`  // list of articles
}

// PocketRSS - RSS XML struct
type PocketRSS struct {
	XMLName xml.Name `xml:"rss"`
	Items   struct {
		XMLName  xml.Name `xml:"channel"`
		ItemList Articles `xml:"item"`
	} `xml:"channel"`
}

// PocketAPI - JSON struct
type PocketAPI struct {
	Since float32               `json:"since"`
	Items map[string]ArticleAPI `json:"list"`
}

// ----------------------------------------------------------------------------
func get_pocket_by_rss() (Articles, error) {
	rssText, err := http_get(
		fmt.Sprintf(POKET_RSS_URL, os.Getenv("POCKET_USER")),
		os.Getenv("POCKET_USER"),
		os.Getenv("POCKET_PASS"),
	)
	if err != nil {
		return nil, err
	}

	rss := PocketRSS{}
	if xml.Unmarshal(rssText, &rss) != nil {
		return nil, err
	}

	result := Articles{}
	for _, item := range rss.Items.ItemList {
		date, err := time.Parse(time.RFC1123Z, item.Date)
		if err == nil {
			item.Date = date.Format("2006-01-02 15:04:05")
			item.Timestamp = date.Unix()
		}
		result = append(result, item)
	}
	sort.Sort(result)

	return result, nil
}

// ----------------------------------------------------------------------------
func get_pocket_by_api(cfg Config) (Articles, error) {
	jsonText, err := http_get(
		fmt.Sprintf(POKET_API_URL, cfg.PocketKey, cfg.PocketToken),
		"",
		"",
	)
	if err != nil {
		return nil, err
	}

	raw_data := PocketAPI{}
	if err := json.Unmarshal(jsonText, &raw_data); err != nil {
		return nil, err
	}

	result := Articles{}
	for _, item := range raw_data.Items {
		article := Article{
			Title:      item.Title,
			URL:        item.URL,
			IsFavorite: item.Favorite == "1",
		}
		timestamp, err := strconv.ParseInt(item.Date, 10, 64)
		if err == nil {
			article.Date = time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
			article.Timestamp = timestamp
		}

		result = append(result, article)
	}
	sort.Sort(result)

	return result, nil
}

// ----------------------------------------------------------------------------
func load_articles_info(cfg Config) (Articles, error) {
	info := InfoJSON{}
	local_info_path := os.Getenv("HOME") + "/" + LOCAL_INFO_PATH
	json_info, err := ioutil.ReadFile(local_info_path)
	if err == nil {
		if err := json.Unmarshal(json_info, &info); err != nil {
			return nil, err
		}
	}

	return info.Items, nil
}

// ----------------------------------------------------------------------------
func save_articles_info(articles Articles, cfg Config) error {
	info := InfoJSON{
		Timestamp: time.Now().Unix(),
		Items:     articles,
	}
	json_info, err := json.MarshalIndent(info, "", "  ")
	if err != nil {
		return err
	}

	local_info_path := os.Getenv("HOME") + "/" + LOCAL_INFO_PATH
	err = ioutil.WriteFile(local_info_path, json_info, 0644)
	if err != nil {
		return err
	}

	err = upload_to_dropbox(local_info_path, LOCAL_INFO_FILENAME, cfg)
	if err != nil {
		return err
	}

	return nil
}

// ----------------------------------------------------------------------------
func merge_local_and_remote_info(local_articles, remote_articles Articles) (Articles, bool) {
	local_as_map := make(map[string]Article, len(local_articles))
	for _, item := range local_articles {
		local_as_map[item.URL] = item
	}

	result := make(Articles, 0, len(remote_articles))
	hasChanges := false

	for _, item := range remote_articles {
		if local_item, ok := local_as_map[item.URL]; ok {
			item.FileName = local_item.FileName
			item.IsDownloaded = local_item.IsDownloaded
			item.IsUploadedInDB = local_item.IsUploadedInDB

			if item.IsFavorite != local_item.IsFavorite ||
				item.FileName != local_item.FileName ||
				item.IsDownloaded != local_item.IsDownloaded ||
				item.IsUploadedInDB != local_item.IsUploadedInDB {
				hasChanges = true
			}

			delete(local_as_map, item.URL) // for add later rest of local list
		}
		result = append(result, item)
	}

	// add rest local
	for _, item := range local_as_map {
		result = append(result, item)
	}

	sort.Sort(result)

	return result, hasChanges
}

// ----------------------------------------------------------------------------
// sorting
func (list Articles) Len() int {
	return len(list)
}

func (list Articles) Less(i, j int) bool {
	return list[i].Timestamp < list[j].Timestamp
}

func (list Articles) Swap(i, j int) {
	list[i], list[j] = list[j], list[i]
}
<commit_msg>Fixed save info again<commit_after>package main

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strconv"
	"time"
)

const (
	// POKET_API_URL - Pocket API URL for get articles
	POKET_API_URL = "https://getpocket.com/v3/get?consumer_key=%s&access_token=%s&state=unread"

	// POKET_RSS_URL - Pocket RSS for get articles
	POKET_RSS_URL = "https://getpocket.com/users/%s/feed/unread"
)

// ----------------------------------------------------------------------------

// Article - one article from pocket
type Article struct {
	Title          string `json:"resolved_title" xml:"title"`
	URL            string `json:"resolved_url" xml:"link"`
	Date           string `json:"time_added" xml:"pubDate"`
	IsFavorite     bool   `json:"favorite"`
	Timestamp      int64  `json:"timestamp"`
	FileName       string `json:"filename"`          // local filename in cache
	IsDownloaded   bool   `json:"is_downloaded"`     // download to cache
	IsUploadedInDB bool   `json:"is_uploaded_in_db"` // uploaded to dropbox
}

// ArticleAPI - one article from pocket (for parse API out)
type ArticleAPI struct {
	Title    string `json:"resolved_title" xml:"title"`
	URL      string `json:"resolved_url" xml:"link"`
	Date     string `json:"time_added" xml:"pubDate"`
	Favorite string `json:"favorite"`
}

// Articles - list
type Articles []Article

// InfoJSON - save local info about all articles
type InfoJSON struct {
	Timestamp int64    `json:"timestamp"` // last exec time
	Items     Articles `json:"articles"`  // list of articles
}

// PocketRSS - RSS XML struct
type PocketRSS struct {
	XMLName xml.Name `xml:"rss"`
	Items   struct {
		XMLName  xml.Name `xml:"channel"`
		ItemList Articles `xml:"item"`
	} `xml:"channel"`
}

// PocketAPI - JSON struct
type PocketAPI struct {
	Since float32               `json:"since"`
	Items map[string]ArticleAPI `json:"list"`
}

// ----------------------------------------------------------------------------
func get_pocket_by_rss() (Articles, error) {
	rssText, err := http_get(
		fmt.Sprintf(POKET_RSS_URL, os.Getenv("POCKET_USER")),
		os.Getenv("POCKET_USER"),
		os.Getenv("POCKET_PASS"),
	)
	if err != nil {
		return nil, err
	}

	rss := PocketRSS{}
	if xml.Unmarshal(rssText, &rss) != nil {
		return nil, err
	}

	result := Articles{}
	for _, item := range rss.Items.ItemList {
		date, err := time.Parse(time.RFC1123Z, item.Date)
		if err == nil {
			item.Date = date.Format("2006-01-02 15:04:05")
			item.Timestamp = date.Unix()
		}
		result = append(result, item)
	}
	sort.Sort(result)

	return result, nil
}

// ----------------------------------------------------------------------------
func get_pocket_by_api(cfg Config) (Articles, error) {
	jsonText, err := http_get(
		fmt.Sprintf(POKET_API_URL, cfg.PocketKey, cfg.PocketToken),
		"",
		"",
	)
	if err != nil {
		return nil, err
	}

	raw_data := PocketAPI{}
	if err := json.Unmarshal(jsonText, &raw_data); err != nil {
		return nil, err
	}

	result := Articles{}
	for _, item := range raw_data.Items {
		article := Article{
			Title:      item.Title,
			URL:        item.URL,
			IsFavorite: item.Favorite == "1",
		}
		timestamp, err := strconv.ParseInt(item.Date, 10, 64)
		if err == nil {
			article.Date = time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
			article.Timestamp = timestamp
		}

		result = append(result, article)
	}
	sort.Sort(result)

	return result, nil
}

// ----------------------------------------------------------------------------
func load_articles_info(cfg Config) (Articles, error) {
	info := InfoJSON{}
	local_info_path := os.Getenv("HOME") + "/" + LOCAL_INFO_PATH
	json_info, err := ioutil.ReadFile(local_info_path)
	if err == nil {
		if err := json.Unmarshal(json_info, &info); err != nil {
			return nil, err
		}
	}

	return info.Items, nil
}

// ----------------------------------------------------------------------------
func save_articles_info(articles Articles, cfg Config) error {
	info := InfoJSON{
		Timestamp: time.Now().Unix(),
		Items:     articles,
	}
	json_info, err := json.MarshalIndent(info, "", "  ")
	if err != nil {
		return err
	}

	local_info_path := os.Getenv("HOME") + "/" + LOCAL_INFO_PATH
	err = ioutil.WriteFile(local_info_path, json_info, 0644)
	if err != nil {
		return err
	}

	err = upload_to_dropbox(local_info_path, LOCAL_INFO_FILENAME, cfg)
	if err != nil {
		return err
	}

	return nil
}

// ----------------------------------------------------------------------------
func merge_local_and_remote_info(local_articles, remote_articles Articles) (Articles, bool) {
	local_as_map := make(map[string]Article, len(local_articles))
	for _, item := range local_articles {
		local_as_map[item.URL] = item
	}

	result := make(Articles, 0, len(remote_articles))
	hasChanges := false

	for _, item := range remote_articles {
		if local_item, ok := local_as_map[item.URL]; ok {
			if item.IsFavorite != local_item.IsFavorite ||
				item.FileName != local_item.FileName ||
				item.IsDownloaded != local_item.IsDownloaded ||
				item.IsUploadedInDB != local_item.IsUploadedInDB {
				hasChanges = true
			}

			item.FileName = local_item.FileName
			item.IsDownloaded = local_item.IsDownloaded
			item.IsUploadedInDB = local_item.IsUploadedInDB

			delete(local_as_map, item.URL) // for add later rest of local list
		}
		result = append(result, item)
	}

	// add rest local
	for _, item := range local_as_map {
		result = append(result, item)
	}

	sort.Sort(result)

	return result, hasChanges
}

// ----------------------------------------------------------------------------
// sorting
func (list Articles) Len() int {
	return len(list)
}

func (list Articles) Less(i, j int) bool {
	return list[i].Timestamp < list[j].Timestamp
}

func (list Articles) Swap(i, j int) {
	list[i], list[j] = list[j], list[i]
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"unicode"
)

// BackupGame if a game has a custom image, backs it up by appending "(original)" to the
// file name.
func BackupGame(gridDir string, game *Game, artStyleExtensions []string) error {
	if game.CleanImageBytes != nil {
		return ioutil.WriteFile(getBackupPath(gridDir, game, artStyleExtensions), game.CleanImageBytes, 0666)
	}
	return nil
}

func getBackupPath(gridDir string, game *Game, artStyleExtensions []string) string {
	hash := sha256.Sum256(game.OverlayImageBytes)
	// [:] is required to convert a fixed length byte array to a byte slice.
	hexHash := hex.EncodeToString(hash[:])
	return filepath.Join(gridDir, "originals", game.ID + artStyleExtensions[0] + " " + hexHash+game.ImageExt)
}

func RemoveExisting(gridDir string, gameId string, artStyleExtensions []string) error {
	images, err := filepath.Glob(filepath.Join(gridDir, gameId + artStyleExtensions[0] + ".*"))
	if err != nil {
		return err
	}

	backups, err := filepath.Glob(filepath.Join(gridDir, "originals", gameId + artStyleExtensions[0] + " *.*"))
	if err != nil {
		return err
	}

	all := append(images, backups...)
	for _, path := range all {
		err = os.Remove(path)
		if err != nil {
			return err
		}
	}

	return nil
}

func loadImage(game *Game, sourceName string, imagePath string) error {
	imageBytes, err := ioutil.ReadFile(imagePath)
	if err == nil {
		game.ImageExt = filepath.Ext(imagePath)
		game.CleanImageBytes = imageBytes
		game.ImageSource = sourceName
	}
	return err
}

// https://wenzr.wordpress.com/2018/04/09/go-glob-case-insensitive/
func InsensitiveFilepath(path string) string {
	if runtime.GOOS == "windows" {
		return path
	}

	p := ""
	for _, r := range path {
		if unicode.IsLetter(r) {
			p += fmt.Sprintf("[%c%c]", unicode.ToLower(r), unicode.ToUpper(r))
		} else {
			p += string(r)
		}
	}
	return p
}

func LoadExisting(overridePath string, gridDir string, game *Game, artStyleExtensions []string) {
	overridenIDs, _ := filepath.Glob(filepath.Join(overridePath, game.ID + artStyleExtensions[0] + ".*"))
	if overridenIDs != nil && len(overridenIDs) > 0 {
		loadImage(game, "local file in directory 'games'", overridenIDs[0])
		return
	}

	if game.Name != "" {
		re := regexp.MustCompile(`\W+`)
		globName := re.ReplaceAllString(game.Name, "*")
		overridenNames, _ := filepath.Glob(filepath.Join(overridePath, InsensitiveFilepath(globName) + artStyleExtensions[1] + ".*"))
		if overridenNames != nil && len(overridenNames) > 0 {
			loadImage(game, "local file in directory games/", overridenNames[0])
			return
		}
	}

	// If there are any old-style backups (without hash), load them over the existing (with overlay) images.
	oldBackups, err := filepath.Glob(filepath.Join(gridDir, game.ID + artStyleExtensions[0] + " (original)*"))
	if err == nil && len(oldBackups) > 0 {
		err = loadImage(game, "legacy backup (now converted)", oldBackups[0])
		if err == nil {
			os.Remove(oldBackups[0])
			return
		}
	}

	files, err := filepath.Glob(filepath.Join(gridDir, game.ID + artStyleExtensions[0] + ".*"))
	if err == nil && len(files) > 0 {
		err = loadImage(game, "manual customization", files[0])
		if err == nil {
			// set as overlay to check for hash in getBackupPath()
			game.OverlayImageBytes = game.CleanImageBytes

			// See if there exists a backup image with no overlays or modifications.
			loadImage(game, "backup", getBackupPath(gridDir, game, artStyleExtensions))

			// remove overlay
			game.OverlayImageBytes = nil
		}
	}

}
<commit_msg>Filter search results…<commit_after>package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"unicode"
)

// BackupGame if a game has a custom image, backs it up by appending "(original)" to the
// file name.
func BackupGame(gridDir string, game *Game, artStyleExtensions []string) error {
	if game.CleanImageBytes != nil {
		return ioutil.WriteFile(getBackupPath(gridDir, game, artStyleExtensions), game.CleanImageBytes, 0666)
	}
	return nil
}

func getBackupPath(gridDir string, game *Game, artStyleExtensions []string) string {
	hash := sha256.Sum256(game.OverlayImageBytes)
	// [:] is required to convert a fixed length byte array to a byte slice.
	hexHash := hex.EncodeToString(hash[:])
	return filepath.Join(gridDir, "originals", game.ID + artStyleExtensions[0] + " " + hexHash+game.ImageExt)
}

func RemoveExisting(gridDir string, gameId string, artStyleExtensions []string) error {
	images, err := filepath.Glob(filepath.Join(gridDir, gameId + artStyleExtensions[0] + ".*"))
	if err != nil {
		return err
	}
	images = filterForImages(images)

	backups, err := filepath.Glob(filepath.Join(gridDir, "originals", gameId + artStyleExtensions[0] + " *.*"))
	if err != nil {
		return err
	}
	backups = filterForImages(backups)

	all := append(images, backups...)
	for _, path := range all {
		err = os.Remove(path)
		if err != nil {
			return err
		}
	}

	return nil
}

func loadImage(game *Game, sourceName string, imagePath string) error {
	imageBytes, err := ioutil.ReadFile(imagePath)
	if err == nil {
		game.ImageExt = filepath.Ext(imagePath)
		game.CleanImageBytes = imageBytes
		game.ImageSource = sourceName
	}
	return err
}

// https://wenzr.wordpress.com/2018/04/09/go-glob-case-insensitive/
func InsensitiveFilepath(path string) string {
	if runtime.GOOS == "windows" {
		return path
	}

	p := ""
	for _, r := range path {
		if unicode.IsLetter(r) {
			p += fmt.Sprintf("[%c%c]", unicode.ToLower(r), unicode.ToUpper(r))
		} else {
			p += string(r)
		}
	}
	return p
}

func filterForImages(paths []string) []string {
	var matchedPaths []string
	for _, path := range paths {
		ext := filepath.Ext(path)
		switch ext {
		case ".png":
			matchedPaths = append(matchedPaths, path)
		case ".jpg":
			matchedPaths = append(matchedPaths, path)
		case ".jpeg":
			matchedPaths = append(matchedPaths, path)
		}
	}
	return matchedPaths
}

func LoadExisting(overridePath string, gridDir string, game *Game, artStyleExtensions []string) {
	overridenIDs, _ := filepath.Glob(filepath.Join(overridePath, game.ID + artStyleExtensions[0] + ".*"))
	if overridenIDs != nil && len(overridenIDs) > 0 {
		loadImage(game, "local file in directory 'games'", overridenIDs[0])
		return
	}
	overridenIDs = filterForImages(overridenIDs)

	if game.Name != "" {
		re := regexp.MustCompile(`\W+`)
		globName := re.ReplaceAllString(game.Name, "*")
		overridenNames, _ := filepath.Glob(filepath.Join(overridePath, InsensitiveFilepath(globName) + artStyleExtensions[1] + ".*"))
		if overridenNames != nil && len(overridenNames) > 0 {
			loadImage(game, "local file in directory games/", overridenNames[0])
			return
		}
	}

	// If there are any old-style backups (without hash), load them over the existing (with overlay) images.
	oldBackups, err := filepath.Glob(filepath.Join(gridDir, game.ID + artStyleExtensions[0] + " (original)*"))
	if err == nil && len(oldBackups) > 0 {
		err = loadImage(game, "legacy backup (now converted)", oldBackups[0])
		if err == nil {
			os.Remove(oldBackups[0])
			return
		}
	}

	files, err := filepath.Glob(filepath.Join(gridDir, game.ID + artStyleExtensions[0] + ".*"))
	files = filterForImages(files)
	if err == nil && len(files) > 0 {
		err = loadImage(game, "manual customization", files[0])
		if err == nil {
			// set as overlay to check for hash in getBackupPath()
			game.OverlayImageBytes = game.CleanImageBytes

			// See if there exists a backup image with no overlays or modifications.
			loadImage(game, "backup", getBackupPath(gridDir, game, artStyleExtensions))

			// remove overlay
			game.OverlayImageBytes = nil
		}
	}

}
<|endoftext|>


<commit_before>package main

import (
	"compress/gzip"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/dustin/go-hashset"
	"github.com/dustin/gomemcached"

	"encoding/hex"
	"github.com/couchbaselabs/cbfs/config"
	"sync"
)

const backupKey = "/@backup"

type backupItem struct {
	Fn   string                `json:"filename"`
	Oid  string                `json:"oid"`
	When time.Time             `json:"when"`
	Conf cbfsconfig.CBFSConfig `json:"conf"`
}

type backups struct {
	Latest  backupItem   `json:"latest"`
	Backups []backupItem `json:"backups"`
}

func logDuration(m string, startTime time.Time) {
	log.Printf("Completed %v in %v", m, time.Since(startTime))
}

func streamFileMeta(w io.Writer,
	fch chan *namedFile,
	ech chan error) error {

	enc := json.NewEncoder(w)
	for {
		select {
		case f, ok := <-fch:
			if !ok {
				return nil
			}
			err := enc.Encode(map[string]interface{}{
				"path": f.name,
				"meta": f.meta,
			})
			if err != nil {
				return err
			}
		case e, ok := <-ech:
			if ok {
				return e
			}
			ech = nil
		}
	}
}

func backupTo(w io.Writer) (err error) {
	fch := make(chan *namedFile)
	ech := make(chan error)
	qch := make(chan bool)

	defer close(qch)

	defer logDuration("backup", time.Now())

	go pathGenerator("", fch, ech, qch)

	gz := gzip.NewWriter(w)
	defer func() {
		e := gz.Close()
		if err != nil {
			err = e
		}
	}()

	return streamFileMeta(gz, fch, ech)
}

func recordBackupObject() error {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil {
		return err
	}

	removeDeadBackups(&b)

	f, err := os.Create(filepath.Join(*root, ".backup.json"))
	if err != nil {
		return err
	}
	defer f.Close()
	e := json.NewEncoder(f)
	return e.Encode(&b)
}

func recordRemoteBackupObjects() {
	rn, err := findRemoteNodes()
	if err != nil {
		log.Printf("Error getting remote nodes for recording backup: %v",
			err)
		return
	}
	for _, n := range rn {
		u := fmt.Sprintf("http://%s%s",
			n.Address(), markBackupPrefix)
		c := n.Client()
		res, err := c.Post(u, "application/octet-stream", nil)
		if err != nil {
			log.Printf("Error posting to %v: %v", u, err)
			continue
		}
		res.Body.Close()
		if res.StatusCode != 204 {
			log.Printf("HTTP Error posting to %v: %v", u, res.Status)
		}
	}
}

func removeDeadBackups(b *backups) {
	// Keep only backups we're pretty sure still exist.
	obn := b.Backups
	b.Backups = nil
	for _, bi := range obn {
		fm := fileMeta{}
		err := couchbase.Get(shortName(bi.Fn), &fm)
		if gomemcached.IsNotFound(err) {
			log.Printf("Dropping previous (deleted) backup: %v",
				bi.Fn)
		} else {
			b.Backups = append(b.Backups, bi)
		}
	}

}

func storeBackupObject(fn, h string) error {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil && !gomemcached.IsNotFound(err) {
		log.Printf("Weird: %v", err)
		// return err
	}

	removeDeadBackups(&b)

	ob := backupItem{fn, h, time.Now().UTC(), *globalConfig}

	b.Latest = ob
	b.Backups = append(b.Backups, ob)

	return couchbase.Set(backupKey, 0, &b)
}

func backupToCBFS(fn string) error {
	f, err := NewHashRecord(*root, "")
	if err != nil {
		return err
	}
	defer f.Close()

	pr, pw := io.Pipe()

	go func() { pw.CloseWithError(backupTo(pw)) }()

	h, length, err := f.Process(pr)
	if err != nil {
		return err
	}

	err = recordBlobOwnership(h, length, true)
	if err != nil {
		return err
	}

	fm := fileMeta{
		OID:      h,
		Length:   length,
		Modified: time.Now().UTC(),
	}

	err = storeMeta(fn, 0, fm, 1, nil)
	if err != nil {
		return err
	}

	err = storeBackupObject(fn, h)
	if err != nil {
		return err
	}

	err = recordBackupObject()
	if err != nil {
		log.Printf("Failed to record backup OID: %v", err)
	}

	go recordRemoteBackupObjects()

	log.Printf("Replicating backup %v.", h)
	go increaseReplicaCount(h, length, globalConfig.MinReplicas-1)

	return nil
}

func doMarkBackup(w http.ResponseWriter, req *http.Request) {
	if req.FormValue("all") == "true" {
		go recordRemoteBackupObjects()
	}
	err := recordBackupObject()
	if err != nil {
		http.Error(w, fmt.Sprintf("Error marking backup; %v", err), 500)
		return
	}
	w.WriteHeader(204)
}

func doBackupDocs(w http.ResponseWriter, req *http.Request) {
	fn := req.FormValue("fn")
	if fn == "" {
		http.Error(w, "Missing fn parameter", 400)
		return
	}

	if bg, _ := strconv.ParseBool(req.FormValue("bg")); bg {
		go func() {
			err := backupToCBFS(fn)
			if err != nil {
				log.Printf("Error performing bg backup: %v", err)
			}
		}()
		w.WriteHeader(202)
		return
	}

	err := backupToCBFS(fn)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error performing backup: %v", err), 500)
		return
	}

	w.WriteHeader(201)
}

func doGetBackupInfo(w http.ResponseWriter, req *http.Request) {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil {
		code := 500
		if gomemcached.IsNotFound(err) {
			code = 404
		}
		http.Error(w, err.Error(), code)
		return
	}

	removeDeadBackups(&b)

	sendJson(w, req, &b)
}

var errExists = errors.New("item exists")

func maybeStoreMeta(k string, fm fileMeta, exp int, force bool) error {
	if force {
		return couchbase.Set(k, exp, fm)
	}
	added, err := couchbase.Add(k, exp, fm)
	if err == nil && !added {
		err = errExists
	}
	return err
}

func doRestoreDocument(w http.ResponseWriter, req *http.Request, fn string) {
	d := json.NewDecoder(req.Body)
	fm := fileMeta{}
	err := d.Decode(&fm)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	for len(fn) > 0 && fn[0] == '/' {
		fn = fn[1:]
	}

	if fn == "" {
		http.Error(w, "No filename", 400)
		return
	}

	if strings.Contains(fn, "//") {
		http.Error(w,
			fmt.Sprintf("Too many slashes in the path name: %v", fn), 400)
		return
	}

	_, err = referenceBlob(fm.OID)
	if err != nil {
		log.Printf("Missing blob %v while restoring %v - restoring anyway",
			fm.OID, fn)
	}

	exp := getExpirationFrom(fm.Headers.Get("X-CBFS-ExpirationOverride"))
	if exp == 0 {
		exp = getExpiration(fm.Headers)
		if exp > 0 && exp < 60*60*24*30 {
			exp = int(fm.Modified.Add(time.Second * time.Duration(exp)).Unix())
		}
	}

	if exp < 0 {
		log.Printf("Attempt to restore expired file: %v", fn)
		w.WriteHeader(201)
		return
	}

	force := false
	err = maybeStoreMeta(fn, fm, exp, force)
	switch err {
	case errExists:
		http.Error(w, err.Error(), 409)
		return
	case nil:
	default:
		log.Printf("Error storing file meta of %v -> %v: %v",
			fn, fm.OID, err)
		http.Error(w,
			fmt.Sprintf("Error recording file meta: %v", err), 500)
		return
	}

	log.Printf("Restored %v -> %v (exp=%v)", fn, fm.OID, exp)

	w.WriteHeader(201)
}

func loadBackupHashes(oid string) (*hashset.Hashset, int, error) {
	rv := &hashset.Hashset{}

	r := blobReader(oid)
	defer r.Close()
	gz, err := gzip.NewReader(r)
	if err != nil {
		return nil, 0, err
	}
	defer gz.Close()

	d := json.NewDecoder(gz)

	hashlen := getHash().Size()

	visited := 0
	for {
		ob := struct {
			Meta struct {
				OID   string
				Older []struct {
					OID string
				}
			}
		}{}

		err := d.Decode(&ob)
		switch err {
		case nil:
			oid, err := hex.DecodeString(ob.Meta.OID)
			if err != nil {
				return nil, visited, err
			}
			if len(oid) != hashlen {
				log.Printf("Invalid hash from %#v", ob)
				continue
			}
			rv.Add(oid)
			visited++
			for _, obs := range ob.Meta.Older {
				oid, err = hex.DecodeString(obs.OID)
				if err != nil {
					return nil, visited, err
				}
				rv.Add(oid)
				visited++
			}
		case io.EOF:
			return rv, visited, nil
		default:
			return nil, visited, err
		}
	}
}

func loadExistingHashes() (*hashset.Hashset, error) {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil && !gomemcached.IsNotFound(err) {
		return nil, err
	}

	oids := make(chan string)
	hsch := make(chan *hashset.Hashset)
	visitch := make(chan int)
	errch := make(chan error)

	wg := sync.WaitGroup{}

	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for o := range oids {
				h, v, e := loadBackupHashes(o)
				if e == nil {
					hsch <- h
				} else {
					errch <- e
				}
				visitch <- v
			}
		}()
	}
	go func() {
		wg.Wait()
		close(hsch)
		close(visitch)
		close(errch)
	}()

	go func() {
		for _, i := range b.Backups {
			log.Printf("Loading backups from %v / %v", i.Oid, i.Fn)
			oids <- i.Oid
		}
		close(oids)
	}()

	visited := 0
	hs := &hashset.Hashset{}
	for {
		// Done getting all the things
		if hsch == nil && visitch == nil && errch == nil {
			break
		}
		select {
		case v, ok := <-visitch:
			visited += v
			if !ok {
				visitch = nil
			}
		case e, ok := <-errch:
			err = e
			if !ok {
				errch = nil
			}
		case h, ok := <-hsch:
			if ok {
				log.Printf("Got %v hashes from a backup",
					h.Len())
				hs.AddAll(h)
			} else {
				hsch = nil
			}
		}
	}

	log.Printf("Visited %v obs, kept %v", visited, hs.Len())

	return hs, err
}
<commit_msg>Get expiration override from regular headers<commit_after>package main

import (
	"compress/gzip"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/dustin/go-hashset"
	"github.com/dustin/gomemcached"

	"encoding/hex"
	"github.com/couchbaselabs/cbfs/config"
	"sync"
)

const backupKey = "/@backup"

type backupItem struct {
	Fn   string                `json:"filename"`
	Oid  string                `json:"oid"`
	When time.Time             `json:"when"`
	Conf cbfsconfig.CBFSConfig `json:"conf"`
}

type backups struct {
	Latest  backupItem   `json:"latest"`
	Backups []backupItem `json:"backups"`
}

func logDuration(m string, startTime time.Time) {
	log.Printf("Completed %v in %v", m, time.Since(startTime))
}

func streamFileMeta(w io.Writer,
	fch chan *namedFile,
	ech chan error) error {

	enc := json.NewEncoder(w)
	for {
		select {
		case f, ok := <-fch:
			if !ok {
				return nil
			}
			err := enc.Encode(map[string]interface{}{
				"path": f.name,
				"meta": f.meta,
			})
			if err != nil {
				return err
			}
		case e, ok := <-ech:
			if ok {
				return e
			}
			ech = nil
		}
	}
}

func backupTo(w io.Writer) (err error) {
	fch := make(chan *namedFile)
	ech := make(chan error)
	qch := make(chan bool)

	defer close(qch)

	defer logDuration("backup", time.Now())

	go pathGenerator("", fch, ech, qch)

	gz := gzip.NewWriter(w)
	defer func() {
		e := gz.Close()
		if err != nil {
			err = e
		}
	}()

	return streamFileMeta(gz, fch, ech)
}

func recordBackupObject() error {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil {
		return err
	}

	removeDeadBackups(&b)

	f, err := os.Create(filepath.Join(*root, ".backup.json"))
	if err != nil {
		return err
	}
	defer f.Close()
	e := json.NewEncoder(f)
	return e.Encode(&b)
}

func recordRemoteBackupObjects() {
	rn, err := findRemoteNodes()
	if err != nil {
		log.Printf("Error getting remote nodes for recording backup: %v",
			err)
		return
	}
	for _, n := range rn {
		u := fmt.Sprintf("http://%s%s",
			n.Address(), markBackupPrefix)
		c := n.Client()
		res, err := c.Post(u, "application/octet-stream", nil)
		if err != nil {
			log.Printf("Error posting to %v: %v", u, err)
			continue
		}
		res.Body.Close()
		if res.StatusCode != 204 {
			log.Printf("HTTP Error posting to %v: %v", u, res.Status)
		}
	}
}

func removeDeadBackups(b *backups) {
	// Keep only backups we're pretty sure still exist.
	obn := b.Backups
	b.Backups = nil
	for _, bi := range obn {
		fm := fileMeta{}
		err := couchbase.Get(shortName(bi.Fn), &fm)
		if gomemcached.IsNotFound(err) {
			log.Printf("Dropping previous (deleted) backup: %v",
				bi.Fn)
		} else {
			b.Backups = append(b.Backups, bi)
		}
	}

}

func storeBackupObject(fn, h string) error {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil && !gomemcached.IsNotFound(err) {
		log.Printf("Weird: %v", err)
		// return err
	}

	removeDeadBackups(&b)

	ob := backupItem{fn, h, time.Now().UTC(), *globalConfig}

	b.Latest = ob
	b.Backups = append(b.Backups, ob)

	return couchbase.Set(backupKey, 0, &b)
}

func backupToCBFS(fn string) error {
	f, err := NewHashRecord(*root, "")
	if err != nil {
		return err
	}
	defer f.Close()

	pr, pw := io.Pipe()

	go func() { pw.CloseWithError(backupTo(pw)) }()

	h, length, err := f.Process(pr)
	if err != nil {
		return err
	}

	err = recordBlobOwnership(h, length, true)
	if err != nil {
		return err
	}

	fm := fileMeta{
		OID:      h,
		Length:   length,
		Modified: time.Now().UTC(),
	}

	err = storeMeta(fn, 0, fm, 1, nil)
	if err != nil {
		return err
	}

	err = storeBackupObject(fn, h)
	if err != nil {
		return err
	}

	err = recordBackupObject()
	if err != nil {
		log.Printf("Failed to record backup OID: %v", err)
	}

	go recordRemoteBackupObjects()

	log.Printf("Replicating backup %v.", h)
	go increaseReplicaCount(h, length, globalConfig.MinReplicas-1)

	return nil
}

func doMarkBackup(w http.ResponseWriter, req *http.Request) {
	if req.FormValue("all") == "true" {
		go recordRemoteBackupObjects()
	}
	err := recordBackupObject()
	if err != nil {
		http.Error(w, fmt.Sprintf("Error marking backup; %v", err), 500)
		return
	}
	w.WriteHeader(204)
}

func doBackupDocs(w http.ResponseWriter, req *http.Request) {
	fn := req.FormValue("fn")
	if fn == "" {
		http.Error(w, "Missing fn parameter", 400)
		return
	}

	if bg, _ := strconv.ParseBool(req.FormValue("bg")); bg {
		go func() {
			err := backupToCBFS(fn)
			if err != nil {
				log.Printf("Error performing bg backup: %v", err)
			}
		}()
		w.WriteHeader(202)
		return
	}

	err := backupToCBFS(fn)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error performing backup: %v", err), 500)
		return
	}

	w.WriteHeader(201)
}

func doGetBackupInfo(w http.ResponseWriter, req *http.Request) {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil {
		code := 500
		if gomemcached.IsNotFound(err) {
			code = 404
		}
		http.Error(w, err.Error(), code)
		return
	}

	removeDeadBackups(&b)

	sendJson(w, req, &b)
}

var errExists = errors.New("item exists")

func maybeStoreMeta(k string, fm fileMeta, exp int, force bool) error {
	if force {
		return couchbase.Set(k, exp, fm)
	}
	added, err := couchbase.Add(k, exp, fm)
	if err == nil && !added {
		err = errExists
	}
	return err
}

func doRestoreDocument(w http.ResponseWriter, req *http.Request, fn string) {
	d := json.NewDecoder(req.Body)
	fm := fileMeta{}
	err := d.Decode(&fm)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	for len(fn) > 0 && fn[0] == '/' {
		fn = fn[1:]
	}

	if fn == "" {
		http.Error(w, "No filename", 400)
		return
	}

	if strings.Contains(fn, "//") {
		http.Error(w,
			fmt.Sprintf("Too many slashes in the path name: %v", fn), 400)
		return
	}

	_, err = referenceBlob(fm.OID)
	if err != nil {
		log.Printf("Missing blob %v while restoring %v - restoring anyway",
			fm.OID, fn)
	}

	exp := getExpiration(req.Header)
	if exp == 0 {
		exp = getExpiration(fm.Headers)
		if exp > 0 && exp < 60*60*24*30 {
			exp = int(fm.Modified.Add(time.Second * time.Duration(exp)).Unix())
		}
	}

	if exp < 0 {
		log.Printf("Attempt to restore expired file: %v", fn)
		w.WriteHeader(201)
		return
	}

	force := false
	err = maybeStoreMeta(fn, fm, exp, force)
	switch err {
	case errExists:
		http.Error(w, err.Error(), 409)
		return
	case nil:
	default:
		log.Printf("Error storing file meta of %v -> %v: %v",
			fn, fm.OID, err)
		http.Error(w,
			fmt.Sprintf("Error recording file meta: %v", err), 500)
		return
	}

	log.Printf("Restored %v -> %v (exp=%v)", fn, fm.OID, exp)

	w.WriteHeader(201)
}

func loadBackupHashes(oid string) (*hashset.Hashset, int, error) {
	rv := &hashset.Hashset{}

	r := blobReader(oid)
	defer r.Close()
	gz, err := gzip.NewReader(r)
	if err != nil {
		return nil, 0, err
	}
	defer gz.Close()

	d := json.NewDecoder(gz)

	hashlen := getHash().Size()

	visited := 0
	for {
		ob := struct {
			Meta struct {
				OID   string
				Older []struct {
					OID string
				}
			}
		}{}

		err := d.Decode(&ob)
		switch err {
		case nil:
			oid, err := hex.DecodeString(ob.Meta.OID)
			if err != nil {
				return nil, visited, err
			}
			if len(oid) != hashlen {
				log.Printf("Invalid hash from %#v", ob)
				continue
			}
			rv.Add(oid)
			visited++
			for _, obs := range ob.Meta.Older {
				oid, err = hex.DecodeString(obs.OID)
				if err != nil {
					return nil, visited, err
				}
				rv.Add(oid)
				visited++
			}
		case io.EOF:
			return rv, visited, nil
		default:
			return nil, visited, err
		}
	}
}

func loadExistingHashes() (*hashset.Hashset, error) {
	b := backups{}
	err := couchbase.Get(backupKey, &b)
	if err != nil && !gomemcached.IsNotFound(err) {
		return nil, err
	}

	oids := make(chan string)
	hsch := make(chan *hashset.Hashset)
	visitch := make(chan int)
	errch := make(chan error)

	wg := sync.WaitGroup{}

	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for o := range oids {
				h, v, e := loadBackupHashes(o)
				if e == nil {
					hsch <- h
				} else {
					errch <- e
				}
				visitch <- v
			}
		}()
	}
	go func() {
		wg.Wait()
		close(hsch)
		close(visitch)
		close(errch)
	}()

	go func() {
		for _, i := range b.Backups {
			log.Printf("Loading backups from %v / %v", i.Oid, i.Fn)
			oids <- i.Oid
		}
		close(oids)
	}()

	visited := 0
	hs := &hashset.Hashset{}
	for {
		// Done getting all the things
		if hsch == nil && visitch == nil && errch == nil {
			break
		}
		select {
		case v, ok := <-visitch:
			visited += v
			if !ok {
				visitch = nil
			}
		case e, ok := <-errch:
			err = e
			if !ok {
				errch = nil
			}
		case h, ok := <-hsch:
			if ok {
				log.Printf("Got %v hashes from a backup",
					h.Len())
				hs.AddAll(h)
			} else {
				hsch = nil
			}
		}
	}

	log.Printf("Visited %v obs, kept %v", visited, hs.Len())

	return hs, err
}
<|endoftext|>


<commit_before>package bslack

import (
	"errors"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/matterhook"
	"github.com/nlopes/slack"
)

type BLegacy struct {
	*Bslack
}

func NewLegacy(cfg *bridge.Config) bridge.Bridger {
	return &BLegacy{Bslack: newBridge(cfg)}
}

func (b *BLegacy) Connect() error {
	b.RLock()
	defer b.RUnlock()
	if b.GetString(incomingWebhookConfig) != "" {
		switch {
		case b.GetString(outgoingWebhookConfig) != "":
			b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.GetString(outgoingWebhookConfig), matterhook.Config{
				InsecureSkipVerify: b.GetBool(skipTLSConfig),
				BindAddress:        b.GetString(incomingWebhookConfig),
			})
		case b.GetString(tokenConfig) != "":
			b.Log.Info("Connecting using token (sending)")
			b.sc = slack.New(b.GetString(tokenConfig))
			b.rtm = b.sc.NewRTM()
			go b.rtm.ManageConnection()
			b.Log.Info("Connecting using webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.GetString(outgoingWebhookConfig), matterhook.Config{
				InsecureSkipVerify: b.GetBool(skipTLSConfig),
				BindAddress:        b.GetString(incomingWebhookConfig),
			})
		default:
			b.Log.Info("Connecting using webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.GetString(outgoingWebhookConfig), matterhook.Config{
				InsecureSkipVerify: b.GetBool(skipTLSConfig),
				BindAddress:        b.GetString(incomingWebhookConfig),
			})
		}
		go b.handleSlack()
		return nil
	}
	if b.GetString(outgoingWebhookConfig) != "" {
		b.Log.Info("Connecting using webhookurl (sending)")
		b.mh = matterhook.New(b.GetString(outgoingWebhookConfig), matterhook.Config{
			InsecureSkipVerify: b.GetBool(skipTLSConfig),
			DisableServer:      true,
		})
		if b.GetString(tokenConfig) != "" {
			b.Log.Info("Connecting using token (receiving)")
			b.sc = slack.New(b.GetString(tokenConfig))
			b.rtm = b.sc.NewRTM()
			go b.rtm.ManageConnection()
			go b.handleSlack()
		}
	} else if b.GetString(tokenConfig) != "" {
		b.Log.Info("Connecting using token (sending and receiving)")
		b.sc = slack.New(b.GetString(tokenConfig))
		b.rtm = b.sc.NewRTM()
		go b.rtm.ManageConnection()
		go b.handleSlack()
	}
	if b.GetString(incomingWebhookConfig) == "" && b.GetString(outgoingWebhookConfig) == "" && b.GetString(tokenConfig) == "" {
		return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured")
	}
	return nil
}
<commit_msg>Fix regression for legacy slack by #766 (#768)<commit_after>package bslack

import (
	"errors"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/matterhook"
	"github.com/nlopes/slack"
)

type BLegacy struct {
	*Bslack
}

func NewLegacy(cfg *bridge.Config) bridge.Bridger {
	return &BLegacy{Bslack: newBridge(cfg)}
}

func (b *BLegacy) Connect() error {
	b.RLock()
	defer b.RUnlock()
	if b.GetString(incomingWebhookConfig) != "" {
		switch {
		case b.GetString(outgoingWebhookConfig) != "":
			b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.GetString(outgoingWebhookConfig), matterhook.Config{
				InsecureSkipVerify: b.GetBool(skipTLSConfig),
				BindAddress:        b.GetString(incomingWebhookConfig),
			})
		case b.GetString(tokenConfig) != "":
			b.Log.Info("Connecting using token (sending)")
			b.sc = slack.New(b.GetString(tokenConfig))
			b.rtm = b.sc.NewRTM()
			go b.rtm.ManageConnection()
			b.Log.Info("Connecting using webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.GetString(outgoingWebhookConfig), matterhook.Config{
				InsecureSkipVerify: b.GetBool(skipTLSConfig),
				BindAddress:        b.GetString(incomingWebhookConfig),
			})
		default:
			b.Log.Info("Connecting using webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.GetString(outgoingWebhookConfig), matterhook.Config{
				InsecureSkipVerify: b.GetBool(skipTLSConfig),
				BindAddress:        b.GetString(incomingWebhookConfig),
			})
		}
		go b.handleSlack()
		return nil
	}
	if b.GetString(outgoingWebhookConfig) != "" {
		b.Log.Info("Connecting using webhookurl (sending)")
		b.mh = matterhook.New(b.GetString(outgoingWebhookConfig), matterhook.Config{
			InsecureSkipVerify: b.GetBool(skipTLSConfig),
			DisableServer:      true,
		})
		if b.GetString(tokenConfig) != "" {
			b.Log.Info("Connecting using token (receiving)")
			b.sc = slack.New(b.GetString(tokenConfig), slack.OptionDebug(b.GetBool("debug")))
			b.channels = newChannelManager(b.Log, b.sc)
			b.users = newUserManager(b.Log, b.sc)
			b.rtm = b.sc.NewRTM()
			go b.rtm.ManageConnection()
			go b.handleSlack()
		}
	} else if b.GetString(tokenConfig) != "" {
		b.Log.Info("Connecting using token (sending and receiving)")
		b.sc = slack.New(b.GetString(tokenConfig), slack.OptionDebug(b.GetBool("debug")))
		b.channels = newChannelManager(b.Log, b.sc)
		b.users = newUserManager(b.Log, b.sc)
		b.rtm = b.sc.NewRTM()
		go b.rtm.ManageConnection()
		go b.handleSlack()
	}
	if b.GetString(incomingWebhookConfig) == "" && b.GetString(outgoingWebhookConfig) == "" && b.GetString(tokenConfig) == "" {
		return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured")
	}
	return nil
}
<|endoftext|>