<commit_before>package chain

import (
	"bytes"
	"fmt"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/rpcclient"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightninglabs/gozmq"
)

// BitcoindConn represents a persistent client connection to a bitcoind node
// that listens for events read from a ZMQ connection.
type BitcoindConn struct {
	started int32 // To be used atomically.
	stopped int32 // To be used atomically.

	// rescanClientCounter is an atomic counter that assigns a unique ID to
	// each new bitcoind rescan client using the current bitcoind
	// connection.
	rescanClientCounter uint64

	// chainParams identifies the current network the bitcoind node is
	// running on.
	chainParams *chaincfg.Params

	// client is the RPC client to the bitcoind node.
	client *rpcclient.Client

	// zmqBlockHost is the host listening for ZMQ connections that will be
	// responsible for delivering raw transaction events.
	zmqBlockHost string

	// zmqTxHost is the host listening for ZMQ connections that will be
	// responsible for delivering raw transaction events.
	zmqTxHost string

	// zmqPollInterval is the interval at which we'll attempt to retrieve an
	// event from the ZMQ connection.
	zmqPollInterval time.Duration

	// rescanClients is the set of active bitcoind rescan clients to which
	// ZMQ event notfications will be sent to.
	rescanClientsMtx sync.Mutex
	rescanClients    map[uint64]*BitcoindClient

	quit chan struct{}
	wg   sync.WaitGroup
}

// NewBitcoindConn creates a client connection to the node described by the host
// string. The connection is not established immediately, but must be done using
// the Start method. If the remote node does not operate on the same bitcoin
// network as described by the passed chain parameters, the connection will be
// disconnected.
func NewBitcoindConn(chainParams *chaincfg.Params,
	host, user, pass, zmqBlockHost, zmqTxHost string,
	zmqPollInterval time.Duration) (*BitcoindConn, error) {

	clientCfg := &rpcclient.ConnConfig{
		Host:                 host,
		User:                 user,
		Pass:                 pass,
		DisableAutoReconnect: false,
		DisableConnectOnNew:  true,
		DisableTLS:           true,
		HTTPPostMode:         true,
	}

	client, err := rpcclient.New(clientCfg, nil)
	if err != nil {
		return nil, err
	}

	conn := &BitcoindConn{
		chainParams:     chainParams,
		client:          client,
		zmqBlockHost:    zmqBlockHost,
		zmqTxHost:       zmqTxHost,
		zmqPollInterval: zmqPollInterval,
		rescanClients:   make(map[uint64]*BitcoindClient),
		quit:            make(chan struct{}),
	}

	return conn, nil
}

// Start attempts to establish a RPC and ZMQ connection to a bitcoind node. If
// successful, a goroutine is spawned to read events from the ZMQ connection.
// It's possible for this function to fail due to a limited number of connection
// attempts. This is done to prevent waiting forever on the connection to be
// established in the case that the node is down.
func (c *BitcoindConn) Start() error {
	if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
		return nil
	}

	// Verify that the node is running on the expected network.
	net, err := c.getCurrentNet()
	if err != nil {
		c.client.Disconnect()
		return err
	}
	if net != c.chainParams.Net {
		c.client.Disconnect()
		return fmt.Errorf("expected network %v, got %v",
			c.chainParams.Net, net)
	}

	// Establish two different ZMQ connections to bitcoind to retrieve block
	// and transaction event notifications. We'll use two as a separation of
	// concern to ensure one type of event isn't dropped from the connection
	// queue due to another type of event filling it up.
	zmqBlockConn, err := gozmq.Subscribe(
		c.zmqBlockHost, []string{"rawblock"}, c.zmqPollInterval,
	)
	if err != nil {
		c.client.Disconnect()
		return fmt.Errorf("unable to subscribe for zmq block events: "+
			"%v", err)
	}

	zmqTxConn, err := gozmq.Subscribe(
		c.zmqTxHost, []string{"rawtx"}, c.zmqPollInterval,
	)
	if err != nil {
		c.client.Disconnect()
		return fmt.Errorf("unable to subscribe for zmq tx events: %v",
			err)
	}

	c.wg.Add(2)
	go c.blockEventHandler(zmqBlockConn)
	go c.txEventHandler(zmqTxConn)

	return nil
}

// Stop terminates the RPC and ZMQ connection to a bitcoind node and removes any
// active rescan clients.
func (c *BitcoindConn) Stop() {
	if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
		return
	}

	for _, client := range c.rescanClients {
		client.Stop()
	}

	close(c.quit)
	c.client.Shutdown()

	c.client.WaitForShutdown()
	c.wg.Wait()
}

// blockEventHandler reads raw blocks events from the ZMQ block socket and
// forwards them along to the current rescan clients.
//
// NOTE: This must be run as a goroutine.
func (c *BitcoindConn) blockEventHandler(conn *gozmq.Conn) {
	defer c.wg.Done()
	defer conn.Close()

	log.Info("Started listening for bitcoind block notifications via ZMQ "+
		"on", c.zmqBlockHost)

	for {
		// Before attempting to read from the ZMQ socket, we'll make
		// sure to check if we've been requested to shut down.
		select {
		case <-c.quit:
			return
		default:
		}

		// Poll an event from the ZMQ socket.
		msgBytes, err := conn.Receive()
		if err != nil {
			// It's possible that the connection to the socket
			// continuously times out, so we'll prevent logging this
			// error to prevent spamming the logs.
			netErr, ok := err.(net.Error)
			if ok && netErr.Timeout() {
				continue
			}

			log.Errorf("Unable to receive ZMQ message: %v", err)
			continue
		}

		// We have an event! We'll now ensure it is a block event,
		// deserialize it, and report it to the different rescan
		// clients.
		eventType := string(msgBytes[0])
		switch eventType {
		case "rawblock":
			block := &wire.MsgBlock{}
			r := bytes.NewReader(msgBytes[1])
			if err := block.Deserialize(r); err != nil {
				log.Errorf("Unable to deserialize block: %v",
					err)
				continue
			}

			c.rescanClientsMtx.Lock()
			for _, client := range c.rescanClients {
				select {
				case client.zmqBlockNtfns <- block:
				case <-client.quit:
				case <-c.quit:
					c.rescanClientsMtx.Unlock()
					return
				}
			}
			c.rescanClientsMtx.Unlock()
		default:
			// It's possible that the message wasn't fully read if
			// bitcoind shuts down, which will produce an unreadable
			// event type. To prevent from logging it, we'll make
			// sure it conforms to the ASCII standard.
			if !isASCII(eventType) {
				continue
			}

			log.Warnf("Received unexpected event type from "+
				"rawblock subscription: %v", eventType)
		}
	}
}

// txEventHandler reads raw blocks events from the ZMQ block socket and forwards
// them along to the current rescan clients.
//
// NOTE: This must be run as a goroutine.
func (c *BitcoindConn) txEventHandler(conn *gozmq.Conn) {
	defer c.wg.Done()
	defer conn.Close()

	log.Info("Started listening for bitcoind transaction notifications "+
		"via ZMQ on", c.zmqTxHost)

	for {
		// Before attempting to read from the ZMQ socket, we'll make
		// sure to check if we've been requested to shut down.
		select {
		case <-c.quit:
			return
		default:
		}

		// Poll an event from the ZMQ socket.
		msgBytes, err := conn.Receive()
		if err != nil {
			// It's possible that the connection to the socket
			// continuously times out, so we'll prevent logging this
			// error to prevent spamming the logs.
			netErr, ok := err.(net.Error)
			if ok && netErr.Timeout() {
				continue
			}

			log.Errorf("Unable to receive ZMQ message: %v", err)
			continue
		}

		// We have an event! We'll now ensure it is a transaction event,
		// deserialize it, and report it to the different rescan
		// clients.
		eventType := string(msgBytes[0])
		switch eventType {
		case "rawtx":
			tx := &wire.MsgTx{}
			r := bytes.NewReader(msgBytes[1])
			if err := tx.Deserialize(r); err != nil {
				log.Errorf("Unable to deserialize "+
					"transaction: %v", err)
				continue
			}

			c.rescanClientsMtx.Lock()
			for _, client := range c.rescanClients {
				select {
				case client.zmqTxNtfns <- tx:
				case <-client.quit:
				case <-c.quit:
					c.rescanClientsMtx.Unlock()
					return
				}
			}
			c.rescanClientsMtx.Unlock()
		default:
			log.Warnf("Received unexpected event type from rawtx "+
				"subscription: %v", eventType)
		}
	}
}

// getCurrentNet returns the network on which the bitcoind node is running.
func (c *BitcoindConn) getCurrentNet() (wire.BitcoinNet, error) {
	hash, err := c.client.GetBlockHash(0)
	if err != nil {
		return 0, err
	}

	switch *hash {
	case *chaincfg.TestNet3Params.GenesisHash:
		return chaincfg.TestNet3Params.Net, nil
	case *chaincfg.RegressionNetParams.GenesisHash:
		return chaincfg.RegressionNetParams.Net, nil
	case *chaincfg.MainNetParams.GenesisHash:
		return chaincfg.MainNetParams.Net, nil
	default:
		return 0, fmt.Errorf("unknown network with genesis hash %v", hash)
	}
}

// NewBitcoindClient returns a bitcoind client using the current bitcoind
// connection. This allows us to share the same connection using multiple
// clients. The birthday signifies the earliest time for which we should begin
// scanning the chain.
func (c *BitcoindConn) NewBitcoindClient(birthday time.Time) *BitcoindClient {
	return &BitcoindClient{
		quit: make(chan struct{}),

		id: atomic.AddUint64(&c.rescanClientCounter, 1),

		birthday:    birthday,
		chainParams: c.chainParams,
		chainConn:   c,

		rescanUpdate:     make(chan interface{}),
		watchedAddresses: make(map[string]struct{}),
		watchedOutPoints: make(map[wire.OutPoint]struct{}),
		watchedTxs:       make(map[chainhash.Hash]struct{}),

		notificationQueue: NewConcurrentQueue(20),
		zmqTxNtfns:        make(chan *wire.MsgTx),
		zmqBlockNtfns:     make(chan *wire.MsgBlock),

		mempool:        make(map[chainhash.Hash]struct{}),
		expiredMempool: make(map[int32]map[chainhash.Hash]struct{}),
	}
}

// AddClient adds a client to the set of active rescan clients of the current
// chain connection. This allows the connection to include the specified client
// in its notification delivery.
//
// NOTE: This function is safe for concurrent access.
func (c *BitcoindConn) AddClient(client *BitcoindClient) {
	c.rescanClientsMtx.Lock()
	defer c.rescanClientsMtx.Unlock()

	c.rescanClients[client.id] = client
}

// RemoveClient removes the client with the given ID from the set of active
// rescan clients. Once removed, the client will no longer receive block and
// transaction notifications from the chain connection.
//
// NOTE: This function is safe for concurrent access.
func (c *BitcoindConn) RemoveClient(id uint64) {
	c.rescanClientsMtx.Lock()
	defer c.rescanClientsMtx.Unlock()

	delete(c.rescanClients, id)
}

// isASCII is a helper method that checks whether all bytes in `data` would be
// printable ASCII characters if interpreted as a string.
func isASCII(s string) bool {
	for _, c := range s {
		if c < 32 || c > 126 {
			return false
		}
	}
	return true
}
<commit_msg>chain/bitcoind_conn: only print ASCII rawtx event types<commit_after>package chain

import (
	"bytes"
	"fmt"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/rpcclient"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightninglabs/gozmq"
)

// BitcoindConn represents a persistent client connection to a bitcoind node
// that listens for events read from a ZMQ connection.
type BitcoindConn struct {
	started int32 // To be used atomically.
	stopped int32 // To be used atomically.

	// rescanClientCounter is an atomic counter that assigns a unique ID to
	// each new bitcoind rescan client using the current bitcoind
	// connection.
	rescanClientCounter uint64

	// chainParams identifies the current network the bitcoind node is
	// running on.
	chainParams *chaincfg.Params

	// client is the RPC client to the bitcoind node.
	client *rpcclient.Client

	// zmqBlockHost is the host listening for ZMQ connections that will be
	// responsible for delivering raw transaction events.
	zmqBlockHost string

	// zmqTxHost is the host listening for ZMQ connections that will be
	// responsible for delivering raw transaction events.
	zmqTxHost string

	// zmqPollInterval is the interval at which we'll attempt to retrieve an
	// event from the ZMQ connection.
	zmqPollInterval time.Duration

	// rescanClients is the set of active bitcoind rescan clients to which
	// ZMQ event notfications will be sent to.
	rescanClientsMtx sync.Mutex
	rescanClients    map[uint64]*BitcoindClient

	quit chan struct{}
	wg   sync.WaitGroup
}

