<commit_before>package database

import (
	"database/sql"
	"fmt"
	"strings"

	"github.com/omniscale/imposm3/element"
	"github.com/omniscale/imposm3/parser/changeset"
	"github.com/omniscale/imposm3/parser/diff"

	_ "github.com/lib/pq"
)

var initSql = []string{
	`CREATE SCHEMA IF NOT EXISTS "%[1]s";`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".nodes (
    id BIGINT,
    add BOOLEAN,
    modify BOOLEAN,
    delete BOOLEAN,
    changeset INT,
    geometry GEOMETRY(Point, 4326),
    user_name VARCHAR,
    user_id INT,
    timestamp TIMESTAMP,
    version INT,
    tags HSTORE,
    PRIMARY KEY (id, version)
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".ways (
    id INT NOT NULL,
    add BOOLEAN,
    modify BOOLEAN,
    delete BOOLEAN,
    changeset INT,
    user_name VARCHAR,
    user_id INT,
    timestamp TIMESTAMP,
    version INT,
    tags HSTORE,
    PRIMARY KEY (id, version)
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".nds (
    way_id INT NOT NULL,
    way_version INT NOT NULL,
    idx INT,
    node_id BIGINT NOT NULL,
    PRIMARY KEY (way_id, way_version, idx, node_id),
    FOREIGN KEY (way_id, way_version)
        REFERENCES "%[1]s".ways (id, version)
          ON UPDATE CASCADE
          ON DELETE CASCADE
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".relations (
    id INT NOT NULL,
    add BOOLEAN,
    modify BOOLEAN,
    delete BOOLEAN,
    changeset INT,
    geometry GEOMETRY(Point, 4326),
    user_name VARCHAR,
    user_id INT,
    timestamp TIMESTAMP,
    version INT,
    tags HSTORE,
    PRIMARY KEY (id, version)
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".members (
    relation_id INT NOT NULL,
    relation_version INT,
    type VARCHAR,
    role VARCHAR,
    idx INT,
    member_node_id BIGINT,
    member_way_id INT,
    member_relation_id INT,
    FOREIGN KEY (relation_id, relation_version)
        REFERENCES "%[1]s".relations (id, version)
          ON UPDATE CASCADE
          ON DELETE CASCADE
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".changesets (
    id INT NOT NULL,
    created_at TIMESTAMP,
    closed_at TIMESTAMP,
    num_changes INT,
    open BOOLEAN,
    user_name VARCHAR,
    user_id INT,
    tags HSTORE,
    bbox Geometry(POLYGON, 4326),
    PRIMARY KEY (id)
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".comments (
    changeset_id BIGINT NOT NULL,
    idx INT,
    user_name VARCHAR,
    user_id INT,
    timestamp TIMESTAMP,
    text VARCHAR,
    PRIMARY KEY (changeset_id, idx),
    FOREIGN KEY (changeset_id)
        REFERENCES "%[1]s".changesets (id)
          ON UPDATE CASCADE
          ON DELETE CASCADE
);`,
}

type PostGIS struct {
	db               *sql.DB
	tx               *sql.Tx
	nodeStmt         *sql.Stmt
	wayStmt          *sql.Stmt
	ndsStmt          *sql.Stmt
	relStmt          *sql.Stmt
	memberStmt       *sql.Stmt
	changeStmt       *sql.Stmt
	changeUpdateStmt *sql.Stmt
	commentStmt      *sql.Stmt
	schema           string
}

func NewPostGIS(params string, schema string) (*PostGIS, error) {
	db, err := sql.Open("postgres", params)
	if err != nil {
		return nil, err
	}

	if schema == "" {
		schema = "public"
	}

	return &PostGIS{
		db:     db,
		schema: schema,
	}, nil
}

func newSqlError(err error, elem interface{}) error {
	return &sqlError{elem: elem, err: err}
}

type sqlError struct {
	elem interface{}
	err  error
}

func (s *sqlError) Error() string {
	return fmt.Sprintf("error: %s; for %#v", s.err, s.elem)

}

func (p *PostGIS) Init() error {
	tx, err := p.db.Begin()
	if err != nil {
		return err
	}
	for _, s := range initSql {
		stmt := fmt.Sprintf(s, p.schema)
		if _, err := tx.Exec(stmt); err != nil {
			tx.Rollback()
			return fmt.Errorf("error while calling %v: %v", stmt, err)
		}
	}
	return tx.Commit()
}

func (p *PostGIS) Begin() error {
	var err error
	p.tx, err = p.db.Begin()
	if err != nil {
		return err
	}
	nodeStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".nodes (
            id,
            add,
            modify,
            delete,
            geometry,
            user_name,
            user_id,
            timestamp,
            version,
            changeset,
            tags) VALUES ($1, $2, $3, $4, ST_SetSRID(ST_Point($5, $6), 4326), $7, $8, $9, $10, $11, $12)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.nodeStmt = nodeStmt

	wayStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".ways (
            id,
            add,
            modify,
            delete,
            user_name,
            user_id,
            timestamp,
            version,
            changeset,
            tags
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.wayStmt = wayStmt

	ndsStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".nds (
            way_id,
            way_version,
            idx,
            node_id
        ) VALUES ($1, $2, $3, $4)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.ndsStmt = ndsStmt

	relStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".relations (
            id,
            add,
            modify,
            delete,
            user_name,
            user_id,
            timestamp,
            version,
            changeset,
            tags
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.relStmt = relStmt

	memberStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".members (
            relation_id,
            relation_version,
            type,
            role,
            idx,
            member_node_id,
            member_way_id,
            member_relation_id
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.memberStmt = memberStmt

	changeStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".changesets (
            id,
            created_at,
            closed_at,
            open,
            num_changes,
            user_name,
            user_id,
            bbox,
            tags
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, ST_GeomFromText($8), $9)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.changeStmt = changeStmt

	changeUpdateStmt, err := p.tx.Prepare(
		fmt.Sprintf(`UPDATE "%[1]s".changesets SET
            created_at=$2,
            closed_at=$3,
            open=$4,
            num_changes=$5,
            user_name=$6,
            user_id=$7,
            bbox=ST_GeomFromText($8),
            tags=$9
        WHERE id = $1`, p.schema),
	)
	if err != nil {
		return err
	}
	p.changeUpdateStmt = changeUpdateStmt

	commentStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".comments (
            changeset_id,
            idx,
            user_name,
            user_id,
            timestamp,
            text
        ) VALUES ($1, $2, $3, $4, $5, $6)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.commentStmt = commentStmt

	return nil
}

func (p *PostGIS) Commit() error {
	return p.tx.Commit()
}

func (p *PostGIS) ImportElem(elem diff.Element) (err error) {
	_, err = p.tx.Exec("SAVEPOINT insert")
	if err != nil {
		return
	}

	defer func() {
		if err != nil {
			p.tx.Exec("ROLLBACK TO SAVEPOINT insert")
		} else {
			_, err = p.tx.Exec("RELEASE SAVEPOINT insert")
		}
	}()
	var add, mod, del bool
	if elem.Mod {
		mod = true
	} else if elem.Add {
		add = true
	} else if elem.Del {
		del = true
	}
	if elem.Node != nil {
		nd := elem.Node
		if _, err = p.nodeStmt.Exec(
			nd.Id,
			add,
			mod,
			del,
			nd.Long, nd.Lat,
			nd.Metadata.UserName,
			nd.Metadata.UserId,
			nd.Metadata.Timestamp,
			nd.Metadata.Version,
			nd.Metadata.Changeset,
			hstoreString(nd.Tags),
		); err != nil {
			return newSqlError(err, elem.Node)
		}
	} else if elem.Way != nil {
		w := elem.Way
		if _, err = p.wayStmt.Exec(
			w.Id,
			add,
			mod,
			del,
			w.Metadata.UserName,
			w.Metadata.UserId,
			w.Metadata.Timestamp,
			w.Metadata.Version,
			w.Metadata.Changeset,
			hstoreString(w.Tags),
		); err != nil {
			return newSqlError(err, elem.Way)
		}
		for i, ref := range elem.Way.Refs {
			if _, err = p.ndsStmt.Exec(
				w.Id,
				w.Metadata.Version,
				i,
				ref,
			); err != nil {
				return newSqlError(err, elem.Way)
			}
		}
	} else if elem.Rel != nil {
		rel := elem.Rel
		if _, err = p.relStmt.Exec(
			rel.Id,
			add,
			mod,
			del,
			rel.Metadata.UserName,
			rel.Metadata.UserId,
			rel.Metadata.Timestamp,
			rel.Metadata.Version,
			rel.Metadata.Changeset,
			hstoreString(rel.Tags),
		); err != nil {
			return newSqlError(err, elem.Rel)
		}
		for i, m := range elem.Rel.Members {
			var nodeId, wayId, relId interface{}
			switch m.Type {
			case element.NODE:
				nodeId = m.Id
			case element.WAY:
				wayId = m.Id
			case element.RELATION:
				relId = m.Id
			}
			if _, err = p.memberStmt.Exec(
				rel.Id,
				rel.Metadata.Version,
				m.Type,
				m.Role,
				i,
				nodeId,
				wayId,
				relId,
			); err != nil {
				return newSqlError(err, elem.Rel)
			}
		}
	}

	return nil
}

func (p *PostGIS) ImportChangeset(c changeset.Changeset) error {
	bbox := bboxPolygon(c)
	if _, err := p.tx.Exec("SAVEPOINT insert_changeset"); err != nil {
		return err
	}
	if _, err := p.changeStmt.Exec(
		c.Id,
		c.CreatedAt.Time,
		c.ClosedAt.Time,
		c.Open,
		c.NumChanges,
		c.User,
		c.UserId,
		bbox,
		hstoreStringChangeset(c.Tags),
	); err != nil {
		if _, err := p.tx.Exec("ROLLBACK TO SAVEPOINT insert_changeset"); err != nil {
			return err
		}
		if _, err := p.changeUpdateStmt.Exec(
			c.Id,
			c.CreatedAt.Time,
			c.ClosedAt.Time,
			c.Open,
			c.NumChanges,
			c.User,
			c.UserId,
			bbox,
			hstoreStringChangeset(c.Tags),
		); err != nil {
			return newSqlError(err, c)
		}
		if _, err := p.tx.Exec(fmt.Sprintf(`DELETE FROM "%[1]s".comments WHERE changeset_id = $1`, p.schema), c.Id); err != nil {
			return err
		}
	}

	for i, com := range c.Comments {
		if _, err := p.commentStmt.Exec(
			c.Id,
			i,
			com.User,
			com.UserId,
			com.Date.Time,
			com.Text,
		); err != nil {
			return newSqlError(err, c)
		}
	}

	if _, err := p.tx.Exec("RELEASE SAVEPOINT insert_changeset"); err != nil {
		return err
	}

	return nil
}

var hstoreReplacer = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")

func hstoreString(tags element.Tags) string {
	kv := make([]string, 0, len(tags))
	for k, v := range tags {
		kv = append(kv, `"`+hstoreReplacer.Replace(k)+`"=>"`+hstoreReplacer.Replace(v)+`"`)
	}
	return strings.Join(kv, ", ")
}

func hstoreStringChangeset(tags []changeset.Tag) string {
	kv := make([]string, 0, len(tags))
	for _, t := range tags {
		kv = append(kv, `"`+hstoreReplacer.Replace(t.Key)+`"=>"`+hstoreReplacer.Replace(t.Value)+`"`)
	}
	return strings.Join(kv, ", ")
}

func bboxPolygon(c changeset.Changeset) interface{} {
	if c.MinLon != 0.0 && c.MaxLon != 0.0 && c.MinLat != 0.0 && c.MaxLat != 0.0 {
		return fmt.Sprintf(
			"SRID=4326; POLYGON((%[1]f %[2]f, %[1]f %[4]f, %[3]f %[4]f, %[3]f %[2]f, %[1]f %[2]f))",
			c.MinLon, c.MinLat, c.MaxLon, c.MaxLat,
		)
	}
	return nil
}
<commit_msg>store times as UTC<commit_after>package database

import (
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/omniscale/imposm3/element"
	"github.com/omniscale/imposm3/parser/changeset"
	"github.com/omniscale/imposm3/parser/diff"

	_ "github.com/lib/pq"
)

var initSql = []string{
	`CREATE SCHEMA IF NOT EXISTS "%[1]s";`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".nodes (
    id BIGINT,
    add BOOLEAN,
    modify BOOLEAN,
    delete BOOLEAN,
    changeset INT,
    geometry GEOMETRY(Point, 4326),
    user_name VARCHAR,
    user_id INT,
    timestamp TIMESTAMP WITH TIME ZONE,
    version INT,
    tags HSTORE,
    PRIMARY KEY (id, version)
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".ways (
    id INT NOT NULL,
    add BOOLEAN,
    modify BOOLEAN,
    delete BOOLEAN,
    changeset INT,
    user_name VARCHAR,
    user_id INT,
    timestamp TIMESTAMP WITH TIME ZONE,
    version INT,
    tags HSTORE,
    PRIMARY KEY (id, version)
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".nds (
    way_id INT NOT NULL,
    way_version INT NOT NULL,
    idx INT,
    node_id BIGINT NOT NULL,
    PRIMARY KEY (way_id, way_version, idx, node_id),
    FOREIGN KEY (way_id, way_version)
        REFERENCES "%[1]s".ways (id, version)
          ON UPDATE CASCADE
          ON DELETE CASCADE
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".relations (
    id INT NOT NULL,
    add BOOLEAN,
    modify BOOLEAN,
    delete BOOLEAN,
    changeset INT,
    geometry GEOMETRY(Point, 4326),
    user_name VARCHAR,
    user_id INT,
    timestamp TIMESTAMP WITH TIME ZONE,
    version INT,
    tags HSTORE,
    PRIMARY KEY (id, version)
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".members (
    relation_id INT NOT NULL,
    relation_version INT,
    type VARCHAR,
    role VARCHAR,
    idx INT,
    member_node_id BIGINT,
    member_way_id INT,
    member_relation_id INT,
    FOREIGN KEY (relation_id, relation_version)
        REFERENCES "%[1]s".relations (id, version)
          ON UPDATE CASCADE
          ON DELETE CASCADE
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".changesets (
    id INT NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE,
    closed_at TIMESTAMP WITH TIME ZONE,
    num_changes INT,
    open BOOLEAN,
    user_name VARCHAR,
    user_id INT,
    tags HSTORE,
    bbox Geometry(POLYGON, 4326),
    PRIMARY KEY (id)
);`,
	`CREATE TABLE IF NOT EXISTS "%[1]s".comments (
    changeset_id BIGINT NOT NULL,
    idx INT,
    user_name VARCHAR,
    user_id INT,
    timestamp TIMESTAMP WITH TIME ZONE,
    text VARCHAR,
    PRIMARY KEY (changeset_id, idx),
    FOREIGN KEY (changeset_id)
        REFERENCES "%[1]s".changesets (id)
          ON UPDATE CASCADE
          ON DELETE CASCADE
);`,
}

