<commit_before>package main

import (
	"fmt"
	"os"
	"reflect"
	"testing"
	"time"

	"github.com/UserStack/ustackd/backends"
	"github.com/UserStack/ustackd/client"
	"github.com/UserStack/ustackd/server"
)

var serverInstance *server.Server
var started bool

func newClient() *client.Client {
	if started == false {
		serverInstance = server.NewServer()
		var configPath string
		if configPath = os.Getenv("TEST_CONFIG"); configPath == "" {
			configPath = "config/test.conf"
		}
		go (func() {
			serverInstance.Run([]string{
				"./ustackd", "-f", "-c", configPath,
			})
		})()
		started = true
		time.Sleep(1 * time.Second)
	}
	client, err := client.Dial("localhost:35786")
	if err != nil {
		panic("client was unable to connect to server: " + err.Error())
	}
	return client
}

func uniqName() string {
	return fmt.Sprintf("test-%d", time.Now().UnixNano())
}

func TestConnect(t *testing.T) {
	client := newClient()
	defer client.Close()
}

func TestConnectTls(t *testing.T) {
	client := newClient()
	defer client.Close()
	aerr := client.StartTlsWithCert("config/cert.pem")
	if aerr != nil {
		t.Fatal("unable to establish tls", aerr)
	}
	username := uniqName()
	defer client.DeleteUser(username)
	id, serr := client.CreateUser(username, "secret")
	if id <= 0 {
		t.Fatal("user not created, expected id bigger than 0 got", id, serr)
	}
}

func TestCreateUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	id, err := client.CreateUser(username, "secret")
	if id <= 0 {
		t.Fatal("user not created, expected id bigger than 0 got", id, err)
	}
}

func TestDisableUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	client.CreateUser(username, "secret")
	err := client.DisableUser(username)
	if err != nil {
		t.Fatal("unable to disable user", err)
	}
}

func TestEnableUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	client.CreateUser(username, "secret")
	err := client.EnableUser(username)
	if err != nil {
		t.Fatal("unable to enable user", err)
	}
}

func TestSetGetUserData(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	client.CreateUser(username, "secret")
	client.SetUserData(username, "firstname", "Tester")
	firstname, err := client.GetUserData(username, "firstname")
	if firstname != "Tester" {
		t.Fatal("firstname should have been 'Tester' but was", firstname, err)
	}
}

func TestLoginUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	client.CreateUser(username, "secret")
	_, lerr := client.LoginUser(username, "secret")
	if lerr != nil {
		t.Fatal("should be able to login", lerr)
	}
	client.DisableUser(username)
	_, lerr1 := client.LoginUser(username, "secret")
	if lerr1 == nil {
		t.Fatal("should not be able to login, but was able -> not disabled")
	}
	client.EnableUser(username)
	_, lerr2 := client.LoginUser(username, "secret")
	if lerr2 != nil {
		t.Fatal("should be able to login again", lerr2)
	}
}

func TestChangeUserPassword(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()

	ierr1 := client.ChangeUserPassword("", "secret2", "secret2")
	if ierr1.Code != "EINVAL" {
		t.Fatal("should fail since user value is empty")
	}

	ierr2 := client.ChangeUserPassword(username, "", "secret2")
	if ierr2.Code != "EINVAL" {
		t.Fatal("should fail since passwd value is empty")
	}

	ierr3 := client.ChangeUserPassword(username, "secret2", "")
	if ierr3.Code != "EINVAL" {
		t.Fatal("should fail since new passwd value is empty")
	}

	client.CreateUser(username, "secret")
	client.LoginUser(username, "secret")
	serr := client.ChangeUserPassword(username, "secret2", "secret2")
	// fails if password is wrong
	if serr.Code != "ENOENT" {
		t.Fatal("Should have failed to change with wrong password")
	}
	client.ChangeUserPassword(username, "secret", "secret2")
	_, err := client.LoginUser(username, "secret2")
	if err != nil {
		t.Fatalf("User passwd should have been changed: %s, (%s)", err.Code, err.Message)
	}
}

func TestChangeUserName(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	newusername := uniqName()

	ierr1 := client.ChangeUserName("", "secret2", newusername)
	if ierr1.Code != "EINVAL" {
		t.Fatal("should fail since name value is empty")
	}

	ierr2 := client.ChangeUserName(username, "", newusername)
	if ierr2.Code != "EINVAL" {
		t.Fatal("should fail since passwd value is empty")
	}

	ierr3 := client.ChangeUserName(username, "secret2", "")
	if ierr3.Code != "EINVAL" {
		t.Fatal("should fail since new name value is empty")
	}

	client.CreateUser(username, "secret")
	client.LoginUser(username, "secret")
	serr := client.ChangeUserName(username, "secret2", newusername)
	if serr.Code != "ENOENT" {
		t.Fatal("Should have failed to change with wrong password")
	}
	client.ChangeUserName(username, "secret", newusername)
	_, err := client.LoginUser(newusername, "secret")
	if err != nil {
		t.Fatalf("User name should have been changed: %s, (%s)", err.Code, err.Message)
	}
}

func TestDeleteUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	client.CreateUser(username, "secret")
	_, lerr := client.LoginUser(username, "secret")
	if lerr != nil {
		t.Fatal("should be able to login", lerr)
	}
	derr := client.DeleteUser(username)
	if derr != nil {
		t.Fatal("unable to delete the user", derr.Code)
	}
	_, lerr1 := client.LoginUser(username, "secret")
	if lerr1 == nil {
		t.Fatal("should not be able to login, but was able -> not deleted")
	}
}

func TestUsers(t *testing.T) {
	client := newClient()
	defer client.Close()
	username0 := uniqName()
	uid0, _ := client.CreateUser(username0, "secret")
	username1 := uniqName()
	uid1, _ := client.CreateUser(username1, "secret")
	users, _ := client.Users()
	if len(users) < 2 {
		t.Fatal("should have at least two users")
	}
	found0, found1 := false, false
	for _, user := range users {
		if user.Name == username0 && user.Uid == uid0 {
			found0 = true
		} else if user.Name == username1 && user.Uid == uid1 {
			found1 = true
		}
	}
	if found0 && found1 != true {
		t.Fatalf("should have found created users %s and %s in %v",
			username0, username1, users)
	}
}

func TestGroup(t *testing.T) {
	client := newClient()
	defer client.Close()
	group := uniqName()

	_, err := client.CreateGroup("")
	if err.Code != "EFAULT" {
		t.Fatal("should return EINVAL instead of", err.Code)
	}

	gid, cerr := client.CreateGroup(group)
	defer client.DeleteGroup(group)
	if gid <= 0 {
		t.Fatal("should have created group", cerr.Code, cerr.Message)
	}

	_, eerr := client.CreateGroup(group)
	if eerr.Code != "EEXIST" {
		t.Fatal("should return EEXIST instead of", eerr.Code, eerr.Message)
	}
}

func TestDeleteGroup(t *testing.T) {
	client := newClient()
	defer client.Close()

	group0 := uniqName()
	group1 := uniqName()

	err := client.DeleteUser("")
	if err.Code != "EFAULT" {
		t.Fatal("should error on missing parameter", err.Code)
	}

	// create two groups
	client.CreateGroup(group0)
	gid, _ := client.CreateGroup(group1)
	defer client.DeleteGroup(group1)

	// now has two users
	groups, _ := client.Groups()
	if len(groups) != 2 {
		t.Fatal("user count should have been 2 but was", len(groups))
	}

	// delete one using uid one using name
	derr1 := client.DeleteGroup(group0)
	if derr1 != nil {
		t.Fatal("should not error on delete", derr1.Code, derr1.Message)
	}
	derr1 = client.DeleteGroup(fmt.Sprintf("%d", gid))
	if derr1 != nil {
		t.Fatal("should not error on delete", derr1.Code, derr1.Message)
	}
	groups, _ = client.Groups()
	if len(groups) != 0 {
		t.Fatal("group count should have been 0 but was", len(groups))
	}
}

func TestGroups(t *testing.T) {
	client := newClient()
	defer client.Close()

	group0 := uniqName()
	group1 := uniqName()
	group2 := uniqName()

	client.CreateGroup(group0)
	defer client.DeleteGroup(group0)
	client.CreateGroup(group1)
	defer client.DeleteGroup(group1)
	client.CreateGroup(group2)
	defer client.DeleteGroup(group2)
	groups, _ := client.Groups()

	if len(groups) < 3 {
		t.Fatalf("expected to have at least 3 groups but got %v", groups)
	}
}

func TestUsersAndGroupsAssociations(t *testing.T) {
	client := newClient()
	defer client.Close()

	joe := uniqName()
	mike := uniqName()
	developers := uniqName()
	admins := uniqName()
	gid0, _ := client.CreateGroup(developers)
	defer client.DeleteGroup(developers)
	gid1, _ := client.CreateGroup(admins)
	defer client.DeleteGroup(admins)
	uid0, _ := client.CreateUser(joe, "secret")
	defer client.DeleteUser(joe)
	uid1, _ := client.CreateUser(mike, "secret")
	defer client.DeleteUser(mike)

	gid0s := fmt.Sprintf("%d", gid0)
	uid0s := fmt.Sprintf("%d", uid0)

	// User Groups
	groups, _ := client.UserGroups(joe)
	if len(groups) != 0 {
		t.Fatal("expected joe not to have any groups")
	}
	client.AddUserToGroup(joe, gid0s)
	client.AddUserToGroup(uid0s, admins)
	expectedGroups := []backends.Group{
		backends.Group{Gid: gid0, Name: developers},
		backends.Group{Gid: gid1, Name: admins},
	}
	groups, _ = client.UserGroups(joe)
	if !reflect.DeepEqual(expectedGroups, groups) {
		t.Fatalf("expected joe have groups %v but has %v", expectedGroups, groups)
	}

	// Group Users
	users, _ := client.GroupUsers(admins)
	if len(users) < 1 {
		t.Fatalf("expected to have one admin, got %v", users)
	}
	client.AddUserToGroup(mike, admins)
	users, _ = client.GroupUsers(admins)
	expectedUsers := []backends.User{
		backends.User{Uid: uid0, Name: joe},
		backends.User{Uid: uid1, Name: mike},
	}
	if reflect.DeepEqual(expectedUsers, users) {
		t.Fatalf("expected admins have users %v but has %v", expectedUsers, users)
	}

	// Remove associations
	client.RemoveUserFromGroup(mike, admins)
	client.RemoveUserFromGroup(joe, admins)
	adminsG, _ := client.GroupUsers(admins)
	if len(adminsG) != 0 {
		t.Fatalf("expected to have no admin, got %v", adminsG)
	}
	users, _ = client.GroupUsers(admins)
	if len(users) != 0 {
		t.Fatalf("expected to have no admin, got %v", users)
	}
}