// NewBitcoindConn creates a client connection to the node described by the host
// string. The connection is not established immediately, but must be done using
// the Start method. If the remote node does not operate on the same bitcoin
// network as described by the passed chain parameters, the connection will be
// disconnected.
func NewBitcoindConn(chainParams *chaincfg.Params,
	host, user, pass, zmqBlockHost, zmqTxHost string,
	zmqPollInterval time.Duration) (*BitcoindConn, error) {

	clientCfg := &rpcclient.ConnConfig{
		Host:                 host,
		User:                 user,
		Pass:                 pass,
		DisableAutoReconnect: false,
		DisableConnectOnNew:  true,
		DisableTLS:           true,
		HTTPPostMode:         true,
	}

	client, err := rpcclient.New(clientCfg, nil)
	if err != nil {
		return nil, err
	}

	conn := &BitcoindConn{
		chainParams:     chainParams,
		client:          client,
		zmqBlockHost:    zmqBlockHost,
		zmqTxHost:       zmqTxHost,
		zmqPollInterval: zmqPollInterval,
		rescanClients:   make(map[uint64]*BitcoindClient),
		quit:            make(chan struct{}),
	}

	return conn, nil
}

// Start attempts to establish a RPC and ZMQ connection to a bitcoind node. If
// successful, a goroutine is spawned to read events from the ZMQ connection.
// It's possible for this function to fail due to a limited number of connection
// attempts. This is done to prevent waiting forever on the connection to be
// established in the case that the node is down.
func (c *BitcoindConn) Start() error {
	if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
		return nil
	}

	// Verify that the node is running on the expected network.
	net, err := c.getCurrentNet()
	if err != nil {
		c.client.Disconnect()
		return err
	}
	if net != c.chainParams.Net {
		c.client.Disconnect()
		return fmt.Errorf("expected network %v, got %v",
			c.chainParams.Net, net)
	}

	// Establish two different ZMQ connections to bitcoind to retrieve block
	// and transaction event notifications. We'll use two as a separation of
	// concern to ensure one type of event isn't dropped from the connection
	// queue due to another type of event filling it up.
	zmqBlockConn, err := gozmq.Subscribe(
		c.zmqBlockHost, []string{"rawblock"}, c.zmqPollInterval,
	)
	if err != nil {
		c.client.Disconnect()
		return fmt.Errorf("unable to subscribe for zmq block events: "+
			"%v", err)
	}

	zmqTxConn, err := gozmq.Subscribe(
		c.zmqTxHost, []string{"rawtx"}, c.zmqPollInterval,
	)
	if err != nil {
		c.client.Disconnect()
		return fmt.Errorf("unable to subscribe for zmq tx events: %v",
			err)
	}

	c.wg.Add(2)
	go c.blockEventHandler(zmqBlockConn)
	go c.txEventHandler(zmqTxConn)

	return nil
}

// Stop terminates the RPC and ZMQ connection to a bitcoind node and removes any
// active rescan clients.
func (c *BitcoindConn) Stop() {
	if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
		return
	}

	for _, client := range c.rescanClients {
		client.Stop()
	}

	close(c.quit)
	c.client.Shutdown()

	c.client.WaitForShutdown()
	c.wg.Wait()
}

// blockEventHandler reads raw blocks events from the ZMQ block socket and
// forwards them along to the current rescan clients.
//
// NOTE: This must be run as a goroutine.
func (c *BitcoindConn) blockEventHandler(conn *gozmq.Conn) {
	defer c.wg.Done()
	defer conn.Close()

	log.Info("Started listening for bitcoind block notifications via ZMQ "+
		"on", c.zmqBlockHost)

	for {
		// Before attempting to read from the ZMQ socket, we'll make
		// sure to check if we've been requested to shut down.
		select {
		case <-c.quit:
			return
		default:
		}

		// Poll an event from the ZMQ socket.
		msgBytes, err := conn.Receive()
		if err != nil {
			// It's possible that the connection to the socket
			// continuously times out, so we'll prevent logging this
			// error to prevent spamming the logs.
			netErr, ok := err.(net.Error)
			if ok && netErr.Timeout() {
				continue
			}

			log.Errorf("Unable to receive ZMQ rawblock message: %v",
				err)
			continue
		}

		// We have an event! We'll now ensure it is a block event,
		// deserialize it, and report it to the different rescan
		// clients.
		eventType := string(msgBytes[0])
		switch eventType {
		case "rawblock":
			block := &wire.MsgBlock{}
			r := bytes.NewReader(msgBytes[1])
			if err := block.Deserialize(r); err != nil {
				log.Errorf("Unable to deserialize block: %v",
					err)
				continue
			}

			c.rescanClientsMtx.Lock()
			for _, client := range c.rescanClients {
				select {
				case client.zmqBlockNtfns <- block:
				case <-client.quit:
				case <-c.quit:
					c.rescanClientsMtx.Unlock()
					return
				}
			}
			c.rescanClientsMtx.Unlock()
		default:
			// It's possible that the message wasn't fully read if
			// bitcoind shuts down, which will produce an unreadable
			// event type. To prevent from logging it, we'll make
			// sure it conforms to the ASCII standard.
			if eventType == "" || !isASCII(eventType) {
				continue
			}

			log.Warnf("Received unexpected event type from "+
				"rawblock subscription: %v", eventType)
		}
	}
}

// txEventHandler reads raw blocks events from the ZMQ block socket and forwards
// them along to the current rescan clients.
//
// NOTE: This must be run as a goroutine.
func (c *BitcoindConn) txEventHandler(conn *gozmq.Conn) {
	defer c.wg.Done()
	defer conn.Close()

	log.Info("Started listening for bitcoind transaction notifications "+
		"via ZMQ on", c.zmqTxHost)

	for {
		// Before attempting to read from the ZMQ socket, we'll make
		// sure to check if we've been requested to shut down.
		select {
		case <-c.quit:
			return
		default:
		}

		// Poll an event from the ZMQ socket.
		msgBytes, err := conn.Receive()
		if err != nil {
			// It's possible that the connection to the socket
			// continuously times out, so we'll prevent logging this
			// error to prevent spamming the logs.
			netErr, ok := err.(net.Error)
			if ok && netErr.Timeout() {
				continue
			}

			log.Errorf("Unable to receive ZMQ rawtx message: %v",
				err)
			continue
		}

		// We have an event! We'll now ensure it is a transaction event,
		// deserialize it, and report it to the different rescan
		// clients.
		eventType := string(msgBytes[0])
		switch eventType {
		case "rawtx":
			tx := &wire.MsgTx{}
			r := bytes.NewReader(msgBytes[1])
			if err := tx.Deserialize(r); err != nil {
				log.Errorf("Unable to deserialize "+
					"transaction: %v", err)
				continue
			}

			c.rescanClientsMtx.Lock()
			for _, client := range c.rescanClients {
				select {
				case client.zmqTxNtfns <- tx:
				case <-client.quit:
				case <-c.quit:
					c.rescanClientsMtx.Unlock()
					return
				}
			}
			c.rescanClientsMtx.Unlock()
		default:
			// It's possible that the message wasn't fully read if
			// bitcoind shuts down, which will produce an unreadable
			// event type. To prevent from logging it, we'll make
			// sure it conforms to the ASCII standard.
			if eventType == "" || !isASCII(eventType) {
				continue
			}

			log.Warnf("Received unexpected event type from rawtx "+
				"subscription: %v", eventType)
		}
	}
}

// getCurrentNet returns the network on which the bitcoind node is running.
func (c *BitcoindConn) getCurrentNet() (wire.BitcoinNet, error) {
	hash, err := c.client.GetBlockHash(0)
	if err != nil {
		return 0, err
	}

	switch *hash {
	case *chaincfg.TestNet3Params.GenesisHash:
		return chaincfg.TestNet3Params.Net, nil
	case *chaincfg.RegressionNetParams.GenesisHash:
		return chaincfg.RegressionNetParams.Net, nil
	case *chaincfg.MainNetParams.GenesisHash:
		return chaincfg.MainNetParams.Net, nil
	default:
		return 0, fmt.Errorf("unknown network with genesis hash %v", hash)
	}
}

// NewBitcoindClient returns a bitcoind client using the current bitcoind
// connection. This allows us to share the same connection using multiple
// clients. The birthday signifies the earliest time for which we should begin
// scanning the chain.
func (c *BitcoindConn) NewBitcoindClient(birthday time.Time) *BitcoindClient {
	return &BitcoindClient{
		quit: make(chan struct{}),

		id: atomic.AddUint64(&c.rescanClientCounter, 1),

		birthday:    birthday,
		chainParams: c.chainParams,
		chainConn:   c,

		rescanUpdate:     make(chan interface{}),
		watchedAddresses: make(map[string]struct{}),
		watchedOutPoints: make(map[wire.OutPoint]struct{}),
		watchedTxs:       make(map[chainhash.Hash]struct{}),

		notificationQueue: NewConcurrentQueue(20),
		zmqTxNtfns:        make(chan *wire.MsgTx),
		zmqBlockNtfns:     make(chan *wire.MsgBlock),

		mempool:        make(map[chainhash.Hash]struct{}),
		expiredMempool: make(map[int32]map[chainhash.Hash]struct{}),
	}
}

// AddClient adds a client to the set of active rescan clients of the current
// chain connection. This allows the connection to include the specified client
// in its notification delivery.
//
// NOTE: This function is safe for concurrent access.
func (c *BitcoindConn) AddClient(client *BitcoindClient) {
	c.rescanClientsMtx.Lock()
	defer c.rescanClientsMtx.Unlock()

	c.rescanClients[client.id] = client
}

// RemoveClient removes the client with the given ID from the set of active
// rescan clients. Once removed, the client will no longer receive block and
// transaction notifications from the chain connection.
//
// NOTE: This function is safe for concurrent access.
func (c *BitcoindConn) RemoveClient(id uint64) {
	c.rescanClientsMtx.Lock()
	defer c.rescanClientsMtx.Unlock()

	delete(c.rescanClients, id)
}

