<commit_before>package GoMM

import (
	"github.com/stretchr/testify/assert"
	"log"
	"testing"
	"time"
)

// Get clients for the test
func GetClients(t *testing.T, num int, headName string) []client {
	factory := ClientFactory{}

	// Create clients
	clients := make([]client, num)
	clientNames := make([]string, num)
	for i := 0; i < num; i++ {
		clients[i] = factory.NewClient()

		tcpAddr := clients[i].node.GetTCPAddr()
		clientNames[i] = tcpAddr.String()
		log.Println("[DEBUG] Created client", tcpAddr.String())
	}

	// Create channel messengers
	resolverMap := make(map[string]chan Message)
	messengers := GetChannelMessengers(clientNames, resolverMap)

	// Attach chennel messengers to clients
	for i := 0; i < num; i++ {
		clients[i].messenger = messengers[i]
	}

	return clients

}

func TestInteg_ChannelMessenger(t *testing.T) {
	assert := assert.New(t)

	timeout := time.AfterFunc(2000*time.Millisecond, func() {
		panic("TestInteg_ChannelMessenger timed out!")
	})
	defer timeout.Stop()

	headName := "0.0.0.0:7946"
	clients := GetClients(t, 3, headName)

	for i := range clients {
		clients[i].Start()
	}

	clients[1].Join(headName)
	clients[2].Join(headName)
	num_clients := clients[0].NumMembers()
	assert.Equal(num_clients, 3, "Incorrect num of initial clients")

	// Test tracking of active nodes
	assert.Equal(1, clients[0].NumActiveMembers(), "bad initial active members")
	assert.Equal(0, clients[1].NumActiveMembers(), "Not purging active after join")

	// Test tracking of pending nodes
	assert.Equal(2, len(clients[0].pendingMembers), "Not tracking pending members")

	num_active := clients[0].UpdateActiveMembers()
	assert.Equal(3, num_active, "invlaid new number of active members.")

	clients[1].WaitActive()
	clients[2].WaitActive()
	assert.Equal(3, clients[1].NumActiveMembers(), "invlaid new number of active members.")
	assert.Equal(3, clients[2].NumActiveMembers(), "invlaid new number of active members.")

	assert.True(clients[1].IsActive())
	assert.True(clients[2].IsActive())

	clients[1].Close()
	time.Sleep(time.Millisecond * 100)
	assert.Equal(2, clients[0].NumActiveMembers(), "Didn't handle client leaving")
	assert.Equal(2, clients[2].NumActiveMembers(), "Didn't handle client leaving")

}
<commit_msg>Removed testing references to GetLocalClients.<commit_after>package GoMM

import (
	"github.com/stretchr/testify/assert"
	"log"
	"testing"
	"time"
)

// Get clients for the test
func GetLocalClients(num int, headName string) []client {
	factory := ClientFactory{}

	// Create clients
	clients := make([]client, num)
	clientNames := make([]string, num)
	for i := 0; i < num; i++ {
		clients[i] = factory.NewClient()

		tcpAddr := clients[i].node.GetTCPAddr()
		clientNames[i] = tcpAddr.String()
		log.Println("[DEBUG] Created client", tcpAddr.String())
	}

	// Create channel messengers
	resolverMap := make(map[string]chan Message)
	messengers := GetChannelMessengers(clientNames, resolverMap)

	// Attach chennel messengers to clients
	for i := 0; i < num; i++ {
		clients[i].messenger = messengers[i]
	}

	return clients

}

func TestInteg_ChannelMessenger(t *testing.T) {
	assert := assert.New(t)

	timeout := time.AfterFunc(2000*time.Millisecond, func() {
		panic("TestInteg_ChannelMessenger timed out!")
	})
	defer timeout.Stop()

	headName := "0.0.0.0:7946"
	clients := GetLocalClients(3, headName)

	for i := range clients {
		clients[i].Start()
	}

	clients[1].Join(headName)
	clients[2].Join(headName)
	num_clients := clients[0].NumMembers()
	assert.Equal(num_clients, 3, "Incorrect num of initial clients")

	// Test tracking of active nodes
	assert.Equal(1, clients[0].NumActiveMembers(), "bad initial active members")
	assert.Equal(0, clients[1].NumActiveMembers(), "Not purging active after join")

	// Test tracking of pending nodes
	assert.Equal(2, len(clients[0].pendingMembers), "Not tracking pending members")

	num_active := clients[0].UpdateActiveMembers()
	assert.Equal(3, num_active, "invlaid new number of active members.")

	clients[1].WaitActive()
	clients[2].WaitActive()
	assert.Equal(3, clients[1].NumActiveMembers(), "invlaid new number of active members.")
	assert.Equal(3, clients[2].NumActiveMembers(), "invlaid new number of active members.")

	assert.True(clients[1].IsActive())
	assert.True(clients[2].IsActive())

	clients[1].Close()
	time.Sleep(time.Millisecond * 100)
	assert.Equal(2, clients[0].NumActiveMembers(), "Didn't handle client leaving")
	assert.Equal(2, clients[2].NumActiveMembers(), "Didn't handle client leaving")

}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha256"
	"database/sql/driver"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/GuiaBolso/darwin"
	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"
)

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

type cryptoString string

func (c cryptoString) Value() (driver.Value, error) {
	return driver.Value(base64.StdEncoding.EncodeToString([]byte(c))), nil
}

func (c *cryptoString) Scan(src interface{}) error {
	var source string

	switch src.(type) {
	case string:
		source = src.(string)
	case []byte:
		source = string(src.([]byte))
	default:
		return errors.New("Incompatible type for cryptoString")
	}

	decoded, err := base64.StdEncoding.DecodeString(source)

	if err != nil {
		return err
	}

	*c = cryptoString(decoded)

	return nil
}

type person struct {
	ID   uint64 `db:"id"`
	Name string `db:"name"`
}

var (
	migrations = []darwin.Migration{
		{
			Version:     1,
			Description: "Creating table people",
			Script: `CREATE TABLE people (
                        id INTEGER PRIMARY KEY,
                        name       TEXT
                     )`,
		},
		{
			Version:     2,
			Description: "Creating table hashes",
			Script: `CREATE TABLE hashes (
                        id INTEGER PRIMARY KEY,
                        hash TEXT,
                        UNIQUE(hash)
                     )`,
		},
		{
			Version:     3,
			Description: "Creating table hashes_people",
			Script: `CREATE TABLE hashes_people (
                        hash_id INTEGER,
                        person_id INTEGER
                     )`,
		},
	}
)

func addPerson(db *sqlx.DB, p person) error {
	rs, err := db.Exec("INSERT INTO people (name) VALUES (?)", cryptoString(p.Name))

	if err != nil {
		return err
	}

	personID, err := rs.LastInsertId()

	if err != nil {
		return err
	}

	tokens := strings.Split(p.Name, " ")

	for _, token := range tokens {
		crypted := fmt.Sprintf("%x", sha256.Sum256([]byte(strings.TrimSpace(token))))
		row := db.QueryRow("SELECT h.id FROM hashes h WHERE h.hash = ?", crypted)

		if err != nil {
			return err
		}

		var hashID int64

		if err = row.Scan(&hashID); err != nil {
			rs, err = db.Exec("INSERT INTO hashes (hash) VALUES (?)", crypted)
			if err != nil {
				return err
			}

			hashID, err = rs.LastInsertId()

			if err != nil {
				return err
			}
		}

		_, err = db.Exec("INSERT INTO hashes_people (hash_id, person_id) VALUES (?, ?)", hashID, personID)

		if err != nil {
			return err
		}
	}

	return nil
}

func search(db *sqlx.DB, query string) ([]person, error) {
	people := []person{}
	tokens := strings.Split(query, " ")
	cryptoTokens := []string{}

	for _, token := range tokens {
		crypted := fmt.Sprintf("%x", sha256.Sum256([]byte(strings.TrimSpace(token))))
		cryptoTokens = append(cryptoTokens, crypted)
	}

	q, args, err := sqlx.In(`SELECT DISTINCT p.id, p.name
                                 FROM people p
                                 JOIN hashes_people hp ON hp.person_id = p.id
                                 JOIN hashes h ON h.id = hp.hash_id AND h.hash IN (?)
                             GROUP BY p.name`, cryptoTokens)

	if err != nil {
		return people, err
	}

	rows, err := db.Queryx(q, args...)

	if err != nil {
		return people, err
	}

	defer rows.Close()

	for rows.Next() {
		var id uint64
		var name cryptoString

		err := rows.Scan(&id, &name)

		if err != nil {
			return people, err
		}

		people = append(people, person{id, string(name)})
	}

	return people, err
}

func main() {
	//	database, err := sqlx.Connect("sqlite3", "file:database")
	database, err := sqlx.Connect("sqlite3", ":memory:")

	if err != nil {
		log.Fatalf("Error while opening database, %s", err)
	}

	driver := darwin.NewGenericDriver(database.DB, darwin.SqliteDialect{})
	info := make(chan darwin.MigrationInfo)

	go func() {
		for {
			select {
			case i := <-info:
				log.Printf("== %s\n", i.Migration.Description)
			}
		}
	}()

	d := darwin.New(driver, migrations, info)
	err = d.Migrate()

	if err != nil {
		log.Println(err)
	}

	if err := addPerson(database, person{Name: "Claudemiro Alves Feitosa Neto"}); err != nil {
		log.Fatal(err)
	}

	if err := addPerson(database, person{Name: "Antonio Alves Souza Feitosa"}); err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		q := r.URL.Query().Get("q")

		results, err := search(database, q)

		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		w.WriteHeader(http.StatusOK)
		w.Header().Add("Content-Type", "application/json")
		json.NewEncoder(w).Encode(results)
	})

	log.Println("Starting at 8080...")
	http.ListenAndServe(":8080", nil)
}
<commit_msg>Lower case<commit_after>package main

import (
	"crypto/sha256"
	"database/sql/driver"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/GuiaBolso/darwin"
	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"
)

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

type cryptoString string

func (c cryptoString) Value() (driver.Value, error) {
	return driver.Value(base64.StdEncoding.EncodeToString([]byte(c))), nil
}

func (c *cryptoString) Scan(src interface{}) error {
	var source string

	switch src.(type) {
	case string:
		source = src.(string)
	case []byte:
		source = string(src.([]byte))
	default:
		return errors.New("Incompatible type for cryptoString")
	}

	decoded, err := base64.StdEncoding.DecodeString(source)

	if err != nil {
		return err
	}

	*c = cryptoString(decoded)

	return nil
}

type person struct {
	ID   uint64 `db:"id"`
	Name string `db:"name"`
}

var (
	migrations = []darwin.Migration{
		{
			Version:     1,
			Description: "Creating table people",
			Script: `CREATE TABLE people (
                        id INTEGER PRIMARY KEY,
                        name       TEXT
                     )`,
		},
		{
			Version:     2,
			Description: "Creating table hashes",
			Script: `CREATE TABLE hashes (
                        id INTEGER PRIMARY KEY,
                        hash TEXT,
                        UNIQUE(hash)
                     )`,
		},
		{
			Version:     3,
			Description: "Creating table hashes_people",
			Script: `CREATE TABLE hashes_people (
                        hash_id INTEGER,
                        person_id INTEGER
                     )`,
		},
	}
)

func addPerson(db *sqlx.DB, p person) error {
	rs, err := db.Exec("INSERT INTO people (name) VALUES (?)", cryptoString(p.Name))

	if err != nil {
		return err
	}

	personID, err := rs.LastInsertId()

	if err != nil {
		return err
	}

	tokens := strings.Split(p.Name, " ")

	for _, token := range tokens {
		token = strings.TrimSpace(strings.ToLower(token))
		crypted := fmt.Sprintf("%x", sha256.Sum256([]byte(token)))
		row := db.QueryRow("SELECT h.id FROM hashes h WHERE h.hash = ?", crypted)

		if err != nil {
			return err
		}

		var hashID int64

		if err = row.Scan(&hashID); err != nil {
			rs, err = db.Exec("INSERT INTO hashes (hash) VALUES (?)", crypted)
			if err != nil {
				return err
			}

			hashID, err = rs.LastInsertId()

			if err != nil {
				return err
			}
		}

		_, err = db.Exec("INSERT INTO hashes_people (hash_id, person_id) VALUES (?, ?)", hashID, personID)

		if err != nil {
			return err
		}
	}

	return nil
}