func TestStats(t *testing.T) {
	client := newClient()
	defer client.Close()
	serverInstance.Stats.Reset()

	username := uniqName()
	client.CreateUser(username, "secret")
	defer client.DeleteUser(username)
	client.LoginUser(username, "secret") // Successfull login
	client.LoginUser("foobar", "123456") // Failed login
	client.LoginUser("foobar", "123456") // twice
	tempClient := newClient()            // Connects++

	stats, _ := client.Stats()
	expected := map[string]int{
		"Connects":                             1,
		"Disconnects":                          0,
		"Active Connections":                   1,
		"Successfull logins":                   1,
		"Failed logins":                        2,
		"Unrestricted Commands":                0,
		"Restricted Commands":                  4,
		"Access denied on Restricted Commands": 0,
		"Users": 8,
	}
	if !reflect.DeepEqual(stats, expected) {
		t.Fatalf("expected %v to be %v", stats, expected)
	}

	tempClient.Close() // Disconnects++

	stats, _ = client.Stats()
	expected = map[string]int{
		"Connects":                             1,
		"Disconnects":                          1,
		"Active Connections":                   0,
		"Successfull logins":                   1,
		"Failed logins":                        2,
		"Unrestricted Commands":                1,
		"Restricted Commands":                  5,
		"Access denied on Restricted Commands": 0,
		"Users": 8,
	}
	if !reflect.DeepEqual(stats, expected) {
		t.Fatalf("expected %v to be %v", stats, expected)
	}
}
<commit_msg>make the stats test more resilient<commit_after>package main

import (
	"fmt"
	"os"
	"reflect"
	"testing"
	"time"

	"github.com/UserStack/ustackd/backends"
	"github.com/UserStack/ustackd/client"
	"github.com/UserStack/ustackd/server"
)

var serverInstance *server.Server
var started bool

func newClient() *client.Client {
	if started == false {
		serverInstance = server.NewServer()
		var configPath string
		if configPath = os.Getenv("TEST_CONFIG"); configPath == "" {
			configPath = "config/test.conf"
		}
		go (func() {
			serverInstance.Run([]string{
				"./ustackd", "-f", "-c", configPath,
			})
		})()
		started = true
		time.Sleep(1 * time.Second)
	}
	client, err := client.Dial("localhost:35786")
	if err != nil {
		panic("client was unable to connect to server: " + err.Error())
	}
	return client
}

func uniqName() string {
	return fmt.Sprintf("test-%d", time.Now().UnixNano())
}

func TestConnect(t *testing.T) {
	client := newClient()
	defer client.Close()
}

func TestConnectTls(t *testing.T) {
	client := newClient()
	defer client.Close()
	aerr := client.StartTlsWithCert("config/cert.pem")
	if aerr != nil {
		t.Fatal("unable to establish tls", aerr)
	}
	username := uniqName()
	defer client.DeleteUser(username)
	id, serr := client.CreateUser(username, "secret")
	if id <= 0 {
		t.Fatal("user not created, expected id bigger than 0 got", id, serr)
	}
}

func TestCreateUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	id, err := client.CreateUser(username, "secret")
	if id <= 0 {
		t.Fatal("user not created, expected id bigger than 0 got", id, err)
	}
}

func TestDisableUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	client.CreateUser(username, "secret")
	err := client.DisableUser(username)
	if err != nil {
		t.Fatal("unable to disable user", err)
	}
}

func TestEnableUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	client.CreateUser(username, "secret")
	err := client.EnableUser(username)
	if err != nil {
		t.Fatal("unable to enable user", err)
	}
}

func TestSetGetUserData(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	client.CreateUser(username, "secret")
	client.SetUserData(username, "firstname", "Tester")
	firstname, err := client.GetUserData(username, "firstname")
	if firstname != "Tester" {
		t.Fatal("firstname should have been 'Tester' but was", firstname, err)
	}
}

func TestLoginUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	defer client.DeleteUser(username)
	client.CreateUser(username, "secret")
	_, lerr := client.LoginUser(username, "secret")
	if lerr != nil {
		t.Fatal("should be able to login", lerr)
	}
	client.DisableUser(username)
	_, lerr1 := client.LoginUser(username, "secret")
	if lerr1 == nil {
		t.Fatal("should not be able to login, but was able -> not disabled")
	}
	client.EnableUser(username)
	_, lerr2 := client.LoginUser(username, "secret")
	if lerr2 != nil {
		t.Fatal("should be able to login again", lerr2)
	}
}

func TestChangeUserPassword(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()

	ierr1 := client.ChangeUserPassword("", "secret2", "secret2")
	if ierr1.Code != "EINVAL" {
		t.Fatal("should fail since user value is empty")
	}

	ierr2 := client.ChangeUserPassword(username, "", "secret2")
	if ierr2.Code != "EINVAL" {
		t.Fatal("should fail since passwd value is empty")
	}

	ierr3 := client.ChangeUserPassword(username, "secret2", "")
	if ierr3.Code != "EINVAL" {
		t.Fatal("should fail since new passwd value is empty")
	}

	client.CreateUser(username, "secret")
	client.LoginUser(username, "secret")
	serr := client.ChangeUserPassword(username, "secret2", "secret2")
	// fails if password is wrong
	if serr.Code != "ENOENT" {
		t.Fatal("Should have failed to change with wrong password")
	}
	client.ChangeUserPassword(username, "secret", "secret2")
	_, err := client.LoginUser(username, "secret2")
	if err != nil {
		t.Fatalf("User passwd should have been changed: %s, (%s)", err.Code, err.Message)
	}
}

func TestChangeUserName(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	newusername := uniqName()

	ierr1 := client.ChangeUserName("", "secret2", newusername)
	if ierr1.Code != "EINVAL" {
		t.Fatal("should fail since name value is empty")
	}

	ierr2 := client.ChangeUserName(username, "", newusername)
	if ierr2.Code != "EINVAL" {
		t.Fatal("should fail since passwd value is empty")
	}

	ierr3 := client.ChangeUserName(username, "secret2", "")
	if ierr3.Code != "EINVAL" {
		t.Fatal("should fail since new name value is empty")
	}

	client.CreateUser(username, "secret")
	client.LoginUser(username, "secret")
	serr := client.ChangeUserName(username, "secret2", newusername)
	if serr.Code != "ENOENT" {
		t.Fatal("Should have failed to change with wrong password")
	}
	client.ChangeUserName(username, "secret", newusername)
	_, err := client.LoginUser(newusername, "secret")
	if err != nil {
		t.Fatalf("User name should have been changed: %s, (%s)", err.Code, err.Message)
	}
}

func TestDeleteUser(t *testing.T) {
	client := newClient()
	defer client.Close()
	username := uniqName()
	client.CreateUser(username, "secret")
	_, lerr := client.LoginUser(username, "secret")
	if lerr != nil {
		t.Fatal("should be able to login", lerr)
	}
	derr := client.DeleteUser(username)
	if derr != nil {
		t.Fatal("unable to delete the user", derr.Code)
	}
	_, lerr1 := client.LoginUser(username, "secret")
	if lerr1 == nil {
		t.Fatal("should not be able to login, but was able -> not deleted")
	}
}

func TestUsers(t *testing.T) {
	client := newClient()
	defer client.Close()
	username0 := uniqName()
	uid0, _ := client.CreateUser(username0, "secret")
	username1 := uniqName()
	uid1, _ := client.CreateUser(username1, "secret")
	users, _ := client.Users()
	if len(users) < 2 {
		t.Fatal("should have at least two users")
	}
	found0, found1 := false, false
	for _, user := range users {
		if user.Name == username0 && user.Uid == uid0 {
			found0 = true
		} else if user.Name == username1 && user.Uid == uid1 {
			found1 = true
		}
	}
	if found0 && found1 != true {
		t.Fatalf("should have found created users %s and %s in %v",
			username0, username1, users)
	}
}

func TestGroup(t *testing.T) {
	client := newClient()
	defer client.Close()
	group := uniqName()

	_, err := client.CreateGroup("")
	if err.Code != "EFAULT" {
		t.Fatal("should return EINVAL instead of", err.Code)
	}

	gid, cerr := client.CreateGroup(group)
	defer client.DeleteGroup(group)
	if gid <= 0 {
		t.Fatal("should have created group", cerr.Code, cerr.Message)
	}

	_, eerr := client.CreateGroup(group)
	if eerr.Code != "EEXIST" {
		t.Fatal("should return EEXIST instead of", eerr.Code, eerr.Message)
	}
}

func TestDeleteGroup(t *testing.T) {
	client := newClient()
	defer client.Close()

	group0 := uniqName()
	group1 := uniqName()

	err := client.DeleteUser("")
	if err.Code != "EFAULT" {
		t.Fatal("should error on missing parameter", err.Code)
	}

	// create two groups
	client.CreateGroup(group0)
	gid, _ := client.CreateGroup(group1)
	defer client.DeleteGroup(group1)

	// now has two users
	groups, _ := client.Groups()
	if len(groups) != 2 {
		t.Fatal("user count should have been 2 but was", len(groups))
	}

	// delete one using uid one using name
	derr1 := client.DeleteGroup(group0)
	if derr1 != nil {
		t.Fatal("should not error on delete", derr1.Code, derr1.Message)
	}
	derr1 = client.DeleteGroup(fmt.Sprintf("%d", gid))
	if derr1 != nil {
		t.Fatal("should not error on delete", derr1.Code, derr1.Message)
	}
	groups, _ = client.Groups()
	if len(groups) != 0 {
		t.Fatal("group count should have been 0 but was", len(groups))
	}
}

func TestGroups(t *testing.T) {
	client := newClient()
	defer client.Close()

	group0 := uniqName()
	group1 := uniqName()
	group2 := uniqName()

	client.CreateGroup(group0)
	defer client.DeleteGroup(group0)
	client.CreateGroup(group1)
	defer client.DeleteGroup(group1)
	client.CreateGroup(group2)
	defer client.DeleteGroup(group2)
	groups, _ := client.Groups()

	if len(groups) < 3 {
		t.Fatalf("expected to have at least 3 groups but got %v", groups)
	}
}

func TestUsersAndGroupsAssociations(t *testing.T) {
	client := newClient()
	defer client.Close()

	joe := uniqName()
	mike := uniqName()
	developers := uniqName()
	admins := uniqName()
	gid0, _ := client.CreateGroup(developers)
	defer client.DeleteGroup(developers)
	gid1, _ := client.CreateGroup(admins)
	defer client.DeleteGroup(admins)
	uid0, _ := client.CreateUser(joe, "secret")
	defer client.DeleteUser(joe)
	uid1, _ := client.CreateUser(mike, "secret")
	defer client.DeleteUser(mike)

	gid0s := fmt.Sprintf("%d", gid0)
	uid0s := fmt.Sprintf("%d", uid0)

	// User Groups
	groups, _ := client.UserGroups(joe)
	if len(groups) != 0 {
		t.Fatal("expected joe not to have any groups")
	}
	client.AddUserToGroup(joe, gid0s)
	client.AddUserToGroup(uid0s, admins)
	expectedGroups := []backends.Group{
		backends.Group{Gid: gid0, Name: developers},
		backends.Group{Gid: gid1, Name: admins},
	}
	groups, _ = client.UserGroups(joe)
	if !reflect.DeepEqual(expectedGroups, groups) {
		t.Fatalf("expected joe have groups %v but has %v", expectedGroups, groups)
	}

	// Group Users
	users, _ := client.GroupUsers(admins)
	if len(users) < 1 {
		t.Fatalf("expected to have one admin, got %v", users)
	}
	client.AddUserToGroup(mike, admins)
	users, _ = client.GroupUsers(admins)
	expectedUsers := []backends.User{
		backends.User{Uid: uid0, Name: joe},
		backends.User{Uid: uid1, Name: mike},
	}
	if reflect.DeepEqual(expectedUsers, users) {
		t.Fatalf("expected admins have users %v but has %v", expectedUsers, users)
	}

	// Remove associations
	client.RemoveUserFromGroup(mike, admins)
	client.RemoveUserFromGroup(joe, admins)
	adminsG, _ := client.GroupUsers(admins)
	if len(adminsG) != 0 {
		t.Fatalf("expected to have no admin, got %v", adminsG)
	}
	users, _ = client.GroupUsers(admins)
	if len(users) != 0 {
		t.Fatalf("expected to have no admin, got %v", users)
	}
}