// isASCII is a helper method that checks whether all bytes in `data` would be
// printable ASCII characters if interpreted as a string.
func isASCII(s string) bool {
	for _, c := range s {
		if c < 32 || c > 126 {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before><commit_msg>Separate OpenAPI V2 and V3 Config<commit_after><|endoftext|>


<commit_before><commit_msg>Fix test, the test started failing because the logic of returnig resources changed, when getting a list of resources if empty then don't return 500, return 200 and empty list<commit_after><|endoftext|>


<commit_before>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libgit

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strings"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/kbfs/libfs"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/pkg/errors"
)

const (
	kbfsRepoDir         = ".kbfs_git"
	kbfsConfigName      = "kbfs_config"
	kbfsConfigNameTemp  = "._kbfs_config"
	gitSuffixToIgnore   = ".git"
	kbfsDeletedReposDir = ".kbfs_deleted_repos"
)

// This character set is what Github supports in repo names.  It's
// probably to avoid any problems when cloning onto filesystems that
// have different Unicode decompression schemes
// (https://en.wikipedia.org/wiki/Unicode_equivalence).  There's no
// internal reason to be so restrictive, but it probably makes sense
// to start off more restrictive and then relax things later as we
// test.
var repoNameRE = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9_\.-]*)$`)

func checkValidRepoName(repoName string, config libkbfs.Config) bool {
	return len(repoName) >= 1 &&
		uint32(len(repoName)) <= config.MaxNameBytes() &&
		(os.Getenv("KBFS_GIT_REPONAME_SKIP_CHECK") != "" ||
			repoNameRE.MatchString(repoName))
}

// UpdateRepoMD lets the Keybase service know that a repo's MD has
// been updated.
func UpdateRepoMD(ctx context.Context, config libkbfs.Config,
	tlfHandle *libkbfs.TlfHandle, fs *libfs.FS) error {
	folder := tlfHandle.ToFavorite().ToKBFolder(false)

	// Get the user-formatted repo name.
	f, err := fs.Open(kbfsConfigName)
	if err != nil {
		return err
	}
	defer f.Close()
	buf, err := ioutil.ReadAll(f)
	if err != nil {
		return err
	}
	c, err := configFromBytes(buf)
	if err != nil {
		return err
	}

	log := config.MakeLogger("")
	log.CDebugf(ctx, "Putting git MD update")
	err = config.KBPKI().PutGitMetadata(
		ctx, folder, keybase1.RepoID(c.ID.String()),
		keybase1.GitRepoName(c.Name))
	if err != nil {
		// Just log the put error, it shouldn't block the success of
		// the overall git operation.
		log.CDebugf(ctx, "Failed to put git metadata: %+v", err)
	}
	return nil
}

func createNewRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string, fs *libfs.FS) (ID, error) {
	// TODO: take a global repo lock here to make sure only one
	// client generates the repo ID.
	repoID, err := makeRandomID()
	if err != nil {
		return NullID, err
	}
	config.MakeLogger("").CDebugf(ctx,
		"Creating a new repo %s in %s: repoID=%s",
		repoName, tlfHandle.GetCanonicalPath(), repoID)

	// Lock a temp file to avoid a duplicate create of the actual
	// file.  TODO: clean up this file at some point?
	lockFile, err := fs.Create(kbfsConfigNameTemp)
	if err != nil && !os.IsExist(err) {
		return NullID, err
	} else if os.IsExist(err) {
		lockFile, err = fs.Open(kbfsConfigNameTemp)
	}
	if err != nil {
		return NullID, err
	}
	defer lockFile.Close()

	// Take a lock during creation.
	err = lockFile.Lock()
	if err != nil {
		return NullID, err
	}

	f, err := fs.Create(kbfsConfigName)
	if err != nil && !os.IsExist(err) {
		return NullID, err
	} else if os.IsExist(err) {
		// The config file already exists, so someone else already
		// initialized the repo.
		config.MakeLogger("").CDebugf(
			ctx, "Config file for repo %s already exists", repoName)
		f, err := fs.Open(kbfsConfigName)
		if err != nil {
			return NullID, err
		}
		defer f.Close()
		buf, err := ioutil.ReadAll(f)
		if err != nil {
			return NullID, err
		}
		existingConfig, err := configFromBytes(buf)
		if err != nil {
			return NullID, err
		}
		return NullID, errors.WithStack(libkb.RepoAlreadyExistsError{
			DesiredName:  repoName,
			ExistingName: existingConfig.Name,
			ExistingID:   existingConfig.ID.String(),
		})
	}
	defer f.Close()

	session, err := config.KBPKI().GetCurrentSession(ctx)
	if err != nil {
		return NullID, err
	}
	c := &Config{
		ID:         repoID,
		Name:       repoName,
		CreatorUID: session.UID.String(),
		Ctime:      config.Clock().Now().UnixNano(),
	}
	buf, err := c.toBytes()
	if err != nil {
		return NullID, err
	}
	_, err = f.Write(buf)
	if err != nil {
		return NullID, err
	}

	err = UpdateRepoMD(ctx, config, tlfHandle, fs)
	if err != nil {
		return NullID, err
	}

	return repoID, nil
}

func normalizeRepoName(repoName string) string {
	return strings.TrimSuffix(strings.ToLower(repoName), gitSuffixToIgnore)
}

func lookupOrCreateDir(ctx context.Context, config libkbfs.Config,
	n libkbfs.Node, name string) (libkbfs.Node, error) {
	newNode, _, err := config.KBFSOps().Lookup(ctx, n, name)
	switch errors.Cause(err).(type) {
	case libkbfs.NoSuchNameError:
		newNode, _, err = config.KBFSOps().CreateDir(ctx, n, name)
		if err != nil {
			return nil, err
		}
	case nil:
	default:
		return nil, err
	}
	return newNode, nil
}

type repoOpType int

const (
	getOrCreate repoOpType = iota
	createOnly
	getOnly
)

// NoSuchRepoError indicates that a repo doesn't yet exist, and it
// will not be created.
type NoSuchRepoError struct {
	name string
}

func (nsre NoSuchRepoError) Error() string {
	return fmt.Sprintf("A repo named %s hasn't been created yet", nsre.name)
}

func getOrCreateRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string, uniqID string, op repoOpType) (*libfs.FS, ID, error) {
	if !checkValidRepoName(repoName, config) {
		return nil, NullID, errors.WithStack(libkb.InvalidRepoNameError{Name: repoName})
	}

	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
		ctx, tlfHandle, libkbfs.MasterBranch)
	if err != nil {
		return nil, NullID, err
	}
	normalizedRepoName := normalizeRepoName(repoName)

	repoDir, err := lookupOrCreateDir(ctx, config, rootNode, kbfsRepoDir)
	if err != nil {
		return nil, NullID, err
	}
	if op == getOnly {
		_, _, err = config.KBFSOps().Lookup(ctx, repoDir, normalizedRepoName)
		switch errors.Cause(err).(type) {
		case libkbfs.NoSuchNameError:
			return nil, NullID, errors.WithStack(NoSuchRepoError{repoName})
		case nil:
		default:
			return nil, NullID, err
		}
	} else {
		_, err = lookupOrCreateDir(ctx, config, repoDir, normalizedRepoName)
		if err != nil {
			return nil, NullID, err
		}
	}

	fs, err := libfs.NewFS(
		ctx, config, tlfHandle, path.Join(kbfsRepoDir, normalizedRepoName),
		uniqID)
	if err != nil {
		return nil, NullID, err
	}

	f, err := fs.Open(kbfsConfigName)
	if err != nil && !os.IsNotExist(err) {
		return nil, NullID, err
	} else if os.IsNotExist(err) {
		if op == getOnly {
			return nil, NullID, errors.WithStack(NoSuchRepoError{repoName})
		}

		// Create a new repo ID.
		repoID, err := createNewRepoAndID(ctx, config, tlfHandle, repoName, fs)
		if err != nil {
			return nil, NullID, err
		}
		return fs, repoID, nil
	}
	defer f.Close()

	buf, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, NullID, err
	}
	c, err := configFromBytes(buf)
	if err != nil {
		return nil, NullID, err
	}

	if op == createOnly {
		// If this was already created, but we were expected to create
		// it, then send back an error.
		return nil, NullID, libkb.RepoAlreadyExistsError{
			DesiredName:  repoName,
			ExistingName: c.Name,
			ExistingID:   c.ID.String(),
		}
	}

	fs.SetLockNamespace(c.ID.Bytes())

	return fs, c.ID, nil
}

// GetOrCreateRepoAndID returns a filesystem object rooted at the
// specified repo, along with the stable repo ID.  If the repo hasn't
// been created yet, it generates a new ID and creates the repo.  The
// caller is responsible for syncing the FS and flushing the journal,
// if desired.
func GetOrCreateRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string, uniqID string) (*libfs.FS, ID, error) {
	return getOrCreateRepoAndID(
		ctx, config, tlfHandle, repoName, uniqID, getOrCreate)
}

// GetRepoAndID returns a filesystem object rooted at the
// specified repo, along with the stable repo ID, if it already
// exists.
func GetRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string, uniqID string) (*libfs.FS, ID, error) {
	return getOrCreateRepoAndID(
		ctx, config, tlfHandle, repoName, uniqID, getOnly)
}

// CreateRepoAndID returns a new stable repo ID for the provided
// repoName in the given TLF.  If the repo has already been created,
// it returns a `RepoAlreadyExistsError`.  The caller is responsible
// for syncing the FS and flushing the journal, if desired.  It
// expects the `config` object to be unique during the lifetime of
// this call.
func CreateRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string) (ID, error) {
	// Create a unique ID using the verifying key and the `config`
	// object, which should be unique to each call in practice.
	session, err := config.KBPKI().GetCurrentSession(ctx)
	if err != nil {
		return NullID, err
	}
	uniqID := fmt.Sprintf("%s-%p", session.VerifyingKey.String(), config)

	fs, id, err := getOrCreateRepoAndID(
		ctx, config, tlfHandle, repoName, uniqID, createOnly)
	if err != nil {
		return NullID, err
	}
	err = fs.SyncAll()
	if err != nil {
		return NullID, err
	}
	return id, err
}

// DeleteRepo "deletes" the given repo in the given TLF.  Right now it
// simply moves the repo out of the way to a special directory, to
// allow any concurrent writers to finish their pushes without
// triggering conflict resolution.  The caller is responsible for
// syncing the FS and flushing the journal, if desired.  It expects
// the `config` object to be unique during the lifetime of this call.
func DeleteRepo(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string) error {
	// Create a unique ID using the verifying key and the `config`
	// object, which should be unique to each call in practice.
	session, err := config.KBPKI().GetCurrentSession(ctx)
	if err != nil {
		return err
	}

	kbfsOps := config.KBFSOps()
	rootNode, _, err := kbfsOps.GetOrCreateRootNode(
		ctx, tlfHandle, libkbfs.MasterBranch)
	if err != nil {
		return err
	}
	normalizedRepoName := normalizeRepoName(repoName)

	repoNode, _, err := kbfsOps.Lookup(ctx, rootNode, kbfsRepoDir)
	if err != nil {
		return err
	}

	_, _, err = kbfsOps.Lookup(ctx, repoNode, normalizedRepoName)
	if err != nil {
		return err
	}

	deletedReposNode, err := lookupOrCreateDir(
		ctx, config, repoNode, kbfsDeletedReposDir)
	if err != nil {
		return err
	}

	// For now, just rename the repo out of the way, using the device
	// ID and the current time in nanoseconds to make uniqueness
	// probable.  TODO(KBFS-2442): periodically delete old-enough
	// repos from `kbfsDeletedReposDir`.
	dirSuffix := fmt.Sprintf(
		"%s-%d", session.VerifyingKey.String(), config.Clock().Now().UnixNano())
	return kbfsOps.Rename(
		ctx, repoNode, normalizedRepoName, deletedReposNode,
		normalizedRepoName+dirSuffix)
}
<commit_msg>libgit: when repo doesn't exist, give readers a NoSuchRepoError<commit_after>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libgit

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strings"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/kbfs/libfs"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/pkg/errors"
)

const (
	kbfsRepoDir         = ".kbfs_git"
	kbfsConfigName      = "kbfs_config"
	kbfsConfigNameTemp  = "._kbfs_config"
	gitSuffixToIgnore   = ".git"
	kbfsDeletedReposDir = ".kbfs_deleted_repos"
)

// This character set is what Github supports in repo names.  It's
// probably to avoid any problems when cloning onto filesystems that
// have different Unicode decompression schemes
// (https://en.wikipedia.org/wiki/Unicode_equivalence).  There's no
// internal reason to be so restrictive, but it probably makes sense
// to start off more restrictive and then relax things later as we
// test.
var repoNameRE = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9_\.-]*)$`)

func checkValidRepoName(repoName string, config libkbfs.Config) bool {
	return len(repoName) >= 1 &&
		uint32(len(repoName)) <= config.MaxNameBytes() &&
		(os.Getenv("KBFS_GIT_REPONAME_SKIP_CHECK") != "" ||
			repoNameRE.MatchString(repoName))
}

// UpdateRepoMD lets the Keybase service know that a repo's MD has
// been updated.
func UpdateRepoMD(ctx context.Context, config libkbfs.Config,
	tlfHandle *libkbfs.TlfHandle, fs *libfs.FS) error {
	folder := tlfHandle.ToFavorite().ToKBFolder(false)

	// Get the user-formatted repo name.
	f, err := fs.Open(kbfsConfigName)
	if err != nil {
		return err
	}
	defer f.Close()
	buf, err := ioutil.ReadAll(f)
	if err != nil {
		return err
	}
	c, err := configFromBytes(buf)
	if err != nil {
		return err
	}

	log := config.MakeLogger("")
	log.CDebugf(ctx, "Putting git MD update")
	err = config.KBPKI().PutGitMetadata(
		ctx, folder, keybase1.RepoID(c.ID.String()),
		keybase1.GitRepoName(c.Name))
	if err != nil {
		// Just log the put error, it shouldn't block the success of
		// the overall git operation.
		log.CDebugf(ctx, "Failed to put git metadata: %+v", err)
	}
	return nil
}

func createNewRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string, fs *libfs.FS) (ID, error) {
	// TODO: take a global repo lock here to make sure only one
	// client generates the repo ID.
	repoID, err := makeRandomID()
	if err != nil {
		return NullID, err
	}
	config.MakeLogger("").CDebugf(ctx,
		"Creating a new repo %s in %s: repoID=%s",
		repoName, tlfHandle.GetCanonicalPath(), repoID)

	// Lock a temp file to avoid a duplicate create of the actual
	// file.  TODO: clean up this file at some point?
	lockFile, err := fs.Create(kbfsConfigNameTemp)
	if err != nil && !os.IsExist(err) {
		return NullID, err
	} else if os.IsExist(err) {
		lockFile, err = fs.Open(kbfsConfigNameTemp)
	}
	if err != nil {
		return NullID, err
	}
	defer lockFile.Close()

	// Take a lock during creation.
	err = lockFile.Lock()
	if err != nil {
		return NullID, err
	}

	f, err := fs.Create(kbfsConfigName)
	if err != nil && !os.IsExist(err) {
		return NullID, err
	} else if os.IsExist(err) {
		// The config file already exists, so someone else already
		// initialized the repo.
		config.MakeLogger("").CDebugf(
			ctx, "Config file for repo %s already exists", repoName)
		f, err := fs.Open(kbfsConfigName)
		if err != nil {
			return NullID, err
		}
		defer f.Close()
		buf, err := ioutil.ReadAll(f)
		if err != nil {
			return NullID, err
		}
		existingConfig, err := configFromBytes(buf)
		if err != nil {
			return NullID, err
		}
		return NullID, errors.WithStack(libkb.RepoAlreadyExistsError{
			DesiredName:  repoName,
			ExistingName: existingConfig.Name,
			ExistingID:   existingConfig.ID.String(),
		})
	}
	defer f.Close()

	session, err := config.KBPKI().GetCurrentSession(ctx)
	if err != nil {
		return NullID, err
	}
	c := &Config{
		ID:         repoID,
		Name:       repoName,
		CreatorUID: session.UID.String(),
		Ctime:      config.Clock().Now().UnixNano(),
	}
	buf, err := c.toBytes()
	if err != nil {
		return NullID, err
	}
	_, err = f.Write(buf)
	if err != nil {
		return NullID, err
	}

	err = UpdateRepoMD(ctx, config, tlfHandle, fs)
	if err != nil {
		return NullID, err
	}

	return repoID, nil
}

func normalizeRepoName(repoName string) string {
	return strings.TrimSuffix(strings.ToLower(repoName), gitSuffixToIgnore)
}

func lookupOrCreateDir(ctx context.Context, config libkbfs.Config,
	n libkbfs.Node, name string) (libkbfs.Node, error) {
	newNode, _, err := config.KBFSOps().Lookup(ctx, n, name)
	switch errors.Cause(err).(type) {
	case libkbfs.NoSuchNameError:
		newNode, _, err = config.KBFSOps().CreateDir(ctx, n, name)
		if err != nil {
			return nil, err
		}
	case nil:
	default:
		return nil, err
	}
	return newNode, nil
}

type repoOpType int

const (
	getOrCreate repoOpType = iota
	createOnly
	getOnly
)

// NoSuchRepoError indicates that a repo doesn't yet exist, and it
// will not be created.
type NoSuchRepoError struct {
	name string
}

func (nsre NoSuchRepoError) Error() string {
	return fmt.Sprintf("A repo named %s hasn't been created yet", nsre.name)
}

func getOrCreateRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string, uniqID string, op repoOpType) (
	fs *libfs.FS, id ID, err error) {
	if !checkValidRepoName(repoName, config) {
		return nil, NullID, errors.WithStack(libkb.InvalidRepoNameError{Name: repoName})
	}

	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
		ctx, tlfHandle, libkbfs.MasterBranch)
	if err != nil {
		return nil, NullID, err
	}
	normalizedRepoName := normalizeRepoName(repoName)

	// If the user doesn't have write access, but the repo doesn't
	// exist, give them a nice error message.
	repoExists := false
	defer func() {
		_, isWriteAccessErr := errors.Cause(err).(libkbfs.WriteAccessError)
		if !repoExists && isWriteAccessErr {
			err = NoSuchRepoError{repoName}
		}
	}()

	repoDir, err := lookupOrCreateDir(ctx, config, rootNode, kbfsRepoDir)
	if err != nil {
		return nil, NullID, err
	}
	if op == getOnly {
		_, _, err = config.KBFSOps().Lookup(ctx, repoDir, normalizedRepoName)
		switch errors.Cause(err).(type) {
		case libkbfs.NoSuchNameError:
			return nil, NullID, errors.WithStack(NoSuchRepoError{repoName})
		case nil:
		default:
			return nil, NullID, err
		}
	} else {
		_, err = lookupOrCreateDir(ctx, config, repoDir, normalizedRepoName)
		if err != nil {
			return nil, NullID, err
		}
	}
	repoExists = true

	fs, err = libfs.NewFS(
		ctx, config, tlfHandle, path.Join(kbfsRepoDir, normalizedRepoName),
		uniqID)
	if err != nil {
		return nil, NullID, err
	}

	f, err := fs.Open(kbfsConfigName)
	if err != nil && !os.IsNotExist(err) {
		return nil, NullID, err
	} else if os.IsNotExist(err) {
		if op == getOnly {
			return nil, NullID, errors.WithStack(NoSuchRepoError{repoName})
		}

		// Create a new repo ID.
		repoID, err := createNewRepoAndID(ctx, config, tlfHandle, repoName, fs)
		if err != nil {
			return nil, NullID, err
		}
		return fs, repoID, nil
	}
	defer f.Close()

	buf, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, NullID, err
	}
	c, err := configFromBytes(buf)
	if err != nil {
		return nil, NullID, err
	}

	if op == createOnly {
		// If this was already created, but we were expected to create
		// it, then send back an error.
		return nil, NullID, libkb.RepoAlreadyExistsError{
			DesiredName:  repoName,
			ExistingName: c.Name,
			ExistingID:   c.ID.String(),
		}
	}

	fs.SetLockNamespace(c.ID.Bytes())

	return fs, c.ID, nil
}

// GetOrCreateRepoAndID returns a filesystem object rooted at the
// specified repo, along with the stable repo ID.  If the repo hasn't
// been created yet, it generates a new ID and creates the repo.  The
// caller is responsible for syncing the FS and flushing the journal,
// if desired.
func GetOrCreateRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string, uniqID string) (*libfs.FS, ID, error) {
	return getOrCreateRepoAndID(
		ctx, config, tlfHandle, repoName, uniqID, getOrCreate)
}

// GetRepoAndID returns a filesystem object rooted at the
// specified repo, along with the stable repo ID, if it already
// exists.
func GetRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string, uniqID string) (*libfs.FS, ID, error) {
	return getOrCreateRepoAndID(
		ctx, config, tlfHandle, repoName, uniqID, getOnly)
}

// CreateRepoAndID returns a new stable repo ID for the provided
// repoName in the given TLF.  If the repo has already been created,
// it returns a `RepoAlreadyExistsError`.  The caller is responsible
// for syncing the FS and flushing the journal, if desired.  It
// expects the `config` object to be unique during the lifetime of
// this call.
func CreateRepoAndID(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string) (ID, error) {
	// Create a unique ID using the verifying key and the `config`
	// object, which should be unique to each call in practice.
	session, err := config.KBPKI().GetCurrentSession(ctx)
	if err != nil {
		return NullID, err
	}
	uniqID := fmt.Sprintf("%s-%p", session.VerifyingKey.String(), config)

	fs, id, err := getOrCreateRepoAndID(
		ctx, config, tlfHandle, repoName, uniqID, createOnly)
	if err != nil {
		return NullID, err
	}
	err = fs.SyncAll()
	if err != nil {
		return NullID, err
	}
	return id, err
}

// DeleteRepo "deletes" the given repo in the given TLF.  Right now it
// simply moves the repo out of the way to a special directory, to
// allow any concurrent writers to finish their pushes without
// triggering conflict resolution.  The caller is responsible for
// syncing the FS and flushing the journal, if desired.  It expects
// the `config` object to be unique during the lifetime of this call.
func DeleteRepo(
	ctx context.Context, config libkbfs.Config, tlfHandle *libkbfs.TlfHandle,
	repoName string) error {
	// Create a unique ID using the verifying key and the `config`
	// object, which should be unique to each call in practice.
	session, err := config.KBPKI().GetCurrentSession(ctx)
	if err != nil {
		return err
	}

	kbfsOps := config.KBFSOps()
	rootNode, _, err := kbfsOps.GetOrCreateRootNode(
		ctx, tlfHandle, libkbfs.MasterBranch)
	if err != nil {
		return err
	}
	normalizedRepoName := normalizeRepoName(repoName)

	repoNode, _, err := kbfsOps.Lookup(ctx, rootNode, kbfsRepoDir)
	if err != nil {
		return err
	}

	_, _, err = kbfsOps.Lookup(ctx, repoNode, normalizedRepoName)
	if err != nil {
		return err
	}

	deletedReposNode, err := lookupOrCreateDir(
		ctx, config, repoNode, kbfsDeletedReposDir)
	if err != nil {
		return err
	}

	// For now, just rename the repo out of the way, using the device
	// ID and the current time in nanoseconds to make uniqueness
	// probable.  TODO(KBFS-2442): periodically delete old-enough
	// repos from `kbfsDeletedReposDir`.
	dirSuffix := fmt.Sprintf(
		"%s-%d", session.VerifyingKey.String(), config.Clock().Now().UnixNano())
	return kbfsOps.Rename(
		ctx, repoNode, normalizedRepoName, deletedReposNode,
		normalizedRepoName+dirSuffix)
}
<|endoftext|>


<commit_before>package context

import (
	"sync"

	"fmt"
	"time"
)

type ErrorHandle func(error) error
type CtxOptFunc func(opt *ContextOption) error
type TaskHandle func(ctx Context, raw interface{}) error

type ctxt struct {
	sync.RWMutex
	Stack *Stack
	Reply Replyer

	values map[interface{}]interface{}
	retry  chan bool
	cancel chan bool
	done   chan bool
	rece   chan interface{}

	// 0 for text
	// 1 for text and data
	postMode int

	send      chan string
	sendData  chan ResReply
	sendTable chan *Table
	quit      chan bool

	isRunning bool
	counter   int

	errHandle      ErrorHandle
	replyErrHandle ErrorHandle
}

type Context interface {
	NewContext() Context
	Peek() Context
	Push(Context)
	Pop() Context

	Wait(TaskHandle)
	Retry()
	Cancel()
	Done()
	Send(interface{})

	Value(interface{}) interface{}
	SetValue(interface{}, interface{})
	CtxValue(interface{}) interface{}
	SetCtxValue(interface{}, interface{})
	GlobalValue(interface{}) interface{}
	SetGlobalValue(interface{}, interface{})

	SetPostMode(int)
	PostMode() int

	Post(string, ...interface{}) error
	PostTable(Table) error

	Run()
	RunCallback(handler ContextReplyHander)
	RunCallbackOnce(handler ContextReplyHander)
	Close()

	Reset()
	IsRunning() bool
}

var (
	ErrRetry  = fmt.Errorf("Task Retry")
	ErrCancel = fmt.Errorf("Task Cancel")
)

type ContextOption struct {
	Reply Replyer
	Error ErrorHandle
}

func NewContext(args ...CtxOptFunc) (*ctxt, error) {
	opt, err := ctxOption(args)
	if err != nil {
		return nil, err
	}

	root := &ctxt{
		values:    make(map[interface{}]interface{}),
		retry:     make(chan bool),
		cancel:    make(chan bool),
		rece:      make(chan interface{}),
		done:      make(chan bool),
		send:      make(chan string),
		sendData:  make(chan ResReply),
		sendTable: make(chan *Table),
		quit:      make(chan bool),
		errHandle: opt.Error,
		Reply:     opt.Reply,
	}

	root.Stack = NewStack(root)
	return root, nil
}

func ctxOption(args []CtxOptFunc) (*ContextOption, error) {
	var opt = ContextOption{
		Reply: StdoutReply,
	}

	for _, f := range args {
		if err := f(&opt); err != nil {
			return nil, err
		}
	}

	return &opt, nil
}

func UseReply(reply Replyer) CtxOptFunc {
	return func(opt *ContextOption) error {
		opt.Reply = reply
		return nil
	}
}

func OnError(errHandle ErrorHandle) CtxOptFunc {
	return func(opt *ContextOption) error {
		opt.Error = errHandle
		return nil
	}
}

func (ctx *ctxt) NewContext() Context {
	childCtx, _ := NewContext()
	childCtx.Stack = ctx.Stack

	return childCtx
}