func search(db *sqlx.DB, query string) ([]person, error) {
	people := []person{}
	tokens := strings.Split(query, " ")
	cryptoTokens := []string{}

	for _, token := range tokens {
		token = strings.TrimSpace(strings.ToLower(token))
		crypted := fmt.Sprintf("%x", sha256.Sum256([]byte(token)))
		cryptoTokens = append(cryptoTokens, crypted)
	}

	q, args, err := sqlx.In(`SELECT DISTINCT p.id, p.name
                                 FROM people p
                                 JOIN hashes_people hp ON hp.person_id = p.id
                                 JOIN hashes h ON h.id = hp.hash_id AND h.hash IN (?)
                             GROUP BY p.name`, cryptoTokens)

	if err != nil {
		return people, err
	}

	rows, err := db.Queryx(q, args...)

	if err != nil {
		return people, err
	}

	defer rows.Close()

	for rows.Next() {
		var id uint64
		var name cryptoString

		err := rows.Scan(&id, &name)

		if err != nil {
			return people, err
		}

		people = append(people, person{id, string(name)})
	}

	return people, err
}

func main() {
	//	database, err := sqlx.Connect("sqlite3", "file:database")
	database, err := sqlx.Connect("sqlite3", ":memory:")

	if err != nil {
		log.Fatalf("Error while opening database, %s", err)
	}

	driver := darwin.NewGenericDriver(database.DB, darwin.SqliteDialect{})
	info := make(chan darwin.MigrationInfo)

	go func() {
		for {
			select {
			case i := <-info:
				log.Printf("== %s\n", i.Migration.Description)
			}
		}
	}()

	d := darwin.New(driver, migrations, info)
	err = d.Migrate()

	if err != nil {
		log.Println(err)
	}

	if err := addPerson(database, person{Name: "Claudemiro Alves Feitosa Neto"}); err != nil {
		log.Fatal(err)
	}

	if err := addPerson(database, person{Name: "Antonio Alves Souza Feitosa"}); err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		q := r.URL.Query().Get("q")

		results, err := search(database, q)

		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		w.WriteHeader(http.StatusOK)
		w.Header().Add("Content-Type", "application/json")
		json.NewEncoder(w).Encode(results)
	})

	log.Println("Starting at 8080...")
	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>package gocassa

import (
	"time"
)

// Connection exists because one can not connect to a keyspace if it does not exist, thus having a Create on KeySpace is not possible.
// Use ConnectToKeySpace to acquire an instance of KeySpace without getting a Connection.
type Connection interface {
	CreateKeySpace(name string) error
	DropKeySpace(name string) error
	KeySpace(name string) KeySpace
}

// KeySpace is used to obtain tables from.
type KeySpace interface {
	MapTable(tableName, id string, row interface{}) MapTable
	MultimapTable(tableName, fieldToIndexBy, uniqueKey string, row interface{}) MultimapTable
	TimeSeriesTable(tableName, timeField, uniqueKey string, bucketSize time.Duration, row interface{}) TimeSeriesTable
	MultiTimeSeriesTable(tableName, fieldToIndexByField, timeField, uniqueKey string, bucketSize time.Duration, row interface{}) MultiTimeSeriesTable
	Table(tableName string, row interface{}, keys Keys) Table
	// DebugMode enables/disables debug mode depending on the value of the input boolean.
	// When DebugMode is enabled, all built CQL statements are printe to stdout.
	DebugMode(bool)
	// Name returns the keyspace name as in C*
	Name() string
	// Tables returns the name of all configured column families in this keyspace
	Tables() ([]string, error)
	// Exists returns whether the specified column family exists within the keyspace
	Exists(string) (bool, error)
}

//
// Map recipe
//

// MapTable gives you basic CRUD functionality. If you need fancier ways to query your data set have a look at the other tables.
type MapTable interface {
	SetWithOptions(v interface{}, opts Options) Op
	Set(v interface{}) Op
	UpdateWithOptions(id interface{}, m map[string]interface{}, opts Options) Op
	Update(id interface{}, m map[string]interface{}) Op
	Delete(id interface{}) Op
	Read(id, pointer interface{}) Op
	MultiRead(ids []interface{}, pointerToASlice interface{}) Op
	TableChanger
}

//
// Multimap recipe
//

// MultimapTable lets you list rows based on a field equality, eg. 'list all sales where seller id = v'.
type MultimapTable interface {
	SetWithOptions(v interface{}, opts Options) Op
	Set(v interface{}) Op
	UpdateWithOptions(v, id interface{}, m map[string]interface{}, opts Options) Op
	Update(v, id interface{}, m map[string]interface{}) Op
	Delete(v, id interface{}) Op
	DeleteAll(v interface{}) Op
	List(v, startId interface{}, limit int, pointerToASlice interface{}) Op
	Read(v, id, pointer interface{}) Op
	MultiRead(v interface{}, ids []interface{}, pointerToASlice interface{}) Op
	TableChanger
}

//
// TimeSeries recipe
//

// TimeSeriesTable lets you list rows which have a field value between two date ranges.
type TimeSeriesTable interface {
	// timeField and idField must be present
	SetWithOptions(v interface{}, opts Options) Op
	Set(v interface{}) Op
	UpdateWithOptions(timeStamp time.Time, id interface{}, m map[string]interface{}, opts Options) Op
	Update(timeStamp time.Time, id interface{}, m map[string]interface{}) Op
	Delete(timeStamp time.Time, id interface{}) Op
	Read(timeStamp time.Time, id, pointer interface{}) Op
	List(start, end time.Time, pointerToASlice interface{}) Op
	TableChanger
}

//
// TimeSeries B recipe
//

// MultiTimeSeriesTable is a cross between TimeSeries and Multimap tables.
type MultiTimeSeriesTable interface {
	// timeField and idField must be present
	SetWithOptions(v interface{}, opts Options) Op
	Set(v interface{}) Op
	UpdateWithOptions(v interface{}, timeStamp time.Time, id interface{}, m map[string]interface{}, opts Options) Op
	Update(v interface{}, timeStamp time.Time, id interface{}, m map[string]interface{}) Op
	Delete(v interface{}, timeStamp time.Time, id interface{}) Op
	Read(v interface{}, timeStamp time.Time, id, pointer interface{}) Op
	List(v interface{}, start, end time.Time, pointerToASlice interface{}) Op
	TableChanger
}

//
// Raw CQL
//

// Query is a subset of a Table intended to be read
type Query interface {
	// Read the results. Make sure you pass in a pointer to a slice.
	Read(pointerToASlice interface{}) Op
	// Read one result. Make sure you pass in a pointer.
	ReadOne(pointer interface{}) Op
	// Limit the number of rows to be returned.
	Limit(int) Query
}

// Filter is a subset of a Table, filtered by Relations.
// You can do writes or reads on a filter.
type Filter interface {
	// Selection modifiers
	Query() Query
	// Updates does a partial update. Use this if you don't want to overwrite your whole row, but you want to modify fields atomically.
	Update(m map[string]interface{}) Op // Probably this is danger zone (can't be implemented efficiently) on a selectuinb with more than 1 document
	// UpdateWithOptions is the same as Update but with extra options.
	UpdateWithOptions(m map[string]interface{}, opts Options) Op
	// Delete all rows matching the filter.
	Delete() Op
}

// Keys is used with the raw CQL Table type. It is implicit when using recipe tables.
type Keys struct {
	PartitionKeys     []string
	ClusteringColumns []string
}

// Op is returned by both read and write methods, you have to run them explicitly to take effect.
// It represents one or more operations.
type Op interface {
	// Run the operation.
	Run() error
	// You do not need this in 95% of the use cases, use Run!
	// Using atomic batched writes (logged batches in Cassandra terminolohu) comes at a high performance cost!
	RunAtomically() error
	// Add an other Op to this one.
	Add(...Op) Op
}

// Danger zone! Do not use this interface unless you really know what you are doing
type TableChanger interface {
	// Create creates the table in the keySpace, but only if it does not exist already.
	// If the table already exists, it returns an error.
	Create() error
	// CreateStatement returns you the CQL query which can be used to create the tably manually in cqlsh
	CreateStatement() (string, error)
	// Recreate drops the table if exists and creates it again.
	// This is useful for test purposes only.
	Recreate() error
	// Name returns the name of the table, as in C*
	Name() string
	//Drop() error
	//CreateIfDoesNotExist() error
}

// Table is the only non-recipe table, it is the "raw CQL table", it lets you do pretty much whatever you want
// with the downside that you have to know what you are doing - eg. you have to know what queries can you make
// on a certain partition key - clustering column combination.
type Table interface {
	// Set Inserts, or Replaces your row with the supplied struct. Be aware that what is not in your struct
	// will be deleted. To only overwrite some of the fields, use Query.Update.
	Set(v interface{}) Op
	// SetWithOptions is the same as set, but with Options, like TTL, see the Options type for details
	SetWithOptions(v interface{}, opts Options) Op
	// Where accepts a bunch of realtions and returns a filter. See the documentation for Relation and Filter to understand what that means.
	Where(relations ...Relation) Filter // Because we provide selections
	// Name returns the underlying table name, as stored in C*
	TableChanger
}

// QueryExecutor actually executes the queries - this is mostly useful for testing/mocking purposes,
// ignore this otherwise. This library is using github.com/gocql/gocql as the query executor by default.
type QueryExecutor interface {
	// Query executes a query and returns the results
	Query(stmt string, params ...interface{}) ([]map[string]interface{}, error)
	// Execute executes a DML query
	Execute(stmt string, params ...interface{}) error
	// ExecuteAtomically executs multiple DML queries with a logged batch
	ExecuteAtomically(stmt []string, params [][]interface{}) error
}

type Counter int
<commit_msg>Add updated constructors to keyspace interface<commit_after>package gocassa

import (
	"time"
)

// Connection exists because one can not connect to a keyspace if it does not exist, thus having a Create on KeySpace is not possible.
// Use ConnectToKeySpace to acquire an instance of KeySpace without getting a Connection.
type Connection interface {
	CreateKeySpace(name string) error
	DropKeySpace(name string) error
	KeySpace(name string) KeySpace
}

// KeySpace is used to obtain tables from.
type KeySpace interface {
	MapTable(tableName, id string, row interface{}) MapTable
	MultimapTable(tableName, fieldToIndexBy, uniqueKey string, row interface{}) MultimapTable
	TimeSeriesTable(tableName, timeField, uniqueKey string, bucketSize time.Duration, row interface{}) TimeSeriesTable
	TimeSeriesTableWithName(tableName, timeField, uniqueKey string, bucketSize time.Duration, row interface{}) TimeSeriesTable
	MultiTimeSeriesTable(tableName, fieldToIndexByField, timeField, uniqueKey string, bucketSize time.Duration, row interface{}) MultiTimeSeriesTable
	MultiTimeSeriesTableWithName(tableName, fieldToIndexByField, timeField, uniqueKey string, bucketSize time.Duration, row interface{}) MultiTimeSeriesTable
	Table(tableName string, row interface{}, keys Keys) Table
	// DebugMode enables/disables debug mode depending on the value of the input boolean.
	// When DebugMode is enabled, all built CQL statements are printe to stdout.
	DebugMode(bool)
	// Name returns the keyspace name as in C*
	Name() string
	// Tables returns the name of all configured column families in this keyspace
	Tables() ([]string, error)
	// Exists returns whether the specified column family exists within the keyspace
	Exists(string) (bool, error)
}

//
// Map recipe
//

// MapTable gives you basic CRUD functionality. If you need fancier ways to query your data set have a look at the other tables.
type MapTable interface {
	SetWithOptions(v interface{}, opts Options) Op
	Set(v interface{}) Op
	UpdateWithOptions(id interface{}, m map[string]interface{}, opts Options) Op
	Update(id interface{}, m map[string]interface{}) Op
	Delete(id interface{}) Op
	Read(id, pointer interface{}) Op
	MultiRead(ids []interface{}, pointerToASlice interface{}) Op
	TableChanger
}

//
// Multimap recipe
//

// MultimapTable lets you list rows based on a field equality, eg. 'list all sales where seller id = v'.
type MultimapTable interface {
	SetWithOptions(v interface{}, opts Options) Op
	Set(v interface{}) Op
	UpdateWithOptions(v, id interface{}, m map[string]interface{}, opts Options) Op
	Update(v, id interface{}, m map[string]interface{}) Op
	Delete(v, id interface{}) Op
	DeleteAll(v interface{}) Op
	List(v, startId interface{}, limit int, pointerToASlice interface{}) Op
	Read(v, id, pointer interface{}) Op
	MultiRead(v interface{}, ids []interface{}, pointerToASlice interface{}) Op
	TableChanger
}

//
// TimeSeries recipe
//

// TimeSeriesTable lets you list rows which have a field value between two date ranges.
type TimeSeriesTable interface {
	// timeField and idField must be present
	SetWithOptions(v interface{}, opts Options) Op
	Set(v interface{}) Op
	UpdateWithOptions(timeStamp time.Time, id interface{}, m map[string]interface{}, opts Options) Op
	Update(timeStamp time.Time, id interface{}, m map[string]interface{}) Op
	Delete(timeStamp time.Time, id interface{}) Op
	Read(timeStamp time.Time, id, pointer interface{}) Op
	List(start, end time.Time, pointerToASlice interface{}) Op
	TableChanger
}