func TestStats(t *testing.T) {
	client := newClient()
	defer client.Close()
	users, _ := client.Users()
	serverInstance.Stats.Reset()

	username := uniqName()
	client.CreateUser(username, "secret")
	userCount := len(users) + 1
	defer client.DeleteUser(username)
	client.LoginUser(username, "secret") // Successfull login
	client.LoginUser("foobar", "123456") // Failed login
	client.LoginUser("foobar", "123456") // twice
	tempClient := newClient()            // Connects++
	// since the server and client are not in sync sleep
	time.Sleep(100 * time.Millisecond)

	stats, _ := client.Stats()
	expected := map[string]int{
		"Connects":                             1,
		"Disconnects":                          0,
		"Active Connections":                   1,
		"Successfull logins":                   1,
		"Failed logins":                        2,
		"Unrestricted Commands":                0,
		"Restricted Commands":                  4,
		"Access denied on Restricted Commands": 0,
		"Users": userCount,
	}
	if !reflect.DeepEqual(stats, expected) {
		t.Fatalf("expected %v to be %v", stats, expected)
	}

	tempClient.Close() // Disconnects++
	// since the server and client are not in sync sleep
	time.Sleep(100 * time.Millisecond)

	stats, _ = client.Stats()
	expected = map[string]int{
		"Connects":                             1,
		"Disconnects":                          1,
		"Active Connections":                   0,
		"Successfull logins":                   1,
		"Failed logins":                        2,
		"Unrestricted Commands":                1,
		"Restricted Commands":                  5,
		"Access denied on Restricted Commands": 0,
		"Users": userCount,
	}
	if !reflect.DeepEqual(stats, expected) {
		t.Fatalf("expected %v to be %v", stats, expected)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/xyproto/browserspeak"
	"github.com/xyproto/genericsite"
	"github.com/xyproto/instapage"
	"github.com/xyproto/siteengines"
	"github.com/xyproto/web"
)

const JQUERY_VERSION = "2.0.0"

func hello(val string) string {
	return instapage.Message("root page", "hello: "+val)
}

func helloHandle(ctx *web.Context, name string) string {
	return "Hello, " + name
}

func notFound2(ctx *web.Context, val string) {
	ctx.ResponseWriter.WriteHeader(404)
	ctx.ResponseWriter.Write([]byte(browserspeak.NotFound(ctx, val)))
}

func ServeEngines(userState *genericsite.UserState, mainMenuEntries genericsite.MenuEntries) {
	// The user engine
	userEngine := siteengines.NewUserEngine(userState)
	userEngine.ServePages()

	// The admin engine
	adminEngine := siteengines.NewAdminEngine(userState)
	adminEngine.ServePages(ArchBaseCP, mainMenuEntries)

	// TODO: Move this one to roboticoverlords instead
	// The dynamic IP webpage (returns an *IPState)
	ipEngine := siteengines.NewIPEngine(userState)
	ipEngine.ServePages()

	// The chat system (see also the menu entry in ArchBaseCP)
	chatEngine := siteengines.NewChatEngine(userState)
	chatEngine.ServePages(ArchBaseCP, mainMenuEntries)

	// Wiki engine
	wikiEngine := siteengines.NewWikiEngine(userState)
	wikiEngine.ServePages(ArchBaseCP, mainMenuEntries)

	// Blog engine
	//blogEngine := NewBlogEngine(userState)
	//blogEngine.ServePages(ArchBaseCP, mainMenuEntries)
}

// TODO: Caching, login
func main() {

	// UserState with a Redis Connection Pool
	userState := genericsite.NewUserState()
	defer userState.Close()

	// The archlinux.no webpage,
	mainMenuEntries := ServeArchlinuxNo(userState, "/js/jquery-"+JQUERY_VERSION+".min.js")

	ServeEngines(userState, mainMenuEntries)

	// Compilation errors, vim-compatible filename
	web.Get("/error", browserspeak.GenerateErrorHandle("errors.err"))
	web.Get("/errors", browserspeak.GenerateErrorHandle("errors.err"))

	// Various .php and .asp urls that showed up in the log
	genericsite.ServeForFun()

	// TODO: Incorporate this check into web.go, to only return
	// stuff in the header when the HEAD method is requested:
	// if ctx.Request.Method == "HEAD" { return }
	// See also: curl -I

	// Serve on port 3000 for the Nginx instance to use
	web.Run("0.0.0.0:3000")
}
<commit_msg>Minor change<commit_after>package main

import (
	"github.com/xyproto/browserspeak"
	"github.com/xyproto/genericsite"
	"github.com/xyproto/instapage"
	"github.com/xyproto/siteengines"
	"github.com/xyproto/web"
)

const JQUERY_VERSION = "2.0.0"

func hello(val string) string {
	return instapage.Message("root page", "hello: "+val)
}

func helloHandle(ctx *web.Context, name string) string {
	return "Hello, " + name
}

func notFound2(ctx *web.Context, val string) {
	ctx.ResponseWriter.WriteHeader(404)
	ctx.ResponseWriter.Write([]byte(browserspeak.NotFound(ctx, val)))
}

func ServeEngines(userState *genericsite.UserState, mainMenuEntries genericsite.MenuEntries) {
	// The user engine
	userEngine := siteengines.NewUserEngine(userState)
	userEngine.ServePages()

	// The admin engine
	adminEngine := siteengines.NewAdminEngine(userState)
	adminEngine.ServePages(ArchBaseCP, mainMenuEntries)

	// TODO: Move this one to roboticoverlords instead
	// The dynamic IP webpage (returns an *IPState)
	ipEngine := siteengines.NewIPEngine(userState)
	ipEngine.ServePages()

	// The chat system (see also the menu entry in ArchBaseCP)
	chatEngine := siteengines.NewChatEngine(userState)
	chatEngine.ServePages(ArchBaseCP, mainMenuEntries)

	// Wiki engine
	wikiEngine := siteengines.NewWikiEngine(userState)
	wikiEngine.ServePages(ArchBaseCP, mainMenuEntries)

	// Blog engine
	//blogEngine := NewBlogEngine(userState)
	//blogEngine.ServePages(ArchBaseCP, mainMenuEntries)
}

// TODO: One database per site
func main() {

	// UserState with a Redis Connection Pool
	userState := genericsite.NewUserState()
	defer userState.Close()

	// The archlinux.no webpage,
	mainMenuEntries := ServeArchlinuxNo(userState, "/js/jquery-"+JQUERY_VERSION+".min.js")

	ServeEngines(userState, mainMenuEntries)

	// Compilation errors, vim-compatible filename
	web.Get("/error", browserspeak.GenerateErrorHandle("errors.err"))
	web.Get("/errors", browserspeak.GenerateErrorHandle("errors.err"))

	// Various .php and .asp urls that showed up in the log
	genericsite.ServeForFun()

	// TODO: Incorporate this check into web.go, to only return
	// stuff in the header when the HEAD method is requested:
	// if ctx.Request.Method == "HEAD" { return }
	// See also: curl -I

	// Serve on port 3000 for the Nginx instance to use
	web.Run("0.0.0.0:3000")
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015, Shinya Yagyu
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package util

import (
	"bufio"
	"bytes"
	"encoding/json"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"

	"github.com/gorilla/mux"
	"github.com/nfnt/resize"
)

//provider represents oembed provider.
type provider struct {
	ProviderURL    string `json:"provider_url"`
	regProviderURL *regexp.Regexp
	Endpoints      []*struct {
		Schemes    []string
		regSchemes []*regexp.Regexp
		URL        string
	}
}

type emoji struct {
	Unicode      string
	Shortname    string
	AliasesASCII []string `json:"aliases_ascii"`
}

//prov is oembed providers from oembed_providers.go
var prov []*provider
var emojis map[string]*emoji

//init loads oembed providers in json.
func init() {
	if err := json.Unmarshal([]byte(oembedProviders), &prov); err != nil {
		log.Fatal(err)
	}
	for _, p := range prov {
		p.regProviderURL = regexp.MustCompile(p.ProviderURL + ".*")
		for _, e := range p.Endpoints {
			e.regSchemes = make([]*regexp.Regexp, len(e.Schemes))
			for i, s := range e.Schemes {
				e.regSchemes[i] = regexp.MustCompile(s + ".*")
			}
		}
	}

	js, err := Asset("www/emoji.json")
	if err != nil {
		log.Fatal(err)
	}
	if err := json.Unmarshal(js, &emojis); err != nil {
		log.Fatal(err)
	}
}

//EachIOLine iterates each line to  a ReadCloser ,calls func and close f.
func EachIOLine(f io.ReadCloser, handler func(line string, num int) error) error {
	defer Fclose(f)
	r := bufio.NewReader(f)
	var err error
	for i := 0; err == nil; i++ {
		var line string
		line, err = r.ReadString('\n')
		if err != nil && line == "" {
			break
		}
		line = strings.Trim(line, "\n\r")
		errr := handler(line, i)
		if errr != nil {
			return errr
		}
	}
	if err == io.EOF {
		return nil
	}
	return err
}

//EachLine iterates each lines and calls a func.
func EachLine(path string, handler func(line string, num int) error) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	return EachIOLine(f, handler)
}

//EachKeyValueLine calls func for each line which contains key and value separated with "<>"
func EachKeyValueLine(path string, handler func(key string, value []string, num int) error) error {
	err := EachLine(path, func(line string, i int) error {
		kv := strings.Split(line, "<>")
		if len(kv) != 2 {
			log.Fatal("illegal line in", path)
		}
		vs := strings.Split(kv[1], " ")
		var err error
		if len(vs) == 1 && vs[0] == "" {
			err = handler(kv[0], nil, i)
		} else {
			err = handler(kv[0], vs, i)
		}
		return err
	})
	return err
}

//HasString returns true if ary has val.
func HasString(s []string, val string) bool {
	return FindString(s, val) != -1
}

//FindString search the val in ary and returns index. it returns -1 if not found.
func FindString(s []string, val string) int {
	for i, v := range s {
		if v == val {
			return i
		}
	}
	return -1
}

//WriteSlice write ary into a path.
func WriteSlice(path string, ary []string) error {
	if path == "" {
		panic("path is null")
	}
	f, err := os.Create(path)
	defer Fclose(f)
	if err != nil {
		log.Println(err)
		return err
	}

	for _, v := range ary {
		_, err := f.WriteString(v + "\n")
		if err != nil {
			return err
		}
	}
	return nil
}

//WriteMap write map into a path.
func WriteMap(path string, ary map[string][]string) error {
	f, err := os.Create(path)
	if err != nil {
		log.Println(err)
		return err
	}
	defer Fclose(f)

	for k, v := range ary {
		_, err := f.WriteString(k + "<>" + strings.Join(v, " ") + "\n")
		if err != nil {
			log.Println(err)
			return err
		}
	}
	return nil
}

//EachFiles iterates each dirs in dir and calls handler,not recirsively.
func EachFiles(dir string, handler func(dir os.FileInfo) error) error {
	dirs, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, i := range dirs {
		if err := handler(i); err != nil {
			return err
		}
	}
	return nil
}

//IsFile returns true is path is an existing file.
func IsFile(path string) bool {
	fs, err := os.Stat(path)
	if err != nil {
		return false
	}
	return !fs.IsDir()
}

//IsDir returns true is path is an existing dir.
func IsDir(path string) bool {
	fs, err := os.Stat(path)
	if err != nil {
		return false
	}
	return fs.IsDir()
}

//MoveFile moves a file from src to dest.
func MoveFile(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer Fclose(in)

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer Fclose(out)

	_, err = io.Copy(out, in)
	if err != nil {
		return err
	}
	return os.Remove(src)
}

//Shufflable interface is for shuffle ary.
type Shufflable interface {
	Len() int
	Swap(i int, j int)
}

//Shuffle shuffles shufflable ary.
func Shuffle(slc Shufflable) {
	N := slc.Len()
	for i := 0; i < N; i++ {
		// choose index uniformly in [i, N-1]
		r := i + rand.Intn(N-i)
		slc.Swap(r, i)
	}
}

//Fclose closes io.Close, if err exists ,println err.
func Fclose(f io.Closer) {
	if err := f.Close(); err != nil {
		log.Println(err)
	}
}

//RegistToRouter registers fn to s with path and returns the handler.
func RegistToRouter(s *mux.Router, path string, fn func(w http.ResponseWriter, r *http.Request)) {
	s.Handle(path, http.HandlerFunc(fn))
}

//FileSize returns file size of file.
//returns 0 if file is not found.
func FileSize(path string) int64 {
	st, err := os.Stat(path)
	if err != nil {
		log.Println(err)
		return 0
	}
	return st.Size()
}

//WriteFile writes date to path.
func WriteFile(path, data string) error {
	err := ioutil.WriteFile(path, []byte(data), 0666)
	if err != nil {
		log.Println(err)
		return err
	}
	return nil
}

//MakeThumbnail makes thumbnail to suffix image format with thumbnailSize.
func MakeThumbnail(encoded []byte, suffix, thumbnailSize string) []byte {
	size := strings.Split(thumbnailSize, "x")
	if len(size) != 2 {
		return nil
	}
	x, err1 := strconv.Atoi(size[0])
	y, err2 := strconv.Atoi(size[1])
	if err1 != nil || err2 != nil {
		log.Println(thumbnailSize, "is illegal format")
		return nil
	}

	file := bytes.NewReader(encoded)
	img, _, err := image.Decode(file)
	if err != nil {
		log.Println(err)
		return nil
	}
	m := resize.Resize(uint(x), uint(y), img, resize.Lanczos3)
	var out bytes.Buffer
	switch suffix {
	case "jpg", "jpeg":
		err = jpeg.Encode(&out, m, nil)
	case "png":
		err = png.Encode(&out, m)
	case "gif":
		err = gif.Encode(&out, m, nil)
	default:
		log.Println("illegal format", suffix)
	}
	if err != nil {
		log.Println(err)
	}
	return out.Bytes()
}

// ToSJIS converts an string (a valid UTF-8 string) to a ShiftJIS string
func ToSJIS(b string) string {
	return convertSJIS(b, true)
}

// convertSJIS converts an string (a valid UTF-8 string) to/from a ShiftJIS string
func convertSJIS(b string, toSJIS bool) string {
	t := japanese.ShiftJIS.NewDecoder()
	if toSJIS {
		t = japanese.ShiftJIS.NewEncoder()
	}
	r, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader([]byte(b)), t))
	if err != nil {
		log.Println(err)
	}
	return string(r)
}

