<commit_before>package websession

import (
	"bytes"
	"fmt"
	"github.com/kr/pretty"
	"golang.org/x/net/context"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"testing"
	util_capnp "zenhack.net/go/sandstorm/capnp/util"
	"zenhack.net/go/sandstorm/capnp/websession"
	"zenhack.net/go/sandstorm/util"
	"zenhack.net/go/sandstorm/websession/pogs"
	"zombiezen.com/go/capnproto2/pogs"
)

type testCase struct {
	name     string
	handler  http.Handler
	request  testRequest
	response testResponse
}

type testRequest interface {
	Call(ctx context.Context, ws websession.WebSession) (testResponse, error)
}

type testResponse struct {
	resp       websession_pogs.Response
	streamBody []byte
}

type GetHeadReq websession_pogs.Get_args
type PostReq websession_pogs.Post_args

func doCall(wsCtx *websession_pogs.Context, f func() (websession.WebSession_Response, error)) (testResponse, error) {
	r, w := io.Pipe()
	buf := &bytes.Buffer{}
	done := make(chan struct{})
	go func() {
		io.Copy(buf, r)
		done <- struct{}{}
	}()
	wsCtx.ResponseStream = util_capnp.ByteStream_ServerToClient(
		&util.WriteCloserByteStream{w},
	)
	resp, err := f()
	if err != nil {
		return testResponse{}, err
	}
	goResp := testResponse{
		resp: websession_pogs.Response{},
	}
	err = pogs.Extract(&goResp.resp, websession.WebSession_Response_TypeID, resp.Struct)
	<-done
	goResp.streamBody = buf.Bytes()
	return goResp, err
}

func (req GetHeadReq) Call(ctx context.Context, ws websession.WebSession) (testResponse, error) {
	return doCall(&req.Context, func() (websession.WebSession_Response, error) {
		return ws.Get(ctx, func(p websession.WebSession_get_Params) error {
			return pogs.Insert(websession.WebSession_get_Params_TypeID, p.Struct, req)
		}).Struct()
	})
}

func (req PostReq) Call(ctx context.Context, ws websession.WebSession) (testResponse, error) {
	return doCall(&req.Context, func() (websession.WebSession_Response, error) {
		return ws.Post(ctx, func(p websession.WebSession_post_Params) error {
			return pogs.Insert(websession.WebSession_post_Params_TypeID, p.Struct, req)
		}).Struct()
	})
}

var testCases = []testCase{
	{
		name:    "Simple 200 OK",
		request: GetHeadReq{Path: "/", IgnoreBody: false},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte{},
		},
	},
	{
		name:    "Hello, World!",
		request: GetHeadReq{Path: "/", IgnoreBody: false},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			w.Write([]byte("Hello, World!"))
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("Hello, World!"),
		},
	},
	{
		name:    "Basic routing",
		request: GetHeadReq{Path: "/foo", IgnoreBody: false},
		handler: func() http.Handler {
			mux := http.NewServeMux()
			mux.HandleFunc("/foo", func(w http.ResponseWriter, req *http.Request) {
				w.Write([]byte("Visited Foo"))
			})
			mux.HandleFunc("/bar", func(w http.ResponseWriter, req *http.Request) {
				w.Write([]byte("Visited Bar"))
			})
			return mux
		}(),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("Visited Foo"),
		},
	},
	{
		name:    "GET vs HEAD (GET)",
		request: GetHeadReq{Path: "/", IgnoreBody: false},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			w.Write([]byte(req.Method))
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("GET"),
		},
	},
	{
		name:    "GET vs HEAD (HEAD)",
		request: GetHeadReq{Path: "/", IgnoreBody: true},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			w.Write([]byte(req.Method))
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("HEAD"),
		},
	},
	{
		name: "POST",
		request: PostReq{
			Path: "/post-test",
			Content: websession_pogs.PContent{
				MimeType: "text/plain",
				Content:  []byte("The Data."),
				Encoding: "utf-8",
			},
		},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			if req.URL.Path != "/post-test" {
				w.Write([]byte("bad path"))
				return
			}
			body, err := ioutil.ReadAll(req.Body)
			if err != nil {
				return
				w.Write([]byte("ReadAll error: " + err.Error()))
			}
			contentType := req.Header.Get("Content-Type")
			if contentType != "text/plain" {
				fmt.Printf("Unexpected Content-Type: %q", contentType)
				return
			}
			contentEncoding := req.Header.Get("Content-Encoding")
			if contentEncoding != "utf-8" {
				fmt.Printf("Unexpected Content-Encoding: %q", contentEncoding)
			}
			if string(body) != "The Data." {
				fmt.Fprintf(w, "Unexpected request body: %q", body)
				return
			}
			w.Write([]byte("ok"))
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("ok"),
		},
	},
}

// Return a bare-bones capnp response object.
func mkOkResponse() websession_pogs.Response {
	return websession_pogs.Response{
		Which: websession.WebSession_Response_Which_content,
		Content: websession_pogs.Response_content{
			StatusCode: websession.WebSession_Response_SuccessCode_ok,
			Body: websession_pogs.Response_content_body{
				Which:  websession.WebSession_Response_content_body_Which_stream,
				Stream: util_capnp.Handle_ServerToClient(struct{}{}),
			},
		},
	}

}

func TestTable(t *testing.T) {
	for _, v := range testCases {
		ctx := context.TODO()
		handlerWS := websession.WebSession_ServerToClient(FromHandler(ctx, v.handler))
		resp, err := v.request.Call(ctx, handlerWS)
		if err != nil {
			t.Errorf("Error in v.reqeust.Call in table test case %q: %v",
				v.name, err)
			continue
		}
		if !responseEq(v.response, resp) {
			t.Errorf("Unexpected response in table test case %q", v.name)
			pretty.Ldiff(t, resp, v.response)
			continue
		}
	}
}

func responseEq(expected, actual testResponse) bool {
	// reflect.DeepEqual does what we want for *most* of the data,
	// but not the interfaces. So, we set those to nil before the check,
	// then restore them.
	eStream := &expected.resp.Content.Body.Stream
	aStream := &actual.resp.Content.Body.Stream
	eClient := eStream.Client
	aClient := aStream.Client
	eStream.Client = nil
	aStream.Client = nil
	defer func() {
		eStream.Client = eClient
		aStream.Client = aClient
	}()

	return reflect.DeepEqual(expected, actual) &&
		// either the clients should both be nil, or neither of them
		// should be:
		(eClient == nil) == (aClient == nil)
}
<commit_msg>Use realistic value for Content-Encoding in test<commit_after>package websession

import (
	"bytes"
	"fmt"
	"github.com/kr/pretty"
	"golang.org/x/net/context"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"testing"
	util_capnp "zenhack.net/go/sandstorm/capnp/util"
	"zenhack.net/go/sandstorm/capnp/websession"
	"zenhack.net/go/sandstorm/util"
	"zenhack.net/go/sandstorm/websession/pogs"
	"zombiezen.com/go/capnproto2/pogs"
)

type testCase struct {
	name     string
	handler  http.Handler
	request  testRequest
	response testResponse
}

type testRequest interface {
	Call(ctx context.Context, ws websession.WebSession) (testResponse, error)
}

type testResponse struct {
	resp       websession_pogs.Response
	streamBody []byte
}

type GetHeadReq websession_pogs.Get_args
type PostReq websession_pogs.Post_args

func doCall(wsCtx *websession_pogs.Context, f func() (websession.WebSession_Response, error)) (testResponse, error) {
	r, w := io.Pipe()
	buf := &bytes.Buffer{}
	done := make(chan struct{})
	go func() {
		io.Copy(buf, r)
		done <- struct{}{}
	}()
	wsCtx.ResponseStream = util_capnp.ByteStream_ServerToClient(
		&util.WriteCloserByteStream{w},
	)
	resp, err := f()
	if err != nil {
		return testResponse{}, err
	}
	goResp := testResponse{
		resp: websession_pogs.Response{},
	}
	err = pogs.Extract(&goResp.resp, websession.WebSession_Response_TypeID, resp.Struct)
	<-done
	goResp.streamBody = buf.Bytes()
	return goResp, err
}

func (req GetHeadReq) Call(ctx context.Context, ws websession.WebSession) (testResponse, error) {
	return doCall(&req.Context, func() (websession.WebSession_Response, error) {
		return ws.Get(ctx, func(p websession.WebSession_get_Params) error {
			return pogs.Insert(websession.WebSession_get_Params_TypeID, p.Struct, req)
		}).Struct()
	})
}

func (req PostReq) Call(ctx context.Context, ws websession.WebSession) (testResponse, error) {
	return doCall(&req.Context, func() (websession.WebSession_Response, error) {
		return ws.Post(ctx, func(p websession.WebSession_post_Params) error {
			return pogs.Insert(websession.WebSession_post_Params_TypeID, p.Struct, req)
		}).Struct()
	})
}

var testCases = []testCase{
	{
		name:    "Simple 200 OK",
		request: GetHeadReq{Path: "/", IgnoreBody: false},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte{},
		},
	},
	{
		name:    "Hello, World!",
		request: GetHeadReq{Path: "/", IgnoreBody: false},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			w.Write([]byte("Hello, World!"))
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("Hello, World!"),
		},
	},
	{
		name:    "Basic routing",
		request: GetHeadReq{Path: "/foo", IgnoreBody: false},
		handler: func() http.Handler {
			mux := http.NewServeMux()
			mux.HandleFunc("/foo", func(w http.ResponseWriter, req *http.Request) {
				w.Write([]byte("Visited Foo"))
			})
			mux.HandleFunc("/bar", func(w http.ResponseWriter, req *http.Request) {
				w.Write([]byte("Visited Bar"))
			})
			return mux
		}(),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("Visited Foo"),
		},
	},
	{
		name:    "GET vs HEAD (GET)",
		request: GetHeadReq{Path: "/", IgnoreBody: false},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			w.Write([]byte(req.Method))
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("GET"),
		},
	},
	{
		name:    "GET vs HEAD (HEAD)",
		request: GetHeadReq{Path: "/", IgnoreBody: true},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			w.Write([]byte(req.Method))
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("HEAD"),
		},
	},
	{
		name: "POST",
		request: PostReq{
			Path: "/post-test",
			Content: websession_pogs.PContent{
				MimeType: "text/plain",
				Content:  []byte("The Data."),
				Encoding: "identity",
			},
		},
		handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			if req.URL.Path != "/post-test" {
				w.Write([]byte("bad path"))
				return
			}
			body, err := ioutil.ReadAll(req.Body)
			if err != nil {
				return
				w.Write([]byte("ReadAll error: " + err.Error()))
			}
			contentType := req.Header.Get("Content-Type")
			if contentType != "text/plain" {
				fmt.Printf("Unexpected Content-Type: %q", contentType)
				return
			}
			contentEncoding := req.Header.Get("Content-Encoding")
			if contentEncoding != "identity" {
				fmt.Printf("Unexpected Content-Encoding: %q", contentEncoding)
			}
			if string(body) != "The Data." {
				fmt.Fprintf(w, "Unexpected request body: %q", body)
				return
			}
			w.Write([]byte("ok"))
		}),
		response: testResponse{
			resp:       mkOkResponse(),
			streamBody: []byte("ok"),
		},
	},
}

// Return a bare-bones capnp response object.
func mkOkResponse() websession_pogs.Response {
	return websession_pogs.Response{
		Which: websession.WebSession_Response_Which_content,
		Content: websession_pogs.Response_content{
			StatusCode: websession.WebSession_Response_SuccessCode_ok,
			Body: websession_pogs.Response_content_body{
				Which:  websession.WebSession_Response_content_body_Which_stream,
				Stream: util_capnp.Handle_ServerToClient(struct{}{}),
			},
		},
	}

}