type PostGIS struct {
	db               *sql.DB
	tx               *sql.Tx
	nodeStmt         *sql.Stmt
	wayStmt          *sql.Stmt
	ndsStmt          *sql.Stmt
	relStmt          *sql.Stmt
	memberStmt       *sql.Stmt
	changeStmt       *sql.Stmt
	changeUpdateStmt *sql.Stmt
	commentStmt      *sql.Stmt
	schema           string
}

func NewPostGIS(params string, schema string) (*PostGIS, error) {
	db, err := sql.Open("postgres", params)
	if err != nil {
		return nil, err
	}

	if schema == "" {
		schema = "public"
	}

	return &PostGIS{
		db:     db,
		schema: schema,
	}, nil
}

func newSqlError(err error, elem interface{}) error {
	return &sqlError{elem: elem, err: err}
}

type sqlError struct {
	elem interface{}
	err  error
}

func (s *sqlError) Error() string {
	return fmt.Sprintf("error: %s; for %#v", s.err, s.elem)

}

func (p *PostGIS) Init() error {
	tx, err := p.db.Begin()
	if err != nil {
		return err
	}
	for _, s := range initSql {
		stmt := fmt.Sprintf(s, p.schema)
		if _, err := tx.Exec(stmt); err != nil {
			tx.Rollback()
			return fmt.Errorf("error while calling %v: %v", stmt, err)
		}
	}
	return tx.Commit()
}

func (p *PostGIS) Begin() error {
	var err error
	p.tx, err = p.db.Begin()
	if err != nil {
		return err
	}
	nodeStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".nodes (
            id,
            add,
            modify,
            delete,
            geometry,
            user_name,
            user_id,
            timestamp,
            version,
            changeset,
            tags) VALUES ($1, $2, $3, $4, ST_SetSRID(ST_Point($5, $6), 4326), $7, $8, $9, $10, $11, $12)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.nodeStmt = nodeStmt

	wayStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".ways (
            id,
            add,
            modify,
            delete,
            user_name,
            user_id,
            timestamp,
            version,
            changeset,
            tags
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.wayStmt = wayStmt

	ndsStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".nds (
            way_id,
            way_version,
            idx,
            node_id
        ) VALUES ($1, $2, $3, $4)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.ndsStmt = ndsStmt

	relStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".relations (
            id,
            add,
            modify,
            delete,
            user_name,
            user_id,
            timestamp,
            version,
            changeset,
            tags
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.relStmt = relStmt

	memberStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".members (
            relation_id,
            relation_version,
            type,
            role,
            idx,
            member_node_id,
            member_way_id,
            member_relation_id
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.memberStmt = memberStmt

	changeStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".changesets (
            id,
            created_at,
            closed_at,
            open,
            num_changes,
            user_name,
            user_id,
            bbox,
            tags
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, ST_GeomFromText($8), $9)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.changeStmt = changeStmt

	changeUpdateStmt, err := p.tx.Prepare(
		fmt.Sprintf(`UPDATE "%[1]s".changesets SET
            created_at=$2,
            closed_at=$3,
            open=$4,
            num_changes=$5,
            user_name=$6,
            user_id=$7,
            bbox=ST_GeomFromText($8),
            tags=$9
        WHERE id = $1`, p.schema),
	)
	if err != nil {
		return err
	}
	p.changeUpdateStmt = changeUpdateStmt

	commentStmt, err := p.tx.Prepare(
		fmt.Sprintf(`INSERT INTO "%[1]s".comments (
            changeset_id,
            idx,
            user_name,
            user_id,
            timestamp,
            text
        ) VALUES ($1, $2, $3, $4, $5, $6)`, p.schema),
	)
	if err != nil {
		return err
	}
	p.commentStmt = commentStmt

	return nil
}

func (p *PostGIS) Commit() error {
	return p.tx.Commit()
}

func (p *PostGIS) ImportElem(elem diff.Element) (err error) {
	_, err = p.tx.Exec("SAVEPOINT insert")
	if err != nil {
		return
	}

	defer func() {
		if err != nil {
			p.tx.Exec("ROLLBACK TO SAVEPOINT insert")
		} else {
			_, err = p.tx.Exec("RELEASE SAVEPOINT insert")
		}
	}()
	var add, mod, del bool
	if elem.Mod {
		mod = true
	} else if elem.Add {
		add = true
	} else if elem.Del {
		del = true
	}
	if elem.Node != nil {
		nd := elem.Node
		if _, err = p.nodeStmt.Exec(
			nd.Id,
			add,
			mod,
			del,
			nd.Long, nd.Lat,
			nd.Metadata.UserName,
			nd.Metadata.UserId,
			nd.Metadata.Timestamp.UTC(),
			nd.Metadata.Version,
			nd.Metadata.Changeset,
			hstoreString(nd.Tags),
		); err != nil {
			return newSqlError(err, elem.Node)
		}
	} else if elem.Way != nil {
		w := elem.Way
		if _, err = p.wayStmt.Exec(
			w.Id,
			add,
			mod,
			del,
			w.Metadata.UserName,
			w.Metadata.UserId,
			w.Metadata.Timestamp.UTC(),
			w.Metadata.Version,
			w.Metadata.Changeset,
			hstoreString(w.Tags),
		); err != nil {
			return newSqlError(err, elem.Way)
		}
		for i, ref := range elem.Way.Refs {
			if _, err = p.ndsStmt.Exec(
				w.Id,
				w.Metadata.Version,
				i,
				ref,
			); err != nil {
				return newSqlError(err, elem.Way)
			}
		}
	} else if elem.Rel != nil {
		rel := elem.Rel
		if _, err = p.relStmt.Exec(
			rel.Id,
			add,
			mod,
			del,
			rel.Metadata.UserName,
			rel.Metadata.UserId,
			rel.Metadata.Timestamp.UTC(),
			rel.Metadata.Version,
			rel.Metadata.Changeset,
			hstoreString(rel.Tags),
		); err != nil {
			return newSqlError(err, elem.Rel)
		}
		for i, m := range elem.Rel.Members {
			var nodeId, wayId, relId interface{}
			switch m.Type {
			case element.NODE:
				nodeId = m.Id
			case element.WAY:
				wayId = m.Id
			case element.RELATION:
				relId = m.Id
			}
			if _, err = p.memberStmt.Exec(
				rel.Id,
				rel.Metadata.Version,
				m.Type,
				m.Role,
				i,
				nodeId,
				wayId,
				relId,
			); err != nil {
				return newSqlError(err, elem.Rel)
			}
		}
	}

	return nil
}

func (p *PostGIS) ImportChangeset(c changeset.Changeset) error {
	bbox := bboxPolygon(c)
	if _, err := p.tx.Exec("SAVEPOINT insert_changeset"); err != nil {
		return err
	}
	// insert null if closedAt isZero
	var closedAt *time.Time
	if !c.ClosedAt.IsZero() {
		closedUtc := c.ClosedAt.UTC()
		closedAt = &closedUtc
	}
	if _, err := p.changeStmt.Exec(
		c.Id,
		c.CreatedAt.UTC(),
		closedAt,
		c.Open,
		c.NumChanges,
		c.User,
		c.UserId,
		bbox,
		hstoreStringChangeset(c.Tags),
	); err != nil {
		if _, err := p.tx.Exec("ROLLBACK TO SAVEPOINT insert_changeset"); err != nil {
			return err
		}
		if _, err := p.changeUpdateStmt.Exec(
			c.Id,
			c.CreatedAt.UTC(),
			closedAt,
			c.Open,
			c.NumChanges,
			c.User,
			c.UserId,
			bbox,
			hstoreStringChangeset(c.Tags),
		); err != nil {
			return newSqlError(err, c)
		}
		if _, err := p.tx.Exec(fmt.Sprintf(`DELETE FROM "%[1]s".comments WHERE changeset_id = $1`, p.schema), c.Id); err != nil {
			return err
		}
	}

	for i, com := range c.Comments {
		if _, err := p.commentStmt.Exec(
			c.Id,
			i,
			com.User,
			com.UserId,
			com.Date.UTC(),
			com.Text,
		); err != nil {
			return newSqlError(err, c)
		}
	}

	if _, err := p.tx.Exec("RELEASE SAVEPOINT insert_changeset"); err != nil {
		return err
	}

	return nil
}

var hstoreReplacer = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")

func hstoreString(tags element.Tags) string {
	kv := make([]string, 0, len(tags))
	for k, v := range tags {
		kv = append(kv, `"`+hstoreReplacer.Replace(k)+`"=>"`+hstoreReplacer.Replace(v)+`"`)
	}
	return strings.Join(kv, ", ")
}

func hstoreStringChangeset(tags []changeset.Tag) string {
	kv := make([]string, 0, len(tags))
	for _, t := range tags {
		kv = append(kv, `"`+hstoreReplacer.Replace(t.Key)+`"=>"`+hstoreReplacer.Replace(t.Value)+`"`)
	}
	return strings.Join(kv, ", ")
}

func bboxPolygon(c changeset.Changeset) interface{} {
	if c.MinLon != 0.0 && c.MaxLon != 0.0 && c.MinLat != 0.0 && c.MaxLat != 0.0 {
		return fmt.Sprintf(
			"SRID=4326; POLYGON((%[1]f %[2]f, %[1]f %[4]f, %[3]f %[4]f, %[3]f %[2]f, %[1]f %[2]f))",
			c.MinLon, c.MinLat, c.MaxLon, c.MaxLat,
		)
	}
	return nil
}
<|endoftext|>


<commit_before>package reporting

import (
	"bytes"
	"encoding/json"
	"github.com/smartystreets/goconvey/gotest"
	"github.com/smartystreets/goconvey/printing"
)

func (self *jsonReporter) BeginStory(test gotest.T) {
	top := newScope("TOP", self.depth) // TODO: this could have the GoTest name.
	self.scopes = append(self.scopes, top)
	self.stack = append(self.stack, top)
}

func (self *jsonReporter) Enter(title, id string) {
	self.depth++
	if _, found := self.titlesById[id]; !found {
		self.registerScope(title, id)
	}
}
func (self *jsonReporter) registerScope(title, id string) {
	self.titlesById[id] = title
	next := newScope(title, self.depth)
	self.scopes = append(self.scopes, next)
	self.stack = append(self.stack, next)
}

func (self *jsonReporter) Report(r *Report) {
	current := self.stack[len(self.stack)-1]
	current.Reports = append(current.Reports, newJsonReport(r))
}