func (ctx *ctxt) Peek() Context {

	return ctx.Stack.Peek()
}

func (ctx *ctxt) Push(cc Context) {
	ctx.Stack.Push(cc)
}

func (ctx *ctxt) Pop() Context {
	return ctx.Stack.Pop()
}

// TODO 混合私聊和群聊情况，私聊可以去读群聊，而群聊不可以读取私聊信息？
func (ctx *ctxt) CtxValue(name interface{}) interface{} {
	if GroupChat(ctx) {
		name = fmt.Sprintf("Group:%v", name)
	}

	ctx.RLock()
	defer ctx.RUnlock()
	return ctx.values[name]
}

func (ctx *ctxt) SetCtxValue(name, value interface{}) {
	if GroupChat(ctx) {
		name = fmt.Sprintf("Group:%v", name)
	}

	ctx.Lock()
	defer ctx.Unlock()
	ctx.values[name] = value
}

func (ctx *ctxt) Value(name interface{}) interface{} {
	ctx.RLock()
	defer ctx.RUnlock()

	return ctx.values[name]
}

func (ctx *ctxt) SetValue(name, value interface{}) {
	ctx.Lock()
	defer ctx.Unlock()

	ctx.values[name] = value
}

func (ctx *ctxt) GlobalValue(name interface{}) interface{} {
	return ctx.Stack.Root.Value(name)
	// return ctx.Stack.Root.values[name]
}

func (ctx *ctxt) SetGlobalValue(name, value interface{}) {
	ctx.Stack.Root.SetValue(name, value)
	// ctx.Stack.Root.values[name] = value
}

func (ctx *ctxt) Wait(task TaskHandle) {
	if ctx.isRunning {
		return
	}

	ctx.isRunning = true
	for ctx.isRunning {
		select {
		case info := <-ctx.rece: // do process
			task(ctx, info)
		case <-ctx.retry:
		case <-ctx.done:
			ctx.isRunning = false
			break
		case <-ctx.cancel:
			ctx.isRunning = false
			break
		}
	}
}

func (ctx *ctxt) Cancel() {
	if ctx.isRunning {
		ctx.cancel <- true
	}
}

func (ctx *ctxt) Retry() {
	if ctx.isRunning {
		ctx.retry <- true
	}
}

func (ctx *ctxt) Done() {
	if ctx.isRunning {
		ctx.done <- true
	}
}

func (ctx *ctxt) Send(raw interface{}) {
	if ctx.isRunning {
		ctx.rece <- raw
	}
}

// func (ctx *ctxt) Post(msg string, args ...interface{}) error {
// 	// ctx.Stack.
// 	log.Printf(msg, args...)
// 	return nil
// }

func (ctx *ctxt) SetPostMode(mode int) {
	ctx.postMode = mode
}

func (ctx ctxt) PostMode() int {
	return ctx.postMode
}

func (ctx *ctxt) Post(text string, args ...interface{}) error {
	ctx.counter++

	if 0 == ctx.PostMode() {
		// ctx.Stack.Root.send <- fmt.Sprintf(text, args...)
		ctx.Stack.Root.send <- text
	} else {
		r := map[string]interface{}{
			"reply": text,
		}

		if len(args) > 0 {
			r["data"] = args[0]
		}

		reply := ResReply{
			Data: r,
		}

		if len(args) > 1 {
			switch req := args[1].(type) {
			case Request:
				reply.Req = &req
			}
		}

		ctx.Stack.Root.sendData <- reply
	}

	return nil
}

func (ctx *ctxt) PostTable(table Table) error {
	ctx.counter++
	ctx.Stack.Root.sendTable <- &table
	return nil
}

func (ctx *ctxt) Run() {
	var err error
LOOP:
	for {
		select {
		case txt := <-ctx.send:
			ctx.counter--
			err = ctx.Reply.Text(txt, ctx)
		case _ = <-ctx.sendData:
			ctx.counter--
			err = ctx.Reply.Text("unimplimented data channel", ctx)
		case table := <-ctx.sendTable:
			ctx.counter--
			err = ctx.Reply.Table(table, ctx)
		case <-ctx.quit:
			// ctx.waitingEnd()
			break LOOP
		}
		ctx.doReplyError(err)
	}
}

type ContextReplyHander func(txt *string, table *Table, data interface{}, req *Request)

func (ctx *ctxt) RunCallback(handler ContextReplyHander) {
	var err error
LOOP:
	for {
		select {
		case txt := <-ctx.send:
			ctx.counter--
			handler(&txt, nil, nil, nil)
		case data := <-ctx.sendData:
			ctx.counter--
			handler(nil, nil, data.Data, data.Req)
		case table := <-ctx.sendTable:
			ctx.counter--
			handler(nil, table, nil, nil)
		case <-ctx.quit:
			// ctx.waitingEnd()
			break LOOP
		}
		ctx.doReplyError(err)
	}
}

func (ctx *ctxt) RunCallbackOnce(handler ContextReplyHander) {
	select {
	case txt := <-ctx.send:
		ctx.counter--
		handler(&txt, nil, nil, nil)
	case table := <-ctx.sendTable:
		ctx.counter--
		handler(nil, table, nil, nil)
	case data := <-ctx.sendData:
		ctx.counter--
		handler(nil, nil, data.Data, data.Req)
	case <-time.After(time.Second * 20):
		txt := "请求超时，请稍候再试"
		handler(&txt, nil, nil, nil)
	}
}

func (ctx *ctxt) IsRunning() bool {
	return ctx.isRunning
}

func (ctx *ctxt) Reset() {
	for _, child := range ctx.Stack.Children {
		if child.IsRunning() {
			go child.Cancel()
		}
	}

	if ctx.Stack.Root.IsRunning() {
		go ctx.Stack.Root.Cancel()
	}
	ctx.Lock()
	defer ctx.Unlock()
	root := ctx.Stack.Root
	root.values = make(map[interface{}]interface{})
	root.retry = make(chan bool)
	root.cancel = make(chan bool)
	root.rece = make(chan interface{})
	root.done = make(chan bool)
	root.isRunning = false
	root.Stack.Children = make([]Context, 0)
}

func (ctx *ctxt) Close() {
	var (
		tick = 1
		tt   = time.NewTimer(15 * time.Second)
		exit bool
	)

	for !exit {
		select {
		case <-time.After(time.Duration(tick) * time.Millisecond):
			if ctx.counter > 0 {
				tick *= 2
			} else {
				exit = true
				break
			}
		case <-tt.C:
			exit = true
			break
		}
	}
	ctx.quit <- true
}

func (ctx *ctxt) OnError(handle ErrorHandle) {
	ctx.errHandle = handle
}

func (ctx *ctxt) OnReplyError(handle ErrorHandle) {
	ctx.replyErrHandle = handle
}

func (ctx *ctxt) doError(err error) error {
	if ctx.errHandle != nil {
		return ctx.errHandle(err)
	}
	return err
}

func (ctx *ctxt) doReplyError(err error) error {
	if ctx.errHandle != nil {
		return ctx.errHandle(err)
	}
	return err
}
<commit_msg>pass ResReply pointer<commit_after>package context

import (
	"sync"

	"fmt"
	"time"
)

type ErrorHandle func(error) error
type CtxOptFunc func(opt *ContextOption) error
type TaskHandle func(ctx Context, raw interface{}) error

type ctxt struct {
	sync.RWMutex
	Stack *Stack
	Reply Replyer

	values map[interface{}]interface{}
	retry  chan bool
	cancel chan bool
	done   chan bool
	rece   chan interface{}

	// 0 for text
	// 1 for text and data
	postMode int

	send      chan string
	sendData  chan ResReply
	sendTable chan *Table
	quit      chan bool

	isRunning bool
	counter   int

	errHandle      ErrorHandle
	replyErrHandle ErrorHandle
}

type Context interface {
	NewContext() Context
	Peek() Context
	Push(Context)
	Pop() Context

	Wait(TaskHandle)
	Retry()
	Cancel()
	Done()
	Send(interface{})

	Value(interface{}) interface{}
	SetValue(interface{}, interface{})
	CtxValue(interface{}) interface{}
	SetCtxValue(interface{}, interface{})
	GlobalValue(interface{}) interface{}
	SetGlobalValue(interface{}, interface{})

	SetPostMode(int)
	PostMode() int

	Post(string, ...interface{}) error
	PostTable(Table) error

	Run()
	RunCallback(handler ContextReplyHander)
	RunCallbackOnce(handler ContextReplyHander)
	Close()

	Reset()
	IsRunning() bool
}

var (
	ErrRetry  = fmt.Errorf("Task Retry")
	ErrCancel = fmt.Errorf("Task Cancel")
)

type ContextOption struct {
	Reply Replyer
	Error ErrorHandle
}

func NewContext(args ...CtxOptFunc) (*ctxt, error) {
	opt, err := ctxOption(args)
	if err != nil {
		return nil, err
	}

	root := &ctxt{
		values:    make(map[interface{}]interface{}),
		retry:     make(chan bool),
		cancel:    make(chan bool),
		rece:      make(chan interface{}),
		done:      make(chan bool),
		send:      make(chan string),
		sendData:  make(chan ResReply),
		sendTable: make(chan *Table),
		quit:      make(chan bool),
		errHandle: opt.Error,
		Reply:     opt.Reply,
	}

	root.Stack = NewStack(root)
	return root, nil
}

func ctxOption(args []CtxOptFunc) (*ContextOption, error) {
	var opt = ContextOption{
		Reply: StdoutReply,
	}

	for _, f := range args {
		if err := f(&opt); err != nil {
			return nil, err
		}
	}

	return &opt, nil
}

func UseReply(reply Replyer) CtxOptFunc {
	return func(opt *ContextOption) error {
		opt.Reply = reply
		return nil
	}
}

func OnError(errHandle ErrorHandle) CtxOptFunc {
	return func(opt *ContextOption) error {
		opt.Error = errHandle
		return nil
	}
}

func (ctx *ctxt) NewContext() Context {
	childCtx, _ := NewContext()
	childCtx.Stack = ctx.Stack

	return childCtx
}

func (ctx *ctxt) Peek() Context {

	return ctx.Stack.Peek()
}

func (ctx *ctxt) Push(cc Context) {
	ctx.Stack.Push(cc)
}

func (ctx *ctxt) Pop() Context {
	return ctx.Stack.Pop()
}

// TODO 混合私聊和群聊情况，私聊可以去读群聊，而群聊不可以读取私聊信息？
func (ctx *ctxt) CtxValue(name interface{}) interface{} {
	if GroupChat(ctx) {
		name = fmt.Sprintf("Group:%v", name)
	}

	ctx.RLock()
	defer ctx.RUnlock()
	return ctx.values[name]
}

func (ctx *ctxt) SetCtxValue(name, value interface{}) {
	if GroupChat(ctx) {
		name = fmt.Sprintf("Group:%v", name)
	}

	ctx.Lock()
	defer ctx.Unlock()
	ctx.values[name] = value
}

func (ctx *ctxt) Value(name interface{}) interface{} {
	ctx.RLock()
	defer ctx.RUnlock()

	return ctx.values[name]
}

func (ctx *ctxt) SetValue(name, value interface{}) {
	ctx.Lock()
	defer ctx.Unlock()

	ctx.values[name] = value
}

func (ctx *ctxt) GlobalValue(name interface{}) interface{} {
	return ctx.Stack.Root.Value(name)
	// return ctx.Stack.Root.values[name]
}

func (ctx *ctxt) SetGlobalValue(name, value interface{}) {
	ctx.Stack.Root.SetValue(name, value)
	// ctx.Stack.Root.values[name] = value
}

func (ctx *ctxt) Wait(task TaskHandle) {
	if ctx.isRunning {
		return
	}

	ctx.isRunning = true
	for ctx.isRunning {
		select {
		case info := <-ctx.rece: // do process
			task(ctx, info)
		case <-ctx.retry:
		case <-ctx.done:
			ctx.isRunning = false
			break
		case <-ctx.cancel:
			ctx.isRunning = false
			break
		}
	}
}

func (ctx *ctxt) Cancel() {
	if ctx.isRunning {
		ctx.cancel <- true
	}
}

func (ctx *ctxt) Retry() {
	if ctx.isRunning {
		ctx.retry <- true
	}
}

func (ctx *ctxt) Done() {
	if ctx.isRunning {
		ctx.done <- true
	}
}

func (ctx *ctxt) Send(raw interface{}) {
	if ctx.isRunning {
		ctx.rece <- raw
	}
}