func TestTable(t *testing.T) {
	for _, v := range testCases {
		ctx := context.TODO()
		handlerWS := websession.WebSession_ServerToClient(FromHandler(ctx, v.handler))
		resp, err := v.request.Call(ctx, handlerWS)
		if err != nil {
			t.Errorf("Error in v.reqeust.Call in table test case %q: %v",
				v.name, err)
			continue
		}
		if !responseEq(v.response, resp) {
			t.Errorf("Unexpected response in table test case %q", v.name)
			pretty.Ldiff(t, resp, v.response)
			continue
		}
	}
}

func responseEq(expected, actual testResponse) bool {
	// reflect.DeepEqual does what we want for *most* of the data,
	// but not the interfaces. So, we set those to nil before the check,
	// then restore them.
	eStream := &expected.resp.Content.Body.Stream
	aStream := &actual.resp.Content.Body.Stream
	eClient := eStream.Client
	aClient := aStream.Client
	eStream.Client = nil
	aStream.Client = nil
	defer func() {
		eStream.Client = eClient
		aStream.Client = aClient
	}()

	return reflect.DeepEqual(expected, actual) &&
		// either the clients should both be nil, or neither of them
		// should be:
		(eClient == nil) == (aClient == nil)
}
<|endoftext|>


<commit_before>// Copyright 2013-2019 Aerospike, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aerospike

import "time"

// MultiPolicy contains parameters for policy attributes used in
// query and scan operations.
type MultiPolicy struct {
	BasePolicy

	// Maximum number of concurrent requests to server nodes at any poin int time.
	// If there are 16 nodes in the cluster and maxConcurrentNodes is 8, then queries
	// will be made to 8 nodes in parallel.  When a query completes, a new query will
	// be issued until all 16 nodes have been queried.
	// Default (0) is to issue requests to all server nodes in parallel.
	MaxConcurrentNodes int

	// FailOnClusterChange determines scan termination if cluster is in fluctuating state.
	FailOnClusterChange bool

	// Number of records to place in queue before blocking.
	// Records received from multiple server nodes will be placed in a queue.
	// A separate goroutine consumes these records in parallel.
	// If the queue is full, the producer goroutines will block until records are consumed.
	RecordQueueSize int //= 50

	// Indicates if bin data is retrieved. If false, only record digests are retrieved.
	IncludeBinData bool //= true;
}

// NewMultiPolicy initializes a MultiPolicy instance with default values.
func NewMultiPolicy() *MultiPolicy {
	bp := *NewPolicy()
	bp.SocketTimeout = 30 * time.Second

	return &MultiPolicy{
		BasePolicy:          bp,
		MaxConcurrentNodes:  0,
		RecordQueueSize:     50,
		IncludeBinData:      true,
		FailOnClusterChange: true,
	}
}
<commit_msg>Fix typo<commit_after>// Copyright 2013-2019 Aerospike, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aerospike

import "time"

// MultiPolicy contains parameters for policy attributes used in
// query and scan operations.
type MultiPolicy struct {
	BasePolicy

	// Maximum number of concurrent requests to server nodes at any point in time.
	// If there are 16 nodes in the cluster and maxConcurrentNodes is 8, then queries
	// will be made to 8 nodes in parallel.  When a query completes, a new query will
	// be issued until all 16 nodes have been queried.
	// Default (0) is to issue requests to all server nodes in parallel.
	MaxConcurrentNodes int

	// FailOnClusterChange determines scan termination if cluster is in fluctuating state.
	FailOnClusterChange bool

	// Number of records to place in queue before blocking.
	// Records received from multiple server nodes will be placed in a queue.
	// A separate goroutine consumes these records in parallel.
	// If the queue is full, the producer goroutines will block until records are consumed.
	RecordQueueSize int //= 50

	// Indicates if bin data is retrieved. If false, only record digests are retrieved.
	IncludeBinData bool //= true;
}

// NewMultiPolicy initializes a MultiPolicy instance with default values.
func NewMultiPolicy() *MultiPolicy {
	bp := *NewPolicy()
	bp.SocketTimeout = 30 * time.Second

	return &MultiPolicy{
		BasePolicy:          bp,
		MaxConcurrentNodes:  0,
		RecordQueueSize:     50,
		IncludeBinData:      true,
		FailOnClusterChange: true,
	}
}
<|endoftext|>


<commit_before>package notify

import (
	"errors"
	"fmt"
	"log"
	"sync"

	"github.com/godbus/dbus/v5"
)

const (
	dbusRemoveMatch            = "org.freedesktop.DBus.RemoveMatch"
	dbusAddMatch               = "org.freedesktop.DBus.AddMatch"
	dbusObjectPath             = "/org/freedesktop/Notifications" // the DBUS object path
	dbusNotificationsInterface = "org.freedesktop.Notifications"  // DBUS Interface
	signalNotificationClosed   = "org.freedesktop.Notifications.NotificationClosed"
	signalActionInvoked        = "org.freedesktop.Notifications.ActionInvoked"
	callGetCapabilities        = "org.freedesktop.Notifications.GetCapabilities"
	callCloseNotification      = "org.freedesktop.Notifications.CloseNotification"
	callNotify                 = "org.freedesktop.Notifications.Notify"
	callGetServerInformation   = "org.freedesktop.Notifications.GetServerInformation"

	channelBufferSize = 10
)

// Notification holds all information needed for creating a notification
type Notification struct {
	AppName string
	// Setting ReplacesID atomically replaces the notification with this ID.
	// Optional.
	ReplacesID uint32
	// See predefined icons here: http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
	// Optional.
	AppIcon string
	Summary string
	Body    string
	// Actions are tuples of (action_key, label), e.g.: []string{"cancel", "Cancel", "open", "Open"}
	Actions []string
	Hints   map[string]dbus.Variant
	// ExpireTimeout: milliseconds to show notification
	ExpireTimeout int32
}