func (self *jsonReporter) Exit() {
	self.depth--
	if len(self.stack) > 0 {
		self.stack = self.stack[:len(self.stack)-1]
	}
}

func (self *jsonReporter) EndStory() {
	self.report()
	self.reset()
}
func (self *jsonReporter) report() {
	serialized, _ := json.Marshal(self.scopes)
	var buffer bytes.Buffer
	json.Indent(&buffer, serialized, "", "  ")
	self.out.Print(buffer.String() + ",")
}
func (self *jsonReporter) reset() {
	self.titlesById = make(map[string]string)
	self.scopes = []*scope{}
	self.stack = []*scope{}
	self.depth = 0
}

func NewJsonReporter(out *printing.Printer) *jsonReporter {
	self := &jsonReporter{}
	self.out = out
	self.reset()
	return self
}

type jsonReporter struct {
	out        *printing.Printer
	titlesById map[string]string
	scopes     []*scope
	stack      []*scope
	depth      int
}

type scope struct {
	Title   string
	File    string
	Line    int
	Depth   int
	Reports []*report
}

func newScope(title string, depth int) *scope {
	self := &scope{}
	self.Depth = depth
	self.Title = title
	self.Reports = []*report{}
	return self
}

type report struct {
	File       string
	Line       int
	Failure    string
	Error      interface{}
	StackTrace string
	Skipped    bool
}

func newJsonReport(r *Report) *report {
	return &report{
		r.File,
		r.Line,
		r.Failure,
		r.Error,
		r.stackTrace,
		r.Skipped,
	}
}
<commit_msg>First attempt at geting file and line for each Convey() registration.<commit_after>package reporting

import (
	"bytes"
	"encoding/json"
	"github.com/smartystreets/goconvey/gotest"
	"github.com/smartystreets/goconvey/printing"
)

func (self *jsonReporter) BeginStory(test gotest.T) {
	file, line, testName := gotest.ResolveExternalCaller()
	top := newScope(testName, self.depth, file, line)
	self.scopes = append(self.scopes, top)
	self.stack = append(self.stack, top)
}

func (self *jsonReporter) Enter(title, id string) {
	self.depth++
	if _, found := self.titlesById[id]; !found {
		self.registerScope(title, id)
	}
}
func (self *jsonReporter) registerScope(title, id string) {
	self.titlesById[id] = title
	file, line, _ := gotest.ResolveExternalCaller()
	// ok, this isn't working--we're getting the wrong line numbers at this
	// point because we are executing from the top-level Convey statement and
	// not the actual Convey statement.
	// because that's where we execute runner.Run() from).
	// I probably need to capture the file and line information when I parse
	// registration in the convey package. That info would be stored on the
	// scope struct and then passed to the reporter during the Enter method
	// so we'd have it here. Although, I hate to modify the signature of the
	// Reporter interface just for the json reporter but I don't see a better
	// way at this point...
	next := newScope(title, self.depth, file, line)
	self.scopes = append(self.scopes, next)
	self.stack = append(self.stack, next)
}

func (self *jsonReporter) Report(r *Report) {
	current := self.stack[len(self.stack)-1]
	current.Reports = append(current.Reports, newJsonReport(r))
}

func (self *jsonReporter) Exit() {
	self.depth--
	if len(self.stack) > 0 {
		self.stack = self.stack[:len(self.stack)-1]
	}
}

func (self *jsonReporter) EndStory() {
	self.report()
	self.reset()
}
func (self *jsonReporter) report() {
	serialized, _ := json.Marshal(self.scopes)
	var buffer bytes.Buffer
	json.Indent(&buffer, serialized, "", "  ")
	self.out.Print(buffer.String() + ",")
}
func (self *jsonReporter) reset() {
	self.titlesById = make(map[string]string)
	self.scopes = []*scope{}
	self.stack = []*scope{}
	self.depth = 0
}

func NewJsonReporter(out *printing.Printer) *jsonReporter {
	self := &jsonReporter{}
	self.out = out
	self.reset()
	return self
}

type jsonReporter struct {
	out        *printing.Printer
	titlesById map[string]string
	scopes     []*scope
	stack      []*scope
	depth      int
}

type scope struct {
	Title   string
	File    string
	Line    int
	Depth   int
	Reports []*report
}

func newScope(title string, depth int, file string, line int) *scope {
	self := &scope{}
	self.Title = title
	self.Depth = depth
	self.File = file
	self.Line = line
	self.Reports = []*report{}
	return self
}

type report struct {
	File       string
	Line       int
	Failure    string
	Error      interface{}
	StackTrace string
	Skipped    bool
}

func newJsonReport(r *Report) *report {
	return &report{
		r.File,
		r.Line,
		r.Failure,
		r.Error,
		r.stackTrace,
		r.Skipped,
	}
}
<|endoftext|>


<commit_before>package pi

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"mime/multipart"
)

var (
	errNoFiles = fmt.Errorf("no files")
)

// RequestContext represents the context of the HTTP request.
// It is shared across interceptors and handler.
type RequestContext struct {
	W        http.ResponseWriter
	R        *http.Request
	RouteURL string
	Data     map[interface{}]interface{}
}

func newRequestContext(w http.ResponseWriter, r *http.Request, routeURL string) *RequestContext {
	return &RequestContext{
		W:        w,
		R:        r,
		RouteURL: routeURL,
		Data:     make(map[interface{}]interface{}),
	}
}

// WriteJSON marshal the object to JSON and writes it via the ResponseWriter.
func (c *RequestContext) WriteJSON(object interface{}) error {
	c.W.Header().Add("Content-Type", "application/json; charset=utf-8")
	if debugMode {
		output, err := json.MarshalIndent(object, "", "  ")
		if err != nil {
			return err
		}
		writeDebug("WriteJSON", c.R.RemoteAddr, string(output))
		c.W.Write(output)
	} else {
		output, err := json.Marshal(object)
		if err != nil {
			return err
		}
		c.W.Write(output)
	}
	return nil
}

// WriteXML marshal the object to XML and writes it via the ResponseWriter.
func (c *RequestContext) WriteXML(object interface{}) error {
	c.W.Header().Add("Content-Type", "application/xml; charset=utf-8")
	if debugMode {
		output, err := xml.MarshalIndent(object, "", "  ")
		if err != nil {
			return err
		}
		writeDebug("WriteXML", c.R.RemoteAddr, string(output))
		c.W.Write(output)
	} else {
		output, err := xml.Marshal(object)
		if err != nil {
			return err
		}
		c.W.Write(output)
	}
	return nil
}

// GetBody return the body as a ReadCloser. It is the client responsibility to close the body.
func (c *RequestContext) GetBody() io.ReadCloser { return c.R.Body }

// GetRawBody returns the body as a byte array, closing the body reader.
func (c *RequestContext) GetRawBody() ([]byte, error) {
	body := c.GetBody()
	defer body.Close()
	rawBody, err := ioutil.ReadAll(body)
	if err != nil {
		return nil, err
	}
	if debugMode {
		writeDebug("GetRawBody", c.R.RemoteAddr, fmt.Sprintf("got %s", string(rawBody)))
	}
	return ioutil.ReadAll(body)
}

// GetJSONObject call json.Unmarshal by sending the reference of the given object.
// For example:
//		func GetUser(c *pi.RequestContext) error {
// 			user := &User{}
// 			if err := c.GetJSONObject(user); err != nil {
//				return pi.NewError(400, err)
//			}
//			// Do something with the user...
//			return nil
//		}
//
func (c *RequestContext) GetJSONObject(object interface{}) error {
	rawBody, err := c.GetRawBody()
	if err != nil {
		return err
	}
	return json.Unmarshal(rawBody, &object)
}

// GetXMLObject call xml.Unmarshal by sending the reference of the given object.
func (c *RequestContext) GetXMLObject(object interface{}) error {
	rawBody, err := c.GetRawBody()
	if err != nil {
		return err
	}
	return xml.Unmarshal(rawBody, &object)
}

// GetURLParam returns an URL parameter.
// For example, given this URL:
//		/user?id=1234
//
//		fmt.Println(c.GetURLParam("id"))
//		// Outputs 1234
//
func (c *RequestContext) GetURLParam(param string) string { return c.R.FormValue(param) }

// GetRouteVariable returns a route variable.
// For example:
//		getUserByID := func(c *RequestContext) error {
//			id := c.GetRouteVariable("id")
//			// Do something with the ID.
//			return nil
//		}
//
//		p := pi.New()
//		p.Route("/user/{id}").Get(getUserByID)
//		p.ListenAndServe(":8080")
//
func (c *RequestContext) GetRouteVariable(key string) string { return c.R.URL.Query().Get(":" + key) }

// GetFileHeaders returns an array of FileHeader.
// For example:
//		getImages := func(c *RequestContext) error {
//			files, err := c.GetFileHeaders("files")
//			if err != nil {
//				return pi.NewError(400, err)
//			}
//			// Handle files
//			return nil
//		}
//
//		p := pi.New()
//		p.Route("/images").Post(getImages)
//		p.ListenAndServe(":8080")
//
func (c *RequestContext) GetFileHeaders(key string) ([]*multipart.FileHeader, error) {
	if err := c.R.ParseMultipartForm(32 << 2); err != nil {
		return nil, err
	}
	if c.R.MultipartForm != nil && c.R.MultipartForm.File[key] != nil {
		return c.R.MultipartForm.File[key], nil
	}
	return nil, errNoFiles
}
<commit_msg>Fixed cache size.<commit_after>package pi

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"mime/multipart"
)

var (
	errNoFiles = fmt.Errorf("no files")
)

// RequestContext represents the context of the HTTP request.
// It is shared across interceptors and handler.
type RequestContext struct {
	W        http.ResponseWriter
	R        *http.Request
	RouteURL string
	Data     map[interface{}]interface{}
}

func newRequestContext(w http.ResponseWriter, r *http.Request, routeURL string) *RequestContext {
	return &RequestContext{
		W:        w,
		R:        r,
		RouteURL: routeURL,
		Data:     make(map[interface{}]interface{}),
	}
}

// WriteJSON marshal the object to JSON and writes it via the ResponseWriter.
func (c *RequestContext) WriteJSON(object interface{}) error {
	c.W.Header().Add("Content-Type", "application/json; charset=utf-8")
	if debugMode {
		output, err := json.MarshalIndent(object, "", "  ")
		if err != nil {
			return err
		}
		writeDebug("WriteJSON", c.R.RemoteAddr, string(output))
		c.W.Write(output)
	} else {
		output, err := json.Marshal(object)
		if err != nil {
			return err
		}
		c.W.Write(output)
	}
	return nil
}

// WriteXML marshal the object to XML and writes it via the ResponseWriter.
func (c *RequestContext) WriteXML(object interface{}) error {
	c.W.Header().Add("Content-Type", "application/xml; charset=utf-8")
	if debugMode {
		output, err := xml.MarshalIndent(object, "", "  ")
		if err != nil {
			return err
		}
		writeDebug("WriteXML", c.R.RemoteAddr, string(output))
		c.W.Write(output)
	} else {
		output, err := xml.Marshal(object)
		if err != nil {
			return err
		}
		c.W.Write(output)
	}
	return nil
}

// GetBody return the body as a ReadCloser. It is the client responsibility to close the body.
func (c *RequestContext) GetBody() io.ReadCloser { return c.R.Body }

// GetRawBody returns the body as a byte array, closing the body reader.
func (c *RequestContext) GetRawBody() ([]byte, error) {
	body := c.GetBody()
	defer body.Close()
	rawBody, err := ioutil.ReadAll(body)
	if err != nil {
		return nil, err
	}
	if debugMode {
		writeDebug("GetRawBody", c.R.RemoteAddr, fmt.Sprintf("got %s", string(rawBody)))
	}
	return ioutil.ReadAll(body)
}

// GetJSONObject call json.Unmarshal by sending the reference of the given object.
// For example:
//		func GetUser(c *pi.RequestContext) error {
// 			user := &User{}
// 			if err := c.GetJSONObject(user); err != nil {
//				return pi.NewError(400, err)
//			}
//			// Do something with the user...
//			return nil
//		}
//
func (c *RequestContext) GetJSONObject(object interface{}) error {
	rawBody, err := c.GetRawBody()
	if err != nil {
		return err
	}
	return json.Unmarshal(rawBody, &object)
}

// GetXMLObject call xml.Unmarshal by sending the reference of the given object.
func (c *RequestContext) GetXMLObject(object interface{}) error {
	rawBody, err := c.GetRawBody()
	if err != nil {
		return err
	}
	return xml.Unmarshal(rawBody, &object)
}

// GetURLParam returns an URL parameter.
// For example, given this URL:
//		/user?id=1234
//
//		fmt.Println(c.GetURLParam("id"))
//		// Outputs 1234
//
func (c *RequestContext) GetURLParam(param string) string { return c.R.FormValue(param) }