// func (ctx *ctxt) Post(msg string, args ...interface{}) error {
// 	// ctx.Stack.
// 	log.Printf(msg, args...)
// 	return nil
// }

func (ctx *ctxt) SetPostMode(mode int) {
	ctx.postMode = mode
}

func (ctx ctxt) PostMode() int {
	return ctx.postMode
}

func (ctx *ctxt) Post(text string, args ...interface{}) error {
	ctx.counter++

	if 0 == ctx.PostMode() {
		// ctx.Stack.Root.send <- fmt.Sprintf(text, args...)
		ctx.Stack.Root.send <- text
	} else {
		r := map[string]interface{}{
			"reply": text,
		}

		if len(args) > 0 {
			r["data"] = args[0]
		}

		reply := ResReply{
			Data: r,
		}

		if len(args) > 1 {
			switch req := args[1].(type) {
			case Request:
				reply.Req = &req
			}
		}

		ctx.Stack.Root.sendData <- reply
	}

	return nil
}

func (ctx *ctxt) PostTable(table Table) error {
	ctx.counter++
	ctx.Stack.Root.sendTable <- &table
	return nil
}

func (ctx *ctxt) Run() {
	var err error
LOOP:
	for {
		select {
		case txt := <-ctx.send:
			ctx.counter--
			err = ctx.Reply.Text(txt, ctx)
		case _ = <-ctx.sendData:
			ctx.counter--
			err = ctx.Reply.Text("unimplimented data channel", ctx)
		case table := <-ctx.sendTable:
			ctx.counter--
			err = ctx.Reply.Table(table, ctx)
		case <-ctx.quit:
			// ctx.waitingEnd()
			break LOOP
		}
		ctx.doReplyError(err)
	}
}

type ContextReplyHander func(txt *string, table *Table, data *ResReply)

func (ctx *ctxt) RunCallback(handler ContextReplyHander) {
	var err error
LOOP:
	for {
		select {
		case txt := <-ctx.send:
			ctx.counter--
			handler(&txt, nil, nil)
		case data := <-ctx.sendData:
			ctx.counter--
			handler(nil, nil, &data)
		case table := <-ctx.sendTable:
			ctx.counter--
			handler(nil, table, nil)
		case <-ctx.quit:
			// ctx.waitingEnd()
			break LOOP
		}
		ctx.doReplyError(err)
	}
}

func (ctx *ctxt) RunCallbackOnce(handler ContextReplyHander) {
	select {
	case txt := <-ctx.send:
		ctx.counter--
		handler(&txt, nil, nil)
	case table := <-ctx.sendTable:
		ctx.counter--
		handler(nil, table, nil)
	case data := <-ctx.sendData:
		ctx.counter--
		handler(nil, nil, &data)
	case <-time.After(time.Second * 20):
		txt := "请求超时，请稍候再试"
		handler(&txt, nil, nil)
	}
}

func (ctx *ctxt) IsRunning() bool {
	return ctx.isRunning
}

func (ctx *ctxt) Reset() {
	for _, child := range ctx.Stack.Children {
		if child.IsRunning() {
			go child.Cancel()
		}
	}

	if ctx.Stack.Root.IsRunning() {
		go ctx.Stack.Root.Cancel()
	}
	ctx.Lock()
	defer ctx.Unlock()
	root := ctx.Stack.Root
	root.values = make(map[interface{}]interface{})
	root.retry = make(chan bool)
	root.cancel = make(chan bool)
	root.rece = make(chan interface{})
	root.done = make(chan bool)
	root.isRunning = false
	root.Stack.Children = make([]Context, 0)
}

func (ctx *ctxt) Close() {
	var (
		tick = 1
		tt   = time.NewTimer(15 * time.Second)
		exit bool
	)

	for !exit {
		select {
		case <-time.After(time.Duration(tick) * time.Millisecond):
			if ctx.counter > 0 {
				tick *= 2
			} else {
				exit = true
				break
			}
		case <-tt.C:
			exit = true
			break
		}
	}
	ctx.quit <- true
}

func (ctx *ctxt) OnError(handle ErrorHandle) {
	ctx.errHandle = handle
}

func (ctx *ctxt) OnReplyError(handle ErrorHandle) {
	ctx.replyErrHandle = handle
}

func (ctx *ctxt) doError(err error) error {
	if ctx.errHandle != nil {
		return ctx.errHandle(err)
	}
	return err
}

func (ctx *ctxt) doReplyError(err error) error {
	if ctx.errHandle != nil {
		return ctx.errHandle(err)
	}
	return err
}
<|endoftext|>


<commit_before>package context

import (
	"bytes"
	"encoding/binary"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/opt"
)

const (
	routesDbFilename = "routes.db"
	idLogFilename    = "id"
)

// Route is the value part of a shortcut.
type Route struct {
	URL  string    `json:"url"`
	Time time.Time `json:"time"`
}

// Serialize this Route into the given Writer.
func (o *Route) write(w io.Writer) error {
	if err := binary.Write(w, binary.LittleEndian, o.Time.UnixNano()); err != nil {
		return err
	}

	if _, err := w.Write([]byte(o.URL)); err != nil {
		return err
	}

	return nil
}

// Deserialize this Route from the given Reader.
func (o *Route) read(r io.Reader) error {
	var t int64
	if err := binary.Read(r, binary.LittleEndian, &t); err != nil {
		return err
	}

	b, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}

	o.URL = string(b)
	o.Time = time.Unix(0, t)
	return nil
}

// Context provides access to the data store.
type Context struct {
	path string
	db   *leveldb.DB
	lck  sync.Mutex
	id   uint64
}

// Commit the given ID to the data store.
func commit(filename string, id uint64) error {
	w, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer w.Close()

	if err := binary.Write(w, binary.LittleEndian, id); err != nil {
		return err
	}

	return w.Sync()
}

// Load the current ID from the data store.
func load(filename string) (uint64, error) {
	if _, err := os.Stat(filename); err != nil {
		return 0, commit(filename, 0)
	}

	r, err := os.Open(filename)
	if err != nil {
		return 0, err
	}
	defer r.Close()

	var id uint64
	if err := binary.Read(r, binary.LittleEndian, &id); err != nil {
		return 0, err
	}

	return id, nil
}

// Open the context using path as the data store location.
func Open(path string) (*Context, error) {
	if _, err := os.Stat(path); err != nil {
		if err := os.MkdirAll(path, os.ModePerm); err != nil {
			return nil, err
		}
	}

	// open the database
	db, err := leveldb.OpenFile(filepath.Join(path, routesDbFilename), nil)
	if err != nil {
		return nil, err
	}

	id, err := load(filepath.Join(path, idLogFilename))
	if err != nil {
		return nil, err
	}

	return &Context{
		path: path,
		db:   db,
		id:   id,
	}, nil
}

// Close the resources associated with this context.
func (c *Context) Close() error {
	return c.db.Close()
}

// Get retreives a shortcut from the data store.
func (c *Context) Get(name string) (*Route, error) {
	val, err := c.db.Get([]byte(name), nil)
	if err != nil {
		return nil, err
	}

	rt := &Route{}
	if err := rt.read(bytes.NewBuffer(val)); err != nil {
		return nil, err
	}

	return rt, nil
}

// Put stores a new shortcut in the data store.
func (c *Context) Put(key string, rt *Route) error {
	var buf bytes.Buffer
	if err := rt.write(&buf); err != nil {
		return err
	}

	return c.db.Put([]byte(key), buf.Bytes(), &opt.WriteOptions{Sync: true})
}

// Del removes an existing shortcut from the data store.
func (c *Context) Del(key string) error {
	return c.db.Delete([]byte(key), &opt.WriteOptions{Sync: true})
}

// get everything in the db so dump it out for backup purposes
func (c *Context) GetAll() (map [string]Route, error) {
	golinks := map[string]Route{}
	iter := c.db.NewIterator(nil, nil)
	for iter.Next() {
		key := iter.Key()
		val := iter.Value()
		rt := &Route{}
		if err := rt.read(bytes.NewBuffer(val)); err != nil {
			return nil, err
		}
		golinks[string(key[:])] = *rt
	}

	return golinks, nil
}

func (c *Context) commit(id uint64) error {
	w, err := os.Create(filepath.Join(c.path, idLogFilename))
	if err != nil {
		return err
	}
	defer w.Close()

	if err := binary.Write(w, binary.LittleEndian, id); err != nil {
		return err
	}

	return w.Sync()
}

// NextID generates the next numeric ID to be used for an auto-named shortcut.
func (c *Context) NextID() (uint64, error) {
	c.lck.Lock()
	defer c.lck.Unlock()

	c.id++

	if err := commit(filepath.Join(c.path, idLogFilename), c.id); err != nil {
		return 0, err
	}

	return c.id, nil
}
<commit_msg>Update context.go<commit_after>package context

import (
	"bytes"
	"encoding/binary"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/opt"
)

const (
	routesDbFilename = "routes.db"
	idLogFilename    = "id"
)

// Route is the value part of a shortcut.
type Route struct {
	URL  string    `json:"url"`
	Time time.Time `json:"time"`
}

// Serialize this Route into the given Writer.
func (o *Route) write(w io.Writer) error {
	if err := binary.Write(w, binary.LittleEndian, o.Time.UnixNano()); err != nil {
		return err
	}

	if _, err := w.Write([]byte(o.URL)); err != nil {
		return err
	}

	return nil
}

// Deserialize this Route from the given Reader.
func (o *Route) read(r io.Reader) error {
	var t int64
	if err := binary.Read(r, binary.LittleEndian, &t); err != nil {
		return err
	}

	b, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}

	o.URL = string(b)
	o.Time = time.Unix(0, t)
	return nil
}

// Context provides access to the data store.
type Context struct {
	path string
	db   *leveldb.DB
	lck  sync.Mutex
	id   uint64
}

// Commit the given ID to the data store.
func commit(filename string, id uint64) error {
	w, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer w.Close()

	if err := binary.Write(w, binary.LittleEndian, id); err != nil {
		return err
	}

	return w.Sync()
}

// Load the current ID from the data store.
func load(filename string) (uint64, error) {
	if _, err := os.Stat(filename); err != nil {
		return 0, commit(filename, 0)
	}

	r, err := os.Open(filename)
	if err != nil {
		return 0, err
	}
	defer r.Close()

	var id uint64
	if err := binary.Read(r, binary.LittleEndian, &id); err != nil {
		return 0, err
	}

	return id, nil
}

// Open the context using path as the data store location.
func Open(path string) (*Context, error) {
	if _, err := os.Stat(path); err != nil {
		if err := os.MkdirAll(path, os.ModePerm); err != nil {
			return nil, err
		}
	}

	// open the database
	db, err := leveldb.OpenFile(filepath.Join(path, routesDbFilename), nil)
	if err != nil {
		return nil, err
	}

	id, err := load(filepath.Join(path, idLogFilename))
	if err != nil {
		return nil, err
	}

	return &Context{
		path: path,
		db:   db,
		id:   id,
	}, nil
}

// Close the resources associated with this context.
func (c *Context) Close() error {
	return c.db.Close()
}

// Get retreives a shortcut from the data store.
func (c *Context) Get(name string) (*Route, error) {
	val, err := c.db.Get([]byte(name), nil)
	if err != nil {
		return nil, err
	}

	rt := &Route{}
	if err := rt.read(bytes.NewBuffer(val)); err != nil {
		return nil, err
	}

	return rt, nil
}

// Put stores a new shortcut in the data store.
func (c *Context) Put(key string, rt *Route) error {
	var buf bytes.Buffer
	if err := rt.write(&buf); err != nil {
		return err
	}

	return c.db.Put([]byte(key), buf.Bytes(), &opt.WriteOptions{Sync: true})
}

// Del removes an existing shortcut from the data store.
func (c *Context) Del(key string) error {
	return c.db.Delete([]byte(key), &opt.WriteOptions{Sync: true})
}

// get everything in the db to dump it out for backup purposes
func (c *Context) GetAll() (map [string]Route, error) {
	golinks := map[string]Route{}
	iter := c.db.NewIterator(nil, nil)
	for iter.Next() {
		key := iter.Key()
		val := iter.Value()
		rt := &Route{}
		if err := rt.read(bytes.NewBuffer(val)); err != nil {
			return nil, err
		}
		golinks[string(key[:])] = *rt
	}

	return golinks, nil
}