// SendNotification is provided for convenience.
// Use if you only want to deliver a notification and dont care about events.
func SendNotification(conn *dbus.Conn, note Notification) (uint32, error) {
	actions := len(note.Actions)
	if (actions % 2) != 0 {
		return 0, errors.New("actions must be pairs of (key, label)")
	}

	obj := conn.Object(dbusNotificationsInterface, dbusObjectPath)
	call := obj.Call(callNotify, 0,
		note.AppName,
		note.ReplacesID,
		note.AppIcon,
		note.Summary,
		note.Body,
		note.Actions,
		note.Hints,
		note.ExpireTimeout)
	if call.Err != nil {
		return 0, fmt.Errorf("error sending notification: %w", call.Err)
	}
	var ret uint32
	err := call.Store(&ret)
	if err != nil {
		return ret, fmt.Errorf("error getting uint32 ret value: %w", err)
	}
	return ret, 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 GetServerInformation(conn *dbus.Conn) (ServerInformation, error) {
	obj := conn.Object(dbusNotificationsInterface, dbusObjectPath)
	if obj == nil {
		return ServerInformation{}, errors.New("error creating dbus call object")
	}
	call := obj.Call(callGetServerInformation, 0)
	if call.Err != nil {
		return ServerInformation{}, fmt.Errorf("error calling %v: %v", callGetServerInformation, call.Err)
	}

	ret := ServerInformation{}
	err := call.Store(&ret.Name, &ret.Vendor, &ret.Version, &ret.SpecVersion)
	if err != nil {
		return ret, fmt.Errorf("error reading %v return values: %v", callGetServerInformation, 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/
// GetCapabilities provide an exported method for this operation
func GetCapabilities(conn *dbus.Conn) ([]string, error) {
	obj := conn.Object(dbusNotificationsInterface, dbusObjectPath)
	call := obj.Call(callGetCapabilities, 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
}

// Notifier is an interface for implementing the operations supported by the
// freedesktop DBus Notifications object.
//
// New() sets up a Notifier that listens on dbus' signals regarding
// Notifications: NotificationClosed and ActionInvoked.
//
// Note this also means the caller MUST consume output from these channels,
// given in methods NotificationClosed() and ActionInvoked().
// Users that only want to send a simple notification, but don't care about
// interactions, see exported method: SendNotification(conn, Notification)
//
// Caller is also responsible to call Close() before exiting,
// to shut down event loop and cleanup.
type Notifier interface {
	SendNotification(n Notification) (uint32, error)
	GetCapabilities() ([]string, error)
	GetServerInformation() (ServerInformation, error)
	CloseNotification(id int) (bool, error)
	//NotificationClosed() <-chan *NotificationClosedSignal
	//ActionInvoked() <-chan *ActionInvokedSignal
	Close() error
}

type NotificationClosedHandler func(*NotificationClosedSignal)
type ActionInvokedHandler func(*ActionInvokedSignal)

// notifier implements Notifier interface
type notifier struct {
	conn     *dbus.Conn
	signal   chan *dbus.Signal
	done     chan bool
	onClosed NotificationClosedHandler
	onAction ActionInvokedHandler
	wg       *sync.WaitGroup
	log      logger
}

type logger interface {
	Printf(format string, v ...interface{})
}

// Option overrides certain parts of a Notifier
type Option func(*notifier)

// SetLogger sets a new logger func
func SetLogger(logz logger) Option {
	return func(n *notifier) {
		n.log = logz
	}
}

// SetOnAction sets ActionInvokedHandler handler
func SetOnAction(h ActionInvokedHandler) Option {
	return func(n *notifier) {
		n.onAction = h
	}
}

// SetOnClosed sets NotificationClosed handler
func SetOnClosed(h NotificationClosedHandler) Option {
	return func(n *notifier) {
		n.onClosed = h
	}
}

// New creates a new Notifier using conn.
// See also: Notifier
func New(conn *dbus.Conn, opts ...Option) (Notifier, error) {
	n := &notifier{
		conn:   conn,
		signal: make(chan *dbus.Signal, channelBufferSize),
		done:   make(chan bool),
		wg:     &sync.WaitGroup{},
		onClosed: func(s *NotificationClosedSignal) {},
		onAction: func(s *ActionInvokedSignal) {},
	}

	for _, val := range opts {
		val(n)
	}

	// add a listener in dbus for signals to Notification interface.
	call := n.conn.BusObject().Call(dbusAddMatch, 0,
		"type='signal',path='"+dbusObjectPath+"',interface='"+dbusNotificationsInterface+"'")
	if call.Err != nil {
		return nil, call.Err
	}

	// start eventloop
	go n.eventLoop()

	// register in dbus for signal delivery
	n.conn.Signal(n.signal)

	return n, nil
}

func (n notifier) eventLoop() {
	n.wg.Add(1)
	defer n.wg.Done()
	for {
		select {
		case signal := <-n.signal:
			n.handleSignal(signal)
		case <-n.done:
			n.log.Printf("Got Close() signal, shutting down...")
			return
		}
	}
}

// signal handler that translates and sends notifications to channels
func (n notifier) handleSignal(signal *dbus.Signal) {
	switch signal.Name {
	case signalNotificationClosed:
		nc := &NotificationClosedSignal{
			ID:     signal.Body[0].(uint32),
			Reason: Reason(signal.Body[1].(uint32)),
		}
		n.onClosed(nc)
	case signalActionInvoked:
		is := &ActionInvokedSignal{
			ID:        signal.Body[0].(uint32),
			ActionKey: signal.Body[1].(string),
		}
		n.onAction(is)
	default:
		n.log.Printf("unknown signal: %+v", signal)
	}
}

func (n *notifier) GetCapabilities() ([]string, error) {
	return GetCapabilities(n.conn)
}
func (n *notifier) GetServerInformation() (ServerInformation, error) {
	return GetServerInformation(n.conn)
}

// 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 (n *notifier) SendNotification(note Notification) (uint32, error) {
	return SendNotification(n.conn, note)
}

// 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 (n *notifier) CloseNotification(id int) (bool, error) {
	obj := n.conn.Object(dbusNotificationsInterface, dbusObjectPath)
	call := obj.Call(callCloseNotification, 0, uint32(id))
	if call.Err != nil {
		return false, call.Err
	}
	return true, nil
}

// NotificationClosedSignal holds data for *Closed callbacks from Notifications Interface.
type NotificationClosedSignal struct {
	ID     uint32
	Reason Reason
}

// Reason for the closed notification
type Reason uint32

const (
	// ReasonExpired when a notification expired
	ReasonExpired Reason = 1

	// ReasonDismissedByUser when a notification has been dismissed by a user
	ReasonDismissedByUser Reason = 2

	// ReasonClosedByCall when a notification has been closed by a call to CloseNotification
	ReasonClosedByCall Reason = 3

	// ReasonUnknown when as notification has been closed for an unknown reason
	ReasonUnknown Reason = 4
)

func (r Reason) String() string {
	switch r {
	case ReasonExpired:
		return "Expired"
	case ReasonDismissedByUser:
		return "DismissedByUser"
	case ReasonClosedByCall:
		return "ClosedByCall"
	case ReasonUnknown:
		return "Unknown"
	default:
		return "Other"
	}
}

// ActionInvokedSignal holds callback data from any Actions passed to Notification
type ActionInvokedSignal struct {
	ID        uint32
	ActionKey string
}

// Close cleans up and shuts down signal delivery loop
func (n *notifier) Close() error {
	n.done <- true

	n.conn.
		BusObject().
		Call(
			dbusRemoveMatch,
			0,
			"type='signal',path='"+dbusObjectPath+"',interface='"+dbusNotificationsInterface+"'")

	// remove signal reception
	defer n.conn.Signal(n.signal)
	close(n.done)

	return nil
}
<commit_msg>fix run<commit_after>package notify

import (
	"errors"
	"fmt"
	"log"
	"sync"

	"github.com/godbus/dbus/v5"
)

const (
	dbusRemoveMatch            = "org.freedesktop.DBus.RemoveMatch"
	dbusAddMatch               = "org.freedesktop.DBus.AddMatch"
	dbusObjectPath             = "/org/freedesktop/Notifications" // the DBUS object path
	dbusNotificationsInterface = "org.freedesktop.Notifications"  // DBUS Interface
	signalNotificationClosed   = "org.freedesktop.Notifications.NotificationClosed"
	signalActionInvoked        = "org.freedesktop.Notifications.ActionInvoked"
	callGetCapabilities        = "org.freedesktop.Notifications.GetCapabilities"
	callCloseNotification      = "org.freedesktop.Notifications.CloseNotification"
	callNotify                 = "org.freedesktop.Notifications.Notify"
	callGetServerInformation   = "org.freedesktop.Notifications.GetServerInformation"

	channelBufferSize = 10
)

// Notification holds all information needed for creating a notification
type Notification struct {
	AppName string
	// Setting ReplacesID atomically replaces the notification with this ID.
	// Optional.
	ReplacesID uint32
	// See predefined icons here: http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
	// Optional.
	AppIcon string
	Summary string
	Body    string
	// Actions are tuples of (action_key, label), e.g.: []string{"cancel", "Cancel", "open", "Open"}
	Actions []string
	Hints   map[string]dbus.Variant
	// ExpireTimeout: milliseconds to show notification
	ExpireTimeout int32
}

// SendNotification is provided for convenience.
// Use if you only want to deliver a notification and dont care about events.
func SendNotification(conn *dbus.Conn, note Notification) (uint32, error) {
	actions := len(note.Actions)
	if (actions % 2) != 0 {
		return 0, errors.New("actions must be pairs of (key, label)")
	}

	obj := conn.Object(dbusNotificationsInterface, dbusObjectPath)
	call := obj.Call(callNotify, 0,
		note.AppName,
		note.ReplacesID,
		note.AppIcon,
		note.Summary,
		note.Body,
		note.Actions,
		note.Hints,
		note.ExpireTimeout)
	if call.Err != nil {
		return 0, fmt.Errorf("error sending notification: %w", call.Err)
	}
	var ret uint32
	err := call.Store(&ret)
	if err != nil {
		return ret, fmt.Errorf("error getting uint32 ret value: %w", err)
	}
	return ret, 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 GetServerInformation(conn *dbus.Conn) (ServerInformation, error) {
	obj := conn.Object(dbusNotificationsInterface, dbusObjectPath)
	if obj == nil {
		return ServerInformation{}, errors.New("error creating dbus call object")
	}
	call := obj.Call(callGetServerInformation, 0)
	if call.Err != nil {
		return ServerInformation{}, fmt.Errorf("error calling %v: %v", callGetServerInformation, call.Err)
	}

	ret := ServerInformation{}
	err := call.Store(&ret.Name, &ret.Vendor, &ret.Version, &ret.SpecVersion)
	if err != nil {
		return ret, fmt.Errorf("error reading %v return values: %v", callGetServerInformation, 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/
// GetCapabilities provide an exported method for this operation
func GetCapabilities(conn *dbus.Conn) ([]string, error) {
	obj := conn.Object(dbusNotificationsInterface, dbusObjectPath)
	call := obj.Call(callGetCapabilities, 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
}

// Notifier is an interface for implementing the operations supported by the
// freedesktop DBus Notifications object.
//
// New() sets up a Notifier that listens on dbus' signals regarding
// Notifications: NotificationClosed and ActionInvoked.
//
// Note this also means the caller MUST consume output from these channels,
// given in methods NotificationClosed() and ActionInvoked().
// Users that only want to send a simple notification, but don't care about
// interactions, see exported method: SendNotification(conn, Notification)
//
// Caller is also responsible to call Close() before exiting,
// to shut down event loop and cleanup.
type Notifier interface {
	SendNotification(n Notification) (uint32, error)
	GetCapabilities() ([]string, error)
	GetServerInformation() (ServerInformation, error)
	CloseNotification(id int) (bool, error)
	//NotificationClosed() <-chan *NotificationClosedSignal
	//ActionInvoked() <-chan *ActionInvokedSignal
	Close() error
}

type NotificationClosedHandler func(*NotificationClosedSignal)
type ActionInvokedHandler func(*ActionInvokedSignal)

// notifier implements Notifier interface
type notifier struct {
	conn     *dbus.Conn
	signal   chan *dbus.Signal
	done     chan bool
	onClosed NotificationClosedHandler
	onAction ActionInvokedHandler
	wg       *sync.WaitGroup
	log      logger
}

type logger interface {
	Printf(format string, v ...interface{})
}

// Option overrides certain parts of a Notifier
type Option func(*notifier)

// SetLogger sets a new logger func
func SetLogger(logz logger) Option {
	return func(n *notifier) {
		n.log = logz
	}
}

// SetOnAction sets ActionInvokedHandler handler
func SetOnAction(h ActionInvokedHandler) Option {
	return func(n *notifier) {
		n.onAction = h
	}
}

// SetOnClosed sets NotificationClosed handler
func SetOnClosed(h NotificationClosedHandler) Option {
	return func(n *notifier) {
		n.onClosed = h
	}
}

type loggerWrapper struct {
	prefix string
}

func (l *loggerWrapper) Printf(format string, v ...interface{}) {
	log.Printf(l.prefix+format, v...)
}

// New creates a new Notifier using conn.
// See also: Notifier
func New(conn *dbus.Conn, opts ...Option) (Notifier, error) {
	n := &notifier{
		conn:     conn,
		signal:   make(chan *dbus.Signal, channelBufferSize),
		done:     make(chan bool),
		wg:       &sync.WaitGroup{},
		onClosed: func(s *NotificationClosedSignal) {},
		onAction: func(s *ActionInvokedSignal) {},
		log:      &loggerWrapper{"notify: "},
	}

	for _, val := range opts {
		val(n)
	}

	// add a listener in dbus for signals to Notification interface.
	call := n.conn.BusObject().Call(dbusAddMatch, 0,
		"type='signal',path='"+dbusObjectPath+"',interface='"+dbusNotificationsInterface+"'")
	if call.Err != nil {
		return nil, call.Err
	}

	// start eventloop
	go n.eventLoop()

	// register in dbus for signal delivery
	n.conn.Signal(n.signal)

	return n, nil
}

func (n notifier) eventLoop() {
	n.wg.Add(1)
	defer n.wg.Done()
	for {
		select {
		case signal := <-n.signal:
			n.handleSignal(signal)
		case <-n.done:
			n.log.Printf("Got Close() signal, shutting down...")
			return
		}
	}
}

// signal handler that translates and sends notifications to channels
func (n notifier) handleSignal(signal *dbus.Signal) {
	switch signal.Name {
	case signalNotificationClosed:
		nc := &NotificationClosedSignal{
			ID:     signal.Body[0].(uint32),
			Reason: Reason(signal.Body[1].(uint32)),
		}
		n.onClosed(nc)
	case signalActionInvoked:
		is := &ActionInvokedSignal{
			ID:        signal.Body[0].(uint32),
			ActionKey: signal.Body[1].(string),
		}
		n.onAction(is)
	default:
		n.log.Printf("unknown signal: %+v", signal)
	}
}

func (n *notifier) GetCapabilities() ([]string, error) {
	return GetCapabilities(n.conn)
}
func (n *notifier) GetServerInformation() (ServerInformation, error) {
	return GetServerInformation(n.conn)
}

// 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 (n *notifier) SendNotification(note Notification) (uint32, error) {
	return SendNotification(n.conn, note)
}

// 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 (n *notifier) CloseNotification(id int) (bool, error) {
	obj := n.conn.Object(dbusNotificationsInterface, dbusObjectPath)
	call := obj.Call(callCloseNotification, 0, uint32(id))
	if call.Err != nil {
		return false, call.Err
	}
	return true, nil
}

// NotificationClosedSignal holds data for *Closed callbacks from Notifications Interface.
type NotificationClosedSignal struct {
	ID     uint32
	Reason Reason
}

// Reason for the closed notification
type Reason uint32

const (
	// ReasonExpired when a notification expired
	ReasonExpired Reason = 1

	// ReasonDismissedByUser when a notification has been dismissed by a user
	ReasonDismissedByUser Reason = 2

	// ReasonClosedByCall when a notification has been closed by a call to CloseNotification
	ReasonClosedByCall Reason = 3

	// ReasonUnknown when as notification has been closed for an unknown reason
	ReasonUnknown Reason = 4
)

func (r Reason) String() string {
	switch r {
	case ReasonExpired:
		return "Expired"
	case ReasonDismissedByUser:
		return "DismissedByUser"
	case ReasonClosedByCall:
		return "ClosedByCall"
	case ReasonUnknown:
		return "Unknown"
	default:
		return "Other"
	}
}

// ActionInvokedSignal holds callback data from any Actions passed to Notification
type ActionInvokedSignal struct {
	ID        uint32
	ActionKey string
}

// Close cleans up and shuts down signal delivery loop
func (n *notifier) Close() error {
	n.done <- true

	n.conn.
		BusObject().
		Call(
			dbusRemoveMatch,
			0,
			"type='signal',path='"+dbusObjectPath+"',interface='"+dbusNotificationsInterface+"'")

	// remove signal reception
	defer n.conn.Signal(n.signal)
	close(n.done)

	return nil
}
<|endoftext|>


<commit_before>package apns

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"time"
)

const (
	PriorityImmediate     = 10
	PriorityPowerConserve = 5
)

const (
	commandID = 2

	// Items IDs
	deviceTokenItemID            = 1
	payloadItemID                = 2
	notificationIdentifierItemID = 3
	expirationDateItemID         = 4
	priorityItemID               = 5

	// Item lengths
	deviceTokenItemLength            = 32
	notificationIdentifierItemLength = 4
	expirationDateItemLength         = 4
	priorityItemLength               = 1
)

type NotificationResult struct {
	Notif Notification
	Err   Error
}

type Alert struct {
	// Do not add fields without updating the implementation of isZero.
	Body         string   `json:"body,omitempty"`
	Title        string   `json:"title,omitempty"`
	Action       string   `json:"action,omitempty"`
	LocKey       string   `json:"loc-key,omitempty"`
	LocArgs      []string `json:"loc-args,omitempty"`
	ActionLocKey string   `json:"action-loc-key,omitempty"`
	LaunchImage  string   `json:"launch-image,omitempty"`
}

func (a *Alert) isZero() bool {
	return len(a.Body) == 0 && len(a.LocKey) == 0 && len(a.LocArgs) == 0 && len(a.ActionLocKey) == 0 && len(a.LaunchImage) == 0
}

type APS struct {
	Alert            Alert
	Badge            *int // 0 to clear notifications, nil to leave as is.
	Sound            string
	ContentAvailable int
	URLArgs          []string
	Category         string // requires iOS 8+
}

func (aps APS) MarshalJSON() ([]byte, error) {
	data := make(map[string]interface{})

	if !aps.Alert.isZero() {
		data["alert"] = aps.Alert
	}
	if aps.Badge != nil {
		data["badge"] = aps.Badge
	}
	if aps.Sound != "" {
		data["sound"] = aps.Sound
	}
	if aps.ContentAvailable != 0 {
		data["content-available"] = aps.ContentAvailable
	}
	if aps.Category != "" {
		data["category"] = aps.Category
	}
	if aps.URLArgs != nil && len(aps.URLArgs) != 0 {
		data["url-args"] = aps.URLArgs
	}

	return json.Marshal(data)
}

type Payload struct {
	APS APS
	// MDM for mobile device management
	MDM          string
	customValues map[string]interface{}
}

func (p *Payload) MarshalJSON() ([]byte, error) {
	if len(p.MDM) != 0 {
		p.customValues["mdm"] = p.MDM
	} else {
		p.customValues["aps"] = p.APS
	}

	return json.Marshal(p.customValues)
}

func (p *Payload) SetCustomValue(key string, value interface{}) error {
	if key == "aps" {
		return errors.New("cannot assign a custom APS value in payload")
	}

	p.customValues[key] = value

	return nil
}

type Notification struct {
	ID          string
	DeviceToken string
	Identifier  uint32
	Expiration  *time.Time
	Priority    int
	Payload     *Payload
}

func NewNotification() Notification {
	return Notification{Payload: NewPayload()}
}

func NewPayload() *Payload {
	return &Payload{customValues: map[string]interface{}{}}
}

func (n Notification) ToBinary() ([]byte, error) {
	b := []byte{}

	binTok, err := hex.DecodeString(n.DeviceToken)
	if err != nil {
		return b, fmt.Errorf("convert token to hex error: %s", err)
	}

	j, _ := json.Marshal(n.Payload)

	buf := bytes.NewBuffer(b)

	// Token
	binary.Write(buf, binary.BigEndian, uint8(deviceTokenItemID))
	binary.Write(buf, binary.BigEndian, uint16(deviceTokenItemLength))
	binary.Write(buf, binary.BigEndian, binTok)

	// Payload
	binary.Write(buf, binary.BigEndian, uint8(payloadItemID))
	binary.Write(buf, binary.BigEndian, uint16(len(j)))
	binary.Write(buf, binary.BigEndian, j)

	// Identifier
	binary.Write(buf, binary.BigEndian, uint8(notificationIdentifierItemID))
	binary.Write(buf, binary.BigEndian, uint16(notificationIdentifierItemLength))
	binary.Write(buf, binary.BigEndian, uint32(n.Identifier))

	// Expiry
	binary.Write(buf, binary.BigEndian, uint8(expirationDateItemID))
	binary.Write(buf, binary.BigEndian, uint16(expirationDateItemLength))
	if n.Expiration == nil {
		binary.Write(buf, binary.BigEndian, uint32(0))
	} else {
		binary.Write(buf, binary.BigEndian, uint32(n.Expiration.Unix()))
	}

	// Priority
	binary.Write(buf, binary.BigEndian, uint8(priorityItemID))
	binary.Write(buf, binary.BigEndian, uint16(priorityItemLength))
	binary.Write(buf, binary.BigEndian, uint8(n.Priority))

	framebuf := bytes.NewBuffer([]byte{})
	binary.Write(framebuf, binary.BigEndian, uint8(commandID))
	binary.Write(framebuf, binary.BigEndian, uint32(buf.Len()))
	binary.Write(framebuf, binary.BigEndian, buf.Bytes())

	return framebuf.Bytes(), nil
}
<commit_msg>revise Alert isZero to check Title and Action<commit_after>package apns

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"time"
)

const (
	PriorityImmediate     = 10
	PriorityPowerConserve = 5
)

const (
	commandID = 2

	// Items IDs
	deviceTokenItemID            = 1
	payloadItemID                = 2
	notificationIdentifierItemID = 3
	expirationDateItemID         = 4
	priorityItemID               = 5

	// Item lengths
	deviceTokenItemLength            = 32
	notificationIdentifierItemLength = 4
	expirationDateItemLength         = 4
	priorityItemLength               = 1
)

type NotificationResult struct {
	Notif Notification
	Err   Error
}

type Alert struct {
	// Do not add fields without updating the implementation of isZero.
	Body         string   `json:"body,omitempty"`
	Title        string   `json:"title,omitempty"`
	Action       string   `json:"action,omitempty"`
	LocKey       string   `json:"loc-key,omitempty"`
	LocArgs      []string `json:"loc-args,omitempty"`
	ActionLocKey string   `json:"action-loc-key,omitempty"`
	LaunchImage  string   `json:"launch-image,omitempty"`
}

func (a *Alert) isZero() bool {
	return len(a.Body) == 0 && len(a.Title) == 0 && len(a.Action) == 0 && len(a.LocKey) == 0 && len(a.LocArgs) == 0 && len(a.ActionLocKey) == 0 && len(a.LaunchImage) == 0
}

type APS struct {
	Alert            Alert
	Badge            *int // 0 to clear notifications, nil to leave as is.
	Sound            string
	ContentAvailable int
	URLArgs          []string
	Category         string // requires iOS 8+
}

func (aps APS) MarshalJSON() ([]byte, error) {
	data := make(map[string]interface{})

	if !aps.Alert.isZero() {
		data["alert"] = aps.Alert
	}
	if aps.Badge != nil {
		data["badge"] = aps.Badge
	}
	if aps.Sound != "" {
		data["sound"] = aps.Sound
	}
	if aps.ContentAvailable != 0 {
		data["content-available"] = aps.ContentAvailable
	}
	if aps.Category != "" {
		data["category"] = aps.Category
	}
	if aps.URLArgs != nil && len(aps.URLArgs) != 0 {
		data["url-args"] = aps.URLArgs
	}

	return json.Marshal(data)
}

type Payload struct {
	APS APS
	// MDM for mobile device management
	MDM          string
	customValues map[string]interface{}
}

func (p *Payload) MarshalJSON() ([]byte, error) {
	if len(p.MDM) != 0 {
		p.customValues["mdm"] = p.MDM
	} else {
		p.customValues["aps"] = p.APS
	}

	return json.Marshal(p.customValues)
}

func (p *Payload) SetCustomValue(key string, value interface{}) error {
	if key == "aps" {
		return errors.New("cannot assign a custom APS value in payload")
	}

	p.customValues[key] = value

	return nil
}

type Notification struct {
	ID          string
	DeviceToken string
	Identifier  uint32
	Expiration  *time.Time
	Priority    int
	Payload     *Payload
}

func NewNotification() Notification {
	return Notification{Payload: NewPayload()}
}

func NewPayload() *Payload {
	return &Payload{customValues: map[string]interface{}{}}
}

func (n Notification) ToBinary() ([]byte, error) {
	b := []byte{}

	binTok, err := hex.DecodeString(n.DeviceToken)
	if err != nil {
		return b, fmt.Errorf("convert token to hex error: %s", err)
	}

	j, _ := json.Marshal(n.Payload)

	buf := bytes.NewBuffer(b)

	// Token
	binary.Write(buf, binary.BigEndian, uint8(deviceTokenItemID))
	binary.Write(buf, binary.BigEndian, uint16(deviceTokenItemLength))
	binary.Write(buf, binary.BigEndian, binTok)

	// Payload
	binary.Write(buf, binary.BigEndian, uint8(payloadItemID))
	binary.Write(buf, binary.BigEndian, uint16(len(j)))
	binary.Write(buf, binary.BigEndian, j)

	// Identifier
	binary.Write(buf, binary.BigEndian, uint8(notificationIdentifierItemID))
	binary.Write(buf, binary.BigEndian, uint16(notificationIdentifierItemLength))
	binary.Write(buf, binary.BigEndian, uint32(n.Identifier))

	// Expiry
	binary.Write(buf, binary.BigEndian, uint8(expirationDateItemID))
	binary.Write(buf, binary.BigEndian, uint16(expirationDateItemLength))
	if n.Expiration == nil {
		binary.Write(buf, binary.BigEndian, uint32(0))
	} else {
		binary.Write(buf, binary.BigEndian, uint32(n.Expiration.Unix()))
	}

	// Priority
	binary.Write(buf, binary.BigEndian, uint8(priorityItemID))
	binary.Write(buf, binary.BigEndian, uint16(priorityItemLength))
	binary.Write(buf, binary.BigEndian, uint8(n.Priority))

	framebuf := bytes.NewBuffer([]byte{})
	binary.Write(framebuf, binary.BigEndian, uint8(commandID))
	binary.Write(framebuf, binary.BigEndian, uint32(buf.Len()))
	binary.Write(framebuf, binary.BigEndian, buf.Bytes())

	return framebuf.Bytes(), nil
}
<|endoftext|>


<commit_before>package class

import (
	"testing"
)

func TestClassFactory(t *testing.T) {
	NewClassAssassin()
	NewClassDisabler()
	NewClassHealer()
	NewClassMage()
	NewClassTank()
}
<commit_msg>Delete the meaningless tests<commit_after><|endoftext|>


<commit_before>/*
   Copyright 2020 Docker, Inc.

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

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

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

package mobycli

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"strings"

	apicontext "github.com/docker/api/context"
	"github.com/docker/api/context/store"
)

var delegatedContextTypes = []string{store.DefaultContextType}

// ComDockerCli name of the classic cli binary
const ComDockerCli = "com.docker.cli"

// ExecIfDefaultCtxType delegates to com.docker.cli if on moby or AWS context (until there is an AWS backend)
func ExecIfDefaultCtxType(ctx context.Context) {
	currentContext := apicontext.CurrentContext(ctx)

	s := store.ContextStore(ctx)

	currentCtx, err := s.Get(currentContext)
	// Only run original docker command if the current context is not ours.
	if err != nil || mustDelegateToMoby(currentCtx.Type()) {
		Exec()
	}
}

func mustDelegateToMoby(ctxType string) bool {
	for _, ctype := range delegatedContextTypes {
		if ctxType == ctype {
			return true
		}
	}
	return false
}

// Exec delegates to com.docker.cli if on moby context
func Exec() {
	cmd := exec.Command(ComDockerCli, os.Args[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		if exiterr, ok := err.(*exec.ExitError); ok {
			os.Exit(exiterr.ExitCode())
		}
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	os.Exit(0)
}

// IsDefaultContextCommand checks if the command exists in the classic cli (issues a shellout --help)
func IsDefaultContextCommand(dockerCommand string) bool {
	cmd := exec.Command(ComDockerCli, dockerCommand, "--help")
	b, e := cmd.CombinedOutput()
	if e != nil {
		fmt.Println(e)
	}
	output := string(b)
	contains := strings.Contains(output, "Usage:\tdocker "+dockerCommand)
	return contains
}

// ExecSilent executes a command and do redirect output to stdOut, return output
func ExecSilent(ctx context.Context) ([]byte, error) {
	cmd := exec.CommandContext(ctx, ComDockerCli, os.Args[1:]...)
	return cmd.CombinedOutput()
}
<commit_msg>Forward all signals to child process<commit_after>/*
   Copyright 2020 Docker, Inc.

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

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

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

package mobycli

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strings"

	apicontext "github.com/docker/api/context"
	"github.com/docker/api/context/store"
)

var delegatedContextTypes = []string{store.DefaultContextType}

// ComDockerCli name of the classic cli binary
const ComDockerCli = "com.docker.cli"

// ExecIfDefaultCtxType delegates to com.docker.cli if on moby or AWS context (until there is an AWS backend)
func ExecIfDefaultCtxType(ctx context.Context) {
	currentContext := apicontext.CurrentContext(ctx)

	s := store.ContextStore(ctx)

	currentCtx, err := s.Get(currentContext)
	// Only run original docker command if the current context is not ours.
	if err != nil || mustDelegateToMoby(currentCtx.Type()) {
		Exec()
	}
}

func mustDelegateToMoby(ctxType string) bool {
	for _, ctype := range delegatedContextTypes {
		if ctxType == ctype {
			return true
		}
	}
	return false
}

// Exec delegates to com.docker.cli if on moby context
func Exec() {
	cmd := exec.Command(ComDockerCli, os.Args[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	s := make(chan os.Signal)
	signal.Notify(s) // catch all signals
	go func() {
		for sig := range s {
			err := cmd.Process.Signal(sig)
			if err != nil {
				fmt.Printf("WARNING could not forward signal %s to %s : %s\n", sig.String(), ComDockerCli, err.Error())
			}
		}
	}()

	if err := cmd.Run(); err != nil {
		if exiterr, ok := err.(*exec.ExitError); ok {
			os.Exit(exiterr.ExitCode())
		}
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	os.Exit(0)
}

// IsDefaultContextCommand checks if the command exists in the classic cli (issues a shellout --help)
func IsDefaultContextCommand(dockerCommand string) bool {
	cmd := exec.Command(ComDockerCli, dockerCommand, "--help")
	b, e := cmd.CombinedOutput()
	if e != nil {
		fmt.Println(e)
	}
	output := string(b)
	contains := strings.Contains(output, "Usage:\tdocker "+dockerCommand)
	return contains
}

// ExecSilent executes a command and do redirect output to stdOut, return output
func ExecSilent(ctx context.Context) ([]byte, error) {
	cmd := exec.CommandContext(ctx, ComDockerCli, os.Args[1:]...)
	return cmd.CombinedOutput()
}
<|endoftext|>


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

package main

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

func loadSyms(t *testing.T) map[string]string {
	cmd := exec.Command("go", "tool", "nm", os.Args[0])
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go tool nm %v: %v\n%s", os.Args[0], err, string(out))
	}
	syms := make(map[string]string)
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		f := strings.Fields(scanner.Text())
		if len(f) < 3 {
			continue
		}
		syms[f[2]] = f[0]
	}
	if err := scanner.Err(); err != nil {
		t.Fatalf("error reading symbols: %v", err)
	}
	return syms
}

func runAddr2Line(t *testing.T, exepath, addr string) (funcname, path, lineno string) {
	cmd := exec.Command(exepath, os.Args[0])
	cmd.Stdin = strings.NewReader(addr)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go tool addr2line %v: %v\n%s", os.Args[0], err, string(out))
	}
	f := strings.Split(string(out), "\n")
	if len(f) < 3 && f[2] == "" {
		t.Fatal("addr2line output must have 2 lines")
	}
	funcname = f[0]
	pathAndLineNo := f[1]
	f = strings.Split(pathAndLineNo, ":")
	if runtime.GOOS == "windows" {
		switch len(f) {
		case 2:
			return funcname, f[0], f[1]
		case 3:
			return funcname, f[0] + ":" + f[1], f[2]
		default:
			t.Fatalf("no line number found in %q", pathAndLineNo)
		}
	}
	if len(f) != 2 {
		t.Fatalf("no line number found in %q", pathAndLineNo)
	}
	return funcname, f[0], f[1]
}

func TestAddr2Line(t *testing.T) {
	syms := loadSyms(t)

	tmpDir, err := ioutil.TempDir("", "TestAddr2Line")
	if err != nil {
		t.Fatal("TempDir failed: ", err)
	}
	defer os.RemoveAll(tmpDir)

	exepath := filepath.Join(tmpDir, "testaddr2line.exe")
	out, err := exec.Command("go", "build", "-o", exepath, "cmd/addr2line").CombinedOutput()
	if err != nil {
		t.Fatalf("go build -o %v cmd/addr2line: %v\n%s", exepath, err, string(out))
	}

	const symName = "cmd/addr2line.TestAddr2Line"
	funcName, srcPath, srcLineNo := runAddr2Line(t, exepath, syms[symName])
	if symName != funcName {
		t.Fatalf("expected function name %v; got %v", symName, funcName)
	}
	fi1, err := os.Stat("addr2line_test.go")
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	fi2, err := os.Stat(srcPath)
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	if !os.SameFile(fi1, fi2) {
		t.Fatalf("addr2line_test.go and %s are not same file", srcPath)
	}
	if srcLineNo != "70" {
		t.Fatalf("line number = %v; want 70", srcLineNo)
	}
}
<commit_msg>cmd/addr2line: skip broken TestAddr2Line on plan9 (fixes build)<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

func loadSyms(t *testing.T) map[string]string {
	cmd := exec.Command("go", "tool", "nm", os.Args[0])
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go tool nm %v: %v\n%s", os.Args[0], err, string(out))
	}
	syms := make(map[string]string)
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		f := strings.Fields(scanner.Text())
		if len(f) < 3 {
			continue
		}
		syms[f[2]] = f[0]
	}
	if err := scanner.Err(); err != nil {
		t.Fatalf("error reading symbols: %v", err)
	}
	return syms
}

func runAddr2Line(t *testing.T, exepath, addr string) (funcname, path, lineno string) {
	cmd := exec.Command(exepath, os.Args[0])
	cmd.Stdin = strings.NewReader(addr)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go tool addr2line %v: %v\n%s", os.Args[0], err, string(out))
	}
	f := strings.Split(string(out), "\n")
	if len(f) < 3 && f[2] == "" {
		t.Fatal("addr2line output must have 2 lines")
	}
	funcname = f[0]
	pathAndLineNo := f[1]
	f = strings.Split(pathAndLineNo, ":")
	if runtime.GOOS == "windows" {
		switch len(f) {
		case 2:
			return funcname, f[0], f[1]
		case 3:
			return funcname, f[0] + ":" + f[1], f[2]
		default:
			t.Fatalf("no line number found in %q", pathAndLineNo)
		}
	}
	if len(f) != 2 {
		t.Fatalf("no line number found in %q", pathAndLineNo)
	}
	return funcname, f[0], f[1]
}

func TestAddr2Line(t *testing.T) {
	if runtime.GOOS == "plan9" {
		t.Skip("skipping test; see http://golang.org/issue/7947")
	}
	syms := loadSyms(t)

	tmpDir, err := ioutil.TempDir("", "TestAddr2Line")
	if err != nil {
		t.Fatal("TempDir failed: ", err)
	}
	defer os.RemoveAll(tmpDir)

	exepath := filepath.Join(tmpDir, "testaddr2line.exe")
	out, err := exec.Command("go", "build", "-o", exepath, "cmd/addr2line").CombinedOutput()
	if err != nil {
		t.Fatalf("go build -o %v cmd/addr2line: %v\n%s", exepath, err, string(out))
	}

	const symName = "cmd/addr2line.TestAddr2Line"
	funcName, srcPath, srcLineNo := runAddr2Line(t, exepath, syms[symName])
	if symName != funcName {
		t.Fatalf("expected function name %v; got %v", symName, funcName)
	}
	fi1, err := os.Stat("addr2line_test.go")
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	fi2, err := os.Stat(srcPath)
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	if !os.SameFile(fi1, fi2) {
		t.Fatalf("addr2line_test.go and %s are not same file", srcPath)
	}
	if srcLineNo != "70" {
		t.Fatalf("line number = %v; want 70", srcLineNo)
	}
}
<|endoftext|>


<commit_before>// Taken from RakutenTech nozzle
// Thanks to them
package stats

import (
	"bytes"
	"encoding/json"
	"os"
	"sync"
	"testing"
)

func TestStatsInc(t *testing.T) {

	s := NewStats()

	loop := 20
	inc := 5

	var wg sync.WaitGroup
	wg.Add(loop)
	for i := 0; i < loop; i++ {
		go func() {
			defer wg.Done()
			for i := 0; i < inc; i++ {
				s.Inc(Consume)
			}
		}()
	}

	wg.Wait()

	expect := loop * inc
	if s.Consume != uint64(expect) {
		t.Fatalf("expect %d to be eq %d", s.Consume, expect)
	}
}

func TestStatsJson(t *testing.T) {
	s := NewStats()

	for i := 0; i < 100; i++ {
		s.Inc(Consume)
	}

	for i := 0; i < 50; i++ {
		s.Inc(PublishFail)
	}

	for i := 0; i < 50; i++ {
		s.Inc(Publish)
	}

	for i := 0; i < 100; i++ {
		s.Inc(SubInputBuffer)
	}

	for i := 0; i < 50; i++ {
		s.Dec(SubInputBuffer)
	}

	for i := 0; i < 100; i++ {
		s.Inc(Forwarded)
	}

	expect := `{
  "consume": 100,
  "consume_per_sec": 0,
  "consume_fail": 0,
  "consume_http_start_stop": 0,
  "consume_value_metric": 0,
  "consume_counter_event": 0,
  "consume_log_message": 0,
  "consume_error": 0,
  "consume_container_metric": 0,
  "consume_unknown": 0,
  "ignored": 0,
  "forwarded": 100,
  "publish": 50,
  "publish_per_sec": 0,
  "publish_fail": 50,
  "slow_consumer_alert": 0,
  "subinupt_buffer": 50,
  "delay": 0,
  "instance_id": 0
}`

	b, _ := s.Json()

	var buf bytes.Buffer
	json.Indent(&buf, b, "", "  ")
	if buf.String() != expect {
		t.Fatalf("expect %v to be eq %v", buf.String(), expect)
	}
}

func setEnv(k, v string) func() {
	prev := os.Getenv(k)
	os.Setenv(k, v)
	return func() {
		os.Setenv(k, prev)
	}
}

func TestNewStats(t *testing.T) {
	reset := setEnv(EnvCFInstanceIndex, "4")
	defer reset()

	stats := NewStats()
	if stats.InstanceID != 4 {
		t.Fatalf("expect %d to be eq 4", stats.InstanceID)
	}
}

func TestNewStats_nonNumber(t *testing.T) {
	reset := setEnv(EnvCFInstanceIndex, "ab")
	defer reset()

	stats := NewStats()
	if stats.InstanceID != defaultInstanceID {
		t.Fatalf("expect %d to be eq %d", stats.InstanceID, defaultInstanceID)
	}
}
<commit_msg>REmoving delay from test<commit_after>// Taken from RakutenTech nozzle
// Thanks to them
package stats

import (
	"bytes"
	"encoding/json"
	"os"
	"sync"
	"testing"
)

func TestStatsInc(t *testing.T) {

	s := NewStats()

	loop := 20
	inc := 5

	var wg sync.WaitGroup
	wg.Add(loop)
	for i := 0; i < loop; i++ {
		go func() {
			defer wg.Done()
			for i := 0; i < inc; i++ {
				s.Inc(Consume)
			}
		}()
	}

	wg.Wait()

	expect := loop * inc
	if s.Consume != uint64(expect) {
		t.Fatalf("expect %d to be eq %d", s.Consume, expect)
	}
}

func TestStatsJson(t *testing.T) {
	s := NewStats()

	for i := 0; i < 100; i++ {
		s.Inc(Consume)
	}

	for i := 0; i < 50; i++ {
		s.Inc(PublishFail)
	}

	for i := 0; i < 50; i++ {
		s.Inc(Publish)
	}

	for i := 0; i < 100; i++ {
		s.Inc(SubInputBuffer)
	}

	for i := 0; i < 50; i++ {
		s.Dec(SubInputBuffer)
	}

	for i := 0; i < 100; i++ {
		s.Inc(Forwarded)
	}

	expect := `{
  "consume": 100,
  "consume_per_sec": 0,
  "consume_fail": 0,
  "consume_http_start_stop": 0,
  "consume_value_metric": 0,
  "consume_counter_event": 0,
  "consume_log_message": 0,
  "consume_error": 0,
  "consume_container_metric": 0,
  "consume_unknown": 0,
  "ignored": 0,
  "forwarded": 100,
  "publish": 50,
  "publish_per_sec": 0,
  "publish_fail": 50,
  "slow_consumer_alert": 0,
  "subinupt_buffer": 50,
  "instance_id": 0
}`

	b, _ := s.Json()

	var buf bytes.Buffer
	json.Indent(&buf, b, "", "  ")
	if buf.String() != expect {
		t.Fatalf("expect %v to be eq %v", buf.String(), expect)
	}
}

func setEnv(k, v string) func() {
	prev := os.Getenv(k)
	os.Setenv(k, v)
	return func() {
		os.Setenv(k, prev)
	}
}

func TestNewStats(t *testing.T) {
	reset := setEnv(EnvCFInstanceIndex, "4")
	defer reset()

	stats := NewStats()
	if stats.InstanceID != 4 {
		t.Fatalf("expect %d to be eq 4", stats.InstanceID)
	}
}

func TestNewStats_nonNumber(t *testing.T) {
	reset := setEnv(EnvCFInstanceIndex, "ab")
	defer reset()

	stats := NewStats()
	if stats.InstanceID != defaultInstanceID {
		t.Fatalf("expect %d to be eq %d", stats.InstanceID, defaultInstanceID)
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package bootstrap

import (
	"blueprint"
	"bufio"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"syscall"
)

const logFileName = ".ninja_log"

// removeAbandonedFiles removes any files that appear in the Ninja log that are
// not currently build targets.
func removeAbandonedFiles(ctx *blueprint.Context, config interface{},
	srcDir, manifestFile string) error {

	buildDir := "."
	if generatingBootstrapper(config) {
		buildDir = bootstrapDir
	}

	targetRules, err := ctx.AllTargets()
	if err != nil {
		return fmt.Errorf("error determining target list: %s", err)
	}

	replacer := strings.NewReplacer(
		"@@SrcDir@@", srcDir,
		"@@BootstrapManifest@@", manifestFile)
	targets := make(map[string]bool)
	for target := range targetRules {
		replacedTarget := replacer.Replace(target)
		targets[replacedTarget] = true
	}

	filePaths, err := parseNinjaLog(buildDir)
	if err != nil {
		return err
	}

	for _, filePath := range filePaths {
		isTarget := targets[filePath]
		if !isTarget {
			err = removeFileAndEmptyDirs(filePath)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func parseNinjaLog(buildDir string) ([]string, error) {
	logFilePath := filepath.Join(buildDir, logFileName)
	logFile, err := os.Open(logFilePath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer logFile.Close()

	scanner := bufio.NewScanner(logFile)

	// Check that the first line indicates that this is a Ninja log version 5
	const expectedFirstLine = "# ninja log v5"
	if !scanner.Scan() || scanner.Text() != expectedFirstLine {
		return nil, errors.New("unrecognized ninja log format")
	}

	var filePaths []string
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "#") {
			continue
		}

		const fieldSeperator = "\t"
		fields := strings.Split(line, fieldSeperator)

		const precedingFields = 3
		const followingFields = 1

		if len(fields) < precedingFields+followingFields+1 {
			return nil, fmt.Errorf("log entry has too few fields: %q", line)
		}

		start := precedingFields
		end := len(fields) - followingFields
		filePath := strings.Join(fields[start:end], fieldSeperator)

		filePaths = append(filePaths, filePath)
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return filePaths, nil
}

func removeFileAndEmptyDirs(path string) error {
	err := os.Remove(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}

	path, err = filepath.Abs(path)
	if err != nil {
		return err
	}

	cwd, err := os.Getwd()
	if err != nil {
		return err
	}

	for dir := filepath.Dir(path); dir != cwd; dir = filepath.Dir(dir) {
		err = os.Remove(dir)
		if err != nil {
			pathErr := err.(*os.PathError)
			switch pathErr.Err {
			case syscall.ENOTEMPTY, syscall.EEXIST:
				// We've come to a nonempty directory, so we're done.
				return nil
			default:
				return err
			}
		}
	}

	return nil
}
<commit_msg>Print names of files deleted by cleanup phase<commit_after>// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package bootstrap

import (
	"blueprint"
	"bufio"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"syscall"
)

const logFileName = ".ninja_log"

// removeAbandonedFiles removes any files that appear in the Ninja log that are
// not currently build targets.
func removeAbandonedFiles(ctx *blueprint.Context, config interface{},
	srcDir, manifestFile string) error {

	buildDir := "."
	if generatingBootstrapper(config) {
		buildDir = bootstrapDir
	}

	targetRules, err := ctx.AllTargets()
	if err != nil {
		return fmt.Errorf("error determining target list: %s", err)
	}

	replacer := strings.NewReplacer(
		"@@SrcDir@@", srcDir,
		"@@BootstrapManifest@@", manifestFile)
	targets := make(map[string]bool)
	for target := range targetRules {
		replacedTarget := replacer.Replace(target)
		targets[replacedTarget] = true
	}

	filePaths, err := parseNinjaLog(buildDir)
	if err != nil {
		return err
	}

	for _, filePath := range filePaths {
		isTarget := targets[filePath]
		if !isTarget {
			err = removeFileAndEmptyDirs(filePath)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func parseNinjaLog(buildDir string) ([]string, error) {
	logFilePath := filepath.Join(buildDir, logFileName)
	logFile, err := os.Open(logFilePath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer logFile.Close()

	scanner := bufio.NewScanner(logFile)

	// Check that the first line indicates that this is a Ninja log version 5
	const expectedFirstLine = "# ninja log v5"
	if !scanner.Scan() || scanner.Text() != expectedFirstLine {
		return nil, errors.New("unrecognized ninja log format")
	}

	var filePaths []string
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "#") {
			continue
		}

		const fieldSeperator = "\t"
		fields := strings.Split(line, fieldSeperator)

		const precedingFields = 3
		const followingFields = 1

		if len(fields) < precedingFields+followingFields+1 {
			return nil, fmt.Errorf("log entry has too few fields: %q", line)
		}

		start := precedingFields
		end := len(fields) - followingFields
		filePath := strings.Join(fields[start:end], fieldSeperator)

		filePaths = append(filePaths, filePath)
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return filePaths, nil
}

func removeFileAndEmptyDirs(path string) error {
	err := os.Remove(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}
	fmt.Printf("removed old ninja-created file %s because it has no rule to generate it\n", path)

	path, err = filepath.Abs(path)
	if err != nil {
		return err
	}

	cwd, err := os.Getwd()
	if err != nil {
		return err
	}

	for dir := filepath.Dir(path); dir != cwd; dir = filepath.Dir(dir) {
		err = os.Remove(dir)
		if err != nil {
			pathErr := err.(*os.PathError)
			switch pathErr.Err {
			case syscall.ENOTEMPTY, syscall.EEXIST:
				// We've come to a nonempty directory, so we're done.
				return nil
			default:
				return err
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package gcs

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"regexp"
	"sort"
	"strings"
	"sync"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iterator"
	"vbom.ml/util/sortorder"

	"k8s.io/test-infra/testgrid/metadata"
	"k8s.io/test-infra/testgrid/metadata/junit"
)

// Started holds started.json data.
type Started = metadata.Started

// Finished holds finished.json data.
type Finished struct {
	metadata.Finished
	// Running when the job hasn't finished and finished.json doesn't exist
	Running bool // TODO(fejta): remove?
}

// Build points to a build stored under a particular gcs prefix.
type Build struct {
	Bucket     *storage.BucketHandle
	Context    context.Context
	Prefix     string
	BucketPath string
}

func (build Build) String() string {
	return "gs://" + build.BucketPath + "/" + build.Prefix
}

// Builds is a slice of builds.
type Builds []Build

func (b Builds) Len() int      { return len(b) }
func (b Builds) Swap(i, j int) { b[i], b[j] = b[j], b[i] }

// Expect builds to be in monotonically increasing order.
// So build8 < build9 < build10 < build888
func (b Builds) Less(i, j int) bool {
	return sortorder.NaturalLess(b[i].Prefix, b[j].Prefix)
}

// ListBuilds returns the array of builds under path, sorted in monotonically decreasing order.
func ListBuilds(ctx context.Context, client *storage.Client, path Path) (Builds, error) {
	p := path.Object()
	if !strings.HasSuffix(p, "/") {
		p += "/"
	}
	bkt := client.Bucket(path.Bucket())
	it := bkt.Objects(ctx, &storage.Query{
		Delimiter: "/",
		Prefix:    p,
	})
	var all Builds
	for {
		objAttrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("failed to list objects: %v", err)
		}

		// if this is a link under directory, resolve the build value
		if link := objAttrs.Metadata["link"]; len(link) > 0 {
			// links created by bootstrap.py have a space
			link = strings.TrimSpace(link)
			u, err := url.Parse(link)
			if err != nil {
				return nil, fmt.Errorf("could not parse link for key %s: %v", objAttrs.Name, err)
			}
			if !strings.HasSuffix(u.Path, "/") {
				u.Path += "/"
			}
			var linkPath Path
			if err := linkPath.SetURL(u); err != nil {
				return nil, fmt.Errorf("could not make GCS path for key %s: %v", objAttrs.Name, err)
			}
			all = append(all, Build{
				Bucket:     bkt,
				Context:    ctx,
				Prefix:     linkPath.Object(),
				BucketPath: path.Bucket(),
			})
			continue
		}

		if len(objAttrs.Prefix) == 0 {
			continue
		}

		all = append(all, Build{
			Bucket:     bkt,
			Context:    ctx,
			Prefix:     objAttrs.Prefix,
			BucketPath: path.Bucket(),
		})
	}
	sort.Sort(sort.Reverse(all))
	return all, nil
}

// junit_CONTEXT_TIMESTAMP_THREAD.xml
var re = regexp.MustCompile(`.+/junit(_[^_]+)?(_\d+-\d+)?(_\d+)?\.xml$`)

// dropPrefix removes the _ in _CONTEXT to help keep the regexp simple
func dropPrefix(name string) string {
	if len(name) == 0 {
		return name
	}
	return name[1:]
}

// parseSuitesMeta returns the metadata for this junit file (nil for a non-junit file).
//
// Expected format: junit_context_20180102-1256-07.xml
// Results in {
//   "Context": "context",
//   "Timestamp": "20180102-1256",
//   "Thread": "07",
// }
func parseSuitesMeta(name string) map[string]string {
	mat := re.FindStringSubmatch(name)
	if mat == nil {
		return nil
	}
	return map[string]string{
		"Context":   dropPrefix(mat[1]),
		"Timestamp": dropPrefix(mat[2]),
		"Thread":    dropPrefix(mat[3]),
	}

}

// readJSON will decode the json object stored in GCS.
func readJSON(ctx context.Context, obj *storage.ObjectHandle, i interface{}) error {
	reader, err := obj.NewReader(ctx)
	if err == storage.ErrObjectNotExist {
		return err
	}
	if err != nil {
		return fmt.Errorf("open: %v", err)
	}
	if err = json.NewDecoder(reader).Decode(i); err != nil {
		return fmt.Errorf("decode: %v", err)
	}
	return nil
}

// Started parses the build's started metadata.
func (build Build) Started() (*Started, error) {
	uri := build.Prefix + "started.json"
	var started Started
	if err := readJSON(build.Context, build.Bucket.Object(uri), &started); err != nil {
		return nil, fmt.Errorf("read %s: %v", uri, err)
	}
	return &started, nil
}

// Finished parses the build's finished metadata.
func (build Build) Finished() (*Finished, error) {
	uri := build.Prefix + "finished.json"
	var finished Finished
	err := readJSON(build.Context, build.Bucket.Object(uri), &finished)
	if err == storage.ErrObjectNotExist {
		finished.Running = true
		return &finished, nil
	}
	if err != nil {
		return nil, fmt.Errorf("read %s: %v", uri, err)
	}
	return &finished, nil
}

// Artifacts writes the object name of all paths under the build's artifact dir to the output channel.
func (build Build) Artifacts(artifacts chan<- string) error {
	pref := build.Prefix
	objs := build.Bucket.Objects(build.Context, &storage.Query{Prefix: pref})
	for {
		obj, err := objs.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return fmt.Errorf("failed to list %s: %v", pref, err)
		}
		select {
		case <-build.Context.Done():
			return fmt.Errorf("interrupted listing %s", pref)
		case artifacts <- obj.Name:
		}
	}
	return nil
}

// readSuites parses the <testsuite> or <testsuites> object in obj
func readSuites(ctx context.Context, obj *storage.ObjectHandle) (*junit.Suites, error) {
	reader, err := obj.NewReader(ctx)
	if err != nil {
		return nil, fmt.Errorf("open: %v", err)
	}

	buf, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, fmt.Errorf("read: %v", err)
	}

	suites, err := junit.Parse(buf)
	if err != nil {
		return nil, fmt.Errorf("parse: %v", err)
	}
	return &suites, nil
}

// SuitesMeta holds testsuites xml and metadata from the filename
type SuitesMeta struct {
	Suites   junit.Suites      // suites data extracted from file contents
	Metadata map[string]string // metadata extracted from path name
	Path     string
}

// Suites takes a channel of artifact names, parses those representing junit suites, writing the result to the suites channel.
//
// Note that junit suites are parsed in parallel, so there are no guarantees about suites ordering.
func (build Build) Suites(artifacts <-chan string, suites chan<- SuitesMeta) error {

	var wg sync.WaitGroup
	ec := make(chan error)
	ctx, cancel := context.WithCancel(build.Context)
	defer cancel()
	for art := range artifacts {
		meta := parseSuitesMeta(art)
		if meta == nil {
			continue // not a junit file ignore it, ignore it
		}
		wg.Add(1)
		// concurrently parse each file because there may be a lot of them, and
		// each takes a non-trivial amount of time waiting for the network.
		go func(art string, meta map[string]string) {
			defer wg.Done()
			suitesData, err := readSuites(ctx, build.Bucket.Object(art))
			if err != nil {
				select {
				case <-ctx.Done():
				case ec <- err:
				}
				return
			}
			out := SuitesMeta{
				Suites:   *suitesData,
				Metadata: meta,
				Path:     "gs://" + build.BucketPath + "/" + art,
			}
			select {
			case <-ctx.Done():
			case suites <- out:
			}
		}(art, meta)
	}

	go func() {
		wg.Wait()
		select {
		case ec <- nil: // tell parent we exited cleanly
		case <-ctx.Done(): // parent already exited
		}
		close(ec) // no one will send t
	}()

	select {
	case <-ctx.Done(): // parent context marked as finished.
		return ctx.Err()
	case err := <-ec: // finished listing
		return err
	}
}
<commit_msg>Wait for builds.Suites() go-routines to finish before returning<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package gcs

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"regexp"
	"sort"
	"strings"
	"sync"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iterator"
	"vbom.ml/util/sortorder"

	"k8s.io/test-infra/testgrid/metadata"
	"k8s.io/test-infra/testgrid/metadata/junit"
)

// Started holds started.json data.
type Started = metadata.Started

// Finished holds finished.json data.
type Finished struct {
	metadata.Finished
	// Running when the job hasn't finished and finished.json doesn't exist
	Running bool
}

// Build points to a build stored under a particular gcs prefix.
type Build struct {
	Bucket     *storage.BucketHandle
	Context    context.Context
	Prefix     string
	BucketPath string
}

func (build Build) String() string {
	return "gs://" + build.BucketPath + "/" + build.Prefix
}

// Builds is a slice of builds.
type Builds []Build

func (b Builds) Len() int      { return len(b) }
func (b Builds) Swap(i, j int) { b[i], b[j] = b[j], b[i] }

// Expect builds to be in monotonically increasing order.
// So build8 < build9 < build10 < build888
func (b Builds) Less(i, j int) bool {
	return sortorder.NaturalLess(b[i].Prefix, b[j].Prefix)
}

// ListBuilds returns the array of builds under path, sorted in monotonically decreasing order.
func ListBuilds(ctx context.Context, client *storage.Client, path Path) (Builds, error) {
	p := path.Object()
	if !strings.HasSuffix(p, "/") {
		p += "/"
	}
	bkt := client.Bucket(path.Bucket())
	it := bkt.Objects(ctx, &storage.Query{
		Delimiter: "/",
		Prefix:    p,
	})
	var all Builds
	for {
		objAttrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("failed to list objects: %v", err)
		}

		// if this is a link under directory, resolve the build value
		if link := objAttrs.Metadata["link"]; len(link) > 0 {
			// links created by bootstrap.py have a space
			link = strings.TrimSpace(link)
			u, err := url.Parse(link)
			if err != nil {
				return nil, fmt.Errorf("could not parse link for key %s: %v", objAttrs.Name, err)
			}
			if !strings.HasSuffix(u.Path, "/") {
				u.Path += "/"
			}
			var linkPath Path
			if err := linkPath.SetURL(u); err != nil {
				return nil, fmt.Errorf("could not make GCS path for key %s: %v", objAttrs.Name, err)
			}
			all = append(all, Build{
				Bucket:     bkt,
				Context:    ctx,
				Prefix:     linkPath.Object(),
				BucketPath: path.Bucket(),
			})
			continue
		}

		if len(objAttrs.Prefix) == 0 {
			continue
		}

		all = append(all, Build{
			Bucket:     bkt,
			Context:    ctx,
			Prefix:     objAttrs.Prefix,
			BucketPath: path.Bucket(),
		})
	}
	sort.Sort(sort.Reverse(all))
	return all, nil
}

// junit_CONTEXT_TIMESTAMP_THREAD.xml
var re = regexp.MustCompile(`.+/junit(_[^_]+)?(_\d+-\d+)?(_\d+)?\.xml$`)

// dropPrefix removes the _ in _CONTEXT to help keep the regexp simple
func dropPrefix(name string) string {
	if len(name) == 0 {
		return name
	}
	return name[1:]
}

// parseSuitesMeta returns the metadata for this junit file (nil for a non-junit file).
//
// Expected format: junit_context_20180102-1256-07.xml
// Results in {
//   "Context": "context",
//   "Timestamp": "20180102-1256",
//   "Thread": "07",
// }
func parseSuitesMeta(name string) map[string]string {
	mat := re.FindStringSubmatch(name)
	if mat == nil {
		return nil
	}
	return map[string]string{
		"Context":   dropPrefix(mat[1]),
		"Timestamp": dropPrefix(mat[2]),
		"Thread":    dropPrefix(mat[3]),
	}

}

// readJSON will decode the json object stored in GCS.
func readJSON(ctx context.Context, obj *storage.ObjectHandle, i interface{}) error {
	reader, err := obj.NewReader(ctx)
	if err == storage.ErrObjectNotExist {
		return err
	}
	if err != nil {
		return fmt.Errorf("open: %v", err)
	}
	if err = json.NewDecoder(reader).Decode(i); err != nil {
		return fmt.Errorf("decode: %v", err)
	}
	return nil
}

// Started parses the build's started metadata.
func (build Build) Started() (*Started, error) {
	uri := build.Prefix + "started.json"
	var started Started
	if err := readJSON(build.Context, build.Bucket.Object(uri), &started); err != nil {
		return nil, fmt.Errorf("read %s: %v", uri, err)
	}
	return &started, nil
}

// Finished parses the build's finished metadata.
func (build Build) Finished() (*Finished, error) {
	uri := build.Prefix + "finished.json"
	var finished Finished
	err := readJSON(build.Context, build.Bucket.Object(uri), &finished)
	if err == storage.ErrObjectNotExist {
		finished.Running = true
		return &finished, nil
	}
	if err != nil {
		return nil, fmt.Errorf("read %s: %v", uri, err)
	}
	return &finished, nil
}

// Artifacts writes the object name of all paths under the build's artifact dir to the output channel.
func (build Build) Artifacts(artifacts chan<- string) error {
	pref := build.Prefix
	objs := build.Bucket.Objects(build.Context, &storage.Query{Prefix: pref})
	for {
		obj, err := objs.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return fmt.Errorf("failed to list %s: %v", pref, err)
		}
		select {
		case <-build.Context.Done():
			return fmt.Errorf("interrupted listing %s", pref)
		case artifacts <- obj.Name:
		}
	}
	return nil
}

// readSuites parses the <testsuite> or <testsuites> object in obj
func readSuites(ctx context.Context, obj *storage.ObjectHandle) (*junit.Suites, error) {
	reader, err := obj.NewReader(ctx)
	if err != nil {
		return nil, fmt.Errorf("open: %v", err)
	}

	buf, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, fmt.Errorf("read: %v", err)
	}

	suites, err := junit.Parse(buf)
	if err != nil {
		return nil, fmt.Errorf("parse: %v", err)
	}
	return &suites, nil
}

// SuitesMeta holds testsuites xml and metadata from the filename
type SuitesMeta struct {
	Suites   junit.Suites      // suites data extracted from file contents
	Metadata map[string]string // metadata extracted from path name
	Path     string
}

// Suites takes a channel of artifact names, parses those representing junit suites, writing the result to the suites channel.
//
// Note that junit suites are parsed in parallel, so there are no guarantees about suites ordering.
func (build Build) Suites(artifacts <-chan string, suites chan<- SuitesMeta) error {

	var wg sync.WaitGroup
	ec := make(chan error)
	ctx, cancel := context.WithCancel(build.Context)
	defer cancel()
	for art := range artifacts {
		meta := parseSuitesMeta(art)
		if meta == nil {
			continue // not a junit file ignore it, ignore it
		}
		wg.Add(1)
		// concurrently parse each file because there may be a lot of them, and
		// each takes a non-trivial amount of time waiting for the network.
		go func(art string, meta map[string]string) {
			defer wg.Done()
			suitesData, err := readSuites(ctx, build.Bucket.Object(art))
			if err != nil {
				select {
				case <-ctx.Done():
				case ec <- err:
				}
				return
			}
			out := SuitesMeta{
				Suites:   *suitesData,
				Metadata: meta,
				Path:     "gs://" + build.BucketPath + "/" + art,
			}
			select {
			case <-ctx.Done():
			case suites <- out:
			}
		}(art, meta)
	}

	go func() {
		wg.Wait()
		select {
		case ec <- nil: // tell parent we exited cleanly
		case <-ctx.Done(): // parent already exited
		}
		close(ec) // no one will send t
	}()

	// TODO(fejta): refactor to return the suites chan, so we can control channel closure
	// Until then don't return until all go functions return
	select {
	case <-ctx.Done(): // parent context marked as finished.
		wg.Wait()
		return ctx.Err()
	case err := <-ec: // finished listing
		cancel()
		wg.Wait()
		return err
	}
}
<|endoftext|>


<commit_before>package i18n

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strings"
	"sync"

	"github.com/getlantern/golog"
	"github.com/getlantern/jibber_jabber"
)

// ReadFunc is the func provided to SetMessagesFunc which returns the byte
// sequence given a file name
type ReadFunc func(fileName string) ([]byte, error)

var (
	localeRegexp  string   = "^[a-z]{2}([_-][A-Z]{2}){0,1}$"
	log                    = golog.LoggerFor("i18n")
	readFunc      ReadFunc = makeReadFunc("locale")
	defaultLocale string   = "en_US"
	defaultLang   string   = "en"
	trMutex       sync.RWMutex
	// read from a nil map is ok, so leave it uninitialized here
	trMap map[string]string
)

// T translates the given key into a message based on the current locale,
// formatting the string using the supplied (optional) args. This method will
// fall back to other locales if the key isn't defined for the current locale.
// The search order (with examples) is as follows:
//
//   1. current locale        (zh_CN)
//   2. lang only             (zh)
//   3. default locale        (en_US)
//   4. lang only of default  (en)
//
func T(key string, args ...interface{}) string {
	trMutex.RLock()
	defer trMutex.RUnlock()
	s := trMap[key]
	// Format string
	if s != "" && len(args) > 0 {
		s = fmt.Sprintf(s, args...)
	}

	return s
}

// SetMessagesDir sets the directory from which to load translations
// if they are not under the default directory 'locale'
func SetMessagesDir(d string) {
	readFunc = makeReadFunc(d)
}

func makeReadFunc(d string) ReadFunc {
	return func(p string) (buf []byte, err error) {
		fileName := path.Join(d, p)
		var f *os.File
		if f, err = os.Open(fileName); err != nil {
			err = fmt.Errorf("Error open file %s: %s", fileName, err)
			return
		}
		defer f.Close()
		if buf, err = ioutil.ReadAll(f); err != nil {
			err = fmt.Errorf("Error read file %s: %s", fileName, err)
		}
		return
	}
}

// SetMessagesFunc tells i18n to read translations through ReadFunc
func SetMessagesFunc(f ReadFunc) {
	readFunc = f
}

// UseOSLocale detect OS locale for current user and let i18n to use it
func UseOSLocale() error {
	userLocale, err := jibber_jabber.DetectIETF()
	if err != nil || userLocale == "C" {
		userLocale = defaultLocale
	}
	log.Tracef("Using OS locale of current user: %v", userLocale)
	return SetLocale(userLocale)
}

// SetLocale sets the current locale to the given value. If the locale is not in
// a valid format, this function will return an error and leave the current
// locale as is.
func SetLocale(locale string) error {
	if matched, _ := regexp.MatchString(localeRegexp, locale); !matched {
		return fmt.Errorf("Malformated locale string %s", locale)
	}
	locale = strings.Replace(locale, "-", "_", -1)
	parts := strings.Split(locale, "_")
	lang := parts[0]
	log.Debugf("Setting locale %v", locale)
	newTrMap := make(map[string]string)
	mergeLocaleToMap(newTrMap, defaultLang)
	mergeLocaleToMap(newTrMap, defaultLocale)
	mergeLocaleToMap(newTrMap, lang)
	mergeLocaleToMap(newTrMap, locale)
	if len(newTrMap) == 0 {
		return fmt.Errorf("Not found any translations, locale not set")
	}
	log.Debugf("Translations: %v", newTrMap)
	trMutex.Lock()
	defer trMutex.Unlock()
	trMap = newTrMap
	return nil
}

func mergeLocaleToMap(dst map[string]string, locale string) {
	if m, e := loadMapFromFile(locale); e != nil {
		log.Tracef("Locale %s not loaded: %s", locale, e)
	} else {
		for k, v := range m {
			dst[k] = v
		}
	}
}

func loadMapFromFile(locale string) (m map[string]string, err error) {
	fileName := locale + ".json"
	var buf []byte
	if buf, err = readFunc(fileName); err != nil {
		err = fmt.Errorf("Error read file %s: %s", fileName, err)
		return
	}
	if err = json.Unmarshal(buf, &m); err != nil {
		err = fmt.Errorf("Error decode json file %s: %s", fileName, err)
	}
	return
}
<commit_msg>Made printout of translations a trace level thing<commit_after>package i18n

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strings"
	"sync"

	"github.com/getlantern/golog"
	"github.com/getlantern/jibber_jabber"
)

// ReadFunc is the func provided to SetMessagesFunc which returns the byte
// sequence given a file name
type ReadFunc func(fileName string) ([]byte, error)

var (
	localeRegexp  string   = "^[a-z]{2}([_-][A-Z]{2}){0,1}$"
	log                    = golog.LoggerFor("i18n")
	readFunc      ReadFunc = makeReadFunc("locale")
	defaultLocale string   = "en_US"
	defaultLang   string   = "en"
	trMutex       sync.RWMutex
	// read from a nil map is ok, so leave it uninitialized here
	trMap map[string]string
)

// T translates the given key into a message based on the current locale,
// formatting the string using the supplied (optional) args. This method will
// fall back to other locales if the key isn't defined for the current locale.
// The search order (with examples) is as follows:
//
//   1. current locale        (zh_CN)
//   2. lang only             (zh)
//   3. default locale        (en_US)
//   4. lang only of default  (en)
//
func T(key string, args ...interface{}) string {
	trMutex.RLock()
	defer trMutex.RUnlock()
	s := trMap[key]
	// Format string
	if s != "" && len(args) > 0 {
		s = fmt.Sprintf(s, args...)
	}

	return s
}

// SetMessagesDir sets the directory from which to load translations
// if they are not under the default directory 'locale'
func SetMessagesDir(d string) {
	readFunc = makeReadFunc(d)
}

func makeReadFunc(d string) ReadFunc {
	return func(p string) (buf []byte, err error) {
		fileName := path.Join(d, p)
		var f *os.File
		if f, err = os.Open(fileName); err != nil {
			err = fmt.Errorf("Error open file %s: %s", fileName, err)
			return
		}
		defer f.Close()
		if buf, err = ioutil.ReadAll(f); err != nil {
			err = fmt.Errorf("Error read file %s: %s", fileName, err)
		}
		return
	}
}

// SetMessagesFunc tells i18n to read translations through ReadFunc
func SetMessagesFunc(f ReadFunc) {
	readFunc = f
}

// UseOSLocale detect OS locale for current user and let i18n to use it
func UseOSLocale() error {
	userLocale, err := jibber_jabber.DetectIETF()
	if err != nil || userLocale == "C" {
		userLocale = defaultLocale
	}
	log.Tracef("Using OS locale of current user: %v", userLocale)
	return SetLocale(userLocale)
}

// SetLocale sets the current locale to the given value. If the locale is not in
// a valid format, this function will return an error and leave the current
// locale as is.
func SetLocale(locale string) error {
	if matched, _ := regexp.MatchString(localeRegexp, locale); !matched {
		return fmt.Errorf("Malformated locale string %s", locale)
	}
	locale = strings.Replace(locale, "-", "_", -1)
	parts := strings.Split(locale, "_")
	lang := parts[0]
	log.Debugf("Setting locale %v", locale)
	newTrMap := make(map[string]string)
	mergeLocaleToMap(newTrMap, defaultLang)
	mergeLocaleToMap(newTrMap, defaultLocale)
	mergeLocaleToMap(newTrMap, lang)
	mergeLocaleToMap(newTrMap, locale)
	if len(newTrMap) == 0 {
		return fmt.Errorf("Not found any translations, locale not set")
	}
	log.Tracef("Translations: %v", newTrMap)
	trMutex.Lock()
	defer trMutex.Unlock()
	trMap = newTrMap
	return nil
}

func mergeLocaleToMap(dst map[string]string, locale string) {
	if m, e := loadMapFromFile(locale); e != nil {
		log.Tracef("Locale %s not loaded: %s", locale, e)
	} else {
		for k, v := range m {
			dst[k] = v
		}
	}
}

func loadMapFromFile(locale string) (m map[string]string, err error) {
	fileName := locale + ".json"
	var buf []byte
	if buf, err = readFunc(fileName); err != nil {
		err = fmt.Errorf("Error read file %s: %s", fileName, err)
		return
	}
	if err = json.Unmarshal(buf, &m); err != nil {
		err = fmt.Errorf("Error decode json file %s: %s", fileName, err)
	}
	return
}
<|endoftext|>


<commit_before>package defaults

import (
	"os"

	"github.com/doubledutch/lager"
	"github.com/doubledutch/quantum"
	"github.com/doubledutch/quantum/consul"
	"github.com/doubledutch/quantum/inmemory"
)

// NewClientResolver creates a default client resolver
func NewClientResolver(config quantum.ClientResolverConfig) quantum.ClientResolver {
	return consul.NewClientResolver(config)
}

// NewRegistry returns a default registry
func NewRegistry() quantum.Registry {
	return inmemory.NewRegistry(NewLager())
}

// NewLager returns a default Lager
func NewLager() lager.Lager {
	return lager.NewLogLager(&lager.LogConfig{
		Levels: lager.LevelsFromString("IE"),
		Output: os.Stdout,
	})
}
<commit_msg>remove default resolver<commit_after>package defaults

import (
	"os"

	"github.com/doubledutch/lager"
	"github.com/doubledutch/quantum"
	"github.com/doubledutch/quantum/inmemory"
)

// NewRegistry returns a default registry
func NewRegistry() quantum.Registry {
	return inmemory.NewRegistry(NewLager())
}

// NewLager returns a default Lager
func NewLager() lager.Lager {
	return lager.NewLogLager(&lager.LogConfig{
		Levels: lager.LevelsFromString("IE"),
		Output: os.Stdout,
	})
}
<|endoftext|>