// GetRouteVariable returns a route variable.
// For example:
//		getUserByID := func(c *RequestContext) error {
//			id := c.GetRouteVariable("id")
//			// Do something with the ID.
//			return nil
//		}
//
//		p := pi.New()
//		p.Route("/user/{id}").Get(getUserByID)
//		p.ListenAndServe(":8080")
//
func (c *RequestContext) GetRouteVariable(key string) string { return c.R.URL.Query().Get(":" + key) }

// GetFileHeaders returns an array of FileHeader.
// For example:
//		getImages := func(c *RequestContext) error {
//			files, err := c.GetFileHeaders("files")
//			if err != nil {
//				return pi.NewError(400, err)
//			}
//			// Handle files
//			return nil
//		}
//
//		p := pi.New()
//		p.Route("/images").Post(getImages)
//		p.ListenAndServe(":8080")
//
func (c *RequestContext) GetFileHeaders(key string) ([]*multipart.FileHeader, error) {
	if err := c.R.ParseMultipartForm(32 << 20); err != nil {
		return nil, err
	}
	if c.R.MultipartForm != nil && c.R.MultipartForm.File[key] != nil {
		return c.R.MultipartForm.File[key], nil
	}
	return nil, errNoFiles
}
<|endoftext|>


<commit_before>package astar

import (
	"fmt"
	"testing"
)

// dummy Node impl
type node string

func (n node) Nbs() []Node             { return nil }
func (n node) DistanceTo(n2 Node) Cost { return 1 }
func (n node) EstimateTo(n2 Node) Cost { return 10 }

func qmapExample() []Node {
	q := new(QMap)
	q.Init()
	q.Add(node("foo"), 10)
	q.Add(node("five"), 5)
	q.Add(node("five2"), 5)
	item, _ := q.Item(node("foo"))
	q.Update(item, 3)
	q.Add(node("two"), 2)
	res := make([]Node, 0, 4)
	for q.Len() > 0 {
		v := q.Pop()
		res = append(res, v)
	}
	return res
}

func ExampleQMap() {
	fmt.Print(qmapExample())
	// Output: [two foo five five2]
}

func BenchmarkQMap(b *testing.B) {
	for n := 0; n < b.N; n++ {
		qmapExample()
	}
}
<commit_msg>FIX 1c29105 changed QMap API<commit_after>package astar

import (
	"fmt"
	"testing"
)

// dummy Node impl
type node string

func (n node) Nbs() []Node             { return nil }
func (n node) DistanceTo(n2 Node) Cost { return 1 }
func (n node) EstimateTo(n2 Node) Cost { return 10 }

func qmapExample() []Node {
	q := new(QMap)
	q.Init()
	q.Add(node("foo"), 10)
	q.Add(node("five"), 5)
	q.Add(node("five2"), 5)
	item, _ := q.Get(node("foo"))
	q.Update(item, 3)
	q.Add(node("two"), 2)
	res := make([]Node, 0, 4)
	for q.Len() > 0 {
		v := q.Pop()
		res = append(res, v)
	}
	return res
}

func ExampleQMap() {
	fmt.Print(qmapExample())
	// Output: [two foo five five2]
}

func BenchmarkQMap(b *testing.B) {
	for n := 0; n < b.N; n++ {
		qmapExample()
	}
}
<|endoftext|>


<commit_before>package charm_test

import (
	"io/ioutil"
	. "launchpad.net/gocheck"
	corecharm "launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/worker/uniter/charm"
	"os"
	"path/filepath"
)

type DeployerSuite struct{}

var _ = Suite(&DeployerSuite{})

func (s *DeployerSuite) TestUnsetCharm(c *C) {
	d := charm.NewDeployer(filepath.Join(c.MkDir(), "deployer"))
	err := d.Deploy(charm.NewGitDir(c.MkDir()))
	c.Assert(err, ErrorMatches, "charm deployment failed: no charm set")
}

func (s *DeployerSuite) TestInstall(c *C) {
	// Install.
	d := charm.NewDeployer(filepath.Join(c.MkDir(), "deployer"))
	bun := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("hello"), 0644)
		c.Assert(err, IsNil)
	})
	err := d.Stage(bun, corecharm.MustParseURL("cs:s/c-1"))
	c.Assert(err, IsNil)
	target := charm.NewGitDir(filepath.Join(c.MkDir(), "target"))
	err = d.Deploy(target)
	c.Assert(err, IsNil)

	// Check content.
	data, err := ioutil.ReadFile(filepath.Join(target.Path(), "some-file"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "hello")
	url, err := charm.ReadCharmURL(target)
	c.Assert(err, IsNil)
	c.Assert(url, DeepEquals, corecharm.MustParseURL("cs:s/c-1"))
	lines, err := target.Log()
	c.Assert(err, IsNil)
	c.Assert(lines, HasLen, 2)
	c.Assert(lines[0], Matches, `[0-9a-f]{7} Deployed charm "cs:s/c-1".`)
	c.Assert(lines[1], Matches, `[0-9a-f]{7} Imported charm "cs:s/c-1" from ".*".`)
}

func (s *DeployerSuite) TestUpgrade(c *C) {
	// Install.
	d := charm.NewDeployer(filepath.Join(c.MkDir(), "deployer"))
	bun1 := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("hello"), 0644)
		c.Assert(err, IsNil)
	})
	err := d.Stage(bun1, corecharm.MustParseURL("cs:s/c-1"))
	c.Assert(err, IsNil)
	target := charm.NewGitDir(filepath.Join(c.MkDir(), "target"))
	err = d.Deploy(target)
	c.Assert(err, IsNil)

	// Upgrade.
	bun2 := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("goodbye"), 0644)
		c.Assert(err, IsNil)
	})
	err = d.Stage(bun2, corecharm.MustParseURL("cs:s/c-2"))
	c.Assert(err, IsNil)
	err = d.Deploy(target)
	c.Assert(err, IsNil)

	// Check content.
	data, err := ioutil.ReadFile(filepath.Join(target.Path(), "some-file"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "goodbye")
	url, err := charm.ReadCharmURL(target)
	c.Assert(err, IsNil)
	c.Assert(url, DeepEquals, corecharm.MustParseURL("cs:s/c-2"))
	lines, err := target.Log()
	c.Assert(err, IsNil)
	c.Assert(lines, HasLen, 5)
	c.Assert(lines[0], Matches, `[0-9a-f]{7} Upgraded charm to "cs:s/c-2".`)
}

func (s *DeployerSuite) TestConflict(c *C) {
	// Install.
	d := charm.NewDeployer(filepath.Join(c.MkDir(), "deployer"))
	bun1 := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("hello"), 0644)
		c.Assert(err, IsNil)
	})
	err := d.Stage(bun1, corecharm.MustParseURL("cs:s/c-1"))
	c.Assert(err, IsNil)
	target := charm.NewGitDir(filepath.Join(c.MkDir(), "target"))
	err = d.Deploy(target)
	c.Assert(err, IsNil)

	// Mess up target.
	err = ioutil.WriteFile(filepath.Join(target.Path(), "some-file"), []byte("mu!"), 0644)
	c.Assert(err, IsNil)

	// Upgrade.
	bun2 := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("goodbye"), 0644)
		c.Assert(err, IsNil)
	})
	err = d.Stage(bun2, corecharm.MustParseURL("cs:s/c-2"))
	c.Assert(err, IsNil)
	err = d.Deploy(target)
	c.Assert(err, Equals, charm.ErrConflict)

	// Check state.
	conflicted, err := target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, true)

	// Revert and check initial content.
	err = target.Revert()
	c.Assert(err, IsNil)
	data, err := ioutil.ReadFile(filepath.Join(target.Path(), "some-file"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "mu!")
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, false)

	// Try to upgrade again.
	err = d.Deploy(target)
	c.Assert(err, Equals, charm.ErrConflict)
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, true)

	// And again.
	err = d.Deploy(target)
	c.Assert(err, Equals, charm.ErrConflict)
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, true)

	// Manually resolve, and commit.
	err = ioutil.WriteFile(filepath.Join(target.Path(), "some-file"), []byte("nu!"), 0644)
	c.Assert(err, IsNil)
	err = target.Snapshotf("user resolved conflicts")
	c.Assert(err, IsNil)
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, false)

	// Try a final upgrade to the same charm and check it doesn't write anything
	// except the upgrade log line.
	err = d.Deploy(target)
	c.Assert(err, IsNil)
	data, err = ioutil.ReadFile(filepath.Join(target.Path(), "some-file"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "nu!")
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, false)
	lines, err := target.Log()
	c.Assert(err, IsNil)
	c.Assert(lines[0], Matches, `[0-9a-f]{7} Upgraded charm to "cs:s/c-2".`)
}

func (s *DeployerSuite) bundle(c *C, customize func(path string)) *corecharm.Bundle {
	base := c.MkDir()
	dirpath := testing.Charms.ClonedDirPath(base, "series", "dummy")
	customize(dirpath)
	dir, err := corecharm.ReadDir(dirpath)
	c.Assert(err, IsNil)
	bunpath := filepath.Join(base, "bundle")
	file, err := os.Create(bunpath)
	c.Assert(err, IsNil)
	defer file.Close()
	err = dir.BundleTo(file)
	c.Assert(err, IsNil)
	bun, err := corecharm.ReadBundle(bunpath)
	c.Assert(err, IsNil)
	return bun
}
<commit_msg>add explicit test for overwriting symlinks, to verify lp:988115<commit_after>package charm_test

import (
	"io/ioutil"
	. "launchpad.net/gocheck"
	corecharm "launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/worker/uniter/charm"
	"os"
	"path/filepath"
)

type DeployerSuite struct{}

var _ = Suite(&DeployerSuite{})

func (s *DeployerSuite) TestUnsetCharm(c *C) {
	d := charm.NewDeployer(filepath.Join(c.MkDir(), "deployer"))
	err := d.Deploy(charm.NewGitDir(c.MkDir()))
	c.Assert(err, ErrorMatches, "charm deployment failed: no charm set")
}

func (s *DeployerSuite) TestInstall(c *C) {
	// Install.
	d := charm.NewDeployer(filepath.Join(c.MkDir(), "deployer"))
	bun := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("hello"), 0644)
		c.Assert(err, IsNil)
	})
	err := d.Stage(bun, corecharm.MustParseURL("cs:s/c-1"))
	c.Assert(err, IsNil)
	target := charm.NewGitDir(filepath.Join(c.MkDir(), "target"))
	err = d.Deploy(target)
	c.Assert(err, IsNil)

	// Check content.
	data, err := ioutil.ReadFile(filepath.Join(target.Path(), "some-file"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "hello")
	url, err := charm.ReadCharmURL(target)
	c.Assert(err, IsNil)
	c.Assert(url, DeepEquals, corecharm.MustParseURL("cs:s/c-1"))
	lines, err := target.Log()
	c.Assert(err, IsNil)
	c.Assert(lines, HasLen, 2)
	c.Assert(lines[0], Matches, `[0-9a-f]{7} Deployed charm "cs:s/c-1".`)
	c.Assert(lines[1], Matches, `[0-9a-f]{7} Imported charm "cs:s/c-1" from ".*".`)
}