//FromSJIS converts an array of bytes (a valid ShiftJIS string) to a UTF-8 string
func FromSJIS(b string) string {
	return convertSJIS(b, false)
}

//miscURL returns url for embeding for nicovideo, images.
func miscURL(url string) string {
	reg1 := regexp.MustCompile("http://www.nicovideo.jp/watch/([a-z0-9]+)")
	id := reg1.FindStringSubmatch(url)
	if len(id) > 1 {
		return `	<script type="text/javascript" src="http://ext.nicovideo.jp/thumb_watch/` + id[1] + `"></script>`
	}

	reg2 := regexp.MustCompile("https?://github.com/([^/]+)/([^/]+)")
	id = reg2.FindStringSubmatch(url)
	if len(id) > 2 {
		return `<div class="github-card" data-user="` + id[1] + `" data-repo="` + id[2] + `"></div>
<script src="http://cdn.jsdelivr.net/github-cards/latest/widget.js"></script>`
	}

	images := []string{"jpeg", "jpg", "gif", "png"}
	for _, img := range images {
		if strings.HasSuffix(url, img) {
			return `<img src="/x.gif" data-lazyimg data-src="` + url + `" height="210" alt="" />`
		}
	}
	return ""
}

//getJSON get json and converts map[string]interface{} from url by using GET.
func getJSON(url string) (map[string]interface{}, error) {
	resp, err := http.Get(url)
	if err != nil {
		log.Print(err)
		return nil, err
	}
	defer Fclose(resp.Body)
	js, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Print(err)
		return nil, err
	}
	var m map[string]interface{}
	if err := json.Unmarshal(js, &m); err != nil {
		log.Print(err)
		return nil, err
	}
	return m, err
}

//oEmbedURL returns url for embed by using oEmbed.
func oEmbedURL(url string) string {
	for _, p := range prov {
		match := false
		if p.regProviderURL != nil {
			match = p.regProviderURL.MatchString(url)
		}
		for _, e := range p.Endpoints {
			for _, s := range e.regSchemes {
				if s == nil {
					continue
				}
				match = match || s.MatchString(url)
			}
			if !match {
				continue
			}
			log.Println("geting embed url from", e.URL)
			m, err := getJSON(e.URL + "?url=" + url + "&format=json")
			if err != nil {
				log.Print(err)
				continue
			}
			if html, ok := m["html"].(string); ok {
				return html
			}
		}
	}
	return ""
}

//EmbedURL gets the url for embeding by using oEmbed API.
func EmbedURL(url string) string {
	if e := miscURL(url); e != "" {
		return e
	}
	return oEmbedURL(url)
}

//HasExt returns true if fname has prefix and not secret.
func HasExt(fname, suffix string) bool {
	return strings.HasSuffix(fname, "."+suffix) && (!strings.HasPrefix(fname, ".") || strings.HasPrefix(fname, "_"))
}