//
// TimeSeries B recipe
//

// MultiTimeSeriesTable is a cross between TimeSeries and Multimap tables.
type MultiTimeSeriesTable interface {
	// timeField and idField must be present
	SetWithOptions(v interface{}, opts Options) Op
	Set(v interface{}) Op
	UpdateWithOptions(v interface{}, timeStamp time.Time, id interface{}, m map[string]interface{}, opts Options) Op
	Update(v interface{}, timeStamp time.Time, id interface{}, m map[string]interface{}) Op
	Delete(v interface{}, timeStamp time.Time, id interface{}) Op
	Read(v interface{}, timeStamp time.Time, id, pointer interface{}) Op
	List(v interface{}, start, end time.Time, pointerToASlice interface{}) Op
	TableChanger
}

//
// Raw CQL
//

// Query is a subset of a Table intended to be read
type Query interface {
	// Read the results. Make sure you pass in a pointer to a slice.
	Read(pointerToASlice interface{}) Op
	// Read one result. Make sure you pass in a pointer.
	ReadOne(pointer interface{}) Op
	// Limit the number of rows to be returned.
	Limit(int) Query
}

// Filter is a subset of a Table, filtered by Relations.
// You can do writes or reads on a filter.
type Filter interface {
	// Selection modifiers
	Query() Query
	// Updates does a partial update. Use this if you don't want to overwrite your whole row, but you want to modify fields atomically.
	Update(m map[string]interface{}) Op // Probably this is danger zone (can't be implemented efficiently) on a selectuinb with more than 1 document
	// UpdateWithOptions is the same as Update but with extra options.
	UpdateWithOptions(m map[string]interface{}, opts Options) Op
	// Delete all rows matching the filter.
	Delete() Op
}

// Keys is used with the raw CQL Table type. It is implicit when using recipe tables.
type Keys struct {
	PartitionKeys     []string
	ClusteringColumns []string
}

// Op is returned by both read and write methods, you have to run them explicitly to take effect.
// It represents one or more operations.
type Op interface {
	// Run the operation.
	Run() error
	// You do not need this in 95% of the use cases, use Run!
	// Using atomic batched writes (logged batches in Cassandra terminolohu) comes at a high performance cost!
	RunAtomically() error
	// Add an other Op to this one.
	Add(...Op) Op
}

// Danger zone! Do not use this interface unless you really know what you are doing
type TableChanger interface {
	// Create creates the table in the keySpace, but only if it does not exist already.
	// If the table already exists, it returns an error.
	Create() error
	// CreateStatement returns you the CQL query which can be used to create the tably manually in cqlsh
	CreateStatement() (string, error)
	// Recreate drops the table if exists and creates it again.
	// This is useful for test purposes only.
	Recreate() error
	// Name returns the name of the table, as in C*
	Name() string
	//Drop() error
	//CreateIfDoesNotExist() error
}

// Table is the only non-recipe table, it is the "raw CQL table", it lets you do pretty much whatever you want
// with the downside that you have to know what you are doing - eg. you have to know what queries can you make
// on a certain partition key - clustering column combination.
type Table interface {
	// Set Inserts, or Replaces your row with the supplied struct. Be aware that what is not in your struct
	// will be deleted. To only overwrite some of the fields, use Query.Update.
	Set(v interface{}) Op
	// SetWithOptions is the same as set, but with Options, like TTL, see the Options type for details
	SetWithOptions(v interface{}, opts Options) Op
	// Where accepts a bunch of realtions and returns a filter. See the documentation for Relation and Filter to understand what that means.
	Where(relations ...Relation) Filter // Because we provide selections
	// Name returns the underlying table name, as stored in C*
	TableChanger
}

// QueryExecutor actually executes the queries - this is mostly useful for testing/mocking purposes,
// ignore this otherwise. This library is using github.com/gocql/gocql as the query executor by default.
type QueryExecutor interface {
	// Query executes a query and returns the results
	Query(stmt string, params ...interface{}) ([]map[string]interface{}, error)
	// Execute executes a DML query
	Execute(stmt string, params ...interface{}) error
	// ExecuteAtomically executs multiple DML queries with a logged batch
	ExecuteAtomically(stmt []string, params [][]interface{}) error
}

type Counter int
<|endoftext|>


<commit_before>package docs_test

import (
	"os"
	"path/filepath"
	"runtime"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"

	"github.com/flant/werf/pkg/testing/utils"
)

type entry struct {
	fixturesDir   string
	extraArgsFunc func() []string
}

var _ = BeforeEach(func() {
	if runtime.GOOS == "windows" {
		Skip("skip on windows")
	}

	stubs.SetEnv("WERF_HELM_HOME", "~/.helm")
	stubs.SetEnv("WERF_LOG_TERMINAL_WIDTH", "100")
})

var itBody = func(entry entry) {
	resolvedExpectationPath, err := filepath.EvalSymlinks(utils.FixturePath(entry.fixturesDir, "expectation"))
	Ω(err).ShouldNot(HaveOccurred())

	utils.CopyIn(resolvedExpectationPath, filepath.Join(testDirPath, "expectation"))

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"init",
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"add", ".",
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"add", "-A",
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"commit", "-m", "+",
	)

	Ω(os.RemoveAll("output_dir")).Should(Succeed())

	werfArgs := []string{"docs", "--dir", filepath.Join(testDirPath, "expectation")}

	if entry.extraArgsFunc != nil {
		werfArgs = append(werfArgs, entry.extraArgsFunc()...)
	}

	utils.RunSucceedCommand(
		testDirPath,
		werfBinPath,
		werfArgs...,
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"add", "-A",
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"diff-index", "--exit-code", "HEAD", "--",
	)
}

var _ = DescribeTable("docs", itBody,
	Entry("docs", entry{
		fixturesDir: "docs",
	}),
	Entry("readme", entry{
		fixturesDir: "readme",
		extraArgsFunc: func() []string {
			readmePath, err := filepath.Abs(utils.FixturePath("readme", "README.md"))
			Ω(err).ShouldNot(HaveOccurred())

			return []string{"--split-readme", "--readme", readmePath}
		},
	}))
<commit_msg>[docs] Disable docs suite for macOS<commit_after>package docs_test

import (
	"os"
	"path/filepath"
	"runtime"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"

	"github.com/flant/werf/pkg/testing/utils"
)

type entry struct {
	fixturesDir   string
	extraArgsFunc func() []string
}

var _ = BeforeEach(func() {
	if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
		Skip("skip on windows")
	}

	stubs.SetEnv("WERF_HELM_HOME", "~/.helm")
	stubs.SetEnv("WERF_LOG_TERMINAL_WIDTH", "100")
})

var itBody = func(entry entry) {
	resolvedExpectationPath, err := filepath.EvalSymlinks(utils.FixturePath(entry.fixturesDir, "expectation"))
	Ω(err).ShouldNot(HaveOccurred())

	utils.CopyIn(resolvedExpectationPath, filepath.Join(testDirPath, "expectation"))

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"init",
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"add", ".",
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"add", "-A",
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"commit", "-m", "+",
	)

	Ω(os.RemoveAll("output_dir")).Should(Succeed())

	werfArgs := []string{"docs", "--dir", filepath.Join(testDirPath, "expectation")}

	if entry.extraArgsFunc != nil {
		werfArgs = append(werfArgs, entry.extraArgsFunc()...)
	}

	utils.RunSucceedCommand(
		testDirPath,
		werfBinPath,
		werfArgs...,
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"add", "-A",
	)

	utils.RunSucceedCommand(
		testDirPath,
		"git",
		"diff-index", "--exit-code", "HEAD", "--",
	)
}

var _ = DescribeTable("docs", itBody,
	Entry("docs", entry{
		fixturesDir: "docs",
	}),
	Entry("readme", entry{
		fixturesDir: "readme",
		extraArgsFunc: func() []string {
			readmePath, err := filepath.Abs(utils.FixturePath("readme", "README.md"))
			Ω(err).ShouldNot(HaveOccurred())

			return []string{"--split-readme", "--readme", readmePath}
		},
	}))
<|endoftext|>


<commit_before>package client

import (
	"context"
	"errors"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/client-go/rest"

	"github.com/coreos-inc/alm/pkg/apis/clusterserviceversion/v1alpha1"
)

type ClusterServiceVersionInterface interface {
	UpdateCSV(csv *v1alpha1.ClusterServiceVersion) (result *v1alpha1.ClusterServiceVersion, err error)
	CreateCSV(csv *v1alpha1.ClusterServiceVersion) (err error)
}

type ClusterServiceVersionClient struct {
	*rest.RESTClient
}

var _ ClusterServiceVersionInterface = &ClusterServiceVersionClient{}

// NewClusterServiceVersionClient creates a client that can interact with the ClusterServiceVersion resource in k8s api
func NewClusterServiceVersionClient(kubeconfig string) (client *ClusterServiceVersionClient, err error) {
	var config *rest.Config
	config, err = getConfig(kubeconfig)
	if err != nil {
		return
	}

	scheme := runtime.NewScheme()
	if err = v1alpha1.AddToScheme(scheme); err != nil {
		return
	}

	config.GroupVersion = &v1alpha1.SchemeGroupVersion
	config.APIPath = "/apis"
	config.ContentType = runtime.ContentTypeJSON
	config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}

	var restClient *rest.RESTClient
	restClient, err = rest.RESTClientFor(config)
	if err != nil {
		return
	}
	return &ClusterServiceVersionClient{restClient}, nil
}

func (c *ClusterServiceVersionClient) UpdateCSV(in *v1alpha1.ClusterServiceVersion) (result *v1alpha1.ClusterServiceVersion, err error) {
	result = &v1alpha1.ClusterServiceVersion{}
	err = c.RESTClient.Put().Context(context.TODO()).
		Namespace(in.Namespace).
		Resource("clusterserviceversion-v1s").
		Name(in.Name).
		Body(in).
		Do().
		Into(result)
	if err != nil {
		err = errors.New("failed to update CR status: " + err.Error())
	}
	return
}

func (c *ClusterServiceVersionClient) CreateCSV(csv *v1alpha1.ClusterServiceVersion) error {
	out := &v1alpha1.ClusterServiceVersion{}
	return c.RESTClient.
		Post().
		Context(context.TODO()).
		Namespace(csv.Namespace).
		Resource("clusterserviceversion-v1s").
		Name(csv.Name).
		Body(csv).
		Do().
		Into(out)
}
<commit_msg>Tiny fix to use a const to set the CSV CRD name<commit_after>package client

import (
	"context"
	"errors"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/client-go/rest"

	"github.com/coreos-inc/alm/pkg/apis/clusterserviceversion/v1alpha1"
)

type ClusterServiceVersionInterface interface {
	UpdateCSV(csv *v1alpha1.ClusterServiceVersion) (result *v1alpha1.ClusterServiceVersion, err error)
	CreateCSV(csv *v1alpha1.ClusterServiceVersion) (err error)
}

type ClusterServiceVersionClient struct {
	*rest.RESTClient
}

var _ ClusterServiceVersionInterface = &ClusterServiceVersionClient{}

// NewClusterServiceVersionClient creates a client that can interact with the ClusterServiceVersion resource in k8s api
func NewClusterServiceVersionClient(kubeconfig string) (client *ClusterServiceVersionClient, err error) {
	var config *rest.Config
	config, err = getConfig(kubeconfig)
	if err != nil {
		return
	}

	scheme := runtime.NewScheme()
	if err = v1alpha1.AddToScheme(scheme); err != nil {
		return
	}

	config.GroupVersion = &v1alpha1.SchemeGroupVersion
	config.APIPath = "/apis"
	config.ContentType = runtime.ContentTypeJSON
	config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}

	var restClient *rest.RESTClient
	restClient, err = rest.RESTClientFor(config)
	if err != nil {
		return
	}
	return &ClusterServiceVersionClient{restClient}, nil
}

func (c *ClusterServiceVersionClient) UpdateCSV(in *v1alpha1.ClusterServiceVersion) (result *v1alpha1.ClusterServiceVersion, err error) {
	result = &v1alpha1.ClusterServiceVersion{}
	err = c.RESTClient.Put().Context(context.TODO()).
		Namespace(in.Namespace).
		Resource(v1alpha1.ClusterServiceVersionCRDName).
		Name(in.Name).
		Body(in).
		Do().
		Into(result)
	if err != nil {
		err = errors.New("failed to update CR status: " + err.Error())
	}
	return
}

