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

package time

import (
	"os";
	"time"
)

// Seconds since January 1, 1970 00:00:00 GMT
export func Seconds() int64 {
	sec, nsec, err := os.Time();
	if err != nil {
		panic("time: os.Time: ", err.String());
	}
	return sec
}

// Nanoseconds since January 1, 1970 00:00:00 GMT
export func Nanoseconds() int64 {
	sec, nsec, err := os.Time();
	if err != nil {
		panic("time: os.Time: ", err.String());
	}
	return sec*1e9 + nsec
}

export const (
	Sunday = iota;
	Monday;
	Tuesday;
	Wednesday;
	Thursday;
	Friday;
	Saturday;
)

export type Time struct {
	year int64;	// 2008 is 2008
	month, day int;	// Sep-17 is 9, 17
	hour, minute, second int;	// 10:43:12 is 10, 43, 12
	weekday int;		// Sunday = 0, Monday = 1, ...
	zoneoffset int;	// seconds west of UTC
	zone string;
}

var RegularMonths = []int{
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
}
var LeapMonths = []int{
	31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
}

func Months(year int64) *[]int {
	if year%4 == 0 && (year%100 != 0 || year%400 == 0) {
		return &LeapMonths
	} else {
		return &RegularMonths
	}
	return nil	// not reached
}

const (
	SecondsPerDay = 24*60*60;

	DaysPer400Years = 365*400+97;
	DaysPer100Years = 365*100+24;
	DaysPer4Years = 365*4+1;

	Days1970To2001 = 31*365+8;
)

export func SecondsToUTC(sec int64) *Time {
	t := new(Time);

	// Split into time and day.
	day := sec/SecondsPerDay;
	sec -= day*SecondsPerDay;
	if sec < 0 {
		day--;
		sec += SecondsPerDay
	}

	// Time
	t.hour = int(sec/3600);
	t.minute = int((sec/60)%60);
	t.second = int(sec%60);

	// Day 0 = January 1, 1970 was a Thursday
	t.weekday = int((day + Thursday) % 7);
	if t.weekday < 0 {
		t.weekday += 7
	}

	// Change day from 0 = 1970 to 0 = 2001,
	// to make leap year calculations easier
	// (2001 begins 4-, 100-, and 400-year cycles ending in a leap year.)
	day -= Days1970To2001;

	year := int64(2001);
	if day < 0 {
		// Go back enough 400 year cycles to make day positive.
		n := -day/DaysPer400Years + 1;
		year -= 400*n;
		day += DaysPer400Years*n;
	} else {
		// Cut off 400 year cycles.
		n := day/DaysPer400Years;
		year += 400*n;
		day -= DaysPer400Years*n;
	}

	// Cut off 100-year cycles
	n := day/DaysPer100Years;
	year += 100*n;
	day -= DaysPer100Years*n;

	// Cut off 4-year cycles
	n = day/DaysPer4Years;
	year += 4*n;
	day -= DaysPer4Years*n;

	// Cut off non-leap years.
	n = day/365;
	year += n;
	day -= 365*n;

	t.year = year;

	// If someone ever needs yearday,
	// tyearday = day (+1?)

	months := Months(year);
	var m int;
	yday := int(day);
	for m = 0; m < 12 && yday >= months[m]; m++ {
		yday -= months[m]
	}
	t.month = m+1;
	t.day = yday+1;
	t.zone = "GMT";

	return t;
}

export func UTC() (t *Time, err *os.Error) {
	return SecondsToUTC(Seconds()), nil
}

// TODO: Should this return an error?
export func SecondsToLocalTime(sec int64) *Time {
	zone, offset, err := time.LookupTimezone(sec);
	if err != nil {
		return SecondsToUTC(sec)
	}
	t := SecondsToUTC(sec+int64(offset));
	t.zone = zone;
	t.zoneoffset = offset;
	return t
}

export func LocalTime() (t *Time, err *os.Error) {
	return SecondsToLocalTime(Seconds()), nil
}

// Compute number of seconds since January 1, 1970.
func (t *Time) Seconds() int64 {
	// First, accumulate days since January 1, 2001.
	// Using 2001 instead of 1970 makes the leap-year
	// handling easier (see SecondsToUTC), because
	// it is at the beginning of the 4-, 100-, and 400-year cycles.
	day := int64(0);

	// Rewrite year to be >= 2001.
	year := t.year;
	if year < 2001 {
		n := (2001 - year)/400 + 1;
		year += 400*n;
		day -= DaysPer400Years*n;
	}

	// Add in days from 400-year cycles.
	n := (year - 2001) / 400;
	year -= 400*n;
	day += DaysPer400Years*n;

	// Add in 100-year cycles.
	n = (year - 2001) / 100;
	year -= 100*n;
	day += DaysPer100Years*n;

	// Add in 4-year cycles.
	n = (year - 2001) / 4;
	year -= 4*n;
	day += DaysPer4Years*n;

	// Add in non-leap years.
	n = year - 2001;
	day += 365*n;

	// Add in days this year.
	months := Months(t.year);
	for m := 0; m < t.month-1; m++ {
		day += int64(months[m])
	}
	day += int64(t.day - 1);

	// Convert days to seconds since January 1, 2001.
	sec := day * SecondsPerDay;

	// Add in time elapsed today.
	sec += int64(t.hour) * 3600;
	sec += int64(t.minute) * 60;
	sec += int64(t.second);

	// Convert from seconds since 2001 to seconds since 1970.
	sec += Days1970To2001 * SecondsPerDay;

	// Account for local time zone.
	sec -= int64(t.zoneoffset);
	return sec
}

var LongDayNames = []string{
	"Sunday",
	"Monday",
	"Tuesday",
	"Wednesday",
	"Thursday",
	"Friday",
	"Saturday"
}

var ShortDayNames = []string{
	"Sun",
	"Mon",
	"Tue",
	"Wed",
	"Thu",
	"Fri",
	"Sat"
}

var ShortMonthNames = []string{
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec"
}

func Copy(dst *[]byte, s string) {
	for i := 0; i < len(s); i++ {
		dst[i] = s[i]
	}
}

func Decimal(dst *[]byte, n int) {
	if n < 0 {
		n = 0
	}
	for i := len(dst)-1; i >= 0; i-- {
		dst[i] = byte(n%10 + '0');
		n /= 10
	}
}

func AddString(buf *[]byte, bp int, s string) int {
	n := len(s);
	Copy(buf[bp:bp+n], s);
	return bp+n
}

// Just enough of strftime to implement the date formats below.
// Not exported.
func Format(t *Time, fmt string) string {
	buf := new([]byte, 128);
	bp := 0;

	for i := 0; i < len(fmt); i++ {
		if fmt[i] == '%' {
			i++;
			switch fmt[i] {
			case 'A':	// %A full weekday name
				bp = AddString(buf, bp, LongDayNames[t.weekday]);
			case 'a':	// %a abbreviated weekday name
				bp = AddString(buf, bp, ShortDayNames[t.weekday]);
			case 'b':	// %b abbreviated month name
				bp = AddString(buf, bp, ShortMonthNames[t.month-1]);
			case 'd':	// %d day of month (01-31)
				Decimal(buf[bp:bp+2], t.day);
				bp += 2;
			case 'e':	// %e day of month ( 1-31)
				if t.day >= 10 {
					Decimal(buf[bp:bp+2], t.day)
				} else {
					buf[bp] = ' ';
					buf[bp+1] = byte(t.day + '0')
				}
				bp += 2;
			case 'H':	// %H hour 00-23
				Decimal(buf[bp:bp+2], t.hour);
				bp += 2;
			case 'M':	// %M minute 00-59
				Decimal(buf[bp:bp+2], t.minute);
				bp += 2;
			case 'S':	// %S second 00-59
				Decimal(buf[bp:bp+2], t.second);
				bp += 2;
			case 'Y':	// %Y year 2008
				Decimal(buf[bp:bp+4], int(t.year));
				bp += 4;
			case 'y':	// %y year 08
				Decimal(buf[bp:bp+2], int(t.year%100));
				bp += 2;
			case 'Z':
				bp = AddString(buf, bp, t.zone);
			default:
				buf[bp] = '%';
				buf[bp+1] = fmt[i];
				bp += 2
			}
		} else {
			buf[bp] = fmt[i];
			bp++;
		}
	}
	return string(buf[0:bp])
}

// ANSI C asctime: Sun Nov  6 08:49:37 1994
func (t *Time) Asctime() string {
	return Format(t, "%a %b %e %H:%M:%S %Y")
}

// RFC 850: Sunday, 06-Nov-94 08:49:37 GMT
func (t *Time) RFC850() string {
	return Format(t, "%A, %d-%b-%y %H:%M:%S %Z")
}

// RFC 1123: Sun, 06 Nov 1994 08:49:37 GMT
func (t *Time) RFC1123() string {
	return Format(t, "%a, %d %b %Y %H:%M:%S %Z")
}

// date(1) - Sun Nov  6 08:49:37 GMT 1994
func (t *Time) String() string {
	return Format(t, "%a %b %e %H:%M:%S %Z %Y")
}

<commit_msg>more impossible time errors<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package time

import (
	"os";
	"time"
)

// Seconds since January 1, 1970 00:00:00 GMT
export func Seconds() int64 {
	sec, nsec, err := os.Time();
	if err != nil {
		panic("time: os.Time: ", err.String());
	}
	return sec
}

// Nanoseconds since January 1, 1970 00:00:00 GMT
export func Nanoseconds() int64 {
	sec, nsec, err := os.Time();
	if err != nil {
		panic("time: os.Time: ", err.String());
	}
	return sec*1e9 + nsec
}

export const (
	Sunday = iota;
	Monday;
	Tuesday;
	Wednesday;
	Thursday;
	Friday;
	Saturday;
)

export type Time struct {
	year int64;	// 2008 is 2008
	month, day int;	// Sep-17 is 9, 17
	hour, minute, second int;	// 10:43:12 is 10, 43, 12
	weekday int;		// Sunday = 0, Monday = 1, ...
	zoneoffset int;	// seconds west of UTC
	zone string;
}

var RegularMonths = []int{
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
}
var LeapMonths = []int{
	31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
}

func Months(year int64) *[]int {
	if year%4 == 0 && (year%100 != 0 || year%400 == 0) {
		return &LeapMonths
	} else {
		return &RegularMonths
	}
	return nil	// not reached
}

const (
	SecondsPerDay = 24*60*60;

	DaysPer400Years = 365*400+97;
	DaysPer100Years = 365*100+24;
	DaysPer4Years = 365*4+1;

	Days1970To2001 = 31*365+8;
)

export func SecondsToUTC(sec int64) *Time {
	t := new(Time);

	// Split into time and day.
	day := sec/SecondsPerDay;
	sec -= day*SecondsPerDay;
	if sec < 0 {
		day--;
		sec += SecondsPerDay
	}

	// Time
	t.hour = int(sec/3600);
	t.minute = int((sec/60)%60);
	t.second = int(sec%60);

	// Day 0 = January 1, 1970 was a Thursday
	t.weekday = int((day + Thursday) % 7);
	if t.weekday < 0 {
		t.weekday += 7
	}

	// Change day from 0 = 1970 to 0 = 2001,
	// to make leap year calculations easier
	// (2001 begins 4-, 100-, and 400-year cycles ending in a leap year.)
	day -= Days1970To2001;

	year := int64(2001);
	if day < 0 {
		// Go back enough 400 year cycles to make day positive.
		n := -day/DaysPer400Years + 1;
		year -= 400*n;
		day += DaysPer400Years*n;
	} else {
		// Cut off 400 year cycles.
		n := day/DaysPer400Years;
		year += 400*n;
		day -= DaysPer400Years*n;
	}

	// Cut off 100-year cycles
	n := day/DaysPer100Years;
	year += 100*n;
	day -= DaysPer100Years*n;

	// Cut off 4-year cycles
	n = day/DaysPer4Years;
	year += 4*n;
	day -= DaysPer4Years*n;

	// Cut off non-leap years.
	n = day/365;
	year += n;
	day -= 365*n;

	t.year = year;

	// If someone ever needs yearday,
	// tyearday = day (+1?)

	months := Months(year);
	var m int;
	yday := int(day);
	for m = 0; m < 12 && yday >= months[m]; m++ {
		yday -= months[m]
	}
	t.month = m+1;
	t.day = yday+1;
	t.zone = "GMT";

	return t;
}

export func UTC() *Time {
	return SecondsToUTC(Seconds())
}

