<commit_before>// scribble is a tiny JSON database
package scribble

import (
	"encoding/json"
	"fmt"
	"github.com/jcelliott/lumber"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

const Version = "1.0.2"

type (

	//
	Logger interface {
		Fatal(string, ...interface{})
		Error(string, ...interface{})
		Warn(string, ...interface{})
		Info(string, ...interface{})
		Debug(string, ...interface{})
		Trace(string, ...interface{})
	}

	// a Driver is what is used to interact with the scribble database. It runs
	// transactions, and provides log output
	Driver struct {
		mutex   sync.Mutex
		mutexes map[string]sync.Mutex
		dir     string // the directory where scribble will create the database
		log     Logger // the logger scribble will log to
	}
)

// New creates a new scribble database at the desired directory location, and
// returns a *Driver to then use for interacting with the database
func New(dir string, logger Logger) (driver *Driver, err error) {

	//
	dir = filepath.Clean(dir)

	//
	if logger == nil {
		logger = lumber.NewConsoleLogger(lumber.INFO)
	}

	logger.Info("Creating scribble database at '%v'...\n", dir)

	//
	driver = &Driver{
		dir:     dir,
		mutexes: make(map[string]sync.Mutex),
		log:     logger,
	}

	// create database
	return driver, mkDir(dir)
}

// Read a record from the database
func (d *Driver) Read(collection, resource string, v interface{}) error {

	//
	path := filepath.Join(collection, resource)
	dir := filepath.Join(d.dir, path)

	//
	switch fi, err := stat(dir); {

	// if fi is nil or error is not nil return
	case fi == nil, err != nil:
		return fmt.Errorf("Unable to find file or directory named %v\n", path)

	// if the path is a directory, attempt to read all entries into v
	case fi.Mode().IsDir():

		// read all the files in the transaction.Collection; an error here just means
		// the collection is either empty or doesn't exist
		files, _ := ioutil.ReadDir(dir)

		// the files read from the database
		var f []string

		// iterate over each of the files, attempting to read the file. If successful
		// append the files to the collection of read files
		for _, file := range files {
			b, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
			if err != nil {
				return err
			}

			// append read file
			f = append(f, string(b))
		}

		// unmarhsal the read files as a comma delimeted byte array
		return json.Unmarshal([]byte("["+strings.Join(f, ",")+"]"), v)

		// if the path is a file, attempt to read the single file
	case fi.Mode().IsRegular():

		// read record from database
		b, err := ioutil.ReadFile(dir + ".json")
		if err != nil {
			return err
		}

		// unmarshal data
		return json.Unmarshal(b, &v)
	}

	return nil
}

// Write locks the database and attempts to write the record to the database under
// the [collection] specified with the [resource] name given
func (d *Driver) Write(collection, resource string, v interface{}) error {

	mutex := d.getOrCreateMutex(collection)
	mutex.Lock()
	defer mutex.Unlock()

	//
	dir := filepath.Join(d.dir, collection)

	//
	b, err := json.MarshalIndent(v, "", "\t")
	if err != nil {
		return err
	}

	// create collection directory
	if err := mkDir(dir); err != nil {
		return err
	}

	finalPath := filepath.Join(dir, resource+".json")
	tmpPath := finalPath + "~"

	// write marshaled data to the temp file
	if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil {
		return err
	}

	// move final file into place
	return os.Rename(tmpPath, finalPath)
}

// Delete locks that database and then attempts to remove the collection/resource
// specified by [path]
func (d *Driver) Delete(collection, resource string) error {
	path := filepath.Join(collection, resource)
	//
	mutex := d.getOrCreateMutex(path)
	mutex.Lock()
	defer mutex.Unlock()

	//
	dir := filepath.Join(d.dir, path)

	switch fi, err := stat(dir); {

	// if fi is nil or error is not nil return
	case fi == nil, err != nil:
		return fmt.Errorf("Unable to find file or directory named %v\n", path)

	// remove directory and all contents
	case fi.Mode().IsDir():
		return os.RemoveAll(dir)

	// remove file
	case fi.Mode().IsRegular():
		return os.RemoveAll(dir + ".json")
	}

	return nil
}

//
func stat(path string) (fi os.FileInfo, err error) {

	// check for dir, if path isn't a directory check to see if it's a file
	if fi, err = os.Stat(path); os.IsNotExist(err) {
		fi, err = os.Stat(path + ".json")
	}

	return
}

// getOrCreateMutex creates a new collection specific mutex any time a collection
// is being modfied to avoid unsafe operations
func (d *Driver) getOrCreateMutex(collection string) sync.Mutex {

	d.mutex.Lock()
	defer d.mutex.Unlock()

	m, ok := d.mutexes[collection]

	// if the mutex doesn't exist make it
	if !ok {
		m = sync.Mutex{}
		d.mutexes[collection] = m
	}

	return m
}

// mkDir is a simple wrapper that attempts to make a directory at a specified
// location
func mkDir(d string) (err error) {

	//
	dir, _ := os.Stat(d)

	switch {
	case dir == nil:
		err = os.MkdirAll(d, 0755)
	case !dir.IsDir():
		err = os.ErrInvalid
	}

	return
}
<commit_msg>updating the mkDir wrapper to be less weird (you'll see what i mean)<commit_after>// scribble is a tiny JSON database
package scribble

import (
	"encoding/json"
	"fmt"
	"github.com/jcelliott/lumber"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

const Version = "1.0.3"

type (

	//
	Logger interface {
		Fatal(string, ...interface{})
		Error(string, ...interface{})
		Warn(string, ...interface{})
		Info(string, ...interface{})
		Debug(string, ...interface{})
		Trace(string, ...interface{})
	}

	// a Driver is what is used to interact with the scribble database. It runs
	// transactions, and provides log output
	Driver struct {
		mutex   sync.Mutex
		mutexes map[string]sync.Mutex
		dir     string // the directory where scribble will create the database
		log     Logger // the logger scribble will log to
	}
)

// New creates a new scribble database at the desired directory location, and
// returns a *Driver to then use for interacting with the database
func New(dir string, logger Logger) (driver *Driver, err error) {

	//
	dir = filepath.Clean(dir)

	//
	if logger == nil {
		logger = lumber.NewConsoleLogger(lumber.INFO)
	}

	logger.Info("Creating scribble database at '%v'...\n", dir)

	//
	driver = &Driver{
		dir:     dir,
		mutexes: make(map[string]sync.Mutex),
		log:     logger,
	}

	// create database
	return driver, mkDir(dir)
}

// Read a record from the database
func (d *Driver) Read(collection, resource string, v interface{}) error {

	//
	path := filepath.Join(collection, resource)
	dir := filepath.Join(d.dir, path)

	//
	switch fi, err := stat(dir); {

	// if fi is nil or error is not nil return
	case fi == nil, err != nil:
		return fmt.Errorf("Unable to find file or directory named %v\n", path)

	// if the path is a directory, attempt to read all entries into v
	case fi.Mode().IsDir():

		// read all the files in the transaction.Collection; an error here just means
		// the collection is either empty or doesn't exist
		files, _ := ioutil.ReadDir(dir)

		// the files read from the database
		var f []string

		// iterate over each of the files, attempting to read the file. If successful
		// append the files to the collection of read files
		for _, file := range files {
			b, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
			if err != nil {
				return err
			}

			// append read file
			f = append(f, string(b))
		}

		// unmarhsal the read files as a comma delimeted byte array
		return json.Unmarshal([]byte("["+strings.Join(f, ",")+"]"), v)

		// if the path is a file, attempt to read the single file
	case fi.Mode().IsRegular():

		// read record from database
		b, err := ioutil.ReadFile(dir + ".json")
		if err != nil {
			return err
		}

		// unmarshal data
		return json.Unmarshal(b, &v)
	}

	return nil
}

// Write locks the database and attempts to write the record to the database under
// the [collection] specified with the [resource] name given
func (d *Driver) Write(collection, resource string, v interface{}) error {

	mutex := d.getOrCreateMutex(collection)
	mutex.Lock()
	defer mutex.Unlock()

	//
	dir := filepath.Join(d.dir, collection)

	//
	b, err := json.MarshalIndent(v, "", "\t")
	if err != nil {
		return err
	}

	// create collection directory
	if err := mkDir(dir); err != nil {
		return err
	}

	finalPath := filepath.Join(dir, resource+".json")
	tmpPath := finalPath + "~"

	// write marshaled data to the temp file
	if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil {
		return err
	}

	// move final file into place
	return os.Rename(tmpPath, finalPath)
}

// Delete locks that database and then attempts to remove the collection/resource
// specified by [path]
func (d *Driver) Delete(collection, resource string) error {
	path := filepath.Join(collection, resource)
	//
	mutex := d.getOrCreateMutex(path)
	mutex.Lock()
	defer mutex.Unlock()

	//
	dir := filepath.Join(d.dir, path)

	switch fi, err := stat(dir); {

	// if fi is nil or error is not nil return
	case fi == nil, err != nil:
		return fmt.Errorf("Unable to find file or directory named %v\n", path)

	// remove directory and all contents
	case fi.Mode().IsDir():
		return os.RemoveAll(dir)

	// remove file
	case fi.Mode().IsRegular():
		return os.RemoveAll(dir + ".json")
	}

	return nil
}

//
func stat(path string) (fi os.FileInfo, err error) {

	// check for dir, if path isn't a directory check to see if it's a file
	if fi, err = os.Stat(path); os.IsNotExist(err) {
		fi, err = os.Stat(path + ".json")
	}

	return
}

// getOrCreateMutex creates a new collection specific mutex any time a collection
// is being modfied to avoid unsafe operations
func (d *Driver) getOrCreateMutex(collection string) sync.Mutex {

	d.mutex.Lock()
	defer d.mutex.Unlock()

	m, ok := d.mutexes[collection]

	// if the mutex doesn't exist make it
	if !ok {
		m = sync.Mutex{}
		d.mutexes[collection] = m
	}

	return m
}

// mkDir is a simple wrapper that attempts to make a directory at a specified
// location
func mkDir(d string) (err error) {

	//
	if _, err = os.Stat(d); err != nil {
		return
	}

	return os.MkdirAll(d, 0755)
}
<|endoftext|>


<commit_before>package main

import (
	"testing"
)

func TestWriteMessage(t *testing.T) {
	/*
		TODO: write tests.
		n := new(slackNotifier)
		b := &cbpb.Build{
			ProjectId: "my-project-id",
			Id:        "some-build-id",
			Status:    cbpb.Build_SUCCESS,
			LogUrl:    "https://some.example.com/log/url?foo=bar",
		}

		got, err := n.writeMessage(b)
		if err != nil {
			t.Fatalf("writeMessage failed: %v", err)
		}

		want := &slack.WebhookMessage{
			Attachments: []slack.Attachment{{
				Text:  "Cloud Build (my-project-id, some-build-id): SUCCESS",
				Color: "good",
				Actions: []slack.AttachmentAction{{
					Text: "View Logs",
					Type: "button",
					URL:  "https://some.example.com/log/url?foo=bar&utm_campaign=google-cloud-build-notifiers&utm_medium=chat&utm_source=google-cloud-build",
				}},
			}},
		}

		if diff := cmp.Diff(got, want); diff != "" {
			t.Errorf("writeMessage got unexpected diff: %s", diff)
		}
	*/
}
<commit_msg>Add unit tests<commit_after>package main

import (
	"testing"

	"github.com/google/go-cmp/cmp"
	chat "google.golang.org/api/chat/v1"
	cbpb "google.golang.org/genproto/googleapis/devtools/cloudbuild/v1"
)

func TestWriteMessage(t *testing.T) {

	n := new(googlechatNotifier)
	b := &cbpb.Build{
		ProjectId: "my-project-id",
		Id:        "some-build-id",
		Status:    cbpb.Build_SUCCESS,
		LogUrl:    "https://some.example.com/log/url?foo=bar",
	}

	got, err := n.writeMessage(b)
	if err != nil {
		t.Fatalf("writeMessage failed: %v", err)
	}

	want := &chat.Message{
		Cards: []*chat.Card{{
			Header: &chat.CardHeader{
				ImageUrl: "https://www.gstatic.com/images/icons/material/system/2x/check_circle_googgreen_48dp.png",
				Subtitle: "my-project-id",
				Title:    "Build some-bui Status: SUCCESS",
			},
			Sections: []*chat.Section{
				{
					Widgets: []*chat.WidgetMarkup{
						{
							KeyValue: &chat.KeyValue{
								TopLabel: "Duration",
								Content:  "0 min 0 sec",
							},
						},
					},
				},
				{
					Widgets: []*chat.WidgetMarkup{
						{
							Buttons: []*chat.Button{
								{
									TextButton: &chat.TextButton{
										Text: "open logs",
										OnClick: &chat.OnClick{
											OpenLink: &chat.OpenLink{
												Url: "https://some.example.com/log/url?foo=bar&utm_campaign=google-cloud-build-notifiers&utm_medium=chat&utm_source=google-cloud-build",
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
		}}

	if diff := cmp.Diff(got, want); diff != "" {
		t.Errorf("writeMessage got unexpected diff: %s", diff)
	}

}
<|endoftext|>


<commit_before>// Package httpclient provides an HTTP client with several conveniency
// functions.
//
// This package is cross platform, so it works on both standalone deployments
// as well as on App Engine.
package httpclient
<commit_msg>Clarify that this package supports profiling<commit_after>// Package httpclient provides an HTTP client with several conveniency
// functions.
//
// This package is cross platform, so it works on both standalone deployments
// as well as on App Engine.
//
// Also, requests made with an httpclient instance are properly measured
// when profiling an app.
package httpclient
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package builder

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	log "github.com/Sirupsen/logrus"

	"mynewt.apache.org/newt/newt/interfaces"
	"mynewt.apache.org/newt/newt/project"
	"mynewt.apache.org/newt/newt/target"
	"mynewt.apache.org/newt/newt/toolchain"
	"mynewt.apache.org/newt/util"
)

const CMAKELISTS_FILENAME string = "CMakeLists.txt"

func CmakeListsPath() string {
	return project.GetProject().BasePath + "/" + CMAKELISTS_FILENAME
}

func EscapeName(name string) string {
	return strings.Replace(name, "/", "_", -1)
}

func trimProjectPath(path string) string {
	proj := interfaces.GetProject()
	path = strings.TrimPrefix(path, proj.Path()+"/")
	return path
}

func trimProjectPathSlice(elements []string) {
	for e := range elements {
		elements[e] = trimProjectPath(elements[e])
	}
}

func extractIncludes(flags *[]string, includes *[]string, other *[]string) {
	for _, f := range *flags {
		if strings.HasPrefix(f, "-I") {
			*includes = append(*includes, strings.TrimPrefix(f, "-I"))
		} else {
			*other = append(*other, f)
		}
	}
}

func CmakeSourceObjectWrite(w io.Writer, cj toolchain.CompilerJob, includeDirs *[]string) {
	c := cj.Compiler

	compileFlags := []string{}
	otherFlags := []string{}

	switch cj.CompilerType {
	case toolchain.COMPILER_TYPE_C:
		compileFlags = append(compileFlags, c.GetCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Cflags...)
	case toolchain.COMPILER_TYPE_ASM:
		compileFlags = append(compileFlags, c.GetCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetCompilerInfo().Aflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Aflags...)
	case toolchain.COMPILER_TYPE_CPP:
		compileFlags = append(compileFlags, c.GetCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetCompilerInfo().CXXflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().CXXflags...)
	}

	extractIncludes(&compileFlags, includeDirs, &otherFlags)
	cj.Filename = trimProjectPath(cj.Filename)

	// Sort and remove duplicate flags
	otherFlags = util.SortFields(otherFlags...)

	fmt.Fprintf(w, `set_property(SOURCE %s APPEND_STRING
														PROPERTY
														COMPILE_FLAGS
														"%s")`,
		cj.Filename,
		strings.Replace(strings.Join(otherFlags, " "), "\"", "\\\\\\\"", -1))
	fmt.Fprintln(w)
}

func (b *Builder) CMakeBuildPackageWrite(w io.Writer, bpkg *BuildPackage) (*BuildPackage, error) {
	entries, err := b.collectCompileEntriesBpkg(bpkg)
	if err != nil {
		return nil, err
	}

	if len(entries) <= 0 {
		return nil, nil
	}

	otherIncludes := []string{}
	files := []string{}

	for _, s := range entries {
		filename := filepath.ToSlash(s.Filename)
		if s.Compiler.ShouldIgnoreFile(filename) {
			log.Infof("Ignoring %s because package dictates it.\n", filename)
			continue
		}

		CmakeSourceObjectWrite(w, s, &otherIncludes)
		s.Filename = trimProjectPath(s.Filename)
		files = append(files, s.Filename)
	}

	if len(files) <= 0 {
		return nil, nil
	}

	pkgName := bpkg.rpkg.Lpkg.Name()

	util.StatusMessage(util.VERBOSITY_DEFAULT, "Generating CMakeLists.txt for %s\n", pkgName)
	fmt.Fprintf(w, "# Generating CMakeLists.txt for %s\n\n", pkgName)
	fmt.Fprintf(w, "add_library(%s %s)\n\n",
		EscapeName(pkgName),
		strings.Join(files, " "))

	archivePath := filepath.Dir(b.ArchivePath(bpkg))
	archivePath = trimProjectPath(archivePath)
	CmakeCompilerInfoWrite(w, archivePath, bpkg, entries[0], otherIncludes)

	return bpkg, nil
}

func (b *Builder) CMakeTargetWrite(w io.Writer, targetCompiler *toolchain.Compiler) error {
	bpkgs := b.sortedBuildPackages()

	c := targetCompiler

	builtPackages := []*BuildPackage{}
	for _, bpkg := range bpkgs {
		builtPackage, err := b.CMakeBuildPackageWrite(w, bpkg)
		if err != nil {
			return err
		}

		if builtPackage != nil {
			builtPackages = append(builtPackages, builtPackage)
		}
	}

	elfName := "cmake_" + filepath.Base(b.AppElfPath())
	fmt.Fprintf(w, "# Generating code for %s\n\n", elfName)

	var targetObjectsBuffer bytes.Buffer

	for _, bpkg := range builtPackages {
		targetObjectsBuffer.WriteString(fmt.Sprintf("%s ",
			EscapeName(bpkg.rpkg.Lpkg.Name())))
	}

	elfOutputDir := trimProjectPath(filepath.Dir(b.AppElfPath()))
	fmt.Fprintf(w, "file(WRITE %s \"\")\n", filepath.Join(elfOutputDir, "null.c"))
	fmt.Fprintf(w, "add_executable(%s %s)\n\n", elfName, filepath.Join(elfOutputDir, "null.c"))

	if c.GetLdResolveCircularDeps() {
		fmt.Fprintf(w, "target_link_libraries(%s -Wl,--start-group %s -Wl,--end-group)\n",
			elfName, targetObjectsBuffer.String())
	} else {
		fmt.Fprintf(w, "target_link_libraries(%s %s)\n",
			elfName, targetObjectsBuffer.String())
	}

	var flags []string
	flags = append(flags, c.GetCompilerInfo().Cflags...)
	flags = append(flags, c.GetLocalCompilerInfo().Cflags...)
	flags = append(flags, c.GetCompilerInfo().CXXflags...)
	flags = append(flags, c.GetLocalCompilerInfo().CXXflags...)

	fmt.Fprintf(w, `set_property(TARGET %s APPEND_STRING
														PROPERTY
														COMPILE_FLAGS
														"%s")`,
		elfName,
		strings.Replace(strings.Join(flags, " "), "\"", "\\\\\\\"", -1))
	fmt.Fprintln(w)

	lFlags := append(c.GetCompilerInfo().Lflags, c.GetLocalCompilerInfo().Lflags...)
	for _, ld := range c.LinkerScripts {
		lFlags = append(lFlags, "-T"+ld)
	}

	lFlags = append(lFlags, c.GetLocalCompilerInfo().Cflags...)
	lFlags = append(lFlags, c.GetLocalCompilerInfo().CXXflags...)
	fmt.Fprintf(w, `set_target_properties(%s
							PROPERTIES
							ARCHIVE_OUTPUT_DIRECTORY %s
							LIBRARY_OUTPUT_DIRECTORY %s
							RUNTIME_OUTPUT_DIRECTORY %s
							LINK_FLAGS "%s"
							LINKER_LANGUAGE C)`,
		elfName,
		elfOutputDir,
		elfOutputDir,
		elfOutputDir,
		strings.Replace(strings.Join(lFlags, " "), "\"", "\\\\\\\"", -1))

	fmt.Fprintln(w)

	libs := strings.Join(getLibsFromLinkerFlags(lFlags), " ")
	fmt.Fprintf(w, "# Workaround for gcc linker woes\n")
	fmt.Fprintf(w, "set(CMAKE_C_LINK_EXECUTABLE \"${CMAKE_C_LINK_EXECUTABLE} %s\")\n", libs)
	fmt.Fprintln(w)

	return nil
}

func getLibsFromLinkerFlags(lflags []string) []string {
	libs := []string{}

	for _, flag := range lflags {
		if strings.HasPrefix(flag, "-l") {
			libs = append(libs, flag)
		}
	}

	return libs
}

func CmakeCompilerInfoWrite(w io.Writer, archiveFile string, bpkg *BuildPackage,
	cj toolchain.CompilerJob, otherIncludes []string) {
	c := cj.Compiler

	var includes []string

	includes = append(includes, c.GetCompilerInfo().Includes...)
	includes = append(includes, c.GetLocalCompilerInfo().Includes...)
	includes = append(includes, otherIncludes...)

	// Sort and remove duplicate flags
	includes = util.SortFields(includes...)
	trimProjectPathSlice(includes)

	fmt.Fprintf(w, `set_target_properties(%s
							PROPERTIES
							ARCHIVE_OUTPUT_DIRECTORY %s
							LIBRARY_OUTPUT_DIRECTORY %s
							RUNTIME_OUTPUT_DIRECTORY %s)`,
		EscapeName(bpkg.rpkg.Lpkg.Name()),
		archiveFile,
		archiveFile,
		archiveFile,
	)
	fmt.Fprintln(w)
	fmt.Fprintf(w, "target_include_directories(%s PUBLIC %s)\n\n",
		EscapeName(bpkg.rpkg.Lpkg.Name()),
		strings.Join(includes, " "))
}

func (t *TargetBuilder) CMakeTargetBuilderWrite(w io.Writer, targetCompiler *toolchain.Compiler) error {
	if err := t.PrepBuild(); err != nil {
		return err
	}

	/* Build the Apps */
	project.ResetDeps(t.AppList)

	targetCompiler.LinkerScripts = t.bspPkg.LinkerScripts

	if err := t.bspPkg.Reload(t.AppBuilder.cfg.SettingValues()); err != nil {
		return err
	}

	if err := t.AppBuilder.CMakeTargetWrite(w, targetCompiler); err != nil {
		return err
	}

	return nil
}

func CmakeCompilerWrite(w io.Writer, c *toolchain.Compiler) {
	/* Since CMake 3 it is required to set a full path to the compiler */
	/* TODO: get rid of the prefix to /usr/bin */
	fmt.Fprintln(w, "set(CMAKE_SYSTEM_NAME Generic)")
	fmt.Fprintln(w, "set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)")
	fmt.Fprintf(w, "set(CMAKE_C_COMPILER %s)\n", c.GetCcPath())
	fmt.Fprintf(w, "set(CMAKE_CXX_COMPILER %s)\n", c.GetCppPath())
	fmt.Fprintf(w, "set(CMAKE_ASM_COMPILER %s)\n", c.GetAsPath())
	/* TODO: cmake returns error on link */
	//fmt.Fprintf(w, "set(CMAKE_AR %s)\n", c.GetArPath())
	fmt.Fprintln(w)
}

func CmakeHeaderWrite(w io.Writer, c *toolchain.Compiler, targetName string) {
	fmt.Fprintln(w, "cmake_minimum_required(VERSION 3.7)\n")
	CmakeCompilerWrite(w, c)
	fmt.Fprintf(w, "project(%s VERSION 0.0.0 LANGUAGES C ASM)\n\n", targetName)
	fmt.Fprintln(w, "SET(CMAKE_C_FLAGS_BACKUP  \"${CMAKE_C_FLAGS}\")")
	fmt.Fprintln(w, "SET(CMAKE_CXX_FLAGS_BACKUP  \"${CMAKE_CXX_FLAGS}\")")
	fmt.Fprintln(w, "SET(CMAKE_ASM_FLAGS_BACKUP  \"${CMAKE_ASM_FLAGS}\")")
	fmt.Fprintln(w)
}

func CMakeTargetGenerate(target *target.Target) error {
	CmakeFileHandle, err := os.Create(CmakeListsPath())
	if err != nil {
		return util.ChildNewtError(err)
	}

	var b = bytes.Buffer{}
	w := bufio.NewWriter(&b)
	defer CmakeFileHandle.Close()

	targetBuilder, err := NewTargetBuilder(target)
	if err != nil {
		return err
	}

	targetCompiler, err := targetBuilder.NewCompiler("", "")
	if err != nil {
		return err
	}

	CmakeHeaderWrite(w, targetCompiler, target.ShortName())

	if err := targetBuilder.CMakeTargetBuilderWrite(w, targetCompiler); err != nil {
		return err
	}

	w.Flush()

	CmakeFileHandle.Write(b.Bytes())
	return nil
}
<commit_msg>cmake: Add CXX to project languages<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package builder

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	log "github.com/Sirupsen/logrus"

	"mynewt.apache.org/newt/newt/interfaces"
	"mynewt.apache.org/newt/newt/project"
	"mynewt.apache.org/newt/newt/target"
	"mynewt.apache.org/newt/newt/toolchain"
	"mynewt.apache.org/newt/util"
)

const CMAKELISTS_FILENAME string = "CMakeLists.txt"

func CmakeListsPath() string {
	return project.GetProject().BasePath + "/" + CMAKELISTS_FILENAME
}

func EscapeName(name string) string {
	return strings.Replace(name, "/", "_", -1)
}

func trimProjectPath(path string) string {
	proj := interfaces.GetProject()
	path = strings.TrimPrefix(path, proj.Path()+"/")
	return path
}

func trimProjectPathSlice(elements []string) {
	for e := range elements {
		elements[e] = trimProjectPath(elements[e])
	}
}

func extractIncludes(flags *[]string, includes *[]string, other *[]string) {
	for _, f := range *flags {
		if strings.HasPrefix(f, "-I") {
			*includes = append(*includes, strings.TrimPrefix(f, "-I"))
		} else {
			*other = append(*other, f)
		}
	}
}

func CmakeSourceObjectWrite(w io.Writer, cj toolchain.CompilerJob, includeDirs *[]string) {
	c := cj.Compiler

	compileFlags := []string{}
	otherFlags := []string{}

	switch cj.CompilerType {
	case toolchain.COMPILER_TYPE_C:
		compileFlags = append(compileFlags, c.GetCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Cflags...)
	case toolchain.COMPILER_TYPE_ASM:
		compileFlags = append(compileFlags, c.GetCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetCompilerInfo().Aflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Aflags...)
	case toolchain.COMPILER_TYPE_CPP:
		compileFlags = append(compileFlags, c.GetCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().Cflags...)
		compileFlags = append(compileFlags, c.GetCompilerInfo().CXXflags...)
		compileFlags = append(compileFlags, c.GetLocalCompilerInfo().CXXflags...)
	}

	extractIncludes(&compileFlags, includeDirs, &otherFlags)
	cj.Filename = trimProjectPath(cj.Filename)

	// Sort and remove duplicate flags
	otherFlags = util.SortFields(otherFlags...)

	fmt.Fprintf(w, `set_property(SOURCE %s APPEND_STRING
														PROPERTY
														COMPILE_FLAGS
														"%s")`,
		cj.Filename,
		strings.Replace(strings.Join(otherFlags, " "), "\"", "\\\\\\\"", -1))
	fmt.Fprintln(w)
}

func (b *Builder) CMakeBuildPackageWrite(w io.Writer, bpkg *BuildPackage) (*BuildPackage, error) {
	entries, err := b.collectCompileEntriesBpkg(bpkg)
	if err != nil {
		return nil, err
	}

	if len(entries) <= 0 {
		return nil, nil
	}

	otherIncludes := []string{}
	files := []string{}

	for _, s := range entries {
		filename := filepath.ToSlash(s.Filename)
		if s.Compiler.ShouldIgnoreFile(filename) {
			log.Infof("Ignoring %s because package dictates it.\n", filename)
			continue
		}

		CmakeSourceObjectWrite(w, s, &otherIncludes)
		s.Filename = trimProjectPath(s.Filename)
		files = append(files, s.Filename)
	}

	if len(files) <= 0 {
		return nil, nil
	}

	pkgName := bpkg.rpkg.Lpkg.Name()

	util.StatusMessage(util.VERBOSITY_DEFAULT, "Generating CMakeLists.txt for %s\n", pkgName)
	fmt.Fprintf(w, "# Generating CMakeLists.txt for %s\n\n", pkgName)
	fmt.Fprintf(w, "add_library(%s %s)\n\n",
		EscapeName(pkgName),
		strings.Join(files, " "))

	archivePath := filepath.Dir(b.ArchivePath(bpkg))
	archivePath = trimProjectPath(archivePath)
	CmakeCompilerInfoWrite(w, archivePath, bpkg, entries[0], otherIncludes)

	return bpkg, nil
}

func (b *Builder) CMakeTargetWrite(w io.Writer, targetCompiler *toolchain.Compiler) error {
	bpkgs := b.sortedBuildPackages()

	c := targetCompiler

	builtPackages := []*BuildPackage{}
	for _, bpkg := range bpkgs {
		builtPackage, err := b.CMakeBuildPackageWrite(w, bpkg)
		if err != nil {
			return err
		}

		if builtPackage != nil {
			builtPackages = append(builtPackages, builtPackage)
		}
	}

	elfName := "cmake_" + filepath.Base(b.AppElfPath())
	fmt.Fprintf(w, "# Generating code for %s\n\n", elfName)

	var targetObjectsBuffer bytes.Buffer

	for _, bpkg := range builtPackages {
		targetObjectsBuffer.WriteString(fmt.Sprintf("%s ",
			EscapeName(bpkg.rpkg.Lpkg.Name())))
	}

	elfOutputDir := trimProjectPath(filepath.Dir(b.AppElfPath()))
	fmt.Fprintf(w, "file(WRITE %s \"\")\n", filepath.Join(elfOutputDir, "null.c"))
	fmt.Fprintf(w, "add_executable(%s %s)\n\n", elfName, filepath.Join(elfOutputDir, "null.c"))

	if c.GetLdResolveCircularDeps() {
		fmt.Fprintf(w, "target_link_libraries(%s -Wl,--start-group %s -Wl,--end-group)\n",
			elfName, targetObjectsBuffer.String())
	} else {
		fmt.Fprintf(w, "target_link_libraries(%s %s)\n",
			elfName, targetObjectsBuffer.String())
	}

	var flags []string
	flags = append(flags, c.GetCompilerInfo().Cflags...)
	flags = append(flags, c.GetLocalCompilerInfo().Cflags...)
	flags = append(flags, c.GetCompilerInfo().CXXflags...)
	flags = append(flags, c.GetLocalCompilerInfo().CXXflags...)

	fmt.Fprintf(w, `set_property(TARGET %s APPEND_STRING
														PROPERTY
														COMPILE_FLAGS
														"%s")`,
		elfName,
		strings.Replace(strings.Join(flags, " "), "\"", "\\\\\\\"", -1))
	fmt.Fprintln(w)

	lFlags := append(c.GetCompilerInfo().Lflags, c.GetLocalCompilerInfo().Lflags...)
	for _, ld := range c.LinkerScripts {
		lFlags = append(lFlags, "-T"+ld)
	}

	lFlags = append(lFlags, c.GetLocalCompilerInfo().Cflags...)
	lFlags = append(lFlags, c.GetLocalCompilerInfo().CXXflags...)
	fmt.Fprintf(w, `set_target_properties(%s
							PROPERTIES
							ARCHIVE_OUTPUT_DIRECTORY %s
							LIBRARY_OUTPUT_DIRECTORY %s
							RUNTIME_OUTPUT_DIRECTORY %s
							LINK_FLAGS "%s"
							LINKER_LANGUAGE C)`,
		elfName,
		elfOutputDir,
		elfOutputDir,
		elfOutputDir,
		strings.Replace(strings.Join(lFlags, " "), "\"", "\\\\\\\"", -1))

	fmt.Fprintln(w)

	libs := strings.Join(getLibsFromLinkerFlags(lFlags), " ")
	fmt.Fprintf(w, "# Workaround for gcc linker woes\n")
	fmt.Fprintf(w, "set(CMAKE_C_LINK_EXECUTABLE \"${CMAKE_C_LINK_EXECUTABLE} %s\")\n", libs)
	fmt.Fprintln(w)

	return nil
}

func getLibsFromLinkerFlags(lflags []string) []string {
	libs := []string{}

	for _, flag := range lflags {
		if strings.HasPrefix(flag, "-l") {
			libs = append(libs, flag)
		}
	}

	return libs
}

func CmakeCompilerInfoWrite(w io.Writer, archiveFile string, bpkg *BuildPackage,
	cj toolchain.CompilerJob, otherIncludes []string) {
	c := cj.Compiler

	var includes []string

	includes = append(includes, c.GetCompilerInfo().Includes...)
	includes = append(includes, c.GetLocalCompilerInfo().Includes...)
	includes = append(includes, otherIncludes...)

	// Sort and remove duplicate flags
	includes = util.SortFields(includes...)
	trimProjectPathSlice(includes)

	fmt.Fprintf(w, `set_target_properties(%s
							PROPERTIES
							ARCHIVE_OUTPUT_DIRECTORY %s
							LIBRARY_OUTPUT_DIRECTORY %s
							RUNTIME_OUTPUT_DIRECTORY %s)`,
		EscapeName(bpkg.rpkg.Lpkg.Name()),
		archiveFile,
		archiveFile,
		archiveFile,
	)
	fmt.Fprintln(w)
	fmt.Fprintf(w, "target_include_directories(%s PUBLIC %s)\n\n",
		EscapeName(bpkg.rpkg.Lpkg.Name()),
		strings.Join(includes, " "))
}

func (t *TargetBuilder) CMakeTargetBuilderWrite(w io.Writer, targetCompiler *toolchain.Compiler) error {
	if err := t.PrepBuild(); err != nil {
		return err
	}

	/* Build the Apps */
	project.ResetDeps(t.AppList)

	targetCompiler.LinkerScripts = t.bspPkg.LinkerScripts

	if err := t.bspPkg.Reload(t.AppBuilder.cfg.SettingValues()); err != nil {
		return err
	}

	if err := t.AppBuilder.CMakeTargetWrite(w, targetCompiler); err != nil {
		return err
	}

	return nil
}

func CmakeCompilerWrite(w io.Writer, c *toolchain.Compiler) {
	/* Since CMake 3 it is required to set a full path to the compiler */
	/* TODO: get rid of the prefix to /usr/bin */
	fmt.Fprintln(w, "set(CMAKE_SYSTEM_NAME Generic)")
	fmt.Fprintln(w, "set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)")
	fmt.Fprintf(w, "set(CMAKE_C_COMPILER %s)\n", c.GetCcPath())
	fmt.Fprintf(w, "set(CMAKE_CXX_COMPILER %s)\n", c.GetCppPath())
	fmt.Fprintf(w, "set(CMAKE_ASM_COMPILER %s)\n", c.GetAsPath())
	/* TODO: cmake returns error on link */
	//fmt.Fprintf(w, "set(CMAKE_AR %s)\n", c.GetArPath())
	fmt.Fprintln(w)
}

func CmakeHeaderWrite(w io.Writer, c *toolchain.Compiler, targetName string) {
	fmt.Fprintln(w, "cmake_minimum_required(VERSION 3.7)\n")
	CmakeCompilerWrite(w, c)
	fmt.Fprintf(w, "project(%s VERSION 0.0.0 LANGUAGES C CXX ASM)\n\n", targetName)
	fmt.Fprintln(w, "SET(CMAKE_C_FLAGS_BACKUP  \"${CMAKE_C_FLAGS}\")")
	fmt.Fprintln(w, "SET(CMAKE_CXX_FLAGS_BACKUP  \"${CMAKE_CXX_FLAGS}\")")
	fmt.Fprintln(w, "SET(CMAKE_ASM_FLAGS_BACKUP  \"${CMAKE_ASM_FLAGS}\")")
	fmt.Fprintln(w)
}

func CMakeTargetGenerate(target *target.Target) error {
	CmakeFileHandle, err := os.Create(CmakeListsPath())
	if err != nil {
		return util.ChildNewtError(err)
	}

	var b = bytes.Buffer{}
	w := bufio.NewWriter(&b)
	defer CmakeFileHandle.Close()

	targetBuilder, err := NewTargetBuilder(target)
	if err != nil {
		return err
	}

	targetCompiler, err := targetBuilder.NewCompiler("", "")
	if err != nil {
		return err
	}

	CmakeHeaderWrite(w, targetCompiler, target.ShortName())

	if err := targetBuilder.CMakeTargetBuilderWrite(w, targetCompiler); err != nil {
		return err
	}

	w.Flush()

	CmakeFileHandle.Write(b.Bytes())
	return nil
}
<|endoftext|>


<commit_before>//
// Written by Michael Mattioli
//

package main

import (
    "fmt"
)

// It takes one (1) minute to travel from one stop to another, there are eight (8) hours in a work
// day and sixty (60) minutes in an hour which totals four-hundred and eighty (480) minutes and,
// therefore, four-hundred and eighty (480) trips made in a work day.
const MaxTrips int = 480

// A BusDriver has a daily route composed of MaxTrips stops and a collection of gossip which can be
// shared with other BusDrivers.
type BusDriver struct {
    DailyRoute []int
    Gossips []*BusDriver
}

// NewBusDriver creates and initializes a new BusDriver. The BusDriver's route for the entire day is
// filled out and starts with one (1) gossip to share.
func NewBusDriver(r ...int) *BusDriver {

    var bd BusDriver

    // Initial gossip.
    bd.Gossips = append(bd.Gossips, &bd)

    // Determine daily route.
    var t int
    for {
        for s := range r {
            if t == MaxTrips {
                return &bd
            }
            bd.DailyRoute = append(bd.DailyRoute, r[s])
            t++
        }
    }

}

// ExchangeGossip shares unknown gossip between two BusDrivers.
func ExchangeGossip(bd1, bd2 *BusDriver) {
    giveGossip := func(src, dst *BusDriver) {
        for sg := range src.Gossips {
            var known bool
            for dg := range dst.Gossips {
                if src.Gossips[sg] == dst.Gossips[dg] {
                    known = true
                }
            }
            if !known {
                dst.Gossips = append(dst.Gossips, src.Gossips[sg])
            }
        }
    }
    giveGossip(bd1, bd2)
    giveGossip(bd2, bd1)
}

// AllGossipExchanged determines if all of the BusDrivers have shared their gossip with each other.
func AllGossipExchanged(bds ...*BusDriver) bool {
    for i := 0; i < len(bds) - 1; i++ {
        switch {
        // If a BusDriver only has 1 gossip then hasn't received anything.
        case len(bds[i].Gossips) == 1:
            return false
        // If any two BusDriver's gossips are not of equal length then everyone has not shared their
        // gossip with everyone.
        case len(bds[i].Gossips) != len(bds[i + 1].Gossips):
            return false
        }
    }
    return true
}

// BusDriverGossipExchange calculates the number of stops each BusDriver must make before they all
// have shared each other's gossip. Returns -1 if all of the BusDrivers have not shared and heard
// all of the gossip there is to share and hear by the end of their routes.
func BusDriverGossipExchange(r ...[]int) int {

    var drvs []*BusDriver

    for br := range r {
        drvs = append(drvs, NewBusDriver(r[br]...))
    }

    for t := 0; t < MaxTrips; t++ {
        for src := range drvs {
            for dst := range drvs {
                switch {
                // Dont't exchange gossip with the same BusDriver.
                case drvs[src] == drvs[dst]:
                    continue
                // Two different BusDrivers at the same bus stop.
                case drvs[src].DailyRoute[t] == drvs[dst].DailyRoute[t]:
                    ExchangeGossip(drvs[src], drvs[dst])
                }
            }
        }
        if AllGossipExchanged(drvs...) {
            return t + 1
        }
    }

    return -1

}

func main() {

    tests := [][][]int{
        [][]int {
            []int{3, 1, 2, 3},
            []int{3, 2, 3, 1},
            []int{4, 2, 3, 4, 5},
        },
        [][]int{
            []int{2, 1, 2},
            []int{5, 2, 8},
        },
        [][]int{
            []int{7, 11, 2, 2, 4, 8, 2, 2},
            []int{3, 0, 11, 8},
            []int{5, 11, 8, 10, 3, 11},
            []int{5, 9, 2, 5, 0, 3},
            []int{7, 4, 8, 2, 8, 1, 0, 5},
            []int{3, 6, 8, 9},
            []int{4, 2, 11, 3, 3},
        },
        [][]int {
            []int{12, 23, 15, 2, 8, 20, 21, 3, 23, 3, 27, 20, 0},
            []int{21, 14, 8, 20, 10, 0, 23, 3, 24, 23, 0, 19, 14, 12, 10, 9, 12, 12, 11, 6, 27, 5},
            []int{8, 18, 27, 10, 11, 22, 29, 23, 14},
            []int{13, 7, 14, 1, 9, 14, 16, 12, 0, 10, 13, 19, 16, 17},
            []int{24, 25, 21, 4, 6, 19, 1, 3, 26, 11, 22, 28, 14, 14, 27, 7, 20, 8, 7, 4, 1, 8, 10, 18, 21},
            []int{13, 20, 26, 22, 6, 5, 6, 23, 26, 2, 21, 16, 26, 24},
            []int{6, 7, 17, 2, 22, 23, 21},
            []int{23, 14, 22, 28, 10, 23, 7, 21, 3, 20, 24, 23, 8, 8, 21, 13, 15, 6, 9, 17, 27, 17, 13, 14},
            []int{23, 13, 1, 15, 5, 16, 7, 26, 22, 29, 17, 3, 14, 16, 16, 18, 6, 10, 3, 14, 10, 17, 27, 25},
            []int{25, 28, 5, 21, 8, 10, 27, 21, 23, 28, 7, 20, 6, 6, 9, 29, 27, 26, 24, 3, 12, 10, 21, 10, 12, 17},
            []int{26, 22, 26, 13, 10, 19, 3, 15, 2, 3, 25, 29, 25, 19, 19, 24, 1, 26, 22, 10, 17, 19, 28, 11, 22, 2, 13},
            []int{8, 4, 25, 15, 20, 9, 11, 3, 19},
            []int{24, 29, 4, 17, 2, 0, 8, 19, 11, 28, 13, 4, 16, 5, 15, 25, 16, 5, 6, 1, 0, 19, 7, 4, 6},
            []int{16, 25, 15, 17, 20, 27, 1, 11, 1, 18, 14, 23, 27, 25, 26, 17, 1},
        },
    }

    for t := range tests {
        fmt.Println(BusDriverGossipExchange(tests[t]...))
    }

}
<commit_msg>Minor syntactic improvements to challenge 264<commit_after>//
// Written by Michael Mattioli
//

package main

import (
    "fmt"
)

// It takes one (1) minute to travel from one stop to another, there are eight (8) hours in a work
// day and sixty (60) minutes in an hour which totals four-hundred and eighty (480) minutes and,
// therefore, four-hundred and eighty (480) trips made in a work day.
const MaxTrips int = 480

// A BusDriver has a daily route composed of MaxTrips stops and a collection of gossip which can be
// shared with other BusDrivers.
type BusDriver struct {
    DailyRoute []int
    Gossips []*BusDriver
}

// NewBusDriver creates and initializes a new BusDriver. The BusDriver's route for the entire day is
// filled out and starts with one (1) gossip to share.
func NewBusDriver(r ...int) *BusDriver {
    var bd BusDriver
    // Initial gossip.
    bd.Gossips = append(bd.Gossips, &bd)
    // Determine daily route.
    for t := 0; t < MaxTrips; t++ {
        for s := range r {
            bd.DailyRoute = append(bd.DailyRoute, r[s])
        }
    }
    return &bd
}

// ExchangeGossip shares unknown gossip between two BusDrivers.
func ExchangeGossip(bd1, bd2 *BusDriver) {
    giveGossip := func(src, dst *BusDriver) {
        for sg := range src.Gossips {
            var known bool
            for dg := range dst.Gossips {
                if src.Gossips[sg] == dst.Gossips[dg] {
                    known = true
                }
            }
            if !known {
                dst.Gossips = append(dst.Gossips, src.Gossips[sg])
            }
        }
    }
    giveGossip(bd1, bd2)
    giveGossip(bd2, bd1)
}

// AllGossipExchanged determines if all of the BusDrivers have shared their gossip with each other.
func AllGossipExchanged(bds ...*BusDriver) bool {
    for i := 0; i < len(bds) - 1; i++ {
        switch {
        // If a BusDriver only has 1 gossip then hasn't received anything.
        case len(bds[i].Gossips) == 1:
            return false
        // If any two BusDriver's gossips are not of equal length then everyone has not shared their
        // gossip with everyone.
        case len(bds[i].Gossips) != len(bds[i + 1].Gossips):
            return false
        }
    }
    return true
}

// BusDriverGossipExchange calculates the number of stops each BusDriver must make before they all
// have shared each other's gossip. Returns -1 if all of the BusDrivers have not shared and heard
// all of the gossip there is to share and hear by the end of their routes.
func BusDriverGossipExchange(r ...[]int) int {
    var drvs []*BusDriver
    for br := range r {
        drvs = append(drvs, NewBusDriver(r[br]...))
    }
    for t := 0; t < MaxTrips; t++ {
        for src := range drvs {
            for dst := range drvs {
                switch {
                // Dont't exchange gossip with the same BusDriver.
                case drvs[src] == drvs[dst]:
                    continue
                // Two different BusDrivers at the same bus stop.
                case drvs[src].DailyRoute[t] == drvs[dst].DailyRoute[t]:
                    ExchangeGossip(drvs[src], drvs[dst])
                }
            }
        }
        if AllGossipExchanged(drvs...) {
            return t + 1
        }
    }
    return -1
}

func main() {

    tests := [][][]int{
        [][]int {
            []int{3, 1, 2, 3},
            []int{3, 2, 3, 1},
            []int{4, 2, 3, 4, 5},
        },
        [][]int{
            []int{2, 1, 2},
            []int{5, 2, 8},
        },
        [][]int{
            []int{7, 11, 2, 2, 4, 8, 2, 2},
            []int{3, 0, 11, 8},
            []int{5, 11, 8, 10, 3, 11},
            []int{5, 9, 2, 5, 0, 3},
            []int{7, 4, 8, 2, 8, 1, 0, 5},
            []int{3, 6, 8, 9},
            []int{4, 2, 11, 3, 3},
        },
        [][]int {
            []int{12, 23, 15, 2, 8, 20, 21, 3, 23, 3, 27, 20, 0},
            []int{21, 14, 8, 20, 10, 0, 23, 3, 24, 23, 0, 19, 14, 12, 10, 9, 12, 12, 11, 6, 27, 5},
            []int{8, 18, 27, 10, 11, 22, 29, 23, 14},
            []int{13, 7, 14, 1, 9, 14, 16, 12, 0, 10, 13, 19, 16, 17},
            []int{24, 25, 21, 4, 6, 19, 1, 3, 26, 11, 22, 28, 14, 14, 27, 7, 20, 8, 7, 4, 1, 8, 10, 18, 21},
            []int{13, 20, 26, 22, 6, 5, 6, 23, 26, 2, 21, 16, 26, 24},
            []int{6, 7, 17, 2, 22, 23, 21},
            []int{23, 14, 22, 28, 10, 23, 7, 21, 3, 20, 24, 23, 8, 8, 21, 13, 15, 6, 9, 17, 27, 17, 13, 14},
            []int{23, 13, 1, 15, 5, 16, 7, 26, 22, 29, 17, 3, 14, 16, 16, 18, 6, 10, 3, 14, 10, 17, 27, 25},
            []int{25, 28, 5, 21, 8, 10, 27, 21, 23, 28, 7, 20, 6, 6, 9, 29, 27, 26, 24, 3, 12, 10, 21, 10, 12, 17},
            []int{26, 22, 26, 13, 10, 19, 3, 15, 2, 3, 25, 29, 25, 19, 19, 24, 1, 26, 22, 10, 17, 19, 28, 11, 22, 2, 13},
            []int{8, 4, 25, 15, 20, 9, 11, 3, 19},
            []int{24, 29, 4, 17, 2, 0, 8, 19, 11, 28, 13, 4, 16, 5, 15, 25, 16, 5, 6, 1, 0, 19, 7, 4, 6},
            []int{16, 25, 15, 17, 20, 27, 1, 11, 1, 18, 14, 23, 27, 25, 26, 17, 1},
        },
    }

    for t := range tests {
        fmt.Println(BusDriverGossipExchange(tests[t]...))
    }

}
<|endoftext|>


<commit_before>package main_test

import (
	"net"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"
	"time"

	. "github.com/maxcnunes/waitforit"
)

const regexPort string = `:(\d+)$`

type Server struct {
	conn          *Connection
	listener      net.Listener
	server        *httptest.Server
	serverHandler http.Handler
}

func NewServer(c *Connection, h http.Handler) *Server {
	return &Server{conn: c, serverHandler: h}
}

func (s *Server) Start() (err error) {
	if s.conn == nil {
		return nil
	}

	addr := net.JoinHostPort(s.conn.Host, strconv.Itoa(s.conn.Port))
	s.listener, err = net.Listen(s.conn.Type, addr)

	if s.conn.Scheme == "http" {
		s.server = &httptest.Server{
			Listener: s.listener,
			Config:   &http.Server{Handler: s.serverHandler},
		}

		s.server.Start()
	}
	return err
}

func (s *Server) Close() (err error) {
	if s.conn == nil {
		return nil
	}

	if s.conn.Scheme == "http" {
		if s.server != nil {
			s.server.Close()
		}
	} else {
		if s.listener != nil {
			err = s.listener.Close()
		}
	}
	return err
}

func TestDialConn(t *testing.T) {
	print := func(a ...interface{}) {}

	testCases := []struct {
		title         string
		conn          Connection
		allowStart    bool
		openConnAfter int
		finishOk      bool
		serverHanlder http.Handler
	}{
		{
			title:         "Should successfully check connection that is already available.",
			conn:          Connection{Type: "tcp", Scheme: "", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 0,
			finishOk:      true,
			serverHanlder: nil,
		},
		{
			title:         "Should successfully check connection that open before reach the timeout.",
			conn:          Connection{Type: "tcp", Scheme: "", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 2,
			finishOk:      true,
			serverHanlder: nil,
		},
		{
			title:         "Should successfully check a HTTP connection that is already available.",
			conn:          Connection{Type: "tcp", Scheme: "http", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 0,
			finishOk:      true,
			serverHanlder: nil,
		},
		{
			title:         "Should successfully check a HTTP connection that open before reach the timeout.",
			conn:          Connection{Type: "tcp", Scheme: "http", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 2,
			finishOk:      true,
			serverHanlder: nil,
		},
		{
			title:         "Should successfully check a HTTP connection that returns 404 status code.",
			conn:          Connection{Type: "tcp", Scheme: "http", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 0,
			finishOk:      true,
			serverHanlder: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				http.Error(w, "", 404)
			}),
		},
		{
			title:         "Should fail checking a HTTP connection that returns 500 status code.",
			conn:          Connection{Type: "tcp", Scheme: "http", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 0,
			finishOk:      false,
			serverHanlder: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				http.Error(w, "", 500)
			}),
		},
	}

	defaultTimeout := 5
	defaultRetry := 500
	for _, v := range testCases {
		t.Run(v.title, func(t *testing.T) {
			var err error
			s := NewServer(&v.conn, v.serverHanlder)
			defer s.Close() // nolint

			if v.allowStart {
				go func() {
					if v.openConnAfter > 0 {
						time.Sleep(time.Duration(v.openConnAfter) * time.Second)
					}

					if err := s.Start(); err != nil {
						t.Error(err)
					}
				}()
			}

			err = DialConn(&v.conn, defaultTimeout, defaultRetry, print)
			if err != nil && v.finishOk {
				t.Errorf("Expected to connect successfully %#v. But got error %v.", v.conn, err)
				return
			}

			if err == nil && !v.finishOk {
				t.Errorf("Expected to not connect successfully %#v.", v.conn)
			}
		})
	}
}

func TestDialConfigs(t *testing.T) {
	print := func(a ...interface{}) {}

	type testItem struct {
		conf          Config
		allowStart    bool
		openConnAfter int
		finishOk      bool
		serverHanlder http.Handler
	}
	testCases := []struct {
		title string
		items []testItem
	}{
		{
			"Should successfully check a single connection.",
			[]testItem{
				{
					conf:          Config{Port: 8080, Host: "localhost", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
			},
		},
		{
			"Should successfully check all connections.",
			[]testItem{
				{
					conf:          Config{Port: 8080, Host: "localhost", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
				{
					conf:          Config{Address: "http://localhost:8081", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
			},
		},
		{
			"Should fail when at least a single connection is not available.",
			[]testItem{
				{
					conf:          Config{Port: 8080, Host: "localhost", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
				{
					conf:          Config{Port: 8081, Host: "localhost", Timeout: 5},
					allowStart:    false,
					openConnAfter: 0,
					finishOk:      false,
					serverHanlder: nil,
				},
			},
		},
		{
			"Should fail when at least a single connection is not valid.",
			[]testItem{
				{
					conf:          Config{Port: 8080, Host: "localhost", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
				{
					conf:          Config{Address: "http:/localhost;8081", Timeout: 5},
					allowStart:    false,
					openConnAfter: 0,
					finishOk:      false,
					serverHanlder: nil,
				},
			},
		},
	}

	for _, v := range testCases {
		t.Run(v.title, func(t *testing.T) {
			confs := []Config{}
			finishAllOk := true

			for _, item := range v.items {
				confs = append(confs, item.conf)
				if finishAllOk && !item.finishOk {
					finishAllOk = false
				}

				conn := BuildConn(&item.conf)

				s := NewServer(conn, item.serverHanlder)
				defer s.Close() // nolint

				if item.allowStart {
					go func() {
						if item.openConnAfter > 0 {
							time.Sleep(time.Duration(item.openConnAfter) * time.Second)
						}

						if err := s.Start(); err != nil {
							t.Error(err)
						}
					}()
				}
			}

			err := DialConfigs(confs, print)
			if err != nil && finishAllOk {
				t.Errorf("Expected to connect successfully %#v. But got error %v.", confs, err)
				return
			}

			if err == nil && !finishAllOk {
				t.Errorf("Expected to not connect successfully %#v.", confs)
			}
		})
	}
}
<commit_msg>Fix error handler in a test<commit_after>package main_test

import (
	"net"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"
	"time"

	. "github.com/maxcnunes/waitforit"
)

const regexPort string = `:(\d+)$`

type Server struct {
	conn          *Connection
	listener      net.Listener
	server        *httptest.Server
	serverHandler http.Handler
}

func NewServer(c *Connection, h http.Handler) *Server {
	return &Server{conn: c, serverHandler: h}
}

func (s *Server) Start() (err error) {
	if s.conn == nil {
		return nil
	}

	addr := net.JoinHostPort(s.conn.Host, strconv.Itoa(s.conn.Port))
	s.listener, err = net.Listen(s.conn.Type, addr)
	if err != nil {
		return err
	}

	if s.conn.Scheme == "http" {
		s.server = &httptest.Server{
			Listener: s.listener,
			Config:   &http.Server{Handler: s.serverHandler},
		}

		s.server.Start()
	}
	return nil
}

func (s *Server) Close() (err error) {
	if s.conn == nil {
		return nil
	}

	if s.conn.Scheme == "http" {
		if s.server != nil {
			s.server.Close()
		}
	} else {
		if s.listener != nil {
			err = s.listener.Close()
		}
	}
	return err
}

func TestDialConn(t *testing.T) {
	print := func(a ...interface{}) {}

	testCases := []struct {
		title         string
		conn          Connection
		allowStart    bool
		openConnAfter int
		finishOk      bool
		serverHanlder http.Handler
	}{
		{
			title:         "Should successfully check connection that is already available.",
			conn:          Connection{Type: "tcp", Scheme: "", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 0,
			finishOk:      true,
			serverHanlder: nil,
		},
		{
			title:         "Should successfully check connection that open before reach the timeout.",
			conn:          Connection{Type: "tcp", Scheme: "", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 2,
			finishOk:      true,
			serverHanlder: nil,
		},
		{
			title:         "Should successfully check a HTTP connection that is already available.",
			conn:          Connection{Type: "tcp", Scheme: "http", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 0,
			finishOk:      true,
			serverHanlder: nil,
		},
		{
			title:         "Should successfully check a HTTP connection that open before reach the timeout.",
			conn:          Connection{Type: "tcp", Scheme: "http", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 2,
			finishOk:      true,
			serverHanlder: nil,
		},
		{
			title:         "Should successfully check a HTTP connection that returns 404 status code.",
			conn:          Connection{Type: "tcp", Scheme: "http", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 0,
			finishOk:      true,
			serverHanlder: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				http.Error(w, "", 404)
			}),
		},
		{
			title:         "Should fail checking a HTTP connection that returns 500 status code.",
			conn:          Connection{Type: "tcp", Scheme: "http", Port: 8080, Host: "localhost", Path: ""},
			allowStart:    true,
			openConnAfter: 0,
			finishOk:      false,
			serverHanlder: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				http.Error(w, "", 500)
			}),
		},
	}

	defaultTimeout := 5
	defaultRetry := 500
	for _, v := range testCases {
		t.Run(v.title, func(t *testing.T) {
			var err error
			s := NewServer(&v.conn, v.serverHanlder)
			defer s.Close() // nolint

			if v.allowStart {
				go func() {
					if v.openConnAfter > 0 {
						time.Sleep(time.Duration(v.openConnAfter) * time.Second)
					}

					if err := s.Start(); err != nil {
						t.Error(err)
					}
				}()
			}

			err = DialConn(&v.conn, defaultTimeout, defaultRetry, print)
			if err != nil && v.finishOk {
				t.Errorf("Expected to connect successfully %#v. But got error %v.", v.conn, err)
				return
			}

			if err == nil && !v.finishOk {
				t.Errorf("Expected to not connect successfully %#v.", v.conn)
			}
		})
	}
}

func TestDialConfigs(t *testing.T) {
	print := func(a ...interface{}) {}

	type testItem struct {
		conf          Config
		allowStart    bool
		openConnAfter int
		finishOk      bool
		serverHanlder http.Handler
	}
	testCases := []struct {
		title string
		items []testItem
	}{
		{
			"Should successfully check a single connection.",
			[]testItem{
				{
					conf:          Config{Port: 8080, Host: "localhost", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
			},
		},
		{
			"Should successfully check all connections.",
			[]testItem{
				{
					conf:          Config{Port: 8080, Host: "localhost", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
				{
					conf:          Config{Address: "http://localhost:8081", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
			},
		},
		{
			"Should fail when at least a single connection is not available.",
			[]testItem{
				{
					conf:          Config{Port: 8080, Host: "localhost", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
				{
					conf:          Config{Port: 8081, Host: "localhost", Timeout: 5},
					allowStart:    false,
					openConnAfter: 0,
					finishOk:      false,
					serverHanlder: nil,
				},
			},
		},
		{
			"Should fail when at least a single connection is not valid.",
			[]testItem{
				{
					conf:          Config{Port: 8080, Host: "localhost", Timeout: 5},
					allowStart:    true,
					openConnAfter: 0,
					finishOk:      true,
					serverHanlder: nil,
				},
				{
					conf:          Config{Address: "http:/localhost;8081", Timeout: 5},
					allowStart:    false,
					openConnAfter: 0,
					finishOk:      false,
					serverHanlder: nil,
				},
			},
		},
	}

	for _, v := range testCases {
		t.Run(v.title, func(t *testing.T) {
			confs := []Config{}
			finishAllOk := true

			for _, item := range v.items {
				confs = append(confs, item.conf)
				if finishAllOk && !item.finishOk {
					finishAllOk = false
				}

				conn := BuildConn(&item.conf)

				s := NewServer(conn, item.serverHanlder)
				defer s.Close() // nolint

				if item.allowStart {
					go func() {
						if item.openConnAfter > 0 {
							time.Sleep(time.Duration(item.openConnAfter) * time.Second)
						}

						if err := s.Start(); err != nil {
							t.Error(err)
						}
					}()
				}
			}

			err := DialConfigs(confs, print)
			if err != nil && finishAllOk {
				t.Errorf("Expected to connect successfully %#v. But got error %v.", confs, err)
				return
			}

			if err == nil && !finishAllOk {
				t.Errorf("Expected to not connect successfully %#v.", confs)
			}
		})
	}
}
<|endoftext|>


<commit_before>package oauth

import (
    "strings"

    "github.com/earaujoassis/space/utils"
    "github.com/earaujoassis/space/services"
    "github.com/earaujoassis/space/models"
)

func AccessTokenRequest(data utils.H) (utils.H, error) {
    var user models.User
    var client models.Client

    var code string
    var redirectURI string

    if data["code"] == nil || data["redirect_uri"] == nil || data["client"] == nil {
        return invalidRequestResult("")
    }

    redirectURI = data["redirect_uri"].(string)
    code = data["code"].(string)
    client = data["client"].(models.Client)

    authorizationSession := services.FindSessionByToken(code, models.GrantToken)
    defer services.InvalidateSession(authorizationSession)
    if authorizationSession.ID == 0 {
        return invalidGrantResult("")
    }
    user = authorizationSession.User
    user = services.FindUserByPublicId(user.PublicId)
    if authorizationSession.Client.ID != client.ID {
        return invalidGrantResult("")
    }
    if !strings.Contains(authorizationSession.Client.RedirectURI, redirectURI) {
        return invalidGrantResult("")
    }

    accessToken := services.CreateSession(user,
        client,
        authorizationSession.Ip,
        authorizationSession.UserAgent,
        authorizationSession.Scopes,
        models.AccessToken)
    refreshToken := services.CreateSession(user,
        client,
        authorizationSession.Ip,
        authorizationSession.UserAgent,
        authorizationSession.Scopes,
        models.RefreshToken)

    if accessToken.ID == 0 || refreshToken.ID == 0 {
        return serverErrorResult("")
    }

    return utils.H{
        "user_id": user.PublicId,
        "access_token": accessToken.Token,
        "token_type": "Bearer",
        "expires_in": 0,
        "refresh_token": refreshToken.Token,
        "scope": authorizationSession.Scopes,
    }, nil
}

func RefreshTokenRequest(data utils.H) (utils.H, error) {
    var user models.User
    var client models.Client

    var token string
    var scope string

    if data["refresh_token"] == nil || data["scope"] == nil || data["client"] == nil {
        return invalidRequestResult("")
    }

    token = data["refresh_token"].(string)
    scope = data["scope"].(string)
    client = data["client"].(models.Client)

    refreshSession := services.FindSessionByToken(token, models.RefreshToken)
    defer services.InvalidateSession(refreshSession)
    if refreshSession.ID == 0 {
        return invalidGrantResult("")
    }
    user = refreshSession.User
    user = services.FindUserByPublicId(user.PublicId)
    if refreshSession.Client.ID != client.ID {
        return invalidGrantResult("")
    }
    if scope != refreshSession.Scopes {
        return invalidScopeResult("")
    }

    accessToken := services.CreateSession(user,
        client,
        refreshSession.Ip,
        refreshSession.UserAgent,
        scope,
        models.AccessToken)
    refreshToken := services.CreateSession(user,
        client,
        refreshSession.Ip,
        refreshSession.UserAgent,
        scope,
        models.RefreshToken)

    if accessToken.ID == 0 || refreshToken.ID == 0 {
        return serverErrorResult("")
    }

    return utils.H{
        "user_id": user.PublicId,
        "access_token": accessToken.Token,
        "token_type": "Bearer",
        "expires_in": 0,
        "refresh_token": refreshToken.Token,
        "scope": refreshSession.Scopes,
    }, nil
}
<commit_msg>Security breach: mistakenly sending eternal access tokens<commit_after>package oauth

import (
    "strings"

    "github.com/earaujoassis/space/utils"
    "github.com/earaujoassis/space/services"
    "github.com/earaujoassis/space/models"
)

func AccessTokenRequest(data utils.H) (utils.H, error) {
    var user models.User
    var client models.Client

    var code string
    var redirectURI string

    if data["code"] == nil || data["redirect_uri"] == nil || data["client"] == nil {
        return invalidRequestResult("")
    }

    redirectURI = data["redirect_uri"].(string)
    code = data["code"].(string)
    client = data["client"].(models.Client)

    authorizationSession := services.FindSessionByToken(code, models.GrantToken)
    defer services.InvalidateSession(authorizationSession)
    if authorizationSession.ID == 0 {
        return invalidGrantResult("")
    }
    user = authorizationSession.User
    user = services.FindUserByPublicId(user.PublicId)
    if authorizationSession.Client.ID != client.ID {
        return invalidGrantResult("")
    }
    if !strings.Contains(authorizationSession.Client.RedirectURI, redirectURI) {
        return invalidGrantResult("")
    }

    accessToken := services.CreateSession(user,
        client,
        authorizationSession.Ip,
        authorizationSession.UserAgent,
        authorizationSession.Scopes,
        models.AccessToken)
    refreshToken := services.CreateSession(user,
        client,
        authorizationSession.Ip,
        authorizationSession.UserAgent,
        authorizationSession.Scopes,
        models.RefreshToken)

    if accessToken.ID == 0 || refreshToken.ID == 0 {
        return serverErrorResult("")
    }

    return utils.H{
        "user_id": user.PublicId,
        "access_token": accessToken.Token,
        "token_type": "Bearer",
        "expires_in": accessToken.ExpiresIn,
        "refresh_token": refreshToken.Token,
        "scope": authorizationSession.Scopes,
    }, nil
}

func RefreshTokenRequest(data utils.H) (utils.H, error) {
    var user models.User
    var client models.Client

    var token string
    var scope string

    if data["refresh_token"] == nil || data["scope"] == nil || data["client"] == nil {
        return invalidRequestResult("")
    }

    token = data["refresh_token"].(string)
    scope = data["scope"].(string)
    client = data["client"].(models.Client)

    refreshSession := services.FindSessionByToken(token, models.RefreshToken)
    defer services.InvalidateSession(refreshSession)
    if refreshSession.ID == 0 {
        return invalidGrantResult("")
    }
    user = refreshSession.User
    user = services.FindUserByPublicId(user.PublicId)
    if refreshSession.Client.ID != client.ID {
        return invalidGrantResult("")
    }
    if scope != refreshSession.Scopes {
        return invalidScopeResult("")
    }

    accessToken := services.CreateSession(user,
        client,
        refreshSession.Ip,
        refreshSession.UserAgent,
        scope,
        models.AccessToken)
    refreshToken := services.CreateSession(user,
        client,
        refreshSession.Ip,
        refreshSession.UserAgent,
        scope,
        models.RefreshToken)

    if accessToken.ID == 0 || refreshToken.ID == 0 {
        return serverErrorResult("")
    }

    return utils.H{
        "user_id": user.PublicId,
        "access_token": accessToken.Token,
        "token_type": "Bearer",
        "expires_in": accessToken.ExpiresIn,
        "refresh_token": refreshToken.Token,
        "scope": refreshSession.Scopes,
    }, nil
}
<|endoftext|>


<commit_before>package node

import (
	"errors"
	"sync"
	"time"

	"github.com/anycable/anycable-go/common"
	"github.com/anycable/anycable-go/encoders"
	"github.com/anycable/anycable-go/ws"
	"github.com/apex/log"
)

const (
	writeWait = 10 * time.Second
)

// Executor handles incoming commands (messages)
type Executor interface {
	HandleCommand(*Session, *common.Message) error
}

// Session represents active client
type Session struct {
	node          *Node
	conn          Connection
	encoder       encoders.Encoder
	executor      Executor
	env           *common.SessionEnv
	subscriptions map[string]bool
	closed        bool
	// Main mutex (for read/write and important session updates)
	mu sync.Mutex
	// Mutex for protocol-related state (env, subscriptions)
	smu sync.Mutex

	sendCh chan *ws.SentFrame

	pingTimer    *time.Timer
	pingInterval time.Duration

	pingTimestampPrecision string

	UID         string
	Identifiers string
	Connected   bool
	Log         *log.Entry
}

// NewSession build a new Session struct from ws connetion and http request
func NewSession(node *Node, conn Connection, url string, headers *map[string]string, uid string) *Session {
	session := &Session{
		node:                   node,
		conn:                   conn,
		env:                    common.NewSessionEnv(url, headers),
		subscriptions:          make(map[string]bool),
		sendCh:                 make(chan *ws.SentFrame, 256),
		closed:                 false,
		Connected:              false,
		pingInterval:           time.Duration(node.config.PingInterval) * time.Second,
		pingTimestampPrecision: node.config.PingTimestampPrecision,
		// Use JSON by default
		encoder: encoders.JSON{},
		// Use Action Cable executor by default (implemented by node)
		executor: node,
	}

	session.UID = uid

	ctx := node.log.WithFields(log.Fields{
		"sid": session.UID,
	})

	session.Log = ctx

	session.addPing()
	go session.SendMessages()

	return session
}

func (s *Session) SetEncoder(enc encoders.Encoder) {
	s.encoder = enc
}

func (s *Session) SetExecutor(ex Executor) {
	s.executor = ex
}

// Serve enters a loop to read incoming data
func (s *Session) Serve(callback func()) error {
	go func() {
		defer callback()

		for {
			err := s.ReadMessage()

			if err != nil {
				return
			}
		}
	}()

	return nil
}

// SendMessages waits for incoming messages and send them to the client connection
func (s *Session) SendMessages() {
	defer s.Disconnect("Write Failed", ws.CloseAbnormalClosure)
	for message := range s.sendCh {
		err := s.writeFrame(message)

		if err != nil {
			s.node.Metrics.Counter(metricsFailedSent).Inc()
			return
		}

		s.node.Metrics.Counter(metricsSentMsg).Inc()
	}
}

// ReadMessage reads messages from ws connection and send them to node
func (s *Session) ReadMessage() error {
	message, err := s.conn.Read()

	if err != nil {
		if ws.IsCloseError(err) {
			s.Log.Debugf("Websocket closed: %v", err)
			s.Disconnect("Read closed", ws.CloseNormalClosure)
		} else {
			s.Log.Debugf("Websocket close error: %v", err)
			s.Disconnect("Read failed", ws.CloseAbnormalClosure)
		}
		return err
	}

	s.node.Metrics.Counter(metricsDataReceived).Add(uint64(len(message)))

	command, err := s.decodeMessage(message)

	if err != nil {
		s.node.Metrics.Counter(metricsFailedCommandReceived).Inc()
		return err
	}

	if command == nil {
		return nil
	}

	s.node.Metrics.Counter(metricsReceivedMsg).Inc()

	if err := s.executor.HandleCommand(s, command); err != nil {
		s.node.Metrics.Counter(metricsFailedCommandReceived).Inc()
		s.Log.Warnf("Failed to handle incoming message '%s' with error: %v", message, err)
	}

	return nil
}

// Send schedules a data transmission
func (s *Session) Send(msg encoders.EncodedMessage) {
	if b, err := s.encodeMessage(msg); err == nil {
		if b != nil {
			s.sendFrame(b)
		}
	} else {
		s.Log.Warnf("Failed to encode message %v. Error: %v", msg, err)
	}
}

// SendJSONTransmission is used to propagate the direct transmission to the client
// (from RPC call result)
func (s *Session) SendJSONTransmission(msg string) {
	if b, err := s.encodeTransmission(msg); err == nil {
		if b != nil {
			s.sendFrame(b)
		}
	} else {
		s.Log.Warnf("Failed to encode transmission %v. Error: %v", msg, err)
	}
}

// Disconnect schedules connection disconnect
func (s *Session) Disconnect(reason string, code int) {
	s.mu.Lock()
	if s.Connected {
		defer s.node.Disconnect(s) // nolint:errcheck
	}
	s.Connected = false
	s.mu.Unlock()

	s.close(reason, code)
}

func (s *Session) close(reason string, code int) {
	s.mu.Lock()

	if s.closed {
		s.mu.Unlock()
		return
	}

	s.closed = true
	s.mu.Unlock()

	s.sendClose(reason, code)

	if s.pingTimer != nil {
		s.pingTimer.Stop()
	}
}

func (s *Session) sendClose(reason string, code int) {
	s.sendFrame(&ws.SentFrame{
		FrameType:   ws.CloseFrame,
		CloseReason: reason,
		CloseCode:   code,
	})
}

func (s *Session) sendFrame(message *ws.SentFrame) {
	s.mu.Lock()

	if s.sendCh == nil {
		s.mu.Unlock()
		return
	}

	select {
	case s.sendCh <- message:
	default:
		if s.sendCh != nil {
			close(s.sendCh)
			defer s.Disconnect("Write failed", ws.CloseAbnormalClosure)
		}

		s.sendCh = nil
	}

	s.mu.Unlock()
}

func (s *Session) writeFrame(message *ws.SentFrame) error {
	return s.writeFrameWithDeadline(message, time.Now().Add(writeWait))
}

func (s *Session) writeFrameWithDeadline(message *ws.SentFrame, deadline time.Time) error {
	s.node.Metrics.Counter(metricsDataSent).Add(uint64(len(message.Payload)))

	switch message.FrameType {
	case ws.TextFrame:
		s.mu.Lock()
		defer s.mu.Unlock()

		err := s.conn.Write(message.Payload, deadline)
		return err
	case ws.BinaryFrame:
		s.mu.Lock()
		defer s.mu.Unlock()

		err := s.conn.WriteBinary(message.Payload, deadline)

		return err
	case ws.CloseFrame:
		s.conn.Close(message.CloseCode, message.CloseReason)
		return errors.New("Closed")
	default:
		s.Log.Errorf("Unknown frame type: %v", message)
		return errors.New("Unknown frame type")
	}
}

func (s *Session) sendPing() {
	if s.closed {
		return
	}

	deadline := time.Now().Add(s.pingInterval / 2)

	b, err := s.encodeMessage(newPingMessage(s.pingTimestampPrecision))

	if err != nil {
		s.Log.Errorf("Failed to encode ping message: %v", err)
	} else if b != nil {
		err = s.writeFrameWithDeadline(b, deadline)
	}

	if err != nil {
		s.Disconnect("Ping failed", ws.CloseAbnormalClosure)
		return
	}

	s.addPing()
}

func (s *Session) addPing() {
	s.pingTimer = time.AfterFunc(s.pingInterval, s.sendPing)
}

func newPingMessage(format string) *common.PingMessage {
	var ts int64

	switch format {
	case "ns":
		ts = time.Now().UnixNano()
	case "ms":
		ts = time.Now().UnixNano() / int64(time.Millisecond)
	default:
		ts = time.Now().Unix()
	}

	return (&common.PingMessage{Type: "ping", Message: ts})
}

func (s *Session) encodeMessage(msg encoders.EncodedMessage) (*ws.SentFrame, error) {
	if cm, ok := msg.(*CachedEncodedMessage); ok {
		return cm.Fetch(
			s.encoder.ID(),
			func(m encoders.EncodedMessage) (*ws.SentFrame, error) {
				return s.encoder.Encode(m)
			})
	}

	return s.encoder.Encode(msg)
}

func (s *Session) encodeTransmission(msg string) (*ws.SentFrame, error) {
	return s.encoder.EncodeTransmission(msg)
}

func (s *Session) decodeMessage(raw []byte) (*common.Message, error) {
	return s.encoder.Decode(raw)
}
<commit_msg>refactor: disconnect immediately when read failed<commit_after>package node

import (
	"errors"
	"sync"
	"time"

	"github.com/anycable/anycable-go/common"
	"github.com/anycable/anycable-go/encoders"
	"github.com/anycable/anycable-go/ws"
	"github.com/apex/log"
)

const (
	writeWait = 10 * time.Second
)

// Executor handles incoming commands (messages)
type Executor interface {
	HandleCommand(*Session, *common.Message) error
}

// Session represents active client
type Session struct {
	node          *Node
	conn          Connection
	encoder       encoders.Encoder
	executor      Executor
	env           *common.SessionEnv
	subscriptions map[string]bool
	closed        bool
	// Main mutex (for read/write and important session updates)
	mu sync.Mutex
	// Mutex for protocol-related state (env, subscriptions)
	smu sync.Mutex

	sendCh chan *ws.SentFrame

	pingTimer    *time.Timer
	pingInterval time.Duration

	pingTimestampPrecision string

	UID         string
	Identifiers string
	Connected   bool
	Log         *log.Entry
}

// NewSession build a new Session struct from ws connetion and http request
func NewSession(node *Node, conn Connection, url string, headers *map[string]string, uid string) *Session {
	session := &Session{
		node:                   node,
		conn:                   conn,
		env:                    common.NewSessionEnv(url, headers),
		subscriptions:          make(map[string]bool),
		sendCh:                 make(chan *ws.SentFrame, 256),
		closed:                 false,
		Connected:              false,
		pingInterval:           time.Duration(node.config.PingInterval) * time.Second,
		pingTimestampPrecision: node.config.PingTimestampPrecision,
		// Use JSON by default
		encoder: encoders.JSON{},
		// Use Action Cable executor by default (implemented by node)
		executor: node,
	}

	session.UID = uid

	ctx := node.log.WithFields(log.Fields{
		"sid": session.UID,
	})

	session.Log = ctx

	session.addPing()
	go session.SendMessages()

	return session
}

func (s *Session) SetEncoder(enc encoders.Encoder) {
	s.encoder = enc
}

func (s *Session) SetExecutor(ex Executor) {
	s.executor = ex
}

// Serve enters a loop to read incoming data
func (s *Session) Serve(callback func()) error {
	go func() {
		defer callback()

		for {
			message, err := s.conn.Read()

			if err != nil {
				if ws.IsCloseError(err) {
					s.Log.Debugf("Websocket closed: %v", err)
					s.disconnectNow("Read closed", ws.CloseNormalClosure)
				} else {
					s.Log.Debugf("Websocket close error: %v", err)
					s.disconnectNow("Read failed", ws.CloseAbnormalClosure)
				}
				return
			}

			err = s.ReadMessage(message)

			if err != nil {
				return
			}
		}
	}()

	return nil
}

// SendMessages waits for incoming messages and send them to the client connection
func (s *Session) SendMessages() {
	defer s.disconnectNow("Write Failed", ws.CloseAbnormalClosure)

	for message := range s.sendCh {
		err := s.writeFrame(message)

		if err != nil {
			s.node.Metrics.Counter(metricsFailedSent).Inc()
			return
		}

		s.node.Metrics.Counter(metricsSentMsg).Inc()
	}
}

// ReadMessage reads messages from ws connection and send them to node
func (s *Session) ReadMessage(message []byte) error {
	s.node.Metrics.Counter(metricsDataReceived).Add(uint64(len(message)))

	command, err := s.decodeMessage(message)

	if err != nil {
		s.node.Metrics.Counter(metricsFailedCommandReceived).Inc()
		return err
	}

	if command == nil {
		return nil
	}

	s.node.Metrics.Counter(metricsReceivedMsg).Inc()

	if err := s.executor.HandleCommand(s, command); err != nil {
		s.node.Metrics.Counter(metricsFailedCommandReceived).Inc()
		s.Log.Warnf("Failed to handle incoming message '%s' with error: %v", message, err)
	}

	return nil
}

// Send schedules a data transmission
func (s *Session) Send(msg encoders.EncodedMessage) {
	if b, err := s.encodeMessage(msg); err == nil {
		if b != nil {
			s.sendFrame(b)
		}
	} else {
		s.Log.Warnf("Failed to encode message %v. Error: %v", msg, err)
	}
}

// SendJSONTransmission is used to propagate the direct transmission to the client
// (from RPC call result)
func (s *Session) SendJSONTransmission(msg string) {
	if b, err := s.encodeTransmission(msg); err == nil {
		if b != nil {
			s.sendFrame(b)
		}
	} else {
		s.Log.Warnf("Failed to encode transmission %v. Error: %v", msg, err)
	}
}

// Disconnect schedules connection disconnect
func (s *Session) Disconnect(reason string, code int) {
	s.disconnectFromNode()
	s.sendClose(reason, code)
	s.close()
}

func (s *Session) disconnectFromNode() {
	s.mu.Lock()
	if s.Connected {
		defer s.node.Disconnect(s) // nolint:errcheck
	}
	s.Connected = false
	s.mu.Unlock()
}

func (s *Session) disconnectNow(reason string, code int) {
	s.disconnectFromNode()
	s.writeFrame(&ws.SentFrame{ // nolint:errcheck
		FrameType:   ws.CloseFrame,
		CloseReason: reason,
		CloseCode:   code,
	})

	s.mu.Lock()
	if s.sendCh != nil {
		close(s.sendCh)
		s.sendCh = nil
	}
	s.mu.Unlock()

	s.close()
}

func (s *Session) close() {
	s.mu.Lock()

	if s.closed {
		s.mu.Unlock()
		return
	}

	s.closed = true
	s.mu.Unlock()

	if s.pingTimer != nil {
		s.pingTimer.Stop()
	}
}

func (s *Session) sendClose(reason string, code int) {
	s.sendFrame(&ws.SentFrame{
		FrameType:   ws.CloseFrame,
		CloseReason: reason,
		CloseCode:   code,
	})
}

func (s *Session) sendFrame(message *ws.SentFrame) {
	s.mu.Lock()

	if s.sendCh == nil {
		s.mu.Unlock()
		return
	}

	select {
	case s.sendCh <- message:
	default:
		if s.sendCh != nil {
			close(s.sendCh)
			defer s.Disconnect("Write failed", ws.CloseAbnormalClosure)
		}

		s.sendCh = nil
	}

	s.mu.Unlock()
}

func (s *Session) writeFrame(message *ws.SentFrame) error {
	return s.writeFrameWithDeadline(message, time.Now().Add(writeWait))
}

func (s *Session) writeFrameWithDeadline(message *ws.SentFrame, deadline time.Time) error {
	s.node.Metrics.Counter(metricsDataSent).Add(uint64(len(message.Payload)))

	switch message.FrameType {
	case ws.TextFrame:
		s.mu.Lock()
		defer s.mu.Unlock()

		err := s.conn.Write(message.Payload, deadline)
		return err
	case ws.BinaryFrame:
		s.mu.Lock()
		defer s.mu.Unlock()

		err := s.conn.WriteBinary(message.Payload, deadline)

		return err
	case ws.CloseFrame:
		s.conn.Close(message.CloseCode, message.CloseReason)
		return errors.New("Closed")
	default:
		s.Log.Errorf("Unknown frame type: %v", message)
		return errors.New("Unknown frame type")
	}
}

func (s *Session) sendPing() {
	if s.closed {
		return
	}

	deadline := time.Now().Add(s.pingInterval / 2)

	b, err := s.encodeMessage(newPingMessage(s.pingTimestampPrecision))

	if err != nil {
		s.Log.Errorf("Failed to encode ping message: %v", err)
	} else if b != nil {
		err = s.writeFrameWithDeadline(b, deadline)
	}

	if err != nil {
		s.Disconnect("Ping failed", ws.CloseAbnormalClosure)
		return
	}

	s.addPing()
}

func (s *Session) addPing() {
	s.pingTimer = time.AfterFunc(s.pingInterval, s.sendPing)
}

func newPingMessage(format string) *common.PingMessage {
	var ts int64

	switch format {
	case "ns":
		ts = time.Now().UnixNano()
	case "ms":
		ts = time.Now().UnixNano() / int64(time.Millisecond)
	default:
		ts = time.Now().Unix()
	}

	return (&common.PingMessage{Type: "ping", Message: ts})
}

func (s *Session) encodeMessage(msg encoders.EncodedMessage) (*ws.SentFrame, error) {
	if cm, ok := msg.(*CachedEncodedMessage); ok {
		return cm.Fetch(
			s.encoder.ID(),
			func(m encoders.EncodedMessage) (*ws.SentFrame, error) {
				return s.encoder.Encode(m)
			})
	}

	return s.encoder.Encode(msg)
}

func (s *Session) encodeTransmission(msg string) (*ws.SentFrame, error) {
	return s.encoder.EncodeTransmission(msg)
}

func (s *Session) decodeMessage(raw []byte) (*common.Message, error) {
	return s.encoder.Decode(raw)
}
<|endoftext|>


<commit_before>package notify

//
// This package is a wrapper around godbus for dbus notification interface
// See: https://developer.gnome.org/notification-spec/
//
// Each notification displayed is allocated a unique ID by the server. (see Notify)
// This ID unique within the dbus session. While the notification server is running,
// the ID will not be recycled unless the capacity of a uint32 is exceeded.
//
// This can be used to hide the notification before the expiration timeout is reached. (see CloseNotification)
//
// The ID can also be used to atomically replace the notification with another (Notification.ReplaceID).
// This allows you to (for instance) modify the contents of a notification while it's on-screen.
//

import (
	"errors"
	"log"

	"github.com/godbus/dbus"
)
const (
	objectPath                 = "/org/freedesktop/Notifications" // the DBUS object path
	dbusNotificationsInterface = "org.freedesktop.Notifications"  // DBUS Interface
	getCapabilities            = "org.freedesktop.Notifications.GetCapabilities"
	closeNotification          = "org.freedesktop.Notifications.CloseNotification"
	notify                     = "org.freedesktop.Notifications.Notify"
	getServerInformation       = "org.freedesktop.Notifications.GetServerInformation"
)

// New creates a new Notificator using conn
func New(conn *dbus.Conn) Notificator {
	return &Notifier{
		conn: conn,
	}
}

// Notifier implements Notificator
type Notifier struct {
	conn *dbus.Conn
}

// Notification holds all information needed for creating a notification
type Notification struct {
	AppName       string
	ReplacesID    uint32
	AppIcon       string // see icons here: http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
	Summary       string
	Body          string
	Actions       []string
	Hints         map[string]dbus.Variant
	ExpireTimeout int32 // millisecond to show notification
}

// SendNotification sends a notification to the notification server.
// Implements dbus call:
//
// UINT32 org.freedesktop.Notifications.Notify ( STRING app_name,
//												 UINT32 replaces_id,
//												 STRING app_icon,
//												 STRING summary,
//												 STRING body,
//												 ARRAY  actions,
//												 DICT   hints,
//												 INT32  expire_timeout);
//
//		 Name	    	Type	Description
//		 app_name		STRING	The optional name of the application sending the notification. Can be blank.
//		 replaces_id	UINT32	The optional notification ID that this notification replaces. The server must atomically (ie with no flicker or other visual cues) replace the given notification with this one. This allows clients to effectively modify the notification while it's active. A value of value of 0 means that this notification won't replace any existing notifications.
//		 app_icon		STRING	The optional program icon of the calling application. Can be an empty string, indicating no icon.
//		 summary		STRING	The summary text briefly describing the notification.
//		 body			STRING	The optional detailed body text. Can be empty.
//		 actions		ARRAY	Actions are sent over as a list of pairs. Each even element in the list (starting at index 0) represents the identifier for the action. Each odd element in the list is the localized string that will be displayed to the user.
//		 hints	        DICT	Optional hints that can be passed to the server from the client program. Although clients and servers should never assume each other supports any specific hints, they can be used to pass along information, such as the process PID or window ID, that the server may be able to make use of. See Hints. Can be empty.
//       expire_timeout INT32   The timeout time in milliseconds since the display of the notification at which the notification should automatically close.
//								If -1, the notification's expiration time is dependent on the notification server's settings, and may vary for the type of notification. If 0, never expire.
//
// If replaces_id is 0, the return value is a UINT32 that represent the notification. It is unique, and will not be reused unless a MAXINT number of notifications have been generated. An acceptable implementation may just use an incrementing counter for the ID. The returned ID is always greater than zero. Servers must make sure not to return zero as an ID.
// If replaces_id is not 0, the returned value is the same value as replaces_id.
func (self *Notifier) SendNotification(n Notification) (uint32, error) {
	return SendNotification(self.conn, n)
}

// SendNotification is same as Notifier.SendNotification
func SendNotification(conn *dbus.Conn, n Notification) (uint32, error) {
	obj := conn.Object(dbusNotificationsInterface, objectPath)
	call := obj.Call(notify, 0,
		n.AppName,
		n.ReplacesID,
		n.AppIcon,
		n.Summary,
		n.Body,
		n.Actions,
		n.Hints,
		n.ExpireTimeout)
	if call.Err != nil {
		return 0, call.Err
	}
	var ret uint32
	err := call.Store(&ret)
	if err != nil {
		log.Printf("error getting uint32 ret value: %v", err)
		return ret, err
	}
	return ret, nil
}

// GetCapabilities gets the capabilities of the notification server.
// This call takes no parameters.
// It returns an array of strings. Each string describes an optional capability implemented by the server.
//
// See also: https://developer.gnome.org/notification-spec/
func (self *Notifier) GetCapabilities() ([]string, error) {
	obj := self.conn.Object(dbusNotificationsInterface, objectPath)
	call := obj.Call(getCapabilities, 0)
	if call.Err != nil {
		log.Printf("error calling GetCapabilities: %v", call.Err)
		return []string{}, call.Err
	}
	var ret []string
	err := call.Store(&ret)
	if err != nil {
		log.Printf("error getting capabilities ret value: %v", err)
		return ret, err
	}
	return ret, nil
}

// CloseNotification causes a notification to be forcefully closed and removed from the user's view.
// It can be used, for example, in the event that what the notification pertains to is no longer relevant,
// or to cancel a notification with no expiration time.
//
// The NotificationClosed (dbus) signal is emitted by this method.
// If the notification no longer exists, an empty D-BUS Error message is sent back.
func (self *Notifier) CloseNotification(id int) (bool, error) {
	obj := self.conn.Object(dbusNotificationsInterface, objectPath)
	call := obj.Call(closeNotification, 0, uint32(id))
	if call.Err != nil {
		return false, call.Err
	}
	return true, nil
}

// ServerInformation is a holder for information returned by
// GetServerInformation call.
type ServerInformation struct {
	Name        string
	Vendor      string
	Version     string
	SpecVersion string
}

// GetServerInformation returns the information on the server.
//
// org.freedesktop.Notifications.GetServerInformation
//
//  GetServerInformation Return Values
//
//		Name		 Type	  Description
//		name		 STRING	  The product name of the server.
//		vendor		 STRING	  The vendor name. For example, "KDE," "GNOME," "freedesktop.org," or "Microsoft."
//		version		 STRING	  The server's version number.
//		spec_version STRING	  The specification version the server is compliant with.
//
func (self *Notifier) GetServerInformation() (ServerInformation, error) {
	obj := self.conn.Object(dbusNotificationsInterface, objectPath)
	if obj == nil {
		return ServerInformation{}, errors.New("error creating dbus call object")
	}
	call := obj.Call(getServerInformation, 0)
	if call.Err != nil {
		log.Printf("Error calling %v: %v", getServerInformation, call.Err)
		return ServerInformation{}, call.Err
	}

	ret := ServerInformation{}
	err := call.Store(&ret.Name, &ret.Vendor, &ret.Version, &ret.SpecVersion)
	if err != nil {
		log.Printf("error reading %v return values: %v", getServerInformation, err)
		return ret, err
	}
	return ret, nil
}

// Notifyer is an interface for implementing SendNotification
type Notifyer interface {
	SendNotification(n Notification) (uint32, error)
}

// ServerInformator is an interface for implementing GetServerInformation
type ServerInformator interface {
	GetServerInformation() (ServerInformation, error)
}

// AskCapabilities is interface for GetCapabilities (see there)
type AskCapabilities interface {
	GetCapabilities() ([]string, error)
}

// Closer is an interface for implementing CloseNotification call
type Closer interface {
	CloseNotification(id int) (bool, error)
}

// Notificator is just a holder for all the small interfaces here.
type Notificator interface {
	Notifyer
	AskCapabilities
	ServerInformator
	Closer
}
<commit_msg>unexport some methods<commit_after>package notify

//
// This package is a wrapper around godbus for dbus notification interface
// See: https://developer.gnome.org/notification-spec/
//
// Each notification displayed is allocated a unique ID by the server. (see Notify)
// This ID unique within the dbus session. While the notification server is running,
// the ID will not be recycled unless the capacity of a uint32 is exceeded.
//
// This can be used to hide the notification before the expiration timeout is reached. (see CloseNotification)
//
// The ID can also be used to atomically replace the notification with another (Notification.ReplaceID).
// This allows you to (for instance) modify the contents of a notification while it's on-screen.
//

import (
	"errors"
	"log"

	"github.com/godbus/dbus"
)
const (
	objectPath                 = "/org/freedesktop/Notifications" // the DBUS object path
	dbusNotificationsInterface = "org.freedesktop.Notifications"  // DBUS Interface
	getCapabilities            = "org.freedesktop.Notifications.GetCapabilities"
	closeNotification          = "org.freedesktop.Notifications.CloseNotification"
	notify                     = "org.freedesktop.Notifications.Notify"
	getServerInformation       = "org.freedesktop.Notifications.GetServerInformation"
)

// New creates a new Notificator using conn
func New(conn *dbus.Conn) Notify {
	return &notifier{
		conn: conn,
	}
}

// notifier implements Notificator
type notifier struct {
	conn *dbus.Conn
}

// Notification holds all information needed for creating a notification
type Notification struct {
	AppName       string
	ReplacesID    uint32
	AppIcon       string // see icons here: http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
	Summary       string
	Body          string
	Actions       []string
	Hints         map[string]dbus.Variant
	ExpireTimeout int32 // millisecond to show notification
}

// SendNotification sends a notification to the notification server.
// Implements dbus call:
//
//     UINT32 org.freedesktop.Notifications.Notify ( STRING app_name,
//	    										 UINT32 replaces_id,
//	    										 STRING app_icon,
//	    										 STRING summary,
//	    										 STRING body,
//	    										 ARRAY  actions,
//	    										 DICT   hints,
//	    										 INT32  expire_timeout);
//
//		Name	    	Type	Description
//		app_name		STRING	The optional name of the application sending the notification. Can be blank.
//		replaces_id	    UINT32	The optional notification ID that this notification replaces. The server must atomically (ie with no flicker or other visual cues) replace the given notification with this one. This allows clients to effectively modify the notification while it's active. A value of value of 0 means that this notification won't replace any existing notifications.
//		app_icon		STRING	The optional program icon of the calling application. Can be an empty string, indicating no icon.
//		summary		    STRING	The summary text briefly describing the notification.
//		body			STRING	The optional detailed body text. Can be empty.
//		actions		    ARRAY	Actions are sent over as a list of pairs. Each even element in the list (starting at index 0) represents the identifier for the action. Each odd element in the list is the localized string that will be displayed to the user.
//		hints	        DICT	Optional hints that can be passed to the server from the client program. Although clients and servers should never assume each other supports any specific hints, they can be used to pass along information, such as the process PID or window ID, that the server may be able to make use of. See Hints. Can be empty.
//      expire_timeout  INT32   The timeout time in milliseconds since the display of the notification at which the notification should automatically close.
//								If -1, the notification's expiration time is dependent on the notification server's settings, and may vary for the type of notification. If 0, never expire.
//
// If replaces_id is 0, the return value is a UINT32 that represent the notification. It is unique, and will not be reused unless a MAXINT number of notifications have been generated. An acceptable implementation may just use an incrementing counter for the ID. The returned ID is always greater than zero. Servers must make sure not to return zero as an ID.
// If replaces_id is not 0, the returned value is the same value as replaces_id.
func (self *notifier) SendNotification(n Notification) (uint32, error) {
	return SendNotification(self.conn, n)
}

// SendNotification is same as Notifier.SendNotification
// Provided for convenience.
func SendNotification(conn *dbus.Conn, n Notification) (uint32, error) {
	obj := conn.Object(dbusNotificationsInterface, objectPath)
	call := obj.Call(notify, 0,
		n.AppName,
		n.ReplacesID,
		n.AppIcon,
		n.Summary,
		n.Body,
		n.Actions,
		n.Hints,
		n.ExpireTimeout)
	if call.Err != nil {
		return 0, call.Err
	}
	var ret uint32
	err := call.Store(&ret)
	if err != nil {
		log.Printf("error getting uint32 ret value: %v", err)
		return ret, err
	}
	return ret, nil
}

// GetCapabilities gets the capabilities of the notification server.
// This call takes no parameters.
// It returns an array of strings. Each string describes an optional capability implemented by the server.
//
// See also: https://developer.gnome.org/notification-spec/
func (self *notifier) GetCapabilities() ([]string, error) {
	obj := self.conn.Object(dbusNotificationsInterface, objectPath)
	call := obj.Call(getCapabilities, 0)
	if call.Err != nil {
		log.Printf("error calling GetCapabilities: %v", call.Err)
		return []string{}, call.Err
	}
	var ret []string
	err := call.Store(&ret)
	if err != nil {
		log.Printf("error getting capabilities ret value: %v", err)
		return ret, err
	}
	return ret, nil
}

// CloseNotification causes a notification to be forcefully closed and removed from the user's view.
// It can be used, for example, in the event that what the notification pertains to is no longer relevant,
// or to cancel a notification with no expiration time.
//
// The NotificationClosed (dbus) signal is emitted by this method.
// If the notification no longer exists, an empty D-BUS Error message is sent back.
func (self *notifier) CloseNotification(id int) (bool, error) {
	obj := self.conn.Object(dbusNotificationsInterface, objectPath)
	call := obj.Call(closeNotification, 0, uint32(id))
	if call.Err != nil {
		return false, call.Err
	}
	return true, nil
}

// ServerInformation is a holder for information returned by
// GetServerInformation call.
type ServerInformation struct {
	Name        string
	Vendor      string
	Version     string
	SpecVersion string
}

// GetServerInformation returns the information on the server.
//
// org.freedesktop.Notifications.GetServerInformation
//
//  GetServerInformation Return Values
//
//		Name		 Type	  Description
//		name		 STRING	  The product name of the server.
//		vendor		 STRING	  The vendor name. For example, "KDE," "GNOME," "freedesktop.org," or "Microsoft."
//		version		 STRING	  The server's version number.
//		spec_version STRING	  The specification version the server is compliant with.
//
func (self *notifier) GetServerInformation() (ServerInformation, error) {
	obj := self.conn.Object(dbusNotificationsInterface, objectPath)
	if obj == nil {
		return ServerInformation{}, errors.New("error creating dbus call object")
	}
	call := obj.Call(getServerInformation, 0)
	if call.Err != nil {
		log.Printf("Error calling %v: %v", getServerInformation, call.Err)
		return ServerInformation{}, call.Err
	}

	ret := ServerInformation{}
	err := call.Store(&ret.Name, &ret.Vendor, &ret.Version, &ret.SpecVersion)
	if err != nil {
		log.Printf("error reading %v return values: %v", getServerInformation, err)
		return ret, err
	}
	return ret, nil
}

// Notifyer is an interface for implementing SendNotification
type Notifyer interface {
	SendNotification(n Notification) (uint32, error)
}

// ServerInformator is an interface for implementing GetServerInformation
type ServerInformator interface {
	GetServerInformation() (ServerInformation, error)
}

// AskCapabilities is interface for GetCapabilities (see there)
type AskCapabilities interface {
	GetCapabilities() ([]string, error)
}

// Closer is an interface for implementing CloseNotification call
type Closer interface {
	CloseNotification(id int) (bool, error)
}

// Notificator is just a holder for all the small interfaces here.
type Notify interface {
	Notifyer
	AskCapabilities
	ServerInformator
	Closer
}
<|endoftext|>