func (c *ClusterServiceVersionClient) CreateCSV(csv *v1alpha1.ClusterServiceVersion) error {
	out := &v1alpha1.ClusterServiceVersion{}
	return c.RESTClient.
		Post().
		Context(context.TODO()).
		Namespace(csv.Namespace).
		Resource(v1alpha1.ClusterServiceVersionCRDName).
		Name(csv.Name).
		Body(csv).
		Do().
		Into(out)
}
<|endoftext|>


<commit_before>package fusefrontend

import (
	"context"
	"os"
	"path/filepath"
	"syscall"

	"golang.org/x/sys/unix"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"

	"github.com/rfjakob/gocryptfs/internal/nametransform"
	"github.com/rfjakob/gocryptfs/internal/syscallcompat"
	"github.com/rfjakob/gocryptfs/internal/tlog"
)

// Node is a file or directory in the filesystem tree
// in a gocryptfs mount.
type Node struct {
	fs.Inode
}

// path returns the relative plaintext path of this node
func (n *Node) path() string {
	return n.Path(n.Root())
}

func (n *Node) rootNode() *RootNode {
	return n.Root().Operations().(*RootNode)
}

// Lookup - FUSE call for discovering a file.
func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
	rn := n.rootNode()
	p := filepath.Join(n.path(), name)
	if rn.isFiltered(p) {
		return nil, syscall.EPERM
	}
	dirfd, cName, err := rn.openBackingDir(p)
	if err != nil {
		return nil, fs.ToErrno(err)
	}
	defer syscall.Close(dirfd)
	// Get device number and inode number into `st`
	st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
	if err != nil {
		return nil, fs.ToErrno(err)
	}
	// Get unique inode number
	rn.inoMap.TranslateStat(st)
	out.Attr.FromStat(st)
	// Create child node
	id := fs.StableAttr{
		Mode: uint32(st.Mode),
		Gen:  1,
		Ino:  st.Ino,
	}
	node := &Node{}
	ch := n.NewInode(ctx, node, id)
	return ch, 0
}

// GetAttr - FUSE call for stat()ing a file.
//
// GetAttr is symlink-safe through use of openBackingDir() and Fstatat().
func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
	rn := n.rootNode()
	dirfd, cName, err := rn.openBackingDir(n.path())
	if err != nil {
		return fs.ToErrno(err)
	}
	defer syscall.Close(dirfd)

	st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
	if err != nil {
		return fs.ToErrno(err)
	}
	rn.inoMap.TranslateStat(st)
	out.Attr.FromStat(st)
	return 0
}

// Create - FUSE call. Creates a new file.
//
// Symlink-safe through the use of Openat().
func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
	rn := n.rootNode()
	path := filepath.Join(n.path(), name)
	if rn.isFiltered(path) {
		return nil, nil, 0, syscall.EPERM
	}
	dirfd, cName, err := rn.openBackingDir(path)
	if err != nil {
		return nil, nil, 0, fs.ToErrno(err)
	}
	defer syscall.Close(dirfd)

	fd := -1
	// Make sure context is nil if we don't want to preserve the owner
	if !rn.args.PreserveOwner {
		ctx = nil
	}
	newFlags := rn.mangleOpenFlags(flags)
	// Handle long file name
	if !rn.args.PlaintextNames && nametransform.IsLongContent(cName) {
		// Create ".name"
		err = rn.nameTransform.WriteLongNameAt(dirfd, cName, path)
		if err != nil {
			return nil, nil, 0, fs.ToErrno(err)
		}
		// Create content
		fd, err = syscallcompat.OpenatUserCtx(dirfd, cName, newFlags|syscall.O_CREAT|syscall.O_EXCL, mode, ctx)
		if err != nil {
			nametransform.DeleteLongNameAt(dirfd, cName)
		}
	} else {
		// Create content, normal (short) file name
		fd, err = syscallcompat.OpenatUserCtx(dirfd, cName, newFlags|syscall.O_CREAT|syscall.O_EXCL, mode, ctx)
	}
	if err != nil {
		// xfstests generic/488 triggers this
		if err == syscall.EMFILE {
			var lim syscall.Rlimit
			syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
			tlog.Warn.Printf("Create %q: too many open files. Current \"ulimit -n\": %d", cName, lim.Cur)
		}
		return nil, nil, 0, fs.ToErrno(err)
	}

	// Get device number and inode number into `st`
	st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
	if err != nil {
		return nil, nil, 0, fs.ToErrno(err)
	}
	// Get unique inode number
	rn.inoMap.TranslateStat(st)
	out.Attr.FromStat(st)
	// Create child node
	id := fs.StableAttr{
		Mode: uint32(st.Mode),
		Gen:  1,
		Ino:  st.Ino,
	}
	node := &Node{}
	ch := n.NewInode(ctx, node, id)

	f := os.NewFile(uintptr(fd), cName)
	return ch, NewFile2(f, rn, st), 0, 0
}

// Unlink - FUSE call. Delete a file.
//
// Symlink-safe through use of Unlinkat().
func (n *Node) Unlink(ctx context.Context, name string) syscall.Errno {
	rn := n.rootNode()
	p := filepath.Join(n.path(), name)
	if rn.isFiltered(p) {
		return syscall.EPERM
	}
	dirfd, cName, err := rn.openBackingDir(p)
	if err != nil {
		return fs.ToErrno(err)
	}
	defer syscall.Close(dirfd)
	// Delete content
	err = syscallcompat.Unlinkat(dirfd, cName, 0)
	if err != nil {
		return fs.ToErrno(err)
	}
	// Delete ".name" file
	if !rn.args.PlaintextNames && nametransform.IsLongContent(cName) {
		err = nametransform.DeleteLongNameAt(dirfd, cName)
		if err != nil {
			tlog.Warn.Printf("Unlink: could not delete .name file: %v", err)
		}
	}
	return fs.ToErrno(err)
}

// Readlink - FUSE call.
//
// Symlink-safe through openBackingDir() + Readlinkat().
func (n *Node) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
	rn := n.rootNode()
	p := n.path()
	if rn.isFiltered(p) {
		return nil, syscall.EPERM
	}
	dirfd, cName, err := rn.openBackingDir(p)
	if err != nil {
		return nil, fs.ToErrno(err)
	}
	defer syscall.Close(dirfd)

	cTarget, err := syscallcompat.Readlinkat(dirfd, cName)
	if err != nil {
		return nil, fs.ToErrno(err)
	}
	if rn.args.PlaintextNames {
		return []byte(cTarget), 0
	}
	// Symlinks are encrypted like file contents (GCM) and base64-encoded
	target, err := rn.decryptSymlinkTarget(cTarget)
	if err != nil {
		tlog.Warn.Printf("Readlink %q: decrypting target failed: %v", cName, err)
		return nil, syscall.EIO
	}
	return []byte(target), 0
}
<commit_msg>v2api: add prepareAtSyscall helper<commit_after>package fusefrontend

import (
	"context"
	"os"
	"path/filepath"
	"syscall"

	"golang.org/x/sys/unix"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"

	"github.com/rfjakob/gocryptfs/internal/nametransform"
	"github.com/rfjakob/gocryptfs/internal/syscallcompat"
	"github.com/rfjakob/gocryptfs/internal/tlog"
)

// Node is a file or directory in the filesystem tree
// in a gocryptfs mount.
type Node struct {
	fs.Inode
}

// path returns the relative plaintext path of this node
func (n *Node) path() string {
	return n.Path(n.Root())
}

// rootNode returns the Root Node of the filesystem.
func (n *Node) rootNode() *RootNode {
	return n.Root().Operations().(*RootNode)
}

// prepareAtSyscall returns a (dirfd, cName) pair that can be used
// with the "___at" family of system calls (openat, fstatat, unlinkat...) to
// access the backing encrypted directory.
//
// If you pass a `child` file name, the (dirfd, cName) pair will refer to
// a child of this node.
// If `child` is empty, the (dirfd, cName) pair refers to this node itself.
func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) {
	p := n.path()
	if child != "" {
		p = filepath.Join(p, child)
	}
	rn := n.rootNode()
	if rn.isFiltered(p) {
		errno = syscall.EPERM
		return
	}
	dirfd, cName, err := rn.openBackingDir(p)
	if err != nil {
		errno = fs.ToErrno(err)
	}
	return
}

// Lookup - FUSE call for discovering a file.
func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
	dirfd, cName, errno := n.prepareAtSyscall(name)
	if errno != 0 {
		return
	}
	defer syscall.Close(dirfd)

	// Get device number and inode number into `st`
	st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
	if err != nil {
		return nil, fs.ToErrno(err)
	}
	// Get unique inode number
	n.rootNode().inoMap.TranslateStat(st)
	out.Attr.FromStat(st)
	// Create child node
	id := fs.StableAttr{
		Mode: uint32(st.Mode),
		Gen:  1,
		Ino:  st.Ino,
	}
	node := &Node{}
	ch = n.NewInode(ctx, node, id)
	return ch, 0
}

// GetAttr - FUSE call for stat()ing a file.
//
// GetAttr is symlink-safe through use of openBackingDir() and Fstatat().
func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) (errno syscall.Errno) {
	dirfd, cName, errno := n.prepareAtSyscall("")
	if errno != 0 {
		return
	}
	defer syscall.Close(dirfd)

	st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
	if err != nil {
		return fs.ToErrno(err)
	}
	n.rootNode().inoMap.TranslateStat(st)
	out.Attr.FromStat(st)
	return 0
}

// Create - FUSE call. Creates a new file.
//
// Symlink-safe through the use of Openat().
func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
	dirfd, cName, errno := n.prepareAtSyscall(name)
	if errno != 0 {
		return
	}
	defer syscall.Close(dirfd)

	var err error
	fd := -1
	// Make sure context is nil if we don't want to preserve the owner
	rn := n.rootNode()
	if !rn.args.PreserveOwner {
		ctx = nil
	}
	newFlags := rn.mangleOpenFlags(flags)
	// Handle long file name
	if !rn.args.PlaintextNames && nametransform.IsLongContent(cName) {
		// Create ".name"
		err = rn.nameTransform.WriteLongNameAt(dirfd, cName, name)
		if err != nil {
			return nil, nil, 0, fs.ToErrno(err)
		}
		// Create content
		fd, err = syscallcompat.OpenatUserCtx(dirfd, cName, newFlags|syscall.O_CREAT|syscall.O_EXCL, mode, ctx)
		if err != nil {
			nametransform.DeleteLongNameAt(dirfd, cName)
		}
	} else {
		// Create content, normal (short) file name
		fd, err = syscallcompat.OpenatUserCtx(dirfd, cName, newFlags|syscall.O_CREAT|syscall.O_EXCL, mode, ctx)
	}
	if err != nil {
		// xfstests generic/488 triggers this
		if err == syscall.EMFILE {
			var lim syscall.Rlimit
			syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
			tlog.Warn.Printf("Create %q: too many open files. Current \"ulimit -n\": %d", cName, lim.Cur)
		}
		return nil, nil, 0, fs.ToErrno(err)
	}

	// Get device number and inode number into `st`
	st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
	if err != nil {
		return nil, nil, 0, fs.ToErrno(err)
	}
	// Get unique inode number
	rn.inoMap.TranslateStat(st)
	out.Attr.FromStat(st)
	// Create child node
	id := fs.StableAttr{
		Mode: uint32(st.Mode),
		Gen:  1,
		Ino:  st.Ino,
	}
	node := &Node{}
	ch := n.NewInode(ctx, node, id)

	f := os.NewFile(uintptr(fd), cName)
	return ch, NewFile2(f, rn, st), 0, 0
}

// Unlink - FUSE call. Delete a file.
//
// Symlink-safe through use of Unlinkat().
func (n *Node) Unlink(ctx context.Context, name string) (errno syscall.Errno) {
	dirfd, cName, errno := n.prepareAtSyscall(name)
	if errno != 0 {
		return
	}
	defer syscall.Close(dirfd)

	// Delete content
	err := syscallcompat.Unlinkat(dirfd, cName, 0)
	if err != nil {
		return fs.ToErrno(err)
	}
	// Delete ".name" file
	if !n.rootNode().args.PlaintextNames && nametransform.IsLongContent(cName) {
		err = nametransform.DeleteLongNameAt(dirfd, cName)
		if err != nil {
			tlog.Warn.Printf("Unlink: could not delete .name file: %v", err)
		}
	}
	return fs.ToErrno(err)
}