// TODO: Should this return an error?
export func SecondsToLocalTime(sec int64) *Time {
	zone, offset, err := time.LookupTimezone(sec);
	if err != nil {
		return SecondsToUTC(sec)
	}
	t := SecondsToUTC(sec+int64(offset));
	t.zone = zone;
	t.zoneoffset = offset;
	return t
}

export func LocalTime() *Time {
	return SecondsToLocalTime(Seconds())
}

// Compute number of seconds since January 1, 1970.
func (t *Time) Seconds() int64 {
	// First, accumulate days since January 1, 2001.
	// Using 2001 instead of 1970 makes the leap-year
	// handling easier (see SecondsToUTC), because
	// it is at the beginning of the 4-, 100-, and 400-year cycles.
	day := int64(0);

	// Rewrite year to be >= 2001.
	year := t.year;
	if year < 2001 {
		n := (2001 - year)/400 + 1;
		year += 400*n;
		day -= DaysPer400Years*n;
	}

	// Add in days from 400-year cycles.
	n := (year - 2001) / 400;
	year -= 400*n;
	day += DaysPer400Years*n;

	// Add in 100-year cycles.
	n = (year - 2001) / 100;
	year -= 100*n;
	day += DaysPer100Years*n;

	// Add in 4-year cycles.
	n = (year - 2001) / 4;
	year -= 4*n;
	day += DaysPer4Years*n;

	// Add in non-leap years.
	n = year - 2001;
	day += 365*n;

	// Add in days this year.
	months := Months(t.year);
	for m := 0; m < t.month-1; m++ {
		day += int64(months[m])
	}
	day += int64(t.day - 1);

	// Convert days to seconds since January 1, 2001.
	sec := day * SecondsPerDay;

	// Add in time elapsed today.
	sec += int64(t.hour) * 3600;
	sec += int64(t.minute) * 60;
	sec += int64(t.second);

	// Convert from seconds since 2001 to seconds since 1970.
	sec += Days1970To2001 * SecondsPerDay;

	// Account for local time zone.
	sec -= int64(t.zoneoffset);
	return sec
}

var LongDayNames = []string{
	"Sunday",
	"Monday",
	"Tuesday",
	"Wednesday",
	"Thursday",
	"Friday",
	"Saturday"
}

var ShortDayNames = []string{
	"Sun",
	"Mon",
	"Tue",
	"Wed",
	"Thu",
	"Fri",
	"Sat"
}

var ShortMonthNames = []string{
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec"
}

func Copy(dst *[]byte, s string) {
	for i := 0; i < len(s); i++ {
		dst[i] = s[i]
	}
}

func Decimal(dst *[]byte, n int) {
	if n < 0 {
		n = 0
	}
	for i := len(dst)-1; i >= 0; i-- {
		dst[i] = byte(n%10 + '0');
		n /= 10
	}
}

func AddString(buf *[]byte, bp int, s string) int {
	n := len(s);
	Copy(buf[bp:bp+n], s);
	return bp+n
}

// Just enough of strftime to implement the date formats below.
// Not exported.
func Format(t *Time, fmt string) string {
	buf := new([]byte, 128);
	bp := 0;

	for i := 0; i < len(fmt); i++ {
		if fmt[i] == '%' {
			i++;
			switch fmt[i] {
			case 'A':	// %A full weekday name
				bp = AddString(buf, bp, LongDayNames[t.weekday]);
			case 'a':	// %a abbreviated weekday name
				bp = AddString(buf, bp, ShortDayNames[t.weekday]);
			case 'b':	// %b abbreviated month name
				bp = AddString(buf, bp, ShortMonthNames[t.month-1]);
			case 'd':	// %d day of month (01-31)
				Decimal(buf[bp:bp+2], t.day);
				bp += 2;
			case 'e':	// %e day of month ( 1-31)
				if t.day >= 10 {
					Decimal(buf[bp:bp+2], t.day)
				} else {
					buf[bp] = ' ';
					buf[bp+1] = byte(t.day + '0')
				}
				bp += 2;
			case 'H':	// %H hour 00-23
				Decimal(buf[bp:bp+2], t.hour);
				bp += 2;
			case 'M':	// %M minute 00-59
				Decimal(buf[bp:bp+2], t.minute);
				bp += 2;
			case 'S':	// %S second 00-59
				Decimal(buf[bp:bp+2], t.second);
				bp += 2;
			case 'Y':	// %Y year 2008
				Decimal(buf[bp:bp+4], int(t.year));
				bp += 4;
			case 'y':	// %y year 08
				Decimal(buf[bp:bp+2], int(t.year%100));
				bp += 2;
			case 'Z':
				bp = AddString(buf, bp, t.zone);
			default:
				buf[bp] = '%';
				buf[bp+1] = fmt[i];
				bp += 2
			}
		} else {
			buf[bp] = fmt[i];
			bp++;
		}
	}
	return string(buf[0:bp])
}

// ANSI C asctime: Sun Nov  6 08:49:37 1994
func (t *Time) Asctime() string {
	return Format(t, "%a %b %e %H:%M:%S %Y")
}

// RFC 850: Sunday, 06-Nov-94 08:49:37 GMT
func (t *Time) RFC850() string {
	return Format(t, "%A, %d-%b-%y %H:%M:%S %Z")
}

// RFC 1123: Sun, 06 Nov 1994 08:49:37 GMT
func (t *Time) RFC1123() string {
	return Format(t, "%a, %d %b %Y %H:%M:%S %Z")
}

// date(1) - Sun Nov  6 08:49:37 GMT 1994
func (t *Time) String() string {
	return Format(t, "%a %b %e %H:%M:%S %Z %Y")
}

<|endoftext|>


<commit_before>package main

const timeFormat = "2006-01-02 15:04:05Z"
const VERSION = "0.1.3"

var (
	GITCOMMIT string
	parser    = NewOptParser()
)
<commit_msg>version 0.1.4<commit_after>package main

const timeFormat = "2006-01-02 15:04:05Z"
const VERSION = "0.1.4"

var (
	GITCOMMIT string
	parser    = NewOptParser()
)
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/inhies/go-bytesize" // Do we really need this?
	"io/ioutil"
	"os"
	"os/exec"
	upath "path"
	fpath "path/filepath"
	"regexp"
	"strconv"
	"strings"
	"text/template"
)

type WikiTrackData struct {
	Duration                 string
	FolderName               string
	Index                    int
	HasAlternateLeadVocalist bool
	Name                     string
	CD                       int
	LinePrefix               string
}

type WikiAlbumData struct {
	Notes      string
	FolderName string
	Tracks     []WikiTrackData
	Duration   string
	Lineage    string
	Size       string
	SampleRate string
	BPS        string
}