//Emoji converts :hoe: to a png pic link.
func Emoji(str string) string {
	for _, v := range emojis {
		match := false
		if str == v.Shortname {
			match = true
		}
		for _, alias := range v.AliasesASCII {
			if str == alias {
				match = true
			}
		}
		if match {
			return `<img height="25" src="http://cdn.jsdelivr.net/emojione/assets/png/` + v.Unicode + `.png">`
		}
	}
	return str
}
<commit_msg>w/a x/text spec changes<commit_after>/*
 * Copyright (c) 2015, Shinya Yagyu
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package util

import (
	"bufio"
	"bytes"
	"encoding/json"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"

	"github.com/gorilla/mux"
	"github.com/nfnt/resize"
)

//provider represents oembed provider.
type provider struct {
	ProviderURL    string `json:"provider_url"`
	regProviderURL *regexp.Regexp
	Endpoints      []*struct {
		Schemes    []string
		regSchemes []*regexp.Regexp
		URL        string
	}
}

type emoji struct {
	Unicode      string
	Shortname    string
	AliasesASCII []string `json:"aliases_ascii"`
}

//prov is oembed providers from oembed_providers.go
var prov []*provider
var emojis map[string]*emoji

//init loads oembed providers in json.
func init() {
	if err := json.Unmarshal([]byte(oembedProviders), &prov); err != nil {
		log.Fatal(err)
	}
	for _, p := range prov {
		p.regProviderURL = regexp.MustCompile(p.ProviderURL + ".*")
		for _, e := range p.Endpoints {
			e.regSchemes = make([]*regexp.Regexp, len(e.Schemes))
			for i, s := range e.Schemes {
				e.regSchemes[i] = regexp.MustCompile(s + ".*")
			}
		}
	}

	js, err := Asset("www/emoji.json")
	if err != nil {
		log.Fatal(err)
	}
	if err := json.Unmarshal(js, &emojis); err != nil {
		log.Fatal(err)
	}
}

//EachIOLine iterates each line to  a ReadCloser ,calls func and close f.
func EachIOLine(f io.ReadCloser, handler func(line string, num int) error) error {
	defer Fclose(f)
	r := bufio.NewReader(f)
	var err error
	for i := 0; err == nil; i++ {
		var line string
		line, err = r.ReadString('\n')
		if err != nil && line == "" {
			break
		}
		line = strings.Trim(line, "\n\r")
		errr := handler(line, i)
		if errr != nil {
			return errr
		}
	}
	if err == io.EOF {
		return nil
	}
	return err
}

//EachLine iterates each lines and calls a func.
func EachLine(path string, handler func(line string, num int) error) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	return EachIOLine(f, handler)
}

//EachKeyValueLine calls func for each line which contains key and value separated with "<>"
func EachKeyValueLine(path string, handler func(key string, value []string, num int) error) error {
	err := EachLine(path, func(line string, i int) error {
		kv := strings.Split(line, "<>")
		if len(kv) != 2 {
			log.Fatal("illegal line in", path)
		}
		vs := strings.Split(kv[1], " ")
		var err error
		if len(vs) == 1 && vs[0] == "" {
			err = handler(kv[0], nil, i)
		} else {
			err = handler(kv[0], vs, i)
		}
		return err
	})
	return err
}

//HasString returns true if ary has val.
func HasString(s []string, val string) bool {
	return FindString(s, val) != -1
}

//FindString search the val in ary and returns index. it returns -1 if not found.
func FindString(s []string, val string) int {
	for i, v := range s {
		if v == val {
			return i
		}
	}
	return -1
}

//WriteSlice write ary into a path.
func WriteSlice(path string, ary []string) error {
	if path == "" {
		panic("path is null")
	}
	f, err := os.Create(path)
	defer Fclose(f)
	if err != nil {
		log.Println(err)
		return err
	}

	for _, v := range ary {
		_, err := f.WriteString(v + "\n")
		if err != nil {
			return err
		}
	}
	return nil
}

//WriteMap write map into a path.
func WriteMap(path string, ary map[string][]string) error {
	f, err := os.Create(path)
	if err != nil {
		log.Println(err)
		return err
	}
	defer Fclose(f)

	for k, v := range ary {
		_, err := f.WriteString(k + "<>" + strings.Join(v, " ") + "\n")
		if err != nil {
			log.Println(err)
			return err
		}
	}
	return nil
}

//EachFiles iterates each dirs in dir and calls handler,not recirsively.
func EachFiles(dir string, handler func(dir os.FileInfo) error) error {
	dirs, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, i := range dirs {
		if err := handler(i); err != nil {
			return err
		}
	}
	return nil
}

//IsFile returns true is path is an existing file.
func IsFile(path string) bool {
	fs, err := os.Stat(path)
	if err != nil {
		return false
	}
	return !fs.IsDir()
}

//IsDir returns true is path is an existing dir.
func IsDir(path string) bool {
	fs, err := os.Stat(path)
	if err != nil {
		return false
	}
	return fs.IsDir()
}

//MoveFile moves a file from src to dest.
func MoveFile(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer Fclose(in)

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer Fclose(out)

	_, err = io.Copy(out, in)
	if err != nil {
		return err
	}
	return os.Remove(src)
}

//Shufflable interface is for shuffle ary.
type Shufflable interface {
	Len() int
	Swap(i int, j int)
}

//Shuffle shuffles shufflable ary.
func Shuffle(slc Shufflable) {
	N := slc.Len()
	for i := 0; i < N; i++ {
		// choose index uniformly in [i, N-1]
		r := i + rand.Intn(N-i)
		slc.Swap(r, i)
	}
}

//Fclose closes io.Close, if err exists ,println err.
func Fclose(f io.Closer) {
	if err := f.Close(); err != nil {
		log.Println(err)
	}
}

//RegistToRouter registers fn to s with path and returns the handler.
func RegistToRouter(s *mux.Router, path string, fn func(w http.ResponseWriter, r *http.Request)) {
	s.Handle(path, http.HandlerFunc(fn))
}

//FileSize returns file size of file.
//returns 0 if file is not found.
func FileSize(path string) int64 {
	st, err := os.Stat(path)
	if err != nil {
		log.Println(err)
		return 0
	}
	return st.Size()
}

//WriteFile writes date to path.
func WriteFile(path, data string) error {
	err := ioutil.WriteFile(path, []byte(data), 0666)
	if err != nil {
		log.Println(err)
		return err
	}
	return nil
}

//MakeThumbnail makes thumbnail to suffix image format with thumbnailSize.
func MakeThumbnail(encoded []byte, suffix, thumbnailSize string) []byte {
	size := strings.Split(thumbnailSize, "x")
	if len(size) != 2 {
		return nil
	}
	x, err1 := strconv.Atoi(size[0])
	y, err2 := strconv.Atoi(size[1])
	if err1 != nil || err2 != nil {
		log.Println(thumbnailSize, "is illegal format")
		return nil
	}

	file := bytes.NewReader(encoded)
	img, _, err := image.Decode(file)
	if err != nil {
		log.Println(err)
		return nil
	}
	m := resize.Resize(uint(x), uint(y), img, resize.Lanczos3)
	var out bytes.Buffer
	switch suffix {
	case "jpg", "jpeg":
		err = jpeg.Encode(&out, m, nil)
	case "png":
		err = png.Encode(&out, m)
	case "gif":
		err = gif.Encode(&out, m, nil)
	default:
		log.Println("illegal format", suffix)
	}
	if err != nil {
		log.Println(err)
	}
	return out.Bytes()
}

// ToSJIS converts an string (a valid UTF-8 string) to a ShiftJIS string
func ToSJIS(b string) string {
	return convertSJIS(b, true)
}

// convertSJIS converts an string (a valid UTF-8 string) to/from a ShiftJIS string
func convertSJIS(b string, toSJIS bool) string {
	var t transform.Transformer
	t = japanese.ShiftJIS.NewDecoder()
	if toSJIS {
		t = japanese.ShiftJIS.NewEncoder()
	}
	r, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader([]byte(b)), t))
	if err != nil {
		log.Println(err)
	}
	return string(r)
}

//FromSJIS converts an array of bytes (a valid ShiftJIS string) to a UTF-8 string
func FromSJIS(b string) string {
	return convertSJIS(b, false)
}

//miscURL returns url for embeding for nicovideo, images.
func miscURL(url string) string {
	reg1 := regexp.MustCompile("http://www.nicovideo.jp/watch/([a-z0-9]+)")
	id := reg1.FindStringSubmatch(url)
	if len(id) > 1 {
		return `	<script type="text/javascript" src="http://ext.nicovideo.jp/thumb_watch/` + id[1] + `"></script>`
	}

	reg2 := regexp.MustCompile("https?://github.com/([^/]+)/([^/]+)")
	id = reg2.FindStringSubmatch(url)
	if len(id) > 2 {
		return `<div class="github-card" data-user="` + id[1] + `" data-repo="` + id[2] + `"></div>
<script src="http://cdn.jsdelivr.net/github-cards/latest/widget.js"></script>`
	}

	images := []string{"jpeg", "jpg", "gif", "png"}
	for _, img := range images {
		if strings.HasSuffix(url, img) {
			return `<img src="/x.gif" data-lazyimg data-src="` + url + `" height="210" alt="" />`
		}
	}
	return ""
}

//getJSON get json and converts map[string]interface{} from url by using GET.
func getJSON(url string) (map[string]interface{}, error) {
	resp, err := http.Get(url)
	if err != nil {
		log.Print(err)
		return nil, err
	}
	defer Fclose(resp.Body)
	js, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Print(err)
		return nil, err
	}
	var m map[string]interface{}
	if err := json.Unmarshal(js, &m); err != nil {
		log.Print(err)
		return nil, err
	}
	return m, err
}

//oEmbedURL returns url for embed by using oEmbed.
func oEmbedURL(url string) string {
	for _, p := range prov {
		match := false
		if p.regProviderURL != nil {
			match = p.regProviderURL.MatchString(url)
		}
		for _, e := range p.Endpoints {
			for _, s := range e.regSchemes {
				if s == nil {
					continue
				}
				match = match || s.MatchString(url)
			}
			if !match {
				continue
			}
			log.Println("geting embed url from", e.URL)
			m, err := getJSON(e.URL + "?url=" + url + "&format=json")
			if err != nil {
				log.Print(err)
				continue
			}
			if html, ok := m["html"].(string); ok {
				return html
			}
		}
	}
	return ""
}

//EmbedURL gets the url for embeding by using oEmbed API.
func EmbedURL(url string) string {
	if e := miscURL(url); e != "" {
		return e
	}
	return oEmbedURL(url)
}

//HasExt returns true if fname has prefix and not secret.
func HasExt(fname, suffix string) bool {
	return strings.HasSuffix(fname, "."+suffix) && (!strings.HasPrefix(fname, ".") || strings.HasPrefix(fname, "_"))
}

//Emoji converts :hoe: to a png pic link.
func Emoji(str string) string {
	for _, v := range emojis {
		match := false
		if str == v.Shortname {
			match = true
		}
		for _, alias := range v.AliasesASCII {
			if str == alias {
				match = true
			}
		}
		if match {
			return `<img height="25" src="http://cdn.jsdelivr.net/emojione/assets/png/` + v.Unicode + `.png">`
		}
	}
	return str
}
<|endoftext|>


<commit_before>package astilectron

import (
	"net"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"

	"github.com/asticode/go-astilog"
	"github.com/asticode/go-astitools/context"
	"github.com/asticode/go-astitools/exec"
	"github.com/pkg/errors"
)

// Versions
const (
	DefaultAcceptTCPTimeout = 30 * time.Second
	VersionAstilectron      = "0.30.0"
	VersionElectron         = "4.0.1"
)

// Misc vars
var (
	validOSes = map[string]bool{
		"darwin":  true,
		"linux":   true,
		"windows": true,
	}
)

// App event names
const (
	EventNameAppClose         = "app.close"
	EventNameAppCmdQuit       = "app.cmd.quit" // Sends an event to Electron to properly quit the app
	EventNameAppCmdStop       = "app.cmd.stop" // Cancel the context which results in exiting abruptly Electron's app
	EventNameAppCrash         = "app.crash"
	EventNameAppErrorAccept   = "app.error.accept"
	EventNameAppEventReady    = "app.event.ready"
	EventNameAppNoAccept      = "app.no.accept"
	EventNameAppTooManyAccept = "app.too.many.accept"
)

// Astilectron represents an object capable of interacting with Astilectron
type Astilectron struct {
	canceller    *asticontext.Canceller
	channelQuit  chan bool
	closeOnce    sync.Once
	dispatcher   *dispatcher
	displayPool  *displayPool
	dock         *Dock
	executer     Executer
	identifier   *identifier
	listener     net.Listener
	options      Options
	paths        *Paths
	provisioner  Provisioner
	reader       *reader
	stderrWriter *astiexec.StdWriter
	stdoutWriter *astiexec.StdWriter
	supported    *Supported
	writer       *writer
}

// Options represents Astilectron options
type Options struct {
	AcceptTCPTimeout   time.Duration
	AppName            string
	AppIconDarwinPath  string // Darwin systems requires a specific .icns file
	AppIconDefaultPath string
	BaseDirectoryPath  string
	DataDirectoryPath  string
	ElectronSwitches   []string
	SingleInstance     bool
}

// Supported represents Astilectron supported features
type Supported struct {
	Notification *bool `json:"notification"`
}

// New creates a new Astilectron instance
func New(o Options) (a *Astilectron, err error) {
	// Validate the OS
	if !IsValidOS(runtime.GOOS) {
		err = errors.Wrapf(err, "OS %s is invalid", runtime.GOOS)
		return
	}

	// Init
	a = &Astilectron{
		canceller:   asticontext.NewCanceller(),
		channelQuit: make(chan bool),
		dispatcher:  newDispatcher(),
		displayPool: newDisplayPool(),
		executer:    DefaultExecuter,
		identifier:  newIdentifier(),
		options:     o,
		provisioner: DefaultProvisioner,
	}

	// Set paths
	if a.paths, err = newPaths(runtime.GOOS, runtime.GOARCH, o); err != nil {
		err = errors.Wrap(err, "creating new paths failed")
		return
	}

	// Add default listeners
	a.On(EventNameAppCmdStop, func(e Event) (deleteListener bool) {
		a.Stop()
		return
	})
	a.On(EventNameDisplayEventAdded, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventMetricsChanged, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventRemoved, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	return
}

// IsValidOS validates the OS
func IsValidOS(os string) (ok bool) {
	_, ok = validOSes[os]
	return
}

// SetProvisioner sets the provisioner
func (a *Astilectron) SetProvisioner(p Provisioner) *Astilectron {
	a.provisioner = p
	return a
}

// SetExecuter sets the executer
func (a *Astilectron) SetExecuter(e Executer) *Astilectron {
	a.executer = e
	return a
}

// On implements the Listenable interface
func (a *Astilectron) On(eventName string, l Listener) {
	a.dispatcher.addListener(targetIDApp, eventName, l)
}

// Start starts Astilectron
func (a *Astilectron) Start() (err error) {
	// Log
	astilog.Debug("Starting...")

	// Provision
	if err = a.provision(); err != nil {
		return errors.Wrap(err, "provisioning failed")
	}

	// Unfortunately communicating with Electron through stdin/stdout doesn't work on Windows so all communications
	// will be done through TCP
	if err = a.listenTCP(); err != nil {
		return errors.Wrap(err, "listening failed")
	}

	// Execute
	if err = a.execute(); err != nil {
		err = errors.Wrap(err, "executing failed")
		return
	}
	return
}

// provision provisions Astilectron
func (a *Astilectron) provision() error {
	astilog.Debug("Provisioning...")
	var ctx, _ = a.canceller.NewContext()
	return a.provisioner.Provision(ctx, a.options.AppName, runtime.GOOS, runtime.GOARCH, *a.paths)
}

// listenTCP listens to the first TCP connection coming its way (this should be Astilectron)
func (a *Astilectron) listenTCP() (err error) {
	// Log
	astilog.Debug("Listening...")

	// Listen
	if a.listener, err = net.Listen("tcp", "127.0.0.1:"); err != nil {
		return errors.Wrap(err, "tcp net.Listen failed")
	}

	// Check a connection has been accepted quickly enough
	var chanAccepted = make(chan bool)
	go a.watchNoAccept(a.options.AcceptTCPTimeout, chanAccepted)

	// Accept connections
	go a.acceptTCP(chanAccepted)
	return
}

// watchNoAccept checks whether a TCP connection is accepted quickly enough
func (a *Astilectron) watchNoAccept(timeout time.Duration, chanAccepted chan bool) {
	//check timeout
	if timeout == 0 {
		timeout = DefaultAcceptTCPTimeout
	}
	var t = time.NewTimer(timeout)
	defer t.Stop()
	for {
		select {
		case <-chanAccepted:
			return
		case <-t.C:
			astilog.Errorf("No TCP connection has been accepted in the past %s", timeout)
			a.dispatcher.dispatch(Event{Name: EventNameAppNoAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			return
		}
	}
}

// watchAcceptTCP accepts TCP connections
func (a *Astilectron) acceptTCP(chanAccepted chan bool) {
	for i := 0; i <= 1; i++ {
		// Accept
		var conn net.Conn
		var err error
		if conn, err = a.listener.Accept(); err != nil {
			astilog.Errorf("%s while TCP accepting", err)
			a.dispatcher.dispatch(Event{Name: EventNameAppErrorAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			return
		}

		// We only accept the first connection which should be Astilectron, close the next one and stop
		// the app
		if i > 0 {
			astilog.Errorf("Too many TCP connections")
			a.dispatcher.dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			conn.Close()
			return
		}

		// Let the timer know a connection has been accepted
		chanAccepted <- true

		// Create reader and writer
		a.writer = newWriter(conn)
		ctx, _ := a.canceller.NewContext()
		a.reader = newReader(ctx, a.dispatcher, conn)
		go a.reader.read()
	}
}

// execute executes Astilectron in Electron
func (a *Astilectron) execute() (err error) {
	// Log
	astilog.Debug("Executing...")

	// Create command
	var ctx, _ = a.canceller.NewContext()
	var singleInstance string
	if a.options.SingleInstance == true {
		singleInstance = "true"
	} else {
		singleInstance = "false"
	}
	var cmd = exec.CommandContext(ctx, a.paths.AppExecutable(), append([]string{a.paths.AstilectronApplication(), a.listener.Addr().String(), singleInstance}, a.options.ElectronSwitches...)...)
	a.stderrWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stderr says: %s", i) })
	a.stdoutWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stdout says: %s", i) })
	cmd.Stderr = a.stderrWriter
	cmd.Stdout = a.stdoutWriter

	// Execute command
	if err = a.executeCmd(cmd); err != nil {
		return errors.Wrap(err, "executing cmd failed")
	}
	return
}

// executeCmd executes the command
func (a *Astilectron) executeCmd(cmd *exec.Cmd) (err error) {
	var e = synchronousFunc(a.canceller, a, func() {
		err = a.executer(a, cmd)
	}, EventNameAppEventReady)

	// Update display pool
	if e.Displays != nil {
		a.displayPool.update(e.Displays)
	}

	// Create dock
	a.dock = newDock(a.canceller, a.dispatcher, a.identifier, a.writer)

	// Update supported features
	a.supported = e.Supported
	return
}

// watchCmd watches the cmd execution
func (a *Astilectron) watchCmd(cmd *exec.Cmd) {
	// Wait
	cmd.Wait()

	// Check the canceller to check whether it was a crash
	if !a.canceller.Cancelled() {
		astilog.Debug("App has crashed")
		a.dispatcher.dispatch(Event{Name: EventNameAppCrash, TargetID: targetIDApp})
	} else {
		astilog.Debug("App has closed")
		a.dispatcher.dispatch(Event{Name: EventNameAppClose, TargetID: targetIDApp})
	}
	a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
}

// Close closes Astilectron properly
func (a *Astilectron) Close() {
	astilog.Debug("Closing...")
	a.canceller.Cancel()
	if a.listener != nil {
		a.listener.Close()
	}
	if a.reader != nil {
		a.reader.close()
	}
	if a.stderrWriter != nil {
		a.stderrWriter.Close()
	}
	if a.stdoutWriter != nil {
		a.stdoutWriter.Close()
	}
	if a.writer != nil {
		a.writer.close()
	}
}

// HandleSignals handles signals
func (a *Astilectron) HandleSignals() {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGABRT, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
	go func() {
		for sig := range ch {
			astilog.Debugf("Received signal %s", sig)
			a.Stop()
		}
	}()
}

// Stop orders Astilectron to stop
func (a *Astilectron) Stop() {
	astilog.Debug("Stopping...")
	a.canceller.Cancel()
	a.closeOnce.Do(func() {
		close(a.channelQuit)
	})
}

// Wait is a blocking pattern
func (a *Astilectron) Wait() {
	<-a.channelQuit
}

// Quit quits the app
func (a *Astilectron) Quit() error {
	return a.writer.write(Event{Name: EventNameAppCmdQuit})
}

// Paths returns the paths
func (a *Astilectron) Paths() Paths {
	return *a.paths
}

// Displays returns the displays
func (a *Astilectron) Displays() []*Display {
	return a.displayPool.all()
}

// Dock returns the dock
func (a *Astilectron) Dock() *Dock {
	return a.dock
}

// PrimaryDisplay returns the primary display
func (a *Astilectron) PrimaryDisplay() *Display {
	return a.displayPool.primary()
}

// NewMenu creates a new app menu
func (a *Astilectron) NewMenu(i []*MenuItemOptions) *Menu {
	return newMenu(nil, targetIDApp, i, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindow creates a new window
func (a *Astilectron) NewWindow(url string, o *WindowOptions) (*Window, error) {
	return newWindow(a.options, a.Paths(), url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindowInDisplay creates a new window in a specific display
// This overrides the center attribute
func (a *Astilectron) NewWindowInDisplay(d *Display, url string, o *WindowOptions) (*Window, error) {
	if o.X != nil {
		*o.X += d.Bounds().X
	} else {
		o.X = PtrInt(d.Bounds().X)
	}
	if o.Y != nil {
		*o.Y += d.Bounds().Y
	} else {
		o.Y = PtrInt(d.Bounds().Y)
	}
	return newWindow(a.options, a.Paths(), url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewTray creates a new tray
func (a *Astilectron) NewTray(o *TrayOptions) *Tray {
	return newTray(o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewNotification creates a new notification
func (a *Astilectron) NewNotification(o *NotificationOptions) *Notification {
	return newNotification(o, a.supported != nil && a.supported.Notification != nil && *a.supported.Notification, a.canceller, a.dispatcher, a.identifier, a.writer)
}
<commit_msg>upgrade astitools version<commit_after>package astilectron

import (
	"net"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"

	"github.com/asticode/go-astilog"
	"github.com/asticode/go-astitools/context"
	"github.com/asticode/go-astitools/exec"
	"github.com/pkg/errors"
)

// Versions
const (
	DefaultAcceptTCPTimeout = 30 * time.Second
	VersionAstilectron      = "0.31.0"
	VersionElectron         = "4.0.1"
)

// Misc vars
var (
	validOSes = map[string]bool{
		"darwin":  true,
		"linux":   true,
		"windows": true,
	}
)

// App event names
const (
	EventNameAppClose         = "app.close"
	EventNameAppCmdQuit       = "app.cmd.quit" // Sends an event to Electron to properly quit the app
	EventNameAppCmdStop       = "app.cmd.stop" // Cancel the context which results in exiting abruptly Electron's app
	EventNameAppCrash         = "app.crash"
	EventNameAppErrorAccept   = "app.error.accept"
	EventNameAppEventReady    = "app.event.ready"
	EventNameAppNoAccept      = "app.no.accept"
	EventNameAppTooManyAccept = "app.too.many.accept"
)

// Astilectron represents an object capable of interacting with Astilectron
type Astilectron struct {
	canceller    *asticontext.Canceller
	channelQuit  chan bool
	closeOnce    sync.Once
	dispatcher   *dispatcher
	displayPool  *displayPool
	dock         *Dock
	executer     Executer
	identifier   *identifier
	listener     net.Listener
	options      Options
	paths        *Paths
	provisioner  Provisioner
	reader       *reader
	stderrWriter *astiexec.StdWriter
	stdoutWriter *astiexec.StdWriter
	supported    *Supported
	writer       *writer
}

// Options represents Astilectron options
type Options struct {
	AcceptTCPTimeout   time.Duration
	AppName            string
	AppIconDarwinPath  string // Darwin systems requires a specific .icns file
	AppIconDefaultPath string
	BaseDirectoryPath  string
	DataDirectoryPath  string
	ElectronSwitches   []string
	SingleInstance     bool
}

// Supported represents Astilectron supported features
type Supported struct {
	Notification *bool `json:"notification"`
}

// New creates a new Astilectron instance
func New(o Options) (a *Astilectron, err error) {
	// Validate the OS
	if !IsValidOS(runtime.GOOS) {
		err = errors.Wrapf(err, "OS %s is invalid", runtime.GOOS)
		return
	}

	// Init
	a = &Astilectron{
		canceller:   asticontext.NewCanceller(),
		channelQuit: make(chan bool),
		dispatcher:  newDispatcher(),
		displayPool: newDisplayPool(),
		executer:    DefaultExecuter,
		identifier:  newIdentifier(),
		options:     o,
		provisioner: DefaultProvisioner,
	}

	// Set paths
	if a.paths, err = newPaths(runtime.GOOS, runtime.GOARCH, o); err != nil {
		err = errors.Wrap(err, "creating new paths failed")
		return
	}

	// Add default listeners
	a.On(EventNameAppCmdStop, func(e Event) (deleteListener bool) {
		a.Stop()
		return
	})
	a.On(EventNameDisplayEventAdded, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventMetricsChanged, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventRemoved, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	return
}

// IsValidOS validates the OS
func IsValidOS(os string) (ok bool) {
	_, ok = validOSes[os]
	return
}

// SetProvisioner sets the provisioner
func (a *Astilectron) SetProvisioner(p Provisioner) *Astilectron {
	a.provisioner = p
	return a
}

// SetExecuter sets the executer
func (a *Astilectron) SetExecuter(e Executer) *Astilectron {
	a.executer = e
	return a
}

// On implements the Listenable interface
func (a *Astilectron) On(eventName string, l Listener) {
	a.dispatcher.addListener(targetIDApp, eventName, l)
}

// Start starts Astilectron
func (a *Astilectron) Start() (err error) {
	// Log
	astilog.Debug("Starting...")

	// Provision
	if err = a.provision(); err != nil {
		return errors.Wrap(err, "provisioning failed")
	}

	// Unfortunately communicating with Electron through stdin/stdout doesn't work on Windows so all communications
	// will be done through TCP
	if err = a.listenTCP(); err != nil {
		return errors.Wrap(err, "listening failed")
	}

	// Execute
	if err = a.execute(); err != nil {
		err = errors.Wrap(err, "executing failed")
		return
	}
	return
}

// provision provisions Astilectron
func (a *Astilectron) provision() error {
	astilog.Debug("Provisioning...")
	var ctx, _ = a.canceller.NewContext()
	return a.provisioner.Provision(ctx, a.options.AppName, runtime.GOOS, runtime.GOARCH, *a.paths)
}

// listenTCP listens to the first TCP connection coming its way (this should be Astilectron)
func (a *Astilectron) listenTCP() (err error) {
	// Log
	astilog.Debug("Listening...")

	// Listen
	if a.listener, err = net.Listen("tcp", "127.0.0.1:"); err != nil {
		return errors.Wrap(err, "tcp net.Listen failed")
	}

	// Check a connection has been accepted quickly enough
	var chanAccepted = make(chan bool)
	go a.watchNoAccept(a.options.AcceptTCPTimeout, chanAccepted)

	// Accept connections
	go a.acceptTCP(chanAccepted)
	return
}

// watchNoAccept checks whether a TCP connection is accepted quickly enough
func (a *Astilectron) watchNoAccept(timeout time.Duration, chanAccepted chan bool) {
	//check timeout
	if timeout == 0 {
		timeout = DefaultAcceptTCPTimeout
	}
	var t = time.NewTimer(timeout)
	defer t.Stop()
	for {
		select {
		case <-chanAccepted:
			return
		case <-t.C:
			astilog.Errorf("No TCP connection has been accepted in the past %s", timeout)
			a.dispatcher.dispatch(Event{Name: EventNameAppNoAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			return
		}
	}
}

// watchAcceptTCP accepts TCP connections
func (a *Astilectron) acceptTCP(chanAccepted chan bool) {
	for i := 0; i <= 1; i++ {
		// Accept
		var conn net.Conn
		var err error
		if conn, err = a.listener.Accept(); err != nil {
			astilog.Errorf("%s while TCP accepting", err)
			a.dispatcher.dispatch(Event{Name: EventNameAppErrorAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			return
		}

		// We only accept the first connection which should be Astilectron, close the next one and stop
		// the app
		if i > 0 {
			astilog.Errorf("Too many TCP connections")
			a.dispatcher.dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			conn.Close()
			return
		}

		// Let the timer know a connection has been accepted
		chanAccepted <- true

		// Create reader and writer
		a.writer = newWriter(conn)
		ctx, _ := a.canceller.NewContext()
		a.reader = newReader(ctx, a.dispatcher, conn)
		go a.reader.read()
	}
}

// execute executes Astilectron in Electron
func (a *Astilectron) execute() (err error) {
	// Log
	astilog.Debug("Executing...")

	// Create command
	var ctx, _ = a.canceller.NewContext()
	var singleInstance string
	if a.options.SingleInstance == true {
		singleInstance = "true"
	} else {
		singleInstance = "false"
	}
	var cmd = exec.CommandContext(ctx, a.paths.AppExecutable(), append([]string{a.paths.AstilectronApplication(), a.listener.Addr().String(), singleInstance}, a.options.ElectronSwitches...)...)
	a.stderrWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stderr says: %s", i) })
	a.stdoutWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stdout says: %s", i) })
	cmd.Stderr = a.stderrWriter
	cmd.Stdout = a.stdoutWriter

	// Execute command
	if err = a.executeCmd(cmd); err != nil {
		return errors.Wrap(err, "executing cmd failed")
	}
	return
}

// executeCmd executes the command
func (a *Astilectron) executeCmd(cmd *exec.Cmd) (err error) {
	var e = synchronousFunc(a.canceller, a, func() {
		err = a.executer(a, cmd)
	}, EventNameAppEventReady)

	// Update display pool
	if e.Displays != nil {
		a.displayPool.update(e.Displays)
	}

	// Create dock
	a.dock = newDock(a.canceller, a.dispatcher, a.identifier, a.writer)

	// Update supported features
	a.supported = e.Supported
	return
}

// watchCmd watches the cmd execution
func (a *Astilectron) watchCmd(cmd *exec.Cmd) {
	// Wait
	cmd.Wait()

	// Check the canceller to check whether it was a crash
	if !a.canceller.Cancelled() {
		astilog.Debug("App has crashed")
		a.dispatcher.dispatch(Event{Name: EventNameAppCrash, TargetID: targetIDApp})
	} else {
		astilog.Debug("App has closed")
		a.dispatcher.dispatch(Event{Name: EventNameAppClose, TargetID: targetIDApp})
	}
	a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
}

// Close closes Astilectron properly
func (a *Astilectron) Close() {
	astilog.Debug("Closing...")
	a.canceller.Cancel()
	if a.listener != nil {
		a.listener.Close()
	}
	if a.reader != nil {
		a.reader.close()
	}
	if a.stderrWriter != nil {
		a.stderrWriter.Close()
	}
	if a.stdoutWriter != nil {
		a.stdoutWriter.Close()
	}
	if a.writer != nil {
		a.writer.close()
	}
}

// HandleSignals handles signals
func (a *Astilectron) HandleSignals() {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGABRT, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
	go func() {
		for sig := range ch {
			astilog.Debugf("Received signal %s", sig)
			a.Stop()
		}
	}()
}

// Stop orders Astilectron to stop
func (a *Astilectron) Stop() {
	astilog.Debug("Stopping...")
	a.canceller.Cancel()
	a.closeOnce.Do(func() {
		close(a.channelQuit)
	})
}

// Wait is a blocking pattern
func (a *Astilectron) Wait() {
	<-a.channelQuit
}

// Quit quits the app
func (a *Astilectron) Quit() error {
	return a.writer.write(Event{Name: EventNameAppCmdQuit})
}

// Paths returns the paths
func (a *Astilectron) Paths() Paths {
	return *a.paths
}

// Displays returns the displays
func (a *Astilectron) Displays() []*Display {
	return a.displayPool.all()
}

// Dock returns the dock
func (a *Astilectron) Dock() *Dock {
	return a.dock
}

// PrimaryDisplay returns the primary display
func (a *Astilectron) PrimaryDisplay() *Display {
	return a.displayPool.primary()
}

// NewMenu creates a new app menu
func (a *Astilectron) NewMenu(i []*MenuItemOptions) *Menu {
	return newMenu(nil, targetIDApp, i, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindow creates a new window
func (a *Astilectron) NewWindow(url string, o *WindowOptions) (*Window, error) {
	return newWindow(a.options, a.Paths(), url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindowInDisplay creates a new window in a specific display
// This overrides the center attribute
func (a *Astilectron) NewWindowInDisplay(d *Display, url string, o *WindowOptions) (*Window, error) {
	if o.X != nil {
		*o.X += d.Bounds().X
	} else {
		o.X = PtrInt(d.Bounds().X)
	}
	if o.Y != nil {
		*o.Y += d.Bounds().Y
	} else {
		o.Y = PtrInt(d.Bounds().Y)
	}
	return newWindow(a.options, a.Paths(), url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewTray creates a new tray
func (a *Astilectron) NewTray(o *TrayOptions) *Tray {
	return newTray(o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewNotification creates a new notification
func (a *Astilectron) NewNotification(o *NotificationOptions) *Notification {
	return newNotification(o, a.supported != nil && a.supported.Notification != nil && *a.supported.Notification, a.canceller, a.dispatcher, a.identifier, a.writer)
}
<|endoftext|>


<commit_before>package main

import (
	"math"
	"regexp"
	"time"

	"github.com/elastic/libbeat/beat"
	"github.com/elastic/libbeat/cfgfile"
	"github.com/elastic/libbeat/common"
	"github.com/elastic/libbeat/logp"
	"github.com/elastic/libbeat/publisher"
)

type ProcsMap map[int]*Process

type Topbeat struct {
	isAlive      bool
	period       time.Duration
	procs        []string
	procsMap     ProcsMap
	lastCpuTimes *CpuTimes
	TbConfig     ConfigSettings
	events       chan common.MapStr
}

func (tb *Topbeat) Config(b *beat.Beat) error {

	err := cfgfile.Read(&tb.TbConfig, "")
	if err != nil {
		logp.Err("Error reading configuration file: %v", err)
		return err
	}

	if tb.TbConfig.Input.Period != nil {
		tb.period = time.Duration(*tb.TbConfig.Input.Period) * time.Second
	} else {
		tb.period = 1 * time.Second
	}
	if tb.TbConfig.Input.Procs != nil {
		tb.procs = *tb.TbConfig.Input.Procs
	} else {
		tb.procs = []string{".*"} //all processes
	}

	logp.Debug("topbeat", "Init toppbeat")
	logp.Debug("topbeat", "Follow processes %q\n", tb.procs)
	logp.Debug("topbeat", "Period %v\n", tb.period)

	return nil
}

func (tb *Topbeat) Setup(b *beat.Beat) error {

	tb.events = publisher.Publisher.Queue
	return nil
}

func (t *Topbeat) Run(b *beat.Beat) error {

	t.isAlive = true

	t.initProcStats()

	var err error

	for t.isAlive {
		time.Sleep(t.period)

		err = t.exportSystemStats()
		if err != nil {
			logp.Err("Error reading system stats: %v", err)
		}
		err = t.exportProcStats()
		if err != nil {
			logp.Err("Error reading proc stats: %v", err)
		}
		err = t.exportFileSystemStats()
		if err != nil {
			logp.Err("Error reading fs stats: %v", err)
		}
	}

	return err
}

func (tb *Topbeat) Cleanup(b *beat.Beat) error {
	return nil
}

func (t *Topbeat) Stop() {

	t.isAlive = false
}

func (t *Topbeat) initProcStats() {

	t.procsMap = make(ProcsMap)

	if len(t.procs) == 0 {
		return
	}

	pids, err := Pids()
	if err != nil {
		logp.Warn("Getting the list of pids: %v", err)
	}

	for _, pid := range pids {
		process, err := GetProcess(pid)
		if err != nil {
			logp.Debug("topbeat", "Skip process %d: %v", pid, err)
			continue
		}
		t.procsMap[process.Pid] = process
	}
}

func (t *Topbeat) exportProcStats() error {

	if len(t.procs) == 0 {
		return nil
	}

	pids, err := Pids()
	if err != nil {
		logp.Warn("Getting the list of pids: %v", err)
		return err
	}

	for _, pid := range pids {
		process, err := GetProcess(pid)
		if err != nil {
			logp.Debug("topbeat", "Skip process %d: %v", pid, err)
			continue
		}

		if t.MatchProcess(process.Name) {

			t.addProcCpuPercentage(process)
			t.addProcMemPercentage(process, 0 /*read total mem usage */)

			t.procsMap[process.Pid] = process

			event := common.MapStr{
				"timestamp": common.Time(time.Now()),
				"type":      "proc",
				"proc": common.MapStr{
					"pid":   process.Pid,
					"ppid":  process.Ppid,
					"name":  process.Name,
					"state": process.State,
					"mem":   process.Mem,
					"cpu":   process.Cpu,
				},
			}
			t.events <- event
		}
	}
	return nil
}