func (c *Context) commit(id uint64) error {
	w, err := os.Create(filepath.Join(c.path, idLogFilename))
	if err != nil {
		return err
	}
	defer w.Close()

	if err := binary.Write(w, binary.LittleEndian, id); err != nil {
		return err
	}

	return w.Sync()
}

// NextID generates the next numeric ID to be used for an auto-named shortcut.
func (c *Context) NextID() (uint64, error) {
	c.lck.Lock()
	defer c.lck.Unlock()

	c.id++

	if err := commit(filepath.Join(c.path, idLogFilename), c.id); err != nil {
		return 0, err
	}

	return c.id, nil
}
<|endoftext|>


<commit_before>package lisp

import (
	"path"
	"reflect"
)

func builtinImport(sc *scope, ss []sexpr) sexpr {
	if len(ss) != 1 {
		panic("Invalid number of arguments")
	}

	pkgPath, ok := ss[0].(string)
	if !ok {
		panic("Invalid argument")
	}

	pkgName := path.Base(pkgPath)

	// find the package in _go_imports
	pkg, found := _go_imports[pkgPath]
	if !found {
		panic("Package not found")
	}

	// import each item
	for name, _go := range pkg {
		sc.define(sym(pkgName+"."+name), wrapGo(_go))
	}
	return Nil
}

func wrapGo(_go interface{}) sexpr {
	typ := reflect.TypeOf(_go)
	kind := typ.Kind()
	switch kind {
	case reflect.Bool:
		b := _go.(bool)
		if b {
			return float64(1)
		} else {
			return Nil
		}
	case reflect.Int:
		return float64(_go.(int))
	case reflect.Int8:
		return float64(_go.(int8))
	case reflect.Int16:
		return float64(_go.(int16))
	case reflect.Int32:
		return float64(_go.(int32))
	case reflect.Int64:
		return float64(_go.(int64))
	case reflect.Uint:
		return float64(_go.(uint))
	case reflect.Uint8:
		return float64(_go.(uint8))
	case reflect.Uint16:
		return float64(_go.(uint16))
	case reflect.Uint32:
		return float64(_go.(uint32))
	case reflect.Uint64:
		return float64(_go.(uint64))
	case reflect.Uintptr:
		return Nil // TODO
	case reflect.Float32:
		return float64(_go.(float32))
	case reflect.Float64:
		return float64(_go.(float64))
	case reflect.Complex64:
		return Nil // TODO
	case reflect.Complex128:
		return Nil // TODO
	case reflect.Array:
		return Nil // TODO
	case reflect.Chan:
		return Nil // TODO
	case reflect.Func:
		return wrapFunc(_go)
	case reflect.Interface:
		return Nil // TODO
	case reflect.Map:
		return Nil // TODO
	case reflect.Ptr:
		return Nil // TODO
	case reflect.Slice:
		return Nil // TODO
	case reflect.String:
		return _go.(string)
	case reflect.Struct:
		return Nil // TODO
	case reflect.UnsafePointer:
		return Nil // can't handle this
	}
	return Nil
}

func wrapFunc(f interface{}) function {
	// TODO patch reflect so we can do type compatibility-checking
	return func(sc *scope, ss []sexpr) sexpr {
		fun := reflect.ValueOf(f)
		vs := make([]reflect.Value, len(ss))
		for i, s := range ss {
			// TODO convert any cons and function arguments
			vs[i] = reflect.ValueOf(s)
		}
		r := fun.Call(vs)
		return wrapGo(r[0])
	}
}
<commit_msg>Bugfix in compat<commit_after>package lisp

import (
	"path"
	"reflect"
)

func builtinImport(sc *scope, ss []sexpr) sexpr {
	if len(ss) != 1 {
		panic("Invalid number of arguments")
	}

	pkgPath, ok := ss[0].(string)
	if !ok {
		panic("Invalid argument")
	}

	pkgName := path.Base(pkgPath)

	// find the package in _go_imports
	pkg, found := _go_imports[pkgPath]
	if !found {
		panic("Package not found")
	}

	// import each item
	for name, _go := range pkg {
		sc.define(sym(pkgName+"."+name), wrapGo(_go))
	}
	return Nil
}

func wrapGo(_go interface{}) sexpr {
	return wrapGoval(reflect.ValueOf(_go))
}

func wrapGoval(r reflect.Value) sexpr {
	typ := r.Type()
	kind := typ.Kind()
	switch kind {
	case reflect.Bool:
		b := r.Bool()
		if b {
			return float64(1)
		} else {
			return Nil
		}
	case reflect.Int:
		return float64(r.Int())
	case reflect.Int8:
		return float64(r.Int())
	case reflect.Int16:
		return float64(r.Int())
	case reflect.Int32:
		return float64(r.Int())
	case reflect.Int64:
		return float64(r.Int())
	case reflect.Uint:
		return float64(r.Uint())
	case reflect.Uint8:
		return float64(r.Uint())
	case reflect.Uint16:
		return float64(r.Uint())
	case reflect.Uint32:
		return float64(r.Uint())
	case reflect.Uint64:
		return float64(r.Uint())
	case reflect.Uintptr:
		return Nil // TODO
	case reflect.Float32:
		return float64(r.Float())
	case reflect.Float64:
		return float64(r.Float())
	case reflect.Complex64:
		return Nil // TODO
	case reflect.Complex128:
		return Nil // TODO
	case reflect.Array:
		return Nil // TODO
	case reflect.Chan:
		return Nil // TODO
	case reflect.Func:
		return wrapFunc(r.Interface())
	case reflect.Interface:
		return Nil // TODO
	case reflect.Map:
		return Nil // TODO
	case reflect.Ptr:
		return Nil // TODO
	case reflect.Slice:
		return Nil // TODO
	case reflect.String:
		return r.String()
	case reflect.Struct:
		return Nil // TODO
	case reflect.UnsafePointer:
		return Nil // can't handle this
	}
	return Nil
}

func wrapFunc(f interface{}) function {
	// TODO patch reflect so we can do type compatibility-checking
	return func(sc *scope, ss []sexpr) sexpr {
		fun := reflect.ValueOf(f)
		vs := make([]reflect.Value, len(ss))
		for i, s := range ss {
			// TODO convert any cons and function arguments
			vs[i] = reflect.ValueOf(s)
		}
		r := fun.Call(vs)
		if len(r) == 0 {
			return Nil
		}
		return wrapGoval(r[0])
	}
}
<|endoftext|>


<commit_before>package copy

import (
	//"bytes"
	"fmt"
	"net/http"
	//"net/url"
	//"os"
	"reflect"
	"testing"
)

var (
	fileService *FileService
)

func setupFileService(t *testing.T) {
	setup(t)
	fileService = &FileService{client: client}
}

func tearDownFileService() {
	defer tearDown()
}

// Checks if the credentials for the integration tests are set in the env vars
func TestGetTopLevelMeta(t *testing.T) {
	setupFileService(t)
	defer tearDownFileService()
	mux.HandleFunc("/meta",
		func(w http.ResponseWriter, r *http.Request) {
			testMethod(t, r, "GET")
			fmt.Fprint(w,
				`{
               "id":"\/",
               "path":"\/",
               "name":"Copy",
               "type":"root",
               "stub":false,
               "children":[
                  {
                     "name":"Personal Files",
                     "type":"copy",
                     "id":"\/copy",
                     "path":"\/",
                     "stub":true,
                     "counts":{
                        "new":0,
                        "viewed":0,
                        "hidden":0
                     }
                  }
               ],
               "children_count":1,
               "link_name":"link test",
               "token":"32234dsad",
               "permissions":"all",
               "public":true,
               "size":3123123,
               "date_last_synced":32131232,
               "share":true,
               "recipient_confirmed":true,
               "object_available":true,
               "links": [
                   {
                        "id":"link1",
                        "public":true,
                        "expires":true,
                        "expired":true,
                        "url":"dsafdsfdsaxfwf",
                        "url_short":"dsadsad",
                        "recipients": [
                            {
                                "contact_Type":"gfgdfd",
                                "contact_id":"fgffsd",
                                "contact_source":"htgdffvdb",
                                "user_id":"3343",
                                "first_name":"ffgfgf",
                                "last_name":"grfesa",
                                "email":"fsdfdsfds",
                                "permissions":"all",
                                "emails": [
                                     {
                                            "confirmed":true,
                                            "primary":true,
                                            "email":"thomashunter@example.com",
                                            "gravatar":"eca957c6552e783627a0ced1035e1888"
                                    }
                                ]
                            }
                        ],
                        "creator_id":"htgdffsdd",
                        "confirmation_required": true
                    }
               ],
               "revisions": [
                    {
                        "revision_id":"231312",
                        "modified_time":"32324",
                        "size":31232,
                        "latest":true,
                        "conflict":4324,
                        "id":"dsdsd",
                        "type":"sdsad",
                        "creator":{
                            "user_id":"44342",
                            "created_time":323423,
                            "email":"fdfdsf@dsadsa.com",
                            "first_name":"sadasd",
                            "last_name":"sdsadsafds",
                            "confirmed":true
                        }
                    }
                ],
                "url":"dasdsafdasddfdf",
                "revision_id":31312,
                "thumb":"test thumb",
                "thumb_original_dimensions":{
                    "width":32432,
                    "height":53543
                }
            }`)
		},
	)

	fileMeta, _ := fileService.GetTopLevelMeta()

	perfectFileMeta := Meta{
		Id:   "/",
		Path: "/",
		Name: "Copy",
		Type: "root",
		Stub: false,
		Children: []Meta{
			Meta{
				Id:   "/copy",
				Path: "/",
				Name: "Personal Files",
				Type: "copy",
				Stub: true,
				Counts: Count{
					New:    0,
					Viewed: 0,
					Hidden: 0,
				},
			},
		},
		ChildrenCount:      1,
		LinkName:           "link test",
		Token:              "32234dsad",
		Permissions:        "all",
		Public:             true,
		Size:               3123123,
		DateLastSynced:     32131232,
		Share:              true,
		RecipientConfirmed: true,
		ObjectAvailable:    true,
		Links: []Link{
			Link{
				Id:       "link1",
				Public:   true,
				Expires:  true,
				Expired:  true,
				Url:      "dsafdsfdsaxfwf",
				UrlShort: "dsadsad",
				Recipients: []Recipient{
					Recipient{
						ContactType:   "gfgdfd",
						ContactId:     "fgffsd",
						ContactSource: "htgdffvdb",
						UserId:        "3343",
						FirstName:     "ffgfgf",
						LastName:      "grfesa",
						Email:         "fsdfdsfds",
						Permissions:   "all",
						Emails: []Email{
							Email{
								Confirmed: true,
								Primary:   true,
								Email:     "thomashunter@example.com",
								Gravatar:  "eca957c6552e783627a0ced1035e1888",
							},
						},
					},
				},
				CreatorId:            "htgdffsdd",
				ConfirmationRequired: true,
			},
		},
		Revisions: []Revision{
			Revision{
				RevisionId:   "231312",
				ModifiedTime: "32324",
				Size:         31232,
				Latest:       true,
				Conflict:     4324,
				Id:           "dsdsd",
				Type:         "sdsad",
				Creator: Creator{
					UserId:      "44342",
					CreatedTime: 323423,
					Email:       "fdfdsf@dsadsa.com",
					FirstName:   "sadasd",
					LastName:    "sdsadsafds",
					Confirmed:   true,
				},
			},
		},
		Url:        "dasdsafdasddfdf",
		RevisionId: 31312,
		Thumb:      "test thumb",
		ThumbOriginalDimensions: ThumbOriginalDimensions{
			Width:  32432,
			Height: 53543,
		},
	}

	// Are bouth content equal?
	if !reflect.DeepEqual(*fileMeta, perfectFileMeta) {
		t.Errorf("Metas are not equal")
	}

	/*
		//Prepare the neccesary data
		appToken := os.Getenv("APP_TOKEN")
		appSecret := os.Getenv("APP_SECRET")
		accessToken := os.Getenv("ACCESS_TOKEN")
		accessSecret := os.Getenv("ACCESS_SECRET")

		// Create the client
		client, err := NewDefaultClient(appToken, appSecret, accessToken, accessSecret)
		if err != nil {
			fmt.Fprint(os.Stderr, "Could not create the client, review the auth params")
			os.Exit(-1)
		}

		//Create the service (in this case for a user)
		fileService = NewFileService(client)

		//Play with the lib :)
		fileMeta, err := fileService.GetTopLevelMeta()
		if err != nil {
			fmt.Fprint(os.Stderr, "Could not retrieve the user")
			os.Exit(-1)
		}
	*/
	//Print the object with reflection (used for debugging)
	/*val := reflect.ValueOf(fileMeta).Elem()
	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)

		fmt.Printf("%s\t: %v\n", typeField.Name, valueField.Interface())
	}*/
}
<commit_msg>Minor changes in test<commit_after>package copy