func generateWikifiles(c *cli.Context) {
	fileInfo, filepath := checkFilepathArgument(c)
	if fileInfo == nil {
		return
	}

	mode := "batch"
	if c.GlobalBool("single") {
		mode = "single"
	}

	fmt.Printf("The following filepath (%s mode) will be processed: %s\n", mode, filepath)
	notifyDeleteMode(c)

	if !shouldContinue(c) {
		return
	}

	regex, err := regexp.Compile(wikiRegex)
	if err != nil {
		fmt.Println("Internal error - wiki regex could not be compiled!")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	wikiTemplate, err := template.New("wiki").Parse(
		// Stupid windows
		strings.Replace(wikiTemplate, "\n", "\r\n", -1),
	)
	if err != nil {
		fmt.Println("Internal error - wiki template could not be parsed!")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	if mode == "single" {
		generateWikifile(filepath, fileInfo.Name(), regex, wikiTemplate, c.GlobalBool("delete"), "")
		return
	}

	// Create the wikifiles folder path
	wikifiles := fpath.Join(filepath, "__wikifiles")

	// MkdirAll is used instead of Mkdir because this function
	// doesn't error if the folder already exists
	err = os.MkdirAll(wikifiles, os.ModeDir)
	if err != nil {
		fmt.Println("Internal error creating __wikifiles folder")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if file.IsDir() {
			name := file.Name()
			if name != "__wikifiles" {
				generateWikifile(fpath.Join(filepath, name), name, regex, wikiTemplate, c.GlobalBool("delete"), wikifiles)
			}
		}
	}
}

func wikiGetInfoFromFlac(filepath string, parsedData *WikiAlbumData) bool {
	directoryContents, err := ioutil.ReadDir(filepath)
	if err != nil {
		fmt.Println("failed to get directory contents")
		fmt.Println(err)
		return false
	}

	for _, file := range directoryContents {
		if file.IsDir() {
			if wikiGetInfoFromFlac(fpath.Join(filepath, file.Name()), parsedData) {
				return true
			}
		} else if fpath.Ext(file.Name()) == ".flac" {
			data, err := exec.Command(
				"metaflac",
				"--show-sample-rate",
				"--show-bps", // update numbers below
				fpath.Join(filepath, file.Name()),
			).Output()

			if err != nil {
				fmt.Println("metaflac returned an invalid response for sample-rate/bps")
				if data != nil {
					fmt.Println(data)
				}
				panic(err)
			}

			lines := strings.Split(string(data), "\n")
			if len(lines) != 1+2 {
				// Update '2' below and above
				fmt.Println("expected 2 metaflac lines, got", len(lines)-1)
				continue
			}

			failure := false
			for i, line := range lines {
				line := strings.TrimSpace(line)

				if i == 0 {
					rateFloat, err := strconv.ParseFloat(line, 32)
					if err != nil {
						fmt.Print("could not convert sample-rate to float")
						fmt.Println(err)
						failure = true
						break
					}
					parsedData.SampleRate = strconv.FormatFloat(rateFloat/1000, 'f', -1, 32) + "KHz"
				} else if i == 1 {
					parsedData.BPS = line
				}
			}

			if !failure {
				return true
			}
		}
	}

	fmt.Println("failed to find file for sampling info")
	return false
}

func generateWikifile(filepath string, foldername string, regex *regexp.Regexp, wikiTemplate *template.Template, deleteMode bool, outBasepath string) {
	basepath := fpath.Join(filepath, foldername)
	infofile := basepath + ".txt"
	wikifile := filepath
	if outBasepath != "" {
		wikifile = outBasepath
	}

	if !deleteMode {
		fmt.Printf("Generating from %s... ", infofile)
	}

	infobytes, err := ioutil.ReadFile(infofile)
	if err != nil {
		reason := "infofile doesn't exist"
		if !os.IsNotExist(err) {
			fmt.Printf(reason+" (%s)\n", err.Error())
		} else {
			fmt.Println(reason)
		}

		return
	}

	matches := regex.FindSubmatch(infobytes)
	if len(matches) != 1+6 {
		// (entire string itself) + (capture groups)
		fmt.Println("parse failure, expected 7 capturing groups!")
		return
	}

	var parsedData WikiAlbumData
	parsedData.FolderName = foldername

	size, err := getDirectorySize(filepath)
	if err != nil {
		fmt.Println("failed to get directory size")
		fmt.Println(err)
		return
	}
	b := bytesize.New(size)
	parsedData.Size = b.String()

	if !wikiGetInfoFromFlac(filepath, &parsedData) {
		return
	}

	tracks := make([]WikiTrackData, 0)

	var lastTrack WikiTrackData
	var currentTrackNumber int = 0

	// Variables for piecing together the new filename
	var date string
	var location string = "location"
	var source string // this is actually just a single digit

	for i, field := range matches {
		if i == 0 {
			// The first one is just itself
			continue
		}

		field := string(bytes.TrimSpace(field))

		switch i {
		case 1:
			date = field
		case 2:
			lineage := ""
			for _, item := range strings.Split(field, "\n") {
				lineage += "*" + strings.TrimSpace(item) + "\r\n"
			}
			parsedData.Lineage = lineage
		case 3:
			parsedData.Notes = field
		case 4:
			source = field
		case 5:
			// parse tracks
			for _, track := range strings.Split(field, "\n") {
				var trackData WikiTrackData
				trackData.FolderName = foldername
				trackData.LinePrefix = "#"

				str := strings.TrimSpace(track)
				f := strings.Index(str, "[")
				l := strings.Index(str, "]")
				trackData.Duration = str[f+1 : l]

				number := str[:f-2]
				separator := strings.Index(number, ".")
				if separator != -1 {
					// This bit only uses the "path" library
					// because URL's only use forward slash
					cdStr := number[:separator]

					cdNumber, err := strconv.Atoi(cdStr)
					if err != nil {
						panic(err.Error())
					}
					trackData.FolderName = upath.Join(foldername, "CD"+cdStr)
					trackData.CD = cdNumber

					if lastTrack.CD != cdNumber {
						currentTrackNumber = 0
						trackData.LinePrefix = fmt.Sprintf("\r\nCD%d:\r\n%s", cdNumber, trackData.LinePrefix)
					}
				} else {
					// Here lies incomplete support for "Bonus." tracks
					// you need to add support for using filenames instead
					// of tracknumbers for bonus tracks

					// _, err := strconv.Atoi(number)
					// if err != nil {
					// 	trackData.FolderName = number
					// 	trackData.CD = -1

					// 	if lastTrack.FolderName != number {
					// 		currentTrackNumber = 0
					// 	}
					// }
					// continue
				}

				name := strings.TrimSpace(str[l+1:])
				nameWithoutSuffix := strings.TrimSuffix(name, " (*)")
				trackData.HasAlternateLeadVocalist = name != nameWithoutSuffix
				trackData.Name = nameWithoutSuffix

				currentTrackNumber += 1
				trackData.Index = currentTrackNumber

				lastTrack = trackData
				tracks = append(tracks, trackData)
			}
		case 6:
			parsedData.Duration = field
		}
	}
	parsedData.Tracks = tracks

	// Piece together wikifile
	wikifile = fpath.Join(wikifile, date+" "+location+"_"+source+".wiki")

	if removeFile(wikifile, deleteMode) && !deleteMode {
		fmt.Print("overwritten...")
	}
	if deleteMode {
		return
	}

	wikiout := createFile(wikifile)
	defer wikiout.Close()

	if wikiout != nil {
		err = wikiTemplate.Execute(wikiout, parsedData)
		if err != nil {
			fmt.Println("could not insert data into template!")
			fmt.Println(err)
			return
		}

		fmt.Println("success!")
	}
}
<commit_msg>Finish wikifile new names (fix #6)<commit_after>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/inhies/go-bytesize" // Do we really need this?
	"io/ioutil"
	"os"
	"os/exec"
	upath "path"
	fpath "path/filepath"
	"regexp"
	"strconv"
	"strings"
	"text/template"
)

type WikiTrackData struct {
	Duration                 string
	FolderName               string
	Index                    int
	HasAlternateLeadVocalist bool
	Name                     string
	CD                       int
	LinePrefix               string
}

type WikiAlbumData struct {
	Notes      string
	FolderName string
	Tracks     []WikiTrackData
	Duration   string
	Lineage    string
	Size       string
	SampleRate string
	BPS        string
}

func generateWikifiles(c *cli.Context) {
	fileInfo, filepath := checkFilepathArgument(c)
	if fileInfo == nil {
		return
	}

	mode := "batch"
	if c.GlobalBool("single") {
		mode = "single"
	}

	fmt.Printf("The following filepath (%s mode) will be processed: %s\n", mode, filepath)
	notifyDeleteMode(c)

	if !shouldContinue(c) {
		return
	}

	regex, err := regexp.Compile(wikiRegex)
	if err != nil {
		fmt.Println("Internal error - wiki regex could not be compiled!")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	wikiTemplate, err := template.New("wiki").Parse(
		// Stupid windows
		strings.Replace(wikiTemplate, "\n", "\r\n", -1),
	)
	if err != nil {
		fmt.Println("Internal error - wiki template could not be parsed!")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	if mode == "single" {
		generateWikifile(filepath, fileInfo.Name(), regex, wikiTemplate, c.GlobalBool("delete"), "")
		return
	}

	// Create the wikifiles folder path
	wikifiles := fpath.Join(filepath, "__wikifiles")

	// MkdirAll is used instead of Mkdir because this function
	// doesn't error if the folder already exists
	err = os.MkdirAll(wikifiles, os.ModeDir)
	if err != nil {
		fmt.Println("Internal error creating __wikifiles folder")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if file.IsDir() {
			name := file.Name()
			if name != "__wikifiles" {
				generateWikifile(fpath.Join(filepath, name), name, regex, wikiTemplate, c.GlobalBool("delete"), wikifiles)
			}
		}
	}
}

func wikiGetInfoFromFlac(filepath string, parsedData *WikiAlbumData) bool {
	directoryContents, err := ioutil.ReadDir(filepath)
	if err != nil {
		fmt.Println("failed to get directory contents")
		fmt.Println(err)
		return false
	}

	for _, file := range directoryContents {
		if file.IsDir() {
			if wikiGetInfoFromFlac(fpath.Join(filepath, file.Name()), parsedData) {
				return true
			}
		} else if fpath.Ext(file.Name()) == ".flac" {
			data, err := exec.Command(
				"metaflac",
				"--show-sample-rate",
				"--show-bps", // update numbers below
				fpath.Join(filepath, file.Name()),
			).Output()

			if err != nil {
				fmt.Println("metaflac returned an invalid response for sample-rate/bps")
				if data != nil {
					fmt.Println(data)
				}
				panic(err)
			}

			lines := strings.Split(string(data), "\n")
			if len(lines) != 1+2 {
				// Update '2' below and above
				fmt.Println("expected 2 metaflac lines, got", len(lines)-1)
				continue
			}

			failure := false
			for i, line := range lines {
				line := strings.TrimSpace(line)

				if i == 0 {
					rateFloat, err := strconv.ParseFloat(line, 32)
					if err != nil {
						fmt.Print("could not convert sample-rate to float")
						fmt.Println(err)
						failure = true
						break
					}
					parsedData.SampleRate = strconv.FormatFloat(rateFloat/1000, 'f', -1, 32) + "KHz"
				} else if i == 1 {
					parsedData.BPS = line
				}
			}

			if !failure {
				return true
			}
		}
	}

	fmt.Println("failed to find file for sampling info")
	return false
}

func generateWikifile(filepath string, foldername string, regex *regexp.Regexp, wikiTemplate *template.Template, deleteMode bool, outBasepath string) {
	basepath := fpath.Join(filepath, foldername)
	infofile := basepath + ".txt"
	wikifile := filepath
	if outBasepath != "" {
		wikifile = outBasepath
	}

	if deleteMode {
		fmt.Printf("Deleting wiki from %s...", infofile)
	} else {
		fmt.Printf("Generating from %s... ", infofile)
	}

	infobytes, err := ioutil.ReadFile(infofile)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Println("infofile doesn't exist")
		} else {
			fmt.Printf("error (%s)\n", err.Error())
		}
		return
	}

	matches := regex.FindSubmatch(infobytes)
	if len(matches) != 1+6 {
		// (entire string itself) + (capture groups)
		fmt.Println("parse failure, expected 7 capturing groups!")
		return
	}

	var parsedData WikiAlbumData
	parsedData.FolderName = foldername

	size, err := getDirectorySize(filepath)
	if err != nil {
		fmt.Println("failed to get directory size")
		fmt.Println(err)
		return
	}
	b := bytesize.New(size)
	parsedData.Size = b.String()

	if !wikiGetInfoFromFlac(filepath, &parsedData) {
		return
	}

	tracks := make([]WikiTrackData, 0)

	var lastTrack WikiTrackData
	var currentTrackNumber int = 0

	// Variables for piecing together the new filename
	source, album := "", getAlbumNameFromPath(filepath)

	if album == "" {
		fmt.Println("could not get album")
		return
	}

	for i, field := range matches {
		if i == 0 {
			// The first one is just itself
			continue
		}

		field := string(bytes.TrimSpace(field))

		switch i {
		case 1:
			// date = field
		case 2:
			lineage := ""
			for _, item := range strings.Split(field, "\n") {
				lineage += "*" + strings.TrimSpace(item) + "\r\n"
			}
			parsedData.Lineage = lineage
		case 3:
			parsedData.Notes = field
		case 4:
			source = field
		case 5:
			// parse tracks
			for _, track := range strings.Split(field, "\n") {
				var trackData WikiTrackData
				trackData.FolderName = foldername
				trackData.LinePrefix = "#"

				str := strings.TrimSpace(track)
				f := strings.Index(str, "[")
				l := strings.Index(str, "]")
				trackData.Duration = str[f+1 : l]

				number := str[:f-2]
				separator := strings.Index(number, ".")
				if separator != -1 {
					// This bit only uses the "path" library
					// because URL's only use forward slash
					cdStr := number[:separator]

					cdNumber, err := strconv.Atoi(cdStr)
					if err != nil {
						panic(err.Error())
					}
					trackData.FolderName = upath.Join(foldername, "CD"+cdStr)
					trackData.CD = cdNumber

					if lastTrack.CD != cdNumber {
						currentTrackNumber = 0
						trackData.LinePrefix = fmt.Sprintf("\r\nCD%d:\r\n%s", cdNumber, trackData.LinePrefix)
					}
				} else {
					// Here lies incomplete support for "Bonus." tracks
					// you need to add support for using filenames instead
					// of tracknumbers for bonus tracks

					// _, err := strconv.Atoi(number)
					// if err != nil {
					// 	trackData.FolderName = number
					// 	trackData.CD = -1

					// 	if lastTrack.FolderName != number {
					// 		currentTrackNumber = 0
					// 	}
					// }
					// continue
				}

				name := strings.TrimSpace(str[l+1:])
				nameWithoutSuffix := strings.TrimSuffix(name, " (*)")
				trackData.HasAlternateLeadVocalist = name != nameWithoutSuffix
				trackData.Name = nameWithoutSuffix

				currentTrackNumber += 1
				trackData.Index = currentTrackNumber

				lastTrack = trackData
				tracks = append(tracks, trackData)
			}
		case 6:
			parsedData.Duration = field
		}
	}
	parsedData.Tracks = tracks

	// Piece together wikifile
	wikifile = fpath.Join(wikifile, album+"_Source "+source+".wiki")
	fmt.Printf("\n - %s... ", wikifile)

	success := removeFile(wikifile, false)
	if deleteMode {
		message := "success!"
		if !success {
			message = "couldn't delete!"
		}
		fmt.Println(message)
		return
	} else if success {
		fmt.Print("overwritten... ")
	}

	wikiout := createFile(wikifile)
	defer wikiout.Close()

	if wikiout != nil {
		err = wikiTemplate.Execute(wikiout, parsedData)
		if err != nil {
			fmt.Println("could not insert data into template!")
			fmt.Println(err)
			return
		}

		fmt.Println("success!")
	}
}

func getAlbumNameFromPath(filepath string) string {
	var iterateDirectory func(string) string
	iterateDirectory = func(path string) string {
		directoryContents, err := ioutil.ReadDir(path)
		if err != nil {
			fmt.Println("failed to get directory contents")
			fmt.Println(err)
			return ""
		}

		for _, file := range directoryContents {
			isDir, name := file.IsDir(), file.Name()
			if isDir && strings.HasPrefix(name, "CD") {
				// Okay, we're in a CD based album...
				return iterateDirectory(fpath.Join(path, name))
			} else if !isDir && (fpath.Ext(name) == ".flac") {
				return fpath.Join(path, name)
			}
		}

		return ""
	}

	filepath = iterateDirectory(filepath)

	// If we can't find a valid file to use
	if filepath == "" {
		return ""
	}

	data, err := exec.Command(
		"metaflac",
		"--show-tag=album",
		filepath,
	).Output()

	if err != nil {
		fmt.Println("metaflac returned an invalid response")
		if data != nil {
			fmt.Println(data)
		}
		return ""
	}

	lines := strings.Split(string(data), "\n")
	if len(lines) != 2 {
		panic(fmt.Sprintf("[invalid metaflac output] Expected %d lines, got %d", 2, len(lines)))
	}

	return strings.TrimSpace(lines[0][6:])
}
<|endoftext|>


<commit_before>package main

import (
	"math/rand"
	"strconv"
	"time"
	"unicode/utf8"

	"github.com/Zamiell/isaac-racing-server/src/log"
	melody "gopkg.in/olahol/melody.v1"
)

const (
	rateLimitRate       = float64(3)  // Number of races created
	rateLimitPer        = float64(60) // Per seconds
	automaticBanAdminID = 1
	automaticBanReason  = "race spamming"
)

func websocketRaceCreate(s *melody.Session, d *IncomingWebsocketData) {
	// Local variables
	username := d.v.Username
	admin := d.v.Admin
	rateLimitAllowance := d.v.RateLimitAllowance
	rateLimitLastCheck := d.v.RateLimitLastCheck
	name := d.Name
	ruleset := d.Ruleset

	/*
		Validation
	*/

	// Validate that the server is not shutting down soon
	if shutdownMode > 0 && admin == 0 {
		websocketWarning(s, d.Command, "The server is restarting soon (when all ongoing races have finished). You cannot start any new races for the time being.")
		return
	}

	// Validate that the race name cannot be empty
	if name == "" {
		name = "-"
	}

	// Validate that the race name is not longer than 100 characters
	if utf8.RuneCountInString(name) > 100 {
		log.Warning("User \"" + username + "\" sent a race name longer than 100 characters.")
		websocketError(s, d.Command, "Race names must not be longer than 100 characters.")
		return
	}

	// Validate that the ruleset options cannot be empty
	if ruleset.Format == "" {
		ruleset.Format = "unseeded"
	}
	if ruleset.Character == "" {
		ruleset.Character = "Judas"
	}
	if ruleset.Goal == "" {
		ruleset.Goal = "The Chest"
	}

	// Validate the submitted ruleset
	if !raceValidateRuleset(s, d) {
		return
	}

	// Pick a random character, if necessary
	if ruleset.Character == "random" {
		ruleset.CharacterRandom = true
		rand.Seed(time.Now().UnixNano())
		ruleset.Character = characters[rand.Intn(len(characters))]
	}

	// Pick a random starting build, if necessary
	// (StartingBuild will be -1 in non-seeded races)
	if ruleset.StartingBuild == 0 {
		ruleset.StartingBuildRandom = true
		rand.Seed(time.Now().UnixNano())
		ruleset.StartingBuild = rand.Intn(numBuilds) + 1 // 1 to numBuilds
	}

	// Check if there are any ongoing races with this name
	for _, race := range races {
		if race.Name == name {
			websocketError(s, d.Command, "There is already a non-finished race with that name.")
			return
		}
	}

	// Validate that the user is not creating new races over and over, which will generate an annoying sound effect for everyone in the lobby
	// Algorithm from: http://stackoverflow.com/questions/667508/whats-a-good-rate-limiting-algorithm
	// (allow staff/admins to create unlimited races)
	if admin == 0 && !ruleset.Solo {
		now := time.Now()
		timePassed := now.Sub(rateLimitLastCheck).Seconds()
		s.Set("rateLimitLastCheck", now)
		log.Info("User \"" + username + "\" has \"" + strconv.FormatFloat(timePassed, 'f', 2, 64) + "\" time passed since the last race creation.")

		newRateLimitAllowance := rateLimitAllowance + timePassed*(rateLimitRate/rateLimitPer)
		if newRateLimitAllowance > rateLimitRate {
			newRateLimitAllowance = rateLimitRate
		}

		if newRateLimitAllowance < 1 {
			// They are spamming new races, so automatically ban them as punishment
			log.Warning("User \"" + username + "\" triggered new race rate-limiting; banning them.")
			ban(s, d)
			return
		} else {
			newRateLimitAllowance -= 1
			s.Set("rateLimitAllowance", newRateLimitAllowance)
		}
	}

	/*
		Create
	*/

	// Create and set a seed if necessary
	ruleset.Seed = "-"
	if ruleset.Format == "seeded" || ruleset.Format == "seeded-hard" {
		// Create a random Isaac seed
		// (using the current Epoch timestamp as a seed)
		ruleset.Seed = isaacGetRandomSeed()
	} else if ruleset.Format == "diversity" {
		ruleset.Seed = diversityGetSeed(ruleset)
	}

	// Populate the starting items field
	if ruleset.Format == "seeded" || ruleset.Format == "seeded-hard" {
		ruleset.StartingItems = make([]int, 0)
	} else if ruleset.Format == "diversity" {
		ruleset.StartingItems = make([]int, 0)
	}

	/*
		Create the race in the database
		(it will have no data associated with it other than the automatically
		generated row ID; we want to use this ID as a unique map key)

		The benefit of doing this is that we won't reuse any race IDs after a
		server restart or crash.
		Furthermore, we want the ability for racers to be able to submit a race
		comment after the race has already ended. (Races are deleted from the
		internal map upon finishing.)
	*/
	var raceID int
	if v, err := db.Races.Insert(); err != nil {
		log.Error("Database error while inserting the race:", err)
		websocketError(s, d.Command, "")
		return
	} else {
		raceID = v
	}

	// Create the race and keep track of it in the races map
	race := &Race{
		ID:              raceID,
		Name:            name,
		Status:          "open",
		Ruleset:         ruleset,
		Captain:         username,
		SoundPlayed:     false,
		DatetimeCreated: getTimestamp(),
		DatetimeStarted: 0,
		Racers:          make(map[string]*Racer, 0),
	}
	races[raceID] = race

	// Send everyone a notification that a new race has been started
	for _, s := range websocketSessions {
		websocketEmit(s, "raceCreated", &RaceCreatedMessage{
			ID:              race.ID,
			Name:            race.Name,
			Status:          race.Status,
			Ruleset:         race.Ruleset,
			Captain:         race.Captain,
			DatetimeCreated: race.DatetimeCreated,
			DatetimeStarted: race.DatetimeStarted,
			Racers:          make([]string, 0),
		})
	}

	d.ID = race.ID
	websocketRaceJoin(s, d)
}

func ban(s *melody.Session, d *IncomingWebsocketData) {
	// Local variables
	userID := d.v.UserID

	/*
		This code is copied from the "websocketAdminBan()" function
	*/

	// Add this username to the ban list in the database
	if err := db.BannedUsers.Insert(userID, automaticBanAdminID, automaticBanReason); err != nil {
		log.Error("Database error while userting the banned user:", err)
		websocketError(s, d.Command, "")
		return
	}

	// Add their IP to the banned IP list
	if err := db.BannedIPs.InsertUserIP(userID, automaticBanAdminID, automaticBanReason); err != nil {
		log.Error("Database error while inserting the banned IP:", err)
		websocketError(s, d.Command, "")
		return
	}

	websocketError(
		s,
		"Banned",
		"New race spamming detected. You have been banned. If you think this was a mistake, please contact the administration to appeal.",
	)
	websocketClose(s)
}
<commit_msg>backing off ban threshold<commit_after>package main

import (
	"math/rand"
	"strconv"
	"time"
	"unicode/utf8"

	"github.com/Zamiell/isaac-racing-server/src/log"
	melody "gopkg.in/olahol/melody.v1"
)

const (
	rateLimitRate       = float64(4)  // Number of races created
	rateLimitPer        = float64(60) // Per seconds
	automaticBanAdminID = 1
	automaticBanReason  = "race spamming"
)

func websocketRaceCreate(s *melody.Session, d *IncomingWebsocketData) {
	// Local variables
	username := d.v.Username
	admin := d.v.Admin
	rateLimitAllowance := d.v.RateLimitAllowance
	rateLimitLastCheck := d.v.RateLimitLastCheck
	name := d.Name
	ruleset := d.Ruleset

	/*
		Validation
	*/

	// Validate that the server is not shutting down soon
	if shutdownMode > 0 && admin == 0 {
		websocketWarning(s, d.Command, "The server is restarting soon (when all ongoing races have finished). You cannot start any new races for the time being.")
		return
	}

	// Validate that the race name cannot be empty
	if name == "" {
		name = "-"
	}

	// Validate that the race name is not longer than 100 characters
	if utf8.RuneCountInString(name) > 100 {
		log.Warning("User \"" + username + "\" sent a race name longer than 100 characters.")
		websocketError(s, d.Command, "Race names must not be longer than 100 characters.")
		return
	}

	// Validate that the ruleset options cannot be empty
	if ruleset.Format == "" {
		ruleset.Format = "unseeded"
	}
	if ruleset.Character == "" {
		ruleset.Character = "Judas"
	}
	if ruleset.Goal == "" {
		ruleset.Goal = "The Chest"
	}

	// Validate the submitted ruleset
	if !raceValidateRuleset(s, d) {
		return
	}

	// Pick a random character, if necessary
	if ruleset.Character == "random" {
		ruleset.CharacterRandom = true
		rand.Seed(time.Now().UnixNano())
		ruleset.Character = characters[rand.Intn(len(characters))]
	}

	// Pick a random starting build, if necessary
	// (StartingBuild will be -1 in non-seeded races)
	if ruleset.StartingBuild == 0 {
		ruleset.StartingBuildRandom = true
		rand.Seed(time.Now().UnixNano())
		ruleset.StartingBuild = rand.Intn(numBuilds) + 1 // 1 to numBuilds
	}

	// Check if there are any ongoing races with this name
	for _, race := range races {
		if race.Name == name {
			websocketError(s, d.Command, "There is already a non-finished race with that name.")
			return
		}
	}

	// Validate that the user is not creating new races over and over, which will generate an annoying sound effect for everyone in the lobby
	// Algorithm from: http://stackoverflow.com/questions/667508/whats-a-good-rate-limiting-algorithm
	// (allow staff/admins to create unlimited races)
	if admin == 0 && !ruleset.Solo {
		now := time.Now()
		timePassed := now.Sub(rateLimitLastCheck).Seconds()
		s.Set("rateLimitLastCheck", now)
		log.Info("User \"" + username + "\" has \"" + strconv.FormatFloat(timePassed, 'f', 2, 64) + "\" time passed since the last race creation.")

		newRateLimitAllowance := rateLimitAllowance + timePassed*(rateLimitRate/rateLimitPer)
		if newRateLimitAllowance > rateLimitRate {
			newRateLimitAllowance = rateLimitRate
		}

		if newRateLimitAllowance < 1 {
			// They are spamming new races, so automatically ban them as punishment
			log.Warning("User \"" + username + "\" triggered new race rate-limiting; banning them.")
			ban(s, d)
			return
		} else {
			newRateLimitAllowance -= 1
			s.Set("rateLimitAllowance", newRateLimitAllowance)
		}
	}

	/*
		Create
	*/

	// Create and set a seed if necessary
	ruleset.Seed = "-"
	if ruleset.Format == "seeded" || ruleset.Format == "seeded-hard" {
		// Create a random Isaac seed
		// (using the current Epoch timestamp as a seed)
		ruleset.Seed = isaacGetRandomSeed()
	} else if ruleset.Format == "diversity" {
		ruleset.Seed = diversityGetSeed(ruleset)
	}

	// Populate the starting items field
	if ruleset.Format == "seeded" || ruleset.Format == "seeded-hard" {
		ruleset.StartingItems = make([]int, 0)
	} else if ruleset.Format == "diversity" {
		ruleset.StartingItems = make([]int, 0)
	}

	/*
		Create the race in the database
		(it will have no data associated with it other than the automatically
		generated row ID; we want to use this ID as a unique map key)

		The benefit of doing this is that we won't reuse any race IDs after a
		server restart or crash.
		Furthermore, we want the ability for racers to be able to submit a race
		comment after the race has already ended. (Races are deleted from the
		internal map upon finishing.)
	*/
	var raceID int
	if v, err := db.Races.Insert(); err != nil {
		log.Error("Database error while inserting the race:", err)
		websocketError(s, d.Command, "")
		return
	} else {
		raceID = v
	}

	// Create the race and keep track of it in the races map
	race := &Race{
		ID:              raceID,
		Name:            name,
		Status:          "open",
		Ruleset:         ruleset,
		Captain:         username,
		SoundPlayed:     false,
		DatetimeCreated: getTimestamp(),
		DatetimeStarted: 0,
		Racers:          make(map[string]*Racer, 0),
	}
	races[raceID] = race

	// Send everyone a notification that a new race has been started
	for _, s := range websocketSessions {
		websocketEmit(s, "raceCreated", &RaceCreatedMessage{
			ID:              race.ID,
			Name:            race.Name,
			Status:          race.Status,
			Ruleset:         race.Ruleset,
			Captain:         race.Captain,
			DatetimeCreated: race.DatetimeCreated,
			DatetimeStarted: race.DatetimeStarted,
			Racers:          make([]string, 0),
		})
	}

	d.ID = race.ID
	websocketRaceJoin(s, d)
}

func ban(s *melody.Session, d *IncomingWebsocketData) {
	// Local variables
	userID := d.v.UserID

	/*
		This code is copied from the "websocketAdminBan()" function
	*/

	// Add this username to the ban list in the database
	if err := db.BannedUsers.Insert(userID, automaticBanAdminID, automaticBanReason); err != nil {
		log.Error("Database error while userting the banned user:", err)
		websocketError(s, d.Command, "")
		return
	}

	// Add their IP to the banned IP list
	if err := db.BannedIPs.InsertUserIP(userID, automaticBanAdminID, automaticBanReason); err != nil {
		log.Error("Database error while inserting the banned IP:", err)
		websocketError(s, d.Command, "")
		return
	}

	websocketError(
		s,
		"Banned",
		"New race spamming detected. You have been banned. If you think this was a mistake, please contact the administration to appeal.",
	)
	websocketClose(s)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/jlaffaye/ftp"
	"testing"
	"time"
	"os"
	"bytes"
	"io"
	"io/ioutil"
)

// Integration style tests for the FTP server.  This requires the server to be running
// on localhost and the variables in env.list to be exported (eg: FTP_PORT, etc).

func getClient(doLogin bool) (*ftp.ServerConn, error) {
	c, err := ftp.DialTimeout("localhost:3000", time.Second)
	if err != nil {
		return nil, err
	}

	if doLogin {
		err = c.Login(os.Getenv("FTP_USER"), os.Getenv("FTP_PASSWD"))
		if err != nil {
			return nil, err
		}
	}

	return c, nil
}

// test that we can connect to the server (in passive mode)
func TestLogin(t *testing.T) {

	var err error
	var c *ftp.ServerConn
	if c, err = getClient(false); err != nil {
		t.Fatal(err)
	}

	err = c.Login(os.Getenv("FTP_USER"), os.Getenv("FTP_PASSWD"))
	if err != nil {
		t.Fatal(err)
	}
}

func testFileContents(t *testing.T, c *ftp.ServerConn) {
	// Upload a file to the FTP server using PUT.  GET it back and confirm it's contents

	var err error
	path := "test.txt"
	testString := "some text in a file like object"
	data := bytes.NewBufferString(testString)

	if err = c.Stor(path, data); err != nil {
		t.Error(err)
	}

	var reader io.ReadCloser
	if reader, err = c.Retr(path); err != nil {
		t.Error(err)
	}
	defer reader.Close()

	var dataRead []byte
	if dataRead, err = ioutil.ReadAll(reader); err != nil {
		t.Error(err)
	}

	if testString != string(dataRead) {
		t.Errorf("Strings do not match, expected: [%s] but saw [%s]", testString, string(dataRead))
	}
}

func TestPutAndGet(t *testing.T) {
	var err error
	var c *ftp.ServerConn
	if c, err = getClient(true); err != nil {
		t.Fatal(err)
	}

	testFileContents(t, c)
}

func TestDirs(t *testing.T) {
	// test mkdir, cd, pwd, del

	var err error
	var c *ftp.ServerConn
	if c, err = getClient(true); err != nil {
		t.Fatal(err)
	}

	// CDing to / in an empty S3 bucket has been troublesome
	if err = c.ChangeDir("/"); err != nil {
		t.Error(err)
	}

	newDir := "newdir"
	expectedCwd := "/newdir"
	if err = c.MakeDir(newDir); err != nil {
		// c.MakeDir seems to return a non-nil error on success.  Strange, probably the ftpserver package
		//t.Error(err)
	}

	if err = c.ChangeDir(newDir); err != nil {
		t.Error(err)
	}

	var cwd string
	if cwd, err = c.CurrentDir(); err != nil {
		t.Error(err)
	}

	if cwd != expectedCwd {
		t.Errorf("expected cwd to be %s but it is %s", expectedCwd, cwd)
	}

	// test getting and putting a file from this directory
	testFileContents(t, c)

	if err = c.ChangeDirToParent(); err != nil {
		t.Error(err)
	}

	if err = c.Delete(newDir); err != nil {
		t.Error(err)
	}

	// directory doesn't exist any more, err should be non nil
	if err = c.ChangeDir(newDir); err == nil {
		t.Error(err)
	}

}

func TestRename(t *testing.T) {
	// Test that renaming a file works

	var err error
	var c *ftp.ServerConn
	if c, err = getClient(true); err != nil {
		t.Fatal(err)
	}

	origPath := "test2.txt"
	testString := "some more text in a file like object"
	data := bytes.NewBufferString(testString)

	if err = c.Stor(origPath, data); err != nil {
		t.Error(err)
	}

	// move the file
	newPath := "test2_moved.txt"
	if err = c.Rename(origPath, newPath); err != nil {
		t.Error(err)
	}

	var reader io.ReadCloser
	if reader, err = c.Retr(newPath); err != nil {
		t.Error(err)
	}

	var dataRead []byte
	if dataRead, err = ioutil.ReadAll(reader); err != nil {
		t.Error(err)
	}
	reader.Close()

	if testString != string(dataRead) {
		t.Errorf("Strings do not match, expected: [%s] but saw [%s]", testString, string(dataRead))
	}

	// Reading the original file should fail.
	if _, err = c.Retr(origPath); err != nil {
		t.Errorf("file should not exist: %s", origPath)
	}
}

func TestDirRenameDelete(t *testing.T) {
	// Test that renaming a directory containing file(s) works as expected
	var err error
	var c *ftp.ServerConn
	if c, err = getClient(true); err != nil {
		t.Fatal(err)
	}

	dirA := "/dirA"
	dirB := "/dirB"
	newDirA := "/dirB/subdir"
	if err = c.MakeDir(dirA); err != nil {
		// c.MakeDir seems to return a non-nil error on success.  Strange, probably the ftpserver package
		//t.Error(err)
	}

	if err = c.MakeDir(dirB); err != nil {
		// c.MakeDir seems to return a non-nil error on success.  Strange, probably the ftpserver package
		//t.Error(err)
	}

	if err = c.ChangeDir(dirA); err != nil {
		t.Error(err)
	}

	testFileContents(t, c)

	if err = c.ChangeDirToParent(); err != nil {
		t.Error(err)
	}

	if err = c.Rename(dirA, newDirA); err != nil {
		t.Error(err)
	}

	if err = c.ChangeDir(newDirA); err != nil {
		t.Error(err)
	}

	var entries []*ftp.Entry
	if entries, err = c.List(newDirA); err != nil {
		t.Error(err)
	}

	if len(entries) != 1 {
		t.Errorf("Expected one file in dir %s, saw %d", newDirA, len(entries))
	}

	// test deleting the nested directory
	if err = c.ChangeDir("/"); err != nil {
		t.Error(err)
	}

	if err = c.Delete(dirB); err != nil {
		t.Error(err)
	}

	// shouldn't be able to cd
	if err = c.ChangeDir(dirB); err == nil {
		t.Error("should not be able to cd into a deleted directory")
	}
}
<commit_msg>using the FTP_PORT env var for the tests<commit_after>package main

import (
	"bytes"
	"github.com/jlaffaye/ftp"
	"io"
	"io/ioutil"
	"os"
	"testing"
	"time"
)

// Integration style tests for the FTP server.  This requires the server to be running
// on localhost and the variables in env.list to be exported (eg: FTP_PORT, etc).

func getClient(doLogin bool) (*ftp.ServerConn, error) {
	c, err := ftp.DialTimeout("localhost:"+os.Getenv("FTP_PORT"), time.Second)
	if err != nil {
		return nil, err
	}

	if doLogin {
		err = c.Login(os.Getenv("FTP_USER"), os.Getenv("FTP_PASSWD"))
		if err != nil {
			return nil, err
		}
	}

	return c, nil
}

// test that we can connect to the server
func TestLogin(t *testing.T) {

	var err error
	var c *ftp.ServerConn
	if c, err = getClient(false); err != nil {
		t.Fatal(err)
	}

	err = c.Login(os.Getenv("FTP_USER"), os.Getenv("FTP_PASSWD"))
	if err != nil {
		t.Fatal(err)
	}
}

func testFileContents(t *testing.T, c *ftp.ServerConn) {
	// Upload a file to the FTP server using PUT.  GET it back and confirm it's contents

	var err error
	path := "test.txt"
	testString := "some text in a file like object"
	data := bytes.NewBufferString(testString)

	if err = c.Stor(path, data); err != nil {
		t.Error(err)
	}

	var reader io.ReadCloser
	if reader, err = c.Retr(path); err != nil {
		t.Error(err)
	}
	defer reader.Close()

	var dataRead []byte
	if dataRead, err = ioutil.ReadAll(reader); err != nil {
		t.Error(err)
	}

	if testString != string(dataRead) {
		t.Errorf("Strings do not match, expected: [%s] but saw [%s]", testString, string(dataRead))
	}
}

func TestPutAndGet(t *testing.T) {
	var err error
	var c *ftp.ServerConn
	if c, err = getClient(true); err != nil {
		t.Fatal(err)
	}

	testFileContents(t, c)
}

func TestDirs(t *testing.T) {
	// test mkdir, cd, pwd, del

	var err error
	var c *ftp.ServerConn
	if c, err = getClient(true); err != nil {
		t.Fatal(err)
	}

	// CDing to / in an empty S3 bucket has been troublesome
	if err = c.ChangeDir("/"); err != nil {
		t.Error(err)
	}

	newDir := "newdir"
	expectedCwd := "/newdir"
	if err = c.MakeDir(newDir); err != nil {
		// c.MakeDir seems to return a non-nil error on success.  Strange, probably the ftpserver package
		//t.Error(err)
	}

	if err = c.ChangeDir(newDir); err != nil {
		t.Error(err)
	}

	var cwd string
	if cwd, err = c.CurrentDir(); err != nil {
		t.Error(err)
	}

	if cwd != expectedCwd {
		t.Errorf("expected cwd to be %s but it is %s", expectedCwd, cwd)
	}

	// test getting and putting a file from this directory
	testFileContents(t, c)

	if err = c.ChangeDirToParent(); err != nil {
		t.Error(err)
	}

	if err = c.Delete(newDir); err != nil {
		t.Error(err)
	}

	// directory doesn't exist any more, err should be non nil
	if err = c.ChangeDir(newDir); err == nil {
		t.Error(err)
	}

}

func TestRename(t *testing.T) {
	// Test that renaming a file works

	var err error
	var c *ftp.ServerConn
	if c, err = getClient(true); err != nil {
		t.Fatal(err)
	}

	origPath := "test2.txt"
	testString := "some more text in a file like object"
	data := bytes.NewBufferString(testString)

	if err = c.Stor(origPath, data); err != nil {
		t.Error(err)
	}

	// move the file
	newPath := "test2_moved.txt"
	if err = c.Rename(origPath, newPath); err != nil {
		t.Error(err)
	}

	var reader io.ReadCloser
	if reader, err = c.Retr(newPath); err != nil {
		t.Error(err)
	}

	var dataRead []byte
	if dataRead, err = ioutil.ReadAll(reader); err != nil {
		t.Error(err)
	}
	reader.Close()

	if testString != string(dataRead) {
		t.Errorf("Strings do not match, expected: [%s] but saw [%s]", testString, string(dataRead))
	}

	// Reading the original file should fail.
	if _, err = c.Retr(origPath); err != nil {
		t.Errorf("file should not exist: %s", origPath)
	}
}

func TestDirRenameDelete(t *testing.T) {
	// Test that renaming a directory containing file(s) works as expected
	var err error
	var c *ftp.ServerConn
	if c, err = getClient(true); err != nil {
		t.Fatal(err)
	}

	dirA := "/dirA"
	dirB := "/dirB"
	newDirA := "/dirB/subdir"
	if err = c.MakeDir(dirA); err != nil {
		// c.MakeDir seems to return a non-nil error on success.  Strange, probably the ftpserver package
		//t.Error(err)
	}

	if err = c.MakeDir(dirB); err != nil {
		// c.MakeDir seems to return a non-nil error on success.  Strange, probably the ftpserver package
		//t.Error(err)
	}

	if err = c.ChangeDir(dirA); err != nil {
		t.Error(err)
	}

	testFileContents(t, c)

	if err = c.ChangeDirToParent(); err != nil {
		t.Error(err)
	}

	if err = c.Rename(dirA, newDirA); err != nil {
		t.Error(err)
	}

	if err = c.ChangeDir(newDirA); err != nil {
		t.Error(err)
	}

	var entries []*ftp.Entry
	if entries, err = c.List(newDirA); err != nil {
		t.Error(err)
	}

	if len(entries) != 1 {
		t.Errorf("Expected one file in dir %s, saw %d", newDirA, len(entries))
	}

	// test deleting the nested directory
	if err = c.ChangeDir("/"); err != nil {
		t.Error(err)
	}

	if err = c.Delete(dirB); err != nil {
		t.Error(err)
	}

	// shouldn't be able to cd
	if err = c.ChangeDir(dirB); err == nil {
		t.Error("should not be able to cd into a deleted directory")
	}
}
<|endoftext|>


<commit_before>package coretest

import (
	"os"
	"os/exec"
	"path"
	"testing"
	"time"
)

const (
	CaPath      = "/usr/share/coreos-ca-certificates/"
	CmdTimeout  = time.Second * 3
	DbusTimeout = time.Second * 3
	HttpTimeout = time.Second * 3
	PortTimeout = time.Second * 3
	UpdateUrl   = "https://api.core-os.net/v1/update/"
)

func TestPortSsh(t *testing.T) {
	t.Parallel()
	err := CheckPort("tcp", "127.0.0.1:22", PortTimeout)
	if err != nil {
		t.Fatal(err)
	}
}

func TestDbusUpdateEngine(t *testing.T) {
	err := CheckDbusInterface("org.chromium.UpdateEngineInterface", DbusTimeout)
	if err != nil {
		t.Fatal(err)
	}
}

func TestDockerEcho(t *testing.T) {
	t.Parallel()
	errc := make(chan error, 1)
	go func() {
		c := exec.Command("docker", "run", "busybox", "echo")
		err := c.Run()
		errc <- err
	}()
	select {
	case <-time.After(CmdTimeout):
		t.Fatalf("DockerEcho timed out after %s.", CmdTimeout)
	case err := <-errc:
		if err != nil {
			t.Error(err)
		}
	}
}

func TestUpdateServiceHttp(t *testing.T) {
	t.Parallel()
	err := CheckHttpStatus("http://api.core-os.net/v1/c10n/group", HttpTimeout)
	if err != nil {
		t.Error(err)
	}
}

func TestSymlinkResolvConf(t *testing.T) {
	t.Parallel()
	f, err := os.Lstat("/etc/resolv.conf")
	if err != nil {
		t.Fatal(err)
	}
	if !IsLink(f) {
		t.Fatal("/etc/resolv.conf is not a symlink.")

	}
}

func TestInstalledCACerts(t *testing.T) {
	t.Parallel()
	caCerts := []string{
		"CoreOS_Internet_Authority.pem",
		"CoreOS_Network_Authority.pem",
	}
	for _, fileName := range caCerts {
		_, err := os.Stat(path.Join(CaPath, fileName))
		if err != nil {
			t.Error(err)
		}
	}
}

func TestInstalledUpdateEngineRsaKeys(t *testing.T) {

}

func TestReadOnlyFs(t *testing.T) {
	mounts, err := GetMountTable()
	if err != nil {
		t.Fatal(err)
	}
	for _, m := range mounts {
		if m.Device == "rootfs" && m.Options[0] == "r" {
			return
		} else {
			t.Fatalf("%s is not mounted ro.", m.MountPoint)
		}
	}
	t.Fatal("could not find rootfs.")
}
<commit_msg>Fixes: update engine and ro fs check<commit_after>package coretest

import (
	"os"
	"os/exec"
	"path"
	"testing"
	"time"
)

const (
	CaPath      = "/usr/share/coreos-ca-certificates/"
	CmdTimeout  = time.Second * 3
	DbusTimeout = time.Second * 20
	HttpTimeout = time.Second * 3
	PortTimeout = time.Second * 3
	UpdateUrl   = "https://api.core-os.net/v1/update/"
)

func TestPortSsh(t *testing.T) {
	t.Parallel()
	err := CheckPort("tcp", "127.0.0.1:22", PortTimeout)
	if err != nil {
		t.Fatal(err)
	}
}

func TestUpdateEngine(t *testing.T) {
	t.Parallel()

	errc := make(chan error, 1)
	go func() {
		c := exec.Command("update_engine_client", "-omaha_url", UpdateUrl)
		err := c.Run()
		errc <- err
	}()

	select {
	case <-time.After(CmdTimeout):
		t.Fatalf("update_engine_client timed out after %s.", CmdTimeout)
	case err := <-errc:
		if err != nil {
			t.Error(err)
		}
	}

	err := CheckDbusInterface("org.chromium.UpdateEngineInterface", DbusTimeout)
	if err != nil {
		t.Fatal(err)
	}
}

func TestDockerEcho(t *testing.T) {
	t.Parallel()
	errc := make(chan error, 1)
	go func() {
		c := exec.Command("docker", "run", "busybox", "echo")
		err := c.Run()
		errc <- err
	}()
	select {
	case <-time.After(CmdTimeout):
		t.Fatalf("DockerEcho timed out after %s.", CmdTimeout)
	case err := <-errc:
		if err != nil {
			t.Error(err)
		}
	}
}

func TestUpdateServiceHttp(t *testing.T) {
	t.Parallel()
	err := CheckHttpStatus("http://api.core-os.net/v1/c10n/group", HttpTimeout)
	if err != nil {
		t.Error(err)
	}
}

func TestSymlinkResolvConf(t *testing.T) {
	t.Parallel()
	f, err := os.Lstat("/etc/resolv.conf")
	if err != nil {
		t.Fatal(err)
	}
	if !IsLink(f) {
		t.Fatal("/etc/resolv.conf is not a symlink.")

	}
}

func TestInstalledCACerts(t *testing.T) {
	t.Parallel()
	caCerts := []string{
		"CoreOS_Internet_Authority.pem",
		"CoreOS_Network_Authority.pem",
	}
	for _, fileName := range caCerts {
		_, err := os.Stat(path.Join(CaPath, fileName))
		if err != nil {
			t.Error(err)
		}
	}
}

func TestInstalledUpdateEngineRsaKeys(t *testing.T) {

}

func TestReadOnlyFs(t *testing.T) {
	mountModes := make(map[string]bool)
	mounts, err := GetMountTable()
	if err != nil {
		t.Fatal(err)
	}
	for _, m := range mounts {
		mountModes[m.MountPoint] = m.Options[0] == "ro"
	}
	if mp, ok := mountModes["/"]; ok {
		if mp {
			return
		} else {
			t.Fatalf("/ is not mounted ro.")
		}
	}
	t.Fatal("could not find rootfs.")
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Uber Technologies, Inc.

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

package jaeger

const (
	// JaegerClientVersion is the version of the client library reported as Span tag.
	JaegerClientVersion = "Go-1.6"

	// JaegerClientVersionTagKey is the name of the tag used to report client version.
	JaegerClientVersionTagKey = "jaeger.version"

	// JaegerDebugHeader is the name of HTTP header or a TextMap carrier key which,
	// if found in the carrier, forces the trace to be sampled as "debug" trace.
	// The value of the header is recorded as the tag on the root span, so that the
	// trace can be found in the UI using this value as a correlation ID.
	JaegerDebugHeader = "jaeger-debug-id"

	// JaegerBaggageHeader is the name of the HTTP header that is used to submit baggage.
	// It differs from TraceBaggageHeaderPrefix in that it can be used only in cases where
	// a root span does not exist.
	JaegerBaggageHeader = "jaeger-baggage"

	// TracerHostnameTagKey used to report host name of the process.
	TracerHostnameTagKey = "jaeger.hostname"

	// SamplerTypeTagKey reports which sampler was used on the root span.
	SamplerTypeTagKey = "sampler.type"

	// SamplerParamTagKey reports the parameter of the sampler, like sampling probability.
	SamplerParamTagKey = "sampler.param"

	// TracerStateHeaderName is the http header name used to propagate tracing context.
	// This must be in lower-case to avoid mismatches when decoding incoming headers.
	TracerStateHeaderName = "uber-trace-id"

	// TraceBaggageHeaderPrefix is the prefix for http headers used to propagate baggage.
	// This must be in lower-case to avoid mismatches when decoding incoming headers.
	TraceBaggageHeaderPrefix = "uberctx-"

	// SamplerTypeConst is the type of sampler that always makes the same decision.
	SamplerTypeConst = "const"

	// SamplerTypeRemote is the type of sampler that polls Jaeger agent for sampling strategy.
	SamplerTypeRemote = "remote"

	// SamplerTypeProbabilistic is the type of sampler that samples traces
	// with a certain fixed probability.
	SamplerTypeProbabilistic = "probabilistic"

	// SamplerTypeRateLimiting is the type of sampler that samples
	// only up to a fixed number of traces per second.
	SamplerTypeRateLimiting = "ratelimiting"

	// SamplerTypeLowerBound is the type of sampler that samples
	// only up to a fixed number of traces per second.
	SamplerTypeLowerBound = "lowerbound"
)
<commit_msg>Update JaegerClientVersion to 2.0.0 (#89)<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.

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

package jaeger

const (
	// JaegerClientVersion is the version of the client library reported as Span tag.
	JaegerClientVersion = "Go-2.0.0"

	// JaegerClientVersionTagKey is the name of the tag used to report client version.
	JaegerClientVersionTagKey = "jaeger.version"

	// JaegerDebugHeader is the name of HTTP header or a TextMap carrier key which,
	// if found in the carrier, forces the trace to be sampled as "debug" trace.
	// The value of the header is recorded as the tag on the root span, so that the
	// trace can be found in the UI using this value as a correlation ID.
	JaegerDebugHeader = "jaeger-debug-id"

	// JaegerBaggageHeader is the name of the HTTP header that is used to submit baggage.
	// It differs from TraceBaggageHeaderPrefix in that it can be used only in cases where
	// a root span does not exist.
	JaegerBaggageHeader = "jaeger-baggage"

	// TracerHostnameTagKey used to report host name of the process.
	TracerHostnameTagKey = "jaeger.hostname"

	// SamplerTypeTagKey reports which sampler was used on the root span.
	SamplerTypeTagKey = "sampler.type"

	// SamplerParamTagKey reports the parameter of the sampler, like sampling probability.
	SamplerParamTagKey = "sampler.param"

	// TracerStateHeaderName is the http header name used to propagate tracing context.
	// This must be in lower-case to avoid mismatches when decoding incoming headers.
	TracerStateHeaderName = "uber-trace-id"

	// TraceBaggageHeaderPrefix is the prefix for http headers used to propagate baggage.
	// This must be in lower-case to avoid mismatches when decoding incoming headers.
	TraceBaggageHeaderPrefix = "uberctx-"

	// SamplerTypeConst is the type of sampler that always makes the same decision.
	SamplerTypeConst = "const"

	// SamplerTypeRemote is the type of sampler that polls Jaeger agent for sampling strategy.
	SamplerTypeRemote = "remote"

	// SamplerTypeProbabilistic is the type of sampler that samples traces
	// with a certain fixed probability.
	SamplerTypeProbabilistic = "probabilistic"

	// SamplerTypeRateLimiting is the type of sampler that samples
	// only up to a fixed number of traces per second.
	SamplerTypeRateLimiting = "ratelimiting"

	// SamplerTypeLowerBound is the type of sampler that samples
	// only up to a fixed number of traces per second.
	SamplerTypeLowerBound = "lowerbound"
)
<|endoftext|>


<commit_before>/****************************************************************************
 * This file is part of Builder.
 *
 * Copyright (C) 2015 Pier Luigi Fiorini
 *
 * Author(s):
 *    Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
 *
 * $BEGIN_LICENSE:AGPL3+$
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $END_LICENSE$
 ***************************************************************************/

package master

import (
	"errors"
	"github.com/hawaii-desktop/builder/src/database"
	"github.com/hawaii-desktop/builder/src/logging"
	pb "github.com/hawaii-desktop/builder/src/protocol"
	"golang.org/x/net/context"
	"io"
	"regexp"
	"sync"
	"sync/atomic"
	"time"
)

// Buffered channel that we can send jobs on.
var BuildJobQueue = make(chan *Job, Config.Build.MaxJobs)

// Master type exposed via RPC
type Master struct {
	// List of slaves.
	Slaves []*Slave
	// List of jobs to be processed.
	Jobs []*Job
	// Protects slave list.
	sMutex sync.Mutex
	// Protects jobs list.
	jMutex sync.Mutex
	// Database.
	db *database.Database
}

// Errors
var (
	ErrAlreadySubscribed  = errors.New("slave has already subscribed")
	ErrSlaveNotFound      = errors.New("slave not found")
	ErrInvalidSlave       = errors.New("slave is not valid")
	ErrJobNotFound        = errors.New("job not found with that id")
	ErrNoMatchingPackages = errors.New("no matching packages")
)

// Map to decode job status.
var jobStatusMap = map[pb.EnumJobStatus]JobStatus{
	pb.EnumJobStatus_JOB_STATUS_JUST_CREATED: JOB_STATUS_JUST_CREATED,
	pb.EnumJobStatus_JOB_STATUS_WAITING:      JOB_STATUS_WAITING,
	pb.EnumJobStatus_JOB_STATUS_PROCESSING:   JOB_STATUS_PROCESSING,
	pb.EnumJobStatus_JOB_STATUS_SUCCESSFUL:   JOB_STATUS_SUCCESSFUL,
	pb.EnumJobStatus_JOB_STATUS_FAILED:       JOB_STATUS_FAILED,
	pb.EnumJobStatus_JOB_STATUS_CRASHED:      JOB_STATUS_CRASHED,
}

// Allocate a new Master with an empty list of slaves.
// The slaves list is initially empty and has a capacity as big as the maximum
// number of slaves from the configuration.
// The jobs list is initially empty and has a capacity as big as
// the maximum number of jobs from the configuration.
// This also create or open the database.
func NewMaster() (*Master, error) {
	db, err := database.NewDatabase(Config.Server.Database)
	if err != nil {
		return nil, err
	}

	m := &Master{}
	m.Slaves = make([]*Slave, 0, Config.Build.MaxSlaves)
	m.Jobs = make([]*Job, 0, Config.Build.MaxJobs)
	m.db = db
	return m, nil
}

// Close the database.
func (m *Master) Close() {
	m.db.Close()
	m.db = nil
}

// Slave call this to initiate a dialog.
func (m *Master) Subscribe(stream pb.Builder_SubscribeServer) error {
	// Function to send back the slave identifier
	var sendSlaveId = func(s *Slave) {
		response := &pb.SubscribeResponse{Id: s.Id}
		reply := &pb.OutputMessage{
			Payload: &pb.OutputMessage_Subscription{
				Subscription: response,
			},
		}
		stream.Send(reply)
	}

	// Function to send a job to a slave
	var sendJob = func(s *Slave, j *Job) {
		response := &pb.JobDispatchRequest{
			Id:     j.Id,
			Target: j.Target,
		}
		reply := &pb.OutputMessage{
			Payload: &pb.OutputMessage_JobDispatch{
				JobDispatch: response,
			},
		}
		stream.Send(reply)
		logging.Infof("Job #%d scheduled on \"%s\"\n", j.Id, s.Name)

	}

	// Slave loop
	var slaveLoop = func(s *Slave) {
		for {
			// Do not queue a slave that suddenly unregisters itself
			if !s.Subscribed || !s.Active {
				return
			}

			// Add to the queue
			s.QueueChannel <- s.JobChannel

			select {
			case j := <-s.JobChannel:
				// Send the job to the slave
				sendJob(s, j)

				// Wait for processing on the other side
				<-j.Channel
			case <-s.QuitChannel:
				// Slave has been asked to stop
				return
			}
		}
	}

	for {
		// Read request from the stream
		in, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}

		subscription := in.GetSubscription()
		if subscription != nil {
			// Register a new slave
			s, err := m.createSlave(subscription)
			if err != nil {
				return err
			}

			// Send back the identifier
			sendSlaveId(s)

			// Start dispatching to this slave
			go slaveLoop(s)
		}

		jobUpdate := in.GetJobUpdate()
		if jobUpdate != nil {
			var slave *Slave = nil
			m.sMutex.Lock()
			for _, s := range m.Slaves {
				if s.Id == jobUpdate.SlaveId {
					slave = s
					break
				}
			}
			m.sMutex.Unlock()

			m.jMutex.Lock()
			var j *Job = nil
			for _, curJob := range m.Jobs {
				// Skip other jobs
				if curJob.Id == jobUpdate.Id {
					j = curJob
				}
			}
			if j == nil {
				logging.Errorf("Cannot find job #%d\n", jobUpdate.Id)
				return ErrJobNotFound
			}
			m.jMutex.Unlock()

			// Update the status
			j.Status = jobStatusMap[jobUpdate.Status]

			// Handle finished jobs
			if j.Status >= JOB_STATUS_SUCCESSFUL && j.Status <= JOB_STATUS_CRASHED {
				// Update finished time and notify
				j.Finished = time.Now()

				// Log the status
				if j.Status == JOB_STATUS_SUCCESSFUL {
					logging.Infof("Job #%d completed successfully on \"%s\"\n",
						j.Id, slave.Name)
				} else {
					logging.Errorf("Job #%d failed on \"%s\"\n",
						j.Id, slave.Name)
				}

				// TODO: Save on the database

				// Remove from the list
				m.removeJob(j)

				// Proceed to the next job
				j.Channel <- true
			} else {
				logging.Tracef("Change job #%d status to \"%s\"\n",
					jobUpdate.Id, jobStatusDescriptionMap[j.Status])
			}
		}
	}
}

// Unregister a slave and stop it immediately.
// Jobs will not be dispatched to this slave until it has subscribed again.
func (m *Master) Unsubscribe(ctx context.Context, args *pb.UnsubscribeRequest) (*pb.UnsubscribeResponse, error) {
	// Prepare response
	reply := &pb.UnsubscribeResponse{Result: false}

	// Find slave and unregister
	m.sMutex.Lock()
	defer m.sMutex.Unlock()
	for _, slave := range m.Slaves {
		if slave == nil {
			continue
		}
		if slave.Id == args.Id {
			logging.Infof("Slave \"%s\" unsubscribed", slave.Name)
			slave.Subscribed = false
			slave.Stop()
			reply.Result = true
			return reply, nil
		}
	}

	// Reply
	reply.Result = false
	return reply, ErrSlaveNotFound
}

// Create and enqueue a job.
func (m *Master) CollectJob(ctx context.Context, args *pb.CollectJobRequest) (*pb.CollectJobResponse, error) {
	j := m.enqueueJob(args.Target)
	reply := &pb.CollectJobResponse{Result: true, Id: j.Id}
	return reply, nil
}

// Add or update a package.
func (m *Master) AddPackage(ctx context.Context, args *pb.PackageInfo) (*pb.BooleanMessage, error) {
	pkg := &database.Package{
		Name:          args.Name,
		Architectures: args.Architectures,
		Ci:            args.Ci,
		Vcs: database.VcsInfo{
			Url:    args.Vcs.Url,
			Branch: args.Vcs.Branch,
		},
		UpstreamVcs: database.VcsInfo{
			Url:    args.UpstreamVcs.Url,
			Branch: args.UpstreamVcs.Branch,
		},
	}
	if err := m.db.AddPackage(pkg); err != nil {
		return nil, err
	}
	return &pb.BooleanMessage{Result: true}, nil
}

// Remove package.
func (m *Master) RemovePackage(ctx context.Context, args *pb.StringMessage) (*pb.BooleanMessage, error) {
	err := m.db.RemovePackage(args.Name)
	if err != nil {
		return nil, err
	}
	return &pb.BooleanMessage{Result: true}, nil
}

// List packages matching the regular expression.
func (m *Master) ListPackages(args *pb.StringMessage, stream pb.Builder_ListPackagesServer) error {
	r, err := regexp.Compile(args.Name)
	if err != nil {
		return err
	}

	list := m.db.ListAllPackages()
	if len(list) == 0 {
		return ErrNoMatchingPackages
	}

	for _, pkg := range list {
		if !r.MatchString(pkg.Name) {
			continue
		}
		reply := &pb.PackageInfo{
			Name:          pkg.Name,
			Architectures: pkg.Architectures,
			Ci:            pkg.Ci,
			Vcs: &pb.VcsInfo{
				Url:    pkg.Vcs.Url,
				Branch: pkg.Vcs.Branch,
			},
			UpstreamVcs: &pb.VcsInfo{
				Url:    pkg.UpstreamVcs.Url,
				Branch: pkg.UpstreamVcs.Branch,
			},
		}
		stream.Send(reply)
	}

	return nil
}

// Create a slave from the subscription request.
func (m *Master) createSlave(args *pb.SubscribeRequest) (*Slave, error) {
	// The same slave cannot subscribe twice
	m.sMutex.Lock()
	defer m.sMutex.Unlock()
	for _, slave := range m.Slaves {
		if slave == nil {
			continue
		}
		if slave.Name == args.Name && slave.Subscribed {
			return nil, ErrAlreadySubscribed
		}
	}

	// Create and append slave
	slave := NewSlave(args.Name, args.Channels, args.Architectures)
	m.Slaves = append(m.Slaves, slave)
	logging.Infof("Subscribed slave \"%s\" with id %d\n", slave.Name, slave.Id)
	return slave, nil
}

// Append a job to the list.
func (m *Master) appendJob(r *Job) {
	m.jMutex.Lock()
	m.Jobs = append(m.Jobs, r)
	m.jMutex.Unlock()
}

// Remove a job from the list.
func (m *Master) removeJob(j *Job) {
	m.jMutex.Lock()
	defer m.jMutex.Unlock()

	// Find index
	i := -1
	for k, v := range m.Jobs {
		if v == j {
			i = k
			break
		}
	}
	if i == -1 {
		logging.Warningf("Unable to remove job #%d from the list\n", j.Id)
		return
	}

	// Remove
	m.Jobs, m.Jobs[len(m.Jobs)-1] =
		append(m.Jobs[:i], m.Jobs[i+1:]...), nil
}

// Enqueue a job.
func (m *Master) enqueueJob(target string) *Job {
	// Allocate a new global id
	id := atomic.AddUint64(&globalJobId, 1)

	// Create a job
	j := &Job{
		Id:       id,
		Target:   target,
		Started:  time.Time{},
		Finished: time.Time{},
		Status:   JOB_STATUS_JUST_CREATED,
		Channel:  make(chan bool),
	}
	m.appendJob(j)

	// Push it onto the queue
	BuildJobQueue <- j
	logging.Infof("Queued job #%d (target \"%s\")\n", id, target)
	return j
}
<commit_msg>Remove empty line<commit_after>/****************************************************************************
 * This file is part of Builder.
 *
 * Copyright (C) 2015 Pier Luigi Fiorini
 *
 * Author(s):
 *    Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
 *
 * $BEGIN_LICENSE:AGPL3+$
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $END_LICENSE$
 ***************************************************************************/

package master

import (
	"errors"
	"github.com/hawaii-desktop/builder/src/database"
	"github.com/hawaii-desktop/builder/src/logging"
	pb "github.com/hawaii-desktop/builder/src/protocol"
	"golang.org/x/net/context"
	"io"
	"regexp"
	"sync"
	"sync/atomic"
	"time"
)

// Buffered channel that we can send jobs on.
var BuildJobQueue = make(chan *Job, Config.Build.MaxJobs)

// Master type exposed via RPC
type Master struct {
	// List of slaves.
	Slaves []*Slave
	// List of jobs to be processed.
	Jobs []*Job
	// Protects slave list.
	sMutex sync.Mutex
	// Protects jobs list.
	jMutex sync.Mutex
	// Database.
	db *database.Database
}

// Errors
var (
	ErrAlreadySubscribed  = errors.New("slave has already subscribed")
	ErrSlaveNotFound      = errors.New("slave not found")
	ErrInvalidSlave       = errors.New("slave is not valid")
	ErrJobNotFound        = errors.New("job not found with that id")
	ErrNoMatchingPackages = errors.New("no matching packages")
)

// Map to decode job status.
var jobStatusMap = map[pb.EnumJobStatus]JobStatus{
	pb.EnumJobStatus_JOB_STATUS_JUST_CREATED: JOB_STATUS_JUST_CREATED,
	pb.EnumJobStatus_JOB_STATUS_WAITING:      JOB_STATUS_WAITING,
	pb.EnumJobStatus_JOB_STATUS_PROCESSING:   JOB_STATUS_PROCESSING,
	pb.EnumJobStatus_JOB_STATUS_SUCCESSFUL:   JOB_STATUS_SUCCESSFUL,
	pb.EnumJobStatus_JOB_STATUS_FAILED:       JOB_STATUS_FAILED,
	pb.EnumJobStatus_JOB_STATUS_CRASHED:      JOB_STATUS_CRASHED,
}

// Allocate a new Master with an empty list of slaves.
// The slaves list is initially empty and has a capacity as big as the maximum
// number of slaves from the configuration.
// The jobs list is initially empty and has a capacity as big as
// the maximum number of jobs from the configuration.
// This also create or open the database.
func NewMaster() (*Master, error) {
	db, err := database.NewDatabase(Config.Server.Database)
	if err != nil {
		return nil, err
	}

	m := &Master{}
	m.Slaves = make([]*Slave, 0, Config.Build.MaxSlaves)
	m.Jobs = make([]*Job, 0, Config.Build.MaxJobs)
	m.db = db
	return m, nil
}

// Close the database.
func (m *Master) Close() {
	m.db.Close()
	m.db = nil
}

// Slave call this to initiate a dialog.
func (m *Master) Subscribe(stream pb.Builder_SubscribeServer) error {
	// Function to send back the slave identifier
	var sendSlaveId = func(s *Slave) {
		response := &pb.SubscribeResponse{Id: s.Id}
		reply := &pb.OutputMessage{
			Payload: &pb.OutputMessage_Subscription{
				Subscription: response,
			},
		}
		stream.Send(reply)
	}

	// Function to send a job to a slave
	var sendJob = func(s *Slave, j *Job) {
		response := &pb.JobDispatchRequest{
			Id:     j.Id,
			Target: j.Target,
		}
		reply := &pb.OutputMessage{
			Payload: &pb.OutputMessage_JobDispatch{
				JobDispatch: response,
			},
		}
		stream.Send(reply)
		logging.Infof("Job #%d scheduled on \"%s\"\n", j.Id, s.Name)
	}

	// Slave loop
	var slaveLoop = func(s *Slave) {
		for {
			// Do not queue a slave that suddenly unregisters itself
			if !s.Subscribed || !s.Active {
				return
			}

			// Add to the queue
			s.QueueChannel <- s.JobChannel

			select {
			case j := <-s.JobChannel:
				// Send the job to the slave
				sendJob(s, j)

				// Wait for processing on the other side
				<-j.Channel
			case <-s.QuitChannel:
				// Slave has been asked to stop
				return
			}
		}
	}

	for {
		// Read request from the stream
		in, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}

		subscription := in.GetSubscription()
		if subscription != nil {
			// Register a new slave
			s, err := m.createSlave(subscription)
			if err != nil {
				return err
			}

			// Send back the identifier
			sendSlaveId(s)

			// Start dispatching to this slave
			go slaveLoop(s)
		}

		jobUpdate := in.GetJobUpdate()
		if jobUpdate != nil {
			var slave *Slave = nil
			m.sMutex.Lock()
			for _, s := range m.Slaves {
				if s.Id == jobUpdate.SlaveId {
					slave = s
					break
				}
			}
			m.sMutex.Unlock()

			m.jMutex.Lock()
			var j *Job = nil
			for _, curJob := range m.Jobs {
				// Skip other jobs
				if curJob.Id == jobUpdate.Id {
					j = curJob
				}
			}
			if j == nil {
				logging.Errorf("Cannot find job #%d\n", jobUpdate.Id)
				return ErrJobNotFound
			}
			m.jMutex.Unlock()

			// Update the status
			j.Status = jobStatusMap[jobUpdate.Status]

			// Handle finished jobs
			if j.Status >= JOB_STATUS_SUCCESSFUL && j.Status <= JOB_STATUS_CRASHED {
				// Update finished time and notify
				j.Finished = time.Now()

				// Log the status
				if j.Status == JOB_STATUS_SUCCESSFUL {
					logging.Infof("Job #%d completed successfully on \"%s\"\n",
						j.Id, slave.Name)
				} else {
					logging.Errorf("Job #%d failed on \"%s\"\n",
						j.Id, slave.Name)
				}

				// TODO: Save on the database

				// Remove from the list
				m.removeJob(j)

				// Proceed to the next job
				j.Channel <- true
			} else {
				logging.Tracef("Change job #%d status to \"%s\"\n",
					jobUpdate.Id, jobStatusDescriptionMap[j.Status])
			}
		}
	}
}

// Unregister a slave and stop it immediately.
// Jobs will not be dispatched to this slave until it has subscribed again.
func (m *Master) Unsubscribe(ctx context.Context, args *pb.UnsubscribeRequest) (*pb.UnsubscribeResponse, error) {
	// Prepare response
	reply := &pb.UnsubscribeResponse{Result: false}

	// Find slave and unregister
	m.sMutex.Lock()
	defer m.sMutex.Unlock()
	for _, slave := range m.Slaves {
		if slave == nil {
			continue
		}
		if slave.Id == args.Id {
			logging.Infof("Slave \"%s\" unsubscribed", slave.Name)
			slave.Subscribed = false
			slave.Stop()
			reply.Result = true
			return reply, nil
		}
	}

	// Reply
	reply.Result = false
	return reply, ErrSlaveNotFound
}

// Create and enqueue a job.
func (m *Master) CollectJob(ctx context.Context, args *pb.CollectJobRequest) (*pb.CollectJobResponse, error) {
	j := m.enqueueJob(args.Target)
	reply := &pb.CollectJobResponse{Result: true, Id: j.Id}
	return reply, nil
}

// Add or update a package.
func (m *Master) AddPackage(ctx context.Context, args *pb.PackageInfo) (*pb.BooleanMessage, error) {
	pkg := &database.Package{
		Name:          args.Name,
		Architectures: args.Architectures,
		Ci:            args.Ci,
		Vcs: database.VcsInfo{
			Url:    args.Vcs.Url,
			Branch: args.Vcs.Branch,
		},
		UpstreamVcs: database.VcsInfo{
			Url:    args.UpstreamVcs.Url,
			Branch: args.UpstreamVcs.Branch,
		},
	}
	if err := m.db.AddPackage(pkg); err != nil {
		return nil, err
	}
	return &pb.BooleanMessage{Result: true}, nil
}

// Remove package.
func (m *Master) RemovePackage(ctx context.Context, args *pb.StringMessage) (*pb.BooleanMessage, error) {
	err := m.db.RemovePackage(args.Name)
	if err != nil {
		return nil, err
	}
	return &pb.BooleanMessage{Result: true}, nil
}

// List packages matching the regular expression.
func (m *Master) ListPackages(args *pb.StringMessage, stream pb.Builder_ListPackagesServer) error {
	r, err := regexp.Compile(args.Name)
	if err != nil {
		return err
	}

	list := m.db.ListAllPackages()
	if len(list) == 0 {
		return ErrNoMatchingPackages
	}

	for _, pkg := range list {
		if !r.MatchString(pkg.Name) {
			continue
		}
		reply := &pb.PackageInfo{
			Name:          pkg.Name,
			Architectures: pkg.Architectures,
			Ci:            pkg.Ci,
			Vcs: &pb.VcsInfo{
				Url:    pkg.Vcs.Url,
				Branch: pkg.Vcs.Branch,
			},
			UpstreamVcs: &pb.VcsInfo{
				Url:    pkg.UpstreamVcs.Url,
				Branch: pkg.UpstreamVcs.Branch,
			},
		}
		stream.Send(reply)
	}

	return nil
}

// Create a slave from the subscription request.
func (m *Master) createSlave(args *pb.SubscribeRequest) (*Slave, error) {
	// The same slave cannot subscribe twice
	m.sMutex.Lock()
	defer m.sMutex.Unlock()
	for _, slave := range m.Slaves {
		if slave == nil {
			continue
		}
		if slave.Name == args.Name && slave.Subscribed {
			return nil, ErrAlreadySubscribed
		}
	}

	// Create and append slave
	slave := NewSlave(args.Name, args.Channels, args.Architectures)
	m.Slaves = append(m.Slaves, slave)
	logging.Infof("Subscribed slave \"%s\" with id %d\n", slave.Name, slave.Id)
	return slave, nil
}

// Append a job to the list.
func (m *Master) appendJob(r *Job) {
	m.jMutex.Lock()
	m.Jobs = append(m.Jobs, r)
	m.jMutex.Unlock()
}

// Remove a job from the list.
func (m *Master) removeJob(j *Job) {
	m.jMutex.Lock()
	defer m.jMutex.Unlock()

	// Find index
	i := -1
	for k, v := range m.Jobs {
		if v == j {
			i = k
			break
		}
	}
	if i == -1 {
		logging.Warningf("Unable to remove job #%d from the list\n", j.Id)
		return
	}

	// Remove
	m.Jobs, m.Jobs[len(m.Jobs)-1] =
		append(m.Jobs[:i], m.Jobs[i+1:]...), nil
}

// Enqueue a job.
func (m *Master) enqueueJob(target string) *Job {
	// Allocate a new global id
	id := atomic.AddUint64(&globalJobId, 1)

	// Create a job
	j := &Job{
		Id:       id,
		Target:   target,
		Started:  time.Time{},
		Finished: time.Time{},
		Status:   JOB_STATUS_JUST_CREATED,
		Channel:  make(chan bool),
	}
	m.appendJob(j)

	// Push it onto the queue
	BuildJobQueue <- j
	logging.Infof("Queued job #%d (target \"%s\")\n", id, target)
	return j
}
<|endoftext|>


<commit_before>package matrix

import (
	"color"
	"fmt"
)

const SIDE = 8

type Matrix [SIDE][SIDE]byte

var starting = Matrix{
	{'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'},
	{'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'},
	{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
	{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
	{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
	{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
	{'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'},
	{'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'},
}

func Starting() Matrix {
	return starting
}

func InMatrix(p Point) bool {
	return 0 <= p.X && p.X < SIDE && 0 <= p.Y && p.Y < SIDE
}

func (mat Matrix) ExistBarrier(from, to Point) bool {
	p := from
	p.StepTo(to)
	for p != to {
		if mat[p.Y][p.X] != ' ' {
			return true
		}
		p.StepTo(to)
	}
	return false
}

func (mat Matrix) ToString() string {
	matBytes := make([]byte, (SIDE*2+3)*(SIDE*2+4))
	matBytes = append(matBytes, " "...)
	//matBytes := " "
	for i := 0; i < SIDE; i++ {
		matBytes = append(matBytes, fmt.Sprintf(" %c", 'a'+i)...)
	}
	matBytes = append(matBytes, "\n"...)

	for i := 0; i < SIDE; i++ {
		matBytes = append(matBytes, " +"...)
		for i := 0; i < SIDE; i++ {
			matBytes = append(matBytes, "-+"...)
		}
		matBytes = append(matBytes, "\n"...)
		matBytes = append(matBytes, fmt.Sprintf("%d|", SIDE-i)...)
		for j := 0; j < SIDE; j++ {
			matBytes = append(matBytes, fmt.Sprintf("%c|", mat[i][j])...)
		}
		matBytes = append(matBytes, fmt.Sprintf("%d\n", SIDE-i)...)
	}
	matBytes = append(matBytes, " +"...)
	for i := 0; i < SIDE; i++ {
		matBytes = append(matBytes, "-+"...)
	}
	matBytes = append(matBytes, "\n"...)

	matBytes = append(matBytes, " "...)
	for i := 0; i < SIDE; i++ {
		matBytes = append(matBytes, fmt.Sprintf(" %c", 'a'+i)...)
	}

	return string(matBytes)
}

func (mat Matrix) Positions(c color.Color) []Point {
	positions := make([]Point, 0)
	for i := 0; i < SIDE; i++ {
		for j := 0; j < SIDE; j++ {
			if color.WhichColor(mat[i][j]) == c {
				positions = append(positions, Point{i, j})
			}
		}
	}

	return positions
}
<commit_msg>Set default capacity of positions as number of cells<commit_after>package matrix

import (
	"color"
	"fmt"
)

const SIDE = 8

type Matrix [SIDE][SIDE]byte

var starting = Matrix{
	{'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'},
	{'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'},
	{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
	{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
	{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
	{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
	{'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'},
	{'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'},
}

func Starting() Matrix {
	return starting
}

func InMatrix(p Point) bool {
	return 0 <= p.X && p.X < SIDE && 0 <= p.Y && p.Y < SIDE
}

func (mat Matrix) ExistBarrier(from, to Point) bool {
	p := from
	p.StepTo(to)
	for p != to {
		if mat[p.Y][p.X] != ' ' {
			return true
		}
		p.StepTo(to)
	}
	return false
}

func (mat Matrix) ToString() string {
	matBytes := make([]byte, (SIDE*2+3)*(SIDE*2+4))
	matBytes = append(matBytes, " "...)
	//matBytes := " "
	for i := 0; i < SIDE; i++ {
		matBytes = append(matBytes, fmt.Sprintf(" %c", 'a'+i)...)
	}
	matBytes = append(matBytes, "\n"...)

	for i := 0; i < SIDE; i++ {
		matBytes = append(matBytes, " +"...)
		for i := 0; i < SIDE; i++ {
			matBytes = append(matBytes, "-+"...)
		}
		matBytes = append(matBytes, "\n"...)
		matBytes = append(matBytes, fmt.Sprintf("%d|", SIDE-i)...)
		for j := 0; j < SIDE; j++ {
			matBytes = append(matBytes, fmt.Sprintf("%c|", mat[i][j])...)
		}
		matBytes = append(matBytes, fmt.Sprintf("%d\n", SIDE-i)...)
	}
	matBytes = append(matBytes, " +"...)
	for i := 0; i < SIDE; i++ {
		matBytes = append(matBytes, "-+"...)
	}
	matBytes = append(matBytes, "\n"...)

	matBytes = append(matBytes, " "...)
	for i := 0; i < SIDE; i++ {
		matBytes = append(matBytes, fmt.Sprintf(" %c", 'a'+i)...)
	}

	return string(matBytes)
}

func (mat Matrix) Positions(c color.Color) []Point {
	positions := make([]Point, SIDE*SIDE)
	for i := 0; i < SIDE; i++ {
		for j := 0; j < SIDE; j++ {
			if color.WhichColor(mat[i][j]) == c {
				positions = append(positions, Point{i, j})
			}
		}
	}

	return positions
}
<|endoftext|>