// Readlink - FUSE call.
//
// Symlink-safe through openBackingDir() + Readlinkat().
func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {
	dirfd, cName, errno := n.prepareAtSyscall("")
	if errno != 0 {
		return
	}
	defer syscall.Close(dirfd)

	cTarget, err := syscallcompat.Readlinkat(dirfd, cName)
	if err != nil {
		return nil, fs.ToErrno(err)
	}
	rn := n.rootNode()
	if rn.args.PlaintextNames {
		return []byte(cTarget), 0
	}
	// Symlinks are encrypted like file contents (GCM) and base64-encoded
	target, err := rn.decryptSymlinkTarget(cTarget)
	if err != nil {
		tlog.Warn.Printf("Readlink %q: decrypting target failed: %v", cName, err)
		return nil, syscall.EIO
	}
	return []byte(target), 0
}
<|endoftext|>


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

// Package handlers 用于处理节点下与处理函数相关的逻辑
package handlers

import (
	"fmt"
	"net/http"
	"sort"
)

type optionsState int8

// 表示对 OPTIONAL 请求中 Allow 报头中输出内容的处理方式。
const (
	optionsStateDefault      optionsState = iota // 默认情况
	optionsStateFixedString                      // 设置为固定的字符串
	optionsStateFixedHandler                     // 设置为固定的 http.Handler
	optionsStateDisable                          // 禁用，不会自动生 optionAllow 的值
)

type headState int8

// 表示对 HEAD 请求是否根据 GET 请求自动生成。
const (
	headStateDefault headState = iota // 不作任何额外操作
	headStateAuto                     // 自动生成，有 GET 就生成，作为默认值
	headStateFixed                    // 有固定的 Handler
)

// Handlers 用于表示某节点下各个请求方法对应的处理函数。
type Handlers struct {
	handlers map[methodType]http.Handler // 请求方法及其对应的 http.Handler

	optionsAllow string       // 缓存的 OPTIONS 请求的 allow 报头内容。
	optionsState optionsState // OPTIONS 请求的处理方式
	headState    headState
}

// New 声明一个新的 Handlers 实例
// disableHead 是否自动添加 HEAD 请求内容。
func New(disableOptions, disableHead bool) *Handlers {
	ret := &Handlers{
		handlers:     make(map[methodType]http.Handler, 4), // 大部分不会超过 4 条数据
		optionsState: optionsStateDefault,
		headState:    headStateDefault,
	}

	if !disableHead {
		ret.headState = headStateAuto
	}

	if disableOptions {
		ret.optionsState = optionsStateDisable
	} else {
		ret.handlers[options] = http.HandlerFunc(ret.optionsServeHTTP)
		ret.optionsAllow = ret.getOptionsAllow()
	}

	return ret
}

// Add 添加一个处理函数
func (hs *Handlers) Add(h http.Handler, methods ...string) error {
	if len(methods) == 0 {
		methods = addAny
	}

	for _, m := range methods {
		i, found := methodMap[m]
		if !found {
			return fmt.Errorf("不支持的请求方法 %s", m)
		}

		if err := hs.addSingle(h, i); err != nil {
			return err
		}
	}

	return nil
}

func (hs *Handlers) addSingle(h http.Handler, m methodType) error {
	switch m {
	case options:
		if hs.optionsState == optionsStateFixedHandler { // 被强制修改过，不能再受理。
			return fmt.Errorf("该请求方法 %s 已经存在", optionsStrings[m])
		}

		hs.handlers[m] = h
		hs.optionsState = optionsStateFixedHandler
	case head:
		if hs.headState == headStateFixed {
			return fmt.Errorf("该请求方法 %s 已经存在", optionsStrings[m])
		}

		hs.handlers[m] = h
		hs.headState = headStateFixed
	default:
		if _, found := hs.handlers[m]; found {
			return fmt.Errorf("该请求方法 %s 已经存在", optionsStrings[m])
		}
		hs.handlers[m] = h

		// GET 请求，且状态为 Auto 的时候，可以自动添加
		if m == get && hs.headState == headStateAuto {
			hs.handlers[head] = hs.headServeHTTP(h)
		}

		// 重新生成 optionsAllow 字符串
		if hs.optionsState == optionsStateDefault {
			hs.optionsAllow = hs.getOptionsAllow()
		}
	}
	return nil
}

func (hs *Handlers) optionsServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Allow", hs.optionsAllow)
}

func (hs *Handlers) headServeHTTP(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		h.ServeHTTP(&response{ResponseWriter: w}, r)
	})
}

func (hs *Handlers) getOptionsAllow() string {
	var index methodType
	for method := range hs.handlers {
		index += method
	}
	return optionsStrings[index]
}

// Remove 移除某个请求方法对应的处理函数。
// 返回值表示是否已经被清空。
func (hs *Handlers) Remove(methods ...string) bool {
	if len(methods) == 0 {
		hs.handlers = make(map[methodType]http.Handler, 8)
		hs.optionsAllow = ""
		return true
	}

	for _, m := range methods {
		mm := methodMap[m]
		delete(hs.handlers, mm)

		if mm == options { // 明确要删除 OPTIONS，则将其 optionsState 转为禁止使用
			hs.optionsState = optionsStateDisable
		} else if mm == get && hs.headState == headStateAuto { // 跟随 get 一起删除
			delete(hs.handlers, head)
		}
	}

	// 删完了
	if hs.Len() == 0 {
		hs.optionsAllow = ""
		return true
	}

	// 只有一个 OPTIONS 了，且未经外界强制修改，则将其也一并删除。
	if hs.Len() == 1 &&
		hs.handlers[options] != nil &&
		hs.optionsState == optionsStateDefault {
		delete(hs.handlers, options)
		hs.optionsAllow = ""
		return true
	}

	if hs.optionsState == optionsStateDefault {
		hs.optionsAllow = hs.getOptionsAllow()
	}
	return false
}

// SetAllow 设置 Options 请求头的 Allow 报头。
func (hs *Handlers) SetAllow(optionsAllow string) {
	if hs.optionsState == optionsStateDisable {
		hs.handlers[options] = http.HandlerFunc(hs.optionsServeHTTP)
	}
	hs.optionsAllow = optionsAllow
	hs.optionsState = optionsStateFixedString
}

// Handler 获取指定方法对应的处理函数
func (hs *Handlers) Handler(method string) http.Handler {
	return hs.handlers[methodMap[method]]
}

// Options 获取当前支持的请求方法列表字符串
func (hs *Handlers) Options() string {
	return hs.optionsAllow
}

// Len 获取当前支持请求方法数量
func (hs *Handlers) Len() int {
	return len(hs.handlers)
}

// Methods 当前节点支持的请求方法
func (hs *Handlers) Methods(ignoreHead, ignoreOptions bool) []string {
	methods := make([]string, 0, len(hs.handlers))

LOOP:
	for key := range hs.handlers {
		switch key {
		case options:
			if ignoreOptions && hs.optionsState == optionsStateDefault {
				continue LOOP
			}
		case head:
			if ignoreHead && hs.headState == headStateAuto {
				continue LOOP
			}
		}
		methods = append(methods, methodTypeMap[key])
	}

	sort.Strings(methods)
	return methods
}
<commit_msg>改进 methods 方法<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package handlers 用于处理节点下与处理函数相关的逻辑
package handlers

import (
	"fmt"
	"net/http"
	"sort"
)

type optionsState int8

// 表示对 OPTIONAL 请求中 Allow 报头中输出内容的处理方式。
const (
	optionsStateDefault      optionsState = iota // 默认情况
	optionsStateFixedString                      // 设置为固定的字符串
	optionsStateFixedHandler                     // 设置为固定的 http.Handler
	optionsStateDisable                          // 禁用，不会自动生 optionAllow 的值
)

type headState int8

// 表示对 HEAD 请求是否根据 GET 请求自动生成。
const (
	headStateDefault headState = iota // 不作任何额外操作
	headStateAuto                     // 自动生成，有 GET 就生成，作为默认值
	headStateFixed                    // 有固定的 Handler
)

// Handlers 用于表示某节点下各个请求方法对应的处理函数。
type Handlers struct {
	handlers map[methodType]http.Handler // 请求方法及其对应的 http.Handler

	optionsAllow string       // 缓存的 OPTIONS 请求的 allow 报头内容。
	optionsState optionsState // OPTIONS 请求的处理方式
	headState    headState
}

// New 声明一个新的 Handlers 实例
// disableHead 是否自动添加 HEAD 请求内容。
func New(disableOptions, disableHead bool) *Handlers {
	ret := &Handlers{
		handlers:     make(map[methodType]http.Handler, 4), // 大部分不会超过 4 条数据
		optionsState: optionsStateDefault,
		headState:    headStateDefault,
	}

	if !disableHead {
		ret.headState = headStateAuto
	}

	if disableOptions {
		ret.optionsState = optionsStateDisable
	} else {
		ret.handlers[options] = http.HandlerFunc(ret.optionsServeHTTP)
		ret.optionsAllow = ret.getOptionsAllow()
	}

	return ret
}

// Add 添加一个处理函数
func (hs *Handlers) Add(h http.Handler, methods ...string) error {
	if len(methods) == 0 {
		methods = addAny
	}

	for _, m := range methods {
		i, found := methodMap[m]
		if !found {
			return fmt.Errorf("不支持的请求方法 %s", m)
		}

		if err := hs.addSingle(h, i); err != nil {
			return err
		}
	}

	return nil
}

func (hs *Handlers) addSingle(h http.Handler, m methodType) error {
	switch m {
	case options:
		if hs.optionsState == optionsStateFixedHandler { // 被强制修改过，不能再受理。
			return fmt.Errorf("该请求方法 %s 已经存在", optionsStrings[m])
		}

		hs.handlers[m] = h
		hs.optionsState = optionsStateFixedHandler
	case head:
		if hs.headState == headStateFixed {
			return fmt.Errorf("该请求方法 %s 已经存在", optionsStrings[m])
		}

		hs.handlers[m] = h
		hs.headState = headStateFixed
	default:
		if _, found := hs.handlers[m]; found {
			return fmt.Errorf("该请求方法 %s 已经存在", optionsStrings[m])
		}
		hs.handlers[m] = h

		// GET 请求，且状态为 Auto 的时候，可以自动添加
		if m == get && hs.headState == headStateAuto {
			hs.handlers[head] = hs.headServeHTTP(h)
		}

		// 重新生成 optionsAllow 字符串
		if hs.optionsState == optionsStateDefault {
			hs.optionsAllow = hs.getOptionsAllow()
		}
	}
	return nil
}

func (hs *Handlers) optionsServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Allow", hs.optionsAllow)
}

func (hs *Handlers) headServeHTTP(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		h.ServeHTTP(&response{ResponseWriter: w}, r)
	})
}

func (hs *Handlers) getOptionsAllow() string {
	var index methodType
	for method := range hs.handlers {
		index += method
	}
	return optionsStrings[index]
}

// Remove 移除某个请求方法对应的处理函数。
// 返回值表示是否已经被清空。
func (hs *Handlers) Remove(methods ...string) bool {
	if len(methods) == 0 {
		hs.handlers = make(map[methodType]http.Handler, 8)
		hs.optionsAllow = ""
		return true
	}

	for _, m := range methods {
		mm := methodMap[m]
		delete(hs.handlers, mm)

		if mm == options { // 明确要删除 OPTIONS，则将其 optionsState 转为禁止使用
			hs.optionsState = optionsStateDisable
		} else if mm == get && hs.headState == headStateAuto { // 跟随 get 一起删除
			delete(hs.handlers, head)
		}
	}

	// 删完了
	if hs.Len() == 0 {
		hs.optionsAllow = ""
		return true
	}

	// 只有一个 OPTIONS 了，且未经外界强制修改，则将其也一并删除。
	if hs.Len() == 1 &&
		hs.handlers[options] != nil &&
		hs.optionsState == optionsStateDefault {
		delete(hs.handlers, options)
		hs.optionsAllow = ""
		return true
	}

	if hs.optionsState == optionsStateDefault {
		hs.optionsAllow = hs.getOptionsAllow()
	}
	return false
}

// SetAllow 设置 Options 请求头的 Allow 报头。
func (hs *Handlers) SetAllow(optionsAllow string) {
	if hs.optionsState == optionsStateDisable {
		hs.handlers[options] = http.HandlerFunc(hs.optionsServeHTTP)
	}
	hs.optionsAllow = optionsAllow
	hs.optionsState = optionsStateFixedString
}

// Handler 获取指定方法对应的处理函数
func (hs *Handlers) Handler(method string) http.Handler {
	return hs.handlers[methodMap[method]]
}

// Options 获取当前支持的请求方法列表字符串
func (hs *Handlers) Options() string {
	return hs.optionsAllow
}

// Len 获取当前支持请求方法数量
func (hs *Handlers) Len() int {
	return len(hs.handlers)
}

// Methods 当前节点支持的请求方法
func (hs *Handlers) Methods(ignoreHead, ignoreOptions bool) []string {
	methods := make([]string, 0, len(hs.handlers))

	for key := range hs.handlers {
		if (key == options && ignoreOptions && hs.optionsState == optionsStateDefault) ||
			key == head && ignoreHead && hs.headState == headStateAuto {
			continue
		}

		methods = append(methods, methodTypeMap[key])
	}

	sort.Strings(methods)
	return methods
}
<|endoftext|>