func (t *Topbeat) exportSystemStats() error {

	load_stat, err := GetSystemLoad()
	if err != nil {
		logp.Warn("Getting load statistics: %v", err)
		return err
	}
	cpu_stat, err := GetCpuTimes()
	if err != nil {
		logp.Warn("Getting cpu times: %v", err)
		return err
	}

	t.addCpuPercentage(cpu_stat)

	mem_stat, err := GetMemory()
	if err != nil {
		logp.Warn("Getting memory details: %v", err)
		return err
	}
	t.addMemPercentage(mem_stat)

	swap_stat, err := GetSwap()
	if err != nil {
		logp.Warn("Getting swap details: %v", err)
		return err
	}
	t.addMemPercentage(swap_stat)

	event := common.MapStr{
		"timestamp": common.Time(time.Now()),
		"type":      "system",
		"load":      load_stat,
		"cpu":       cpu_stat,
		"mem":       mem_stat,
		"swap":      swap_stat,
	}

	t.events <- event

	return nil
}

func (t *Topbeat) exportFileSystemStats() error {

	fss, err := GetFileSystemList()
	if err != nil {
		logp.Warn("Getting filesystem list: %v", err)
		return err
	}

	for _, fs := range fss {
		fs_stat, err := GetFileSystemStat(fs)
		if err != nil {
			logp.Debug("topbeat", "Skip filesystem %d: %v", fs_stat, err)
			continue
		}
		t.addFileSystemUsedPercentage(fs_stat)

		event := common.MapStr{
			"timestamp": common.Time(time.Now()),
			"type":      "filesystem",
			"fs":        fs_stat,
		}
		t.events <- event
	}

	return nil
}