func (s *DeployerSuite) TestUpgrade(c *C) {
	// Install.
	d := charm.NewDeployer(filepath.Join(c.MkDir(), "deployer"))
	bun1 := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("hello"), 0644)
		c.Assert(err, IsNil)
		err = os.Symlink("./some-file", "a-symlink")
		c.Assert(err, IsNil)
	})
	err := d.Stage(bun1, corecharm.MustParseURL("cs:s/c-1"))
	c.Assert(err, IsNil)
	target := charm.NewGitDir(filepath.Join(c.MkDir(), "target"))
	err = d.Deploy(target)
	c.Assert(err, IsNil)

	// Upgrade.
	bun2 := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("goodbye"), 0644)
		c.Assert(err, IsNil)
		err = ioutil.WriteFile(filepath.Join(path, "a-symlink"), []byte("not any more!"), 0644)
		c.Assert(err, IsNil)
	})
	err = d.Stage(bun2, corecharm.MustParseURL("cs:s/c-2"))
	c.Assert(err, IsNil)
	err = d.Deploy(target)
	c.Assert(err, IsNil)

	// Check content.
	data, err := ioutil.ReadFile(filepath.Join(target.Path(), "some-file"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "goodbye")
	data, err = ioutil.ReadFile(filepath.Join(target.Path(), "a-symlink"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "not any more!")
	url, err := charm.ReadCharmURL(target)
	c.Assert(err, IsNil)
	c.Assert(url, DeepEquals, corecharm.MustParseURL("cs:s/c-2"))
	lines, err := target.Log()
	c.Assert(err, IsNil)
	c.Assert(lines, HasLen, 5)
	c.Assert(lines[0], Matches, `[0-9a-f]{7} Upgraded charm to "cs:s/c-2".`)
}

func (s *DeployerSuite) TestConflict(c *C) {
	// Install.
	d := charm.NewDeployer(filepath.Join(c.MkDir(), "deployer"))
	bun1 := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("hello"), 0644)
		c.Assert(err, IsNil)
	})
	err := d.Stage(bun1, corecharm.MustParseURL("cs:s/c-1"))
	c.Assert(err, IsNil)
	target := charm.NewGitDir(filepath.Join(c.MkDir(), "target"))
	err = d.Deploy(target)
	c.Assert(err, IsNil)

	// Mess up target.
	err = ioutil.WriteFile(filepath.Join(target.Path(), "some-file"), []byte("mu!"), 0644)
	c.Assert(err, IsNil)

	// Upgrade.
	bun2 := s.bundle(c, func(path string) {
		err := ioutil.WriteFile(filepath.Join(path, "some-file"), []byte("goodbye"), 0644)
		c.Assert(err, IsNil)
	})
	err = d.Stage(bun2, corecharm.MustParseURL("cs:s/c-2"))
	c.Assert(err, IsNil)
	err = d.Deploy(target)
	c.Assert(err, Equals, charm.ErrConflict)

	// Check state.
	conflicted, err := target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, true)

	// Revert and check initial content.
	err = target.Revert()
	c.Assert(err, IsNil)
	data, err := ioutil.ReadFile(filepath.Join(target.Path(), "some-file"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "mu!")
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, false)

	// Try to upgrade again.
	err = d.Deploy(target)
	c.Assert(err, Equals, charm.ErrConflict)
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, true)

	// And again.
	err = d.Deploy(target)
	c.Assert(err, Equals, charm.ErrConflict)
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, true)

	// Manually resolve, and commit.
	err = ioutil.WriteFile(filepath.Join(target.Path(), "some-file"), []byte("nu!"), 0644)
	c.Assert(err, IsNil)
	err = target.Snapshotf("user resolved conflicts")
	c.Assert(err, IsNil)
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, false)

	// Try a final upgrade to the same charm and check it doesn't write anything
	// except the upgrade log line.
	err = d.Deploy(target)
	c.Assert(err, IsNil)
	data, err = ioutil.ReadFile(filepath.Join(target.Path(), "some-file"))
	c.Assert(err, IsNil)
	c.Assert(string(data), Equals, "nu!")
	conflicted, err = target.Conflicted()
	c.Assert(err, IsNil)
	c.Assert(conflicted, Equals, false)
	lines, err := target.Log()
	c.Assert(err, IsNil)
	c.Assert(lines[0], Matches, `[0-9a-f]{7} Upgraded charm to "cs:s/c-2".`)
}

func (s *DeployerSuite) bundle(c *C, customize func(path string)) *corecharm.Bundle {
	base := c.MkDir()
	dirpath := testing.Charms.ClonedDirPath(base, "series", "dummy")
	customize(dirpath)
	dir, err := corecharm.ReadDir(dirpath)
	c.Assert(err, IsNil)
	bunpath := filepath.Join(base, "bundle")
	file, err := os.Create(bunpath)
	c.Assert(err, IsNil)
	defer file.Close()
	err = dir.BundleTo(file)
	c.Assert(err, IsNil)
	bun, err := corecharm.ReadBundle(bunpath)
	c.Assert(err, IsNil)
	return bun
}
<|endoftext|>


<commit_before>package elastic

import (
	"context"
	"fmt"
	"math"
	"strings"

	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	weed_util "github.com/chrislusf/seaweedfs/weed/util"
	jsoniter "github.com/json-iterator/go"
	elastic "github.com/olivere/elastic/v7"
)

var (
	indexType   = "_doc"
	indexPrefix = ".seaweedfs_"
	indexKV     = ".seaweedfs_kv_entries"
	kvMappings  = ` {
		     "mappings": {
		   	"enabled": false,
		       "properties": {
		         "Value":{
		           "type": "binary"
		         }
		       }
		     }
		   }`
)

type ESEntry struct {
	ParentId string `json:"ParentId"`
	Entry    *filer.Entry
}

type ESKVEntry struct {
	Value []byte `json:"Value"`
}

func init() {
	filer.Stores = append(filer.Stores, &ElasticStore{})
}

type ElasticStore struct {
	client      *elastic.Client
	maxPageSize int
}

func (store *ElasticStore) GetName() string {
	return "elastic7"
}

func (store *ElasticStore) Initialize(configuration weed_util.Configuration, prefix string) (err error) {
	options := []elastic.ClientOptionFunc{}
	servers := configuration.GetStringSlice(prefix + "servers")
	options = append(options, elastic.SetURL(servers...))
	username := configuration.GetString(prefix + "username")
	password := configuration.GetString(prefix + "password")
	if username != "" && password != "" {
		options = append(options, elastic.SetBasicAuth(username, password))
	}
	options = append(options, elastic.SetSniff(configuration.GetBool(prefix+"sniff_enabled")))
	options = append(options, elastic.SetHealthcheck(configuration.GetBool(prefix+"healthcheck_enabled")))
	store.maxPageSize = configuration.GetInt(prefix + "index.max_result_window")
	if store.maxPageSize <= 0 {
		store.maxPageSize = 10000
	}
	glog.Infof("filer store elastic endpoints: %v.", servers)
	return store.initialize(options)
}

func (store *ElasticStore) initialize(options []elastic.ClientOptionFunc) (err error) {
	ctx := context.Background()
	store.client, err = elastic.NewClient(options...)
	if err != nil {
		return fmt.Errorf("init elastic %v.", err)
	}
	if ok, err := store.client.IndexExists(indexKV).Do(ctx); err == nil && !ok {
		_, err = store.client.CreateIndex(indexKV).Body(kvMappings).Do(ctx)
		if err != nil {
			return fmt.Errorf("create index(%s) %v.", indexKV, err)
		}
	}
	return nil
}

func (store *ElasticStore) BeginTransaction(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func (store *ElasticStore) CommitTransaction(ctx context.Context) error {
	return nil
}
func (store *ElasticStore) RollbackTransaction(ctx context.Context) error {
	return nil
}

func (store *ElasticStore) ListDirectoryPrefixedEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int, prefix string) (entries []*filer.Entry, err error) {
	return nil, filer.ErrUnsupportedListDirectoryPrefixed
}

func (store *ElasticStore) InsertEntry(ctx context.Context, entry *filer.Entry) (err error) {
	index := getIndex(entry.FullPath)
	dir, _ := entry.FullPath.DirAndName()
	id := weed_util.Md5String([]byte(entry.FullPath))
	esEntry := &ESEntry{
		ParentId: weed_util.Md5String([]byte(dir)),
		Entry:    entry,
	}
	value, err := jsoniter.Marshal(esEntry)
	if err != nil {
		glog.Errorf("insert entry(%s) %v.", string(entry.FullPath), err)
		return fmt.Errorf("insert entry %v.", err)
	}
	_, err = store.client.Index().
		Index(index).
		Type(indexType).
		Id(id).
		BodyJson(string(value)).
		Do(ctx)
	if err != nil {
		glog.Errorf("insert entry(%s) %v.", string(entry.FullPath), err)
		return fmt.Errorf("insert entry %v.", err)
	}
	return nil
}

func (store *ElasticStore) UpdateEntry(ctx context.Context, entry *filer.Entry) (err error) {
	return store.InsertEntry(ctx, entry)
}

func (store *ElasticStore) FindEntry(ctx context.Context, fullpath weed_util.FullPath) (entry *filer.Entry, err error) {
	index := getIndex(fullpath)
	id := weed_util.Md5String([]byte(fullpath))
	searchResult, err := store.client.Get().
		Index(index).
		Type(indexType).
		Id(id).
		Do(ctx)
	if elastic.IsNotFound(err) {
		return nil, filer_pb.ErrNotFound
	}
	if searchResult != nil && searchResult.Found {
		esEntry := &ESEntry{
			ParentId: "",
			Entry:    &filer.Entry{},
		}
		err := jsoniter.Unmarshal(searchResult.Source, esEntry)
		return esEntry.Entry, err
	}
	glog.Errorf("find entry(%s),%v.", string(fullpath), err)
	return nil, filer_pb.ErrNotFound
}

func (store *ElasticStore) DeleteEntry(ctx context.Context, fullpath weed_util.FullPath) (err error) {
	index := getIndex(fullpath)
	id := weed_util.Md5String([]byte(fullpath))
	if strings.Count(string(fullpath), "/") == 1 {
		return store.deleteIndex(ctx, index)
	}
	return store.deleteEntry(ctx, index, id)
}

func (store *ElasticStore) deleteIndex(ctx context.Context, index string) (err error) {
	deleteResult, err := store.client.DeleteIndex(index).Do(ctx)
	if elastic.IsNotFound(err) || (err == nil && deleteResult.Acknowledged) {
		return nil
	}
	glog.Errorf("delete index(%s) %v.", index, err)
	return err
}

func (store *ElasticStore) deleteEntry(ctx context.Context, index, id string) (err error) {
	deleteResult, err := store.client.Delete().
		Index(index).
		Type(indexType).
		Id(id).
		Do(ctx)
	if err == nil {
		if deleteResult.Result == "deleted" || deleteResult.Result == "not_found" {
			return nil
		}
	}
	glog.Errorf("delete entry(index:%s,_id:%s) %v.", index, id, err)
	return fmt.Errorf("delete entry %v.", err)
}

func (store *ElasticStore) DeleteFolderChildren(ctx context.Context, fullpath weed_util.FullPath) (err error) {
	if entries, err := store.ListDirectoryEntries(ctx, fullpath, "", false, math.MaxInt32); err == nil {
		for _, entry := range entries {
			store.DeleteEntry(ctx, entry.FullPath)
		}
	}
	return nil
}

func (store *ElasticStore) ListDirectoryEntries(
	ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int,
) (entries []*filer.Entry, err error) {
	if string(fullpath) == "/" {
		return store.listRootDirectoryEntries(ctx, startFileName, inclusive, limit)
	}
	return store.listDirectoryEntries(ctx, fullpath, startFileName, inclusive, limit)
}

func (store *ElasticStore) listRootDirectoryEntries(ctx context.Context, startFileName string, inclusive bool, limit int) (entries []*filer.Entry, err error) {
	indexResult, err := store.client.CatIndices().Do(ctx)
	if err != nil {
		glog.Errorf("list indices %v.", err)
		return entries, err
	}
	for _, index := range indexResult {
		if index.Index == indexKV {
			continue
		}
		if strings.HasPrefix(index.Index, indexPrefix) {
			if entry, err := store.FindEntry(ctx,
				weed_util.FullPath("/"+strings.Replace(index.Index, indexPrefix, "", 1))); err == nil {
				fileName := getFileName(entry.FullPath)
				if fileName == startFileName && !inclusive {
					continue
				}
				limit--
				if limit < 0 {
					break
				}
				entries = append(entries, entry)
			}
		}
	}
	return entries, nil
}

func (store *ElasticStore) listDirectoryEntries(
	ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int,
) (entries []*filer.Entry, err error) {
	first := true
	index := getIndex(fullpath)
	nextStart := ""
	parentId := weed_util.Md5String([]byte(fullpath))
	if _, err := store.client.Refresh(index).Do(ctx); err != nil {
		if elastic.IsNotFound(err) {
			store.client.CreateIndex(index).Do(ctx)
			return entries, nil
		}
	}
	for {
		result := &elastic.SearchResult{}
		if (startFileName == "" && first) || inclusive {
			if result, err = store.search(ctx, index, parentId); err != nil {
				glog.Errorf("search (%s,%s,%t,%d) %v.", string(fullpath), startFileName, inclusive, limit, err)
				return entries, err
			}
		} else {
			fullPath := string(fullpath) + "/" + startFileName
			if !first {
				fullPath = nextStart
			}
			after := weed_util.Md5String([]byte(fullPath))
			if result, err = store.searchAfter(ctx, index, parentId, after); err != nil {
				glog.Errorf("searchAfter (%s,%s,%t,%d) %v.", string(fullpath), startFileName, inclusive, limit, err)
				return entries, err
			}
		}
		first = false
		for _, hit := range result.Hits.Hits {
			esEntry := &ESEntry{
				ParentId: "",
				Entry:    &filer.Entry{},
			}
			if err := jsoniter.Unmarshal(hit.Source, esEntry); err == nil {
				limit--
				if limit < 0 {
					return entries, nil
				}
				nextStart = string(esEntry.Entry.FullPath)
				fileName := getFileName(esEntry.Entry.FullPath)
				if fileName == startFileName && !inclusive {
					continue
				}
				entries = append(entries, esEntry.Entry)
			}
		}
		if len(result.Hits.Hits) < store.maxPageSize {
			break
		}
	}
	return entries, nil
}