<commit_before>package tracing

import (
	"context"
	"net/http"
	"time"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/infra/log/level"
	"github.com/grafana/grafana/pkg/setting"
	"go.etcd.io/etcd/version"
	jaegerpropagator "go.opentelemetry.io/contrib/propagators/jaeger"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/exporters/jaeger"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	tracesdk "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
	trace "go.opentelemetry.io/otel/trace"
)

const (
	jaegerExporter   string = "jaeger"
	otlpExporter     string = "otlp"
	jaegerPropagator string = "jaeger"
	w3cPropagator    string = "w3c"
)

type Tracer interface {
	Run(context.Context) error
	Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, Span)
	Inject(context.Context, http.Header, Span)
}

type Span interface {
	End()
	SetAttributes(key string, value interface{}, kv attribute.KeyValue)
	SetName(name string)
	SetStatus(code codes.Code, description string)
	RecordError(err error, options ...trace.EventOption)
	AddEvents(keys []string, values []EventValue)
}

type Opentelemetry struct {
	enabled     string
	address     string
	propagation string
	log         log.Logger

	tracerProvider *tracesdk.TracerProvider
	tracer         trace.Tracer

	Cfg *setting.Cfg
}

type OpentelemetrySpan struct {
	span trace.Span
}

type EventValue struct {
	Str string
	Num int64
}

type otelErrHandler func(err error)

func (o otelErrHandler) Handle(err error) {
	o(err)
}

func (ots *Opentelemetry) parseSettingsOpentelemetry() error {
	section, err := ots.Cfg.Raw.GetSection("tracing.opentelemetry.jaeger")
	if err != nil {
		return err
	}

	ots.address = section.Key("address").MustString("")
	if ots.address != "" {
		ots.enabled = jaegerExporter
		return nil
	}
	ots.propagation = section.Key("propagation").MustString("")

	section, err = ots.Cfg.Raw.GetSection("tracing.opentelemetry.otlp")
	if err != nil {
		return err
	}

	ots.address = section.Key("address").MustString("")
	if ots.address != "" {
		ots.enabled = otlpExporter
	}
	ots.propagation = section.Key("propagation").MustString("")

	return nil
}

func (ots *Opentelemetry) initJaegerTracerProvider() (*tracesdk.TracerProvider, error) {
	// Create the Jaeger exporter
	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(ots.address)))
	if err != nil {
		return nil, err
	}

	tp := tracesdk.NewTracerProvider(
		tracesdk.WithBatcher(exp),
		tracesdk.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String("grafana"),
			attribute.String("environment", "production"),
		)),
	)

	return tp, nil
}

func (ots *Opentelemetry) initOTLPTracerProvider() (*tracesdk.TracerProvider, error) {
	client := otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(ots.address), otlptracegrpc.WithInsecure())
	exp, err := otlptrace.New(context.Background(), client)
	if err != nil {
		return nil, err
	}

	res, err := resource.New(
		context.Background(),
		resource.WithAttributes(
			semconv.ServiceNameKey.String("grafana"),
			semconv.ServiceVersionKey.String(version.Version),
		),
		resource.WithProcessRuntimeDescription(),
		resource.WithTelemetrySDK(),
	)
	if err != nil {
		return nil, err
	}

	tp := tracesdk.NewTracerProvider(
		tracesdk.WithBatcher(exp),
		tracesdk.WithSampler(tracesdk.ParentBased(
			tracesdk.AlwaysSample(),
		)),
		tracesdk.WithResource(res),
	)
	return tp, nil
}

func (ots *Opentelemetry) initOpentelemetryTracer() error {
	var tp *tracesdk.TracerProvider
	var err error
	switch ots.enabled {
	case jaegerExporter:
		tp, err = ots.initJaegerTracerProvider()
		if err != nil {
			return err
		}
	case otlpExporter:
		tp, err = ots.initOTLPTracerProvider()
		if err != nil {
			return err
		}
	default:
		ots.log.Error("invalid trace exporter")
	}

	// Register our TracerProvider as the global so any imported
	// instrumentation in the future will default to using it
	// only if tracing is enabled
	if ots.enabled != "" {
		otel.SetTracerProvider(tp)
	}

	switch ots.propagation {
	case w3cPropagator:
		otel.SetTextMapPropagator(propagation.TraceContext{})
	case jaegerPropagator:
		otel.SetTextMapPropagator(jaegerpropagator.Jaeger{})
	default:
		otel.SetTextMapPropagator(propagation.TraceContext{})
	}
	ots.tracerProvider = tp
	ots.tracer = otel.GetTracerProvider().Tracer("component-main")

	return nil
}

func (ots *Opentelemetry) Run(ctx context.Context) error {
	otel.SetErrorHandler(otelErrHandler(func(err error) {
		err = level.Error(ots.log).Log("msg", "OpenTelemetry handler returned an error", "err", err)
		if err != nil {
			ots.log.Error("OpenTelemetry log returning error", err)
		}
	}))
	<-ctx.Done()

	ots.log.Info("Closing tracing")
	if ots.tracerProvider == nil {
		return nil
	}
	ctxShutdown, cancel := context.WithTimeout(ctx, time.Second*5)
	defer cancel()

	if err := ots.tracerProvider.Shutdown(ctxShutdown); err != nil {
		return err
	}

	return nil
}

func (ots *Opentelemetry) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, Span) {
	ctx, span := ots.tracer.Start(ctx, spanName)
	opentelemetrySpan := OpentelemetrySpan{
		span: span,
	}
	ctx = context.WithValue(ctx, traceKey{}, traceValue{span.SpanContext().TraceID().String(), span.SpanContext().IsSampled()})
	return ctx, opentelemetrySpan
}

func (ots *Opentelemetry) Inject(ctx context.Context, header http.Header, _ Span) {
	otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(header))
}

func (s OpentelemetrySpan) End() {
	s.span.End()
}

func (s OpentelemetrySpan) SetAttributes(key string, value interface{}, kv attribute.KeyValue) {
	s.span.SetAttributes(kv)
}

func (s OpentelemetrySpan) SetName(name string) {
	s.span.SetName(name)
}

func (s OpentelemetrySpan) SetStatus(code codes.Code, description string) {
	s.span.SetStatus(code, description)
}

func (s OpentelemetrySpan) RecordError(err error, options ...trace.EventOption) {
	for _, o := range options {
		s.span.RecordError(err, o)
	}
}

func (s OpentelemetrySpan) AddEvents(keys []string, values []EventValue) {
	for i, v := range values {
		if v.Num != 0 {
			s.span.AddEvent(keys[i], trace.WithAttributes(attribute.Key(keys[i]).String(v.Str)))
		}
		if v.Str != "" {
			s.span.AddEvent(keys[i], trace.WithAttributes(attribute.Key(keys[i]).Int64(v.Num)))
		}
	}
}
<commit_msg>Fix: add default nop trace exporter to opentelemetry (#48869)<commit_after>package tracing

import (
	"context"
	"net/http"
	"time"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/infra/log/level"
	"github.com/grafana/grafana/pkg/setting"
	"go.etcd.io/etcd/version"
	jaegerpropagator "go.opentelemetry.io/contrib/propagators/jaeger"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/exporters/jaeger"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	tracesdk "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
	trace "go.opentelemetry.io/otel/trace"
)

const (
	jaegerExporter string = "jaeger"
	otlpExporter   string = "otlp"
	noopExporter   string = "noop"

	jaegerPropagator string = "jaeger"
	w3cPropagator    string = "w3c"
)

type Tracer interface {
	Run(context.Context) error
	Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, Span)
	Inject(context.Context, http.Header, Span)
}

type Span interface {
	End()
	SetAttributes(key string, value interface{}, kv attribute.KeyValue)
	SetName(name string)
	SetStatus(code codes.Code, description string)
	RecordError(err error, options ...trace.EventOption)
	AddEvents(keys []string, values []EventValue)
}

type Opentelemetry struct {
	enabled     string
	address     string
	propagation string
	log         log.Logger

	tracerProvider tracerProvider
	tracer         trace.Tracer

	Cfg *setting.Cfg
}

type tracerProvider interface {
	trace.TracerProvider

	Shutdown(ctx context.Context) error
}

type OpentelemetrySpan struct {
	span trace.Span
}

type EventValue struct {
	Str string
	Num int64
}

type otelErrHandler func(err error)

func (o otelErrHandler) Handle(err error) {
	o(err)
}

type noopTracerProvider struct {
	trace.TracerProvider
}

func (noopTracerProvider) Shutdown(ctx context.Context) error {
	return nil
}

func (ots *Opentelemetry) parseSettingsOpentelemetry() error {
	section, err := ots.Cfg.Raw.GetSection("tracing.opentelemetry.jaeger")
	if err != nil {
		return err
	}
	ots.enabled = noopExporter

	ots.address = section.Key("address").MustString("")
	if ots.address != "" {
		ots.enabled = jaegerExporter
		return nil
	}
	ots.propagation = section.Key("propagation").MustString("")

	section, err = ots.Cfg.Raw.GetSection("tracing.opentelemetry.otlp")
	if err != nil {
		return err
	}

	ots.address = section.Key("address").MustString("")
	if ots.address != "" {
		ots.enabled = otlpExporter
	}
	ots.propagation = section.Key("propagation").MustString("")
	return nil
}

func (ots *Opentelemetry) initJaegerTracerProvider() (*tracesdk.TracerProvider, error) {
	// Create the Jaeger exporter
	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(ots.address)))
	if err != nil {
		return nil, err
	}

	tp := tracesdk.NewTracerProvider(
		tracesdk.WithBatcher(exp),
		tracesdk.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String("grafana"),
			attribute.String("environment", "production"),
		)),
	)

	return tp, nil
}

func (ots *Opentelemetry) initOTLPTracerProvider() (*tracesdk.TracerProvider, error) {
	client := otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(ots.address), otlptracegrpc.WithInsecure())
	exp, err := otlptrace.New(context.Background(), client)
	if err != nil {
		return nil, err
	}

	res, err := resource.New(
		context.Background(),
		resource.WithAttributes(
			semconv.ServiceNameKey.String("grafana"),
			semconv.ServiceVersionKey.String(version.Version),
		),
		resource.WithProcessRuntimeDescription(),
		resource.WithTelemetrySDK(),
	)
	if err != nil {
		return nil, err
	}

	tp := tracesdk.NewTracerProvider(
		tracesdk.WithBatcher(exp),
		tracesdk.WithSampler(tracesdk.ParentBased(
			tracesdk.AlwaysSample(),
		)),
		tracesdk.WithResource(res),
	)
	return tp, nil
}

func (ots *Opentelemetry) initNoopTracerProvider() (tracerProvider, error) {
	return &noopTracerProvider{TracerProvider: trace.NewNoopTracerProvider()}, nil
}

func (ots *Opentelemetry) initOpentelemetryTracer() error {
	var tp tracerProvider
	var err error
	switch ots.enabled {
	case jaegerExporter:
		tp, err = ots.initJaegerTracerProvider()
		if err != nil {
			return err
		}
	case otlpExporter:
		tp, err = ots.initOTLPTracerProvider()
		if err != nil {
			return err
		}
	default:
		tp, err = ots.initNoopTracerProvider()
		if err != nil {
			return err
		}
	}

	// Register our TracerProvider as the global so any imported
	// instrumentation in the future will default to using it
	// only if tracing is enabled
	if ots.enabled != "" {
		otel.SetTracerProvider(tp)
	}

	switch ots.propagation {
	case w3cPropagator:
		otel.SetTextMapPropagator(propagation.TraceContext{})
	case jaegerPropagator:
		otel.SetTextMapPropagator(jaegerpropagator.Jaeger{})
	default:
		otel.SetTextMapPropagator(propagation.TraceContext{})
	}
	ots.tracerProvider = tp
	ots.tracer = otel.GetTracerProvider().Tracer("component-main")

	return nil
}

func (ots *Opentelemetry) Run(ctx context.Context) error {
	otel.SetErrorHandler(otelErrHandler(func(err error) {
		err = level.Error(ots.log).Log("msg", "OpenTelemetry handler returned an error", "err", err)
		if err != nil {
			ots.log.Error("OpenTelemetry log returning error", err)
		}
	}))
	<-ctx.Done()

	ots.log.Info("Closing tracing")
	if ots.tracerProvider == nil {
		return nil
	}
	ctxShutdown, cancel := context.WithTimeout(ctx, time.Second*5)
	defer cancel()

	if err := ots.tracerProvider.Shutdown(ctxShutdown); err != nil {
		return err
	}

	return nil
}