func (t *Topbeat) MatchProcess(name string) bool {

	for _, reg := range t.procs {
		matched, _ := regexp.MatchString(reg, name)
		if matched {
			return true
		}
	}
	return false
}

func (t *Topbeat) addMemPercentage(m *MemStat) {

	if m.Total == 0 {
		return
	}

	perc := float64(m.Used) / float64(m.Total)
	m.UsedPercent = Round(perc, .5, 2)
}

func (t *Topbeat) addFileSystemUsedPercentage(f *FileSystemStat) {

	if f.Total == 0 {
		return
	}

	perc := float64(f.Used) / float64(f.Total)
	f.UsedPercent = Round(perc, .5, 2)
}

func (t *Topbeat) addCpuPercentage(t2 *CpuTimes) {

	t1 := t.lastCpuTimes

	if t1 != nil && t2 != nil {
		all_delta := t2.sum() - t1.sum()

		calculate := func(field2 uint64, field1 uint64) float64 {

			perc := 0.0
			delta := field2 - field1
			perc = float64(delta) / float64(all_delta)
			return Round(perc, .5, 2)
		}

		t2.UserPercent = calculate(t2.User, t1.User)
		t2.SystemPercent = calculate(t2.System, t1.System)
	}

	t.lastCpuTimes = t2

}