func (store *ElasticStore) search(ctx context.Context, index, parentId string) (result *elastic.SearchResult, err error) {
	if count, err := store.client.Count(index).Do(ctx); err == nil && count == 0 {
		return &elastic.SearchResult{
			Hits: &elastic.SearchHits{
				Hits: make([]*elastic.SearchHit, 0)},
		}, nil
	}
	queryResult, err := store.client.Search().
		Index(index).
		Query(elastic.NewMatchQuery("ParentId", parentId)).
		Size(store.maxPageSize).
		Sort("_id", false).
		Do(ctx)
	return queryResult, err
}

func (store *ElasticStore) searchAfter(ctx context.Context, index, parentId, after string) (result *elastic.SearchResult, err error) {
	queryResult, err := store.client.Search().
		Index(index).
		Query(elastic.NewMatchQuery("ParentId", parentId)).
		SearchAfter(after).
		Size(store.maxPageSize).
		Sort("_id", false).
		Do(ctx)
	return queryResult, err

}

func (store *ElasticStore) Shutdown() {
	store.client.Stop()
}

func getIndex(fullpath weed_util.FullPath) string {
	path := strings.Split(string(fullpath), "/")
	if len(path) > 1 {
		return indexPrefix + path[1]
	}
	return ""
}

func getFileName(fullpath weed_util.FullPath) string {
	path := strings.Split(string(fullpath), "/")
	if len(path) > 1 {
		return path[len(path)-1]
	}
	return ""
}
<commit_msg>implement elastic changes<commit_after>package elastic

import (
	"context"
	"fmt"
	"math"
	"strings"

	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	weed_util "github.com/chrislusf/seaweedfs/weed/util"
	jsoniter "github.com/json-iterator/go"
	elastic "github.com/olivere/elastic/v7"
)

var (
	indexType   = "_doc"
	indexPrefix = ".seaweedfs_"
	indexKV     = ".seaweedfs_kv_entries"
	kvMappings  = ` {
		     "mappings": {
		   	"enabled": false,
		       "properties": {
		         "Value":{
		           "type": "binary"
		         }
		       }
		     }
		   }`
)

type ESEntry struct {
	ParentId string `json:"ParentId"`
	Entry    *filer.Entry
}

type ESKVEntry struct {
	Value []byte `json:"Value"`
}

func init() {
	filer.Stores = append(filer.Stores, &ElasticStore{})
}

type ElasticStore struct {
	client      *elastic.Client
	maxPageSize int
}

func (store *ElasticStore) GetName() string {
	return "elastic7"
}

func (store *ElasticStore) Initialize(configuration weed_util.Configuration, prefix string) (err error) {
	options := []elastic.ClientOptionFunc{}
	servers := configuration.GetStringSlice(prefix + "servers")
	options = append(options, elastic.SetURL(servers...))
	username := configuration.GetString(prefix + "username")
	password := configuration.GetString(prefix + "password")
	if username != "" && password != "" {
		options = append(options, elastic.SetBasicAuth(username, password))
	}
	options = append(options, elastic.SetSniff(configuration.GetBool(prefix+"sniff_enabled")))
	options = append(options, elastic.SetHealthcheck(configuration.GetBool(prefix+"healthcheck_enabled")))
	store.maxPageSize = configuration.GetInt(prefix + "index.max_result_window")
	if store.maxPageSize <= 0 {
		store.maxPageSize = 10000
	}
	glog.Infof("filer store elastic endpoints: %v.", servers)
	return store.initialize(options)
}

func (store *ElasticStore) initialize(options []elastic.ClientOptionFunc) (err error) {
	ctx := context.Background()
	store.client, err = elastic.NewClient(options...)
	if err != nil {
		return fmt.Errorf("init elastic %v.", err)
	}
	if ok, err := store.client.IndexExists(indexKV).Do(ctx); err == nil && !ok {
		_, err = store.client.CreateIndex(indexKV).Body(kvMappings).Do(ctx)
		if err != nil {
			return fmt.Errorf("create index(%s) %v.", indexKV, err)
		}
	}
	return nil
}

func (store *ElasticStore) BeginTransaction(ctx context.Context) (context.Context, error) {
	return ctx, nil
}
func (store *ElasticStore) CommitTransaction(ctx context.Context) error {
	return nil
}
func (store *ElasticStore) RollbackTransaction(ctx context.Context) error {
	return nil
}

func (store *ElasticStore) ListDirectoryPrefixedEntries(ctx context.Context, dirPath weed_util.FullPath, startFileName string, includeStartFile bool, limit int, prefix string) (entries []*filer.Entry, hasMore bool, err error) {
	return nil, false, filer.ErrUnsupportedListDirectoryPrefixed
}

func (store *ElasticStore) InsertEntry(ctx context.Context, entry *filer.Entry) (err error) {
	index := getIndex(entry.FullPath)
	dir, _ := entry.FullPath.DirAndName()
	id := weed_util.Md5String([]byte(entry.FullPath))
	esEntry := &ESEntry{
		ParentId: weed_util.Md5String([]byte(dir)),
		Entry:    entry,
	}
	value, err := jsoniter.Marshal(esEntry)
	if err != nil {
		glog.Errorf("insert entry(%s) %v.", string(entry.FullPath), err)
		return fmt.Errorf("insert entry %v.", err)
	}
	_, err = store.client.Index().
		Index(index).
		Type(indexType).
		Id(id).
		BodyJson(string(value)).
		Do(ctx)
	if err != nil {
		glog.Errorf("insert entry(%s) %v.", string(entry.FullPath), err)
		return fmt.Errorf("insert entry %v.", err)
	}
	return nil
}

func (store *ElasticStore) UpdateEntry(ctx context.Context, entry *filer.Entry) (err error) {
	return store.InsertEntry(ctx, entry)
}

func (store *ElasticStore) FindEntry(ctx context.Context, fullpath weed_util.FullPath) (entry *filer.Entry, err error) {
	index := getIndex(fullpath)
	id := weed_util.Md5String([]byte(fullpath))
	searchResult, err := store.client.Get().
		Index(index).
		Type(indexType).
		Id(id).
		Do(ctx)
	if elastic.IsNotFound(err) {
		return nil, filer_pb.ErrNotFound
	}
	if searchResult != nil && searchResult.Found {
		esEntry := &ESEntry{
			ParentId: "",
			Entry:    &filer.Entry{},
		}
		err := jsoniter.Unmarshal(searchResult.Source, esEntry)
		return esEntry.Entry, err
	}
	glog.Errorf("find entry(%s),%v.", string(fullpath), err)
	return nil, filer_pb.ErrNotFound
}

func (store *ElasticStore) DeleteEntry(ctx context.Context, fullpath weed_util.FullPath) (err error) {
	index := getIndex(fullpath)
	id := weed_util.Md5String([]byte(fullpath))
	if strings.Count(string(fullpath), "/") == 1 {
		return store.deleteIndex(ctx, index)
	}
	return store.deleteEntry(ctx, index, id)
}

func (store *ElasticStore) deleteIndex(ctx context.Context, index string) (err error) {
	deleteResult, err := store.client.DeleteIndex(index).Do(ctx)
	if elastic.IsNotFound(err) || (err == nil && deleteResult.Acknowledged) {
		return nil
	}
	glog.Errorf("delete index(%s) %v.", index, err)
	return err
}

func (store *ElasticStore) deleteEntry(ctx context.Context, index, id string) (err error) {
	deleteResult, err := store.client.Delete().
		Index(index).
		Type(indexType).
		Id(id).
		Do(ctx)
	if err == nil {
		if deleteResult.Result == "deleted" || deleteResult.Result == "not_found" {
			return nil
		}
	}
	glog.Errorf("delete entry(index:%s,_id:%s) %v.", index, id, err)
	return fmt.Errorf("delete entry %v.", err)
}

func (store *ElasticStore) DeleteFolderChildren(ctx context.Context, fullpath weed_util.FullPath) (err error) {
	if entries, _, err := store.ListDirectoryEntries(ctx, fullpath, "", false, math.MaxInt32); err == nil {
		for _, entry := range entries {
			store.DeleteEntry(ctx, entry.FullPath)
		}
	}
	return nil
}

func (store *ElasticStore) ListDirectoryEntries(ctx context.Context, dirPath weed_util.FullPath, startFileName string, includeStartFile bool, limit int) (entries []*filer.Entry, hasMore bool, err error) {
	if string(dirPath) == "/" {
		return store.listRootDirectoryEntries(ctx, startFileName, includeStartFile, limit)
	}
	return store.listDirectoryEntries(ctx, dirPath, startFileName, includeStartFile, limit)
}

func (store *ElasticStore) listRootDirectoryEntries(ctx context.Context, startFileName string, inclusive bool, limit int) (entries []*filer.Entry, hasMore bool, err error) {
	indexResult, err := store.client.CatIndices().Do(ctx)
	if err != nil {
		glog.Errorf("list indices %v.", err)
		return entries, false, err
	}
	for _, index := range indexResult {
		if index.Index == indexKV {
			continue
		}
		if strings.HasPrefix(index.Index, indexPrefix) {
			if entry, err := store.FindEntry(ctx,
				weed_util.FullPath("/"+strings.Replace(index.Index, indexPrefix, "", 1))); err == nil {
				fileName := getFileName(entry.FullPath)
				if fileName == startFileName && !inclusive {
					continue
				}
				limit--
				if limit < 0 {
					hasMore = true
					break
				}
				entries = append(entries, entry)
			}
		}
	}
	return entries, hasMore, nil
}

func (store *ElasticStore) listDirectoryEntries(
	ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int,
) (entries []*filer.Entry, hasMore bool, err error) {
	first := true
	index := getIndex(fullpath)
	nextStart := ""
	parentId := weed_util.Md5String([]byte(fullpath))
	if _, err := store.client.Refresh(index).Do(ctx); err != nil {
		if elastic.IsNotFound(err) {
			store.client.CreateIndex(index).Do(ctx)
			return entries, hasMore, nil
		}
	}
	for {
		result := &elastic.SearchResult{}
		if (startFileName == "" && first) || inclusive {
			if result, err = store.search(ctx, index, parentId); err != nil {
				glog.Errorf("search (%s,%s,%t,%d) %v.", string(fullpath), startFileName, inclusive, limit, err)
				return entries, hasMore, err
			}
		} else {
			fullPath := string(fullpath) + "/" + startFileName
			if !first {
				fullPath = nextStart
			}
			after := weed_util.Md5String([]byte(fullPath))
			if result, err = store.searchAfter(ctx, index, parentId, after); err != nil {
				glog.Errorf("searchAfter (%s,%s,%t,%d) %v.", string(fullpath), startFileName, inclusive, limit, err)
				return entries, hasMore, err
			}
		}
		first = false
		for _, hit := range result.Hits.Hits {
			esEntry := &ESEntry{
				ParentId: "",
				Entry:    &filer.Entry{},
			}
			if err := jsoniter.Unmarshal(hit.Source, esEntry); err == nil {
				limit--
				if limit < 0 {
					hasMore = true
					return entries, hasMore, nil
				}
				nextStart = string(esEntry.Entry.FullPath)
				fileName := getFileName(esEntry.Entry.FullPath)
				if fileName == startFileName && !inclusive {
					continue
				}
				entries = append(entries, esEntry.Entry)
			}
		}
		if len(result.Hits.Hits) < store.maxPageSize {
			break
		}
	}
	return entries, hasMore, nil
}

func (store *ElasticStore) search(ctx context.Context, index, parentId string) (result *elastic.SearchResult, err error) {
	if count, err := store.client.Count(index).Do(ctx); err == nil && count == 0 {
		return &elastic.SearchResult{
			Hits: &elastic.SearchHits{
				Hits: make([]*elastic.SearchHit, 0)},
		}, nil
	}
	queryResult, err := store.client.Search().
		Index(index).
		Query(elastic.NewMatchQuery("ParentId", parentId)).
		Size(store.maxPageSize).
		Sort("_id", false).
		Do(ctx)
	return queryResult, err
}

func (store *ElasticStore) searchAfter(ctx context.Context, index, parentId, after string) (result *elastic.SearchResult, err error) {
	queryResult, err := store.client.Search().
		Index(index).
		Query(elastic.NewMatchQuery("ParentId", parentId)).
		SearchAfter(after).
		Size(store.maxPageSize).
		Sort("_id", false).
		Do(ctx)
	return queryResult, err

}

func (store *ElasticStore) Shutdown() {
	store.client.Stop()
}