func (ots *Opentelemetry) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, Span) {
	ctx, span := ots.tracer.Start(ctx, spanName)
	opentelemetrySpan := OpentelemetrySpan{
		span: span,
	}
	ctx = context.WithValue(ctx, traceKey{}, traceValue{span.SpanContext().TraceID().String(), span.SpanContext().IsSampled()})
	return ctx, opentelemetrySpan
}

func (ots *Opentelemetry) Inject(ctx context.Context, header http.Header, _ Span) {
	otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(header))
}

func (s OpentelemetrySpan) End() {
	s.span.End()
}

func (s OpentelemetrySpan) SetAttributes(key string, value interface{}, kv attribute.KeyValue) {
	s.span.SetAttributes(kv)
}

func (s OpentelemetrySpan) SetName(name string) {
	s.span.SetName(name)
}

func (s OpentelemetrySpan) SetStatus(code codes.Code, description string) {
	s.span.SetStatus(code, description)
}

func (s OpentelemetrySpan) RecordError(err error, options ...trace.EventOption) {
	for _, o := range options {
		s.span.RecordError(err, o)
	}
}

func (s OpentelemetrySpan) AddEvents(keys []string, values []EventValue) {
	for i, v := range values {
		if v.Num != 0 {
			s.span.AddEvent(keys[i], trace.WithAttributes(attribute.Key(keys[i]).String(v.Str)))
		}
		if v.Str != "" {
			s.span.AddEvent(keys[i], trace.WithAttributes(attribute.Key(keys[i]).Int64(v.Num)))
		}
	}
}
<|endoftext|>


<commit_before>package aggregator

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/traefik/traefik/v2/pkg/config/dynamic"
	"github.com/traefik/traefik/v2/pkg/provider"
	"github.com/traefik/traefik/v2/pkg/safe"
)

func TestProviderAggregator_Provide(t *testing.T) {
	aggregator := ProviderAggregator{
		internalProvider: &providerMock{"internal"},
		fileProvider:     &providerMock{"file"},
		providers: []provider.Provider{
			&providerMock{"salad"},
			&providerMock{"tomato"},
			&providerMock{"onion"},
		},
	}

	cfgCh := make(chan dynamic.Message)
	errCh := make(chan error)
	pool := safe.NewPool(context.Background())

	t.Cleanup(pool.Stop)

	go func() {
		errCh <- aggregator.Provide(cfgCh, pool)
	}()

	// Make sure the file provider is always called first, followed by the internal provider.
	requireReceivedMessageFromProviders(t, cfgCh, []string{"internal"})
	requireReceivedMessageFromProviders(t, cfgCh, []string{"file"})

	// Check if all providers have been called, the order doesn't matter.
	requireReceivedMessageFromProviders(t, cfgCh, []string{"salad", "tomato", "onion"})

	require.NoError(t, <-errCh)
}

// requireReceivedMessageFromProviders makes sure the given providers have emitted a message on the given message channel.
// Providers order is not enforced.
func requireReceivedMessageFromProviders(t *testing.T, cfgCh <-chan dynamic.Message, names []string) {
	t.Helper()

	var msg dynamic.Message
	var receivedMessagesFrom []string

	for range names {
		select {
		case <-time.After(10 * time.Millisecond):
		case msg = <-cfgCh:
			receivedMessagesFrom = append(receivedMessagesFrom, msg.ProviderName)
		}
	}

	require.ElementsMatch(t, names, receivedMessagesFrom)
}

type providerMock struct {
	Name string
}

func (p *providerMock) Init() error {
	return nil
}

func (p *providerMock) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
	configurationChan <- dynamic.Message{
		ProviderName:  p.Name,
		Configuration: &dynamic.Configuration{},
	}

	return nil
}
<commit_msg>Fix aggregator test comment<commit_after>package aggregator

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/traefik/traefik/v2/pkg/config/dynamic"
	"github.com/traefik/traefik/v2/pkg/provider"
	"github.com/traefik/traefik/v2/pkg/safe"
)

func TestProviderAggregator_Provide(t *testing.T) {
	aggregator := ProviderAggregator{
		internalProvider: &providerMock{"internal"},
		fileProvider:     &providerMock{"file"},
		providers: []provider.Provider{
			&providerMock{"salad"},
			&providerMock{"tomato"},
			&providerMock{"onion"},
		},
	}

	cfgCh := make(chan dynamic.Message)
	errCh := make(chan error)
	pool := safe.NewPool(context.Background())

	t.Cleanup(pool.Stop)

	go func() {
		errCh <- aggregator.Provide(cfgCh, pool)
	}()

	// Make sure the internal provider is always called first, followed by the file provider.
	requireReceivedMessageFromProviders(t, cfgCh, []string{"internal"})
	requireReceivedMessageFromProviders(t, cfgCh, []string{"file"})

	// Check if all providers have been called, the order doesn't matter.
	requireReceivedMessageFromProviders(t, cfgCh, []string{"salad", "tomato", "onion"})

	require.NoError(t, <-errCh)
}

// requireReceivedMessageFromProviders makes sure the given providers have emitted a message on the given message channel.
// Providers order is not enforced.
func requireReceivedMessageFromProviders(t *testing.T, cfgCh <-chan dynamic.Message, names []string) {
	t.Helper()

	var msg dynamic.Message
	var receivedMessagesFrom []string

	for range names {
		select {
		case <-time.After(10 * time.Millisecond):
		case msg = <-cfgCh:
			receivedMessagesFrom = append(receivedMessagesFrom, msg.ProviderName)
		}
	}

	require.ElementsMatch(t, names, receivedMessagesFrom)
}

type providerMock struct {
	Name string
}

func (p *providerMock) Init() error {
	return nil
}

func (p *providerMock) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
	configurationChan <- dynamic.Message{
		ProviderName:  p.Name,
		Configuration: &dynamic.Configuration{},
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"

	"github.com/dynport/dgtk/slack"
)

type sendNotification struct {
	Message string   `cli:"arg required"`
	Command []string `cli:"arg required"`

	slackClient *slack.Client
}