func (t *Topbeat) addProcMemPercentage(proc *Process, total_phymem uint64) {

	// in unit tests, total_phymem is set to a value greater than zero

	if total_phymem == 0 {
		mem_stat, err := GetMemory()
		if err != nil {
			logp.Warn("Getting memory details: %v", err)
			return
		}
		total_phymem = mem_stat.Total
	}

	perc := (float64(proc.Mem.Rss) / float64(total_phymem))

	proc.Mem.RssPercent = Round(perc, .5, 2)
}

func (t *Topbeat) addProcCpuPercentage(proc *Process) {

	oproc, ok := t.procsMap[proc.Pid]
	if ok {

		delta_proc := (proc.Cpu.User - oproc.Cpu.User) + (proc.Cpu.System - oproc.Cpu.System)
		delta_time := proc.ctime.Sub(oproc.ctime).Nanoseconds() / 1e6 // in milliseconds
		perc := float64(delta_proc) / float64(delta_time)

		t.procsMap[proc.Pid] = proc

		proc.Cpu.UserPercent = Round(perc, .5, 2)

	}
}

func Round(val float64, roundOn float64, places int) (newVal float64) {
	var round float64
	pow := math.Pow(10, float64(places))
	digit := pow * val
	_, div := math.Modf(digit)
	if div >= roundOn {
		round = math.Ceil(digit)
	} else {
		round = math.Floor(digit)
	}
	newVal = round / pow
	return
}

func (t *CpuTimes) sum() uint64 {
	return t.User + t.Nice + t.System + t.Idle + t.IOWait + t.Irq + t.SoftIrq + t.Steal
}
<commit_msg>update topbeat to publisher changes<commit_after>package main

import (
	"math"
	"regexp"
	"time"

	"github.com/elastic/gosigar"

	"github.com/elastic/libbeat/beat"
	"github.com/elastic/libbeat/cfgfile"
	"github.com/elastic/libbeat/common"
	"github.com/elastic/libbeat/logp"
	"github.com/elastic/libbeat/publisher"
)

type ProcsMap map[int]*Process

type Topbeat struct {
	isAlive      bool
	period       time.Duration
	procs        []string
	procsMap     ProcsMap
	lastCpuTimes *CpuTimes
	TbConfig     ConfigSettings
	events       publisher.Client
}

func (tb *Topbeat) Config(b *beat.Beat) error {

	err := cfgfile.Read(&tb.TbConfig, "")
	if err != nil {
		logp.Err("Error reading configuration file: %v", err)
		return err
	}

	if tb.TbConfig.Input.Period != nil {
		tb.period = time.Duration(*tb.TbConfig.Input.Period) * time.Second
	} else {
		tb.period = 1 * time.Second
	}
	if tb.TbConfig.Input.Procs != nil {
		tb.procs = *tb.TbConfig.Input.Procs
	} else {
		tb.procs = []string{".*"} //all processes
	}

	logp.Debug("topbeat", "Init toppbeat")
	logp.Debug("topbeat", "Follow processes %q\n", tb.procs)
	logp.Debug("topbeat", "Period %v\n", tb.period)

	return nil
}

func (tb *Topbeat) Setup(b *beat.Beat) error {
	tb.events = b.Events
	return nil
}

func (t *Topbeat) Run(b *beat.Beat) error {

	t.isAlive = true

	t.initProcStats()

	var err error

	for t.isAlive {
		time.Sleep(t.period)

		err = t.exportSystemStats()
		if err != nil {
			logp.Err("Error reading system stats: %v", err)
		}
		err = t.exportProcStats()
		if err != nil {
			logp.Err("Error reading proc stats: %v", err)
		}
		err = t.exportFileSystemStats()
		if err != nil {
			logp.Err("Error reading fs stats: %v", err)
		}
	}

	return err
}

func (tb *Topbeat) Cleanup(b *beat.Beat) error {
	return nil
}

func (t *Topbeat) Stop() {

	t.isAlive = false
}

func (t *Topbeat) initProcStats() {

	t.procsMap = make(ProcsMap)

	if len(t.procs) == 0 {
		return
	}

	pids, err := Pids()
	if err != nil {
		logp.Warn("Getting the list of pids: %v", err)
	}

	for _, pid := range pids {
		process, err := GetProcess(pid)
		if err != nil {
			logp.Debug("topbeat", "Skip process %d: %v", pid, err)
			continue
		}
		t.procsMap[process.Pid] = process
	}
}

func (t *Topbeat) exportProcStats() error {

	if len(t.procs) == 0 {
		return nil
	}

	pids, err := Pids()
	if err != nil {
		logp.Warn("Getting the list of pids: %v", err)
		return err
	}

	for _, pid := range pids {
		process, err := GetProcess(pid)
		if err != nil {
			logp.Debug("topbeat", "Skip process %d: %v", pid, err)
			continue
		}

		if t.MatchProcess(process.Name) {

			t.addProcCpuPercentage(process)
			t.addProcMemPercentage(process, 0 /*read total mem usage */)

			t.procsMap[process.Pid] = process

			event := common.MapStr{
				"timestamp": common.Time(time.Now()),
				"type":      "proc",
				"proc": common.MapStr{
					"pid":   process.Pid,
					"ppid":  process.Ppid,
					"name":  process.Name,
					"state": process.State,
					"mem":   process.Mem,
					"cpu":   process.Cpu,
				},
			}
			t.events.PublishEvent(event)
		}
	}
	return nil
}

func (t *Topbeat) exportSystemStats() error {

	load_stat, err := GetSystemLoad()
	if err != nil {
		logp.Warn("Getting load statistics: %v", err)
		return err
	}
	cpu_stat, err := GetCpuTimes()
	if err != nil {
		logp.Warn("Getting cpu times: %v", err)
		return err
	}

	t.addCpuPercentage(cpu_stat)

	mem_stat, err := GetMemory()
	if err != nil {
		logp.Warn("Getting memory details: %v", err)
		return err
	}
	t.addMemPercentage(mem_stat)

	swap_stat, err := GetSwap()
	if err != nil {
		logp.Warn("Getting swap details: %v", err)
		return err
	}
	t.addMemPercentage(swap_stat)

	event := common.MapStr{
		"timestamp": common.Time(time.Now()),
		"type":      "system",
		"load":      load_stat,
		"cpu":       cpu_stat,
		"mem":       mem_stat,
		"swap":      swap_stat,
	}

	t.events.PublishEvent(event)

	return nil
}

func (t *Topbeat) exportFileSystemStats() error {
	fss, err := GetFileSystemList()
	if err != nil {
		logp.Warn("Getting filesystem list: %v", err)
		return err
	}

	t.events.PublishEvents(collectFileSystemStats(fss))
	return nil
}

func collectFileSystemStats(fss []sigar.FileSystem) []common.MapStr {
	events := make([]common.MapStr, 0, len(fss))
	for _, fs := range fss {
		fsStat, err := GetFileSystemStat(fs)
		if err != nil {
			logp.Debug("topbeat", "Skip filesystem %d: %v", fsStat, err)
			continue
		}
		addFileSystemUsedPercentage(fsStat)

		event := common.MapStr{
			"timestamp": common.Time(time.Now()),
			"type":      "filesystem",
			"fs":        fsStat,
		}
		events = append(events, event)
	}
	return events
}

func (t *Topbeat) MatchProcess(name string) bool {

	for _, reg := range t.procs {
		matched, _ := regexp.MatchString(reg, name)
		if matched {
			return true
		}
	}
	return false
}

func (t *Topbeat) addMemPercentage(m *MemStat) {

	if m.Total == 0 {
		return
	}

	perc := float64(m.Used) / float64(m.Total)
	m.UsedPercent = Round(perc, .5, 2)
}

func addFileSystemUsedPercentage(f *FileSystemStat) {
	if f.Total == 0 {
		return
	}

	perc := float64(f.Used) / float64(f.Total)
	f.UsedPercent = Round(perc, .5, 2)
}

func (t *Topbeat) addCpuPercentage(t2 *CpuTimes) {

	t1 := t.lastCpuTimes

	if t1 != nil && t2 != nil {
		all_delta := t2.sum() - t1.sum()

		calculate := func(field2 uint64, field1 uint64) float64 {

			perc := 0.0
			delta := field2 - field1
			perc = float64(delta) / float64(all_delta)
			return Round(perc, .5, 2)
		}

		t2.UserPercent = calculate(t2.User, t1.User)
		t2.SystemPercent = calculate(t2.System, t1.System)
	}

	t.lastCpuTimes = t2

}

func (t *Topbeat) addProcMemPercentage(proc *Process, total_phymem uint64) {

	// in unit tests, total_phymem is set to a value greater than zero

	if total_phymem == 0 {
		mem_stat, err := GetMemory()
		if err != nil {
			logp.Warn("Getting memory details: %v", err)
			return
		}
		total_phymem = mem_stat.Total
	}

	perc := (float64(proc.Mem.Rss) / float64(total_phymem))

	proc.Mem.RssPercent = Round(perc, .5, 2)
}

func (t *Topbeat) addProcCpuPercentage(proc *Process) {

	oproc, ok := t.procsMap[proc.Pid]
	if ok {

		delta_proc := (proc.Cpu.User - oproc.Cpu.User) + (proc.Cpu.System - oproc.Cpu.System)
		delta_time := proc.ctime.Sub(oproc.ctime).Nanoseconds() / 1e6 // in milliseconds
		perc := float64(delta_proc) / float64(delta_time)

		t.procsMap[proc.Pid] = proc

		proc.Cpu.UserPercent = Round(perc, .5, 2)

	}
}

func Round(val float64, roundOn float64, places int) (newVal float64) {
	var round float64
	pow := math.Pow(10, float64(places))
	digit := pow * val
	_, div := math.Modf(digit)
	if div >= roundOn {
		round = math.Ceil(digit)
	} else {
		round = math.Floor(digit)
	}
	newVal = round / pow
	return
}

func (t *CpuTimes) sum() uint64 {
	return t.User + t.Nice + t.System + t.Idle + t.IOWait + t.Irq + t.SoftIrq + t.Steal
}
<|endoftext|>