func getIndex(fullpath weed_util.FullPath) string {
	path := strings.Split(string(fullpath), "/")
	if len(path) > 1 {
		return indexPrefix + path[1]
	}
	return ""
}

func getFileName(fullpath weed_util.FullPath) string {
	path := strings.Split(string(fullpath), "/")
	if len(path) > 1 {
		return path[len(path)-1]
	}
	return ""
}
<|endoftext|>


<commit_before>package editor

import (
	"strings"

	parser "github.com/elpinal/coco3/editor/commandline"
	"github.com/elpinal/coco3/screen"
)

type exCommand struct {
	name string
	fn   func(*commandline, []parser.Token) continuity
}

// exComands represents a table of Ex commands and corresponding functions.
// The order is important. Preceding commands have higher precedence.
var exCommands = []exCommand{
	{"help", (*commandline).help},
	{"delete", (*commandline).delete},
	{"quit", (*commandline).quit},
	{"substitute", (*commandline).substitute},
}

type commandline struct {
	streamSet
	*editor

	basic *basic

	// FIXME: currently live a moment.
	history [][]rune
	age     int
}

func emptyCommandline() *commandline {
	return &commandline{
		editor: newEditor(),
		basic:  &basic{},
	}
}

func newCommandline(s streamSet, e *editor) *commandline {
	return &commandline{
		streamSet: s,
		editor:    e,
		basic:     &basic{},
	}
}

func (e *commandline) Mode() mode {
	return modeCommandline
}

func (e *commandline) Position() int {
	return e.basic.pos + 1
}

func (e *commandline) Runes() []rune {
	return e.buf
}

func (e *commandline) Message() []rune {
	return append([]rune{':'}, e.basic.buf...)
}

func (e *commandline) Highlight() *screen.Hi {
	return nil
}

func (e *commandline) Run() (end continuity, next modeChanger, err error) {
	r, _, err := e.in.ReadRune()
	if err != nil {
		return end, next, err
	}
	switch r {
	case CharCtrlM, CharCtrlJ:
		end, err = e.execute()
		next = norm()

	case CharEscape, CharCtrlC:
		next = norm()

	case CharBackspace, CharCtrlH:
		if len(e.basic.buf) == 0 {
			next = norm()
			return
		}
		e.basic.delete(e.basic.pos-1, e.basic.pos)

	case CharCtrlB:
		e.basic.move(0)
	case CharCtrlE:
		e.basic.move(len(e.basic.buf))
	case CharCtrlN:
		e.historyForward()
	case CharCtrlP:
		e.historyBack()
	case CharCtrlU:
		e.basic.delete(0, e.basic.pos)
	case CharCtrlW:
		pos := e.basic.pos
		e.basic.wordBackward()
		e.basic.delete(pos, e.basic.pos)
	default:
		e.basic.insert([]rune{r}, e.basic.pos)
	}
	return
}

type ErrNoExCommand struct {
	Name string
}

func (e *ErrNoExCommand) Error() string {
	return "no such Ex command: " + e.Name
}

func (e *commandline) execute() (end continuity, err error) {
	command, err := parser.ParseT(string(e.basic.buf))
	if err != nil {
		return cont, err
	}
	if command == nil {
		return
	}
	name := string(command.Name)
	defer func() {
		e.history = append(e.history, e.basic.buf)
	}()
	var candidate exCommand
	for _, cmd := range exCommands {
		if !strings.HasPrefix(cmd.name, name) {
			continue
		}
		if cmd.name == name {
			end = cmd.fn(e, command.Args)
			return
		}
		if candidate.name == "" {
			candidate = cmd
		}
	}
	if candidate.name != "" {
		end = candidate.fn(e, command.Args)
		return
	}
	err = &ErrNoExCommand{Name: name}
	return
}

func (e *commandline) historyBack() {
	l := len(e.history)
	if e.age == l {
		return
	}
	e.age++
	e.basic.buf = e.history[l-e.age]
}

func (e *commandline) historyForward() {
	l := len(e.history)
	if e.age == 0 {
		return
	}
	e.age--
	e.basic.buf = e.history[l-e.age]
}

func (e *commandline) quit(_ []parser.Token) continuity {
	return exit
}

func (e *commandline) delete(_ []parser.Token) (_ continuity) {
	e.editor.delete(0, len(e.editor.buf))
	return
}

func (e *commandline) help(_ []parser.Token) continuity {
	e.buf = []rune("help")
	e.pos = 4
	return execute
}

func (e *commandline) substitute(args []parser.Token) (_ continuity) {
	if len(args) != 2 {
		return
	}
	pat := toString(args[0])
	s0 := toString(args[1])
	s := strings.Replace(string(e.buf), string(pat), string(s0), -1)
	e.buf = []rune(s)
	return
}

func toString(t parser.Token) string {
	switch t.Type {
	case parser.TokenIdent:
	case parser.TokenString:
		return unquote(string(t.Value))
	}
	return string(t.Value)
}

func unquote(s string) string {
	return strings.TrimSuffix(strings.TrimPrefix(s, `"`), `"`)
}
<commit_msg>Add FIXME comment for :substitute<commit_after>package editor

import (
	"strings"

	parser "github.com/elpinal/coco3/editor/commandline"
	"github.com/elpinal/coco3/screen"
)

type exCommand struct {
	name string
	fn   func(*commandline, []parser.Token) continuity
}

// exComands represents a table of Ex commands and corresponding functions.
// The order is important. Preceding commands have higher precedence.
var exCommands = []exCommand{
	{"help", (*commandline).help},
	{"delete", (*commandline).delete},
	{"quit", (*commandline).quit},
	{"substitute", (*commandline).substitute},
}

type commandline struct {
	streamSet
	*editor

	basic *basic

	// FIXME: currently live a moment.
	history [][]rune
	age     int
}

func emptyCommandline() *commandline {
	return &commandline{
		editor: newEditor(),
		basic:  &basic{},
	}
}

func newCommandline(s streamSet, e *editor) *commandline {
	return &commandline{
		streamSet: s,
		editor:    e,
		basic:     &basic{},
	}
}

func (e *commandline) Mode() mode {
	return modeCommandline
}

func (e *commandline) Position() int {
	return e.basic.pos + 1
}

func (e *commandline) Runes() []rune {
	return e.buf
}

func (e *commandline) Message() []rune {
	return append([]rune{':'}, e.basic.buf...)
}

func (e *commandline) Highlight() *screen.Hi {
	return nil
}

func (e *commandline) Run() (end continuity, next modeChanger, err error) {
	r, _, err := e.in.ReadRune()
	if err != nil {
		return end, next, err
	}
	switch r {
	case CharCtrlM, CharCtrlJ:
		end, err = e.execute()
		next = norm()

	case CharEscape, CharCtrlC:
		next = norm()

	case CharBackspace, CharCtrlH:
		if len(e.basic.buf) == 0 {
			next = norm()
			return
		}
		e.basic.delete(e.basic.pos-1, e.basic.pos)

	case CharCtrlB:
		e.basic.move(0)
	case CharCtrlE:
		e.basic.move(len(e.basic.buf))
	case CharCtrlN:
		e.historyForward()
	case CharCtrlP:
		e.historyBack()
	case CharCtrlU:
		e.basic.delete(0, e.basic.pos)
	case CharCtrlW:
		pos := e.basic.pos
		e.basic.wordBackward()
		e.basic.delete(pos, e.basic.pos)
	default:
		e.basic.insert([]rune{r}, e.basic.pos)
	}
	return
}

type ErrNoExCommand struct {
	Name string
}

func (e *ErrNoExCommand) Error() string {
	return "no such Ex command: " + e.Name
}

func (e *commandline) execute() (end continuity, err error) {
	command, err := parser.ParseT(string(e.basic.buf))
	if err != nil {
		return cont, err
	}
	if command == nil {
		return
	}
	name := string(command.Name)
	defer func() {
		e.history = append(e.history, e.basic.buf)
	}()
	var candidate exCommand
	for _, cmd := range exCommands {
		if !strings.HasPrefix(cmd.name, name) {
			continue
		}
		if cmd.name == name {
			end = cmd.fn(e, command.Args)
			return
		}
		if candidate.name == "" {
			candidate = cmd
		}
	}
	if candidate.name != "" {
		end = candidate.fn(e, command.Args)
		return
	}
	err = &ErrNoExCommand{Name: name}
	return
}

func (e *commandline) historyBack() {
	l := len(e.history)
	if e.age == l {
		return
	}
	e.age++
	e.basic.buf = e.history[l-e.age]
}

func (e *commandline) historyForward() {
	l := len(e.history)
	if e.age == 0 {
		return
	}
	e.age--
	e.basic.buf = e.history[l-e.age]
}

func (e *commandline) quit(_ []parser.Token) continuity {
	return exit
}

func (e *commandline) delete(_ []parser.Token) (_ continuity) {
	e.editor.delete(0, len(e.editor.buf))
	return
}

func (e *commandline) help(_ []parser.Token) continuity {
	e.buf = []rune("help")
	e.pos = 4
	return execute
}

func (e *commandline) substitute(args []parser.Token) (_ continuity) {
	if len(args) != 2 {
		return
	}
	pat := toString(args[0])
	s0 := toString(args[1])
	s := strings.Replace(string(e.buf), string(pat), string(s0), -1)
	e.buf = []rune(s)
	// FIXME: e.pos should be changed according to the substitution.
	return
}

func toString(t parser.Token) string {
	switch t.Type {
	case parser.TokenIdent:
	case parser.TokenString:
		return unquote(string(t.Value))
	}
	return string(t.Value)
}

func unquote(s string) string {
	return strings.TrimSuffix(strings.TrimPrefix(s, `"`), `"`)
}
<|endoftext|>


<commit_before><commit_msg>update cert put endpoint api<commit_after><|endoftext|>


<commit_before><commit_msg>whoops, had an unused import<commit_after><|endoftext|>


<commit_before><commit_msg>update dijkstra<commit_after><|endoftext|>


<commit_before><commit_msg>Rename process from director -> sphere-director<commit_after><|endoftext|>


<commit_before><commit_msg>add better error messages for target loads<commit_after><|endoftext|>


<commit_before>package richtext

import (
	"image"
	"time"
	"unicode/utf8"

	"github.com/aarzilli/nucular"
	"github.com/aarzilli/nucular/clipboard"
	"github.com/aarzilli/nucular/label"
	"github.com/aarzilli/nucular/rect"

	"golang.org/x/image/math/fixed"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"
)

func (rtxt *RichText) handleClick(w *nucular.Window, r rect.Rect, in *nucular.Input, styleSel styleSel, line *line, chunkIdx int, hovering *bool, linkClick *int32) {
	if rtxt.flags&Selectable == 0 && !styleSel.isLink && rtxt.flags&Clipboard == 0 && styleSel.Tooltip == nil && rtxt.flags&Keyboard == 0 {
		return
	}

	if rtxt.flags&Clipboard != 0 && r.W > 0 && r.H > 0 {
		fn := styleSel.ContextMenu
		if fn == nil {
			fn = func(w *nucular.Window) {
				w.Row(20).Dynamic(1)
				if w.MenuItem(label.TA("Copy", "LC")) {
					clipboard.Set(rtxt.Get(rtxt.Sel))
					w.Close()
				}
			}
		}
		if w := w.ContextualOpen(0, image.Point{}, r, fn); w != nil {
			rtxt.focused = true
		}
	}

	oldSel := rtxt.Sel

	if rtxt.down {
		rtxt.focused = true
		if in.Mouse.HoveringRect(r) {
			if !in.Mouse.Down(mouse.ButtonLeft) {
				if rtxt.isClick && styleSel.isLink && in.Mouse.HoveringRect(r) {
					if styleSel.link != nil {
						styleSel.link()
					} else if linkClick != nil {
						*linkClick = line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
					}
				}
				rtxt.down = false
				rtxt.isClick = false
			} else {
				q := line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
				if q < rtxt.dragStart {
					rtxt.Sel.S = q
					rtxt.Sel.E = rtxt.dragStart
				} else {
					rtxt.Sel.S = rtxt.dragStart
					rtxt.Sel.E = q
				}
				rtxt.expandSelection()
				if q != rtxt.dragStart {
					rtxt.isClick = false
				}
			}
		}
	} else {
		if in.Mouse.Down(mouse.ButtonLeft) && in.Mouse.HoveringRect(r) {
			rtxt.focused = true
			q := line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
			if time.Since(rtxt.lastClickTime) < 200*time.Millisecond && q == rtxt.dragStart {
				rtxt.clickCount++
			} else {
				rtxt.clickCount = 1
			}
			if rtxt.clickCount > 3 {
				rtxt.clickCount = 3
			}
			rtxt.lastClickTime = time.Now()
			rtxt.dragStart = q
			rtxt.Sel.S = rtxt.dragStart
			rtxt.Sel.E = rtxt.Sel.S
			rtxt.expandSelection()
			rtxt.down = true
			rtxt.isClick = true
		}
		if (styleSel.isLink || styleSel.Tooltip != nil) && hovering != nil && in.Mouse.HoveringRect(r) {
			*hovering = true
		}
	}

	if rtxt.flags&Selectable == 0 {
		rtxt.Sel = oldSel
	}
	return
}