func (act *sendNotification) Run() error {
	err := act.initSlack()
	if err != nil {
		return err
	}

	cmd := exec.Command(act.Command[0], act.Command[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = os.Environ()

	switch err = cmd.Run(); err {
	case nil:
		return act.slackClient.SendSuccess("[Success] %s", act.Message)
	default:
		return act.slackClient.SendError("[Failed] %s\n%s", act.Message, err)
	}
}

func (act *sendNotification) initSlack() error {
	cfPath := os.ExpandEnv("${HOME}/.slack.conf")
	if cfPath == "" {
		return fmt.Errorf("failed to read ${HOME} from env")
	}

	fh, err := os.Open(cfPath)
	if err != nil {
		return fmt.Errorf("failed to read slack config file: %s", err)
	}
	defer fh.Close()

	err = json.NewDecoder(fh).Decode(&act.slackClient)
	if err != nil {
		return fmt.Errorf("failed to decode the slack config file: %s", err)
	}

	if act.slackClient.WebhookURL == "" {
		return fmt.Errorf("no slack webhook URL configured")
	}

	return nil
}
<commit_msg>made channel configurable in slack notifier<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"

	"github.com/dynport/dgtk/slack"
)

type sendNotification struct {
	Message string   `cli:"arg required"`
	Command []string `cli:"arg required"`
	Channel string   `cli:"opt -c --channel"`

	slackClient *slack.Client
}

func (act *sendNotification) Run() error {
	err := act.initSlack()
	if err != nil {
		return err
	}

	cmd := exec.Command(act.Command[0], act.Command[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = os.Environ()

	n := &slack.Notification{Attachments: []slack.Attachment{{Text: act.Message}}}

	if act.Channel != "" {
		n.Channel = act.Channel
	}

	switch err = cmd.Run(); err {
	case nil:
		n.Attachments[0].Fallback = "[SUCCESS] " + act.Message
		n.Attachments[0].Color = "good"
	default:
		n.Attachments[0].Fallback = "[FAILURE] " + act.Message
		n.Attachments[0].Color = "danger"
	}

	return act.slackClient.Send(n)
}

func (act *sendNotification) initSlack() error {
	cfPath := os.ExpandEnv("${HOME}/.slack.conf")
	if cfPath == "" {
		return fmt.Errorf("failed to read ${HOME} from env")
	}

	fh, err := os.Open(cfPath)
	if err != nil {
		return fmt.Errorf("failed to read slack config file: %s", err)
	}
	defer fh.Close()

	err = json.NewDecoder(fh).Decode(&act.slackClient)
	if err != nil {
		return fmt.Errorf("failed to decode the slack config file: %s", err)
	}

	if act.slackClient.WebhookURL == "" {
		return fmt.Errorf("no slack webhook URL configured")
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import (
	"errors"
	"io"
	"strings"
	"sync"

	"github.com/oragono/oragono/irc/utils"
)

var (
	errSendQExceeded = errors.New("SendQ exceeded")

	sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n")
)

// Socket represents an IRC socket.
type Socket struct {
	sync.Mutex

	conn IRCConn

	maxSendQBytes int

	// this is a trylock enforcing that only one goroutine can write to `conn` at a time
	writerSemaphore utils.Semaphore

	buffers       [][]byte
	totalLength   int
	closed        bool
	sendQExceeded bool
	finalData     []byte // what to send when we die
	finalized     bool
}

// NewSocket returns a new Socket.
func NewSocket(conn IRCConn, maxSendQBytes int) *Socket {
	result := Socket{
		conn:          conn,
		maxSendQBytes: maxSendQBytes,
	}
	result.writerSemaphore.Initialize(1)
	return &result
}

// Close stops a Socket from being able to send/receive any more data.
func (socket *Socket) Close() {
	socket.Lock()
	socket.closed = true
	socket.Unlock()

	socket.wakeWriter()
}

// Read returns a single IRC line from a Socket.
func (socket *Socket) Read() (string, error) {
	if socket.IsClosed() {
		return "", io.EOF
	}

	lineBytes, err := socket.conn.ReadLine()

	// convert bytes to string
	line := string(lineBytes)

	// read last message properly (such as ERROR/QUIT/etc), just fail next reads/writes
	if err == io.EOF {
		socket.Close()
	}

	if err == io.EOF && strings.TrimSpace(line) != "" {
		// don't do anything
	} else if err == errInvalidUtf8 {
		// pass the data through so we can parse the command at least
		return line, err
	} else if err != nil {
		return "", err
	}

	return line, nil
}

// Write sends the given string out of Socket. Requirements:
// 1. MUST NOT block for macroscopic amounts of time
// 2. MUST NOT reorder messages
// 3. MUST provide mutual exclusion for socket.conn.Write
// 4. SHOULD NOT tie up additional goroutines, beyond the one blocked on socket.conn.Write
func (socket *Socket) Write(data []byte) (err error) {
	if len(data) == 0 {
		return
	}

	socket.Lock()
	if socket.closed {
		err = io.EOF
	} else {
		prospectiveLen := socket.totalLength + len(data)
		if prospectiveLen > socket.maxSendQBytes {
			socket.sendQExceeded = true
			socket.closed = true
			err = errSendQExceeded
		} else {
			socket.buffers = append(socket.buffers, data)
			socket.totalLength = prospectiveLen
		}
	}
	socket.Unlock()

	socket.wakeWriter()
	return
}

// BlockingWrite sends the given string out of Socket. Requirements:
// 1. MUST block until the message is sent
// 2. MUST bypass sendq (calls to BlockingWrite cannot, on their own, cause a sendq overflow)
// 3. MUST provide mutual exclusion for socket.conn.Write
// 4. MUST respect the same ordering guarantees as Write (i.e., if a call to Write that sends
//    message m1 happens-before a call to BlockingWrite that sends message m2,
//    m1 must be sent on the wire before m2
// Callers MUST be writing to the client's socket from the client's own goroutine;
// other callers must use the nonblocking Write call instead. Otherwise, a client
// with a slow/unreliable connection risks stalling the progress of the system as a whole.
func (socket *Socket) BlockingWrite(data []byte) (err error) {
	if len(data) == 0 {
		return
	}

	// after releasing the semaphore, we must check for fresh data, same as `send`
	defer func() {
		if socket.readyToWrite() {
			socket.wakeWriter()
		}
	}()

	// blocking acquire of the trylock
	socket.writerSemaphore.Acquire()
	defer socket.writerSemaphore.Release()

	// first, flush any buffered data, to preserve the ordering guarantees
	closed := socket.performWrite()
	if closed {
		return io.EOF
	}

	err = socket.conn.WriteLine(data)
	if err != nil {
		socket.finalize()
	}
	return
}

// wakeWriter starts the goroutine that actually performs the write, without blocking
func (socket *Socket) wakeWriter() {
	if socket.writerSemaphore.TryAcquire() {
		// acquired the trylock; send() will release it
		go socket.send()
	}
	// else: do nothing, the holder will check for more data after releasing it
}

// SetFinalData sets the final data to send when the SocketWriter closes.
func (socket *Socket) SetFinalData(data []byte) {
	socket.Lock()
	defer socket.Unlock()
	socket.finalData = data
}

// IsClosed returns whether the socket is closed.
func (socket *Socket) IsClosed() bool {
	socket.Lock()
	defer socket.Unlock()
	return socket.closed
}

// is there data to write?
func (socket *Socket) readyToWrite() bool {
	socket.Lock()
	defer socket.Unlock()
	// on the first time observing socket.closed, we still have to write socket.finalData
	return !socket.finalized && (socket.totalLength > 0 || socket.closed)
}

// send actually writes messages to socket.Conn; it may block
func (socket *Socket) send() {
	for {
		// we are holding the trylock: actually do the write
		socket.performWrite()
		// surrender the trylock, avoiding a race where a write comes in after we've
		// checked readyToWrite() and it returned false, but while we still hold the trylock:
		socket.writerSemaphore.Release()
		// check if more data came in while we held the trylock:
		if !socket.readyToWrite() {
			return
		}
		if !socket.writerSemaphore.TryAcquire() {
			// failed to acquire; exit and wait for the holder to observe readyToWrite()
			// after releasing it
			return
		}
		// got the lock again, loop back around and write
	}
}

// write the contents of the buffer, then see if we need to close
// returns whether we closed
func (socket *Socket) performWrite() (closed bool) {
	// retrieve the buffered data, clear the buffer
	socket.Lock()
	buffers := socket.buffers
	socket.buffers = nil
	socket.totalLength = 0
	closed = socket.closed
	socket.Unlock()

	var err error
	if 0 < len(buffers) {
		err = socket.conn.WriteLines(buffers)
	}

	closed = closed || err != nil
	if closed {
		socket.finalize()
	}
	return
}

// mark closed and send final data. you must be holding the semaphore to call this:
func (socket *Socket) finalize() {
	// mark the socket closed (if someone hasn't already), then write error lines
	socket.Lock()
	socket.closed = true
	finalized := socket.finalized
	socket.finalized = true
	finalData := socket.finalData
	if socket.sendQExceeded {
		finalData = sendQExceededMessage
	}
	socket.Unlock()

	if finalized {
		return
	}

	if len(finalData) != 0 {
		socket.conn.WriteLine(finalData)
	}

	// close the connection
	socket.conn.Close()
}
<commit_msg>simplify Socket.Read<commit_after>// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import (
	"errors"
	"io"
	"sync"

	"github.com/oragono/oragono/irc/utils"
)

var (
	errSendQExceeded = errors.New("SendQ exceeded")

	sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n")
)

// Socket represents an IRC socket.
type Socket struct {
	sync.Mutex

	conn IRCConn

	maxSendQBytes int

	// this is a trylock enforcing that only one goroutine can write to `conn` at a time
	writerSemaphore utils.Semaphore

	buffers       [][]byte
	totalLength   int
	closed        bool
	sendQExceeded bool
	finalData     []byte // what to send when we die
	finalized     bool
}

// NewSocket returns a new Socket.
func NewSocket(conn IRCConn, maxSendQBytes int) *Socket {
	result := Socket{
		conn:          conn,
		maxSendQBytes: maxSendQBytes,
	}
	result.writerSemaphore.Initialize(1)
	return &result
}

// Close stops a Socket from being able to send/receive any more data.
func (socket *Socket) Close() {
	socket.Lock()
	socket.closed = true
	socket.Unlock()

	socket.wakeWriter()
}

// Read returns a single IRC line from a Socket.
func (socket *Socket) Read() (string, error) {
	// immediately fail if Close() has been called, even if there's
	// still data in a bufio.Reader or websocket buffer:
	if socket.IsClosed() {
		return "", io.EOF
	}

	lineBytes, err := socket.conn.ReadLine()
	line := string(lineBytes)

	if err == io.EOF {
		socket.Close()
		// process last message properly (such as ERROR/QUIT/etc), just fail next reads/writes
		if line != "" {
			err = nil
		}
	}

	return line, err
}

// Write sends the given string out of Socket. Requirements:
// 1. MUST NOT block for macroscopic amounts of time
// 2. MUST NOT reorder messages
// 3. MUST provide mutual exclusion for socket.conn.Write
// 4. SHOULD NOT tie up additional goroutines, beyond the one blocked on socket.conn.Write
func (socket *Socket) Write(data []byte) (err error) {
	if len(data) == 0 {
		return
	}

	socket.Lock()
	if socket.closed {
		err = io.EOF
	} else {
		prospectiveLen := socket.totalLength + len(data)
		if prospectiveLen > socket.maxSendQBytes {
			socket.sendQExceeded = true
			socket.closed = true
			err = errSendQExceeded
		} else {
			socket.buffers = append(socket.buffers, data)
			socket.totalLength = prospectiveLen
		}
	}
	socket.Unlock()

	socket.wakeWriter()
	return
}

// BlockingWrite sends the given string out of Socket. Requirements:
// 1. MUST block until the message is sent
// 2. MUST bypass sendq (calls to BlockingWrite cannot, on their own, cause a sendq overflow)
// 3. MUST provide mutual exclusion for socket.conn.Write
// 4. MUST respect the same ordering guarantees as Write (i.e., if a call to Write that sends
//    message m1 happens-before a call to BlockingWrite that sends message m2,
//    m1 must be sent on the wire before m2
// Callers MUST be writing to the client's socket from the client's own goroutine;
// other callers must use the nonblocking Write call instead. Otherwise, a client
// with a slow/unreliable connection risks stalling the progress of the system as a whole.
func (socket *Socket) BlockingWrite(data []byte) (err error) {
	if len(data) == 0 {
		return
	}

	// after releasing the semaphore, we must check for fresh data, same as `send`
	defer func() {
		if socket.readyToWrite() {
			socket.wakeWriter()
		}
	}()

	// blocking acquire of the trylock
	socket.writerSemaphore.Acquire()
	defer socket.writerSemaphore.Release()

	// first, flush any buffered data, to preserve the ordering guarantees
	closed := socket.performWrite()
	if closed {
		return io.EOF
	}

	err = socket.conn.WriteLine(data)
	if err != nil {
		socket.finalize()
	}
	return
}

// wakeWriter starts the goroutine that actually performs the write, without blocking
func (socket *Socket) wakeWriter() {
	if socket.writerSemaphore.TryAcquire() {
		// acquired the trylock; send() will release it
		go socket.send()
	}
	// else: do nothing, the holder will check for more data after releasing it
}

// SetFinalData sets the final data to send when the SocketWriter closes.
func (socket *Socket) SetFinalData(data []byte) {
	socket.Lock()
	defer socket.Unlock()
	socket.finalData = data
}

// IsClosed returns whether the socket is closed.
func (socket *Socket) IsClosed() bool {
	socket.Lock()
	defer socket.Unlock()
	return socket.closed
}

// is there data to write?
func (socket *Socket) readyToWrite() bool {
	socket.Lock()
	defer socket.Unlock()
	// on the first time observing socket.closed, we still have to write socket.finalData
	return !socket.finalized && (socket.totalLength > 0 || socket.closed)
}

// send actually writes messages to socket.Conn; it may block
func (socket *Socket) send() {
	for {
		// we are holding the trylock: actually do the write
		socket.performWrite()
		// surrender the trylock, avoiding a race where a write comes in after we've
		// checked readyToWrite() and it returned false, but while we still hold the trylock:
		socket.writerSemaphore.Release()
		// check if more data came in while we held the trylock:
		if !socket.readyToWrite() {
			return
		}
		if !socket.writerSemaphore.TryAcquire() {
			// failed to acquire; exit and wait for the holder to observe readyToWrite()
			// after releasing it
			return
		}
		// got the lock again, loop back around and write
	}
}

// write the contents of the buffer, then see if we need to close
// returns whether we closed
func (socket *Socket) performWrite() (closed bool) {
	// retrieve the buffered data, clear the buffer
	socket.Lock()
	buffers := socket.buffers
	socket.buffers = nil
	socket.totalLength = 0
	closed = socket.closed
	socket.Unlock()

	var err error
	if 0 < len(buffers) {
		err = socket.conn.WriteLines(buffers)
	}

	closed = closed || err != nil
	if closed {
		socket.finalize()
	}
	return
}

// mark closed and send final data. you must be holding the semaphore to call this:
func (socket *Socket) finalize() {
	// mark the socket closed (if someone hasn't already), then write error lines
	socket.Lock()
	socket.closed = true
	finalized := socket.finalized
	socket.finalized = true
	finalData := socket.finalData
	if socket.sendQExceeded {
		finalData = sendQExceededMessage
	}
	socket.Unlock()

	if finalized {
		return
	}

	if len(finalData) != 0 {
		socket.conn.WriteLine(finalData)
	}

	// close the connection
	socket.conn.Close()
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"io"
	"log"
	"net/url"
	"path"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

type Link struct {
	NodeName string
	Text     string
	URL      string
}

func NormalizeString(s string) string {
	return strings.Join(strings.Fields(s), " ")
}

func ImageText(s *goquery.Selection) string {
	alt, _ := s.Attr("alt")
	if alt != "" {
		return alt
	}

	title, _ := s.Attr("title")
	if title != "" {
		return title
	}

	src, ok := s.Attr("src")
	if !ok || src == "" {
		return "NO TEXT"
	}

	imageURL, err := url.Parse(src)
	if err != nil {
		log.Fatal(err)
	}

	return path.Base(imageURL.Path)
}

func FindLinks(file io.Reader) ([]Link, error) {
	doc, err := goquery.NewDocumentFromReader(file)
	if err != nil {
		return nil, err
	}

	var links []Link
	doc.Find("a").Each(func(i int, s *goquery.Selection) {
		url, ok := s.Attr("href")
		if !ok {
			return
		}

		node := goquery.NodeName(s)
		text := NormalizeString(s.Text())

		s.Children().Each(func(i int, s *goquery.Selection) {
			node = goquery.NodeName(s)
			if node == "img" {
				text = ImageText(s)
				return
			}
		})

		if text == "" {
			text = "NO TEXT"
		}

		links = append(links, Link{node, text, url})
	})

	if len(links) == 0 {
		return nil, errors.New("no links found")
	}

	return links, nil
}
<commit_msg>Find URLs in non-HTML files via regex as fallback<commit_after>package main

import (
	"bytes"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"net/url"
	"path"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"mvdan.cc/xurls"
)

type Link struct {
	NodeName string
	Text     string
	URL      string
}

func NormalizeString(s string) string {
	return strings.Join(strings.Fields(s), " ")
}

func ImageText(s *goquery.Selection) string {
	alt, _ := s.Attr("alt")
	if alt != "" {
		return alt
	}

	title, _ := s.Attr("title")
	if title != "" {
		return title
	}

	src, ok := s.Attr("src")
	if !ok || src == "" {
		return "NO TEXT"
	}

	imageURL, err := url.Parse(src)
	if err != nil {
		log.Fatal(err)
	}

	return path.Base(imageURL.Path)
}

func FindLinksHTML(file io.Reader) ([]Link, error) {
	doc, err := goquery.NewDocumentFromReader(file)
	if err != nil {
		return nil, err
	}

	var links []Link
	doc.Find("a").Each(func(i int, s *goquery.Selection) {
		url, ok := s.Attr("href")
		if !ok {
			return
		}

		node := goquery.NodeName(s)
		text := NormalizeString(s.Text())

		s.Children().Each(func(i int, s *goquery.Selection) {
			node = goquery.NodeName(s)
			if node == "img" {
				text = ImageText(s)
				return
			}
		})

		if text == "" {
			text = "NO TEXT"
		}

		links = append(links, Link{node, text, url})
	})

	if len(links) == 0 {
		return nil, errors.New("no links found")
	}

	return links, nil
}

func FindLinksRegEx(file []byte) ([]Link, error) {
	var links []Link
	RegexLinks := xurls.Strict().FindAllString(string(file), -1)
	for _, link := range RegexLinks {
		links = append(links, Link{"", link, link})
	}

	if len(links) == 0 {
		return nil, errors.New("no links found")
	}

	return links, nil
}

func FindLinks(file io.Reader) ([]Link, error) {
	var buf bytes.Buffer
	tee := io.TeeReader(file, &buf)

	links, err := FindLinksHTML(tee)
	if err != nil {
		b, err := ioutil.ReadAll(&buf)
		if err != nil {
			return nil, err
		}

		return FindLinksRegEx(b)
	}

	return links, err
}
<|endoftext|>