import (
	//"bytes"
	"fmt"
	"net/http"
	//"net/url"
	//"os"
	"reflect"
	"testing"
)

var (
	fileService *FileService
)

func setupFileService(t *testing.T) {
	setup(t)
	fileService = &FileService{client: client}
}

func tearDownFileService() {
	defer tearDown()
}

// Checks json decoding for the meta object
func TestJsonMetaDecoding(t *testing.T) {
	setupFileService(t)
	defer tearDownFileService()
	mux.HandleFunc("/meta",
		func(w http.ResponseWriter, r *http.Request) {
			testMethod(t, r, "GET")
			fmt.Fprint(w,
				`{
               "id":"\/",
               "path":"\/",
               "name":"Copy",
               "type":"root",
               "stub":false,
               "children":[
                  {
                     "name":"Personal Files",
                     "type":"copy",
                     "id":"\/copy",
                     "path":"\/",
                     "stub":true,
                     "counts":{
                        "new":0,
                        "viewed":0,
                        "hidden":0
                     }
                  }
               ],
               "children_count":1,
               "link_name":"link test",
               "token":"32234dsad",
               "permissions":"all",
               "public":true,
               "size":3123123,
               "date_last_synced":32131232,
               "share":true,
               "recipient_confirmed":true,
               "object_available":true,
               "links": [
                   {
                        "id":"link1",
                        "public":true,
                        "expires":true,
                        "expired":true,
                        "url":"dsafdsfdsaxfwf",
                        "url_short":"dsadsad",
                        "recipients": [
                            {
                                "contact_Type":"gfgdfd",
                                "contact_id":"fgffsd",
                                "contact_source":"htgdffvdb",
                                "user_id":"3343",
                                "first_name":"ffgfgf",
                                "last_name":"grfesa",
                                "email":"fsdfdsfds",
                                "permissions":"all",
                                "emails": [
                                     {
                                            "confirmed":true,
                                            "primary":true,
                                            "email":"thomashunter@example.com",
                                            "gravatar":"eca957c6552e783627a0ced1035e1888"
                                    }
                                ]
                            }
                        ],
                        "creator_id":"htgdffsdd",
                        "confirmation_required": true
                    }
               ],
               "revisions": [
                    {
                        "revision_id":"231312",
                        "modified_time":"32324",
                        "size":31232,
                        "latest":true,
                        "conflict":4324,
                        "id":"dsdsd",
                        "type":"sdsad",
                        "creator":{
                            "user_id":"44342",
                            "created_time":323423,
                            "email":"fdfdsf@dsadsa.com",
                            "first_name":"sadasd",
                            "last_name":"sdsadsafds",
                            "confirmed":true
                        }
                    }
                ],
                "url":"dasdsafdasddfdf",
                "revision_id":31312,
                "thumb":"test thumb",
                "thumb_original_dimensions":{
                    "width":32432,
                    "height":53543
                }
            }`)
		},
	)

	fileMeta, _ := fileService.GetTopLevelMeta()

	perfectFileMeta := Meta{
		Id:   "/",
		Path: "/",
		Name: "Copy",
		Type: "root",
		Stub: false,
		Children: []Meta{
			Meta{
				Id:   "/copy",
				Path: "/",
				Name: "Personal Files",
				Type: "copy",
				Stub: true,
				Counts: Count{
					New:    0,
					Viewed: 0,
					Hidden: 0,
				},
			},
		},
		ChildrenCount:      1,
		LinkName:           "link test",
		Token:              "32234dsad",
		Permissions:        "all",
		Public:             true,
		Size:               3123123,
		DateLastSynced:     32131232,
		Share:              true,
		RecipientConfirmed: true,
		ObjectAvailable:    true,
		Links: []Link{
			Link{
				Id:       "link1",
				Public:   true,
				Expires:  true,
				Expired:  true,
				Url:      "dsafdsfdsaxfwf",
				UrlShort: "dsadsad",
				Recipients: []Recipient{
					Recipient{
						ContactType:   "gfgdfd",
						ContactId:     "fgffsd",
						ContactSource: "htgdffvdb",
						UserId:        "3343",
						FirstName:     "ffgfgf",
						LastName:      "grfesa",
						Email:         "fsdfdsfds",
						Permissions:   "all",
						Emails: []Email{
							Email{
								Confirmed: true,
								Primary:   true,
								Email:     "thomashunter@example.com",
								Gravatar:  "eca957c6552e783627a0ced1035e1888",
							},
						},
					},
				},
				CreatorId:            "htgdffsdd",
				ConfirmationRequired: true,
			},
		},
		Revisions: []Revision{
			Revision{
				RevisionId:   "231312",
				ModifiedTime: "32324",
				Size:         31232,
				Latest:       true,
				Conflict:     4324,
				Id:           "dsdsd",
				Type:         "sdsad",
				Creator: Creator{
					UserId:      "44342",
					CreatedTime: 323423,
					Email:       "fdfdsf@dsadsa.com",
					FirstName:   "sadasd",
					LastName:    "sdsadsafds",
					Confirmed:   true,
				},
			},
		},
		Url:        "dasdsafdasddfdf",
		RevisionId: 31312,
		Thumb:      "test thumb",
		ThumbOriginalDimensions: ThumbOriginalDimensions{
			Width:  32432,
			Height: 53543,
		},
	}

	// Are bouth content equal?
	if !reflect.DeepEqual(*fileMeta, perfectFileMeta) {
		t.Errorf("Metas are not equal")
	}

	/*
		//Prepare the neccesary data
		appToken := os.Getenv("APP_TOKEN")
		appSecret := os.Getenv("APP_SECRET")
		accessToken := os.Getenv("ACCESS_TOKEN")
		accessSecret := os.Getenv("ACCESS_SECRET")

		// Create the client
		client, err := NewDefaultClient(appToken, appSecret, accessToken, accessSecret)
		if err != nil {
			fmt.Fprint(os.Stderr, "Could not create the client, review the auth params")
			os.Exit(-1)
		}

		//Create the service (in this case for a user)
		fileService = NewFileService(client)

		//Play with the lib :)
		fileMeta, err := fileService.GetTopLevelMeta()
		if err != nil {
			fmt.Fprint(os.Stderr, "Could not retrieve the user")
			os.Exit(-1)
		}
	*/
	//Print the object with reflection (used for debugging)
	/*val := reflect.ValueOf(fileMeta).Elem()
	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)

		fmt.Printf("%s\t: %v\n", typeField.Name, valueField.Interface())
	}*/
}
<|endoftext|>


<commit_before>package control

import (
	"context"
	"encoding/json"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/ondevice/ondevice/config"
	"github.com/ondevice/ondevice/daemon"
	"github.com/ondevice/ondevice/logg"
)

// ControlSocket instance
type ControlSocket struct {
	Daemon *daemon.Daemon

	URL    url.URL
	server http.Server
}

// NewSocket -- Creates a new ControlSocket instance
func NewSocket(d *daemon.Daemon, u url.URL) *ControlSocket {
	var rc = ControlSocket{
		Daemon: d,
		URL:    u,
	}

	var mux = new(http.ServeMux)
	mux.HandleFunc("/state", rc.getState)
	rc.server.Handler = mux

	return &rc
}

// Start -- Starts the ControlSocket (parses the URL )
func (c *ControlSocket) Start() {
	var proto, path string
	var u = c.URL

	// TODO move me to NewSocket()
	if u.Scheme == "unix" || u.Scheme == "" {
		proto = "unix"
		path = u.Path
	} else if u.Scheme == "http" {
		proto = "tcp"
		path = u.Host
	} else {
		logg.Fatal("Failed to parse control socket URL: ", u.String())
	}

	go c.run(proto, path)
}

// Stop -- Stops the ControlSocket
func (c *ControlSocket) Stop() error {
	var ctx, cancelFn = context.WithTimeout(context.Background(), 5*time.Second)
	var err = c.server.Shutdown(ctx)
	if err != nil {
		logg.Error("Failed to stop ControlSocket: ", err)
	} else {
		logg.Info("Stopped ControlSocket")
	}

	cancelFn()
	return err
}

func (c *ControlSocket) run(protocol string, path string) {
	if protocol == "unix" {
		os.Remove(path)
		defer os.Remove(path)
	}

	l, err := net.Listen(protocol, path)
	if err != nil {
		log.Fatal(err)
	}

	if protocol == "unix" {
		os.Chmod(path, 0664)
	}

	err = c.server.Serve(l)
	log.Fatal(err)
}

func (c *ControlSocket) getState(w http.ResponseWriter, req *http.Request) {
	devState := "offline"
	if c.Daemon != nil && c.Daemon.IsOnline {
		devState = "online"
	}

	data := DeviceState{
		Version: config.GetVersion(),
		Device: map[string]string{
			"state": devState,
		},
	}

	data.Device["devId"] = config.GetDeviceID()
	_sendJSON(w, data)
}

func _sendJSON(w http.ResponseWriter, data interface{}) {
	d, err := json.Marshal(data)
	if err != nil {
		logg.Fatal("JSON encode failed: ", data)
	}
	// TODO make sure we're not messing up the encoding

	logg.Debug("Sending JSON response: ", string(d))
	io.WriteString(w, string(d))
}
<commit_msg>control.server: fixed unconditional logg.Fatal() call<commit_after>package control

import (
	"context"
	"encoding/json"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/ondevice/ondevice/config"
	"github.com/ondevice/ondevice/daemon"
	"github.com/ondevice/ondevice/logg"
)

// ControlSocket instance
type ControlSocket struct {
	Daemon *daemon.Daemon

	URL    url.URL
	server http.Server
}

// NewSocket -- Creates a new ControlSocket instance
func NewSocket(d *daemon.Daemon, u url.URL) *ControlSocket {
	var rc = ControlSocket{
		Daemon: d,
		URL:    u,
	}

	var mux = new(http.ServeMux)
	mux.HandleFunc("/state", rc.getState)
	rc.server.Handler = mux

	return &rc
}

// Start -- Starts the ControlSocket (parses the URL )
func (c *ControlSocket) Start() {
	var proto, path string
	var u = c.URL

	// TODO move me to NewSocket()
	if u.Scheme == "unix" || u.Scheme == "" {
		proto = "unix"
		path = u.Path
	} else if u.Scheme == "http" {
		proto = "tcp"
		path = u.Host
	} else {
		logg.Fatal("Failed to parse control socket URL: ", u.String())
	}

	go c.run(proto, path)
}

// Stop -- Stops the ControlSocket
func (c *ControlSocket) Stop() error {
	var ctx, cancelFn = context.WithTimeout(context.Background(), 5*time.Second)
	var err = c.server.Shutdown(ctx)
	if err != nil {
		logg.Error("Failed to stop ControlSocket: ", err)
	} else {
		logg.Info("Stopped ControlSocket")
	}

	cancelFn()
	return err
}

func (c *ControlSocket) run(protocol string, path string) {
	if protocol == "unix" {
		os.Remove(path)
		defer os.Remove(path)
	}

	l, err := net.Listen(protocol, path)
	if err != nil {
		log.Fatal(err)
	}

	if protocol == "unix" {
		os.Chmod(path, 0664)
	}

	err = c.server.Serve(l)
	if err != nil {
		logg.Fatal("Couldn't set up control socket: ", err)
	}
}

func (c *ControlSocket) getState(w http.ResponseWriter, req *http.Request) {
	devState := "offline"
	if c.Daemon != nil && c.Daemon.IsOnline {
		devState = "online"
	}

	data := DeviceState{
		Version: config.GetVersion(),
		Device: map[string]string{
			"state": devState,
		},
	}

	data.Device["devId"] = config.GetDeviceID()
	_sendJSON(w, data)
}

func _sendJSON(w http.ResponseWriter, data interface{}) {
	d, err := json.Marshal(data)
	if err != nil {
		logg.Fatal("JSON encode failed: ", data)
	}
	// TODO make sure we're not messing up the encoding

	logg.Debug("Sending JSON response: ", string(d))
	io.WriteString(w, string(d))
}
<|endoftext|>