func (rtxt *RichText) expandSelection() {
	switch rtxt.clickCount {
	case 2:
		sline := rtxt.findLine(rtxt.Sel.S)
		eline := rtxt.findLine(rtxt.Sel.E)

		var citer citer
		for citer.Init(sline, rtxt.Sel.S); citer.Valid(); citer.Prev() {
			if citer.Char() == ' ' {
				citer.Next()
				break
			}

		}
		rtxt.Sel.S = citer.off

		for citer.Init(eline, rtxt.Sel.E); citer.Valid(); citer.Next() {
			if citer.Char() == ' ' {
				break
			}
		}
		rtxt.Sel.E = citer.off
	case 3:
		sline := rtxt.findLine(rtxt.Sel.S)
		eline := rtxt.findLine(rtxt.Sel.E)
		if len(sline.off) > 0 {
			rtxt.Sel.S = sline.off[0]
			rtxt.Sel.E = eline.endoff()
		}
	}
}

func (rtxt *RichText) findLine(q int32) line {
	for _, line := range rtxt.lines {
		if len(line.off) <= 0 {
			continue
		}
		if line.sel().contains(q) {
			return line
		}
	}
	return rtxt.lines[len(rtxt.lines)-1]
}

func (line line) coordToIndex(p image.Point, chunkIdx int, adv []fixed.Int26_6) int32 {
	advance, runeoff := line.chunkAdvance(chunkIdx)
	if len(line.chunks) == 0 {
		return line.off[0]
	}
	chunk := line.chunks[chunkIdx]

	x := advance + line.leftMargin + line.p.X

	w := fixed.I(0)

	off := line.off[chunkIdx]
	for chunk.len() > 0 {
		w += adv[runeoff]

		if x+w.Ceil() > p.X {
			return off
		}

		var rsz int
		if chunk.b != nil {
			_, rsz = utf8.DecodeRune(chunk.b)
			chunk.b = chunk.b[rsz:]
		} else {
			_, rsz = utf8.DecodeRuneInString(chunk.s)
			chunk.s = chunk.s[rsz:]
		}
		off += int32(rsz)
		runeoff++
	}

	return off
}

type citer struct {
	valid bool
	off   int32
	line  line
	i, j  int
}

func (citer *citer) Init(line line, off int32) {
	citer.valid = true
	citer.off = off
	citer.line = line
	found := false
	for i := range citer.line.chunks {
		if citer.line.off[i] <= off && off < citer.line.off[i]+citer.line.chunks[i].len() {
			citer.i = i
			citer.j = int(off - citer.line.off[i])
			found = true
			break
		}
	}
	if !found {
		citer.i = len(citer.line.chunks)
		citer.j = 0
		citer.valid = false
	}
	if len(citer.line.chunks) <= 0 {
		citer.valid = false
	}
}

func (citer *citer) Valid() bool {
	return citer.valid
}

func (citer *citer) Char() byte {
	chunk := citer.line.chunks[citer.i]
	if chunk.b != nil {
		return chunk.b[citer.j]
	}
	return chunk.s[citer.j]
}

func (citer *citer) Prev() {
	citer.j--
	citer.off--
	if citer.j < 0 {
		citer.i--
		if citer.i < 0 {
			citer.i = 0
			citer.off++
			citer.valid = false
		}
	}
}

func (citer *citer) Next() {
	citer.j++
	citer.off++
	for citer.j >= int(citer.line.chunks[citer.i].len()) {
		citer.j = 0
		citer.i++
		if citer.i >= len(citer.line.chunks) {
			citer.valid = false
			return
		}
	}
}

func (rtxt *RichText) handleKeyboard(in *nucular.Input) (arrowKey, pageKey int) {
	if !rtxt.focused || rtxt.flags&Keyboard == 0 {
		return
	}

	for _, k := range in.Keyboard.Keys {
		switch {
		case k.Modifiers == key.ModControl && k.Code == key.CodeC:
			if rtxt.flags&Clipboard != 0 {
				clipboard.Set(rtxt.Get(rtxt.Sel))
			}
		case k.Code == key.CodeUpArrow:
			return -1, 0
		case k.Code == key.CodeDownArrow:
			return +1, 0
		case k.Code == key.CodePageDown:
			return 0, +1
		case k.Code == key.CodePageUp:
			return 0, -1
		}
	}

	return 0, 0
}
<commit_msg>richtext: fix array index out of bounds on double click<commit_after>package richtext

import (
	"image"
	"time"
	"unicode/utf8"

	"github.com/aarzilli/nucular"
	"github.com/aarzilli/nucular/clipboard"
	"github.com/aarzilli/nucular/label"
	"github.com/aarzilli/nucular/rect"

	"golang.org/x/image/math/fixed"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"
)

func (rtxt *RichText) handleClick(w *nucular.Window, r rect.Rect, in *nucular.Input, styleSel styleSel, line *line, chunkIdx int, hovering *bool, linkClick *int32) {
	if rtxt.flags&Selectable == 0 && !styleSel.isLink && rtxt.flags&Clipboard == 0 && styleSel.Tooltip == nil && rtxt.flags&Keyboard == 0 {
		return
	}

	if rtxt.flags&Clipboard != 0 && r.W > 0 && r.H > 0 {
		fn := styleSel.ContextMenu
		if fn == nil {
			fn = func(w *nucular.Window) {
				w.Row(20).Dynamic(1)
				if w.MenuItem(label.TA("Copy", "LC")) {
					clipboard.Set(rtxt.Get(rtxt.Sel))
					w.Close()
				}
			}
		}
		if w := w.ContextualOpen(0, image.Point{}, r, fn); w != nil {
			rtxt.focused = true
		}
	}

	oldSel := rtxt.Sel

	if rtxt.down {
		rtxt.focused = true
		if in.Mouse.HoveringRect(r) {
			if !in.Mouse.Down(mouse.ButtonLeft) {
				if rtxt.isClick && styleSel.isLink && in.Mouse.HoveringRect(r) {
					if styleSel.link != nil {
						styleSel.link()
					} else if linkClick != nil {
						*linkClick = line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
					}
				}
				rtxt.down = false
				rtxt.isClick = false
			} else {
				q := line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
				if q < rtxt.dragStart {
					rtxt.Sel.S = q
					rtxt.Sel.E = rtxt.dragStart
				} else {
					rtxt.Sel.S = rtxt.dragStart
					rtxt.Sel.E = q
				}
				rtxt.expandSelection()
				if q != rtxt.dragStart {
					rtxt.isClick = false
				}
			}
		}
	} else {
		if in.Mouse.Down(mouse.ButtonLeft) && in.Mouse.HoveringRect(r) {
			rtxt.focused = true
			q := line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
			if time.Since(rtxt.lastClickTime) < 200*time.Millisecond && q == rtxt.dragStart {
				rtxt.clickCount++
			} else {
				rtxt.clickCount = 1
			}
			if rtxt.clickCount > 3 {
				rtxt.clickCount = 3
			}
			rtxt.lastClickTime = time.Now()
			rtxt.dragStart = q
			rtxt.Sel.S = rtxt.dragStart
			rtxt.Sel.E = rtxt.Sel.S
			rtxt.expandSelection()
			rtxt.down = true
			rtxt.isClick = true
		}
		if (styleSel.isLink || styleSel.Tooltip != nil) && hovering != nil && in.Mouse.HoveringRect(r) {
			*hovering = true
		}
	}

	if rtxt.flags&Selectable == 0 {
		rtxt.Sel = oldSel
	}
	return
}

func (rtxt *RichText) expandSelection() {
	switch rtxt.clickCount {
	case 2:
		sline := rtxt.findLine(rtxt.Sel.S)
		eline := rtxt.findLine(rtxt.Sel.E)

		var citer citer
		for citer.Init(sline, rtxt.Sel.S); citer.Valid(); citer.Prev() {
			if citer.Char() == ' ' {
				citer.Next()
				break
			}

		}
		rtxt.Sel.S = citer.off

		for citer.Init(eline, rtxt.Sel.E); citer.Valid(); citer.Next() {
			if citer.Char() == ' ' {
				break
			}
		}
		rtxt.Sel.E = citer.off
	case 3:
		sline := rtxt.findLine(rtxt.Sel.S)
		eline := rtxt.findLine(rtxt.Sel.E)
		if len(sline.off) > 0 {
			rtxt.Sel.S = sline.off[0]
			rtxt.Sel.E = eline.endoff()
		}
	}
}

func (rtxt *RichText) findLine(q int32) line {
	for _, line := range rtxt.lines {
		if len(line.off) <= 0 {
			continue
		}
		if line.sel().contains(q) {
			return line
		}
	}
	return rtxt.lines[len(rtxt.lines)-1]
}

func (line line) coordToIndex(p image.Point, chunkIdx int, adv []fixed.Int26_6) int32 {
	advance, runeoff := line.chunkAdvance(chunkIdx)
	if len(line.chunks) == 0 {
		return line.off[0]
	}
	chunk := line.chunks[chunkIdx]

	x := advance + line.leftMargin + line.p.X

	w := fixed.I(0)

	off := line.off[chunkIdx]
	for chunk.len() > 0 {
		w += adv[runeoff]

		if x+w.Ceil() > p.X {
			return off
		}

		var rsz int
		if chunk.b != nil {
			_, rsz = utf8.DecodeRune(chunk.b)
			chunk.b = chunk.b[rsz:]
		} else {
			_, rsz = utf8.DecodeRuneInString(chunk.s)
			chunk.s = chunk.s[rsz:]
		}
		off += int32(rsz)
		runeoff++
	}

	return off
}

type citer struct {
	valid bool
	off   int32
	line  line
	i, j  int
}

func (citer *citer) Init(line line, off int32) {
	citer.valid = true
	citer.off = off
	citer.line = line
	found := false
	for i := range citer.line.chunks {
		if citer.line.off[i] <= off && off < citer.line.off[i]+citer.line.chunks[i].len() {
			citer.i = i
			citer.j = int(off - citer.line.off[i])
			found = true
			break
		}
	}
	if !found {
		citer.i = len(citer.line.chunks)
		citer.j = 0
		citer.valid = false
	}
	if len(citer.line.chunks) <= 0 {
		citer.valid = false
	}
}

func (citer *citer) Valid() bool {
	if !citer.valid {
		return false
	}
	if citer.i < 0 || citer.i >= len(citer.line.chunks) {
		return false
	}
	chunk := citer.line.chunks[citer.i]
	if citer.j < 0 {
		return false
	}
	if chunk.b != nil {
		return citer.j < len(chunk.b)
	}
	return citer.j < len(chunk.s)
}

func (citer *citer) Char() byte {
	chunk := citer.line.chunks[citer.i]
	if chunk.b != nil {
		return chunk.b[citer.j]
	}
	return chunk.s[citer.j]
}

func (citer *citer) Prev() {
	citer.j--
	citer.off--
	if citer.j < 0 {
		citer.i--
		if citer.i < 0 {
			citer.i = 0
			citer.off++
			citer.valid = false
		} else {
			chunk := citer.line.chunks[citer.i]
			if chunk.b != nil {
				citer.j = len(chunk.b) - 1
			} else {
				citer.j = len(chunk.s) - 1
			}
		}
	}
}

func (citer *citer) Next() {
	citer.j++
	citer.off++
	for citer.j >= int(citer.line.chunks[citer.i].len()) {
		citer.j = 0
		citer.i++
		if citer.i >= len(citer.line.chunks) {
			citer.valid = false
			return
		}
	}
}

func (rtxt *RichText) handleKeyboard(in *nucular.Input) (arrowKey, pageKey int) {
	if !rtxt.focused || rtxt.flags&Keyboard == 0 {
		return
	}

	for _, k := range in.Keyboard.Keys {
		switch {
		case k.Modifiers == key.ModControl && k.Code == key.CodeC:
			if rtxt.flags&Clipboard != 0 {
				clipboard.Set(rtxt.Get(rtxt.Sel))
			}
		case k.Code == key.CodeUpArrow:
			return -1, 0
		case k.Code == key.CodeDownArrow:
			return +1, 0
		case k.Code == key.CodePageDown:
			return 0, +1
		case k.Code == key.CodePageUp:
			return 0, -1
		}
	}

	return 0, 0
}
<|endoftext|>


<commit_before><commit_msg>refactor tests<commit_after><|endoftext|>